foxclaw 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (495) hide show
  1. package/README.md +54 -29
  2. package/bin/foxclaw.cjs +51 -0
  3. package/index.js +11 -10
  4. package/package.json +46 -38
  5. package/python/.dockerignore +35 -0
  6. package/python/.env.example +165 -0
  7. package/python/.gitattributes +46 -0
  8. package/python/.github/workflows/ci.yml +122 -0
  9. package/python/.github/workflows/release.yml +183 -0
  10. package/python/Dockerfile +64 -0
  11. package/python/LICENSE +21 -0
  12. package/python/MANIFEST.in +14 -0
  13. package/python/README.md +192 -0
  14. package/python/README_CN.md +324 -0
  15. package/python/bench/bm25_audit.py +199 -0
  16. package/python/bench/bm25_regression.py +178 -0
  17. package/python/bench/bm25_stress.py +159 -0
  18. package/python/bench/cd_audit.py +238 -0
  19. package/python/bench/chaos.py +142 -0
  20. package/python/bench/concurrency.py +103 -0
  21. package/python/bench/e2e_real_models.py +95 -0
  22. package/python/bench/llm_retry_test.py +248 -0
  23. package/python/bench/load_test.py +196 -0
  24. package/python/bench/vector_ivf_upgrade.py +134 -0
  25. package/python/docs/CHANNELS.md +195 -0
  26. package/python/docs/GOVERNANCE.md +164 -0
  27. package/python/docs/RELEASE.md +109 -0
  28. package/python/docs/RUN.md +75 -0
  29. package/python/docs/RUN_CN.md +121 -0
  30. package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
  31. package/python/evals/__init__.py +0 -0
  32. package/python/evals/governance/README.md +61 -0
  33. package/python/evals/governance/__init__.py +0 -0
  34. package/python/evals/governance/cases.py +152 -0
  35. package/python/evals/governance/compare_embeddings.py +94 -0
  36. package/python/evals/governance/convergence.svg +11 -0
  37. package/python/evals/governance/report.json +371 -0
  38. package/python/evals/governance/run_evals.py +400 -0
  39. package/python/examples/governance_middleware_demo.py +77 -0
  40. package/python/examples/langgraph_governance_demo.py +108 -0
  41. package/python/examples/sample_itinerary.csv +6 -0
  42. package/python/examples/sample_knowledge.csv +9 -0
  43. package/python/examples/sample_transactions.csv +14 -0
  44. package/python/foxclaw/__init__.py +526 -0
  45. package/python/foxclaw/agents/__init__.py +12 -0
  46. package/python/foxclaw/agents/agents.py +35 -0
  47. package/python/foxclaw/agents/base.py +865 -0
  48. package/python/foxclaw/agents/impl/__init__.py +1 -0
  49. package/python/foxclaw/agents/impl/_helpers.py +78 -0
  50. package/python/foxclaw/agents/impl/finance.py +248 -0
  51. package/python/foxclaw/agents/impl/office.py +367 -0
  52. package/python/foxclaw/agents/impl/supervisor.py +659 -0
  53. package/python/foxclaw/agents/mixins/_context.py +151 -0
  54. package/python/foxclaw/agents/mixins/augment.py +149 -0
  55. package/python/foxclaw/agents/mixins/llm.py +66 -0
  56. package/python/foxclaw/agents/mixins/memory.py +35 -0
  57. package/python/foxclaw/agents/mixins/preference.py +81 -0
  58. package/python/foxclaw/agents/mixins/prompt.py +77 -0
  59. package/python/foxclaw/agents/mixins/skillbox.py +58 -0
  60. package/python/foxclaw/agents/mixins/tool_exec.py +443 -0
  61. package/python/foxclaw/api/__init__.py +283 -0
  62. package/python/foxclaw/api/app.py +123 -0
  63. package/python/foxclaw/api/middleware.py +317 -0
  64. package/python/foxclaw/api/routes/ahs.py +417 -0
  65. package/python/foxclaw/api/routes/attachments.py +117 -0
  66. package/python/foxclaw/api/routes/browser.py +39 -0
  67. package/python/foxclaw/api/routes/domain.py +314 -0
  68. package/python/foxclaw/api/routes/knowledge.py +163 -0
  69. package/python/foxclaw/api/routes/llm_admin.py +461 -0
  70. package/python/foxclaw/api/routes/memory.py +89 -0
  71. package/python/foxclaw/api/routes/orchestration.py +41 -0
  72. package/python/foxclaw/api/routes/recovery.py +43 -0
  73. package/python/foxclaw/api/routes/review.py +275 -0
  74. package/python/foxclaw/api/routes/search.py +37 -0
  75. package/python/foxclaw/api/routes/system.py +327 -0
  76. package/python/foxclaw/api/routes/task.py +281 -0
  77. package/python/foxclaw/api/routes/ws.py +68 -0
  78. package/python/foxclaw/api/spine.py +55 -0
  79. package/python/foxclaw/browser/__init__.py +13 -0
  80. package/python/foxclaw/browser/action/__init__.py +1 -0
  81. package/python/foxclaw/browser/action/action_executor.py +54 -0
  82. package/python/foxclaw/browser/action/action_types.py +45 -0
  83. package/python/foxclaw/browser/action/action_verifier.py +68 -0
  84. package/python/foxclaw/browser/action/url_policy.py +147 -0
  85. package/python/foxclaw/browser/adapter/__init__.py +1 -0
  86. package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
  87. package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
  88. package/python/foxclaw/browser/driver/__init__.py +1 -0
  89. package/python/foxclaw/browser/driver/base_driver.py +61 -0
  90. package/python/foxclaw/browser/driver/playwright_driver.py +166 -0
  91. package/python/foxclaw/browser/perception/__init__.py +1 -0
  92. package/python/foxclaw/browser/perception/dom_extractor.py +43 -0
  93. package/python/foxclaw/browser/perception/element_indexer.py +57 -0
  94. package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
  95. package/python/foxclaw/browser/perception/snapshot.py +29 -0
  96. package/python/foxclaw/browser/service.py +148 -0
  97. package/python/foxclaw/browser/session/__init__.py +1 -0
  98. package/python/foxclaw/browser/session/session_manager.py +57 -0
  99. package/python/foxclaw/capability/__init__.py +12 -0
  100. package/python/foxclaw/capability/embedding_service.py +67 -0
  101. package/python/foxclaw/channels/__init__.py +140 -0
  102. package/python/foxclaw/channels/discord.py +184 -0
  103. package/python/foxclaw/channels/dispatcher.py +204 -0
  104. package/python/foxclaw/channels/feishu.py +225 -0
  105. package/python/foxclaw/channels/openclaw_bridge.py +156 -0
  106. package/python/foxclaw/channels/signing.py +187 -0
  107. package/python/foxclaw/channels/telegram.py +121 -0
  108. package/python/foxclaw/channels/weixin.py +225 -0
  109. package/python/foxclaw/cli.py +154 -0
  110. package/python/foxclaw/cognitive/__init__.py +4 -0
  111. package/python/foxclaw/cognitive/reasoner.py +82 -0
  112. package/python/foxclaw/cognitive/state.py +147 -0
  113. package/python/foxclaw/core/__init__.py +9 -0
  114. package/python/foxclaw/core/audit_chain.py +133 -0
  115. package/python/foxclaw/core/audit_sink.py +100 -0
  116. package/python/foxclaw/core/compaction.py +283 -0
  117. package/python/foxclaw/core/config.py +65 -0
  118. package/python/foxclaw/core/confirm_nonce.py +157 -0
  119. package/python/foxclaw/core/dispatch.py +27 -0
  120. package/python/foxclaw/core/envfile.py +48 -0
  121. package/python/foxclaw/core/failure_policy.py +151 -0
  122. package/python/foxclaw/core/gateway.py +85 -0
  123. package/python/foxclaw/core/governance_audit.py +175 -0
  124. package/python/foxclaw/core/idempotency.py +289 -0
  125. package/python/foxclaw/core/injection_guard.py +221 -0
  126. package/python/foxclaw/core/memory_adapter.py +516 -0
  127. package/python/foxclaw/core/metrics.py +89 -0
  128. package/python/foxclaw/core/model_router.py +1097 -0
  129. package/python/foxclaw/core/paths.py +103 -0
  130. package/python/foxclaw/core/recovery.py +362 -0
  131. package/python/foxclaw/core/scheduler.py +144 -0
  132. package/python/foxclaw/core/secrets.py +134 -0
  133. package/python/foxclaw/core/sessions.py +157 -0
  134. package/python/foxclaw/core/settings.py +430 -0
  135. package/python/foxclaw/core/shared_write.py +198 -0
  136. package/python/foxclaw/core/steering_queue.py +153 -0
  137. package/python/foxclaw/core/system_bus.py +84 -0
  138. package/python/foxclaw/core/thought_chain.py +318 -0
  139. package/python/foxclaw/core/token_meter.py +235 -0
  140. package/python/foxclaw/core/tools.py +172 -0
  141. package/python/foxclaw/core/tracing.py +390 -0
  142. package/python/foxclaw/core/truth_pipeline.py +114 -0
  143. package/python/foxclaw/core/vec_recall_adapter.py +503 -0
  144. package/python/foxclaw/core/verify.py +181 -0
  145. package/python/foxclaw/core/web_search.py +242 -0
  146. package/python/foxclaw/finance/__init__.py +15 -0
  147. package/python/foxclaw/finance/evidence.py +153 -0
  148. package/python/foxclaw/finance/fx.py +100 -0
  149. package/python/foxclaw/finance/graph.py +188 -0
  150. package/python/foxclaw/finance/models.py +150 -0
  151. package/python/foxclaw/finance/nlparse.py +575 -0
  152. package/python/foxclaw/finance/policy.py +329 -0
  153. package/python/foxclaw/finance/reasoner.py +129 -0
  154. package/python/foxclaw/finance/reasoning.py +340 -0
  155. package/python/foxclaw/finance/reconcile.py +180 -0
  156. package/python/foxclaw/finance/runtime.py +527 -0
  157. package/python/foxclaw/governance/__init__.py +24 -0
  158. package/python/foxclaw/governance/middleware.py +184 -0
  159. package/python/foxclaw/harness/__init__.py +17 -0
  160. package/python/foxclaw/harness/_base.py +72 -0
  161. package/python/foxclaw/harness/_policy.py +359 -0
  162. package/python/foxclaw/harness/drift.py +89 -0
  163. package/python/foxclaw/harness/event_bus.py +31 -0
  164. package/python/foxclaw/harness/harness.py +446 -0
  165. package/python/foxclaw/harness/loop_detection.py +152 -0
  166. package/python/foxclaw/itinerary/__init__.py +10 -0
  167. package/python/foxclaw/itinerary/amap_client.py +117 -0
  168. package/python/foxclaw/itinerary/models.py +80 -0
  169. package/python/foxclaw/itinerary/reasoner.py +101 -0
  170. package/python/foxclaw/itinerary/reasoning.py +174 -0
  171. package/python/foxclaw/itinerary/runtime.py +148 -0
  172. package/python/foxclaw/kernel/__init__.py +13 -0
  173. package/python/foxclaw/kernel/capabilities.py +73 -0
  174. package/python/foxclaw/kernel/policy_gate.py +114 -0
  175. package/python/foxclaw/kernel/process_manager.py +205 -0
  176. package/python/foxclaw/kernel/scheduler.py +131 -0
  177. package/python/foxclaw/kernel/sentinel.py +453 -0
  178. package/python/foxclaw/kernel/sentinel_client.py +246 -0
  179. package/python/foxclaw/kernel/spine.py +97 -0
  180. package/python/foxclaw/knowledge/__init__.py +376 -0
  181. package/python/foxclaw/knowledge/truth_crystallizer.py +92 -0
  182. package/python/foxclaw/learning/__init__.py +12 -0
  183. package/python/foxclaw/learning/knowledge_graph.py +149 -0
  184. package/python/foxclaw/learning/models.py +93 -0
  185. package/python/foxclaw/learning/reasoner.py +70 -0
  186. package/python/foxclaw/learning/runtime.py +363 -0
  187. package/python/foxclaw/learning/spaced_repetition.py +96 -0
  188. package/python/foxclaw/mcp_server.py +189 -0
  189. package/python/foxclaw/orchestration/__init__.py +184 -0
  190. package/python/foxclaw/pipeline/__init__.py +23 -0
  191. package/python/foxclaw/pipeline/cognitive_pipeline.py +481 -0
  192. package/python/foxclaw/pipeline/supervisor_stages.py +230 -0
  193. package/python/foxclaw/planning/__init__.py +17 -0
  194. package/python/foxclaw/planning/llm_planner.py +232 -0
  195. package/python/foxclaw/planning/planner.py +99 -0
  196. package/python/foxclaw/search/__init__.py +24 -0
  197. package/python/foxclaw/search/agent_port.py +194 -0
  198. package/python/foxclaw/search/evidence.py +113 -0
  199. package/python/foxclaw/search/external_search.py +50 -0
  200. package/python/foxclaw/search/hkc_read_port.py +60 -0
  201. package/python/foxclaw/search/internal_search.py +73 -0
  202. package/python/foxclaw/search/models.py +129 -0
  203. package/python/foxclaw/search/planner.py +44 -0
  204. package/python/foxclaw/search/retriever.py +39 -0
  205. package/python/foxclaw/search/runtime.py +134 -0
  206. package/python/foxclaw/search/strategy.py +44 -0
  207. package/python/foxclaw/skills/__init__.py +613 -0
  208. package/python/foxclaw/skills/hooks_spec.py +73 -0
  209. package/python/foxclaw/skills/lifecycle.py +278 -0
  210. package/python/foxclaw/skills/markdown_skill.py +174 -0
  211. package/python/foxclaw/skills/markdown_store.py +39 -0
  212. package/python/foxclaw/skills/mcp_client.py +190 -0
  213. package/python/foxclaw/skills/mcp_store.py +43 -0
  214. package/python/foxclaw/skills/permission.py +180 -0
  215. package/python/foxclaw/skills/pipeline.py +386 -0
  216. package/python/foxclaw/skills/policy.py +80 -0
  217. package/python/foxclaw/skills/review.py +72 -0
  218. package/python/foxclaw/skills/store.py +57 -0
  219. package/python/frontend/assets/core_bg.jpg +0 -0
  220. package/python/frontend/deck/auth.js +32 -0
  221. package/python/frontend/deck/chat_model_picker.js +537 -0
  222. package/python/frontend/deck/domain.js +296 -0
  223. package/python/frontend/deck/main.js +1950 -0
  224. package/python/frontend/deck/markdown.js +48 -0
  225. package/python/frontend/deck/settings.js +324 -0
  226. package/python/frontend/deck/state.js +6 -0
  227. package/python/frontend/deck/util.js +6 -0
  228. package/python/frontend/deck/visuals.js +212 -0
  229. package/python/frontend/deck.css +855 -0
  230. package/python/frontend/deck.html +686 -0
  231. package/python/frontend/deck_v2.html +341 -0
  232. package/python/frontend/planet/index.html +266 -0
  233. package/python/frontend/planet/planet.css +579 -0
  234. package/python/frontend/planet/planet.js +1637 -0
  235. package/python/frontend/planet/vendor/three.core.min.js +6 -0
  236. package/python/frontend/planet/vendor/three.module.min.js +6 -0
  237. package/python/frontend/v3/assets/index-BD7VPbMV.js +33 -0
  238. package/python/frontend/v3/assets/index-CAvnYWGH.css +1 -0
  239. package/python/frontend/v3/index.html +15 -0
  240. package/python/frontend/widget_runtime.js +295 -0
  241. package/python/frontend/widgets.js +696 -0
  242. package/python/frontend_src/index.html +14 -0
  243. package/python/frontend_src/package-lock.json +1376 -0
  244. package/python/frontend_src/package.json +19 -0
  245. package/python/frontend_src/src/App.vue +62 -0
  246. package/python/frontend_src/src/api.js +89 -0
  247. package/python/frontend_src/src/components/ChatDock.vue +377 -0
  248. package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
  249. package/python/frontend_src/src/components/DocViewer.vue +171 -0
  250. package/python/frontend_src/src/components/Inspector.vue +241 -0
  251. package/python/frontend_src/src/components/LeftRail.vue +394 -0
  252. package/python/frontend_src/src/components/MenuPopover.vue +130 -0
  253. package/python/frontend_src/src/components/MessageStream.vue +401 -0
  254. package/python/frontend_src/src/components/ModelPicker.vue +355 -0
  255. package/python/frontend_src/src/components/QuickIngest.vue +118 -0
  256. package/python/frontend_src/src/components/Stage.vue +226 -0
  257. package/python/frontend_src/src/components/ToolMenu.vue +144 -0
  258. package/python/frontend_src/src/components/TopBar.vue +90 -0
  259. package/python/frontend_src/src/components/panels/ActivityPanel.vue +84 -0
  260. package/python/frontend_src/src/components/panels/AgentsPanel.vue +86 -0
  261. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +441 -0
  262. package/python/frontend_src/src/components/panels/MemoryPanel.vue +118 -0
  263. package/python/frontend_src/src/components/panels/PanelBox.vue +51 -0
  264. package/python/frontend_src/src/components/panels/ReviewPanel.vue +308 -0
  265. package/python/frontend_src/src/components/panels/SettingsPanel.vue +254 -0
  266. package/python/frontend_src/src/components/panels/TasksPanel.vue +74 -0
  267. package/python/frontend_src/src/components/panels/ToolsPanel.vue +640 -0
  268. package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
  269. package/python/frontend_src/src/lib/fmt.js +48 -0
  270. package/python/frontend_src/src/lib/md.js +204 -0
  271. package/python/frontend_src/src/main.js +7 -0
  272. package/python/frontend_src/src/stores/app.js +106 -0
  273. package/python/frontend_src/src/stores/chat.js +484 -0
  274. package/python/frontend_src/src/stores/model.js +104 -0
  275. package/python/frontend_src/src/stores/panels.js +330 -0
  276. package/python/frontend_src/src/styles/base.css +138 -0
  277. package/python/frontend_src/src/styles/panel.css +117 -0
  278. package/python/frontend_src/src/styles/tokens.css +78 -0
  279. package/python/frontend_src/vite.config.js +46 -0
  280. package/python/pyproject.toml +265 -0
  281. package/python/skills/README.md +123 -0
  282. package/python/skills/execution-contract/SKILL.md +30 -0
  283. package/python/skills/general-assistant/SKILL.md +34 -0
  284. package/python/skills/planner/SKILL.md +33 -0
  285. package/python/skills/schedule-itinerary/SKILL.md +35 -0
  286. package/python/skills/skill-composition/SKILL.md +31 -0
  287. package/python/skills/skill-guard/SKILL.md +67 -0
  288. package/python/skills/skill-memory/SKILL.md +50 -0
  289. package/python/skills/skill-router/SKILL.md +35 -0
  290. package/python/tests/conftest.py +76 -0
  291. package/python/tests/run_all.py +263 -0
  292. package/python/tests/test_agent_lifecycle.py +360 -0
  293. package/python/tests/test_ahs_p2.py +414 -0
  294. package/python/tests/test_anthropic_tools.py +206 -0
  295. package/python/tests/test_api.py +87 -0
  296. package/python/tests/test_api_auth.py +83 -0
  297. package/python/tests/test_api_host_guard.py +69 -0
  298. package/python/tests/test_attachments.py +167 -0
  299. package/python/tests/test_audit_chain.py +121 -0
  300. package/python/tests/test_audit_sink.py +136 -0
  301. package/python/tests/test_browser.py +153 -0
  302. package/python/tests/test_browser_request_guard.py +184 -0
  303. package/python/tests/test_capabilities.py +158 -0
  304. package/python/tests/test_channel_signing.py +192 -0
  305. package/python/tests/test_channel_trust.py +109 -0
  306. package/python/tests/test_channels.py +184 -0
  307. package/python/tests/test_cli.py +46 -0
  308. package/python/tests/test_compaction_toolpair.py +119 -0
  309. package/python/tests/test_config_layering.py +65 -0
  310. package/python/tests/test_confirm_nonce.py +197 -0
  311. package/python/tests/test_context_compaction_wiring.py +130 -0
  312. package/python/tests/test_dispatch.py +95 -0
  313. package/python/tests/test_domain_evals.py +403 -0
  314. package/python/tests/test_domain_import.py +78 -0
  315. package/python/tests/test_domain_reasoners.py +148 -0
  316. package/python/tests/test_e2e.py +178 -0
  317. package/python/tests/test_emb_cache.py +107 -0
  318. package/python/tests/test_embedding_singleton.py +89 -0
  319. package/python/tests/test_envfile.py +83 -0
  320. package/python/tests/test_evals_smoke.py +114 -0
  321. package/python/tests/test_event_spine.py +182 -0
  322. package/python/tests/test_evidence_semantic.py +101 -0
  323. package/python/tests/test_evidence_sse.py +97 -0
  324. package/python/tests/test_file_ingest.py +146 -0
  325. package/python/tests/test_finance.py +153 -0
  326. package/python/tests/test_finance_ledger.py +252 -0
  327. package/python/tests/test_finance_persist.py +178 -0
  328. package/python/tests/test_gateway.py +144 -0
  329. package/python/tests/test_governance.py +468 -0
  330. package/python/tests/test_governance_audit.py +359 -0
  331. package/python/tests/test_governance_middleware.py +143 -0
  332. package/python/tests/test_hkc.py +286 -0
  333. package/python/tests/test_hkc_index_build_manager.py +339 -0
  334. package/python/tests/test_hkc_manifest_reuse.py +317 -0
  335. package/python/tests/test_hkc_search_event_sync.py +533 -0
  336. package/python/tests/test_import_path_guard.py +161 -0
  337. package/python/tests/test_injection_guard.py +211 -0
  338. package/python/tests/test_integration.py +141 -0
  339. package/python/tests/test_itinerary.py +103 -0
  340. package/python/tests/test_kee_semantic.py +89 -0
  341. package/python/tests/test_kernel.py +222 -0
  342. package/python/tests/test_learning.py +110 -0
  343. package/python/tests/test_llm_admin.py +160 -0
  344. package/python/tests/test_llm_planner.py +366 -0
  345. package/python/tests/test_markdown_skills.py +73 -0
  346. package/python/tests/test_markdown_skills_openclaw.py +71 -0
  347. package/python/tests/test_mcp_connectors.py +109 -0
  348. package/python/tests/test_mcp_guardian_discovery.py +91 -0
  349. package/python/tests/test_mcp_no_memory.py +43 -0
  350. package/python/tests/test_mcp_policy_gate.py +77 -0
  351. package/python/tests/test_mcp_server.py +95 -0
  352. package/python/tests/test_memory_recall.py +95 -0
  353. package/python/tests/test_metrics.py +66 -0
  354. package/python/tests/test_multi_chain.py +102 -0
  355. package/python/tests/test_multimodal.py +135 -0
  356. package/python/tests/test_negotiation.py +146 -0
  357. package/python/tests/test_orchestration.py +119 -0
  358. package/python/tests/test_orchestration_decoupled.py +109 -0
  359. package/python/tests/test_p3_p5_hardening.py +338 -0
  360. package/python/tests/test_parallel_dispatch.py +215 -0
  361. package/python/tests/test_pipeline.py +533 -0
  362. package/python/tests/test_plan_visible.py +86 -0
  363. package/python/tests/test_planner.py +95 -0
  364. package/python/tests/test_policy_gate.py +109 -0
  365. package/python/tests/test_reasoner.py +185 -0
  366. package/python/tests/test_release_check.py +263 -0
  367. package/python/tests/test_resume_checkpoint.py +156 -0
  368. package/python/tests/test_routing.py +95 -0
  369. package/python/tests/test_scheduler.py +141 -0
  370. package/python/tests/test_search.py +204 -0
  371. package/python/tests/test_secrets.py +159 -0
  372. package/python/tests/test_semantic.py +78 -0
  373. package/python/tests/test_sessions.py +91 -0
  374. package/python/tests/test_settings.py +281 -0
  375. package/python/tests/test_shadow_learning.py +330 -0
  376. package/python/tests/test_shared_write.py +207 -0
  377. package/python/tests/test_skill_taint_gate.py +154 -0
  378. package/python/tests/test_skills.py +133 -0
  379. package/python/tests/test_stats_and_events.py +165 -0
  380. package/python/tests/test_streaming.py +60 -0
  381. package/python/tests/test_supervisor_pipeline.py +177 -0
  382. package/python/tests/test_supervisor_triage.py +108 -0
  383. package/python/tests/test_taint.py +117 -0
  384. package/python/tests/test_task_stream.py +85 -0
  385. package/python/tests/test_thinking_sse.py +67 -0
  386. package/python/tests/test_thinking_stream.py +76 -0
  387. package/python/tests/test_tool_center.py +129 -0
  388. package/python/tests/test_tool_parallel.py +113 -0
  389. package/python/tests/test_tool_params.py +103 -0
  390. package/python/tests/test_tool_use.py +242 -0
  391. package/python/tests/test_toplevel_concurrency.py +117 -0
  392. package/python/tests/test_tracing.py +290 -0
  393. package/python/tests/test_truth_crystallizer.py +127 -0
  394. package/python/tests/test_truth_flow.py +93 -0
  395. package/python/tests/test_url_policy.py +53 -0
  396. package/python/tests/test_verify_evidence.py +225 -0
  397. package/python/tests/test_version_sync.py +71 -0
  398. package/python/tests/test_web_trigger.py +99 -0
  399. package/python/tools/check_faiss_type.py +31 -0
  400. package/python/tools/check_log.ps1 +46 -0
  401. package/python/tools/ivf_status.py +72 -0
  402. package/python/tools/mypy_baseline.txt +5 -0
  403. package/python/tools/mypy_ratchet.py +97 -0
  404. package/python/tools/pack.py +178 -0
  405. package/python/tools/release_check.py +349 -0
  406. package/python/tools/restart_foxclaw.ps1 +74 -0
  407. package/python/tools/shared_write_audit.py +204 -0
  408. package/python/tools/size_baseline.txt +28 -0
  409. package/python/tools/size_check.py +102 -0
  410. package/python/tools/verify_env.bat +2 -0
  411. package/python/tools/verify_env.py +25 -0
  412. package/python/vendor/HMR_LICENSE +37 -0
  413. package/python/vendor/HMR_README.md +65 -0
  414. package/python/vendor/hkc/LICENSE +37 -0
  415. package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
  416. package/python/vendor/hkc/hkc_ace/ace.py +457 -0
  417. package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
  418. package/python/vendor/hkc/hkc_api/__init__.py +0 -0
  419. package/python/vendor/hkc/hkc_api/container.py +416 -0
  420. package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
  421. package/python/vendor/hkc/hkc_api/main.py +157 -0
  422. package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
  423. package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
  424. package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
  425. package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
  426. package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
  427. package/python/vendor/hkc/hkc_api/schemas.py +153 -0
  428. package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
  429. package/python/vendor/hkc/hkc_core/__init__.py +0 -0
  430. package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
  431. package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
  432. package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
  433. package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
  434. package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
  435. package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
  436. package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
  437. package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
  438. package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
  439. package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
  440. package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
  441. package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
  442. package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
  443. package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
  444. package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
  445. package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
  446. package/python/vendor/hkc/hkc_kde/kde.py +298 -0
  447. package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
  448. package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
  449. package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
  450. package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
  451. package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
  452. package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
  453. package/python/vendor/hkc/hkc_kde/models.py +74 -0
  454. package/python/vendor/hkc/hkc_kde/packager.py +329 -0
  455. package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
  456. package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
  457. package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
  458. package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
  459. package/python/vendor/hkc/hkc_kee/kee.py +541 -0
  460. package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
  461. package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
  462. package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
  463. package/python/vendor/hkc/hkc_sdk/base.py +151 -0
  464. package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
  465. package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
  466. package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
  467. package/python/vendor/hkc/hkc_sdk/models.py +171 -0
  468. package/python/vendor/hkc/hkc_search/__init__.py +0 -0
  469. package/python/vendor/hkc/hkc_search/bm25.py +325 -0
  470. package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
  471. package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
  472. package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
  473. package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
  474. package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
  475. package/python/vendor/hmr/__init__.py +0 -0
  476. package/python/vendor/hmr/core/__init__.py +0 -0
  477. package/python/vendor/hmr/core/hmr.py +684 -0
  478. package/python/vendor/hmr/core/models.py +310 -0
  479. package/python/vendor/hmr/engines/__init__.py +0 -0
  480. package/python/vendor/hmr/engines/evolution.py +710 -0
  481. package/python/vendor/hmr/engines/jit_compiler.py +472 -0
  482. package/python/vendor/hmr/engines/lifecycle.py +264 -0
  483. package/python/vendor/hmr/engines/policy.py +625 -0
  484. package/python/vendor/hmr/engines/recall.py +260 -0
  485. package/python/vendor/hmr/engines/runtime_state.py +158 -0
  486. package/python/vendor/hmr/engines/scheduler.py +323 -0
  487. package/python/vendor/hmr/engines/semantic.py +150 -0
  488. package/python/vendor/hmr/engines/temporal.py +261 -0
  489. package/python/vendor/hmr/engines/thought_chain.py +635 -0
  490. package/python/vendor/hmr/graph/__init__.py +0 -0
  491. package/python/vendor/hmr/graph/cwg.py +338 -0
  492. package/python/vendor/hmr/graph/memory_graph.py +530 -0
  493. package/python/vendor/hmr/storage/__init__.py +0 -0
  494. package/python/vendor/hmr/storage/memory_fs.py +300 -0
  495. package/python/vendor/hmr/storage/vector_store.py +520 -0
@@ -0,0 +1,417 @@
1
+ """AHS 执行链:技能安装/管理、MCP 接入、Markdown 指令技能、策略封杀、运行、断路器、钩子审计。
2
+
3
+ 从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api._os_instance`
4
+ 晚绑定读取;路由用 `@app` 装饰器在 import 时注册。
5
+ """
6
+
7
+ import asyncio
8
+ import uuid
9
+ from typing import Optional
10
+
11
+ from fastapi import HTTPException
12
+ from pydantic import BaseModel
13
+
14
+ import foxclaw.api as _api
15
+ from .. import app
16
+
17
+
18
+ # ============ AHS 执行链(Agent Harness Skills 标准)============
19
+
20
+ class AHSRunPayload(BaseModel):
21
+ task: str
22
+ confirmed: bool = False
23
+ # 【P2-2】断点续跑:传上次响应里的 pending_confirmation(确认后连 confirmed=True 一起传)。
24
+ # Optional:pydantic v2 下 dict=None 字段收到显式 null 会 422,必须 Optional。
25
+ resume_state: Optional[dict] = None
26
+ # 【P1】关键操作(send/pay/destructive)二次确认码:上次响应 pending_confirmation.confirm_nonce。
27
+ # 单次使用、绑定 skill+参数、默认 5 分钟过期。也可直接放在 resume_state 里,二者等价。
28
+ confirm_nonce: str = ""
29
+
30
+
31
+ @app.get("/api/ahs/skills")
32
+ async def ahs_list_skills():
33
+ """列出注册的 skill(含信任分级 trust + 封杀标记 blocked)。"""
34
+ pol = _api._os_instance.skill_policy
35
+ out = _api._os_instance.skill_registry.catalog()
36
+ for s in out:
37
+ s["blocked"] = pol.is_blocked(s.get("id", ""))
38
+ return {"skills": out}
39
+
40
+
41
+ class PolicyDenyPayload(BaseModel):
42
+ id: str
43
+
44
+
45
+ @app.get("/api/ahs/policy")
46
+ async def ahs_policy_get():
47
+ """全局封杀策略快照(deny/allow)。"""
48
+ return _api._os_instance.skill_policy.snapshot()
49
+
50
+
51
+ @app.post("/api/ahs/policy/deny")
52
+ async def ahs_policy_deny(p: PolicyDenyPayload):
53
+ """封杀一个技能 id(deny 压倒 enable,立即对所有执行/注入生效)。"""
54
+ _api._os_instance.skill_policy.deny(p.id)
55
+ return {"ok": True, "policy": _api._os_instance.skill_policy.snapshot()}
56
+
57
+
58
+ @app.delete("/api/ahs/policy/deny/{skill_id}")
59
+ async def ahs_policy_undeny(skill_id: str):
60
+ """解封。E1:被 policy 层(管理员 FOXCLAW_POLICY_DENY / policy.json)锁定的技能运行时无法解封,
61
+ 诚实返回 ok=False + 原因(不再假报成功);其余情况与改前一致(ok=True)。"""
62
+ _api._os_instance.skill_policy.undeny(skill_id)
63
+ snap = _api._os_instance.skill_policy.snapshot()
64
+ if skill_id in snap.get("policy_deny", []):
65
+ return {"ok": False,
66
+ "reason": f"「{skill_id}」被管理员策略(policy 层)锁定,运行时无法解封", "policy": snap}
67
+ return {"ok": True, "policy": snap}
68
+
69
+
70
+ class SkillInstallPayload(BaseModel):
71
+ id: str
72
+ name: str
73
+ description: str = ""
74
+ kind: str # 只允许 "web_api" | "mcp"
75
+ triggers: list = []
76
+ side_effect: str = "write" # read/write/send/pay;不传按有副作用(会要求确认)
77
+ destructive: bool = False
78
+ external: bool = True
79
+ params: dict = {}
80
+ # web_api
81
+ endpoint: str = ""
82
+ http_method: str = "GET"
83
+ http_headers: dict = {}
84
+ param_location: str = "auto"
85
+ allow_local: bool = False
86
+ timeout: float = 10.0
87
+ # mcp
88
+ mcp_server: str = ""
89
+ mcp_tool: str = ""
90
+ acknowledged: bool = False # warn 级风险,用户确认后置 true
91
+
92
+
93
+ class SkillPatchPayload(BaseModel):
94
+ enabled: bool
95
+
96
+
97
+ class SkillTestPayload(BaseModel):
98
+ args: dict = {}
99
+ confirmed: bool = False
100
+ mode: str = "strict"
101
+ # 关键操作(send/pay/destructive)的二次确认码:上次响应里的 confirm_nonce。
102
+ # 宿主注入了 confirms(见 foxclaw/__init__.py)时,confirmed=True 只有配有效 nonce
103
+ # 才放行 —— 原先这个端点收不到 nonce,界面上点「确认」永远退回 needs_confirmation。
104
+ confirm_nonce: str = ""
105
+
106
+
107
+ _SKILL_KINDS = {"web_api", "mcp"}
108
+ _SIDE_EFFECTS = {"read", "write", "send", "pay"}
109
+
110
+
111
+ def _validate_install(p: "SkillInstallPayload"):
112
+ if p.kind not in _SKILL_KINDS:
113
+ raise HTTPException(400, "kind 只能是 web_api 或 mcp(不支持从界面安装本地代码技能)")
114
+ if p.side_effect not in _SIDE_EFFECTS:
115
+ raise HTTPException(400, "side_effect 非法")
116
+ if p.kind == "web_api":
117
+ if not (p.endpoint.startswith("http://") or p.endpoint.startswith("https://")):
118
+ raise HTTPException(400, "web_api 需合法 http(s) endpoint")
119
+ if p.kind == "mcp" and not (p.mcp_server and p.mcp_tool):
120
+ raise HTTPException(400, "mcp 需同时提供 mcp_server 与 mcp_tool")
121
+
122
+
123
+ @app.post("/api/ahs/skills")
124
+ async def ahs_install_skill(p: SkillInstallPayload):
125
+ """安装/注册一个 web_api|mcp 技能(过安装审核门 + 持久化)。同 id 覆盖更新。"""
126
+ _validate_install(p)
127
+ from foxclaw.skills import SkillSpec
128
+ from foxclaw.skills.review import review_install
129
+ reg = _api._os_instance.skill_registry
130
+ _existing = reg.get(p.id)
131
+ if _existing is not None and _existing.source == "builtin":
132
+ raise HTTPException(409, "该 id 属于内置技能,不能覆盖")
133
+ existed = _existing is not None
134
+ spec = SkillSpec(
135
+ id=p.id, name=p.name, description=p.description, kind=p.kind,
136
+ triggers=list(p.triggers), side_effect=p.side_effect,
137
+ destructive=p.destructive, external=p.external, params=dict(p.params),
138
+ endpoint=p.endpoint, http_method=p.http_method, http_headers=dict(p.http_headers),
139
+ param_location=p.param_location, allow_local=p.allow_local, timeout=p.timeout,
140
+ mcp_server=p.mcp_server, mcp_tool=p.mcp_tool, source="user",
141
+ )
142
+ verdict = review_install(spec) # 安装审核门
143
+ if verdict["verdict"] == "deny":
144
+ raise HTTPException(403, detail={"error": "install_denied", "reasons": verdict["reasons"]})
145
+ if verdict["verdict"] == "warn" and not p.acknowledged:
146
+ raise HTTPException(409, detail={"needs_ack": True, "warnings": verdict["warnings"]})
147
+ reg.register(spec)
148
+ _api._os_instance.skill_store.save(reg)
149
+ return {"ok": True, "skill": spec.to_dict(), "created": not existed, "warnings": verdict["warnings"]}
150
+
151
+
152
+ @app.patch("/api/ahs/skills/{skill_id}")
153
+ async def ahs_patch_skill(skill_id: str, p: SkillPatchPayload):
154
+ """启停一个技能(内置/用户均可,持久化)。"""
155
+ reg = _api._os_instance.skill_registry
156
+ sp = reg.get(skill_id)
157
+ if sp is None:
158
+ raise HTTPException(404, "技能不存在")
159
+ sp.enabled = bool(p.enabled)
160
+ _api._os_instance.skill_store.save(reg)
161
+ return {"ok": True, "skill": sp.to_dict()}
162
+
163
+
164
+ @app.delete("/api/ahs/skills/{skill_id}")
165
+ async def ahs_delete_skill(skill_id: str):
166
+ """删除用户安装的技能;内置技能不可删(只能停用)。"""
167
+ reg = _api._os_instance.skill_registry
168
+ sp = reg.get(skill_id)
169
+ if sp is None:
170
+ raise HTTPException(404, "技能不存在")
171
+ if sp.source == "builtin":
172
+ raise HTTPException(409, "内置技能不可删除,只能停用")
173
+ reg.unregister(skill_id)
174
+ _api._os_instance.skill_store.save(reg)
175
+ return {"ok": True}
176
+
177
+
178
+ @app.post("/api/ahs/skills/{skill_id}/test")
179
+ async def ahs_test_skill(skill_id: str, p: SkillTestPayload):
180
+ """单技能测试调用:走真实权限管线/断路器/SSRF。副作用未确认 → needs_confirmation。"""
181
+ reg = _api._os_instance.skill_registry
182
+ sp = reg.get(skill_id)
183
+ if sp is None:
184
+ raise HTTPException(404, "技能不存在")
185
+ # 阻塞 I/O(web_api httpx / mcp 桥,最长数十秒)放线程池,勿堵死事件循环(否则慢工具冻全站)
186
+ res = await asyncio.to_thread(_api._os_instance.skill_adapter.invoke,
187
+ sp, p.args, p.confirmed, p.mode,
188
+ confirm_nonce=p.confirm_nonce)
189
+ return res.to_dict()
190
+
191
+
192
+ class McpServerPayload(BaseModel):
193
+ id: str
194
+ name: str = ""
195
+ url: str
196
+ headers: dict = {}
197
+ transport: str = "streamable_http" # streamable_http | sse
198
+ acknowledged: bool = False
199
+
200
+
201
+ @app.post("/api/ahs/mcp/servers")
202
+ async def mcp_add_server(p: McpServerPayload):
203
+ """接入一个远程 MCP server:审核→连接拉工具→批量注册 mcp 技能→持久化。"""
204
+ from foxclaw.skills.review import review_mcp_server
205
+ from foxclaw.skills.mcp_client import tool_to_spec
206
+ if p.transport not in ("streamable_http", "sse"):
207
+ raise HTTPException(400, "transport 只能是 streamable_http 或 sse")
208
+ if not (p.url.startswith("http://") or p.url.startswith("https://")):
209
+ raise HTTPException(400, "url 需为 http(s)")
210
+ verdict = review_mcp_server(p.url)
211
+ if verdict["verdict"] == "deny":
212
+ raise HTTPException(403, detail={"error": "install_denied", "reasons": verdict["reasons"]})
213
+ if verdict["verdict"] == "warn" and not p.acknowledged:
214
+ raise HTTPException(409, detail={"needs_ack": True, "warnings": verdict["warnings"]})
215
+ cfg = {"id": p.id, "name": p.name or p.id, "url": p.url,
216
+ "headers": dict(p.headers), "transport": p.transport}
217
+ sentinel = getattr(_api._os_instance, "sentinel", None)
218
+ try:
219
+ if sentinel is not None:
220
+ # 守门人模式:headers 只在这次调用经手,发现由守门人代执行,主进程只存元数据。
221
+ tools = await asyncio.to_thread(_api._os_instance.mcp_add_server_guardian, cfg)
222
+ else:
223
+ tools = await asyncio.to_thread(_api._os_instance.mcp_manager.add_server, cfg) # 桥阻塞→线程池
224
+ _api._os_instance.mcp_store.save(_api._os_instance.mcp_manager.servers())
225
+ except Exception as e:
226
+ raise HTTPException(502, f"连接 MCP server 失败: {e}")
227
+ reg = _api._os_instance.skill_registry
228
+ n = 0
229
+ for tool in tools:
230
+ reg.register(tool_to_spec(p.id, tool)); n += 1
231
+ _api._os_instance.skill_store.save(reg)
232
+ _api._os_instance.sync_mcp_servers_to_sentinel() # 元数据同步;守门人已持有 headers
233
+ return {"ok": True, "server": {k: cfg[k] for k in ("id", "name", "url", "transport")},
234
+ "tools_registered": n, "warnings": verdict["warnings"]}
235
+
236
+
237
+ @app.get("/api/ahs/mcp/servers")
238
+ async def mcp_list_servers():
239
+ """列出已接入 MCP server(工具数由注册表派生,无需连接)。"""
240
+ reg = _api._os_instance.skill_registry
241
+ all_sk = reg.all_including_disabled()
242
+ out = []
243
+ for c in _api._os_instance.mcp_manager.servers():
244
+ tc = sum(1 for s in all_sk if s.kind == "mcp" and s.mcp_server == c["id"])
245
+ out.append({"id": c["id"], "name": c["name"], "url": c["url"],
246
+ "transport": c["transport"], "tool_count": tc})
247
+ return {"servers": out}
248
+
249
+
250
+ @app.delete("/api/ahs/mcp/servers/{server_id}")
251
+ async def mcp_remove_server(server_id: str):
252
+ """移除 server + 其注册的所有 mcp 技能。"""
253
+ mgr = _api._os_instance.mcp_manager
254
+ if not mgr.remove_server(server_id):
255
+ raise HTTPException(404, "server 不存在")
256
+ reg = _api._os_instance.skill_registry
257
+ removed = [s.id for s in reg.all_including_disabled()
258
+ if s.kind == "mcp" and s.mcp_server == server_id]
259
+ for sid in removed:
260
+ reg.unregister(sid)
261
+ _api._os_instance.skill_store.save(reg)
262
+ _api._os_instance.mcp_store.save(mgr.servers())
263
+ _api._os_instance.sync_mcp_servers_to_sentinel() # 配置(含凭证)同步给守门人
264
+ return {"ok": True, "removed_skills": len(removed)}
265
+
266
+
267
+ @app.post("/api/ahs/mcp/servers/{server_id}/refresh")
268
+ async def mcp_refresh_server(server_id: str):
269
+ """重连拉工具:新增/更新技能,删除已消失的工具技能。"""
270
+ from foxclaw.skills.mcp_client import tool_to_spec
271
+ mgr = _api._os_instance.mcp_manager
272
+ if not any(s["id"] == server_id for s in mgr.servers()):
273
+ raise HTTPException(404, "server 不存在")
274
+ sentinel = getattr(_api._os_instance, "sentinel", None)
275
+ try:
276
+ if sentinel is not None:
277
+ tools = await asyncio.to_thread(sentinel.mcp_list_tools, server_id) # 守门人代发现(失败抛异常)
278
+ else:
279
+ tools = await asyncio.to_thread(mgr.list_tools, server_id) # 桥阻塞→线程池
280
+ except Exception as e:
281
+ raise HTTPException(502, f"刷新失败: {e}")
282
+ reg = _api._os_instance.skill_registry
283
+ fresh = set()
284
+ for tool in tools:
285
+ spec = tool_to_spec(server_id, tool); reg.register(spec); fresh.add(spec.id)
286
+ for s in [s for s in reg.all_including_disabled()
287
+ if s.kind == "mcp" and s.mcp_server == server_id and s.id not in fresh]:
288
+ reg.unregister(s.id)
289
+ _api._os_instance.skill_store.save(reg)
290
+ return {"ok": True, "tools": len(fresh)}
291
+
292
+
293
+ class MdSkillPayload(BaseModel):
294
+ content: str = ""
295
+ package_b64: str = "" # OpenClaw 包(zip)的 base64;有则优先,解包取 SKILL.md
296
+ id: str = ""
297
+ acknowledged: bool = False
298
+
299
+
300
+ class MdSkillPatchPayload(BaseModel):
301
+ enabled: bool | None = None
302
+ triggers: list | None = None # 改触发词(免解压重打包);None=不动
303
+
304
+
305
+ @app.post("/api/ahs/md-skills")
306
+ async def md_install(p: MdSkillPayload):
307
+ """上传/注册一个 SKILL.md 指令技能(单 .md 文本 或 OpenClaw zip 包)。解析→审核→持久化。"""
308
+ from foxclaw.skills.markdown_skill import parse_skill_md, extract_skill_package
309
+ content, want_id, want_version = p.content, (p.id or None), ""
310
+ if p.package_b64:
311
+ import base64 as _b64
312
+ try:
313
+ content, meta = extract_skill_package(_b64.b64decode(p.package_b64))
314
+ except ValueError as e:
315
+ raise HTTPException(400, f"OpenClaw 包解析失败: {e}")
316
+ except Exception as e:
317
+ raise HTTPException(400, f"包解码失败: {e}")
318
+ want_id = want_id or (meta.get("slug") or None)
319
+ want_version = str(meta.get("version") or "")
320
+ try:
321
+ skill = parse_skill_md(content, id=want_id)
322
+ except ValueError as e:
323
+ raise HTTPException(400, f"SKILL.md 解析失败: {e}")
324
+ if want_version and not skill.version:
325
+ skill.version = want_version
326
+ if not p.acknowledged:
327
+ raise HTTPException(409, detail={"needs_ack": True, "warnings": [
328
+ "此技能会把内容注入你 AI 的提示词、影响其行为,仅从可信来源安装"]})
329
+ reg = _api._os_instance.md_skills
330
+ existed = reg.get(skill.id) is not None
331
+ reg.register(skill)
332
+ _api._os_instance.md_store.save(reg)
333
+ return {"ok": True, "skill": skill.to_meta(), "created": not existed}
334
+
335
+
336
+ @app.get("/api/ahs/md-skills")
337
+ async def md_list():
338
+ pol = _api._os_instance.skill_policy
339
+ out = _api._os_instance.md_skills.catalog()
340
+ for s in out:
341
+ s["blocked"] = pol.is_blocked(s.get("id", ""))
342
+ return {"skills": out}
343
+
344
+
345
+ @app.get("/api/ahs/md-skills/{skill_id}")
346
+ async def md_get(skill_id: str):
347
+ s = _api._os_instance.md_skills.get(skill_id)
348
+ if s is None:
349
+ raise HTTPException(404, "指令技能不存在")
350
+ return s.to_dict()
351
+
352
+
353
+ @app.patch("/api/ahs/md-skills/{skill_id}")
354
+ async def md_patch(skill_id: str, p: MdSkillPatchPayload):
355
+ reg = _api._os_instance.md_skills
356
+ s = reg.get(skill_id)
357
+ if s is None:
358
+ raise HTTPException(404, "指令技能不存在")
359
+ if p.enabled is not None:
360
+ s.enabled = bool(p.enabled)
361
+ if p.triggers is not None:
362
+ s.triggers = [str(t).strip() for t in p.triggers if str(t).strip()]
363
+ _api._os_instance.md_store.save(reg)
364
+ return {"ok": True, "skill": s.to_meta()}
365
+
366
+
367
+ @app.delete("/api/ahs/md-skills/{skill_id}")
368
+ async def md_delete(skill_id: str):
369
+ reg = _api._os_instance.md_skills
370
+ if reg.get(skill_id) is None:
371
+ raise HTTPException(404, "指令技能不存在")
372
+ reg.unregister(skill_id)
373
+ _api._os_instance.md_store.save(reg)
374
+ return {"ok": True}
375
+
376
+
377
+ @app.post("/api/ahs/run")
378
+ async def ahs_run(payload: AHSRunPayload):
379
+ """跑一遍 AHS 执行链:router→planner→composition,全程过 guard+breaker+hooks。
380
+ 【P3-2】用 run_async 把同步 compose 放到线程池,避免阻塞事件循环。
381
+ 【P2-2】暴露 pending_confirmation/is_pending,供前端识别"待确认"并断点续跑
382
+ (确认后带 confirmed=True + resume_state 重发本接口)。
383
+ 【P1】关键操作还需带 confirm_nonce(响应里的 pending_confirmation.confirm_nonce);
384
+ 缺/错/过期 → 仍返回 pending_confirmation 并换发新码,不会执行。"""
385
+ from foxclaw.skills import set_loop_session, reset_loop_session
386
+ # 循环检测按执行链分槽:断点续跑复用同一 task_id,否则每次请求独立(消除跨请求污染)
387
+ sid = (payload.resume_state or {}).get("task_id") or f"ahs_{uuid.uuid4().hex[:8]}"
388
+ token = set_loop_session(sid)
389
+ try:
390
+ res = await _api._os_instance.ahs_pipeline.run_async(
391
+ payload.task, confirmed=payload.confirmed,
392
+ resume_state=payload.resume_state,
393
+ confirm_nonce=payload.confirm_nonce)
394
+ return {
395
+ "task": res.task, "route": res.route, "plan": res.plan,
396
+ "composition": res.composition, "success": res.success,
397
+ "summary": res.summary,
398
+ "pending_confirmation": res.pending_confirmation,
399
+ "is_pending": res.is_pending,
400
+ }
401
+ except Exception as e:
402
+ return {"success": False, "summary": f"执行链异常: {e}"}
403
+ finally:
404
+ reset_loop_session(token)
405
+
406
+
407
+ @app.get("/api/ahs/breaker")
408
+ async def ahs_breaker_status():
409
+ """断路器状态:各 skill 的失败计数与开闸情况。"""
410
+ br = _api._os_instance.skill_adapter.breaker
411
+ return {"breakers": {sid: br.status(sid) for sid in br._entries}}
412
+
413
+
414
+ @app.get("/api/ahs/hooks/audit")
415
+ async def ahs_hooks_audit():
416
+ """钩子审计日志(最近 20 条)。"""
417
+ return {"audit": _api._os_instance.skill_adapter.hooks.recent_audit(20)}
@@ -0,0 +1,117 @@
1
+ """聊天附件:通用文件上传/下载(多模态渲染·步骤7)。
2
+
3
+ 与「文件摄入知识库」(domain.py 的 import + HKC ingest)不同:这里是**聊天附件**——文件随问题
4
+ 发给 LLM(文本类提取正文、其它降级为元数据提示),并在对话里以卡片渲染。图片多模态(images
5
+ base64)走既有 /chat/stream 的 images 通道,不经过这里。
6
+
7
+ 安全铁律:
8
+ · 文件名只留 basename + 清洗非法字符,杜绝路径穿越;
9
+ · id 只认我们生成的 [A-Za-z0-9_-] 形态;
10
+ · 大小 / 文本提取长度均设上限(防超大 payload / 超长上下文);
11
+ · 附件落 `<data_dir>/attachments/`,下载按 id 定位,不信任客户端传路径。
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import re
16
+ import uuid
17
+ from pathlib import Path
18
+
19
+ from fastapi import File, HTTPException, UploadFile
20
+ from fastapi.responses import FileResponse
21
+
22
+ import foxclaw.api as _api
23
+ from .. import app
24
+
25
+
26
+ _MAX_ATTACH_BYTES = 20 * 1024 * 1024 # 单附件上限 20MB
27
+ _MAX_TEXT_CHARS = 20000 # 单文件提取正文上限(防超长文件撑爆上下文)
28
+ _ID_RE = re.compile(r"^[A-Za-z0-9_-]{1,64}$")
29
+
30
+ # 文本类扩展名 → 直接读正文;.pdf 走 fitz(可选依赖);其余二进制 → 降级为元数据提示
31
+ _TEXT_EXTS = {
32
+ ".txt", ".md", ".markdown", ".csv", ".json", ".yaml", ".yml", ".log",
33
+ ".py", ".js", ".ts", ".html", ".css", ".xml", ".toml", ".ini", ".sh",
34
+ ".go", ".rs", ".java", ".c", ".cpp", ".h", ".sql", ".rb", ".php",
35
+ }
36
+
37
+
38
+ def _att_dir() -> Path:
39
+ d = Path(_api._os_instance.data_dir) / "attachments"
40
+ d.mkdir(parents=True, exist_ok=True)
41
+ return d
42
+
43
+
44
+ def _decode_text(data: bytes) -> str:
45
+ """按常见编码尝试解码,失败返回空(降级)。"""
46
+ for enc in ("utf-8", "gbk", "utf-16"):
47
+ try:
48
+ return data.decode(enc)
49
+ except (UnicodeDecodeError, LookupError):
50
+ continue
51
+ return ""
52
+
53
+
54
+ def _extract_text(path: Path, name: str) -> str:
55
+ """从上传的附件提取正文:文本类直接读、PDF 用 fitz、其它返回空(前端/LLM 降级为元数据提示)。"""
56
+ ext = Path(name).suffix.lower()
57
+ if ext in _TEXT_EXTS:
58
+ try:
59
+ return _decode_text(path.read_bytes())[:_MAX_TEXT_CHARS]
60
+ except Exception:
61
+ return ""
62
+ if ext == ".pdf":
63
+ try:
64
+ import fitz # PyMuPDF(可选依赖 knowledge-files)
65
+ except ImportError:
66
+ return ""
67
+ try:
68
+ doc = fitz.open(str(path))
69
+ try:
70
+ text = ""
71
+ for page in doc:
72
+ text += page.get_text()
73
+ if len(text) >= _MAX_TEXT_CHARS:
74
+ break
75
+ return text[:_MAX_TEXT_CHARS]
76
+ finally:
77
+ doc.close()
78
+ except Exception:
79
+ return ""
80
+ return ""
81
+
82
+
83
+ @app.post("/api/upload")
84
+ async def upload_attachment(file: UploadFile = File(...)):
85
+ """通用聊天附件上传:落盘 + 尽力提取正文,返回 {id,name,size,mime,url,text}。
86
+ text 为空表示不可提取(二进制/图片等),前端/LLM 降级为「文件名 + 元数据」提示。"""
87
+ raw_name = (file.filename or "attachment").strip()
88
+ name = Path(raw_name).name or "attachment" # 只留 basename,去路径分隔符
89
+ data = await file.read()
90
+ if not data:
91
+ raise HTTPException(400, "空文件")
92
+ if len(data) > _MAX_ATTACH_BYTES:
93
+ raise HTTPException(413, f"文件过大({len(data) / 1048576:.1f}MB>{_MAX_ATTACH_BYTES // 1048576}MB)")
94
+ aid = uuid.uuid4().hex[:12]
95
+ safe_name = re.sub(r"[^A-Za-z0-9._\-]", "_", name)[:120] or "attachment"
96
+ dest = _att_dir() / f"{aid}_{safe_name}"
97
+ dest.write_bytes(data)
98
+ text = _extract_text(dest, name)
99
+ return {
100
+ "id": aid,
101
+ "name": name,
102
+ "size": len(data),
103
+ "mime": (file.content_type or "application/octet-stream").lower(),
104
+ "url": f"/api/attachments/{aid}",
105
+ "text": text,
106
+ }
107
+
108
+
109
+ @app.get("/api/attachments/{aid}")
110
+ async def get_attachment(aid: str):
111
+ """下载附件。id 只认自生成形态,路径穿越到此为止。"""
112
+ if not _ID_RE.fullmatch(aid or ""):
113
+ raise HTTPException(400, "非法附件 id")
114
+ d = _att_dir()
115
+ for p in d.glob(f"{aid}_*"):
116
+ return FileResponse(str(p), filename=p.name.split("_", 1)[1])
117
+ raise HTTPException(404, "附件不存在")
@@ -0,0 +1,39 @@
1
+ """浏览器子系统(HIR-Browser)端点。
2
+
3
+ 从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api._os_instance`
4
+ 晚绑定读取;路由用 `@app` 装饰器在 import 时注册。
5
+ """
6
+
7
+ from pydantic import BaseModel
8
+
9
+ import foxclaw.api as _api
10
+ from .. import app
11
+
12
+
13
+ # ── 浏览器子系统(HIR-Browser):AI 用的浏览器 ─────────────────
14
+ # 感知网页→压缩语义结构→执行动作→校验结果。async 安全(专用工作线程),
15
+ # playwright 缺失时结构化降级(available=False),不崩溃。受 /api 鉴权保护。
16
+
17
+ class BrowserActPayload(BaseModel):
18
+ type: str # navigate|click|type|scroll|extract|wait
19
+ target: str | None = None # 元素编号;navigate 时为 URL
20
+ value: object | None = None # type 的文本;scroll 的方向
21
+ meta: dict = {}
22
+
23
+
24
+ @app.get("/api/browser/status")
25
+ async def browser_status():
26
+ """浏览器子系统能力/会话状态(available/session_active/launch_error)。"""
27
+ return _api._os_instance.browser_service.status()
28
+
29
+
30
+ @app.get("/api/browser/perceive")
31
+ async def browser_perceive():
32
+ """只感知当前页面语义结构(带编号的可交互元素 + 摘要),不改变页面。"""
33
+ return await _api._os_instance.browser_service.perceive()
34
+
35
+
36
+ @app.post("/api/browser/act")
37
+ async def browser_act(p: BrowserActPayload):
38
+ """执行一个 Web 动作,返回校验后最新语义结构。"""
39
+ return await _api._os_instance.browser_service.act(p.model_dump())