foxclaw 0.1.0 → 0.1.3

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 (514) hide show
  1. package/README.md +54 -29
  2. package/bin/foxclaw.cjs +51 -0
  3. package/index.js +4 -10
  4. package/package.json +46 -38
  5. package/python/.dockerignore +35 -0
  6. package/python/.env.example +173 -0
  7. package/python/.gitattributes +46 -0
  8. package/python/.github/workflows/ci.yml +167 -0
  9. package/python/.github/workflows/release.yml +183 -0
  10. package/python/CHANGELOG.md +57 -0
  11. package/python/CHANGELOG_CN.md +57 -0
  12. package/python/Dockerfile +64 -0
  13. package/python/LICENSE +21 -0
  14. package/python/MANIFEST.in +14 -0
  15. package/python/README.md +201 -0
  16. package/python/README_CN.md +336 -0
  17. package/python/bench/bm25_audit.py +186 -0
  18. package/python/bench/bm25_regression.py +177 -0
  19. package/python/bench/bm25_stress.py +155 -0
  20. package/python/bench/cd_audit.py +215 -0
  21. package/python/bench/chaos.py +140 -0
  22. package/python/bench/concurrency.py +103 -0
  23. package/python/bench/e2e_real_models.py +94 -0
  24. package/python/bench/llm_retry_test.py +241 -0
  25. package/python/bench/load_test.py +193 -0
  26. package/python/bench/vector_ivf_upgrade.py +128 -0
  27. package/python/docs/CHANNELS.md +195 -0
  28. package/python/docs/GOVERNANCE.md +164 -0
  29. package/python/docs/RELEASE.md +110 -0
  30. package/python/docs/RUN.md +75 -0
  31. package/python/docs/RUN_CN.md +121 -0
  32. package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
  33. package/python/evals/__init__.py +0 -0
  34. package/python/evals/governance/README.md +61 -0
  35. package/python/evals/governance/__init__.py +0 -0
  36. package/python/evals/governance/cases.py +115 -0
  37. package/python/evals/governance/compare_embeddings.py +94 -0
  38. package/python/evals/governance/convergence.svg +11 -0
  39. package/python/evals/governance/report.json +371 -0
  40. package/python/evals/governance/run_evals.py +368 -0
  41. package/python/examples/governance_middleware_demo.py +72 -0
  42. package/python/examples/langgraph_governance_demo.py +104 -0
  43. package/python/examples/sample_itinerary.csv +6 -0
  44. package/python/examples/sample_knowledge.csv +9 -0
  45. package/python/examples/sample_transactions.csv +14 -0
  46. package/python/foxclaw/__init__.py +408 -0
  47. package/python/foxclaw/agents/__init__.py +12 -0
  48. package/python/foxclaw/agents/agents.py +35 -0
  49. package/python/foxclaw/agents/base.py +794 -0
  50. package/python/foxclaw/agents/impl/__init__.py +1 -0
  51. package/python/foxclaw/agents/impl/_helpers.py +70 -0
  52. package/python/foxclaw/agents/impl/finance.py +231 -0
  53. package/python/foxclaw/agents/impl/office.py +350 -0
  54. package/python/foxclaw/agents/impl/supervisor.py +592 -0
  55. package/python/foxclaw/agents/mixins/_context.py +128 -0
  56. package/python/foxclaw/agents/mixins/augment.py +143 -0
  57. package/python/foxclaw/agents/mixins/llm.py +67 -0
  58. package/python/foxclaw/agents/mixins/memory.py +41 -0
  59. package/python/foxclaw/agents/mixins/preference.py +131 -0
  60. package/python/foxclaw/agents/mixins/prompt.py +71 -0
  61. package/python/foxclaw/agents/mixins/skillbox.py +58 -0
  62. package/python/foxclaw/agents/mixins/tool_exec.py +417 -0
  63. package/python/foxclaw/api/__init__.py +224 -0
  64. package/python/foxclaw/api/_state.py +43 -0
  65. package/python/foxclaw/api/bootstrap.py +96 -0
  66. package/python/foxclaw/api/middleware.py +294 -0
  67. package/python/foxclaw/api/routes/ahs.py +404 -0
  68. package/python/foxclaw/api/routes/attachments.py +116 -0
  69. package/python/foxclaw/api/routes/browser.py +35 -0
  70. package/python/foxclaw/api/routes/domain.py +306 -0
  71. package/python/foxclaw/api/routes/knowledge.py +145 -0
  72. package/python/foxclaw/api/routes/llm_admin.py +449 -0
  73. package/python/foxclaw/api/routes/memory.py +105 -0
  74. package/python/foxclaw/api/routes/orchestration.py +39 -0
  75. package/python/foxclaw/api/routes/recovery.py +44 -0
  76. package/python/foxclaw/api/routes/review.py +266 -0
  77. package/python/foxclaw/api/routes/search.py +33 -0
  78. package/python/foxclaw/api/routes/system.py +311 -0
  79. package/python/foxclaw/api/routes/task.py +263 -0
  80. package/python/foxclaw/api/routes/ws.py +64 -0
  81. package/python/foxclaw/api/spine.py +52 -0
  82. package/python/foxclaw/browser/__init__.py +13 -0
  83. package/python/foxclaw/browser/action/__init__.py +1 -0
  84. package/python/foxclaw/browser/action/action_executor.py +50 -0
  85. package/python/foxclaw/browser/action/action_types.py +44 -0
  86. package/python/foxclaw/browser/action/action_verifier.py +68 -0
  87. package/python/foxclaw/browser/action/url_policy.py +143 -0
  88. package/python/foxclaw/browser/adapter/__init__.py +1 -0
  89. package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
  90. package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
  91. package/python/foxclaw/browser/driver/__init__.py +1 -0
  92. package/python/foxclaw/browser/driver/base_driver.py +61 -0
  93. package/python/foxclaw/browser/driver/playwright_driver.py +170 -0
  94. package/python/foxclaw/browser/perception/__init__.py +1 -0
  95. package/python/foxclaw/browser/perception/dom_extractor.py +42 -0
  96. package/python/foxclaw/browser/perception/element_indexer.py +56 -0
  97. package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
  98. package/python/foxclaw/browser/perception/snapshot.py +29 -0
  99. package/python/foxclaw/browser/service.py +144 -0
  100. package/python/foxclaw/browser/session/__init__.py +1 -0
  101. package/python/foxclaw/browser/session/session_manager.py +57 -0
  102. package/python/foxclaw/capability/__init__.py +12 -0
  103. package/python/foxclaw/capability/embedding_service.py +65 -0
  104. package/python/foxclaw/channels/__init__.py +138 -0
  105. package/python/foxclaw/channels/discord.py +175 -0
  106. package/python/foxclaw/channels/dispatcher.py +179 -0
  107. package/python/foxclaw/channels/feishu.py +215 -0
  108. package/python/foxclaw/channels/openclaw_bridge.py +151 -0
  109. package/python/foxclaw/channels/signing.py +187 -0
  110. package/python/foxclaw/channels/telegram.py +120 -0
  111. package/python/foxclaw/channels/weixin.py +215 -0
  112. package/python/foxclaw/cli.py +183 -0
  113. package/python/foxclaw/cognitive/__init__.py +4 -0
  114. package/python/foxclaw/cognitive/reasoner.py +81 -0
  115. package/python/foxclaw/cognitive/state.py +141 -0
  116. package/python/foxclaw/core/__init__.py +9 -0
  117. package/python/foxclaw/core/audit_chain.py +131 -0
  118. package/python/foxclaw/core/audit_sink.py +100 -0
  119. package/python/foxclaw/core/compaction.py +265 -0
  120. package/python/foxclaw/core/config.py +65 -0
  121. package/python/foxclaw/core/confirm_nonce.py +151 -0
  122. package/python/foxclaw/core/dispatch.py +27 -0
  123. package/python/foxclaw/core/envfile.py +48 -0
  124. package/python/foxclaw/core/failure_policy.py +142 -0
  125. package/python/foxclaw/core/gateway.py +81 -0
  126. package/python/foxclaw/core/governance_audit.py +171 -0
  127. package/python/foxclaw/core/idempotency.py +282 -0
  128. package/python/foxclaw/core/injection_guard.py +202 -0
  129. package/python/foxclaw/core/memory_adapter.py +489 -0
  130. package/python/foxclaw/core/metrics.py +88 -0
  131. package/python/foxclaw/core/model_router.py +985 -0
  132. package/python/foxclaw/core/paths.py +103 -0
  133. package/python/foxclaw/core/recovery.py +349 -0
  134. package/python/foxclaw/core/scheduler.py +141 -0
  135. package/python/foxclaw/core/secrets.py +131 -0
  136. package/python/foxclaw/core/sessions.py +155 -0
  137. package/python/foxclaw/core/settings.py +414 -0
  138. package/python/foxclaw/core/shared_write.py +186 -0
  139. package/python/foxclaw/core/steering_queue.py +154 -0
  140. package/python/foxclaw/core/system_bus.py +84 -0
  141. package/python/foxclaw/core/thought_chain.py +316 -0
  142. package/python/foxclaw/core/token_meter.py +231 -0
  143. package/python/foxclaw/core/tools.py +164 -0
  144. package/python/foxclaw/core/tracing.py +375 -0
  145. package/python/foxclaw/core/truth_pipeline.py +110 -0
  146. package/python/foxclaw/core/update_check.py +149 -0
  147. package/python/foxclaw/core/vec_recall_adapter.py +477 -0
  148. package/python/foxclaw/core/verify.py +173 -0
  149. package/python/foxclaw/core/web_search.py +218 -0
  150. package/python/foxclaw/finance/__init__.py +15 -0
  151. package/python/foxclaw/finance/evidence.py +149 -0
  152. package/python/foxclaw/finance/fx.py +98 -0
  153. package/python/foxclaw/finance/graph.py +179 -0
  154. package/python/foxclaw/finance/models.py +146 -0
  155. package/python/foxclaw/finance/nlparse.py +515 -0
  156. package/python/foxclaw/finance/policy.py +301 -0
  157. package/python/foxclaw/finance/reasoner.py +121 -0
  158. package/python/foxclaw/finance/reasoning.py +319 -0
  159. package/python/foxclaw/finance/reconcile.py +175 -0
  160. package/python/foxclaw/finance/runtime.py +500 -0
  161. package/python/foxclaw/governance/__init__.py +24 -0
  162. package/python/foxclaw/governance/middleware.py +176 -0
  163. package/python/foxclaw/harness/__init__.py +17 -0
  164. package/python/foxclaw/harness/_base.py +66 -0
  165. package/python/foxclaw/harness/_policy.py +340 -0
  166. package/python/foxclaw/harness/drift.py +77 -0
  167. package/python/foxclaw/harness/event_bus.py +28 -0
  168. package/python/foxclaw/harness/harness.py +358 -0
  169. package/python/foxclaw/harness/loop_detection.py +145 -0
  170. package/python/foxclaw/itinerary/__init__.py +10 -0
  171. package/python/foxclaw/itinerary/amap_client.py +114 -0
  172. package/python/foxclaw/itinerary/models.py +80 -0
  173. package/python/foxclaw/itinerary/reasoner.py +96 -0
  174. package/python/foxclaw/itinerary/reasoning.py +168 -0
  175. package/python/foxclaw/itinerary/runtime.py +144 -0
  176. package/python/foxclaw/kernel/__init__.py +13 -0
  177. package/python/foxclaw/kernel/capabilities.py +72 -0
  178. package/python/foxclaw/kernel/policy_gate.py +108 -0
  179. package/python/foxclaw/kernel/process_manager.py +186 -0
  180. package/python/foxclaw/kernel/scheduler.py +125 -0
  181. package/python/foxclaw/kernel/sentinel.py +443 -0
  182. package/python/foxclaw/kernel/sentinel_client.py +243 -0
  183. package/python/foxclaw/kernel/spine.py +97 -0
  184. package/python/foxclaw/knowledge/__init__.py +360 -0
  185. package/python/foxclaw/knowledge/truth_crystallizer.py +89 -0
  186. package/python/foxclaw/learning/__init__.py +12 -0
  187. package/python/foxclaw/learning/knowledge_graph.py +144 -0
  188. package/python/foxclaw/learning/models.py +93 -0
  189. package/python/foxclaw/learning/reasoner.py +67 -0
  190. package/python/foxclaw/learning/runtime.py +351 -0
  191. package/python/foxclaw/learning/spaced_repetition.py +91 -0
  192. package/python/foxclaw/mcp_server.py +188 -0
  193. package/python/foxclaw/orchestration/__init__.py +167 -0
  194. package/python/foxclaw/pipeline/__init__.py +23 -0
  195. package/python/foxclaw/pipeline/cognitive_pipeline.py +429 -0
  196. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  197. package/python/foxclaw/pipeline/supervisor_stages.py +201 -0
  198. package/python/foxclaw/planning/__init__.py +17 -0
  199. package/python/foxclaw/planning/llm_planner.py +232 -0
  200. package/python/foxclaw/planning/planner.py +94 -0
  201. package/python/foxclaw/search/__init__.py +24 -0
  202. package/python/foxclaw/search/agent_port.py +190 -0
  203. package/python/foxclaw/search/evidence.py +109 -0
  204. package/python/foxclaw/search/external_search.py +50 -0
  205. package/python/foxclaw/search/hkc_read_port.py +60 -0
  206. package/python/foxclaw/search/internal_search.py +71 -0
  207. package/python/foxclaw/search/models.py +127 -0
  208. package/python/foxclaw/search/planner.py +44 -0
  209. package/python/foxclaw/search/retriever.py +39 -0
  210. package/python/foxclaw/search/runtime.py +127 -0
  211. package/python/foxclaw/search/strategy.py +44 -0
  212. package/python/foxclaw/skills/__init__.py +559 -0
  213. package/python/foxclaw/skills/archive.py +111 -0
  214. package/python/foxclaw/skills/fetch.py +226 -0
  215. package/python/foxclaw/skills/hooks_spec.py +69 -0
  216. package/python/foxclaw/skills/install_cmd.py +216 -0
  217. package/python/foxclaw/skills/lifecycle.py +252 -0
  218. package/python/foxclaw/skills/markdown_skill.py +175 -0
  219. package/python/foxclaw/skills/markdown_store.py +39 -0
  220. package/python/foxclaw/skills/mcp_client.py +184 -0
  221. package/python/foxclaw/skills/mcp_store.py +40 -0
  222. package/python/foxclaw/skills/permission.py +145 -0
  223. package/python/foxclaw/skills/pipeline.py +354 -0
  224. package/python/foxclaw/skills/policy.py +74 -0
  225. package/python/foxclaw/skills/review.py +72 -0
  226. package/python/foxclaw/skills/store.py +57 -0
  227. package/python/frontend/assets/core_bg.jpg +0 -0
  228. package/python/frontend/deck/auth.js +25 -0
  229. package/python/frontend/deck/chat_model_picker.js +483 -0
  230. package/python/frontend/deck/domain.js +276 -0
  231. package/python/frontend/deck/main.js +1806 -0
  232. package/python/frontend/deck/markdown.js +44 -0
  233. package/python/frontend/deck/settings.js +307 -0
  234. package/python/frontend/deck/state.js +5 -0
  235. package/python/frontend/deck/util.js +4 -0
  236. package/python/frontend/deck/visuals.js +190 -0
  237. package/python/frontend/deck.css +855 -0
  238. package/python/frontend/deck.html +686 -0
  239. package/python/frontend/deck_v2.html +341 -0
  240. package/python/frontend/planet/index.html +266 -0
  241. package/python/frontend/planet/planet.css +579 -0
  242. package/python/frontend/planet/planet.js +1436 -0
  243. package/python/frontend/planet/vendor/three.core.min.js +6 -0
  244. package/python/frontend/planet/vendor/three.module.min.js +6 -0
  245. package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
  246. package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
  247. package/python/frontend/v3/index.html +15 -0
  248. package/python/frontend/widget_runtime.js +252 -0
  249. package/python/frontend/widgets.js +639 -0
  250. package/python/frontend_src/index.html +14 -0
  251. package/python/frontend_src/package-lock.json +1376 -0
  252. package/python/frontend_src/package.json +19 -0
  253. package/python/frontend_src/src/App.vue +58 -0
  254. package/python/frontend_src/src/api.js +82 -0
  255. package/python/frontend_src/src/components/ChatDock.vue +365 -0
  256. package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
  257. package/python/frontend_src/src/components/DocViewer.vue +162 -0
  258. package/python/frontend_src/src/components/Inspector.vue +235 -0
  259. package/python/frontend_src/src/components/LeftRail.vue +415 -0
  260. package/python/frontend_src/src/components/MenuPopover.vue +128 -0
  261. package/python/frontend_src/src/components/MessageStream.vue +389 -0
  262. package/python/frontend_src/src/components/ModelPicker.vue +350 -0
  263. package/python/frontend_src/src/components/QuickIngest.vue +112 -0
  264. package/python/frontend_src/src/components/Stage.vue +272 -0
  265. package/python/frontend_src/src/components/ToolMenu.vue +141 -0
  266. package/python/frontend_src/src/components/TopBar.vue +118 -0
  267. package/python/frontend_src/src/components/panels/ActivityPanel.vue +82 -0
  268. package/python/frontend_src/src/components/panels/AgentsPanel.vue +83 -0
  269. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +425 -0
  270. package/python/frontend_src/src/components/panels/MemoryPanel.vue +117 -0
  271. package/python/frontend_src/src/components/panels/PanelBox.vue +50 -0
  272. package/python/frontend_src/src/components/panels/ReviewPanel.vue +292 -0
  273. package/python/frontend_src/src/components/panels/SettingsPanel.vue +358 -0
  274. package/python/frontend_src/src/components/panels/TasksPanel.vue +73 -0
  275. package/python/frontend_src/src/components/panels/ToolsPanel.vue +746 -0
  276. package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
  277. package/python/frontend_src/src/lib/fmt.js +44 -0
  278. package/python/frontend_src/src/lib/i18n.js +53 -0
  279. package/python/frontend_src/src/lib/locales/en.js +173 -0
  280. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  281. package/python/frontend_src/src/lib/md.js +190 -0
  282. package/python/frontend_src/src/main.js +9 -0
  283. package/python/frontend_src/src/stores/app.js +91 -0
  284. package/python/frontend_src/src/stores/chat.js +450 -0
  285. package/python/frontend_src/src/stores/model.js +96 -0
  286. package/python/frontend_src/src/stores/panels.js +289 -0
  287. package/python/frontend_src/src/stores/profile.js +31 -0
  288. package/python/frontend_src/src/stores/system.js +25 -0
  289. package/python/frontend_src/src/styles/base.css +138 -0
  290. package/python/frontend_src/src/styles/panel.css +117 -0
  291. package/python/frontend_src/src/styles/tokens.css +78 -0
  292. package/python/frontend_src/vite.config.js +39 -0
  293. package/python/pyproject.toml +268 -0
  294. package/python/requirements.txt +311 -0
  295. package/python/skills/README.md +123 -0
  296. package/python/skills/execution-contract/SKILL.md +30 -0
  297. package/python/skills/general-assistant/SKILL.md +34 -0
  298. package/python/skills/planner/SKILL.md +33 -0
  299. package/python/skills/schedule-itinerary/SKILL.md +35 -0
  300. package/python/skills/skill-composition/SKILL.md +31 -0
  301. package/python/skills/skill-guard/SKILL.md +67 -0
  302. package/python/skills/skill-memory/SKILL.md +50 -0
  303. package/python/skills/skill-router/SKILL.md +35 -0
  304. package/python/tests/conftest.py +54 -0
  305. package/python/tests/run_all.py +200 -0
  306. package/python/tests/test_agent_lifecycle.py +326 -0
  307. package/python/tests/test_ahs_p2.py +393 -0
  308. package/python/tests/test_anthropic_tools.py +192 -0
  309. package/python/tests/test_api.py +97 -0
  310. package/python/tests/test_api_auth.py +79 -0
  311. package/python/tests/test_api_host_guard.py +69 -0
  312. package/python/tests/test_attachments.py +151 -0
  313. package/python/tests/test_audit_chain.py +116 -0
  314. package/python/tests/test_audit_sink.py +130 -0
  315. package/python/tests/test_browser.py +149 -0
  316. package/python/tests/test_browser_request_guard.py +180 -0
  317. package/python/tests/test_capabilities.py +152 -0
  318. package/python/tests/test_channel_signing.py +185 -0
  319. package/python/tests/test_channel_trust.py +109 -0
  320. package/python/tests/test_channels.py +174 -0
  321. package/python/tests/test_cli.py +46 -0
  322. package/python/tests/test_compaction_toolpair.py +119 -0
  323. package/python/tests/test_config_layering.py +61 -0
  324. package/python/tests/test_confirm_nonce.py +186 -0
  325. package/python/tests/test_context_compaction_wiring.py +124 -0
  326. package/python/tests/test_dispatch.py +89 -0
  327. package/python/tests/test_domain_evals.py +334 -0
  328. package/python/tests/test_domain_import.py +72 -0
  329. package/python/tests/test_domain_reasoners.py +134 -0
  330. package/python/tests/test_e2e.py +161 -0
  331. package/python/tests/test_emb_cache.py +97 -0
  332. package/python/tests/test_embedding_singleton.py +87 -0
  333. package/python/tests/test_envfile.py +83 -0
  334. package/python/tests/test_evals_smoke.py +90 -0
  335. package/python/tests/test_event_spine.py +174 -0
  336. package/python/tests/test_evidence_semantic.py +88 -0
  337. package/python/tests/test_evidence_sse.py +94 -0
  338. package/python/tests/test_file_ingest.py +135 -0
  339. package/python/tests/test_finance.py +144 -0
  340. package/python/tests/test_finance_ledger.py +245 -0
  341. package/python/tests/test_finance_persist.py +174 -0
  342. package/python/tests/test_gateway.py +125 -0
  343. package/python/tests/test_governance.py +421 -0
  344. package/python/tests/test_governance_audit.py +324 -0
  345. package/python/tests/test_governance_middleware.py +137 -0
  346. package/python/tests/test_hkc.py +268 -0
  347. package/python/tests/test_hkc_index_build_manager.py +306 -0
  348. package/python/tests/test_hkc_manifest_reuse.py +294 -0
  349. package/python/tests/test_hkc_search_event_sync.py +482 -0
  350. package/python/tests/test_import_path_guard.py +155 -0
  351. package/python/tests/test_injection_guard.py +193 -0
  352. package/python/tests/test_integration.py +117 -0
  353. package/python/tests/test_itinerary.py +100 -0
  354. package/python/tests/test_kee_semantic.py +84 -0
  355. package/python/tests/test_kernel.py +212 -0
  356. package/python/tests/test_learning.py +101 -0
  357. package/python/tests/test_llm_admin.py +145 -0
  358. package/python/tests/test_llm_planner.py +344 -0
  359. package/python/tests/test_markdown_skills.py +62 -0
  360. package/python/tests/test_markdown_skills_openclaw.py +66 -0
  361. package/python/tests/test_mcp_connectors.py +97 -0
  362. package/python/tests/test_mcp_guardian_discovery.py +87 -0
  363. package/python/tests/test_mcp_no_memory.py +43 -0
  364. package/python/tests/test_mcp_policy_gate.py +76 -0
  365. package/python/tests/test_mcp_server.py +92 -0
  366. package/python/tests/test_memory_recall.py +92 -0
  367. package/python/tests/test_metrics.py +58 -0
  368. package/python/tests/test_multi_chain.py +90 -0
  369. package/python/tests/test_multimodal.py +122 -0
  370. package/python/tests/test_negotiation.py +136 -0
  371. package/python/tests/test_orchestration.py +109 -0
  372. package/python/tests/test_orchestration_decoupled.py +103 -0
  373. package/python/tests/test_p3_p5_hardening.py +324 -0
  374. package/python/tests/test_parallel_dispatch.py +207 -0
  375. package/python/tests/test_pipeline.py +492 -0
  376. package/python/tests/test_plan_visible.py +86 -0
  377. package/python/tests/test_planner.py +88 -0
  378. package/python/tests/test_policy_gate.py +108 -0
  379. package/python/tests/test_profile.py +67 -0
  380. package/python/tests/test_readme_claims.py +163 -0
  381. package/python/tests/test_reasoner.py +170 -0
  382. package/python/tests/test_release_check.py +252 -0
  383. package/python/tests/test_resume_checkpoint.py +148 -0
  384. package/python/tests/test_routing.py +85 -0
  385. package/python/tests/test_scheduler.py +138 -0
  386. package/python/tests/test_search.py +194 -0
  387. package/python/tests/test_secrets.py +153 -0
  388. package/python/tests/test_semantic.py +69 -0
  389. package/python/tests/test_sessions.py +90 -0
  390. package/python/tests/test_settings.py +265 -0
  391. package/python/tests/test_shadow_learning.py +290 -0
  392. package/python/tests/test_shared_write.py +200 -0
  393. package/python/tests/test_skill_taint_gate.py +145 -0
  394. package/python/tests/test_skills.py +117 -0
  395. package/python/tests/test_skills_fetch.py +282 -0
  396. package/python/tests/test_stats_and_events.py +162 -0
  397. package/python/tests/test_streaming.py +55 -0
  398. package/python/tests/test_supervisor_pipeline.py +176 -0
  399. package/python/tests/test_supervisor_triage.py +102 -0
  400. package/python/tests/test_taint.py +112 -0
  401. package/python/tests/test_task_stream.py +74 -0
  402. package/python/tests/test_thinking_sse.py +66 -0
  403. package/python/tests/test_thinking_stream.py +76 -0
  404. package/python/tests/test_tool_center.py +113 -0
  405. package/python/tests/test_tool_parallel.py +113 -0
  406. package/python/tests/test_tool_params.py +99 -0
  407. package/python/tests/test_tool_use.py +229 -0
  408. package/python/tests/test_toplevel_concurrency.py +116 -0
  409. package/python/tests/test_tracing.py +256 -0
  410. package/python/tests/test_truth_crystallizer.py +125 -0
  411. package/python/tests/test_truth_flow.py +88 -0
  412. package/python/tests/test_update_check.py +95 -0
  413. package/python/tests/test_url_policy.py +53 -0
  414. package/python/tests/test_verify_evidence.py +212 -0
  415. package/python/tests/test_version_sync.py +100 -0
  416. package/python/tests/test_web_trigger.py +82 -0
  417. package/python/tools/check_faiss_type.py +30 -0
  418. package/python/tools/check_log.ps1 +38 -0
  419. package/python/tools/ivf_status.py +72 -0
  420. package/python/tools/mypy_baseline.txt +13 -0
  421. package/python/tools/mypy_ratchet.py +143 -0
  422. package/python/tools/pack.py +173 -0
  423. package/python/tools/release_check.py +349 -0
  424. package/python/tools/restart_foxclaw.ps1 +71 -0
  425. package/python/tools/shared_write_audit.py +190 -0
  426. package/python/tools/size_baseline.txt +28 -0
  427. package/python/tools/size_check.py +102 -0
  428. package/python/tools/strip_comments.py +228 -0
  429. package/python/tools/verify_env.bat +2 -0
  430. package/python/tools/verify_env.py +25 -0
  431. package/python/vendor/HMR_LICENSE +37 -0
  432. package/python/vendor/HMR_README.md +65 -0
  433. package/python/vendor/hkc/LICENSE +37 -0
  434. package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
  435. package/python/vendor/hkc/hkc_ace/ace.py +457 -0
  436. package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
  437. package/python/vendor/hkc/hkc_api/__init__.py +0 -0
  438. package/python/vendor/hkc/hkc_api/container.py +416 -0
  439. package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
  440. package/python/vendor/hkc/hkc_api/main.py +157 -0
  441. package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
  442. package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
  443. package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
  444. package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
  445. package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
  446. package/python/vendor/hkc/hkc_api/schemas.py +153 -0
  447. package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
  448. package/python/vendor/hkc/hkc_core/__init__.py +0 -0
  449. package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
  450. package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
  451. package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
  452. package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
  453. package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
  454. package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
  455. package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
  456. package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
  457. package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
  458. package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
  459. package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
  460. package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
  461. package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
  462. package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
  463. package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
  464. package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
  465. package/python/vendor/hkc/hkc_kde/kde.py +298 -0
  466. package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
  467. package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
  468. package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
  469. package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
  470. package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
  471. package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
  472. package/python/vendor/hkc/hkc_kde/models.py +74 -0
  473. package/python/vendor/hkc/hkc_kde/packager.py +329 -0
  474. package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
  475. package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
  476. package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
  477. package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
  478. package/python/vendor/hkc/hkc_kee/kee.py +541 -0
  479. package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
  480. package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
  481. package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
  482. package/python/vendor/hkc/hkc_sdk/base.py +151 -0
  483. package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
  484. package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
  485. package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
  486. package/python/vendor/hkc/hkc_sdk/models.py +171 -0
  487. package/python/vendor/hkc/hkc_search/__init__.py +0 -0
  488. package/python/vendor/hkc/hkc_search/bm25.py +325 -0
  489. package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
  490. package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
  491. package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
  492. package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
  493. package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
  494. package/python/vendor/hmr/__init__.py +0 -0
  495. package/python/vendor/hmr/core/__init__.py +0 -0
  496. package/python/vendor/hmr/core/hmr.py +684 -0
  497. package/python/vendor/hmr/core/models.py +310 -0
  498. package/python/vendor/hmr/engines/__init__.py +0 -0
  499. package/python/vendor/hmr/engines/evolution.py +710 -0
  500. package/python/vendor/hmr/engines/jit_compiler.py +472 -0
  501. package/python/vendor/hmr/engines/lifecycle.py +264 -0
  502. package/python/vendor/hmr/engines/policy.py +625 -0
  503. package/python/vendor/hmr/engines/recall.py +260 -0
  504. package/python/vendor/hmr/engines/runtime_state.py +158 -0
  505. package/python/vendor/hmr/engines/scheduler.py +323 -0
  506. package/python/vendor/hmr/engines/semantic.py +150 -0
  507. package/python/vendor/hmr/engines/temporal.py +261 -0
  508. package/python/vendor/hmr/engines/thought_chain.py +635 -0
  509. package/python/vendor/hmr/graph/__init__.py +0 -0
  510. package/python/vendor/hmr/graph/cwg.py +338 -0
  511. package/python/vendor/hmr/graph/memory_graph.py +530 -0
  512. package/python/vendor/hmr/storage/__init__.py +0 -0
  513. package/python/vendor/hmr/storage/memory_fs.py +300 -0
  514. package/python/vendor/hmr/storage/vector_store.py +520 -0
@@ -0,0 +1,145 @@
1
+ """
2
+ 工具权限管线 (Permission Pipeline) —— HHS 标准 skill-guard 的代码落地。
3
+
4
+ 把抽象的「四阶段权限管线」实现为可运行代码:
5
+ Stage 1 validate —— 输入合法性(参数 schema)
6
+ Stage 2 rule-match —— deny > ask > allow 优先级铁律
7
+ Stage 3 capability —— read_only / destructive / external,Fail-closed
8
+ Stage 4 confirm —— 有副作用/危险操作需确认
9
+
10
+ 对应 docs/internal/HARNESS_STANDARD.md 的 A 类工具权限护栏。
11
+ """
12
+ from __future__ import annotations
13
+ from dataclasses import dataclass, field
14
+ from typing import Literal, Optional
15
+
16
+
17
+ Decision = Literal["allow", "ask", "deny"]
18
+
19
+ MISSING_PARAMS = "MISSING_PARAMS"
20
+ DESTRUCTIVE_CONFIRM = "DESTRUCTIVE_CONFIRM"
21
+ SIDE_EFFECT_CONFIRM = "SIDE_EFFECT_CONFIRM"
22
+ ASK_RULE = "ASK_RULE"
23
+ EXTERNAL_CONFIRM = "EXTERNAL_CONFIRM"
24
+ TAINT_CONFIRM = "TAINT_CONFIRM"
25
+
26
+
27
+ @dataclass
28
+ class PermissionRules:
29
+ """宿主提供的规则集。deny 优先级最高且不可覆盖。"""
30
+ deny: list = field(default_factory=list)
31
+ ask: list = field(default_factory=list)
32
+ allow: list = field(default_factory=list)
33
+
34
+ @staticmethod
35
+ def _match(patterns: list, skill_id: str) -> bool:
36
+ import fnmatch
37
+ return any(fnmatch.fnmatch(skill_id, p) for p in patterns)
38
+
39
+ def hits_deny(self, sid: str) -> bool:
40
+ return self._match(self.deny, sid)
41
+
42
+ def hits_ask(self, sid: str) -> bool:
43
+ return self._match(self.ask, sid)
44
+
45
+ def hits_allow(self, sid: str) -> bool:
46
+ return self._match(self.allow, sid)
47
+
48
+
49
+ @dataclass
50
+ class PermissionDecision:
51
+ decision: Decision
52
+ stage: int
53
+ reason: str = ""
54
+ trust_downgrade: bool = False
55
+ requires_confirmation: bool = False
56
+ reason_code: str = ""
57
+
58
+ def to_dict(self) -> dict:
59
+ return {
60
+ "decision": self.decision, "stage": self.stage, "reason": self.reason,
61
+ "trust_downgrade": self.trust_downgrade,
62
+ "requires_confirmation": self.requires_confirmation,
63
+ "reason_code": self.reason_code,
64
+ }
65
+
66
+
67
+ def evaluate(skill, args: dict, rules: Optional[PermissionRules] = None,
68
+ confirmed: bool = False, mode: str = "strict",
69
+ taint_level: int = 0, taint_threshold: int = 0) -> PermissionDecision:
70
+ """四阶段权限管线。返回 allow / ask / deny。
71
+
72
+ skill: SkillSpec(需有 id / side_effect / kind,及可选 destructive/external/read_only)
73
+
74
+ 【P-Muse·污点门槛】taint_level = 本会话已取回数据的最高敏感度(见
75
+ core.injection_guard.taint_level);taint_threshold = 触发二次确认的阈值。
76
+ 两者默认 0 = 关闭,既有调用方行为逐字不变。仅当「污点达标 且 动作为外发/副作用」时,
77
+ 在 Stage 3 强制升级为 ask(TAINT_CONFIRM)——对齐 Muse「碰过敏感数据 → 动态提门槛」。
78
+ """
79
+ rules = rules or PermissionRules()
80
+ sid = skill.id
81
+
82
+ params = getattr(skill, "params", {}) or {}
83
+ missing = []
84
+ for k, spec in params.items():
85
+ required = isinstance(spec, dict) and spec.get("required")
86
+ if required and k not in (args or {}):
87
+ missing.append(k)
88
+ if missing:
89
+ return PermissionDecision("ask", 1,
90
+ reason=f"缺少必填参数: {', '.join(missing)}",
91
+ requires_confirmation=True, reason_code=MISSING_PARAMS)
92
+
93
+ if rules.hits_deny(sid):
94
+ return PermissionDecision("deny", 2, reason=f"命中 deny 规则(不可覆盖): {sid}")
95
+ ask_by_rule = rules.hits_ask(sid)
96
+ allow_by_rule = rules.hits_allow(sid)
97
+
98
+ side_effect = getattr(skill, "side_effect", "write")
99
+ destructive = getattr(skill, "destructive", False)
100
+ external = getattr(skill, "external", False)
101
+ _ro = getattr(skill, "read_only", None)
102
+ read_only = (side_effect == "read") if _ro is None else _ro
103
+ _tl = getattr(skill, "trust_level", None)
104
+ try:
105
+ trust = _tl() if callable(_tl) else "low"
106
+ except Exception:
107
+ trust = "low"
108
+ downgrade = trust == "low"
109
+
110
+ if mode == "readonly" and not read_only:
111
+ return PermissionDecision("deny", 3,
112
+ reason="readonly 模式下禁止非只读操作", trust_downgrade=downgrade)
113
+
114
+ if destructive and not confirmed:
115
+ return PermissionDecision("ask", 3,
116
+ reason="不可逆(destructive)操作,必须用户确认",
117
+ trust_downgrade=downgrade, requires_confirmation=True,
118
+ reason_code=DESTRUCTIVE_CONFIRM)
119
+
120
+ if taint_threshold and taint_level >= taint_threshold and not confirmed:
121
+ if side_effect in ("write", "send", "pay") or external:
122
+ return PermissionDecision("ask", 3,
123
+ reason="会话已取回用户敏感数据,外发/副作用动作需二次确认(污点防护)",
124
+ trust_downgrade=downgrade, requires_confirmation=True,
125
+ reason_code=TAINT_CONFIRM)
126
+
127
+ if mode == "trusted":
128
+ return PermissionDecision("allow", 3,
129
+ reason="trusted 模式放行", trust_downgrade=downgrade)
130
+
131
+ needs_confirm = (
132
+ ask_by_rule or external or side_effect in ("write", "send", "pay")
133
+ )
134
+ if needs_confirm and not confirmed:
135
+ why = ("命中 ask 规则" if ask_by_rule else
136
+ "外部调用" if external else f"{side_effect} 副作用")
137
+ code = (ASK_RULE if ask_by_rule else
138
+ EXTERNAL_CONFIRM if external else SIDE_EFFECT_CONFIRM)
139
+ return PermissionDecision("ask", 4,
140
+ reason=f"需确认: {why}", trust_downgrade=downgrade,
141
+ requires_confirmation=True, reason_code=code)
142
+
143
+ reason = ("命中 allow 规则" if allow_by_rule else
144
+ "只读无副作用" if read_only else "已确认")
145
+ return PermissionDecision("allow", 4, reason=reason, trust_downgrade=downgrade)
@@ -0,0 +1,354 @@
1
+ """
2
+ AHS 执行链 (Execution Pipeline) —— Agent Harness Skills 标准的全面落地。
3
+
4
+ 把 6 个元 skill 串成一条可运行的执行链:
5
+
6
+ task → [router 选主skill] → 复杂? → [planner 拆解]
7
+ → [composition 编排多skill]
8
+ 每步 skill 执行都过 [guard 权限] + [breaker 断路] + [hooks 钩子]
9
+ 全程 IO 遵循 [execution-contract],结果可审计
10
+
11
+ router / planner / composition 在这里实现;guard / breaker / hooks 复用
12
+ permission.py + lifecycle.py;contract 复用 SkillResult。
13
+ """
14
+ from __future__ import annotations
15
+ import asyncio
16
+ import inspect
17
+ import re
18
+ from dataclasses import dataclass, field
19
+ from typing import Optional
20
+
21
+ from ..planning import planner as P
22
+
23
+
24
+ @dataclass
25
+ class RouteResult:
26
+ primary: Optional[str] = None
27
+ confidence: float = 0.0
28
+ candidates: list = field(default_factory=list)
29
+ fallback: Optional[str] = None
30
+ needs_composition: bool = False
31
+ reason: str = ""
32
+
33
+
34
+ def route(task: str, registry, fallback_id: str | None = None) -> RouteResult:
35
+ """skill-router:把 skill 适配成通用候选,交统一引擎 trigger_route 打分选择。
36
+ 算法(否定过滤触发命中 0.8 + 描述领域匹配 0.2)现由 foxclaw.planning 单一真源提供。"""
37
+ skills = registry.all()
38
+ if not skills:
39
+ return RouteResult(reason="注册表为空")
40
+ cands = [P.ScoredCandidate(
41
+ id=s.id,
42
+ triggers=getattr(s, "triggers", []) or [],
43
+ description=getattr(s, "description", "") or "")
44
+ for s in skills]
45
+ r = P.trigger_route(task, cands, fallback_id=fallback_id)
46
+ return RouteResult(primary=r.primary, confidence=r.confidence,
47
+ candidates=r.candidates, fallback=r.fallback,
48
+ needs_composition=r.needs_composition, reason=r.reason)
49
+
50
+
51
+ @dataclass
52
+ class PlanTask:
53
+ id: str
54
+ name: str
55
+ suggested_skill: Optional[str] = None
56
+ dependencies: list = field(default_factory=list)
57
+
58
+
59
+ @dataclass
60
+ class Plan:
61
+ intent: str
62
+ tasks: list = field(default_factory=list)
63
+ flow_type: str = "sequential"
64
+ steps: list = field(default_factory=list)
65
+ needs_input: bool = False
66
+ missing: list = field(default_factory=list)
67
+
68
+
69
+ def plan(task: str, registry) -> Plan:
70
+ """planner:把任务拆成结构化计划。拆分/并行标记与算法由 foxclaw.planning 单一真源提供。
71
+
72
+ - 含并行连接词(同时/分别/并行)→ 拆成**无依赖**子任务,flow_type=parallel;
73
+ - 含顺序连接词(然后/接着…)→ 拆成**线性依赖链**,flow_type=sequential;
74
+ - 都没有 → 单步。
75
+ 依赖关系决定能否并行:parallel 的子任务 dependencies 为空,sequential 的链式依赖前一步。
76
+ """
77
+ task = (task or "").strip()
78
+ if not task:
79
+ return Plan(intent="", needs_input=True, missing=["task"])
80
+
81
+ if any(k in task for k in P.PARALLEL_TRIGGERS):
82
+ parts = P.split_on(task, P.PARALLEL_TRIGGERS + P.LIST_SEPS)
83
+ flow = "parallel" if len(parts) > 1 else "sequential"
84
+ else:
85
+ parts = P.split_on(task, P.SPLIT_MARKERS)
86
+ flow = "sequential"
87
+ if len(parts) <= 1:
88
+ parts, flow = [task], "sequential"
89
+
90
+ tasks, steps = [], []
91
+ for i, p in enumerate(parts, 1):
92
+ r = route(p, registry)
93
+ tid = f"T{i}"
94
+ deps = [f"T{i-1}"] if (i > 1 and flow == "sequential") else []
95
+ tasks.append(PlanTask(id=tid, name=p, suggested_skill=r.primary,
96
+ dependencies=deps))
97
+ steps.append(tid)
98
+
99
+ return Plan(intent=task, tasks=tasks, flow_type=flow, steps=steps)
100
+
101
+
102
+ @dataclass
103
+ class StageResult:
104
+ stage: int
105
+ skill_id: str
106
+ result: dict
107
+
108
+
109
+ @dataclass
110
+ class CompositionResult:
111
+ mode: str
112
+ stages: list = field(default_factory=list)
113
+ final_output: dict = field(default_factory=dict)
114
+ failed: list = field(default_factory=list)
115
+ rolled_back: bool = False
116
+ rollback_clean: bool = True
117
+ rollback_log: list = field(default_factory=list)
118
+ needs_manual: list = field(default_factory=list)
119
+ pending_confirmation: Optional[dict] = None
120
+ completed_steps: list = field(default_factory=list)
121
+
122
+ @property
123
+ def is_pending(self) -> bool:
124
+ """【P2-2】是否处于待确认暂停态。"""
125
+ return self.pending_confirmation is not None
126
+
127
+ @property
128
+ def is_failed(self) -> bool:
129
+ return bool(self.failed)
130
+
131
+
132
+ def _build_args(skill, task_text: str, upstream: dict) -> dict:
133
+ """把任务文本映射到 skill 声明的参数。
134
+
135
+ 简化策略:单参数 skill → 把任务文本(或提取的算式)塞给那个参数;
136
+ 多参数 skill → 交给 skill 自己解析(传 task + upstream)。
137
+ 真实系统里这一步由 LLM 做参数抽取;此处做轻量映射保证可运行。
138
+ """
139
+ params = skill.params or {}
140
+ if not params:
141
+ return {}
142
+ if "expression" in params:
143
+ m = re.search(r"[-+*/().\d\s]+", task_text)
144
+ expr = (m.group(0).strip() if m else task_text)
145
+ return {"expression": expr or task_text}
146
+ if len(params) == 1:
147
+ only = next(iter(params))
148
+ return {only: task_text}
149
+ return {"task": task_text, "upstream_outputs": dict(upstream)}
150
+
151
+
152
+ def _rollback(executed, registry, res: CompositionResult):
153
+ """逆序回滚已执行且有副作用的步骤。
154
+
155
+ executed: [(skill_id, output)] 按执行顺序。
156
+ 只回滚有副作用(非只读)的;有 compensate 的调用补偿,无的标记需人工处理。
157
+ """
158
+ res.rolled_back = True
159
+ for sid, output in reversed(executed):
160
+ skill = registry.get(sid)
161
+ if skill is None:
162
+ continue
163
+ side_effect = getattr(skill, "side_effect", "read")
164
+ if side_effect == "read":
165
+ continue
166
+ comp = getattr(skill, "compensate", None)
167
+ if comp is not None:
168
+ try:
169
+ comp(output)
170
+ res.rollback_log.append({"skill": sid, "status": "compensated",
171
+ "detail": "已调用补偿动作撤销副作用"})
172
+ except Exception as e:
173
+ res.rollback_log.append({"skill": sid, "status": "compensate_failed",
174
+ "detail": f"补偿失败: {e}"})
175
+ res.rollback_clean = False
176
+ res.needs_manual.append(sid)
177
+ else:
178
+ res.rollback_log.append({"skill": sid, "status": "manual_required",
179
+ "detail": f"{side_effect} 操作无补偿动作,需人工处理"})
180
+ res.rollback_clean = False
181
+ res.needs_manual.append(sid)
182
+
183
+
184
+ def _invoke(adapter, skill, args: dict, confirmed: bool, confirm_nonce: str):
185
+ """调 `adapter.invoke`,老 adapter 自动降级为 4 参旧签名。
186
+
187
+ 测试/第三方 adapter 常只实现 `invoke(skill, args, confirmed=, mode=)`;用签名探测
188
+ 而不是 try/except TypeError —— 后者会把 invoke 内部真正的 TypeError 当"签名不兼容"吞掉。
189
+ """
190
+ fn = adapter.invoke
191
+ try:
192
+ params = inspect.signature(fn).parameters
193
+ accepts = ("confirm_nonce" in params
194
+ or any(p.kind is p.VAR_KEYWORD for p in params.values()))
195
+ except (TypeError, ValueError):
196
+ accepts = False
197
+ if accepts:
198
+ return fn(skill, args, confirmed=confirmed, confirm_nonce=confirm_nonce)
199
+ return fn(skill, args, confirmed=confirmed)
200
+
201
+
202
+ def compose(plan_obj: Plan, registry, adapter, confirmed: bool = False,
203
+ resume_state: dict | None = None) -> CompositionResult:
204
+ """skill-composition:按计划顺序执行各 skill。
205
+
206
+ 结束态三种:
207
+ · success —— 全部成功
208
+ · pending —— 遇到 needs_confirmation,**暂停(不回滚)**,待用户确认后续跑
209
+ · failed —— 遇到 blocked/circuit_open/error/timeout,逆序回滚补偿
210
+
211
+ 【P2-2】needs_confirmation 不再当失败处理:暂停而非回滚,避免撤销已完成的
212
+ 有效步骤(反例:创建日程已成功、发邮件待确认,旧逻辑会把日程回滚掉)。
213
+ 可选 resume_state(上次 compose 的 pending_confirmation)用于从断点续跑:
214
+ 恢复已完成步骤的 upstream/executed 现场,跳过 resume_at 之前的步骤。
215
+ """
216
+ res = CompositionResult(mode=plan_obj.flow_type)
217
+ upstream = {}
218
+ executed = []
219
+
220
+ resume_at = None
221
+ if resume_state:
222
+ for tid, sid, output in resume_state.get("completed_steps", []):
223
+ upstream[sid] = output
224
+ executed.append((sid, output))
225
+ res.completed_steps.append((tid, sid, output))
226
+ resume_at = resume_state.get("resume_at")
227
+
228
+ for i, t in enumerate(plan_obj.tasks, 1):
229
+ if resume_at:
230
+ if t.id != resume_at:
231
+ continue
232
+ resume_at = None
233
+ sid = t.suggested_skill
234
+ if not sid or not registry.get(sid):
235
+ res.failed.append(t.id); continue
236
+ skill = registry.get(sid)
237
+ call_args = _build_args(skill, t.name, upstream)
238
+ _nonce = (resume_state or {}).get("confirm_nonce", "") or ""
239
+ out = _invoke(adapter, skill, call_args, confirmed, _nonce)
240
+ res.stages.append(StageResult(stage=i, skill_id=sid, result=out.to_dict()))
241
+ if out.success:
242
+ upstream[sid] = out.output
243
+ executed.append((sid, out.output))
244
+ res.completed_steps.append((t.id, sid, out.output))
245
+ elif out.status == "needs_confirmation":
246
+ res.pending_confirmation = {
247
+ "task_id": t.id,
248
+ "skill_id": sid,
249
+ "stage": i,
250
+ "message": out.message,
251
+ "args": call_args,
252
+ "completed_steps": list(res.completed_steps),
253
+ "resume_at": t.id,
254
+ "confirm_nonce": getattr(out, "confirm_nonce", "") or "",
255
+ "confirm_expires_at": getattr(out, "confirm_expires_at", 0.0) or 0.0,
256
+ }
257
+ res.final_output = {
258
+ "pending": True,
259
+ "at": t.id,
260
+ "completed": [s[0] for s in res.completed_steps],
261
+ }
262
+ return res
263
+ else:
264
+ res.failed.append(t.id)
265
+ res.final_output = {"error": out.message, "failed_at": t.id}
266
+ _rollback(executed, registry, res)
267
+ return res
268
+ res.final_output = upstream
269
+ return res
270
+
271
+
272
+ @dataclass
273
+ class PipelineResult:
274
+ task: str
275
+ route: dict
276
+ plan: dict
277
+ composition: dict
278
+ success: bool
279
+ summary: str
280
+
281
+ @property
282
+ def pending_confirmation(self) -> dict | None:
283
+ """【P2-2】待确认状态(若无则 None)。结构见 compose 的 pending_confirmation。"""
284
+ return self.composition.get("pending_confirmation")
285
+
286
+ @property
287
+ def is_pending(self) -> bool:
288
+ """【P2-2】是否处于待确认暂停态(此时 success=False,但语义是"暂停等确认")。"""
289
+ return self.pending_confirmation is not None
290
+
291
+
292
+ class AHSPipeline:
293
+ """AHS 执行链编排器:一条龙跑通 router→planner→composition,全程过 guard+breaker+hooks。"""
294
+
295
+ def __init__(self, registry, adapter):
296
+ self.registry = registry
297
+ self.adapter = adapter
298
+
299
+ def run(self, task: str, confirmed: bool = False,
300
+ resume_state: dict | None = None, confirm_nonce: str = "") -> PipelineResult:
301
+ """同步执行。【P2-2】resume_state 用于从待确认状态断点续跑
302
+ (传上次返回的 pending_confirmation;确认后调 run(task, confirmed=True, resume_state=...))。
303
+
304
+ `confirm_nonce`(关键操作):compose 会把它并进 resume_state 传给 adapter 校验;
305
+ 也可以直接放在 resume_state 里(pending_confirmation 已带出该字段)。
306
+ """
307
+ if confirm_nonce:
308
+ resume_state = {**(resume_state or {}), "confirm_nonce": confirm_nonce}
309
+ self.adapter.hooks.fire("session_start", "", {"task": task})
310
+
311
+ r = route(task, self.registry)
312
+
313
+ p = plan(task, self.registry)
314
+
315
+ c = compose(p, self.registry, self.adapter,
316
+ confirmed=confirmed, resume_state=resume_state)
317
+
318
+ self.adapter.hooks.fire("session_end", "", {"task": task})
319
+
320
+ success = not c.failed and bool(c.stages) and not c.pending_confirmation
321
+ if c.pending_confirmation:
322
+ summary = (f"执行 {len(c.stages)} 步,待确认:"
323
+ f"{c.pending_confirmation['skill_id']}")
324
+ elif c.failed:
325
+ summary = f"执行 {len(c.stages)} 步,部分失败:{','.join(c.failed)}"
326
+ else:
327
+ summary = f"执行 {len(c.stages)} 步,全部成功"
328
+ return PipelineResult(
329
+ task=task,
330
+ route={"primary": r.primary, "confidence": r.confidence,
331
+ "needs_composition": r.needs_composition},
332
+ plan={"intent": p.intent, "steps": p.steps,
333
+ "tasks": [{"id": t.id, "name": t.name, "skill": t.suggested_skill}
334
+ for t in p.tasks]},
335
+ composition={"mode": c.mode, "stages": len(c.stages),
336
+ "failed": c.failed, "final": c.final_output,
337
+ "rolled_back": c.rolled_back, "rollback_clean": c.rollback_clean,
338
+ "rollback_log": c.rollback_log, "needs_manual": c.needs_manual,
339
+ "pending_confirmation": c.pending_confirmation,
340
+ "completed_steps": [s[0] for s in c.completed_steps]},
341
+ success=success, summary=summary)
342
+
343
+ async def run_async(self, task: str, confirmed: bool = False,
344
+ resume_state: dict | None = None,
345
+ confirm_nonce: str = "") -> PipelineResult:
346
+ """【P2-1】异步入口:把同步 run 放到线程池,避免阻塞事件循环。
347
+
348
+ 内部用 asyncio.to_thread 包装,故 run 里的同步 IO(MCP/Web API/本地 handler)
349
+ 不会阻塞调用方的事件循环。async 调用方(如 /api/ahs/run)应调本方法。
350
+ 注意:asyncio.to_thread 会复制当前 contextvars 到子线程(如 skill actor),
351
+ 子线程异常原样抛回 await 处——两者都是想要的行为。
352
+ """
353
+ return await asyncio.to_thread(self.run, task, confirmed, resume_state,
354
+ confirm_nonce)
@@ -0,0 +1,74 @@
1
+ """全局技能策略:deny 封杀 + allow 白名单(治理 kill-switch,借鉴 OpenClaw plugins.deny/allow)。
2
+ deny 压倒一切;allow 非空则只放行白名单内。user 层持久化 <data_dir>/skill_policy.json。
3
+ E1 分层:另有 **policy 层** deny(管理员锁定,env FOXCLAW_POLICY_DENY + <data_dir>/policy.json,与
4
+ user 层物理分开),**不可被运行时 undeny 撤销**;经 core/config.py 的 LayeredConfig 合并(policy∪user)。"""
5
+ from __future__ import annotations
6
+ import json
7
+ import threading
8
+ from pathlib import Path
9
+
10
+
11
+ class SkillPolicy:
12
+ def __init__(self, data_dir):
13
+ self._path = Path(data_dir) / "skill_policy.json"
14
+ self._lock = threading.RLock()
15
+ self._deny: set = set()
16
+ self._allow: set = set()
17
+ from ..core.config import LayeredConfig, load_policy_layer
18
+ policy_layer = load_policy_layer(data_dir)
19
+ self._layers = LayeredConfig(policy=policy_layer, user={"deny": self._deny})
20
+ self._load()
21
+
22
+ def is_blocked(self, skill_id: str) -> bool:
23
+ with self._lock:
24
+ if skill_id in self._layers.merged_set("deny"):
25
+ return True
26
+ if self._allow and skill_id not in self._allow:
27
+ return True
28
+ return False
29
+
30
+ def deny(self, skill_id: str):
31
+ with self._lock:
32
+ self._deny.add(skill_id)
33
+ self._save()
34
+
35
+ def undeny(self, skill_id: str) -> bool:
36
+ with self._lock:
37
+ if skill_id in self._layers.policy_locked("deny"):
38
+ return False
39
+ existed = skill_id in self._deny
40
+ self._deny.discard(skill_id)
41
+ self._save()
42
+ return existed
43
+
44
+ def set_allow(self, ids):
45
+ with self._lock:
46
+ self._allow = set(ids or [])
47
+ self._save()
48
+
49
+ def snapshot(self) -> dict:
50
+ with self._lock:
51
+ return {"deny": sorted(self._deny), "allow": sorted(self._allow),
52
+ "policy_deny": sorted(self._layers.policy_locked("deny"))}
53
+
54
+ def _load(self):
55
+ if not self._path.exists():
56
+ return
57
+ try:
58
+ d = json.loads(self._path.read_text(encoding="utf-8"))
59
+ self._deny.clear()
60
+ self._deny.update(d.get("deny") or [])
61
+ self._allow = set(d.get("allow") or [])
62
+ except Exception:
63
+ pass
64
+
65
+ def _save(self):
66
+ with self._lock:
67
+ payload = {"deny": sorted(self._deny), "allow": sorted(self._allow)}
68
+ try:
69
+ self._path.parent.mkdir(parents=True, exist_ok=True)
70
+ tmp = self._path.with_suffix(".tmp")
71
+ tmp.write_text(json.dumps(payload, ensure_ascii=False, indent=1), encoding="utf-8")
72
+ tmp.replace(self._path)
73
+ except Exception:
74
+ pass
@@ -0,0 +1,72 @@
1
+ """安装审核门:技能注册前的治理审核(对齐 OpenClaw security.installPolicy)。
2
+ 非杀毒——远程 URL 无法扫描;做 SSRF 预检/域名黑名单/明文告警/副作用与信任提示,
3
+ 产出 allow|warn|deny,风险项要求用户显式确认。"""
4
+ from __future__ import annotations
5
+ import os
6
+ import ipaddress
7
+ import socket
8
+ from urllib.parse import urlparse
9
+
10
+
11
+ def _host_deny() -> set:
12
+ return {h.strip().lower() for h in os.environ.get("FOXCLAW_SKILL_HOST_DENY", "").split(",") if h.strip()}
13
+
14
+
15
+ def _is_internal_host(host: str) -> bool:
16
+ if not host:
17
+ return True
18
+ if host.lower() == "localhost":
19
+ return True
20
+ try:
21
+ infos = socket.getaddrinfo(host, None)
22
+ except Exception:
23
+ return False
24
+ for info in infos:
25
+ try:
26
+ addr = ipaddress.ip_address(info[4][0])
27
+ except ValueError:
28
+ continue
29
+ if addr.is_private or addr.is_loopback or addr.is_link_local or addr.is_reserved:
30
+ return True
31
+ if str(addr) == "169.254.169.254":
32
+ return True
33
+ return False
34
+
35
+
36
+ def review_install(spec) -> dict:
37
+ """返回 {verdict: allow|warn|deny, reasons: [...], warnings: [...]}。"""
38
+ reasons, warnings = [], []
39
+ if spec.kind == "web_api":
40
+ host = (urlparse(spec.endpoint).hostname or "").lower()
41
+ if host in _host_deny():
42
+ reasons.append(f"域名 {host} 在黑名单")
43
+ if not spec.allow_local and _is_internal_host(host):
44
+ reasons.append(f"endpoint 指向内网/本地/元数据地址({host or '?'}),已拦截(SSRF)")
45
+ if spec.endpoint.startswith("http://"):
46
+ warnings.append("使用明文 HTTP,传输不加密")
47
+ if spec.kind == "mcp":
48
+ warnings.append("MCP 来源为外部低信任,其返回内容不应被当作指令执行")
49
+ if spec.side_effect in ("write", "send", "pay") or spec.destructive:
50
+ warnings.append(f"该技能有副作用({spec.side_effect}{'/不可逆' if spec.destructive else ''}),会真实影响外部")
51
+ if reasons:
52
+ return {"verdict": "deny", "reasons": reasons, "warnings": warnings}
53
+ if warnings:
54
+ return {"verdict": "warn", "reasons": [], "warnings": warnings}
55
+ return {"verdict": "allow", "reasons": [], "warnings": []}
56
+
57
+
58
+ def review_mcp_server(url: str) -> dict:
59
+ """MCP server 接入审核。外部低信任 → 恒 warn(需确认);内网/元数据/黑名单 → deny。"""
60
+ from urllib.parse import urlparse
61
+ reasons, warnings = [], []
62
+ host = (urlparse(url).hostname or "").lower()
63
+ if host in _host_deny():
64
+ reasons.append(f"域名 {host} 在黑名单")
65
+ if _is_internal_host(host):
66
+ reasons.append(f"MCP url 指向内网/本地/元数据地址({host or '?'}),已拦截(SSRF)")
67
+ if url.startswith("http://"):
68
+ warnings.append("使用明文 HTTP,传输不加密")
69
+ warnings.append("接入外部 MCP server,其返回内容为外部不可信数据,不应被当作指令执行")
70
+ if reasons:
71
+ return {"verdict": "deny", "reasons": reasons, "warnings": warnings}
72
+ return {"verdict": "warn", "reasons": [], "warnings": warnings}