foxclaw 0.1.2 → 0.1.4

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 (368) 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/.github/workflows/release.yml +5 -0
  6. package/python/CHANGELOG.md +100 -0
  7. package/python/CHANGELOG_CN.md +100 -0
  8. package/python/README.md +17 -2
  9. package/python/README_CN.md +38 -20
  10. package/python/bench/bm25_audit.py +1 -14
  11. package/python/bench/bm25_regression.py +0 -1
  12. package/python/bench/bm25_stress.py +0 -4
  13. package/python/bench/cd_audit.py +0 -23
  14. package/python/bench/chaos.py +6 -8
  15. package/python/bench/e2e_real_models.py +0 -1
  16. package/python/bench/llm_retry_test.py +2 -9
  17. package/python/bench/load_test.py +5 -8
  18. package/python/bench/vector_ivf_upgrade.py +1 -7
  19. package/python/docs/CONTENT_GUARD.md +189 -0
  20. package/python/docs/CONTENT_GUARD_CN.md +189 -0
  21. package/python/docs/RELEASE.md +78 -37
  22. package/python/docs/RUN_CN.md +2 -2
  23. package/python/evals/governance/cases.py +0 -37
  24. package/python/evals/governance/run_evals.py +5 -37
  25. package/python/examples/governance_middleware_demo.py +1 -6
  26. package/python/examples/langgraph_governance_demo.py +7 -11
  27. package/python/foxclaw/__init__.py +74 -152
  28. package/python/foxclaw/agents/base.py +82 -136
  29. package/python/foxclaw/agents/impl/_helpers.py +2 -10
  30. package/python/foxclaw/agents/impl/finance.py +4 -21
  31. package/python/foxclaw/agents/impl/office.py +7 -24
  32. package/python/foxclaw/agents/impl/supervisor.py +12 -79
  33. package/python/foxclaw/agents/mixins/_context.py +0 -23
  34. package/python/foxclaw/agents/mixins/augment.py +10 -16
  35. package/python/foxclaw/agents/mixins/llm.py +7 -6
  36. package/python/foxclaw/agents/mixins/memory.py +23 -3
  37. package/python/foxclaw/agents/mixins/preference.py +58 -8
  38. package/python/foxclaw/agents/mixins/prompt.py +59 -12
  39. package/python/foxclaw/agents/mixins/skillbox.py +6 -6
  40. package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
  41. package/python/foxclaw/agents/rules/finance/RULES.md +131 -0
  42. package/python/foxclaw/api/__init__.py +18 -77
  43. package/python/foxclaw/api/_state.py +43 -0
  44. package/python/foxclaw/api/bootstrap.py +114 -0
  45. package/python/foxclaw/api/middleware.py +5 -28
  46. package/python/foxclaw/api/routes/ahs.py +60 -73
  47. package/python/foxclaw/api/routes/attachments.py +5 -6
  48. package/python/foxclaw/api/routes/browser.py +7 -11
  49. package/python/foxclaw/api/routes/domain.py +18 -26
  50. package/python/foxclaw/api/routes/knowledge.py +3 -21
  51. package/python/foxclaw/api/routes/llm_admin.py +21 -33
  52. package/python/foxclaw/api/routes/memory.py +50 -12
  53. package/python/foxclaw/api/routes/orchestration.py +3 -5
  54. package/python/foxclaw/api/routes/recovery.py +4 -3
  55. package/python/foxclaw/api/routes/review.py +2 -11
  56. package/python/foxclaw/api/routes/search.py +3 -7
  57. package/python/foxclaw/api/routes/system.py +33 -49
  58. package/python/foxclaw/api/routes/task.py +31 -49
  59. package/python/foxclaw/api/routes/ws.py +2 -6
  60. package/python/foxclaw/api/spine.py +1 -4
  61. package/python/foxclaw/behavior/__init__.py +20 -0
  62. package/python/foxclaw/behavior/behavior.yaml +62 -0
  63. package/python/foxclaw/behavior/spec.py +287 -0
  64. package/python/foxclaw/browser/action/action_executor.py +1 -5
  65. package/python/foxclaw/browser/action/action_types.py +0 -1
  66. package/python/foxclaw/browser/action/url_policy.py +1 -5
  67. package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
  68. package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
  69. package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
  70. package/python/foxclaw/browser/perception/element_indexer.py +1 -2
  71. package/python/foxclaw/browser/service.py +7 -11
  72. package/python/foxclaw/capability/embedding_service.py +4 -6
  73. package/python/foxclaw/channels/__init__.py +9 -11
  74. package/python/foxclaw/channels/discord.py +4 -13
  75. package/python/foxclaw/channels/dispatcher.py +2 -27
  76. package/python/foxclaw/channels/feishu.py +10 -20
  77. package/python/foxclaw/channels/openclaw_bridge.py +4 -9
  78. package/python/foxclaw/channels/signing.py +9 -9
  79. package/python/foxclaw/channels/telegram.py +3 -4
  80. package/python/foxclaw/channels/weixin.py +7 -17
  81. package/python/foxclaw/cli.py +42 -13
  82. package/python/foxclaw/cognitive/reasoner.py +9 -10
  83. package/python/foxclaw/cognitive/state.py +18 -24
  84. package/python/foxclaw/core/audit_chain.py +1 -3
  85. package/python/foxclaw/core/audit_sink.py +3 -3
  86. package/python/foxclaw/core/compaction.py +8 -26
  87. package/python/foxclaw/core/config.py +2 -2
  88. package/python/foxclaw/core/confirm_nonce.py +6 -12
  89. package/python/foxclaw/core/content_guard.py +341 -0
  90. package/python/foxclaw/core/envfile.py +2 -2
  91. package/python/foxclaw/core/failure_policy.py +17 -26
  92. package/python/foxclaw/core/gateway.py +6 -10
  93. package/python/foxclaw/core/governance_audit.py +3 -7
  94. package/python/foxclaw/core/idempotency.py +15 -22
  95. package/python/foxclaw/core/injection_guard.py +10 -29
  96. package/python/foxclaw/core/memory_adapter.py +53 -60
  97. package/python/foxclaw/core/metrics.py +4 -5
  98. package/python/foxclaw/core/model_router.py +74 -150
  99. package/python/foxclaw/core/paths.py +1 -1
  100. package/python/foxclaw/core/recovery.py +21 -34
  101. package/python/foxclaw/core/scheduler.py +23 -26
  102. package/python/foxclaw/core/secrets.py +0 -3
  103. package/python/foxclaw/core/sensitive_write.py +283 -0
  104. package/python/foxclaw/core/sessions.py +4 -6
  105. package/python/foxclaw/core/settings.py +16 -32
  106. package/python/foxclaw/core/shared_write.py +11 -23
  107. package/python/foxclaw/core/steering_queue.py +12 -11
  108. package/python/foxclaw/core/system_bus.py +1 -1
  109. package/python/foxclaw/core/thought_chain.py +30 -32
  110. package/python/foxclaw/core/token_meter.py +9 -13
  111. package/python/foxclaw/core/tools.py +4 -12
  112. package/python/foxclaw/core/tracing.py +25 -40
  113. package/python/foxclaw/core/truth_pipeline.py +5 -9
  114. package/python/foxclaw/core/update_check.py +149 -0
  115. package/python/foxclaw/core/vec_recall_adapter.py +11 -37
  116. package/python/foxclaw/core/verify.py +6 -14
  117. package/python/foxclaw/core/web_search.py +4 -28
  118. package/python/foxclaw/finance/evidence.py +14 -18
  119. package/python/foxclaw/finance/fx.py +1 -3
  120. package/python/foxclaw/finance/graph.py +9 -18
  121. package/python/foxclaw/finance/models.py +20 -24
  122. package/python/foxclaw/finance/nlparse.py +12 -72
  123. package/python/foxclaw/finance/policy.py +8 -36
  124. package/python/foxclaw/finance/reasoner.py +3 -11
  125. package/python/foxclaw/finance/reasoning.py +13 -34
  126. package/python/foxclaw/finance/reconcile.py +5 -10
  127. package/python/foxclaw/finance/runtime.py +19 -46
  128. package/python/foxclaw/governance/middleware.py +13 -21
  129. package/python/foxclaw/harness/_base.py +2 -8
  130. package/python/foxclaw/harness/_policy.py +19 -38
  131. package/python/foxclaw/harness/drift.py +1 -13
  132. package/python/foxclaw/harness/event_bus.py +2 -5
  133. package/python/foxclaw/harness/harness.py +9 -97
  134. package/python/foxclaw/harness/loop_detection.py +11 -18
  135. package/python/foxclaw/itinerary/amap_client.py +2 -5
  136. package/python/foxclaw/itinerary/models.py +7 -7
  137. package/python/foxclaw/itinerary/reasoner.py +1 -6
  138. package/python/foxclaw/itinerary/reasoning.py +15 -21
  139. package/python/foxclaw/itinerary/runtime.py +8 -12
  140. package/python/foxclaw/kernel/capabilities.py +5 -6
  141. package/python/foxclaw/kernel/policy_gate.py +4 -10
  142. package/python/foxclaw/kernel/process_manager.py +17 -36
  143. package/python/foxclaw/kernel/scheduler.py +3 -9
  144. package/python/foxclaw/kernel/sentinel.py +13 -23
  145. package/python/foxclaw/kernel/sentinel_client.py +12 -15
  146. package/python/foxclaw/kernel/spine.py +9 -9
  147. package/python/foxclaw/knowledge/__init__.py +4 -20
  148. package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
  149. package/python/foxclaw/learning/knowledge_graph.py +4 -9
  150. package/python/foxclaw/learning/models.py +13 -13
  151. package/python/foxclaw/learning/reasoner.py +1 -4
  152. package/python/foxclaw/learning/runtime.py +19 -31
  153. package/python/foxclaw/learning/spaced_repetition.py +2 -7
  154. package/python/foxclaw/mcp_server.py +2 -3
  155. package/python/foxclaw/orchestration/__init__.py +16 -33
  156. package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
  157. package/python/foxclaw/pipeline/stage_meta.py +21 -0
  158. package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
  159. package/python/foxclaw/planning/llm_planner.py +3 -3
  160. package/python/foxclaw/planning/planner.py +6 -11
  161. package/python/foxclaw/search/agent_port.py +7 -11
  162. package/python/foxclaw/search/evidence.py +0 -4
  163. package/python/foxclaw/search/internal_search.py +3 -5
  164. package/python/foxclaw/search/models.py +11 -13
  165. package/python/foxclaw/search/planner.py +3 -3
  166. package/python/foxclaw/search/retriever.py +1 -1
  167. package/python/foxclaw/search/runtime.py +1 -8
  168. package/python/foxclaw/skills/__init__.py +46 -100
  169. package/python/foxclaw/skills/archive.py +111 -0
  170. package/python/foxclaw/skills/fetch.py +226 -0
  171. package/python/foxclaw/skills/hooks_spec.py +21 -25
  172. package/python/foxclaw/skills/install_cmd.py +216 -0
  173. package/python/foxclaw/skills/lifecycle.py +16 -42
  174. package/python/foxclaw/skills/markdown_skill.py +10 -9
  175. package/python/foxclaw/skills/mcp_client.py +5 -11
  176. package/python/foxclaw/skills/mcp_store.py +0 -3
  177. package/python/foxclaw/skills/permission.py +13 -48
  178. package/python/foxclaw/skills/pipeline.py +21 -53
  179. package/python/foxclaw/skills/policy.py +4 -10
  180. package/python/foxclaw/skills/review.py +3 -3
  181. package/python/frontend/deck/auth.js +0 -7
  182. package/python/frontend/deck/chat_model_picker.js +0 -54
  183. package/python/frontend/deck/domain.js +0 -20
  184. package/python/frontend/deck/main.js +0 -144
  185. package/python/frontend/deck/markdown.js +0 -4
  186. package/python/frontend/deck/settings.js +1 -18
  187. package/python/frontend/deck/state.js +0 -1
  188. package/python/frontend/deck/util.js +0 -2
  189. package/python/frontend/deck/visuals.js +0 -22
  190. package/python/frontend/planet/planet.js +18 -201
  191. package/python/frontend/v3/assets/index-JKfHlb-a.css +1 -0
  192. package/python/frontend/v3/assets/index-tN90dpUH.js +34 -0
  193. package/python/frontend/v3/index.html +3 -3
  194. package/python/frontend/widget_runtime.js +0 -43
  195. package/python/frontend/widgets.js +0 -57
  196. package/python/frontend_src/index.html +1 -1
  197. package/python/frontend_src/package-lock.json +2 -2
  198. package/python/frontend_src/package.json +1 -1
  199. package/python/frontend_src/src/App.vue +3 -7
  200. package/python/frontend_src/src/api.js +0 -7
  201. package/python/frontend_src/src/components/ChatDock.vue +27 -39
  202. package/python/frontend_src/src/components/DocViewer.vue +0 -9
  203. package/python/frontend_src/src/components/Inspector.vue +0 -6
  204. package/python/frontend_src/src/components/LeftRail.vue +60 -39
  205. package/python/frontend_src/src/components/MenuPopover.vue +9 -11
  206. package/python/frontend_src/src/components/MessageStream.vue +18 -30
  207. package/python/frontend_src/src/components/ModelPicker.vue +0 -5
  208. package/python/frontend_src/src/components/QuickIngest.vue +0 -6
  209. package/python/frontend_src/src/components/Stage.vue +71 -25
  210. package/python/frontend_src/src/components/ToolMenu.vue +0 -3
  211. package/python/frontend_src/src/components/TopBar.vue +38 -10
  212. package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
  213. package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
  214. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
  215. package/python/frontend_src/src/components/panels/MemoryPanel.vue +42 -3
  216. package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
  217. package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
  218. package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
  219. package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
  220. package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
  221. package/python/frontend_src/src/lib/fmt.js +0 -4
  222. package/python/frontend_src/src/lib/i18n.js +53 -0
  223. package/python/frontend_src/src/lib/locales/en.js +173 -0
  224. package/python/frontend_src/src/lib/locales/zh.js +174 -0
  225. package/python/frontend_src/src/lib/md.js +0 -14
  226. package/python/frontend_src/src/main.js +2 -0
  227. package/python/frontend_src/src/stores/app.js +17 -32
  228. package/python/frontend_src/src/stores/chat.js +0 -34
  229. package/python/frontend_src/src/stores/model.js +0 -8
  230. package/python/frontend_src/src/stores/panels.js +16 -44
  231. package/python/frontend_src/src/stores/profile.js +31 -0
  232. package/python/frontend_src/src/stores/system.js +25 -0
  233. package/python/frontend_src/vite.config.js +0 -7
  234. package/python/pyproject.toml +17 -3
  235. package/python/requirements.txt +311 -0
  236. package/python/tests/conftest.py +0 -22
  237. package/python/tests/run_all.py +4 -66
  238. package/python/tests/test_agent_lifecycle.py +4 -38
  239. package/python/tests/test_agent_rules.py +81 -0
  240. package/python/tests/test_ahs_p2.py +4 -25
  241. package/python/tests/test_anthropic_tools.py +1 -15
  242. package/python/tests/test_api.py +19 -9
  243. package/python/tests/test_api_auth.py +1 -5
  244. package/python/tests/test_attachments.py +9 -25
  245. package/python/tests/test_audit_chain.py +2 -7
  246. package/python/tests/test_audit_sink.py +0 -6
  247. package/python/tests/test_behavior_spec.py +198 -0
  248. package/python/tests/test_browser.py +2 -6
  249. package/python/tests/test_browser_request_guard.py +0 -4
  250. package/python/tests/test_capabilities.py +0 -6
  251. package/python/tests/test_channel_signing.py +2 -9
  252. package/python/tests/test_channels.py +1 -11
  253. package/python/tests/test_config_layering.py +0 -4
  254. package/python/tests/test_confirm_nonce.py +1 -12
  255. package/python/tests/test_conflict_dismiss.py +143 -0
  256. package/python/tests/test_content_guard.py +177 -0
  257. package/python/tests/test_context_compaction_wiring.py +1 -7
  258. package/python/tests/test_dispatch.py +0 -6
  259. package/python/tests/test_domain_evals.py +11 -80
  260. package/python/tests/test_domain_import.py +0 -6
  261. package/python/tests/test_domain_reasoners.py +0 -14
  262. package/python/tests/test_e2e.py +0 -17
  263. package/python/tests/test_emb_cache.py +7 -17
  264. package/python/tests/test_embedding_singleton.py +1 -3
  265. package/python/tests/test_evals_smoke.py +0 -24
  266. package/python/tests/test_event_spine.py +10 -18
  267. package/python/tests/test_evidence_semantic.py +1 -14
  268. package/python/tests/test_evidence_sse.py +3 -6
  269. package/python/tests/test_file_ingest.py +0 -11
  270. package/python/tests/test_finance.py +0 -9
  271. package/python/tests/test_finance_ledger.py +5 -12
  272. package/python/tests/test_finance_persist.py +8 -12
  273. package/python/tests/test_gateway.py +3 -22
  274. package/python/tests/test_governance.py +5 -52
  275. package/python/tests/test_governance_audit.py +4 -39
  276. package/python/tests/test_governance_middleware.py +1 -7
  277. package/python/tests/test_hkc.py +6 -24
  278. package/python/tests/test_hkc_index_build_manager.py +0 -33
  279. package/python/tests/test_hkc_manifest_reuse.py +4 -27
  280. package/python/tests/test_hkc_search_event_sync.py +3 -54
  281. package/python/tests/test_import_path_guard.py +0 -6
  282. package/python/tests/test_injection_guard.py +18 -36
  283. package/python/tests/test_integration.py +5 -29
  284. package/python/tests/test_itinerary.py +0 -3
  285. package/python/tests/test_kee_semantic.py +0 -5
  286. package/python/tests/test_kernel.py +1 -11
  287. package/python/tests/test_learning.py +3 -12
  288. package/python/tests/test_llm_admin.py +4 -19
  289. package/python/tests/test_llm_planner.py +1 -23
  290. package/python/tests/test_markdown_skills.py +3 -14
  291. package/python/tests/test_markdown_skills_openclaw.py +4 -9
  292. package/python/tests/test_mcp_connectors.py +3 -15
  293. package/python/tests/test_mcp_guardian_discovery.py +2 -6
  294. package/python/tests/test_mcp_no_memory.py +3 -3
  295. package/python/tests/test_mcp_policy_gate.py +0 -1
  296. package/python/tests/test_mcp_server.py +6 -9
  297. package/python/tests/test_memory_recall.py +1 -4
  298. package/python/tests/test_metrics.py +0 -8
  299. package/python/tests/test_multi_chain.py +1 -13
  300. package/python/tests/test_multimodal.py +5 -18
  301. package/python/tests/test_negotiation.py +3 -13
  302. package/python/tests/test_orchestration.py +1 -11
  303. package/python/tests/test_orchestration_decoupled.py +0 -6
  304. package/python/tests/test_p3_p5_hardening.py +3 -17
  305. package/python/tests/test_parallel_dispatch.py +4 -12
  306. package/python/tests/test_pipeline.py +17 -58
  307. package/python/tests/test_plan_visible.py +4 -4
  308. package/python/tests/test_planner.py +0 -7
  309. package/python/tests/test_policy_gate.py +2 -3
  310. package/python/tests/test_profile.py +67 -0
  311. package/python/tests/test_readme_claims.py +163 -0
  312. package/python/tests/test_reasoner.py +2 -17
  313. package/python/tests/test_release_check.py +2 -13
  314. package/python/tests/test_resume_checkpoint.py +7 -15
  315. package/python/tests/test_routing.py +2 -12
  316. package/python/tests/test_scheduler.py +7 -10
  317. package/python/tests/test_search.py +1 -11
  318. package/python/tests/test_secrets.py +4 -10
  319. package/python/tests/test_semantic.py +1 -10
  320. package/python/tests/test_sensitive_write.py +181 -0
  321. package/python/tests/test_sessions.py +3 -4
  322. package/python/tests/test_settings.py +2 -18
  323. package/python/tests/test_shadow_learning.py +3 -43
  324. package/python/tests/test_shared_write.py +0 -7
  325. package/python/tests/test_skill_taint_gate.py +1 -10
  326. package/python/tests/test_skills.py +2 -18
  327. package/python/tests/test_skills_fetch.py +282 -0
  328. package/python/tests/test_stats_and_events.py +2 -5
  329. package/python/tests/test_streaming.py +2 -7
  330. package/python/tests/test_supervisor_pipeline.py +4 -5
  331. package/python/tests/test_supervisor_triage.py +4 -10
  332. package/python/tests/test_taint.py +2 -7
  333. package/python/tests/test_task_stream.py +1 -12
  334. package/python/tests/test_thinking_sse.py +0 -1
  335. package/python/tests/test_tool_center.py +1 -17
  336. package/python/tests/test_tool_parallel.py +2 -2
  337. package/python/tests/test_tool_params.py +1 -5
  338. package/python/tests/test_tool_use.py +0 -13
  339. package/python/tests/test_toplevel_concurrency.py +3 -4
  340. package/python/tests/test_tracing.py +7 -41
  341. package/python/tests/test_truth_crystallizer.py +4 -6
  342. package/python/tests/test_truth_flow.py +2 -7
  343. package/python/tests/test_update_check.py +95 -0
  344. package/python/tests/test_verify_evidence.py +8 -21
  345. package/python/tests/test_version_sync.py +35 -6
  346. package/python/tests/test_web_trigger.py +0 -17
  347. package/python/tools/build_content_lexicon.py +218 -0
  348. package/python/tools/bundle_web.py +98 -0
  349. package/python/tools/check_faiss_type.py +0 -1
  350. package/python/tools/check_log.ps1 +0 -8
  351. package/python/tools/mypy_baseline.txt +12 -4
  352. package/python/tools/mypy_ratchet.py +52 -6
  353. package/python/tools/pack.py +0 -5
  354. package/python/tools/recheck_conflicts.py +156 -0
  355. package/python/tools/release_check.py +24 -15
  356. package/python/tools/restart_foxclaw.ps1 +5 -8
  357. package/python/tools/shared_write_audit.py +1 -15
  358. package/python/tools/size_check.py +1 -1
  359. package/python/tools/strip_comments.py +228 -0
  360. package/python/vendor/hkc/hkc_api/routes/ability.py +31 -0
  361. package/python/vendor/hkc/hkc_api/schemas.py +11 -0
  362. package/python/vendor/hkc/hkc_kee/kee.py +124 -3
  363. package/python/vendor/hkc/hkc_sdk/base.py +22 -0
  364. package/python/vendor/hkc/hkc_sdk/direct_client.py +10 -0
  365. package/python/vendor/hkc/hkc_sdk/http_client.py +19 -0
  366. package/python/foxclaw/api/app.py +0 -123
  367. package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
  368. package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
@@ -23,40 +23,23 @@ import urllib.error
23
23
  from . import settings
24
24
 
25
25
 
26
- # 触发联网搜索的关键词:问到实时/时效性信息时才搜
27
- # ── 是否需要联网:话题信号(强) vs 时间词(弱) vs 个人数据(否决)──────────────
28
- # 教训:曾把纯时间词(今天/本月/最近…)也当触发,结果「分析一下本月支出」这类**问自己账本**
29
- # 的任务被误判为需要联网 → 白跑一次 ~22s 的付费深度搜索。而 Foxclaw 的领域(个人财务/
30
- # 行程/学习)天生满是时间词,这不是个别误判,是**一整类合法任务**在白交时间和钱。
31
- # 现在:时间词单独**不足以**触发;必须出现"外部世界话题";且个人数据一律否决。
32
- # (与既有的 now⊂knowledge / current⊂concurrent 词边界修复同源:都是过度触发。)
33
-
34
- # 强触发:出现即说明要的是外部实时信息
35
26
  TOPIC_TRIGGERS = [
36
27
  "版本", "几个版本", "第几", "价格", "多少钱", "汇率", "股价", "新闻",
37
28
  "发布", "上线", "更新到", "迭代", "排行", "排名", "天气",
38
- # 问"某产品/技术的模型/型号/出没出/更没更"——都是外部时效信息,过去漏搜→编造。
39
- # 只留明确指向外部产品的词;"有哪些/可用/支持"太宽(会误伤"我有哪些任务"),不加。
40
29
  "模型", "型号", "出了没", "发布没", "更新没", "新版",
41
30
  "latest", "news", "version", "price", "models",
42
31
  ]
43
- # 弱信号:纯时间词。单独出现不触发(个人数据问题天生带这些词)
44
32
  TIME_WORDS = [
45
33
  "最新", "现在", "目前", "今天", "今日", "昨天", "本周", "本月", "今年",
46
34
  "最近", "当前", "today", "current", "now",
47
35
  ]
48
- # 否决:问的是用户自己的账/行程/学习/记忆 —— 这些答案在本地领域内核里,不在网上。
49
- # 注意别放"我的"这类过宽的词:「我的股票现在多少钱」是真需要联网的。
50
36
  PERSONAL_MARKERS = [
51
37
  "支出", "收入", "预算", "交易", "账单", "花了", "消费", "记账", "结余", "余额",
52
38
  "行程", "日程", "出行", "复习", "知识点", "学习计划", "记忆",
53
39
  ]
54
40
 
55
- SEARCH_TRIGGERS = TOPIC_TRIGGERS # 对外保持同名(旧引用不破)
41
+ SEARCH_TRIGGERS = TOPIC_TRIGGERS
56
42
 
57
- # 中文触发词按「子串」匹配(中文无词边界);英文触发词按「词边界」匹配,
58
- # 否则短词会误命中常见词:"now"⊂"knowledge"、"current"⊂"concurrent"、
59
- # "now"⊂"known" —— 在这个满口 knowledge 的项目里会频繁无谓地打百度。
60
43
  _CJK_TRIGGERS = [k for k in SEARCH_TRIGGERS if not k.isascii()]
61
44
  _ASCII_TRIGGERS = [k.lower() for k in SEARCH_TRIGGERS if k.isascii()]
62
45
  _ASCII_RE = (
@@ -85,16 +68,12 @@ def needs_search(query: str) -> bool:
85
68
  return False
86
69
  q = query.lower()
87
70
  if is_personal_query(q):
88
- return False # 问的是自己的数据 → 答案在本地内核,绝不联网
89
- # 自我模型/身份问题(你是什么模型 / 你现在是哪个模型 …)→ 由 ModelRouter 运行时注入本地即答,不联网。
90
- # 否则会因含"模型"触发词白跑一次 ~20s 百度搜索。"你"与"模型"相邻(≤8字)才算自问;
91
- # "你帮我查 X 的模型"(相距远)仍走搜索。
71
+ return False
92
72
  if re.search(r"你[^,。?!]{0,8}模型", q):
93
73
  return False
94
74
  if any(k in q for k in _CJK_TRIGGERS):
95
- return True # 外部世界话题 → 联网
75
+ return True
96
76
  return bool(_ASCII_RE and _ASCII_RE.search(q))
97
- # 注:只含时间词(今天/本月/最近…)而无话题 → 不触发(见上方 TIME_WORDS 说明)
98
77
 
99
78
 
100
79
  def _key() -> str:
@@ -139,7 +118,7 @@ def _search_answer_inner(query: str, key: str) -> dict:
139
118
  "stream": False,
140
119
  "model": _model(),
141
120
  "enable_deep_search": _deep(),
142
- "enable_corner_markers": False, # 不要答案里的 ^[1]^ 角标(参考资料已单列)
121
+ "enable_corner_markers": False,
143
122
  }).encode("utf-8")
144
123
  req = urllib.request.Request(
145
124
  f"{_base()}/chat/completions",
@@ -193,7 +172,6 @@ def format_for_prompt(query: str, payload) -> str:
193
172
  refs = payload.get("references", [])
194
173
  if not answer and not refs:
195
174
  return ""
196
- # 我方的作答指令(可信,放在 <untrusted> 之外);检索到的外部内容(不可信,放在 <untrusted> 之内)。
197
175
  framing = (
198
176
  f"以下是针对「{query}」的实时联网搜索结果(来自百度 AI 搜索)。\n"
199
177
  f"【严格要求】只能依据下面 <untrusted> 数据块里的搜索结果作答:结果里没有出现的型号 / "
@@ -215,13 +193,11 @@ def format_for_prompt(query: str, payload) -> str:
215
193
  if u:
216
194
  inner += f"\n链接:{u}"
217
195
  inner += f"\n[webpage {i} end]\n"
218
- # 隔离外部内容:搜索结果是外部不可控数据,包裹进 <untrusted> 防间接注入(见 injection_guard)。
219
196
  from . import injection_guard as _ig
220
197
  block = _ig.wrap_untrusted(inner.rstrip(), source="web_search")
221
198
  closing = ("\n请**严格基于以上 <untrusted> 内的搜索结果**回答用户的问题;结果里没提到的内容,"
222
199
  "就明确说「搜索结果中未提及」,绝不编造型号/版本号。")
223
200
  return framing + block + closing
224
- # 列表形式(兼容)
225
201
  results = payload or []
226
202
  if not results:
227
203
  return ""
@@ -17,9 +17,9 @@ from datetime import datetime
17
17
  @dataclass
18
18
  class RiskFactor:
19
19
  """单个风险因子:是什么、证据、权重。"""
20
- factor: str # 风险因子名(如"超预算")
21
- evidence: str # 人类可读证据(如"餐饮已用 120%,超 ¥240")
22
- weight: float # 权重 0-1(对总风险的贡献)
20
+ factor: str
21
+ evidence: str
22
+ weight: float
23
23
 
24
24
  def to_dict(self) -> dict:
25
25
  return {"factor": self.factor, "evidence": self.evidence,
@@ -29,14 +29,14 @@ class RiskFactor:
29
29
  @dataclass
30
30
  class EvidenceChain:
31
31
  """结构化决策证据链 —— 财务决策的可审计输出。"""
32
- decision_type: str # 决策类型(如 budget_risk / anomaly)
33
- risk_score: float = 0.0 # 综合风险分 0-1
34
- risk_factors: list = field(default_factory=list) # [RiskFactor]
35
- mitigating_factors: list = field(default_factory=list) # 缓解因素(降低风险的)
36
- rationale: str = "" # 决策理由(人类可读)
37
- recommended_action: str = "" # 建议动作
38
- confidence: float = 0.0 # 置信度 0-1
39
- additional_checks: list = field(default_factory=list) # 还需补充的检查
32
+ decision_type: str
33
+ risk_score: float = 0.0
34
+ risk_factors: list = field(default_factory=list)
35
+ mitigating_factors: list = field(default_factory=list)
36
+ rationale: str = ""
37
+ recommended_action: str = ""
38
+ confidence: float = 0.0
39
+ additional_checks: list = field(default_factory=list)
40
40
  created_at: str = field(default_factory=lambda: datetime.now().isoformat(timespec="seconds"))
41
41
 
42
42
  def add_factor(self, factor: str, evidence: str, weight: float):
@@ -53,10 +53,10 @@ class EvidenceChain:
53
53
  self.risk_score = 0.0
54
54
  return 0.0
55
55
  weights = sorted((f.weight for f in self.risk_factors), reverse=True)
56
- score = weights[0] # 最大因子为主
56
+ score = weights[0]
57
57
  for w in weights[1:]:
58
- score += w * (1 - score) * 0.5 # 其余因子衰减叠加(饱和,不会超过1)
59
- mitigation = min(0.2, 0.1 * len(self.mitigating_factors)) # 缓解最多减0.2
58
+ score += w * (1 - score) * 0.5
59
+ mitigation = min(0.2, 0.1 * len(self.mitigating_factors))
60
60
  self.risk_score = max(0.0, min(1.0, round(score - mitigation, 3)))
61
61
  return self.risk_score
62
62
 
@@ -100,7 +100,6 @@ class FinanceExplainer:
100
100
  cat = b.get("category", "?")
101
101
  if ratio >= 1.0:
102
102
  over = b.get("spent", 0) - b.get("monthly_limit", 0)
103
- # 权重随超支程度递增:超10%→0.5,超50%→0.65,超100%+→0.8(上限)
104
103
  w = min(0.8, 0.45 + (ratio - 1.0) * 0.35)
105
104
  ec.add_factor("超预算", f"{cat} 已用 {ratio*100:.0f}%,超支 ¥{over:.0f}",
106
105
  weight=w)
@@ -109,13 +108,11 @@ class FinanceExplainer:
109
108
  ec.add_factor("接近预算上限",
110
109
  f"{cat} 已用 {ratio*100:.0f}%,接近上限", weight=0.25)
111
110
  near_budgets.append(cat)
112
- # 缓解因素:有预算分类未超支
113
111
  healthy = [b.get("category") for b in budget_status
114
112
  if b.get("usage_ratio", 0) < 0.8]
115
113
  if healthy:
116
114
  ec.mitigating_factors.append(f"{len(healthy)} 个分类预算健康")
117
115
  ec.compute_risk()
118
- # 理由 + 建议
119
116
  if over_budgets:
120
117
  ec.rationale = f"{','.join(over_budgets)} 已超预算,需立即关注"
121
118
  ec.recommended_action = f"削减 {over_budgets[0]} 支出或调整预算"
@@ -134,7 +131,6 @@ class FinanceExplainer:
134
131
  ec = EvidenceChain(decision_type="anomaly", confidence=0.85)
135
132
  for a in anomalies[:5]:
136
133
  z = a.get("z_score", 0)
137
- # z 分数越高,权重越大(裁剪到 0.6)
138
134
  w = min(0.6, 0.15 + z * 0.05)
139
135
  ec.add_factor("异常大额支出",
140
136
  f"{a.get('entity','?')} ¥{abs(a.get('amount',0)):.0f}"
@@ -15,7 +15,6 @@ from pathlib import Path
15
15
 
16
16
  BASE_CURRENCY = "CNY"
17
17
 
18
- # 参考价(1 单位外币 = 多少 CNY)。宁可粗,但要能改。
19
18
  DEFAULT_RATES = {
20
19
  "CNY": 1.0,
21
20
  "USD": 7.2,
@@ -33,7 +32,7 @@ DEFAULT_RATES = {
33
32
  class FxTable:
34
33
  """币种 → 本位币折算率,带落盘(原子写)。"""
35
34
 
36
- def __init__(self, path: str = None):
35
+ def __init__(self, path: str | None = None):
37
36
  self.path = Path(path) if path else None
38
37
  self.rates = dict(DEFAULT_RATES)
39
38
  self.note = "static"
@@ -54,7 +53,6 @@ class FxTable:
54
53
  if data.get("note"):
55
54
  self.note = data["note"]
56
55
  except Exception:
57
- # 汇率表坏了不能拖垮记账:退回出厂参考价,下次保存时自动覆盖
58
56
  pass
59
57
 
60
58
  def save(self) -> bool:
@@ -18,7 +18,7 @@ from dataclasses import dataclass, field
18
18
  @dataclass
19
19
  class GraphNode:
20
20
  node_id: str
21
- node_type: str # transaction/category/entity/account/budget
21
+ node_type: str
22
22
  label: str
23
23
  attrs: dict = field(default_factory=dict)
24
24
 
@@ -31,7 +31,7 @@ class GraphNode:
31
31
  class GraphEdge:
32
32
  src: str
33
33
  dst: str
34
- rel: str # belongs_to/paid_to/charged_on/monitors
34
+ rel: str
35
35
  attrs: dict = field(default_factory=dict)
36
36
 
37
37
  def to_dict(self) -> dict:
@@ -45,15 +45,13 @@ class FinancialDataGraph:
45
45
  def __init__(self):
46
46
  self.nodes: dict[str, GraphNode] = {}
47
47
  self.edges: list[GraphEdge] = []
48
- self._adj: dict[str, list] = defaultdict(list) # 出边邻接
49
- self._radj: dict[str, list] = defaultdict(list) # 入边邻接
48
+ self._adj: dict[str, list] = defaultdict(list)
49
+ self._radj: dict[str, list] = defaultdict(list)
50
50
 
51
- # ── 构建 ──
52
51
  def _ensure_node(self, node_id: str, node_type: str, label: str, **attrs):
53
52
  if node_id not in self.nodes:
54
53
  self.nodes[node_id] = GraphNode(node_id, node_type, label, dict(attrs))
55
54
  else:
56
- # 已存在 → 累加统计类属性
57
55
  self.nodes[node_id].attrs.update(attrs)
58
56
  return self.nodes[node_id]
59
57
 
@@ -63,7 +61,7 @@ class FinancialDataGraph:
63
61
  self._adj[src].append(e)
64
62
  self._radj[dst].append(e)
65
63
 
66
- def build(self, transactions: list, budgets: list = None):
64
+ def build(self, transactions: list, budgets: list | None = None):
67
65
  """从交易 + 预算构建图。"""
68
66
  self.nodes.clear(); self.edges.clear()
69
67
  self._adj.clear(); self._radj.clear()
@@ -73,7 +71,6 @@ class FinancialDataGraph:
73
71
  self._ensure_node(tid, "transaction", f"{t.entity} ¥{abs(t.amount):.0f}",
74
72
  amount=t.amount, timestamp=t.timestamp,
75
73
  is_expense=t.is_expense)
76
- # 分类节点 + 边(消费统计只累加支出,收入/退款不计入'花费')
77
74
  cat_id = f"cat:{t.category}"
78
75
  cnode = self._ensure_node(cat_id, "category", t.category)
79
76
  if t.is_expense:
@@ -82,19 +79,16 @@ class FinancialDataGraph:
82
79
  else:
83
80
  cnode.attrs["income"] = cnode.attrs.get("income", 0) + abs(t.amount)
84
81
  self._add_edge(tid, cat_id, "belongs_to")
85
- # 商户节点 + 边(同样只把支出计入消费)
86
82
  ent_id = f"ent:{t.entity}"
87
83
  enode = self._ensure_node(ent_id, "entity", t.entity)
88
84
  if t.is_expense:
89
85
  enode.attrs["total"] = enode.attrs.get("total", 0) + abs(t.amount)
90
86
  enode.attrs["count"] = enode.attrs.get("count", 0) + 1
91
87
  self._add_edge(tid, ent_id, "paid_to")
92
- # 账户节点 + 边
93
88
  acc_id = f"acc:{t.account}"
94
89
  self._ensure_node(acc_id, "account", t.account)
95
90
  self._add_edge(tid, acc_id, "charged_on")
96
91
 
97
- # 预算节点 + 监管边(预算 monitors 分类)
98
92
  for b in (budgets or []):
99
93
  bid = f"bud:{b.category}"
100
94
  self._ensure_node(bid, "budget", f"{b.category}预算",
@@ -104,8 +98,7 @@ class FinancialDataGraph:
104
98
  self._add_edge(bid, cat_id, "monitors")
105
99
  return self
106
100
 
107
- # ── 查询 ──
108
- def neighbors(self, node_id: str, rel: str = None) -> list:
101
+ def neighbors(self, node_id: str, rel: str | None = None) -> list:
109
102
  """出边邻居(可按关系过滤)。"""
110
103
  return [self.nodes[e.dst] for e in self._adj.get(node_id, [])
111
104
  if rel is None or e.rel == rel]
@@ -119,11 +112,10 @@ class FinancialDataGraph:
119
112
  """某分类下,按消费额排名的商户 —— 回答'这个分类的钱花在谁身上'。"""
120
113
  cat_id = f"cat:{category}"
121
114
  txns = self.transactions_of(cat_id)
122
- by_entity = defaultdict(float)
115
+ by_entity: defaultdict[str, float] = defaultdict(float)
123
116
  for t in txns:
124
117
  if not t.attrs.get("is_expense"):
125
- continue # 只统计支出,排除收入/退款
126
- # 找这笔交易付给了谁
118
+ continue
127
119
  for e in self._adj.get(t.node_id, []):
128
120
  if e.rel == "paid_to":
129
121
  by_entity[self.nodes[e.dst].label] += abs(t.attrs.get("amount", 0))
@@ -139,7 +131,6 @@ class FinancialDataGraph:
139
131
  cat = self.nodes[cat_id]
140
132
  spent = cat.attrs.get("total", 0)
141
133
  limit = self.nodes[bud_id].attrs.get("monthly_limit") if bud_id in self.nodes else None
142
- # 主要贡献者(商户)
143
134
  contributors = self.top_entities_in_category(category, top=3)
144
135
  result = {
145
136
  "category": category, "found": True,
@@ -170,7 +161,7 @@ class FinancialDataGraph:
170
161
  return sorted(out, key=lambda x: -x["outflow"])
171
162
 
172
163
  def stats(self) -> dict:
173
- by_type = defaultdict(int)
164
+ by_type: defaultdict[str, int] = defaultdict(int)
174
165
  for n in self.nodes.values():
175
166
  by_type[n.node_type] += 1
176
167
  return {"nodes": len(self.nodes), "edges": len(self.edges),
@@ -22,21 +22,20 @@ class Transaction:
22
22
  原币金额与币种另存 `original_amount` / `currency` / `fx_rate` —— 这样「100 美元」
23
23
  不会和「100 元」在求和时混在一起,同时账本里又看得到用户当时说的原话数额。
24
24
  """
25
- amount: float # 负数=支出,正数=收入(本位币 CNY)
26
- category: str # 分类:subscription/food/transport/salary...
27
- entity: str # 商户/对手方:Netflix、星巴克、公司
28
- account: str # 账户:credit_card_1、debit、alipay
25
+ amount: float
26
+ category: str
27
+ entity: str
28
+ account: str
29
29
  timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
30
- tags: list = field(default_factory=list) # ["recurring","media"]
30
+ tags: list = field(default_factory=list)
31
31
  note: str = ""
32
32
  id: str = field(default_factory=lambda: f"txn_{uuid.uuid4().hex[:10]}")
33
- # —— 多币种 & 自然语言记账 ——
34
- currency: str = "CNY" # 原币种
35
- original_amount: Optional[float] = None # 原币金额(正负号与 amount 一致)
36
- fx_rate: float = 1.0 # 记账时原币 → 本位币的折算率
37
- description: str = "" # 报备原话里的"买的什么"
38
- status: str = "settled" # settled=已发生 planned=未来计划
39
- source: str = "manual" # manual / text / csv / bank
33
+ currency: str = "CNY"
34
+ original_amount: Optional[float] = None
35
+ fx_rate: float = 1.0
36
+ description: str = ""
37
+ status: str = "settled"
38
+ source: str = "manual"
40
39
 
41
40
  @property
42
41
  def is_expense(self) -> bool:
@@ -72,8 +71,8 @@ class Transaction:
72
71
  class Budget:
73
72
  """某分类的预算状态"""
74
73
  category: str
75
- monthly_limit: float # 月度上限
76
- spent: float = 0.0 # 本期已花(绝对值)
74
+ monthly_limit: float
75
+ spent: float = 0.0
77
76
  period: str = field(default_factory=lambda: datetime.now().strftime("%Y-%m"))
78
77
 
79
78
  @property
@@ -102,12 +101,12 @@ class FinancialEntity:
102
101
  """账户 / 商户 / 订阅"""
103
102
  name: str
104
103
  entity_type: EntityType
105
- balance: Optional[float] = None # 账户余额(仅 account)
106
- recurring_amount: Optional[float] = None # 周期金额(仅 subscription)
107
- cycle: Optional[str] = None # monthly/yearly(仅 subscription)
104
+ balance: Optional[float] = None
105
+ recurring_amount: Optional[float] = None
106
+ cycle: Optional[str] = None
108
107
  last_seen: str = ""
109
108
  txn_count: int = 0
110
- total_amount: float = 0.0 # 累计金额
109
+ total_amount: float = 0.0
111
110
 
112
111
  def to_dict(self) -> dict:
113
112
  return asdict(self)
@@ -119,16 +118,13 @@ class FinancePolicyRule:
119
118
  与 Harness 的 Policy YAML 同构,但作用于资金行为。
120
119
  """
121
120
  id: str
122
- condition: str # 人类可读条件描述
121
+ condition: str
123
122
  action: Literal["alert", "require_confirmation", "freeze", "veto", "suggest"]
124
123
  threshold: Optional[float] = None
125
- scope: str = "global" # global 或某 category
124
+ scope: str = "global"
126
125
  severity: Literal["info", "warn", "critical"] = "warn"
127
126
  source: Literal["builtin", "user", "learned"] = "builtin"
128
127
  enabled: bool = True
129
- # 自校准留痕:calibrated_value = 上次校准写下的阈值(用于判断"之后有没有人手动改过";
130
- # 若 threshold 已偏离它,说明是人的决定,校准就此收手)。calibrated_from = 依据说明,
131
- # 让阈值可解释 —— 用户看得到"这个 800 是怎么来的",才敢信它。
132
128
  calibrated_value: Optional[float] = None
133
129
  calibrated_from: Optional[str] = None
134
130
 
@@ -144,7 +140,7 @@ class PolicyVerdict:
144
140
  severity: str
145
141
  message: str
146
142
  transaction_id: Optional[str] = None
147
- requires_user_action: bool = False # 是否需要用户确认/介入
143
+ requires_user_action: bool = False
148
144
 
149
145
  def to_dict(self) -> dict:
150
146
  return asdict(self)