foxclaw 0.1.2 → 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 (344) hide show
  1. package/index.js +1 -8
  2. package/package.json +1 -1
  3. package/python/.env.example +8 -0
  4. package/python/.github/workflows/ci.yml +47 -2
  5. package/python/CHANGELOG.md +57 -0
  6. package/python/CHANGELOG_CN.md +57 -0
  7. package/python/README.md +11 -2
  8. package/python/README_CN.md +32 -20
  9. package/python/bench/bm25_audit.py +1 -14
  10. package/python/bench/bm25_regression.py +0 -1
  11. package/python/bench/bm25_stress.py +0 -4
  12. package/python/bench/cd_audit.py +0 -23
  13. package/python/bench/chaos.py +6 -8
  14. package/python/bench/e2e_real_models.py +0 -1
  15. package/python/bench/llm_retry_test.py +2 -9
  16. package/python/bench/load_test.py +5 -8
  17. package/python/bench/vector_ivf_upgrade.py +1 -7
  18. package/python/docs/RELEASE.md +4 -3
  19. package/python/docs/RUN_CN.md +2 -2
  20. package/python/evals/governance/cases.py +0 -37
  21. package/python/evals/governance/run_evals.py +5 -37
  22. package/python/examples/governance_middleware_demo.py +1 -6
  23. package/python/examples/langgraph_governance_demo.py +7 -11
  24. package/python/foxclaw/__init__.py +34 -152
  25. package/python/foxclaw/agents/base.py +65 -136
  26. package/python/foxclaw/agents/impl/_helpers.py +2 -10
  27. package/python/foxclaw/agents/impl/finance.py +4 -21
  28. package/python/foxclaw/agents/impl/office.py +7 -24
  29. package/python/foxclaw/agents/impl/supervisor.py +12 -79
  30. package/python/foxclaw/agents/mixins/_context.py +0 -23
  31. package/python/foxclaw/agents/mixins/augment.py +10 -16
  32. package/python/foxclaw/agents/mixins/llm.py +7 -6
  33. package/python/foxclaw/agents/mixins/memory.py +7 -1
  34. package/python/foxclaw/agents/mixins/preference.py +58 -8
  35. package/python/foxclaw/agents/mixins/prompt.py +0 -6
  36. package/python/foxclaw/agents/mixins/skillbox.py +6 -6
  37. package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
  38. package/python/foxclaw/api/__init__.py +18 -77
  39. package/python/foxclaw/api/_state.py +43 -0
  40. package/python/foxclaw/api/{app.py → bootstrap.py} +5 -32
  41. package/python/foxclaw/api/middleware.py +5 -28
  42. package/python/foxclaw/api/routes/ahs.py +60 -73
  43. package/python/foxclaw/api/routes/attachments.py +5 -6
  44. package/python/foxclaw/api/routes/browser.py +7 -11
  45. package/python/foxclaw/api/routes/domain.py +18 -26
  46. package/python/foxclaw/api/routes/knowledge.py +3 -21
  47. package/python/foxclaw/api/routes/llm_admin.py +21 -33
  48. package/python/foxclaw/api/routes/memory.py +28 -12
  49. package/python/foxclaw/api/routes/orchestration.py +3 -5
  50. package/python/foxclaw/api/routes/recovery.py +4 -3
  51. package/python/foxclaw/api/routes/review.py +2 -11
  52. package/python/foxclaw/api/routes/search.py +3 -7
  53. package/python/foxclaw/api/routes/system.py +33 -49
  54. package/python/foxclaw/api/routes/task.py +31 -49
  55. package/python/foxclaw/api/routes/ws.py +2 -6
  56. package/python/foxclaw/api/spine.py +1 -4
  57. package/python/foxclaw/browser/action/action_executor.py +1 -5
  58. package/python/foxclaw/browser/action/action_types.py +0 -1
  59. package/python/foxclaw/browser/action/url_policy.py +1 -5
  60. package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
  61. package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
  62. package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
  63. package/python/foxclaw/browser/perception/element_indexer.py +1 -2
  64. package/python/foxclaw/browser/service.py +7 -11
  65. package/python/foxclaw/capability/embedding_service.py +4 -6
  66. package/python/foxclaw/channels/__init__.py +9 -11
  67. package/python/foxclaw/channels/discord.py +4 -13
  68. package/python/foxclaw/channels/dispatcher.py +2 -27
  69. package/python/foxclaw/channels/feishu.py +10 -20
  70. package/python/foxclaw/channels/openclaw_bridge.py +4 -9
  71. package/python/foxclaw/channels/signing.py +9 -9
  72. package/python/foxclaw/channels/telegram.py +3 -4
  73. package/python/foxclaw/channels/weixin.py +7 -17
  74. package/python/foxclaw/cli.py +42 -13
  75. package/python/foxclaw/cognitive/reasoner.py +9 -10
  76. package/python/foxclaw/cognitive/state.py +18 -24
  77. package/python/foxclaw/core/audit_chain.py +1 -3
  78. package/python/foxclaw/core/audit_sink.py +3 -3
  79. package/python/foxclaw/core/compaction.py +8 -26
  80. package/python/foxclaw/core/config.py +2 -2
  81. package/python/foxclaw/core/confirm_nonce.py +6 -12
  82. package/python/foxclaw/core/envfile.py +2 -2
  83. package/python/foxclaw/core/failure_policy.py +17 -26
  84. package/python/foxclaw/core/gateway.py +6 -10
  85. package/python/foxclaw/core/governance_audit.py +3 -7
  86. package/python/foxclaw/core/idempotency.py +15 -22
  87. package/python/foxclaw/core/injection_guard.py +10 -29
  88. package/python/foxclaw/core/memory_adapter.py +30 -57
  89. package/python/foxclaw/core/metrics.py +4 -5
  90. package/python/foxclaw/core/model_router.py +36 -148
  91. package/python/foxclaw/core/paths.py +1 -1
  92. package/python/foxclaw/core/recovery.py +21 -34
  93. package/python/foxclaw/core/scheduler.py +23 -26
  94. package/python/foxclaw/core/secrets.py +0 -3
  95. package/python/foxclaw/core/sessions.py +4 -6
  96. package/python/foxclaw/core/settings.py +16 -32
  97. package/python/foxclaw/core/shared_write.py +11 -23
  98. package/python/foxclaw/core/steering_queue.py +12 -11
  99. package/python/foxclaw/core/system_bus.py +1 -1
  100. package/python/foxclaw/core/thought_chain.py +30 -32
  101. package/python/foxclaw/core/token_meter.py +9 -13
  102. package/python/foxclaw/core/tools.py +4 -12
  103. package/python/foxclaw/core/tracing.py +25 -40
  104. package/python/foxclaw/core/truth_pipeline.py +5 -9
  105. package/python/foxclaw/core/update_check.py +149 -0
  106. package/python/foxclaw/core/vec_recall_adapter.py +11 -37
  107. package/python/foxclaw/core/verify.py +6 -14
  108. package/python/foxclaw/core/web_search.py +4 -28
  109. package/python/foxclaw/finance/evidence.py +14 -18
  110. package/python/foxclaw/finance/fx.py +1 -3
  111. package/python/foxclaw/finance/graph.py +9 -18
  112. package/python/foxclaw/finance/models.py +20 -24
  113. package/python/foxclaw/finance/nlparse.py +12 -72
  114. package/python/foxclaw/finance/policy.py +8 -36
  115. package/python/foxclaw/finance/reasoner.py +3 -11
  116. package/python/foxclaw/finance/reasoning.py +13 -34
  117. package/python/foxclaw/finance/reconcile.py +5 -10
  118. package/python/foxclaw/finance/runtime.py +19 -46
  119. package/python/foxclaw/governance/middleware.py +13 -21
  120. package/python/foxclaw/harness/_base.py +2 -8
  121. package/python/foxclaw/harness/_policy.py +19 -38
  122. package/python/foxclaw/harness/drift.py +1 -13
  123. package/python/foxclaw/harness/event_bus.py +2 -5
  124. package/python/foxclaw/harness/harness.py +9 -97
  125. package/python/foxclaw/harness/loop_detection.py +11 -18
  126. package/python/foxclaw/itinerary/amap_client.py +2 -5
  127. package/python/foxclaw/itinerary/models.py +7 -7
  128. package/python/foxclaw/itinerary/reasoner.py +1 -6
  129. package/python/foxclaw/itinerary/reasoning.py +15 -21
  130. package/python/foxclaw/itinerary/runtime.py +8 -12
  131. package/python/foxclaw/kernel/capabilities.py +5 -6
  132. package/python/foxclaw/kernel/policy_gate.py +4 -10
  133. package/python/foxclaw/kernel/process_manager.py +17 -36
  134. package/python/foxclaw/kernel/scheduler.py +3 -9
  135. package/python/foxclaw/kernel/sentinel.py +13 -23
  136. package/python/foxclaw/kernel/sentinel_client.py +12 -15
  137. package/python/foxclaw/kernel/spine.py +9 -9
  138. package/python/foxclaw/knowledge/__init__.py +4 -20
  139. package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
  140. package/python/foxclaw/learning/knowledge_graph.py +4 -9
  141. package/python/foxclaw/learning/models.py +13 -13
  142. package/python/foxclaw/learning/reasoner.py +1 -4
  143. package/python/foxclaw/learning/runtime.py +19 -31
  144. package/python/foxclaw/learning/spaced_repetition.py +2 -7
  145. package/python/foxclaw/mcp_server.py +2 -3
  146. package/python/foxclaw/orchestration/__init__.py +16 -33
  147. package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
  148. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  149. package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
  150. package/python/foxclaw/planning/llm_planner.py +3 -3
  151. package/python/foxclaw/planning/planner.py +6 -11
  152. package/python/foxclaw/search/agent_port.py +7 -11
  153. package/python/foxclaw/search/evidence.py +0 -4
  154. package/python/foxclaw/search/internal_search.py +3 -5
  155. package/python/foxclaw/search/models.py +11 -13
  156. package/python/foxclaw/search/planner.py +3 -3
  157. package/python/foxclaw/search/retriever.py +1 -1
  158. package/python/foxclaw/search/runtime.py +1 -8
  159. package/python/foxclaw/skills/__init__.py +46 -100
  160. package/python/foxclaw/skills/archive.py +111 -0
  161. package/python/foxclaw/skills/fetch.py +226 -0
  162. package/python/foxclaw/skills/hooks_spec.py +21 -25
  163. package/python/foxclaw/skills/install_cmd.py +216 -0
  164. package/python/foxclaw/skills/lifecycle.py +16 -42
  165. package/python/foxclaw/skills/markdown_skill.py +10 -9
  166. package/python/foxclaw/skills/mcp_client.py +5 -11
  167. package/python/foxclaw/skills/mcp_store.py +0 -3
  168. package/python/foxclaw/skills/permission.py +13 -48
  169. package/python/foxclaw/skills/pipeline.py +21 -53
  170. package/python/foxclaw/skills/policy.py +4 -10
  171. package/python/foxclaw/skills/review.py +3 -3
  172. package/python/frontend/deck/auth.js +0 -7
  173. package/python/frontend/deck/chat_model_picker.js +0 -54
  174. package/python/frontend/deck/domain.js +0 -20
  175. package/python/frontend/deck/main.js +0 -144
  176. package/python/frontend/deck/markdown.js +0 -4
  177. package/python/frontend/deck/settings.js +1 -18
  178. package/python/frontend/deck/state.js +0 -1
  179. package/python/frontend/deck/util.js +0 -2
  180. package/python/frontend/deck/visuals.js +0 -22
  181. package/python/frontend/planet/planet.js +0 -201
  182. package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
  183. package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
  184. package/python/frontend/v3/index.html +3 -3
  185. package/python/frontend/widget_runtime.js +0 -43
  186. package/python/frontend/widgets.js +0 -57
  187. package/python/frontend_src/index.html +1 -1
  188. package/python/frontend_src/package-lock.json +2 -2
  189. package/python/frontend_src/package.json +1 -1
  190. package/python/frontend_src/src/App.vue +3 -7
  191. package/python/frontend_src/src/api.js +0 -7
  192. package/python/frontend_src/src/components/ChatDock.vue +27 -39
  193. package/python/frontend_src/src/components/DocViewer.vue +0 -9
  194. package/python/frontend_src/src/components/Inspector.vue +0 -6
  195. package/python/frontend_src/src/components/LeftRail.vue +60 -39
  196. package/python/frontend_src/src/components/MenuPopover.vue +9 -11
  197. package/python/frontend_src/src/components/MessageStream.vue +18 -30
  198. package/python/frontend_src/src/components/ModelPicker.vue +0 -5
  199. package/python/frontend_src/src/components/QuickIngest.vue +0 -6
  200. package/python/frontend_src/src/components/Stage.vue +71 -25
  201. package/python/frontend_src/src/components/ToolMenu.vue +0 -3
  202. package/python/frontend_src/src/components/TopBar.vue +38 -10
  203. package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
  204. package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
  205. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
  206. package/python/frontend_src/src/components/panels/MemoryPanel.vue +0 -1
  207. package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
  208. package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
  209. package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
  210. package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
  211. package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
  212. package/python/frontend_src/src/lib/fmt.js +0 -4
  213. package/python/frontend_src/src/lib/i18n.js +53 -0
  214. package/python/frontend_src/src/lib/locales/en.js +173 -0
  215. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  216. package/python/frontend_src/src/lib/md.js +0 -14
  217. package/python/frontend_src/src/main.js +2 -0
  218. package/python/frontend_src/src/stores/app.js +17 -32
  219. package/python/frontend_src/src/stores/chat.js +0 -34
  220. package/python/frontend_src/src/stores/model.js +0 -8
  221. package/python/frontend_src/src/stores/panels.js +0 -41
  222. package/python/frontend_src/src/stores/profile.js +31 -0
  223. package/python/frontend_src/src/stores/system.js +25 -0
  224. package/python/frontend_src/vite.config.js +0 -7
  225. package/python/pyproject.toml +6 -3
  226. package/python/requirements.txt +311 -0
  227. package/python/tests/conftest.py +0 -22
  228. package/python/tests/run_all.py +3 -66
  229. package/python/tests/test_agent_lifecycle.py +4 -38
  230. package/python/tests/test_ahs_p2.py +4 -25
  231. package/python/tests/test_anthropic_tools.py +1 -15
  232. package/python/tests/test_api.py +19 -9
  233. package/python/tests/test_api_auth.py +1 -5
  234. package/python/tests/test_attachments.py +9 -25
  235. package/python/tests/test_audit_chain.py +2 -7
  236. package/python/tests/test_audit_sink.py +0 -6
  237. package/python/tests/test_browser.py +2 -6
  238. package/python/tests/test_browser_request_guard.py +0 -4
  239. package/python/tests/test_capabilities.py +0 -6
  240. package/python/tests/test_channel_signing.py +2 -9
  241. package/python/tests/test_channels.py +1 -11
  242. package/python/tests/test_config_layering.py +0 -4
  243. package/python/tests/test_confirm_nonce.py +1 -12
  244. package/python/tests/test_context_compaction_wiring.py +1 -7
  245. package/python/tests/test_dispatch.py +0 -6
  246. package/python/tests/test_domain_evals.py +11 -80
  247. package/python/tests/test_domain_import.py +0 -6
  248. package/python/tests/test_domain_reasoners.py +0 -14
  249. package/python/tests/test_e2e.py +0 -17
  250. package/python/tests/test_emb_cache.py +7 -17
  251. package/python/tests/test_embedding_singleton.py +1 -3
  252. package/python/tests/test_evals_smoke.py +0 -24
  253. package/python/tests/test_event_spine.py +10 -18
  254. package/python/tests/test_evidence_semantic.py +1 -14
  255. package/python/tests/test_evidence_sse.py +3 -6
  256. package/python/tests/test_file_ingest.py +0 -11
  257. package/python/tests/test_finance.py +0 -9
  258. package/python/tests/test_finance_ledger.py +5 -12
  259. package/python/tests/test_finance_persist.py +8 -12
  260. package/python/tests/test_gateway.py +3 -22
  261. package/python/tests/test_governance.py +5 -52
  262. package/python/tests/test_governance_audit.py +4 -39
  263. package/python/tests/test_governance_middleware.py +1 -7
  264. package/python/tests/test_hkc.py +6 -24
  265. package/python/tests/test_hkc_index_build_manager.py +0 -33
  266. package/python/tests/test_hkc_manifest_reuse.py +4 -27
  267. package/python/tests/test_hkc_search_event_sync.py +3 -54
  268. package/python/tests/test_import_path_guard.py +0 -6
  269. package/python/tests/test_injection_guard.py +18 -36
  270. package/python/tests/test_integration.py +5 -29
  271. package/python/tests/test_itinerary.py +0 -3
  272. package/python/tests/test_kee_semantic.py +0 -5
  273. package/python/tests/test_kernel.py +1 -11
  274. package/python/tests/test_learning.py +3 -12
  275. package/python/tests/test_llm_admin.py +4 -19
  276. package/python/tests/test_llm_planner.py +1 -23
  277. package/python/tests/test_markdown_skills.py +3 -14
  278. package/python/tests/test_markdown_skills_openclaw.py +4 -9
  279. package/python/tests/test_mcp_connectors.py +3 -15
  280. package/python/tests/test_mcp_guardian_discovery.py +2 -6
  281. package/python/tests/test_mcp_no_memory.py +3 -3
  282. package/python/tests/test_mcp_policy_gate.py +0 -1
  283. package/python/tests/test_mcp_server.py +6 -9
  284. package/python/tests/test_memory_recall.py +1 -4
  285. package/python/tests/test_metrics.py +0 -8
  286. package/python/tests/test_multi_chain.py +1 -13
  287. package/python/tests/test_multimodal.py +5 -18
  288. package/python/tests/test_negotiation.py +3 -13
  289. package/python/tests/test_orchestration.py +1 -11
  290. package/python/tests/test_orchestration_decoupled.py +0 -6
  291. package/python/tests/test_p3_p5_hardening.py +3 -17
  292. package/python/tests/test_parallel_dispatch.py +4 -12
  293. package/python/tests/test_pipeline.py +17 -58
  294. package/python/tests/test_plan_visible.py +4 -4
  295. package/python/tests/test_planner.py +0 -7
  296. package/python/tests/test_policy_gate.py +2 -3
  297. package/python/tests/test_profile.py +67 -0
  298. package/python/tests/test_readme_claims.py +163 -0
  299. package/python/tests/test_reasoner.py +2 -17
  300. package/python/tests/test_release_check.py +2 -13
  301. package/python/tests/test_resume_checkpoint.py +7 -15
  302. package/python/tests/test_routing.py +2 -12
  303. package/python/tests/test_scheduler.py +7 -10
  304. package/python/tests/test_search.py +1 -11
  305. package/python/tests/test_secrets.py +4 -10
  306. package/python/tests/test_semantic.py +1 -10
  307. package/python/tests/test_sessions.py +3 -4
  308. package/python/tests/test_settings.py +2 -18
  309. package/python/tests/test_shadow_learning.py +3 -43
  310. package/python/tests/test_shared_write.py +0 -7
  311. package/python/tests/test_skill_taint_gate.py +1 -10
  312. package/python/tests/test_skills.py +2 -18
  313. package/python/tests/test_skills_fetch.py +282 -0
  314. package/python/tests/test_stats_and_events.py +2 -5
  315. package/python/tests/test_streaming.py +2 -7
  316. package/python/tests/test_supervisor_pipeline.py +4 -5
  317. package/python/tests/test_supervisor_triage.py +4 -10
  318. package/python/tests/test_taint.py +2 -7
  319. package/python/tests/test_task_stream.py +1 -12
  320. package/python/tests/test_thinking_sse.py +0 -1
  321. package/python/tests/test_tool_center.py +1 -17
  322. package/python/tests/test_tool_parallel.py +2 -2
  323. package/python/tests/test_tool_params.py +1 -5
  324. package/python/tests/test_tool_use.py +0 -13
  325. package/python/tests/test_toplevel_concurrency.py +3 -4
  326. package/python/tests/test_tracing.py +7 -41
  327. package/python/tests/test_truth_crystallizer.py +4 -6
  328. package/python/tests/test_truth_flow.py +2 -7
  329. package/python/tests/test_update_check.py +95 -0
  330. package/python/tests/test_verify_evidence.py +8 -21
  331. package/python/tests/test_version_sync.py +35 -6
  332. package/python/tests/test_web_trigger.py +0 -17
  333. package/python/tools/check_faiss_type.py +0 -1
  334. package/python/tools/check_log.ps1 +0 -8
  335. package/python/tools/mypy_baseline.txt +12 -4
  336. package/python/tools/mypy_ratchet.py +52 -6
  337. package/python/tools/pack.py +0 -5
  338. package/python/tools/release_check.py +7 -7
  339. package/python/tools/restart_foxclaw.ps1 +5 -8
  340. package/python/tools/shared_write_audit.py +1 -15
  341. package/python/tools/size_check.py +1 -1
  342. package/python/tools/strip_comments.py +228 -0
  343. package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
  344. package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
@@ -1,12 +1,12 @@
1
1
  """AHS 执行链:技能安装/管理、MCP 接入、Markdown 指令技能、策略封杀、运行、断路器、钩子审计。
2
2
 
3
- 从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api._os_instance`
3
+ 从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.current_os()`
4
4
  晚绑定读取;路由用 `@app` 装饰器在 import 时注册。
5
5
  """
6
6
 
7
7
  import asyncio
8
8
  import uuid
9
- from typing import Optional
9
+ from typing import Optional, cast
10
10
 
11
11
  from fastapi import HTTPException
12
12
  from pydantic import BaseModel
@@ -15,24 +15,18 @@ import foxclaw.api as _api
15
15
  from .. import app
16
16
 
17
17
 
18
- # ============ AHS 执行链(Agent Harness Skills 标准)============
19
-
20
18
  class AHSRunPayload(BaseModel):
21
19
  task: str
22
20
  confirmed: bool = False
23
- # 【P2-2】断点续跑:传上次响应里的 pending_confirmation(确认后连 confirmed=True 一起传)。
24
- # Optional:pydantic v2 下 dict=None 字段收到显式 null 会 422,必须 Optional。
25
21
  resume_state: Optional[dict] = None
26
- # 【P1】关键操作(send/pay/destructive)二次确认码:上次响应 pending_confirmation.confirm_nonce。
27
- # 单次使用、绑定 skill+参数、默认 5 分钟过期。也可直接放在 resume_state 里,二者等价。
28
22
  confirm_nonce: str = ""
29
23
 
30
24
 
31
25
  @app.get("/api/ahs/skills")
32
26
  async def ahs_list_skills():
33
27
  """列出注册的 skill(含信任分级 trust + 封杀标记 blocked)。"""
34
- pol = _api._os_instance.skill_policy
35
- out = _api._os_instance.skill_registry.catalog()
28
+ pol = _api.os_instance().skill_policy
29
+ out = _api.os_instance().skill_registry.catalog()
36
30
  for s in out:
37
31
  s["blocked"] = pol.is_blocked(s.get("id", ""))
38
32
  return {"skills": out}
@@ -45,22 +39,22 @@ class PolicyDenyPayload(BaseModel):
45
39
  @app.get("/api/ahs/policy")
46
40
  async def ahs_policy_get():
47
41
  """全局封杀策略快照(deny/allow)。"""
48
- return _api._os_instance.skill_policy.snapshot()
42
+ return _api.os_instance().skill_policy.snapshot()
49
43
 
50
44
 
51
45
  @app.post("/api/ahs/policy/deny")
52
46
  async def ahs_policy_deny(p: PolicyDenyPayload):
53
47
  """封杀一个技能 id(deny 压倒 enable,立即对所有执行/注入生效)。"""
54
- _api._os_instance.skill_policy.deny(p.id)
55
- return {"ok": True, "policy": _api._os_instance.skill_policy.snapshot()}
48
+ _api.os_instance().skill_policy.deny(p.id)
49
+ return {"ok": True, "policy": _api.os_instance().skill_policy.snapshot()}
56
50
 
57
51
 
58
52
  @app.delete("/api/ahs/policy/deny/{skill_id}")
59
53
  async def ahs_policy_undeny(skill_id: str):
60
54
  """解封。E1:被 policy 层(管理员 FOXCLAW_POLICY_DENY / policy.json)锁定的技能运行时无法解封,
61
55
  诚实返回 ok=False + 原因(不再假报成功);其余情况与改前一致(ok=True)。"""
62
- _api._os_instance.skill_policy.undeny(skill_id)
63
- snap = _api._os_instance.skill_policy.snapshot()
56
+ _api.os_instance().skill_policy.undeny(skill_id)
57
+ snap = _api.os_instance().skill_policy.snapshot()
64
58
  if skill_id in snap.get("policy_deny", []):
65
59
  return {"ok": False,
66
60
  "reason": f"「{skill_id}」被管理员策略(policy 层)锁定,运行时无法解封", "policy": snap}
@@ -71,23 +65,21 @@ class SkillInstallPayload(BaseModel):
71
65
  id: str
72
66
  name: str
73
67
  description: str = ""
74
- kind: str # 只允许 "web_api" | "mcp"
68
+ kind: str
75
69
  triggers: list = []
76
- side_effect: str = "write" # read/write/send/pay;不传按有副作用(会要求确认)
70
+ side_effect: str = "write"
77
71
  destructive: bool = False
78
72
  external: bool = True
79
73
  params: dict = {}
80
- # web_api
81
74
  endpoint: str = ""
82
75
  http_method: str = "GET"
83
76
  http_headers: dict = {}
84
77
  param_location: str = "auto"
85
78
  allow_local: bool = False
86
79
  timeout: float = 10.0
87
- # mcp
88
80
  mcp_server: str = ""
89
81
  mcp_tool: str = ""
90
- acknowledged: bool = False # warn 级风险,用户确认后置 true
82
+ acknowledged: bool = False
91
83
 
92
84
 
93
85
  class SkillPatchPayload(BaseModel):
@@ -98,9 +90,6 @@ class SkillTestPayload(BaseModel):
98
90
  args: dict = {}
99
91
  confirmed: bool = False
100
92
  mode: str = "strict"
101
- # 关键操作(send/pay/destructive)的二次确认码:上次响应里的 confirm_nonce。
102
- # 宿主注入了 confirms(见 foxclaw/__init__.py)时,confirmed=True 只有配有效 nonce
103
- # 才放行 —— 原先这个端点收不到 nonce,界面上点「确认」永远退回 needs_confirmation。
104
93
  confirm_nonce: str = ""
105
94
 
106
95
 
@@ -124,66 +113,66 @@ def _validate_install(p: "SkillInstallPayload"):
124
113
  async def ahs_install_skill(p: SkillInstallPayload):
125
114
  """安装/注册一个 web_api|mcp 技能(过安装审核门 + 持久化)。同 id 覆盖更新。"""
126
115
  _validate_install(p)
127
- from foxclaw.skills import SkillSpec
116
+ from foxclaw.skills import SideEffect, SkillKind, SkillSpec
128
117
  from foxclaw.skills.review import review_install
129
- reg = _api._os_instance.skill_registry
118
+ reg = _api.os_instance().skill_registry
130
119
  _existing = reg.get(p.id)
131
120
  if _existing is not None and _existing.source == "builtin":
132
121
  raise HTTPException(409, "该 id 属于内置技能,不能覆盖")
133
122
  existed = _existing is not None
134
123
  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,
124
+ id=p.id, name=p.name, description=p.description,
125
+ kind=cast(SkillKind, p.kind), triggers=list(p.triggers),
126
+ side_effect=cast(SideEffect, p.side_effect),
137
127
  destructive=p.destructive, external=p.external, params=dict(p.params),
138
128
  endpoint=p.endpoint, http_method=p.http_method, http_headers=dict(p.http_headers),
139
129
  param_location=p.param_location, allow_local=p.allow_local, timeout=p.timeout,
140
130
  mcp_server=p.mcp_server, mcp_tool=p.mcp_tool, source="user",
141
131
  )
142
- verdict = review_install(spec) # 安装审核门
132
+ verdict = review_install(spec)
143
133
  if verdict["verdict"] == "deny":
144
134
  raise HTTPException(403, detail={"error": "install_denied", "reasons": verdict["reasons"]})
145
135
  if verdict["verdict"] == "warn" and not p.acknowledged:
146
136
  raise HTTPException(409, detail={"needs_ack": True, "warnings": verdict["warnings"]})
147
137
  reg.register(spec)
148
- _api._os_instance.skill_store.save(reg)
138
+ _api.os_instance().skill_store.save(reg)
149
139
  return {"ok": True, "skill": spec.to_dict(), "created": not existed, "warnings": verdict["warnings"]}
150
140
 
151
141
 
152
142
  @app.patch("/api/ahs/skills/{skill_id}")
153
143
  async def ahs_patch_skill(skill_id: str, p: SkillPatchPayload):
154
144
  """启停一个技能(内置/用户均可,持久化)。"""
155
- reg = _api._os_instance.skill_registry
145
+ reg = _api.os_instance().skill_registry
156
146
  sp = reg.get(skill_id)
157
147
  if sp is None:
158
148
  raise HTTPException(404, "技能不存在")
159
149
  sp.enabled = bool(p.enabled)
160
- _api._os_instance.skill_store.save(reg)
150
+ _api.os_instance().skill_store.save(reg)
161
151
  return {"ok": True, "skill": sp.to_dict()}
162
152
 
163
153
 
164
154
  @app.delete("/api/ahs/skills/{skill_id}")
165
155
  async def ahs_delete_skill(skill_id: str):
166
156
  """删除用户安装的技能;内置技能不可删(只能停用)。"""
167
- reg = _api._os_instance.skill_registry
157
+ reg = _api.os_instance().skill_registry
168
158
  sp = reg.get(skill_id)
169
159
  if sp is None:
170
160
  raise HTTPException(404, "技能不存在")
171
161
  if sp.source == "builtin":
172
162
  raise HTTPException(409, "内置技能不可删除,只能停用")
173
163
  reg.unregister(skill_id)
174
- _api._os_instance.skill_store.save(reg)
164
+ _api.os_instance().skill_store.save(reg)
175
165
  return {"ok": True}
176
166
 
177
167
 
178
168
  @app.post("/api/ahs/skills/{skill_id}/test")
179
169
  async def ahs_test_skill(skill_id: str, p: SkillTestPayload):
180
170
  """单技能测试调用:走真实权限管线/断路器/SSRF。副作用未确认 → needs_confirmation。"""
181
- reg = _api._os_instance.skill_registry
171
+ reg = _api.os_instance().skill_registry
182
172
  sp = reg.get(skill_id)
183
173
  if sp is None:
184
174
  raise HTTPException(404, "技能不存在")
185
- # 阻塞 I/O(web_api httpx / mcp 桥,最长数十秒)放线程池,勿堵死事件循环(否则慢工具冻全站)
186
- res = await asyncio.to_thread(_api._os_instance.skill_adapter.invoke,
175
+ res = await asyncio.to_thread(_api.os_instance().skill_adapter.invoke,
187
176
  sp, p.args, p.confirmed, p.mode,
188
177
  confirm_nonce=p.confirm_nonce)
189
178
  return res.to_dict()
@@ -194,7 +183,7 @@ class McpServerPayload(BaseModel):
194
183
  name: str = ""
195
184
  url: str
196
185
  headers: dict = {}
197
- transport: str = "streamable_http" # streamable_http | sse
186
+ transport: str = "streamable_http"
198
187
  acknowledged: bool = False
199
188
 
200
189
 
@@ -214,22 +203,21 @@ async def mcp_add_server(p: McpServerPayload):
214
203
  raise HTTPException(409, detail={"needs_ack": True, "warnings": verdict["warnings"]})
215
204
  cfg = {"id": p.id, "name": p.name or p.id, "url": p.url,
216
205
  "headers": dict(p.headers), "transport": p.transport}
217
- sentinel = getattr(_api._os_instance, "sentinel", None)
206
+ sentinel = getattr(_api.current_os(), "sentinel", None)
218
207
  try:
219
208
  if sentinel is not None:
220
- # 守门人模式:headers 只在这次调用经手,发现由守门人代执行,主进程只存元数据。
221
- tools = await asyncio.to_thread(_api._os_instance.mcp_add_server_guardian, cfg)
209
+ tools = await asyncio.to_thread(_api.os_instance().mcp_add_server_guardian, cfg)
222
210
  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())
211
+ tools = await asyncio.to_thread(_api.os_instance().mcp_manager.add_server, cfg)
212
+ _api.os_instance().mcp_store.save(_api.os_instance().mcp_manager.servers())
225
213
  except Exception as e:
226
214
  raise HTTPException(502, f"连接 MCP server 失败: {e}")
227
- reg = _api._os_instance.skill_registry
215
+ reg = _api.os_instance().skill_registry
228
216
  n = 0
229
217
  for tool in tools:
230
218
  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
219
+ _api.os_instance().skill_store.save(reg)
220
+ _api.os_instance().sync_mcp_servers_to_sentinel()
233
221
  return {"ok": True, "server": {k: cfg[k] for k in ("id", "name", "url", "transport")},
234
222
  "tools_registered": n, "warnings": verdict["warnings"]}
235
223
 
@@ -237,10 +225,10 @@ async def mcp_add_server(p: McpServerPayload):
237
225
  @app.get("/api/ahs/mcp/servers")
238
226
  async def mcp_list_servers():
239
227
  """列出已接入 MCP server(工具数由注册表派生,无需连接)。"""
240
- reg = _api._os_instance.skill_registry
228
+ reg = _api.os_instance().skill_registry
241
229
  all_sk = reg.all_including_disabled()
242
230
  out = []
243
- for c in _api._os_instance.mcp_manager.servers():
231
+ for c in _api.os_instance().mcp_manager.servers():
244
232
  tc = sum(1 for s in all_sk if s.kind == "mcp" and s.mcp_server == c["id"])
245
233
  out.append({"id": c["id"], "name": c["name"], "url": c["url"],
246
234
  "transport": c["transport"], "tool_count": tc})
@@ -250,17 +238,17 @@ async def mcp_list_servers():
250
238
  @app.delete("/api/ahs/mcp/servers/{server_id}")
251
239
  async def mcp_remove_server(server_id: str):
252
240
  """移除 server + 其注册的所有 mcp 技能。"""
253
- mgr = _api._os_instance.mcp_manager
241
+ mgr = _api.os_instance().mcp_manager
254
242
  if not mgr.remove_server(server_id):
255
243
  raise HTTPException(404, "server 不存在")
256
- reg = _api._os_instance.skill_registry
244
+ reg = _api.os_instance().skill_registry
257
245
  removed = [s.id for s in reg.all_including_disabled()
258
246
  if s.kind == "mcp" and s.mcp_server == server_id]
259
247
  for sid in removed:
260
248
  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() # 配置(含凭证)同步给守门人
249
+ _api.os_instance().skill_store.save(reg)
250
+ _api.os_instance().mcp_store.save(mgr.servers())
251
+ _api.os_instance().sync_mcp_servers_to_sentinel()
264
252
  return {"ok": True, "removed_skills": len(removed)}
265
253
 
266
254
 
@@ -268,38 +256,38 @@ async def mcp_remove_server(server_id: str):
268
256
  async def mcp_refresh_server(server_id: str):
269
257
  """重连拉工具:新增/更新技能,删除已消失的工具技能。"""
270
258
  from foxclaw.skills.mcp_client import tool_to_spec
271
- mgr = _api._os_instance.mcp_manager
259
+ mgr = _api.os_instance().mcp_manager
272
260
  if not any(s["id"] == server_id for s in mgr.servers()):
273
261
  raise HTTPException(404, "server 不存在")
274
- sentinel = getattr(_api._os_instance, "sentinel", None)
262
+ sentinel = getattr(_api.current_os(), "sentinel", None)
275
263
  try:
276
264
  if sentinel is not None:
277
- tools = await asyncio.to_thread(sentinel.mcp_list_tools, server_id) # 守门人代发现(失败抛异常)
265
+ tools = await asyncio.to_thread(sentinel.mcp_list_tools, server_id)
278
266
  else:
279
- tools = await asyncio.to_thread(mgr.list_tools, server_id) # 桥阻塞→线程池
267
+ tools = await asyncio.to_thread(mgr.list_tools, server_id)
280
268
  except Exception as e:
281
269
  raise HTTPException(502, f"刷新失败: {e}")
282
- reg = _api._os_instance.skill_registry
270
+ reg = _api.os_instance().skill_registry
283
271
  fresh = set()
284
272
  for tool in tools:
285
273
  spec = tool_to_spec(server_id, tool); reg.register(spec); fresh.add(spec.id)
286
274
  for s in [s for s in reg.all_including_disabled()
287
275
  if s.kind == "mcp" and s.mcp_server == server_id and s.id not in fresh]:
288
276
  reg.unregister(s.id)
289
- _api._os_instance.skill_store.save(reg)
277
+ _api.os_instance().skill_store.save(reg)
290
278
  return {"ok": True, "tools": len(fresh)}
291
279
 
292
280
 
293
281
  class MdSkillPayload(BaseModel):
294
282
  content: str = ""
295
- package_b64: str = "" # OpenClaw 包(zip)的 base64;有则优先,解包取 SKILL.md
283
+ package_b64: str = ""
296
284
  id: str = ""
297
285
  acknowledged: bool = False
298
286
 
299
287
 
300
288
  class MdSkillPatchPayload(BaseModel):
301
289
  enabled: bool | None = None
302
- triggers: list | None = None # 改触发词(免解压重打包);None=不动
290
+ triggers: list | None = None
303
291
 
304
292
 
305
293
  @app.post("/api/ahs/md-skills")
@@ -326,17 +314,17 @@ async def md_install(p: MdSkillPayload):
326
314
  if not p.acknowledged:
327
315
  raise HTTPException(409, detail={"needs_ack": True, "warnings": [
328
316
  "此技能会把内容注入你 AI 的提示词、影响其行为,仅从可信来源安装"]})
329
- reg = _api._os_instance.md_skills
317
+ reg = _api.os_instance().md_skills
330
318
  existed = reg.get(skill.id) is not None
331
319
  reg.register(skill)
332
- _api._os_instance.md_store.save(reg)
320
+ _api.os_instance().md_store.save(reg)
333
321
  return {"ok": True, "skill": skill.to_meta(), "created": not existed}
334
322
 
335
323
 
336
324
  @app.get("/api/ahs/md-skills")
337
325
  async def md_list():
338
- pol = _api._os_instance.skill_policy
339
- out = _api._os_instance.md_skills.catalog()
326
+ pol = _api.os_instance().skill_policy
327
+ out = _api.os_instance().md_skills.catalog()
340
328
  for s in out:
341
329
  s["blocked"] = pol.is_blocked(s.get("id", ""))
342
330
  return {"skills": out}
@@ -344,7 +332,7 @@ async def md_list():
344
332
 
345
333
  @app.get("/api/ahs/md-skills/{skill_id}")
346
334
  async def md_get(skill_id: str):
347
- s = _api._os_instance.md_skills.get(skill_id)
335
+ s = _api.os_instance().md_skills.get(skill_id)
348
336
  if s is None:
349
337
  raise HTTPException(404, "指令技能不存在")
350
338
  return s.to_dict()
@@ -352,7 +340,7 @@ async def md_get(skill_id: str):
352
340
 
353
341
  @app.patch("/api/ahs/md-skills/{skill_id}")
354
342
  async def md_patch(skill_id: str, p: MdSkillPatchPayload):
355
- reg = _api._os_instance.md_skills
343
+ reg = _api.os_instance().md_skills
356
344
  s = reg.get(skill_id)
357
345
  if s is None:
358
346
  raise HTTPException(404, "指令技能不存在")
@@ -360,17 +348,17 @@ async def md_patch(skill_id: str, p: MdSkillPatchPayload):
360
348
  s.enabled = bool(p.enabled)
361
349
  if p.triggers is not None:
362
350
  s.triggers = [str(t).strip() for t in p.triggers if str(t).strip()]
363
- _api._os_instance.md_store.save(reg)
351
+ _api.os_instance().md_store.save(reg)
364
352
  return {"ok": True, "skill": s.to_meta()}
365
353
 
366
354
 
367
355
  @app.delete("/api/ahs/md-skills/{skill_id}")
368
356
  async def md_delete(skill_id: str):
369
- reg = _api._os_instance.md_skills
357
+ reg = _api.os_instance().md_skills
370
358
  if reg.get(skill_id) is None:
371
359
  raise HTTPException(404, "指令技能不存在")
372
360
  reg.unregister(skill_id)
373
- _api._os_instance.md_store.save(reg)
361
+ _api.os_instance().md_store.save(reg)
374
362
  return {"ok": True}
375
363
 
376
364
 
@@ -383,11 +371,10 @@ async def ahs_run(payload: AHSRunPayload):
383
371
  【P1】关键操作还需带 confirm_nonce(响应里的 pending_confirmation.confirm_nonce);
384
372
  缺/错/过期 → 仍返回 pending_confirmation 并换发新码,不会执行。"""
385
373
  from foxclaw.skills import set_loop_session, reset_loop_session
386
- # 循环检测按执行链分槽:断点续跑复用同一 task_id,否则每次请求独立(消除跨请求污染)
387
374
  sid = (payload.resume_state or {}).get("task_id") or f"ahs_{uuid.uuid4().hex[:8]}"
388
375
  token = set_loop_session(sid)
389
376
  try:
390
- res = await _api._os_instance.ahs_pipeline.run_async(
377
+ res = await _api.os_instance().ahs_pipeline.run_async(
391
378
  payload.task, confirmed=payload.confirmed,
392
379
  resume_state=payload.resume_state,
393
380
  confirm_nonce=payload.confirm_nonce)
@@ -407,11 +394,11 @@ async def ahs_run(payload: AHSRunPayload):
407
394
  @app.get("/api/ahs/breaker")
408
395
  async def ahs_breaker_status():
409
396
  """断路器状态:各 skill 的失败计数与开闸情况。"""
410
- br = _api._os_instance.skill_adapter.breaker
397
+ br = _api.os_instance().skill_adapter.breaker
411
398
  return {"breakers": {sid: br.status(sid) for sid in br._entries}}
412
399
 
413
400
 
414
401
  @app.get("/api/ahs/hooks/audit")
415
402
  async def ahs_hooks_audit():
416
403
  """钩子审计日志(最近 20 条)。"""
417
- return {"audit": _api._os_instance.skill_adapter.hooks.recent_audit(20)}
404
+ return {"audit": _api.os_instance().skill_adapter.hooks.recent_audit(20)}
@@ -23,11 +23,10 @@ import foxclaw.api as _api
23
23
  from .. import app
24
24
 
25
25
 
26
- _MAX_ATTACH_BYTES = 20 * 1024 * 1024 # 单附件上限 20MB
27
- _MAX_TEXT_CHARS = 20000 # 单文件提取正文上限(防超长文件撑爆上下文)
26
+ _MAX_ATTACH_BYTES = 20 * 1024 * 1024
27
+ _MAX_TEXT_CHARS = 20000
28
28
  _ID_RE = re.compile(r"^[A-Za-z0-9_-]{1,64}$")
29
29
 
30
- # 文本类扩展名 → 直接读正文;.pdf 走 fitz(可选依赖);其余二进制 → 降级为元数据提示
31
30
  _TEXT_EXTS = {
32
31
  ".txt", ".md", ".markdown", ".csv", ".json", ".yaml", ".yml", ".log",
33
32
  ".py", ".js", ".ts", ".html", ".css", ".xml", ".toml", ".ini", ".sh",
@@ -36,7 +35,7 @@ _TEXT_EXTS = {
36
35
 
37
36
 
38
37
  def _att_dir() -> Path:
39
- d = Path(_api._os_instance.data_dir) / "attachments"
38
+ d = Path(_api.os_instance().data_dir) / "attachments"
40
39
  d.mkdir(parents=True, exist_ok=True)
41
40
  return d
42
41
 
@@ -61,7 +60,7 @@ def _extract_text(path: Path, name: str) -> str:
61
60
  return ""
62
61
  if ext == ".pdf":
63
62
  try:
64
- import fitz # PyMuPDF(可选依赖 knowledge-files)
63
+ import fitz
65
64
  except ImportError:
66
65
  return ""
67
66
  try:
@@ -85,7 +84,7 @@ async def upload_attachment(file: UploadFile = File(...)):
85
84
  """通用聊天附件上传:落盘 + 尽力提取正文,返回 {id,name,size,mime,url,text}。
86
85
  text 为空表示不可提取(二进制/图片等),前端/LLM 降级为「文件名 + 元数据」提示。"""
87
86
  raw_name = (file.filename or "attachment").strip()
88
- name = Path(raw_name).name or "attachment" # 只留 basename,去路径分隔符
87
+ name = Path(raw_name).name or "attachment"
89
88
  data = await file.read()
90
89
  if not data:
91
90
  raise HTTPException(400, "空文件")
@@ -1,6 +1,6 @@
1
1
  """浏览器子系统(HIR-Browser)端点。
2
2
 
3
- 从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api._os_instance`
3
+ 从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.current_os()`
4
4
  晚绑定读取;路由用 `@app` 装饰器在 import 时注册。
5
5
  """
6
6
 
@@ -10,30 +10,26 @@ import foxclaw.api as _api
10
10
  from .. import app
11
11
 
12
12
 
13
- # ── 浏览器子系统(HIR-Browser):AI 用的浏览器 ─────────────────
14
- # 感知网页→压缩语义结构→执行动作→校验结果。async 安全(专用工作线程),
15
- # playwright 缺失时结构化降级(available=False),不崩溃。受 /api 鉴权保护。
16
-
17
13
  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 的方向
14
+ type: str
15
+ target: str | None = None
16
+ value: object | None = None
21
17
  meta: dict = {}
22
18
 
23
19
 
24
20
  @app.get("/api/browser/status")
25
21
  async def browser_status():
26
22
  """浏览器子系统能力/会话状态(available/session_active/launch_error)。"""
27
- return _api._os_instance.browser_service.status()
23
+ return _api.os_instance().browser_service.status()
28
24
 
29
25
 
30
26
  @app.get("/api/browser/perceive")
31
27
  async def browser_perceive():
32
28
  """只感知当前页面语义结构(带编号的可交互元素 + 摘要),不改变页面。"""
33
- return await _api._os_instance.browser_service.perceive()
29
+ return await _api.os_instance().browser_service.perceive()
34
30
 
35
31
 
36
32
  @app.post("/api/browser/act")
37
33
  async def browser_act(p: BrowserActPayload):
38
34
  """执行一个 Web 动作,返回校验后最新语义结构。"""
39
- return await _api._os_instance.browser_service.act(p.model_dump())
35
+ return await _api.os_instance().browser_service.act(p.model_dump())
@@ -1,6 +1,6 @@
1
1
  """领域内核端点(B 阶段三内核):财务 / 学习 / 行程 + 多格式文件导入。
2
2
 
3
- 从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api._os_instance`
3
+ 从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.current_os()`
4
4
  晚绑定读取;路由用 `@app` 装饰器在 import 时注册。
5
5
  """
6
6
 
@@ -18,11 +18,9 @@ from foxclaw.core.paths import resolve_import_path
18
18
  from .. import app
19
19
 
20
20
 
21
- # ── 领域内核端点(B 阶段三内核)─────────────────────────────
22
-
23
21
  class ImportPayload(BaseModel):
24
22
  csv_path: Optional[str] = None
25
- rows: Optional[list] = None # 也可直接传记录数组
23
+ rows: Optional[list] = None
26
24
 
27
25
 
28
26
  def _resolve_csv_path(raw: str) -> str:
@@ -43,7 +41,7 @@ async def finance_analysis(period: Optional[str] = None,
43
41
  current_balance: Optional[float] = None):
44
42
  """财务内核综合分析:预算/异常/订阅/烧钱/守门人裁决。"""
45
43
  try:
46
- return _api._os_instance.finance.analyze_finances(
44
+ return _api.os_instance().finance.analyze_finances(
47
45
  period=period, current_balance=current_balance)
48
46
  except Exception as e:
49
47
  raise HTTPException(500, f"财务分析失败: {e}")
@@ -52,7 +50,7 @@ async def finance_analysis(period: Optional[str] = None,
52
50
  @app.post("/api/finance/import")
53
51
  async def finance_import(p: ImportPayload):
54
52
  """导入交易数据(CSV 路径或记录数组)。"""
55
- rt = _api._os_instance.finance_runtime
53
+ rt = _api.os_instance().finance_runtime
56
54
  src = _resolve_csv_path(p.csv_path) if p.csv_path else ""
57
55
  try:
58
56
  if src:
@@ -61,7 +59,6 @@ async def finance_import(p: ImportPayload):
61
59
  from foxclaw.finance import Transaction
62
60
  n = 0
63
61
  for row in p.rows:
64
- # save=False:整批导完再落盘一次,别在循环里做 N 次磁盘 IO
65
62
  rt.add_transaction(Transaction(**row), run_gatekeeper=False, save=False)
66
63
  n += 1
67
64
  if n:
@@ -73,12 +70,10 @@ async def finance_import(p: ImportPayload):
73
70
  raise HTTPException(400, f"导入失败: {e}")
74
71
 
75
72
 
76
- # ── 自然语言记账(第一线)────────────────────────────────────
77
-
78
73
  class RecordPayload(BaseModel):
79
74
  text: str
80
75
  account: str = "default"
81
- dry_run: bool = False # 只解析预览,不落账
76
+ dry_run: bool = False
82
77
 
83
78
 
84
79
  class FxPayload(BaseModel):
@@ -89,7 +84,7 @@ class FxPayload(BaseModel):
89
84
  @app.post("/api/finance/record")
90
85
  async def finance_record(p: RecordPayload):
91
86
  """一句话记账:「今天买了一件衣服花了 105 块」/「明天交房租 3000」。"""
92
- rt = _api._os_instance.finance_runtime
87
+ rt = _api.os_instance().finance_runtime
93
88
  try:
94
89
  return rt.record_text(p.text, account=p.account, dry_run=p.dry_run)
95
90
  except Exception as e:
@@ -99,7 +94,7 @@ async def finance_record(p: RecordPayload):
99
94
  @app.get("/api/finance/ledger")
100
95
  async def finance_ledger(period: Optional[str] = None):
101
96
  """记账本:按月汇总(已发生/计划、按天、按分类、按币种)。"""
102
- rt = _api._os_instance.finance_runtime
97
+ rt = _api.os_instance().finance_runtime
103
98
  try:
104
99
  return rt.ledger(period)
105
100
  except Exception as e:
@@ -109,14 +104,14 @@ async def finance_ledger(period: Optional[str] = None):
109
104
  @app.get("/api/finance/fx")
110
105
  async def finance_fx():
111
106
  """当前汇率表(本位币 CNY)。"""
112
- return _api._os_instance.finance_runtime.fx.snapshot()
107
+ return _api.os_instance().finance_runtime.fx.snapshot()
113
108
 
114
109
 
115
110
  @app.post("/api/finance/fx")
116
111
  async def finance_set_fx(p: FxPayload):
117
112
  """设置某个币种的记账汇率(比如刷卡当天的实际结算价)。"""
118
113
  try:
119
- return _api._os_instance.finance_runtime.fx.set_rate(p.currency, p.rate)
114
+ return _api.os_instance().finance_runtime.fx.set_rate(p.currency, p.rate)
120
115
  except ValueError as e:
121
116
  raise HTTPException(400, str(e))
122
117
 
@@ -125,7 +120,7 @@ async def finance_set_fx(p: FxPayload):
125
120
  async def learning_analysis():
126
121
  """学习内核综合分析:掌握度/遗忘风险/知识图谱/学习策略。"""
127
122
  try:
128
- return _api._os_instance.office.analyze_learning()
123
+ return _api.os_instance().office.analyze_learning()
129
124
  except Exception as e:
130
125
  raise HTTPException(500, f"学习分析失败: {e}")
131
126
 
@@ -134,7 +129,7 @@ async def learning_analysis():
134
129
  async def learning_import(p: ImportPayload):
135
130
  """导入知识点(办公学习内核)。learn() 内部已把内容存进 HKC(连续 ConceptKU,知识真源),
136
131
  不再另做桥接。"""
137
- rt = _api._os_instance.learning_runtime
132
+ rt = _api.os_instance().learning_runtime
138
133
  src = _resolve_csv_path(p.csv_path) if p.csv_path else ""
139
134
  try:
140
135
  if src:
@@ -154,7 +149,7 @@ async def learning_import(p: ImportPayload):
154
149
  @app.delete("/api/learning/item/{item_id}")
155
150
  async def learning_delete(item_id: str):
156
151
  """删除一个学习知识点(内存+复习状态+图谱,并落盘)。"""
157
- rt = _api._os_instance.learning_runtime
152
+ rt = _api.os_instance().learning_runtime
158
153
  ok = rt.delete(item_id)
159
154
  return {"deleted": ok, "status": rt.status()}
160
155
 
@@ -167,7 +162,7 @@ class NotePayload(BaseModel):
167
162
  @app.post("/api/learning/note")
168
163
  async def learning_note(p: NotePayload):
169
164
  """给一个学习知识点追加一条笔记(有内容才存;没新增不写盘)。"""
170
- rt = _api._os_instance.learning_runtime
165
+ rt = _api.os_instance().learning_runtime
171
166
  r = rt.add_note(p.item_id, p.note)
172
167
  if "error" in r:
173
168
  raise HTTPException(400, r["error"])
@@ -178,7 +173,7 @@ async def learning_note(p: NotePayload):
178
173
  async def itinerary_plan(mode: str = "driving"):
179
174
  """行程内核规划:路线优化/可达性/冲突/守门人 + 高德可视化数据。"""
180
175
  try:
181
- return _api._os_instance.office.plan_itinerary(mode=mode)
176
+ return _api.os_instance().office.plan_itinerary(mode=mode)
182
177
  except Exception as e:
183
178
  raise HTTPException(500, f"行程规划失败: {e}")
184
179
 
@@ -186,7 +181,7 @@ async def itinerary_plan(mode: str = "driving"):
186
181
  @app.post("/api/itinerary/import")
187
182
  async def itinerary_import(p: ImportPayload):
188
183
  """导入行程点。"""
189
- rt = _api._os_instance.itinerary_runtime
184
+ rt = _api.os_instance().itinerary_runtime
190
185
  src = _resolve_csv_path(p.csv_path) if p.csv_path else ""
191
186
  try:
192
187
  if src:
@@ -202,9 +197,6 @@ async def itinerary_import(p: ImportPayload):
202
197
  raise HTTPException(400, f"导入失败: {e}")
203
198
 
204
199
 
205
- # ── 领域导入:超越 csv_path,支持上传 csv / xlsx / json ─────────────────────
206
- # 三领域运行时的 import_csv 是"列→类型转换"的唯一权威。多格式文件在此统一解析成 rows,
207
- # 再桥接成运行时认识的临时 CSV → 复用 import_csv 的转换逻辑(不碰运行时,单一真源)。
208
200
  _IMPORT_MAX_BYTES = 10 * 1024 * 1024
209
201
  _IMPORT_COLUMNS = {
210
202
  "finance": ["amount", "category", "entity", "account", "timestamp", "tags"],
@@ -291,14 +283,14 @@ async def _domain_import_file(domain: str, runtime, file: UploadFile):
291
283
  @app.post("/api/finance/import/file")
292
284
  async def finance_import_file(file: UploadFile = File(...)):
293
285
  """上传 csv/xlsx/json 导入交易。列:amount,category,entity,account,timestamp,tags。"""
294
- rt = _api._os_instance.finance_runtime
286
+ rt = _api.os_instance().finance_runtime
295
287
  return await _domain_import_file("finance", rt, file)
296
288
 
297
289
 
298
290
  @app.post("/api/learning/import/file")
299
291
  async def learning_import_file(file: UploadFile = File(...)):
300
292
  """上传 csv/xlsx/json 导入知识点。列:content,topic,source,tags。"""
301
- rt = _api._os_instance.learning_runtime
293
+ rt = _api.os_instance().learning_runtime
302
294
  res = await _domain_import_file("learning", rt, file)
303
295
  try:
304
296
  rt.build_connections()
@@ -310,5 +302,5 @@ async def learning_import_file(file: UploadFile = File(...)):
310
302
  @app.post("/api/itinerary/import/file")
311
303
  async def itinerary_import_file(file: UploadFile = File(...)):
312
304
  """上传 csv/xlsx/json 导入行程点。列:name,address,lng,lat,start_time,duration_min,city。"""
313
- rt = _api._os_instance.itinerary_runtime
305
+ rt = _api.os_instance().itinerary_runtime
314
306
  return await _domain_import_file("itinerary", rt, file)