foxclaw 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -29
- package/bin/foxclaw.cjs +51 -0
- package/index.js +11 -10
- package/package.json +46 -38
- package/python/.dockerignore +35 -0
- package/python/.env.example +165 -0
- package/python/.gitattributes +46 -0
- package/python/.github/workflows/ci.yml +122 -0
- package/python/.github/workflows/release.yml +183 -0
- package/python/Dockerfile +64 -0
- package/python/LICENSE +21 -0
- package/python/MANIFEST.in +14 -0
- package/python/README.md +192 -0
- package/python/README_CN.md +324 -0
- package/python/bench/bm25_audit.py +199 -0
- package/python/bench/bm25_regression.py +178 -0
- package/python/bench/bm25_stress.py +159 -0
- package/python/bench/cd_audit.py +238 -0
- package/python/bench/chaos.py +142 -0
- package/python/bench/concurrency.py +103 -0
- package/python/bench/e2e_real_models.py +95 -0
- package/python/bench/llm_retry_test.py +248 -0
- package/python/bench/load_test.py +196 -0
- package/python/bench/vector_ivf_upgrade.py +134 -0
- package/python/docs/CHANNELS.md +195 -0
- package/python/docs/GOVERNANCE.md +164 -0
- package/python/docs/RELEASE.md +109 -0
- package/python/docs/RUN.md +75 -0
- package/python/docs/RUN_CN.md +121 -0
- package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
- package/python/evals/__init__.py +0 -0
- package/python/evals/governance/README.md +61 -0
- package/python/evals/governance/__init__.py +0 -0
- package/python/evals/governance/cases.py +152 -0
- package/python/evals/governance/compare_embeddings.py +94 -0
- package/python/evals/governance/convergence.svg +11 -0
- package/python/evals/governance/report.json +371 -0
- package/python/evals/governance/run_evals.py +400 -0
- package/python/examples/governance_middleware_demo.py +77 -0
- package/python/examples/langgraph_governance_demo.py +108 -0
- package/python/examples/sample_itinerary.csv +6 -0
- package/python/examples/sample_knowledge.csv +9 -0
- package/python/examples/sample_transactions.csv +14 -0
- package/python/foxclaw/__init__.py +526 -0
- package/python/foxclaw/agents/__init__.py +12 -0
- package/python/foxclaw/agents/agents.py +35 -0
- package/python/foxclaw/agents/base.py +865 -0
- package/python/foxclaw/agents/impl/__init__.py +1 -0
- package/python/foxclaw/agents/impl/_helpers.py +78 -0
- package/python/foxclaw/agents/impl/finance.py +248 -0
- package/python/foxclaw/agents/impl/office.py +367 -0
- package/python/foxclaw/agents/impl/supervisor.py +659 -0
- package/python/foxclaw/agents/mixins/_context.py +151 -0
- package/python/foxclaw/agents/mixins/augment.py +149 -0
- package/python/foxclaw/agents/mixins/llm.py +66 -0
- package/python/foxclaw/agents/mixins/memory.py +35 -0
- package/python/foxclaw/agents/mixins/preference.py +81 -0
- package/python/foxclaw/agents/mixins/prompt.py +77 -0
- package/python/foxclaw/agents/mixins/skillbox.py +58 -0
- package/python/foxclaw/agents/mixins/tool_exec.py +443 -0
- package/python/foxclaw/api/__init__.py +283 -0
- package/python/foxclaw/api/app.py +123 -0
- package/python/foxclaw/api/middleware.py +317 -0
- package/python/foxclaw/api/routes/ahs.py +417 -0
- package/python/foxclaw/api/routes/attachments.py +117 -0
- package/python/foxclaw/api/routes/browser.py +39 -0
- package/python/foxclaw/api/routes/domain.py +314 -0
- package/python/foxclaw/api/routes/knowledge.py +163 -0
- package/python/foxclaw/api/routes/llm_admin.py +461 -0
- package/python/foxclaw/api/routes/memory.py +89 -0
- package/python/foxclaw/api/routes/orchestration.py +41 -0
- package/python/foxclaw/api/routes/recovery.py +43 -0
- package/python/foxclaw/api/routes/review.py +275 -0
- package/python/foxclaw/api/routes/search.py +37 -0
- package/python/foxclaw/api/routes/system.py +327 -0
- package/python/foxclaw/api/routes/task.py +281 -0
- package/python/foxclaw/api/routes/ws.py +68 -0
- package/python/foxclaw/api/spine.py +55 -0
- package/python/foxclaw/browser/__init__.py +13 -0
- package/python/foxclaw/browser/action/__init__.py +1 -0
- package/python/foxclaw/browser/action/action_executor.py +54 -0
- package/python/foxclaw/browser/action/action_types.py +45 -0
- package/python/foxclaw/browser/action/action_verifier.py +68 -0
- package/python/foxclaw/browser/action/url_policy.py +147 -0
- package/python/foxclaw/browser/adapter/__init__.py +1 -0
- package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
- package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
- package/python/foxclaw/browser/driver/__init__.py +1 -0
- package/python/foxclaw/browser/driver/base_driver.py +61 -0
- package/python/foxclaw/browser/driver/playwright_driver.py +166 -0
- package/python/foxclaw/browser/perception/__init__.py +1 -0
- package/python/foxclaw/browser/perception/dom_extractor.py +43 -0
- package/python/foxclaw/browser/perception/element_indexer.py +57 -0
- package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
- package/python/foxclaw/browser/perception/snapshot.py +29 -0
- package/python/foxclaw/browser/service.py +148 -0
- package/python/foxclaw/browser/session/__init__.py +1 -0
- package/python/foxclaw/browser/session/session_manager.py +57 -0
- package/python/foxclaw/capability/__init__.py +12 -0
- package/python/foxclaw/capability/embedding_service.py +67 -0
- package/python/foxclaw/channels/__init__.py +140 -0
- package/python/foxclaw/channels/discord.py +184 -0
- package/python/foxclaw/channels/dispatcher.py +204 -0
- package/python/foxclaw/channels/feishu.py +225 -0
- package/python/foxclaw/channels/openclaw_bridge.py +156 -0
- package/python/foxclaw/channels/signing.py +187 -0
- package/python/foxclaw/channels/telegram.py +121 -0
- package/python/foxclaw/channels/weixin.py +225 -0
- package/python/foxclaw/cli.py +154 -0
- package/python/foxclaw/cognitive/__init__.py +4 -0
- package/python/foxclaw/cognitive/reasoner.py +82 -0
- package/python/foxclaw/cognitive/state.py +147 -0
- package/python/foxclaw/core/__init__.py +9 -0
- package/python/foxclaw/core/audit_chain.py +133 -0
- package/python/foxclaw/core/audit_sink.py +100 -0
- package/python/foxclaw/core/compaction.py +283 -0
- package/python/foxclaw/core/config.py +65 -0
- package/python/foxclaw/core/confirm_nonce.py +157 -0
- package/python/foxclaw/core/dispatch.py +27 -0
- package/python/foxclaw/core/envfile.py +48 -0
- package/python/foxclaw/core/failure_policy.py +151 -0
- package/python/foxclaw/core/gateway.py +85 -0
- package/python/foxclaw/core/governance_audit.py +175 -0
- package/python/foxclaw/core/idempotency.py +289 -0
- package/python/foxclaw/core/injection_guard.py +221 -0
- package/python/foxclaw/core/memory_adapter.py +516 -0
- package/python/foxclaw/core/metrics.py +89 -0
- package/python/foxclaw/core/model_router.py +1097 -0
- package/python/foxclaw/core/paths.py +103 -0
- package/python/foxclaw/core/recovery.py +362 -0
- package/python/foxclaw/core/scheduler.py +144 -0
- package/python/foxclaw/core/secrets.py +134 -0
- package/python/foxclaw/core/sessions.py +157 -0
- package/python/foxclaw/core/settings.py +430 -0
- package/python/foxclaw/core/shared_write.py +198 -0
- package/python/foxclaw/core/steering_queue.py +153 -0
- package/python/foxclaw/core/system_bus.py +84 -0
- package/python/foxclaw/core/thought_chain.py +318 -0
- package/python/foxclaw/core/token_meter.py +235 -0
- package/python/foxclaw/core/tools.py +172 -0
- package/python/foxclaw/core/tracing.py +390 -0
- package/python/foxclaw/core/truth_pipeline.py +114 -0
- package/python/foxclaw/core/vec_recall_adapter.py +503 -0
- package/python/foxclaw/core/verify.py +181 -0
- package/python/foxclaw/core/web_search.py +242 -0
- package/python/foxclaw/finance/__init__.py +15 -0
- package/python/foxclaw/finance/evidence.py +153 -0
- package/python/foxclaw/finance/fx.py +100 -0
- package/python/foxclaw/finance/graph.py +188 -0
- package/python/foxclaw/finance/models.py +150 -0
- package/python/foxclaw/finance/nlparse.py +575 -0
- package/python/foxclaw/finance/policy.py +329 -0
- package/python/foxclaw/finance/reasoner.py +129 -0
- package/python/foxclaw/finance/reasoning.py +340 -0
- package/python/foxclaw/finance/reconcile.py +180 -0
- package/python/foxclaw/finance/runtime.py +527 -0
- package/python/foxclaw/governance/__init__.py +24 -0
- package/python/foxclaw/governance/middleware.py +184 -0
- package/python/foxclaw/harness/__init__.py +17 -0
- package/python/foxclaw/harness/_base.py +72 -0
- package/python/foxclaw/harness/_policy.py +359 -0
- package/python/foxclaw/harness/drift.py +89 -0
- package/python/foxclaw/harness/event_bus.py +31 -0
- package/python/foxclaw/harness/harness.py +446 -0
- package/python/foxclaw/harness/loop_detection.py +152 -0
- package/python/foxclaw/itinerary/__init__.py +10 -0
- package/python/foxclaw/itinerary/amap_client.py +117 -0
- package/python/foxclaw/itinerary/models.py +80 -0
- package/python/foxclaw/itinerary/reasoner.py +101 -0
- package/python/foxclaw/itinerary/reasoning.py +174 -0
- package/python/foxclaw/itinerary/runtime.py +148 -0
- package/python/foxclaw/kernel/__init__.py +13 -0
- package/python/foxclaw/kernel/capabilities.py +73 -0
- package/python/foxclaw/kernel/policy_gate.py +114 -0
- package/python/foxclaw/kernel/process_manager.py +205 -0
- package/python/foxclaw/kernel/scheduler.py +131 -0
- package/python/foxclaw/kernel/sentinel.py +453 -0
- package/python/foxclaw/kernel/sentinel_client.py +246 -0
- package/python/foxclaw/kernel/spine.py +97 -0
- package/python/foxclaw/knowledge/__init__.py +376 -0
- package/python/foxclaw/knowledge/truth_crystallizer.py +92 -0
- package/python/foxclaw/learning/__init__.py +12 -0
- package/python/foxclaw/learning/knowledge_graph.py +149 -0
- package/python/foxclaw/learning/models.py +93 -0
- package/python/foxclaw/learning/reasoner.py +70 -0
- package/python/foxclaw/learning/runtime.py +363 -0
- package/python/foxclaw/learning/spaced_repetition.py +96 -0
- package/python/foxclaw/mcp_server.py +189 -0
- package/python/foxclaw/orchestration/__init__.py +184 -0
- package/python/foxclaw/pipeline/__init__.py +23 -0
- package/python/foxclaw/pipeline/cognitive_pipeline.py +481 -0
- package/python/foxclaw/pipeline/supervisor_stages.py +230 -0
- package/python/foxclaw/planning/__init__.py +17 -0
- package/python/foxclaw/planning/llm_planner.py +232 -0
- package/python/foxclaw/planning/planner.py +99 -0
- package/python/foxclaw/search/__init__.py +24 -0
- package/python/foxclaw/search/agent_port.py +194 -0
- package/python/foxclaw/search/evidence.py +113 -0
- package/python/foxclaw/search/external_search.py +50 -0
- package/python/foxclaw/search/hkc_read_port.py +60 -0
- package/python/foxclaw/search/internal_search.py +73 -0
- package/python/foxclaw/search/models.py +129 -0
- package/python/foxclaw/search/planner.py +44 -0
- package/python/foxclaw/search/retriever.py +39 -0
- package/python/foxclaw/search/runtime.py +134 -0
- package/python/foxclaw/search/strategy.py +44 -0
- package/python/foxclaw/skills/__init__.py +613 -0
- package/python/foxclaw/skills/hooks_spec.py +73 -0
- package/python/foxclaw/skills/lifecycle.py +278 -0
- package/python/foxclaw/skills/markdown_skill.py +174 -0
- package/python/foxclaw/skills/markdown_store.py +39 -0
- package/python/foxclaw/skills/mcp_client.py +190 -0
- package/python/foxclaw/skills/mcp_store.py +43 -0
- package/python/foxclaw/skills/permission.py +180 -0
- package/python/foxclaw/skills/pipeline.py +386 -0
- package/python/foxclaw/skills/policy.py +80 -0
- package/python/foxclaw/skills/review.py +72 -0
- package/python/foxclaw/skills/store.py +57 -0
- package/python/frontend/assets/core_bg.jpg +0 -0
- package/python/frontend/deck/auth.js +32 -0
- package/python/frontend/deck/chat_model_picker.js +537 -0
- package/python/frontend/deck/domain.js +296 -0
- package/python/frontend/deck/main.js +1950 -0
- package/python/frontend/deck/markdown.js +48 -0
- package/python/frontend/deck/settings.js +324 -0
- package/python/frontend/deck/state.js +6 -0
- package/python/frontend/deck/util.js +6 -0
- package/python/frontend/deck/visuals.js +212 -0
- package/python/frontend/deck.css +855 -0
- package/python/frontend/deck.html +686 -0
- package/python/frontend/deck_v2.html +341 -0
- package/python/frontend/planet/index.html +266 -0
- package/python/frontend/planet/planet.css +579 -0
- package/python/frontend/planet/planet.js +1637 -0
- package/python/frontend/planet/vendor/three.core.min.js +6 -0
- package/python/frontend/planet/vendor/three.module.min.js +6 -0
- package/python/frontend/v3/assets/index-BD7VPbMV.js +33 -0
- package/python/frontend/v3/assets/index-CAvnYWGH.css +1 -0
- package/python/frontend/v3/index.html +15 -0
- package/python/frontend/widget_runtime.js +295 -0
- package/python/frontend/widgets.js +696 -0
- package/python/frontend_src/index.html +14 -0
- package/python/frontend_src/package-lock.json +1376 -0
- package/python/frontend_src/package.json +19 -0
- package/python/frontend_src/src/App.vue +62 -0
- package/python/frontend_src/src/api.js +89 -0
- package/python/frontend_src/src/components/ChatDock.vue +377 -0
- package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
- package/python/frontend_src/src/components/DocViewer.vue +171 -0
- package/python/frontend_src/src/components/Inspector.vue +241 -0
- package/python/frontend_src/src/components/LeftRail.vue +394 -0
- package/python/frontend_src/src/components/MenuPopover.vue +130 -0
- package/python/frontend_src/src/components/MessageStream.vue +401 -0
- package/python/frontend_src/src/components/ModelPicker.vue +355 -0
- package/python/frontend_src/src/components/QuickIngest.vue +118 -0
- package/python/frontend_src/src/components/Stage.vue +226 -0
- package/python/frontend_src/src/components/ToolMenu.vue +144 -0
- package/python/frontend_src/src/components/TopBar.vue +90 -0
- package/python/frontend_src/src/components/panels/ActivityPanel.vue +84 -0
- package/python/frontend_src/src/components/panels/AgentsPanel.vue +86 -0
- package/python/frontend_src/src/components/panels/KnowledgePanel.vue +441 -0
- package/python/frontend_src/src/components/panels/MemoryPanel.vue +118 -0
- package/python/frontend_src/src/components/panels/PanelBox.vue +51 -0
- package/python/frontend_src/src/components/panels/ReviewPanel.vue +308 -0
- package/python/frontend_src/src/components/panels/SettingsPanel.vue +254 -0
- package/python/frontend_src/src/components/panels/TasksPanel.vue +74 -0
- package/python/frontend_src/src/components/panels/ToolsPanel.vue +640 -0
- package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
- package/python/frontend_src/src/lib/fmt.js +48 -0
- package/python/frontend_src/src/lib/md.js +204 -0
- package/python/frontend_src/src/main.js +7 -0
- package/python/frontend_src/src/stores/app.js +106 -0
- package/python/frontend_src/src/stores/chat.js +484 -0
- package/python/frontend_src/src/stores/model.js +104 -0
- package/python/frontend_src/src/stores/panels.js +330 -0
- package/python/frontend_src/src/styles/base.css +138 -0
- package/python/frontend_src/src/styles/panel.css +117 -0
- package/python/frontend_src/src/styles/tokens.css +78 -0
- package/python/frontend_src/vite.config.js +46 -0
- package/python/pyproject.toml +265 -0
- package/python/skills/README.md +123 -0
- package/python/skills/execution-contract/SKILL.md +30 -0
- package/python/skills/general-assistant/SKILL.md +34 -0
- package/python/skills/planner/SKILL.md +33 -0
- package/python/skills/schedule-itinerary/SKILL.md +35 -0
- package/python/skills/skill-composition/SKILL.md +31 -0
- package/python/skills/skill-guard/SKILL.md +67 -0
- package/python/skills/skill-memory/SKILL.md +50 -0
- package/python/skills/skill-router/SKILL.md +35 -0
- package/python/tests/conftest.py +76 -0
- package/python/tests/run_all.py +263 -0
- package/python/tests/test_agent_lifecycle.py +360 -0
- package/python/tests/test_ahs_p2.py +414 -0
- package/python/tests/test_anthropic_tools.py +206 -0
- package/python/tests/test_api.py +87 -0
- package/python/tests/test_api_auth.py +83 -0
- package/python/tests/test_api_host_guard.py +69 -0
- package/python/tests/test_attachments.py +167 -0
- package/python/tests/test_audit_chain.py +121 -0
- package/python/tests/test_audit_sink.py +136 -0
- package/python/tests/test_browser.py +153 -0
- package/python/tests/test_browser_request_guard.py +184 -0
- package/python/tests/test_capabilities.py +158 -0
- package/python/tests/test_channel_signing.py +192 -0
- package/python/tests/test_channel_trust.py +109 -0
- package/python/tests/test_channels.py +184 -0
- package/python/tests/test_cli.py +46 -0
- package/python/tests/test_compaction_toolpair.py +119 -0
- package/python/tests/test_config_layering.py +65 -0
- package/python/tests/test_confirm_nonce.py +197 -0
- package/python/tests/test_context_compaction_wiring.py +130 -0
- package/python/tests/test_dispatch.py +95 -0
- package/python/tests/test_domain_evals.py +403 -0
- package/python/tests/test_domain_import.py +78 -0
- package/python/tests/test_domain_reasoners.py +148 -0
- package/python/tests/test_e2e.py +178 -0
- package/python/tests/test_emb_cache.py +107 -0
- package/python/tests/test_embedding_singleton.py +89 -0
- package/python/tests/test_envfile.py +83 -0
- package/python/tests/test_evals_smoke.py +114 -0
- package/python/tests/test_event_spine.py +182 -0
- package/python/tests/test_evidence_semantic.py +101 -0
- package/python/tests/test_evidence_sse.py +97 -0
- package/python/tests/test_file_ingest.py +146 -0
- package/python/tests/test_finance.py +153 -0
- package/python/tests/test_finance_ledger.py +252 -0
- package/python/tests/test_finance_persist.py +178 -0
- package/python/tests/test_gateway.py +144 -0
- package/python/tests/test_governance.py +468 -0
- package/python/tests/test_governance_audit.py +359 -0
- package/python/tests/test_governance_middleware.py +143 -0
- package/python/tests/test_hkc.py +286 -0
- package/python/tests/test_hkc_index_build_manager.py +339 -0
- package/python/tests/test_hkc_manifest_reuse.py +317 -0
- package/python/tests/test_hkc_search_event_sync.py +533 -0
- package/python/tests/test_import_path_guard.py +161 -0
- package/python/tests/test_injection_guard.py +211 -0
- package/python/tests/test_integration.py +141 -0
- package/python/tests/test_itinerary.py +103 -0
- package/python/tests/test_kee_semantic.py +89 -0
- package/python/tests/test_kernel.py +222 -0
- package/python/tests/test_learning.py +110 -0
- package/python/tests/test_llm_admin.py +160 -0
- package/python/tests/test_llm_planner.py +366 -0
- package/python/tests/test_markdown_skills.py +73 -0
- package/python/tests/test_markdown_skills_openclaw.py +71 -0
- package/python/tests/test_mcp_connectors.py +109 -0
- package/python/tests/test_mcp_guardian_discovery.py +91 -0
- package/python/tests/test_mcp_no_memory.py +43 -0
- package/python/tests/test_mcp_policy_gate.py +77 -0
- package/python/tests/test_mcp_server.py +95 -0
- package/python/tests/test_memory_recall.py +95 -0
- package/python/tests/test_metrics.py +66 -0
- package/python/tests/test_multi_chain.py +102 -0
- package/python/tests/test_multimodal.py +135 -0
- package/python/tests/test_negotiation.py +146 -0
- package/python/tests/test_orchestration.py +119 -0
- package/python/tests/test_orchestration_decoupled.py +109 -0
- package/python/tests/test_p3_p5_hardening.py +338 -0
- package/python/tests/test_parallel_dispatch.py +215 -0
- package/python/tests/test_pipeline.py +533 -0
- package/python/tests/test_plan_visible.py +86 -0
- package/python/tests/test_planner.py +95 -0
- package/python/tests/test_policy_gate.py +109 -0
- package/python/tests/test_reasoner.py +185 -0
- package/python/tests/test_release_check.py +263 -0
- package/python/tests/test_resume_checkpoint.py +156 -0
- package/python/tests/test_routing.py +95 -0
- package/python/tests/test_scheduler.py +141 -0
- package/python/tests/test_search.py +204 -0
- package/python/tests/test_secrets.py +159 -0
- package/python/tests/test_semantic.py +78 -0
- package/python/tests/test_sessions.py +91 -0
- package/python/tests/test_settings.py +281 -0
- package/python/tests/test_shadow_learning.py +330 -0
- package/python/tests/test_shared_write.py +207 -0
- package/python/tests/test_skill_taint_gate.py +154 -0
- package/python/tests/test_skills.py +133 -0
- package/python/tests/test_stats_and_events.py +165 -0
- package/python/tests/test_streaming.py +60 -0
- package/python/tests/test_supervisor_pipeline.py +177 -0
- package/python/tests/test_supervisor_triage.py +108 -0
- package/python/tests/test_taint.py +117 -0
- package/python/tests/test_task_stream.py +85 -0
- package/python/tests/test_thinking_sse.py +67 -0
- package/python/tests/test_thinking_stream.py +76 -0
- package/python/tests/test_tool_center.py +129 -0
- package/python/tests/test_tool_parallel.py +113 -0
- package/python/tests/test_tool_params.py +103 -0
- package/python/tests/test_tool_use.py +242 -0
- package/python/tests/test_toplevel_concurrency.py +117 -0
- package/python/tests/test_tracing.py +290 -0
- package/python/tests/test_truth_crystallizer.py +127 -0
- package/python/tests/test_truth_flow.py +93 -0
- package/python/tests/test_url_policy.py +53 -0
- package/python/tests/test_verify_evidence.py +225 -0
- package/python/tests/test_version_sync.py +71 -0
- package/python/tests/test_web_trigger.py +99 -0
- package/python/tools/check_faiss_type.py +31 -0
- package/python/tools/check_log.ps1 +46 -0
- package/python/tools/ivf_status.py +72 -0
- package/python/tools/mypy_baseline.txt +5 -0
- package/python/tools/mypy_ratchet.py +97 -0
- package/python/tools/pack.py +178 -0
- package/python/tools/release_check.py +349 -0
- package/python/tools/restart_foxclaw.ps1 +74 -0
- package/python/tools/shared_write_audit.py +204 -0
- package/python/tools/size_baseline.txt +28 -0
- package/python/tools/size_check.py +102 -0
- package/python/tools/verify_env.bat +2 -0
- package/python/tools/verify_env.py +25 -0
- package/python/vendor/HMR_LICENSE +37 -0
- package/python/vendor/HMR_README.md +65 -0
- package/python/vendor/hkc/LICENSE +37 -0
- package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
- package/python/vendor/hkc/hkc_ace/ace.py +457 -0
- package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
- package/python/vendor/hkc/hkc_api/__init__.py +0 -0
- package/python/vendor/hkc/hkc_api/container.py +416 -0
- package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
- package/python/vendor/hkc/hkc_api/main.py +157 -0
- package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
- package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
- package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
- package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
- package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
- package/python/vendor/hkc/hkc_api/schemas.py +153 -0
- package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
- package/python/vendor/hkc/hkc_core/__init__.py +0 -0
- package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
- package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
- package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
- package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
- package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
- package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
- package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
- package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
- package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
- package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
- package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
- package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
- package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
- package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
- package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
- package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
- package/python/vendor/hkc/hkc_kde/kde.py +298 -0
- package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
- package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
- package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
- package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
- package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
- package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
- package/python/vendor/hkc/hkc_kde/models.py +74 -0
- package/python/vendor/hkc/hkc_kde/packager.py +329 -0
- package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
- package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
- package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
- package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
- package/python/vendor/hkc/hkc_kee/kee.py +541 -0
- package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
- package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
- package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
- package/python/vendor/hkc/hkc_sdk/base.py +151 -0
- package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
- package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
- package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
- package/python/vendor/hkc/hkc_sdk/models.py +171 -0
- package/python/vendor/hkc/hkc_search/__init__.py +0 -0
- package/python/vendor/hkc/hkc_search/bm25.py +325 -0
- package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
- package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
- package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
- package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
- package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
- package/python/vendor/hmr/__init__.py +0 -0
- package/python/vendor/hmr/core/__init__.py +0 -0
- package/python/vendor/hmr/core/hmr.py +684 -0
- package/python/vendor/hmr/core/models.py +310 -0
- package/python/vendor/hmr/engines/__init__.py +0 -0
- package/python/vendor/hmr/engines/evolution.py +710 -0
- package/python/vendor/hmr/engines/jit_compiler.py +472 -0
- package/python/vendor/hmr/engines/lifecycle.py +264 -0
- package/python/vendor/hmr/engines/policy.py +625 -0
- package/python/vendor/hmr/engines/recall.py +260 -0
- package/python/vendor/hmr/engines/runtime_state.py +158 -0
- package/python/vendor/hmr/engines/scheduler.py +323 -0
- package/python/vendor/hmr/engines/semantic.py +150 -0
- package/python/vendor/hmr/engines/temporal.py +261 -0
- package/python/vendor/hmr/engines/thought_chain.py +635 -0
- package/python/vendor/hmr/graph/__init__.py +0 -0
- package/python/vendor/hmr/graph/cwg.py +338 -0
- package/python/vendor/hmr/graph/memory_graph.py +530 -0
- package/python/vendor/hmr/storage/__init__.py +0 -0
- package/python/vendor/hmr/storage/memory_fs.py +300 -0
- package/python/vendor/hmr/storage/vector_store.py +520 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""
|
|
2
|
+
foxclaw.knowledge —— HKC(Foxclaw Knowledge Core)融入 Foxclaw 的知识子系统。
|
|
3
|
+
|
|
4
|
+
HKC 是成熟的 9 包子系统(KDE 抽取 / KEE 演化 / SQLite+NetworkX 图存储 / 混合检索
|
|
5
|
+
BM25+向量+图 / ACE 能力编译)。按 HMR 同款方式 vendor 在 `vendor/hkc/` 下,同进程
|
|
6
|
+
装配、经 hkc_sdk DirectClient 调用——不发 HTTP、不起独立服务、无嵌套工程脚手架。
|
|
7
|
+
|
|
8
|
+
设计要点:
|
|
9
|
+
· 可选依赖优雅降级:缺 vendor/hkc 或缺 networkx 等依赖时 available=False,不影响本体。
|
|
10
|
+
· 模型无关:LLM 抽取复用 Foxclaw 探测出的 provider/key,也可用 HKC_* 环境变量覆盖。
|
|
11
|
+
· 资源可释放:HKCService.close() 关闭全部 4 个 SQLite 连接(经修好的 HKCContainer.close)。
|
|
12
|
+
· 惰性装配:由 Foxclaw.hkc 属性首次访问时才装配,避免每次构造 OS 都开 db 句柄。
|
|
13
|
+
"""
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _ensure_hkc_importable() -> bool:
|
|
21
|
+
"""让内嵌的 hkc_* 包可被导入(与 HMR 的 vendor 机制一致)。
|
|
22
|
+
|
|
23
|
+
两种布局:
|
|
24
|
+
- 源码仓: vendor/hkc 在项目根下,需注入 sys.path;
|
|
25
|
+
- wheel 安装: hkc_* 已作为顶层包装进 site-packages,直接可导入,无需注入。
|
|
26
|
+
"""
|
|
27
|
+
vendor_hkc = Path(__file__).resolve().parents[2] / "vendor" / "hkc"
|
|
28
|
+
if vendor_hkc.exists():
|
|
29
|
+
p = str(vendor_hkc)
|
|
30
|
+
if p not in sys.path:
|
|
31
|
+
sys.path.insert(0, p)
|
|
32
|
+
return True
|
|
33
|
+
try:
|
|
34
|
+
import hkc_api # noqa: F401
|
|
35
|
+
except ImportError:
|
|
36
|
+
return False
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _to_hkc_provider(name: str) -> str:
|
|
41
|
+
"""Foxclaw 的 provider 名 → HKC 底层只认的 4 个协议名。
|
|
42
|
+
|
|
43
|
+
(anthropic / deepseek / openai / openai-compatible)其余全是 OpenAI 协议兼容。
|
|
44
|
+
"""
|
|
45
|
+
n = (name or "").strip().lower()
|
|
46
|
+
if n == "anthropic":
|
|
47
|
+
return "anthropic"
|
|
48
|
+
if n == "deepseek":
|
|
49
|
+
return "deepseek"
|
|
50
|
+
return "openai-compatible"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _read_custom_model(data_dir, name):
|
|
54
|
+
"""从 ``custom_models.json`` 取一项,解出 ``(key, base_url, model)``;取不到或 key 为空 → None。
|
|
55
|
+
|
|
56
|
+
base_url 必须和主对话走同一个规范化(缺 path 补 /v1),否则 SDK 拼出
|
|
57
|
+
https://api.deepseek.com/chat/completions 而主对话用 /v1/chat/completions。
|
|
58
|
+
"""
|
|
59
|
+
try:
|
|
60
|
+
models = json.loads((data_dir / "custom_models.json").read_text(encoding="utf-8"))
|
|
61
|
+
except Exception:
|
|
62
|
+
return None
|
|
63
|
+
match = next((m for m in models if m.get("name") == name), None)
|
|
64
|
+
if not match:
|
|
65
|
+
return None
|
|
66
|
+
key = ""
|
|
67
|
+
# OS 凭据库优先,回退 base64(旧记录)/ 更早的明文字段
|
|
68
|
+
from ..core.secrets import load as _load_secret
|
|
69
|
+
key = _load_secret(name, match.get("api_key_obf") or "")
|
|
70
|
+
key = key or (match.get("api_key") or "")
|
|
71
|
+
if not key.strip():
|
|
72
|
+
return None
|
|
73
|
+
base = match.get("base_url") or ""
|
|
74
|
+
try:
|
|
75
|
+
from ..core.model_router import normalize_base_url
|
|
76
|
+
base = normalize_base_url(base)
|
|
77
|
+
except Exception:
|
|
78
|
+
pass
|
|
79
|
+
return key.strip(), base, (match.get("model") or "").strip()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _selected_llm():
|
|
83
|
+
"""读前端「LLM 管理」里当前选中的模型,返回 ``(provider, key, base_url, model)`` 或 None。
|
|
84
|
+
|
|
85
|
+
这是**用户在界面上显式做过的选择**(FOXCLAW_DATA_DIR/.current_model),比任何
|
|
86
|
+
env 默认值都更能代表"现在真实在用的那个模型",所以排在 LLM_* 之前。
|
|
87
|
+
|
|
88
|
+
两个踩过的坑:
|
|
89
|
+
1. preset 写的字段是 ``provider``,不是 ``name`` —— 只读 name 会让 preset 选择被
|
|
90
|
+
整个忽略,HKC 于是退回 .env(里面可能躺着一份早就换掉的旧 key)。
|
|
91
|
+
2. preset 的 key 名义上来自 .env,但用户往往同时在「自定义模型」里配过同一个
|
|
92
|
+
provider(名字相同、key 更新)。界面里填的比 .env 默认新,所以优先它。
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
data_dir = Path(os.environ.get("FOXCLAW_DATA_DIR", "./foxclaw_data"))
|
|
96
|
+
sel_file = data_dir / ".current_model"
|
|
97
|
+
if not sel_file.exists():
|
|
98
|
+
return None
|
|
99
|
+
sel = json.loads(sel_file.read_text(encoding="utf-8"))
|
|
100
|
+
except Exception:
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
source = (sel.get("source") or "").strip().lower()
|
|
104
|
+
# preset 落盘的是 provider;custom 落盘的是 name。两者都当"选中的名字"用。
|
|
105
|
+
name = (sel.get("name") or sel.get("provider") or "").strip()
|
|
106
|
+
model = (sel.get("model_id") or sel.get("model") or "").strip()
|
|
107
|
+
|
|
108
|
+
if source == "custom" and name:
|
|
109
|
+
got = _read_custom_model(data_dir, name)
|
|
110
|
+
if not got:
|
|
111
|
+
return None
|
|
112
|
+
key, base, custom_model = got
|
|
113
|
+
return _to_hkc_provider("generic"), key, base, model or custom_model
|
|
114
|
+
|
|
115
|
+
if source == "preset" and name:
|
|
116
|
+
# 界面里配过的同名自定义项(更新的 key)优先,env 只作最后兜底
|
|
117
|
+
got = _read_custom_model(data_dir, name)
|
|
118
|
+
if got:
|
|
119
|
+
key, base, custom_model = got
|
|
120
|
+
return _to_hkc_provider("generic"), key, base, model or custom_model
|
|
121
|
+
env_name = ""
|
|
122
|
+
try:
|
|
123
|
+
from ..core.model_router import PROVIDER_ENV
|
|
124
|
+
env_name = PROVIDER_ENV.get(name, "")
|
|
125
|
+
except Exception:
|
|
126
|
+
pass
|
|
127
|
+
key = (os.environ.get(env_name) or "").strip() if env_name else ""
|
|
128
|
+
return (_to_hkc_provider(name), key, "", model)
|
|
129
|
+
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _detect_llm(api_key=None):
|
|
134
|
+
"""【P2·单 LLM 入口】模型无关地决定 HKC 抽取用的 provider/key/base_url/model。
|
|
135
|
+
|
|
136
|
+
优先级(由高到低),整体目标是「和主对话用同一个模型」:
|
|
137
|
+
1. ``HKC_LLM_*`` —— 显式让 HKC 独立配(主对话走云、抽取走本地)
|
|
138
|
+
2. **前端 LLM 管理当前选中项** —— 用户在界面上做的显式选择
|
|
139
|
+
3. ``LLM_PROVIDER / LLM_API_KEY / LLM_BASE_URL / LLM_MODEL`` 单入口 env
|
|
140
|
+
4. 主对话的自动探测(``ModelRouter._auto_detect``)
|
|
141
|
+
|
|
142
|
+
HKC 底层只认 4 个协议名(anthropic / deepseek / openai / openai-compatible),
|
|
143
|
+
其余按 OpenAI 兼容协议映射过去。
|
|
144
|
+
|
|
145
|
+
返回 ``(provider, key, base_url, model)``。
|
|
146
|
+
"""
|
|
147
|
+
# 1. 显式 HKC_LLM_PROVIDER 覆盖(向后兼容「HKC 独立配」场景)
|
|
148
|
+
env_provider = (os.environ.get("HKC_LLM_PROVIDER") or "").strip().lower()
|
|
149
|
+
if env_provider in ("anthropic", "deepseek", "openai", "openai-compatible"):
|
|
150
|
+
key = (os.environ.get("HKC_LLM_API_KEY")
|
|
151
|
+
or os.environ.get("LLM_API_KEY")
|
|
152
|
+
or "")
|
|
153
|
+
base = (os.environ.get("HKC_LLM_BASE_URL")
|
|
154
|
+
or os.environ.get("LLM_BASE_URL")
|
|
155
|
+
or "")
|
|
156
|
+
model = (os.environ.get("HKC_LLM_MODEL")
|
|
157
|
+
or os.environ.get("LLM_MODEL")
|
|
158
|
+
or "")
|
|
159
|
+
return env_provider, key or api_key, base, model
|
|
160
|
+
|
|
161
|
+
# 2. 界面里当前选中的模型(用户显式动作,优先于 env 默认)
|
|
162
|
+
selected = _selected_llm()
|
|
163
|
+
if selected:
|
|
164
|
+
provider, key, base, model = selected
|
|
165
|
+
return provider, (key or api_key or "").strip(), (base or "").strip(), model
|
|
166
|
+
|
|
167
|
+
# 3. 单入口 env。未显式指定 provider 时**复用主对话的自动探测**,
|
|
168
|
+
# 不能默认成 "generic" —— 那是个空 key 的壳,会让抽取打 api.openai.com
|
|
169
|
+
# 拿 Missing credentials,每次摄入都 0 条。
|
|
170
|
+
provider = (os.environ.get("LLM_PROVIDER") or "").strip().lower()
|
|
171
|
+
key = (os.environ.get("LLM_API_KEY") or api_key or "").strip()
|
|
172
|
+
base = os.environ.get("LLM_BASE_URL", "").strip()
|
|
173
|
+
model = (os.environ.get("LLM_MODEL") or os.environ.get("HKC_LLM_MODEL") or "").strip()
|
|
174
|
+
|
|
175
|
+
provider_env = {}
|
|
176
|
+
if not provider:
|
|
177
|
+
try:
|
|
178
|
+
from ..core.model_router import ModelRouter, PROVIDER_ENV
|
|
179
|
+
provider_env = PROVIDER_ENV
|
|
180
|
+
detected = ModelRouter._auto_detect()
|
|
181
|
+
except Exception:
|
|
182
|
+
detected = ""
|
|
183
|
+
provider = detected if detected and detected not in ("stub", "local") else "generic"
|
|
184
|
+
if not key:
|
|
185
|
+
env_name = provider_env.get(provider) or ""
|
|
186
|
+
if env_name:
|
|
187
|
+
key = (os.environ.get(env_name) or "").strip()
|
|
188
|
+
|
|
189
|
+
return _to_hkc_provider(provider), key, base, model
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class HKCService:
|
|
193
|
+
"""HKC 在 Foxclaw 内的接入句柄:持有同进程容器 + hkc_sdk DirectClient,统一 close。"""
|
|
194
|
+
|
|
195
|
+
def __init__(self, container, client):
|
|
196
|
+
self._container = container
|
|
197
|
+
self.client = client # DirectClient;供 HSR 检索 / /api/knowledge 路由使用
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def available(self) -> bool:
|
|
201
|
+
return self.client is not None
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def event_bus(self):
|
|
205
|
+
"""HKC 内部 EventBus(或 None)。供桥接到 Foxclaw 的 EventSpine,使知识生命周期
|
|
206
|
+
事件(created/merged/conflict/ability/…)对统一观测层可见。"""
|
|
207
|
+
return getattr(self._container, "event_bus", None)
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def container(self):
|
|
211
|
+
"""同进程 HKCContainer。供 API 把 HKC 自带 REST 路由(星球用的 /knowledge/graph 等)
|
|
212
|
+
的 get_container 依赖指向这同一个已装配容器(依赖倒置,不另开一套)。"""
|
|
213
|
+
return self._container
|
|
214
|
+
|
|
215
|
+
def stats(self) -> dict:
|
|
216
|
+
try:
|
|
217
|
+
return self._container.stats()
|
|
218
|
+
except Exception as e:
|
|
219
|
+
return {"error": str(e)}
|
|
220
|
+
|
|
221
|
+
def close(self):
|
|
222
|
+
"""释放 HKC 全部原生资源(4 个 SQLite 连接 + 摄入线程池)。幂等。"""
|
|
223
|
+
if self._container is not None:
|
|
224
|
+
try:
|
|
225
|
+
self._container.close()
|
|
226
|
+
except Exception:
|
|
227
|
+
pass
|
|
228
|
+
self._container = None
|
|
229
|
+
self.client = None
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def init_hkc(data_dir, api_key=None):
|
|
233
|
+
"""同进程装配 HKC,返回 HKCService(available=True) 或 None(降级)。
|
|
234
|
+
embedding 默认 auto(本地 sentence-transformers 优先,无库/加载失败自动回退 stub;
|
|
235
|
+
可 HKC_EMBEDDING=stub 强制关闭真语义;HKC_EMBEDDING_MODEL 指定 local/auto 的模型名,
|
|
236
|
+
如 BAAI/bge-m3,留空用内置默认模型)。"""
|
|
237
|
+
if not _ensure_hkc_importable():
|
|
238
|
+
print("[Foxclaw] · HKC 未 vendor(可选):vendor/hkc 缺失,知识库降级")
|
|
239
|
+
return None
|
|
240
|
+
try:
|
|
241
|
+
from hkc_api.container import HKCContainer
|
|
242
|
+
from hkc_sdk import connect
|
|
243
|
+
except ImportError as e:
|
|
244
|
+
print(f"[Foxclaw] · HKC 依赖缺失(可选,pip install 'foxclaw[knowledge]'): {e}")
|
|
245
|
+
return None
|
|
246
|
+
try:
|
|
247
|
+
provider, llm_key, llm_base_url, llm_model = _detect_llm(api_key)
|
|
248
|
+
# 【P2·单 LLM 入口】embedding 模型名优先 EMBEDDING_MODEL(系统唯一本地入口),
|
|
249
|
+
# 再 HKC_EMBEDDING_MODEL(向后兼容);其他 HKC 行为开关不变。
|
|
250
|
+
_emb_model = (os.environ.get("EMBEDDING_MODEL")
|
|
251
|
+
or os.environ.get("HKC_EMBEDDING_MODEL")) or None
|
|
252
|
+
container = HKCContainer(
|
|
253
|
+
data_dir=str(Path(data_dir) / "hkc"),
|
|
254
|
+
embedding_kind=os.environ.get("HKC_EMBEDDING", "auto"), # 默认真语义(本地优先,无 ST 回退 stub)
|
|
255
|
+
embedding_url=os.environ.get("HKC_EMBEDDING_URL", "http://localhost:8080"),
|
|
256
|
+
embedding_model=_emb_model, # local/auto 模型名(如 bge-m3)
|
|
257
|
+
llm_api_key=llm_key or None,
|
|
258
|
+
llm_provider=provider,
|
|
259
|
+
llm_model=llm_model or None,
|
|
260
|
+
llm_base_url=llm_base_url or None,
|
|
261
|
+
).assemble()
|
|
262
|
+
# 注册为 hkc_api 全局单例:若将来挂 HKC 自身 /hkc REST 路由,
|
|
263
|
+
# 其 Depends(get_container) 能取到这同一个容器,而非"未初始化"。
|
|
264
|
+
from hkc_api import container as _cm
|
|
265
|
+
_cm._container = container
|
|
266
|
+
print(f"[Foxclaw] ✓ HKC 知识核心已进程内融合 (provider={provider}, "
|
|
267
|
+
f"base={'已配置' if llm_base_url else 'provider默认'}, "
|
|
268
|
+
f"key={'已配置' if llm_key else '未配置(抽取将降级)'})")
|
|
269
|
+
return HKCService(container, connect(container=container))
|
|
270
|
+
except Exception as e:
|
|
271
|
+
print(f"[Foxclaw] ⚠ HKC 装配失败,知识库降级为空: {e}")
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
from .truth_crystallizer import TruthCrystallizer
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# HKC 知识生命周期事件 → 桥接到 EventSpine 的类型。**刻意不含 memory.***(那是 HMR 域,
|
|
279
|
+
# Foxclaw 已有自己的记忆事件,转发 HKC 的会重复/混淆)。
|
|
280
|
+
_HKC_BRIDGED_EVENTS = (
|
|
281
|
+
"knowledge.created", "knowledge.updated", "knowledge.merged", "knowledge.deleted",
|
|
282
|
+
"conflict.detected", "conflict.resolved", "claim.status_changed",
|
|
283
|
+
"ability.created", "ability.updated", "agent.used_ability",
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def bridge_hkc_events(hkc_event_bus, spine) -> bool:
|
|
288
|
+
"""把 HKC 内部 EventBus 的知识生命周期事件桥接到 Foxclaw 的 **EventSpine**——
|
|
289
|
+
消除审计发现的"HKC 事件孤岛"(P0-2 只桥了 harness+system,漏了 HKC)。桥接后知识
|
|
290
|
+
演化(去重/合并/冲突/能力编译)对统一观测(WS/robot/history)可见,source="hkc"。
|
|
291
|
+
|
|
292
|
+
转发在 HKC 摄入线程执行(EventBus handler 同步调用);spine 线程安全;转发失败吞掉、
|
|
293
|
+
绝不影响 HKC 内部流转。返回是否接线成功。高频的 knowledge.created/updated 仍进脊柱
|
|
294
|
+
history,但由 WS 投影过滤(见 api._register_spine_projection),避免批量摄入泛洪前端。"""
|
|
295
|
+
if hkc_event_bus is None or spine is None:
|
|
296
|
+
return False
|
|
297
|
+
|
|
298
|
+
def _forward(evt: dict):
|
|
299
|
+
try:
|
|
300
|
+
spine.publish(evt.get("event", "knowledge.unknown"),
|
|
301
|
+
evt.get("payload") or {}, source="hkc",
|
|
302
|
+
correlation_id=evt.get("event_id"))
|
|
303
|
+
except Exception:
|
|
304
|
+
pass
|
|
305
|
+
|
|
306
|
+
ok = False
|
|
307
|
+
for ev in _HKC_BRIDGED_EVENTS:
|
|
308
|
+
try:
|
|
309
|
+
hkc_event_bus.subscribe(ev, _forward)
|
|
310
|
+
ok = True
|
|
311
|
+
except Exception:
|
|
312
|
+
pass
|
|
313
|
+
return ok
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _ability_triggers(ab) -> list:
|
|
317
|
+
"""从能力生成触发词:display_name + domain + coverage 概念键(供 router 匹配)。"""
|
|
318
|
+
terms = [ab.display_name, ab.domain]
|
|
319
|
+
terms += [str(k) for k in (getattr(ab, "coverage", {}) or {}).keys()]
|
|
320
|
+
return [t for t in terms if t]
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _make_ability_handler(client, ab):
|
|
324
|
+
"""能力 skill 的 handler(read、确定性、无 LLM):在能力 domain 内检索任务相关知识并返回。
|
|
325
|
+
只做知识综合(无外部副作用);LLM 综述可后续叠加。检索失败 → 返回空知识,不崩。"""
|
|
326
|
+
def _handler(query: str = "", **_kw) -> dict:
|
|
327
|
+
try:
|
|
328
|
+
hits = client.search(query, mode="hybrid", top_k=5, domain=ab.domain)
|
|
329
|
+
except Exception:
|
|
330
|
+
hits = []
|
|
331
|
+
return {"ability": ab.ability_key, "domain": ab.domain,
|
|
332
|
+
"knowledge": [{"name": h.name, "summary": h.summary} for h in hits[:5]]}
|
|
333
|
+
return _handler
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def register_abilities_as_skills(registry, hkc) -> list:
|
|
337
|
+
"""【HKC 第2档 Step4·能力→SkillRegistry(最深融合)】把 HKC 中 **coverage 达标**的 ACE 能力
|
|
338
|
+
注册成 SkillRegistry 里的 skill——**证据 gate**:覆盖不足的**不注册**(能力不予授予)。
|
|
339
|
+
|
|
340
|
+
注册的 skill 为 `side_effect=read`(知识综合、无外部副作用)→ 经 SkillAdapter/PolicyGate
|
|
341
|
+
统一治理、且可被认知管线 `stage_route` 路由(复用 A 方案)。返回注册的 skill id 列表。
|
|
342
|
+
HKC 未启用/无达标能力 → 不注册、返回 []。幂等(同 id 覆盖注册)。"""
|
|
343
|
+
if registry is None or hkc is None or not getattr(hkc, "available", False):
|
|
344
|
+
return []
|
|
345
|
+
from ..skills import SkillSpec
|
|
346
|
+
client = hkc.client
|
|
347
|
+
try:
|
|
348
|
+
keys = list(client.list_abilities())
|
|
349
|
+
except Exception:
|
|
350
|
+
return []
|
|
351
|
+
registered = []
|
|
352
|
+
for key in keys:
|
|
353
|
+
try:
|
|
354
|
+
ab = client.compile_ability(key) # 覆盖不足抛异常 → 证据 gate 跳过、不注册
|
|
355
|
+
except Exception:
|
|
356
|
+
continue
|
|
357
|
+
sid = f"ability:{key}"
|
|
358
|
+
registry.register(SkillSpec(
|
|
359
|
+
id=sid, name=ab.display_name or key,
|
|
360
|
+
description=f"知识编译能力:{ab.display_name}(domain={ab.domain},coverage 达标)",
|
|
361
|
+
kind="local", side_effect="read",
|
|
362
|
+
triggers=_ability_triggers(ab),
|
|
363
|
+
handler=_make_ability_handler(client, ab),
|
|
364
|
+
params={"query": "str"}))
|
|
365
|
+
registered.append(sid)
|
|
366
|
+
# 【深审修复·陈旧清理】重新同步时,移除此前注册但**本轮不再达标**的 ability skill
|
|
367
|
+
# (coverage 掉了/能力定义删了)——register-only 会残留陈旧项;prune 使 registry 反映当前覆盖。
|
|
368
|
+
fresh = set(registered)
|
|
369
|
+
for spec in registry.all():
|
|
370
|
+
if spec.id.startswith("ability:") and spec.id not in fresh:
|
|
371
|
+
registry.unregister(spec.id)
|
|
372
|
+
return registered
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
__all__ = ["init_hkc", "HKCService", "TruthCrystallizer", "bridge_hkc_events",
|
|
376
|
+
"register_abilities_as_skills"]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TruthCrystallizer —— 把执行真值经 KDE→KEE 结晶进 HKC(v2 ④ Step1)。
|
|
3
|
+
|
|
4
|
+
审计边界(P2 已厘清):HMR = 情景记忆("发生过什么");HKC = KEE 裁决的知识("什么是真的")。
|
|
5
|
+
真值本质是"知识",应经 **KEE 裁决**(去重/合并/冲突,守 Principle 07)进 HKC,而非只塞 HMR。
|
|
6
|
+
|
|
7
|
+
Step1(叠加、默认关、行为不变):
|
|
8
|
+
· 订阅 `truth.appended`;启用且 HKC 可用时,把真值 fact 作为知识候选**后台**推给
|
|
9
|
+
`hkc.client.ingest_text`(KDE 抽取 → 发 knowledge.created → KEE 去重/合并入图)。
|
|
10
|
+
· **后台线程执行**:KDE 调 LLM,绝不能阻塞 run_task 的同步真值链(truth.appended 处理器
|
|
11
|
+
只提交任务、立即返回)。
|
|
12
|
+
· **双写**:TruthPipeline 的 HMR 共享真值写保留不变;本结晶器只**叠加** HKC 路径。
|
|
13
|
+
· 默认关(FOXCLAW_TRUTH_TO_HKC=1 才开):KDE 有 LLM 成本,先 opt-in;后续步骤再翻默认。
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
import threading
|
|
17
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
18
|
+
from typing import Callable, Optional
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TruthCrystallizer:
|
|
22
|
+
def __init__(self, hkc_provider: Callable[[], object], enabled: bool = False):
|
|
23
|
+
self._hkc_provider = hkc_provider # 惰性取 HKCService(或 None)
|
|
24
|
+
self.enabled = enabled
|
|
25
|
+
self._executor: Optional[ThreadPoolExecutor] = None
|
|
26
|
+
self._closed = False
|
|
27
|
+
self._lock = threading.Lock()
|
|
28
|
+
self._pending: list = [] # 供测试 drain
|
|
29
|
+
|
|
30
|
+
def _ex(self) -> Optional[ThreadPoolExecutor]:
|
|
31
|
+
with self._lock:
|
|
32
|
+
if self._closed:
|
|
33
|
+
return None
|
|
34
|
+
if self._executor is None:
|
|
35
|
+
self._executor = ThreadPoolExecutor(
|
|
36
|
+
max_workers=1, thread_name_prefix="truth-crystallize")
|
|
37
|
+
return self._executor
|
|
38
|
+
|
|
39
|
+
def on_truth(self, msg) -> None:
|
|
40
|
+
"""订阅 truth.appended 的处理器(同步、非阻塞):只提交后台结晶任务。"""
|
|
41
|
+
if not self.enabled:
|
|
42
|
+
return
|
|
43
|
+
p = getattr(msg, "payload", None) or {}
|
|
44
|
+
if p.get("blocked"): # 被拦截任务不结晶(与 TruthPipeline 一致)
|
|
45
|
+
return
|
|
46
|
+
fact = ((p.get("truth") or {}).get("fact") or "").strip()
|
|
47
|
+
if not fact:
|
|
48
|
+
return
|
|
49
|
+
domain = p.get("agent_id") or "execution"
|
|
50
|
+
ex = self._ex()
|
|
51
|
+
if ex is None:
|
|
52
|
+
return
|
|
53
|
+
fut = ex.submit(self._crystallize, fact, domain)
|
|
54
|
+
with self._lock:
|
|
55
|
+
# 【深审修复·长期运行泄漏】_pending 原先只增不减(drain 仅在 close/测试时调用),
|
|
56
|
+
# 启用后每来一次 truth.appended 就永久累积一个已完成 Future → 无界内存泄漏。
|
|
57
|
+
# 改为每次提交时 prune 掉已完成的,_pending 只保留在飞行中的任务(仍支持测试 drain)。
|
|
58
|
+
self._pending = [f for f in self._pending if not f.done()]
|
|
59
|
+
self._pending.append(fut)
|
|
60
|
+
|
|
61
|
+
def _crystallize(self, fact: str, domain: str) -> bool:
|
|
62
|
+
"""后台执行:惰性取 HKC → ingest_text(KDE→KEE)。任何失败都吞掉,不影响主流程。"""
|
|
63
|
+
try:
|
|
64
|
+
hkc = self._hkc_provider()
|
|
65
|
+
except Exception:
|
|
66
|
+
hkc = None
|
|
67
|
+
if hkc is None or not getattr(hkc, "available", False):
|
|
68
|
+
return False
|
|
69
|
+
try:
|
|
70
|
+
hkc.client.ingest_text(fact, source_title="真值", domain=domain)
|
|
71
|
+
return True
|
|
72
|
+
except Exception:
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def drain(self, timeout: float = 10.0) -> None:
|
|
76
|
+
"""等待所有已提交的结晶任务完成(测试/优雅关停用)。"""
|
|
77
|
+
with self._lock:
|
|
78
|
+
pending = list(self._pending)
|
|
79
|
+
self._pending = []
|
|
80
|
+
for fut in pending:
|
|
81
|
+
try:
|
|
82
|
+
fut.result(timeout=timeout)
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
def close(self) -> None:
|
|
87
|
+
self.drain()
|
|
88
|
+
with self._lock:
|
|
89
|
+
self._closed = True
|
|
90
|
+
ex = self._executor
|
|
91
|
+
if ex is not None:
|
|
92
|
+
ex.shutdown(wait=True)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Foxclaw — Learning: office Agent 的学习行为操作系统内核"""
|
|
2
|
+
from .models import (
|
|
3
|
+
KnowledgeItem, ReviewState, KnowledgeEdge, LearningVerdict,
|
|
4
|
+
)
|
|
5
|
+
from .spaced_repetition import SpacedRepetition
|
|
6
|
+
from .knowledge_graph import KnowledgeGraph
|
|
7
|
+
from .runtime import LearningRuntime
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"KnowledgeItem", "ReviewState", "KnowledgeEdge", "LearningVerdict",
|
|
11
|
+
"SpacedRepetition", "KnowledgeGraph", "LearningRuntime",
|
|
12
|
+
]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Foxclaw 学习子系统 — 知识图谱(连接层)
|
|
3
|
+
把分散的知识点串成网。三种边:
|
|
4
|
+
manual(手动标注)、same_topic(同主题/标签自动)、semantic(语义相近自动)
|
|
5
|
+
核心思想:学习不只是记住单点,更是发现知识之间的连接。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
from .models import KnowledgeItem, KnowledgeEdge
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class KnowledgeGraph:
|
|
14
|
+
"""轻量知识图谱。语义相似度优先用外部 embedder(HMR),
|
|
15
|
+
不可用时降级为字符 n-gram 哈希向量余弦(与 Harness 同思路)。"""
|
|
16
|
+
|
|
17
|
+
DIM = 256
|
|
18
|
+
|
|
19
|
+
def __init__(self, embedder=None):
|
|
20
|
+
self.items: dict = {} # id -> KnowledgeItem
|
|
21
|
+
self.edges: list = [] # KnowledgeEdge
|
|
22
|
+
self._embedder = embedder # 可选:HMR 的向量能力
|
|
23
|
+
|
|
24
|
+
def add_item(self, item: KnowledgeItem):
|
|
25
|
+
self.items[item.id] = item
|
|
26
|
+
|
|
27
|
+
def remove_item(self, item_id: str):
|
|
28
|
+
"""移除一个知识点及其相关的所有边。"""
|
|
29
|
+
self.items.pop(item_id, None)
|
|
30
|
+
self.edges = [e for e in self.edges
|
|
31
|
+
if e.source_id != item_id and e.target_id != item_id]
|
|
32
|
+
|
|
33
|
+
# ── 三种连接 ──────────────────────────────────────────
|
|
34
|
+
def link_manual(self, source_id: str, target_id: str, reason: str = ""):
|
|
35
|
+
self.edges.append(KnowledgeEdge(
|
|
36
|
+
source_id=source_id, target_id=target_id,
|
|
37
|
+
edge_type="manual", weight=1.0, reason=reason or "手动关联"))
|
|
38
|
+
|
|
39
|
+
def auto_link_by_topic(self):
|
|
40
|
+
"""同主题 / 共享标签的知识点自动连接。"""
|
|
41
|
+
ids = list(self.items.keys())
|
|
42
|
+
new_edges = 0
|
|
43
|
+
for i in range(len(ids)):
|
|
44
|
+
for j in range(i + 1, len(ids)):
|
|
45
|
+
a, b = self.items[ids[i]], self.items[ids[j]]
|
|
46
|
+
shared_tags = set(a.tags) & set(b.tags)
|
|
47
|
+
if a.topic == b.topic and a.topic != "general":
|
|
48
|
+
self.edges.append(KnowledgeEdge(
|
|
49
|
+
source_id=a.id, target_id=b.id, edge_type="same_topic",
|
|
50
|
+
weight=0.7, reason=f"同主题:{a.topic}"))
|
|
51
|
+
new_edges += 1
|
|
52
|
+
elif shared_tags:
|
|
53
|
+
self.edges.append(KnowledgeEdge(
|
|
54
|
+
source_id=a.id, target_id=b.id, edge_type="same_topic",
|
|
55
|
+
weight=0.5, reason=f"共享标签:{','.join(shared_tags)}"))
|
|
56
|
+
new_edges += 1
|
|
57
|
+
return new_edges
|
|
58
|
+
|
|
59
|
+
def auto_link_by_semantic(self, threshold: float = 0.55):
|
|
60
|
+
"""语义相近的知识点自动连接(余弦相似度)。"""
|
|
61
|
+
ids = list(self.items.keys())
|
|
62
|
+
vecs = {i: self._embed(self.items[i].content) for i in ids}
|
|
63
|
+
new_edges = 0
|
|
64
|
+
for i in range(len(ids)):
|
|
65
|
+
for j in range(i + 1, len(ids)):
|
|
66
|
+
sim = self._cosine(vecs[ids[i]], vecs[ids[j]])
|
|
67
|
+
if sim >= threshold:
|
|
68
|
+
self.edges.append(KnowledgeEdge(
|
|
69
|
+
source_id=ids[i], target_id=ids[j], edge_type="semantic",
|
|
70
|
+
weight=round(sim, 3),
|
|
71
|
+
reason=f"语义相近({sim:.2f})"))
|
|
72
|
+
new_edges += 1
|
|
73
|
+
return new_edges
|
|
74
|
+
|
|
75
|
+
# ── 图分析 ────────────────────────────────────────────
|
|
76
|
+
def neighbors(self, item_id: str) -> list:
|
|
77
|
+
"""某知识点的所有关联点。"""
|
|
78
|
+
out = []
|
|
79
|
+
for e in self.edges:
|
|
80
|
+
if e.source_id == item_id:
|
|
81
|
+
out.append((e.target_id, e.edge_type, e.weight, e.reason))
|
|
82
|
+
elif e.target_id == item_id:
|
|
83
|
+
out.append((e.source_id, e.edge_type, e.weight, e.reason))
|
|
84
|
+
return sorted(out, key=lambda x: -x[2])
|
|
85
|
+
|
|
86
|
+
def clusters(self) -> list:
|
|
87
|
+
"""连通分量 = 知识簇。返回每个簇的成员 id 列表。"""
|
|
88
|
+
adj = defaultdict(set)
|
|
89
|
+
for e in self.edges:
|
|
90
|
+
adj[e.source_id].add(e.target_id)
|
|
91
|
+
adj[e.target_id].add(e.source_id)
|
|
92
|
+
seen, result = set(), []
|
|
93
|
+
for node in self.items:
|
|
94
|
+
if node in seen:
|
|
95
|
+
continue
|
|
96
|
+
stack, comp = [node], []
|
|
97
|
+
while stack:
|
|
98
|
+
n = stack.pop()
|
|
99
|
+
if n in seen:
|
|
100
|
+
continue
|
|
101
|
+
seen.add(n)
|
|
102
|
+
comp.append(n)
|
|
103
|
+
stack.extend(adj[n] - seen)
|
|
104
|
+
result.append(comp)
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
def stats(self) -> dict:
|
|
108
|
+
type_count = defaultdict(int)
|
|
109
|
+
for e in self.edges:
|
|
110
|
+
type_count[e.edge_type] += 1
|
|
111
|
+
return {
|
|
112
|
+
"items": len(self.items),
|
|
113
|
+
"edges": len(self.edges),
|
|
114
|
+
"edge_types": dict(type_count),
|
|
115
|
+
"clusters": len(self.clusters()),
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# ── 向量工具 ──────────────────────────────────────────
|
|
119
|
+
def _embed(self, text: str):
|
|
120
|
+
if self._embedder is not None:
|
|
121
|
+
try:
|
|
122
|
+
return self._embedder(text)
|
|
123
|
+
except Exception:
|
|
124
|
+
pass
|
|
125
|
+
return self._hash_embed(text)
|
|
126
|
+
|
|
127
|
+
def _hash_embed(self, text: str):
|
|
128
|
+
import math
|
|
129
|
+
vec = [0.0] * self.DIM
|
|
130
|
+
text = (text or "").lower().strip()
|
|
131
|
+
grams = [text[i:i+3] for i in range(max(len(text) - 2, 1))]
|
|
132
|
+
for g in grams:
|
|
133
|
+
h = int(hashlib.md5(g.encode("utf-8")).hexdigest(), 16)
|
|
134
|
+
idx = h % self.DIM
|
|
135
|
+
vec[idx] += 1.0 if (h // self.DIM) % 2 == 0 else -1.0
|
|
136
|
+
norm = math.sqrt(sum(v * v for v in vec))
|
|
137
|
+
return [v / norm for v in vec] if norm > 0 else vec
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def _cosine(a, b) -> float:
|
|
141
|
+
# 自己归一化,不假设输入已是单位向量:外部 embedder(如 HMR)返回的向量
|
|
142
|
+
# 未必归一化,直接用点积当余弦会系统性偏高(threshold 失效,什么都连上)。
|
|
143
|
+
import math
|
|
144
|
+
dot = sum(x * y for x, y in zip(a, b))
|
|
145
|
+
na = math.sqrt(sum(x * x for x in a))
|
|
146
|
+
nb = math.sqrt(sum(x * x for x in b))
|
|
147
|
+
if na == 0 or nb == 0:
|
|
148
|
+
return 0.0
|
|
149
|
+
return max(0.0, min(1.0, dot / (na * nb)))
|