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 @@
1
+ """具体 Agent 实现子包(步骤 3 拆分)。"""
@@ -0,0 +1,70 @@
1
+ """共享辅助函数与多步链式常量(步骤 3 从 agents.py 拆出,纯搬移)。"""
2
+
3
+ from ...core.dispatch import dispatch_failed as _dispatch_failed # noqa: F401 (re-export)
4
+
5
+ PRIOR_MAX_STEPS = 3
6
+ PRIOR_MAX_CHARS = 300
7
+
8
+
9
+ def _with_prior(subtask: str, prior: list) -> str:
10
+ """把前序步骤结果并进下一步的任务文本,让多步真正成链。
11
+
12
+ 为什么并进 task 文本而不是只塞 context:各领域 Agent 的 `_execute` 是各自实现的,
13
+ 未必会去读 context;而 task 文本必定进提示词 —— 这样对**所有** Agent 都生效,
14
+ 且无需改动任何一个 Agent。context 里也照样放一份结构化的(两手准备)。
15
+ """
16
+ if not prior:
17
+ return subtask
18
+ lines = "\n".join(
19
+ f"第{p['step']}步({p['agent']}):{str(p.get('output',''))[:PRIOR_MAX_CHARS]}"
20
+ for p in prior[-PRIOR_MAX_STEPS:]
21
+ )
22
+ return f"{subtask}\n\n[前序步骤结果 —— 若与本步相关,请据此作答]\n{lines}"
23
+
24
+
25
+ def _fmt_mem(m: dict, max_chars: int | None = None) -> str:
26
+ """把一条召回记忆格式化为带结构信息的文本(标题/相关度/共享标记)。
27
+
28
+ 替代此前各领域 Agent 只取 content 的注入——那会丢弃 title/score/shared 等
29
+ 结构信息,让 LLM 无法区分"高相关共享决策"与"低相关私有闲聊"。字段可能缺失
30
+ (不同召回路径返回的 dict 字段不一),全部安全降级:title 空 / score 非数字 /
31
+ 无 shared 时退化为纯 content,行为与改前一致。max_chars=None 不截断。
32
+ """
33
+ title = (m.get("title") or "").strip()
34
+ content = (m.get("content") or "").strip()
35
+ if max_chars is not None:
36
+ content = content[:max_chars]
37
+ parts = [title] if title else []
38
+ score = m.get("score")
39
+ if score is not None:
40
+ try:
41
+ parts.append(f"相关度{float(score):.2f}")
42
+ except (TypeError, ValueError):
43
+ pass
44
+ if m.get("shared"):
45
+ parts.append("[共享]")
46
+ head = " ".join(parts)
47
+ return f"{head}:{content}" if head else content
48
+
49
+
50
+ def _extract_step_results(dr) -> list:
51
+ """从 dispatch_result 提取各步结果(供 LLM 反思:含成功/失败状态与输出)。"""
52
+ if not isinstance(dr, dict):
53
+ return []
54
+ result = dr.get("result") or {}
55
+ if not isinstance(result, dict):
56
+ return []
57
+ steps = result.get("parallel_steps") or result.get("multi_steps") or []
58
+ out = []
59
+ for s in steps:
60
+ if not isinstance(s, dict):
61
+ continue
62
+ out.append({
63
+ "subtask": s.get("subtask") or s.get("step"),
64
+ "agent": s.get("agent"),
65
+ "output": s.get("output", ""),
66
+ "blocked": bool(s.get("blocked")),
67
+ "failed": bool(s.get("failed")),
68
+ })
69
+ return out
70
+
@@ -0,0 +1,231 @@
1
+ """FinanceAgent —— 财务(理财、收支、预算)。"""
2
+
3
+ from datetime import datetime
4
+
5
+ from ..base import CognitiveAgent
6
+ from ._helpers import _fmt_mem
7
+
8
+ _ADVICE_WORDS = ("分析", "建议", "怎么", "如何", "多少", "预算", "超支", "优化", "规划",
9
+ "趋势", "为什么", "要不要", "合算", "划算", "省", "控制", "总结",
10
+ "报告", "够不够", "对比", "排名", "异常", "提醒", "合理", "占比",
11
+ "占收入", "比例")
12
+
13
+
14
+ def _wants_advice(task: str) -> bool:
15
+ t = task or ""
16
+ return any(w in t for w in _ADVICE_WORDS)
17
+
18
+
19
+ def _should_autorecord(task: str) -> bool:
20
+ """记账闸门:有金额 + 像报备,且不是「在讨论钱」而不是「花了钱」。
21
+
22
+ 最容易记错的三类:
23
+ - 「衣服预算 800」→ 带金额但是设预算,记成支出就把预算本身花了
24
+ - 「每月房租 3000 占收入 10000 合理吗」→ 讨论额度与比例,不是报账
25
+ - 「本月花了多少」→ 带"花"和数字但没有金额,解析器本来就返回空
26
+
27
+ 判据:带分析意图时,只有**完成态**动词(买了/花了/交了)才证明钱真出去了;
28
+ 否则一律交给分析路径,宁可不记也不要记错。
29
+ """
30
+ from ...finance.nlparse import has_expense_signal, has_settled_spend
31
+ t = task or ""
32
+ if not has_expense_signal(t):
33
+ return False
34
+ settled = has_settled_spend(t)
35
+ if "预算" in t and not settled:
36
+ return False
37
+ if _wants_advice(t) and not settled:
38
+ return False
39
+ if settled:
40
+ return True
41
+ return not _wants_advice(t)
42
+
43
+
44
+ class FinanceAgent(CognitiveAgent):
45
+ AGENT_ID = "finance"
46
+ AGENT_NAME = "财务 Agent"
47
+ ALLOWED_TOOL_CATEGORIES = {"read", "write", "finance"}
48
+
49
+ def attach_runtime(self, runtime):
50
+ """挂载财务内核(FinanceRuntime):记忆+推理+Policy 守门人。"""
51
+ self.fin = runtime
52
+
53
+ def situation(self) -> dict:
54
+ rt = getattr(self, "fin", None)
55
+ if rt is None:
56
+ return {"domain": "finance", "status": {}, "alerts": []}
57
+ try:
58
+ over = [b for b in rt.reasoning.budget_status(rt.transactions, rt.budgets)
59
+ if b["is_over"]]
60
+ except Exception:
61
+ over = []
62
+ status = {"transactions": len(rt.transactions),
63
+ "over_budget_categories": [b["category"] for b in over]}
64
+ alerts = [{"domain": "finance", "severity": "warn",
65
+ "msg": f"{b['category']} 超支 {b['spent']:.0f}/{b['monthly_limit']:.0f}"}
66
+ for b in over]
67
+ return {"domain": "finance", "status": status, "alerts": alerts}
68
+
69
+ def handle_cross_request(self, req) -> None:
70
+ """差旅/采购预算申请 → 财务守门人**认知化**裁决(v2 ③:观察→假设→证据→裁决,
71
+ 规则实现、裁决逐字不变、显式证据链)。"""
72
+ if req.request_type != "budget_request":
73
+ return super().handle_cross_request(req)
74
+ rt = getattr(self, "fin", None)
75
+ amount = req.payload.get("amount", 0)
76
+ category = req.payload.get("category", "travel")
77
+ if rt is None:
78
+ req.status = "approved"; req.verdict = "财务内核未挂载,默认放行"; return
79
+ from ...finance import Transaction
80
+ from ...finance.reasoner import FinanceGatekeeperReasoner
81
+ txn = Transaction(amount=-abs(amount), category=category,
82
+ entity=req.payload.get("entity", "差旅"),
83
+ account=req.payload.get("account", "default"))
84
+ bal = rt.account_balances.get(txn.account)
85
+ import os as _os
86
+ _llm_on = _os.environ.get("FOXCLAW_FINANCE_LLM_ARBITER", "") == "1"
87
+ rv = FinanceGatekeeperReasoner(
88
+ rt.policy, runtime=rt,
89
+ llm=getattr(self, "router", None), llm_enabled=_llm_on).reason((txn, bal))
90
+ req.status = {"approve": "approved", "confirm": "needs_confirmation",
91
+ "reject": "rejected"}[rv.decision]
92
+ req.verdict = rv.rationale
93
+ try:
94
+ req.evidence = [e.to_dict() for e in rv.evidence]
95
+ req.reason_source = rv.source
96
+ except Exception:
97
+ pass
98
+
99
+ def _auto_record(self, task: str) -> str:
100
+ """把「零星花钱报备」变成账本里的一笔,返回可读回执(不该记时返回空串)。
101
+
102
+ 走内核的确定性解析:金额/币种/时间一旦由 LLM 转述就可能失真,
103
+ 而这些数字会直接进预算与风控,错了不报错、只会安静地错。
104
+ """
105
+ fin = getattr(self, "fin", None)
106
+ if fin is None or not _should_autorecord(task):
107
+ return ""
108
+ try:
109
+ rec = fin.record_text(task)
110
+ except Exception:
111
+ return ""
112
+ if not rec.get("recorded"):
113
+ return ""
114
+ ack = rec.get("ack") or ""
115
+ self._think(content=f"记账 {len(rec['recorded'])} 笔:{ack[:120]}",
116
+ node_type="decision", conf=0.95)
117
+ try:
118
+ self._store_memory(
119
+ content=f"用户报备开销:{task[:80]}\n记账结果:{ack[:200]}",
120
+ mem_type="execution",
121
+ title=f"记账_{datetime.now().strftime('%Y%m%d')}",
122
+ tags=["finance", "ledger"],
123
+ )
124
+ except Exception:
125
+ pass
126
+ return ack
127
+
128
+ def analyze_finances(self, period: str | None = None, current_balance: float | None = None) -> dict:
129
+ """调用财务内核做综合分析,并把关键洞察写入 ThoughtChain。"""
130
+ fin = getattr(self, "fin", None)
131
+ if fin is None:
132
+ return {"error": "财务内核未挂载"}
133
+ report = fin.analyze(period=period, current_balance=current_balance)
134
+ verdicts = report.get("gatekeeper_verdicts", [])
135
+ self._think(
136
+ content=f"财务分析完成:{report['transaction_count']} 笔交易,"
137
+ f"{len(report.get('anomalies',[]))} 异常,{len(verdicts)} 条守门人裁决",
138
+ node_type="reflection", conf=0.9,
139
+ )
140
+ return report
141
+
142
+ @property
143
+ def _role_prompt(self) -> str:
144
+ return """你是 Foxclaw 的财务 Agent,专注于个人财务管理。
145
+
146
+ 你的核心职责:
147
+ 1. 追踪收入和支出记录
148
+ 2. 分析消费模式,给出理财建议
149
+ 3. 制定储蓄和投资计划
150
+ 4. 提供财务报告和趋势分析
151
+
152
+ 工作原则:
153
+ - 所有财务建议都基于用户的历史记录和明确说明的情况
154
+ - 保守稳健,不推荐高风险操作
155
+ - 尊重用户的财务隐私
156
+ - 记住用户的财务目标和偏好,越用越了解
157
+
158
+ 注意:你只能访问财务相关的工具和数据,不能操作删除功能。"""
159
+
160
+ _task_entities = lambda self, task: ["财务", "收支", "储蓄"]
161
+ _recall_query = lambda self, task: f"财务 {task}"
162
+
163
+ async def _execute(self, task, context, session_id, memories):
164
+ ledger_ack = self._auto_record(task)
165
+ if ledger_ack and not _wants_advice(task):
166
+ return ledger_ack, {}
167
+
168
+ self._think(
169
+ content=f"财务分析:{task}",
170
+ node_type="reasoning",
171
+ conf=0.93,
172
+ )
173
+
174
+ finance_context = ""
175
+ if memories:
176
+ finance_context = "历史财务记录:\n" + "\n".join([
177
+ _fmt_mem(m, 100) for m in memories[:5]
178
+ ])
179
+
180
+ kernel_insight = ""
181
+ fin = getattr(self, "fin", None)
182
+ if fin is not None and getattr(fin, "transactions", None):
183
+ try:
184
+ report = fin.analyze()
185
+ ec = report.get("evidence_chains", [])
186
+ anomalies = report.get("anomalies", [])
187
+ parts = []
188
+ for chain in ec:
189
+ parts.append(
190
+ f"· {chain['decision_type']}: 风险{chain['risk_score']}"
191
+ f"({chain['risk_level']}) — {chain.get('rationale','')};"
192
+ f"建议: {chain.get('recommended_action','')}")
193
+ if anomalies:
194
+ parts.append(f"· 检测到 {len(anomalies)} 笔异常支出")
195
+ if parts:
196
+ kernel_insight = ("\n\n【财务分析结果(基于真实交易,请据此回答)】\n"
197
+ + "\n".join(parts))
198
+ self._think(
199
+ content=f"财务内核证据链:{len(ec)} 条决策,{len(anomalies)} 异常",
200
+ node_type="reflection", conf=0.9,
201
+ )
202
+ except Exception:
203
+ pass
204
+
205
+ if ledger_ack:
206
+ kernel_insight = ("\n\n【刚刚已记账(已落账,请据此回答,不要重复报账)】\n"
207
+ + ledger_ack + kernel_insight)
208
+
209
+ response = await self._call_llm_web(task, [{
210
+ "role": "user",
211
+ "content": f"""财务任务:{task}
212
+
213
+ {finance_context}{kernel_insight}
214
+
215
+ 请提供专业的财务分析和建议。如果需要记录具体数字,请以结构化格式返回。"""
216
+ }])
217
+
218
+ self._think(
219
+ content=f"财务分析完成:{response[:150]}",
220
+ node_type="decision",
221
+ conf=0.90,
222
+ )
223
+
224
+ self._store_memory(
225
+ content=f"财务分析:{task[:80]}\n结论:{response[:200]}",
226
+ mem_type="decision",
227
+ title=f"财务_{datetime.now().strftime('%Y%m%d')}",
228
+ tags=["finance", "analysis"],
229
+ )
230
+ return response, {}
231
+
@@ -0,0 +1,350 @@
1
+ """OfficeAgent —— 办公学习(学习/知识/文档/写作 + 日程/行程)。
2
+
3
+ Agent 拓扑收缩(5 → 3)后,原 `AdminAgent`(行政:日程/行程/会议)整块并入本 Agent:
4
+ · 行程内核 ItineraryRuntime 改由 `attach_itinerary` 挂载(与学习内核并列);
5
+ · `plan_itinerary / handle_cross_request / concede` 随之下沉到这里 —— 领域逻辑仍归
6
+ "离内核最近的那个 Agent",编排层不直接读 itinerary/learning 内核;
7
+ · `situation()` 改成一次自报**多个**领域(learning + itinerary),驾驶舱按 domains 聚合。
8
+
9
+ 原行政 Agent 的角色行为(日程安排话术、时间偏好、冲突前置检查)已下放为指令技能
10
+ `skills/schedule-itinerary/SKILL.md`,由本 Agent 在命中触发词时自动注入 —— 不再需要一个
11
+ 独立的行政 Agent 常驻。
12
+ """
13
+
14
+ import json
15
+ from datetime import datetime
16
+
17
+ from ..base import CognitiveAgent
18
+ from ._helpers import _fmt_mem
19
+
20
+
21
+ class OfficeAgent(CognitiveAgent):
22
+ AGENT_ID = "office"
23
+ AGENT_NAME = "办公学习 Agent"
24
+ ALLOWED_TOOL_CATEGORIES = {"read", "write", "network", "calendar"}
25
+
26
+ SCHEDULE_HINTS = ("日程", "行程", "会议", "出差", "预约", "日历", "路线", "几点", "提醒")
27
+
28
+ def attach_runtime(self, runtime):
29
+ """挂载学习内核(LearningRuntime):间隔重复 + 知识图谱。"""
30
+ self.learn_rt = runtime
31
+
32
+ def attach_itinerary(self, runtime):
33
+ """挂载行程内核(ItineraryRuntime):路线优化 + 可达性 + 守门人。
34
+
35
+ (原 AdminAgent.attach_runtime 的职责,随行政并入办公)
36
+ """
37
+ self.itin = runtime
38
+
39
+ def situation(self) -> dict:
40
+ """一次自报两个领域:学习 + 行程。编排层按 `domains` 聚合(缺一个不影响另一个)。"""
41
+ return {"domains": {"learning": self._learning_status(),
42
+ "itinerary": self._itinerary_status()},
43
+ "alerts": self._learning_alerts() + self._itinerary_alerts()}
44
+
45
+ def _learning_status(self) -> dict:
46
+ rt = getattr(self, "learn_rt", None)
47
+ if rt is None:
48
+ return {}
49
+ try:
50
+ states = list(rt.review_states.values())
51
+ due = rt.sr.due_items(states)
52
+ risky = rt.sr.forgetting_risk(states)
53
+ except Exception:
54
+ due = risky = []
55
+ return {"total_items": len(rt.items), "due_today": len(due), "at_risk": len(risky)}
56
+
57
+ def _learning_alerts(self) -> list:
58
+ st = self._learning_status()
59
+ return ([{"domain": "learning", "severity": "info",
60
+ "msg": f"{st['at_risk']} 个知识点高遗忘风险待复习"}] if st.get("at_risk") else [])
61
+
62
+ def _itinerary_status(self) -> dict:
63
+ rt = getattr(self, "itin", None)
64
+ if rt is None:
65
+ return {}
66
+ try:
67
+ conflicts = rt.reasoning.detect_conflicts(rt.waypoints)
68
+ except Exception:
69
+ conflicts = []
70
+ return {"waypoints": len(rt.waypoints), "conflicts": len(conflicts)}
71
+
72
+ def _itinerary_alerts(self) -> list:
73
+ rt = getattr(self, "itin", None)
74
+ if rt is None:
75
+ return []
76
+ try:
77
+ conflicts = rt.reasoning.detect_conflicts(rt.waypoints)
78
+ except Exception:
79
+ conflicts = []
80
+ return [{"domain": "itinerary", "severity": "critical", "msg": c["reason"]}
81
+ for c in conflicts]
82
+
83
+ CONCEDE_STEP = 0.75
84
+ CONCEDE_FLOOR = 0.60
85
+
86
+ def concede(self, req, verdict: str):
87
+ """【协商闭环·发起方】差旅预算被财务守门人拒 → 降标准重报。
88
+
89
+ 只处理自己发起的 budget_request;其余回退默认(不让步)。
90
+ 每轮必须**真降**,且有下限 —— 否则会无限贴着守门人的红线来回试探。
91
+ """
92
+ if req.request_type != "budget_request":
93
+ return super().concede(req, verdict)
94
+ try:
95
+ amount = float(req.payload.get("amount") or 0)
96
+ except (TypeError, ValueError):
97
+ return None
98
+ if amount <= 0:
99
+ return None
100
+ original = float(req.payload.get("_original_amount") or amount)
101
+ new_amount = max(round(amount * self.CONCEDE_STEP), round(original * self.CONCEDE_FLOOR))
102
+ if new_amount <= 0 or new_amount >= amount:
103
+ return None
104
+ p = dict(req.payload)
105
+ p["amount"] = new_amount
106
+ p["_original_amount"] = original
107
+ p["_concession"] = f"降标准:{amount:.0f} → {new_amount:.0f}"
108
+ return p
109
+
110
+ def handle_cross_request(self, req) -> None:
111
+ """日程占用申请 → 行程守门人检查冲突。"""
112
+ if req.request_type != "schedule_request":
113
+ return super().handle_cross_request(req)
114
+ rt = getattr(self, "itin", None)
115
+ if rt is None:
116
+ req.status = "approved"; req.verdict = "行程内核未挂载,默认放行"; return
117
+ new_time = req.payload.get("start_time")
118
+ conflict = False
119
+ if new_time:
120
+ from datetime import datetime as _dt
121
+ nt = _dt.fromisoformat(new_time)
122
+ for wp in rt.waypoints:
123
+ if wp.dt and abs((wp.dt - nt).total_seconds()) < 3600:
124
+ conflict = True
125
+ break
126
+ if conflict:
127
+ req.status = "needs_confirmation"
128
+ req.verdict = "该时段附近已有行程,可能冲突,需确认"
129
+ else:
130
+ req.status = "approved"
131
+ req.verdict = "时段空闲,日程可安排"
132
+
133
+ def plan_itinerary(self, mode: str = "driving") -> dict:
134
+ """调用行程内核做规划,关键洞察写入 ThoughtChain。"""
135
+ rt = getattr(self, "itin", None)
136
+ if rt is None:
137
+ return {"error": "行程内核未挂载"}
138
+ report = rt.plan(mode=mode)
139
+ opt = report.get("optimized_route") or {}
140
+ self._think(
141
+ content=f"行程规划:{report['waypoint_count']} 个点,"
142
+ f"最优路线 {opt.get('total_km','?')} km / {opt.get('total_min','?')} 分钟,"
143
+ f"{len(report['conflicts'])} 个冲突,"
144
+ f"高德={'已接入' if report['amap_enabled'] else '估算模式'}",
145
+ node_type="reflection", conf=0.9,
146
+ )
147
+ return report
148
+
149
+ @staticmethod
150
+ def _strip_knowledge_json(text: str) -> str:
151
+ """从回复中剥离 {"new_knowledge": [...]} 结构化块,只保留自然语言。"""
152
+ if not text or "new_knowledge" not in text:
153
+ return text
154
+ import re
155
+ cleaned = re.sub(r'`{0,3}\s*\{\s*"new_knowledge".*?\}\s*\]\s*\}\s*`{0,3}',
156
+ '', text, flags=re.DOTALL)
157
+ return cleaned.strip()
158
+
159
+ def analyze_learning(self) -> dict:
160
+ """调用学习内核做综合分析,关键洞察写入 ThoughtChain。"""
161
+ rt = getattr(self, "learn_rt", None)
162
+ if rt is None:
163
+ return {"error": "学习内核未挂载"}
164
+ report = rt.analyze()
165
+ try:
166
+ with rt._lock:
167
+ _vals = list(rt.items.values())
168
+ report["items"] = [
169
+ {"title": (it.content or "")[:40], "content": it.content,
170
+ "topic": it.topic, "tag": it.topic, "source": it.source,
171
+ "tags": it.tags, "id": it.id, "notes": getattr(it, "notes", [])}
172
+ for it in reversed(_vals)
173
+ ][:30]
174
+ except Exception:
175
+ report.setdefault("items", [])
176
+ self._think(
177
+ content=f"学习分析:{report['total_items']} 个知识点,"
178
+ f"平均掌握度 {report['avg_mastery']},"
179
+ f"{report['due_today']} 个待复习,"
180
+ f"图谱 {report['graph']['clusters']} 个知识簇",
181
+ node_type="reflection", conf=0.9,
182
+ )
183
+ return report
184
+
185
+ @property
186
+ def _role_prompt(self) -> str:
187
+ return """你是 Foxclaw 的办公学习 Agent,专注于办公、学习与日程安排。
188
+
189
+ 你的核心职责:
190
+ 1. 帮助用户学习新知识和技能
191
+ 2. 管理知识储备,建立个人知识库
192
+ 3. 处理文档、报告和写作任务
193
+ 4. 跟踪学习进度和知识演化
194
+ 5. 安排日程、会议与出行行程,检查时间冲突
195
+
196
+ Hermes 自学原则(像赫尔墨斯那样):
197
+ - 不是学习更多技能,而是通过每次交互变得更"智能"
198
+ - 记住用户的学习风格和偏好
199
+ - 识别知识缺口,主动补充
200
+ - 建立知识间的连接,而不只是存储孤立事实
201
+ - 每次对话都让知识库更丰富、更有组织
202
+
203
+ 日程/行程原则:
204
+ - 主动记住用户的时间偏好(如不喜欢早 8 点前的会议)
205
+ - 排期前先查已有行程,提前发现冲突
206
+ - 差旅预算议题与财务协商时,一轮一降、有下限,不硬闯
207
+
208
+ 你的知识是活的,会生长的。"""
209
+
210
+ _task_entities = lambda self, task: ["学习", "知识", "文档", "日程", "行程", "会议"]
211
+ _recall_topk = 8
212
+
213
+ def _recall_query(self, task: str) -> str:
214
+ """学习 + 日程两类记忆都要召回(合并后单一 Agent 覆盖两个域)。"""
215
+ return f"学习 知识 日程 行程 {task}"
216
+
217
+ def _is_schedule_task(self, task: str) -> bool:
218
+ return any(k in (task or "") for k in self.SCHEDULE_HINTS)
219
+
220
+ async def _execute(self, task, context, session_id, memories):
221
+ schedule = self._is_schedule_task(task)
222
+ self._think(
223
+ content=(f"{'日程/行程' if schedule else '知识'}任务:{task},"
224
+ f"已有相关记忆:{len(memories)} 条"),
225
+ node_type="reasoning",
226
+ conf=0.92,
227
+ )
228
+
229
+ knowledge_context = ""
230
+ if memories:
231
+ knowledge_context = "相关知识储备:\n" + "\n".join([
232
+ f"- {_fmt_mem(m, 80)}" for m in memories[:6]
233
+ ])
234
+
235
+ kernel_insight = ""
236
+ learn_rt = getattr(self, "learn_rt", None)
237
+ if learn_rt is not None and getattr(learn_rt, "items", None):
238
+ try:
239
+ report = learn_rt.analyze()
240
+ due = report.get("due_today", 0)
241
+ mastery = report.get("avg_mastery", 0)
242
+ kernel_insight += (
243
+ f"\n\n【学习状态(基于真实知识库)】\n"
244
+ f"· 知识点总数: {report.get('total_items', 0)}\n"
245
+ f"· 平均掌握度: {mastery}\n"
246
+ f"· 今日待复习: {due} 个")
247
+ self._think(
248
+ content=f"学习内核:{report.get('total_items',0)} 知识点,{due} 待复习",
249
+ node_type="reflection", conf=0.9,
250
+ )
251
+ except Exception:
252
+ pass
253
+
254
+ itin = getattr(self, "itin", None)
255
+ if itin is not None and getattr(itin, "waypoints", None):
256
+ try:
257
+ report = itin.plan()
258
+ opt = report.get("optimized_route") or {}
259
+ conflicts = report.get("conflicts", [])
260
+ parts = []
261
+ if opt:
262
+ parts.append(f"· 最优路线: {opt.get('total_km','?')}km / "
263
+ f"{opt.get('total_min','?')}分钟")
264
+ if conflicts:
265
+ parts.append(f"· 检测到 {len(conflicts)} 个日程冲突")
266
+ if parts:
267
+ kernel_insight += ("\n\n【行程分析结果(基于真实路点,请据此回答)】\n"
268
+ + "\n".join(parts))
269
+ self._think(
270
+ content=f"行程内核:路线已优化,{len(conflicts)} 个冲突",
271
+ node_type="reflection", conf=0.9,
272
+ )
273
+ except Exception:
274
+ pass
275
+
276
+ if schedule:
277
+ prompt = f"""行程/日程任务:{task}
278
+
279
+ {knowledge_context}{kernel_insight}
280
+
281
+ 请处理这个日程/行政任务,给出具体安排建议;有冲突就明说,别假装排开了。"""
282
+ else:
283
+ prompt = f"""学习/办公任务:{task}
284
+
285
+ {knowledge_context}{kernel_insight}
286
+
287
+ 请:
288
+ 1. 帮助完成这个学习/办公任务
289
+ 2. 识别涉及的关键知识点
290
+ 3. 指出哪些是新知识(需要记录)
291
+
292
+ 回答后,以 JSON 格式附上需要记录的新知识:
293
+ {{"new_knowledge": [{{"title": "知识标题", "content": "知识内容", "tags": ["标签"]}}]}}"""
294
+
295
+ response = await self._call_llm_web(task, [{"role": "user", "content": prompt}])
296
+
297
+ new_knowledge_count = 0
298
+ if not schedule:
299
+ try:
300
+ start = response.find('{"new_knowledge"')
301
+ if start < 0:
302
+ start = response.find('{ "new_knowledge"')
303
+ if start >= 0:
304
+ depth, end, in_str, esc = 0, -1, False, False
305
+ for i in range(start, len(response)):
306
+ c = response[i]
307
+ if esc:
308
+ esc = False; continue
309
+ if c == '\\':
310
+ esc = True; continue
311
+ if c == '"':
312
+ in_str = not in_str
313
+ elif not in_str:
314
+ if c == '{':
315
+ depth += 1
316
+ elif c == '}':
317
+ depth -= 1
318
+ if depth == 0:
319
+ end = i + 1; break
320
+ if end > start:
321
+ knowledge_data = json.loads(response[start:end])
322
+ for k in knowledge_data.get("new_knowledge", []):
323
+ if not isinstance(k, dict) or not k.get("content"):
324
+ continue
325
+ self._store_memory(
326
+ content=k["content"],
327
+ mem_type="concept",
328
+ title=k.get("title", "未命名知识"),
329
+ tags=k.get("tags", ["office", "learning"]),
330
+ )
331
+ new_knowledge_count += 1
332
+ except Exception:
333
+ pass
334
+
335
+ response = self._strip_knowledge_json(response)
336
+
337
+ self._think(
338
+ content=(f"日程安排完成:{response[:120]}" if schedule
339
+ else f"学习完成,新增知识 {new_knowledge_count} 条"),
340
+ node_type="reflection",
341
+ conf=0.90,
342
+ )
343
+ if schedule:
344
+ self._store_memory(
345
+ content=f"日程任务:{task[:80]}\n安排:{response[:200]}",
346
+ mem_type="execution",
347
+ title=f"日程_{datetime.now().strftime('%Y%m%d')}",
348
+ tags=["office", "schedule"],
349
+ )
350
+ return response, ({} if schedule else {"new_knowledge_stored": new_knowledge_count})