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,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DomainReasoner —— 领域认知化的推理骨架(v2 ③)。
|
|
3
|
+
|
|
4
|
+
把领域裁决从"规则引擎"升级为显式的认知管线:
|
|
5
|
+
观察(observe) → 假设(hypothesize) → 收集证据(gather_evidence) → 裁决(arbitrate)
|
|
6
|
+
|
|
7
|
+
**双层保留(铁律)**:规则永远是明确/硬约束情形的权威 + LLM/推理只增强模糊情形;
|
|
8
|
+
每个裁决都带**证据链**(可审计);LLM 出错一律 **fail-safe**(转确认,呼应 P3 教训)。
|
|
9
|
+
Step1 只做形式化:用现有规则实现本接口,裁决逐字不变、显式产出证据链。
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Evidence:
|
|
18
|
+
factor: str # 证据因子(如规则 id / 历史频率 / 预算余量)
|
|
19
|
+
detail: str = "" # 具体说明
|
|
20
|
+
weight: float = 1.0 # 权重(0~1)
|
|
21
|
+
raw: Any = None # 原始证据对象(如 PolicyVerdict),供裁决内部使用
|
|
22
|
+
|
|
23
|
+
def to_dict(self) -> dict:
|
|
24
|
+
return {"factor": self.factor, "detail": self.detail, "weight": self.weight}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class ReasonedVerdict:
|
|
29
|
+
decision: str # approve | confirm | reject
|
|
30
|
+
rationale: str # 裁决说明(人类可读)
|
|
31
|
+
evidence: list = field(default_factory=list) # [Evidence]
|
|
32
|
+
hypotheses: list = field(default_factory=list) # [str]
|
|
33
|
+
source: str = "rules" # rules | llm(哪层做出的裁决)
|
|
34
|
+
|
|
35
|
+
def to_dict(self) -> dict:
|
|
36
|
+
return {"decision": self.decision, "rationale": self.rationale,
|
|
37
|
+
"source": self.source, "hypotheses": list(self.hypotheses),
|
|
38
|
+
"evidence": [e.to_dict() for e in self.evidence]}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DomainReasoner:
|
|
42
|
+
"""领域推理器基座:reason() 串起四步。子类覆盖各步(默认空/放行)。"""
|
|
43
|
+
|
|
44
|
+
def observe(self, subject) -> dict:
|
|
45
|
+
return {}
|
|
46
|
+
|
|
47
|
+
def hypothesize(self, observation: dict) -> list:
|
|
48
|
+
return []
|
|
49
|
+
|
|
50
|
+
def gather_evidence(self, subject, observation: dict) -> list:
|
|
51
|
+
return []
|
|
52
|
+
|
|
53
|
+
def arbitrate(self, subject, observation: dict,
|
|
54
|
+
hypotheses: list, evidence: list) -> ReasonedVerdict:
|
|
55
|
+
return ReasonedVerdict("approve", "无特殊裁决", evidence, hypotheses, "rules")
|
|
56
|
+
|
|
57
|
+
def reason(self, subject) -> ReasonedVerdict:
|
|
58
|
+
"""观察 → 假设 → 收集证据 → 裁决。返回带证据链的 ReasonedVerdict。"""
|
|
59
|
+
obs = self.observe(subject)
|
|
60
|
+
hyps = self.hypothesize(obs)
|
|
61
|
+
ev = self.gather_evidence(subject, obs)
|
|
62
|
+
return self.arbitrate(subject, obs, hyps, ev)
|
|
63
|
+
|
|
64
|
+
# ── 双层增强的通用工具(v2 ③ Step4:itinerary/learning 复用)────────────
|
|
65
|
+
@staticmethod
|
|
66
|
+
def llm_available(llm, enabled: bool) -> bool:
|
|
67
|
+
"""LLM 是否真正可用:启用 + 非空 + 非 stub(无真实模型时视为不可用)。"""
|
|
68
|
+
return bool(enabled) and llm is not None and not getattr(llm, "is_stub", True)
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def llm_should_escalate(llm, system: str, user: str) -> bool:
|
|
72
|
+
"""让 LLM 就"规则未发现硬问题、但情形微妙"的情况投票是否升级为人工确认。
|
|
73
|
+
**结构上只增强、不推翻**:只返回 True(建议 confirm)/False(维持规则裁决),
|
|
74
|
+
无从产生"推翻规则"的结果。出错/超时/响应不明 → False(维持规则裁决;
|
|
75
|
+
顾问域无治理闸口,不因 LLM 抖动制造确认噪声——与守门域 finance 的
|
|
76
|
+
error→confirm 区别正在于此)。"""
|
|
77
|
+
try:
|
|
78
|
+
resp = (llm.complete(system, [{"role": "user", "content": user}],
|
|
79
|
+
max_tokens=16) or "").upper()
|
|
80
|
+
except Exception:
|
|
81
|
+
return False
|
|
82
|
+
return "CONFIRM" in resp or "ESCALATE" in resp
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
认知状态 (CognitiveState)
|
|
3
|
+
|
|
4
|
+
把"系统此刻在想什么"显式建模成一个可快照的认知状态。
|
|
5
|
+
借鉴 Codex 版 Foxclaw 的 CognitiveState,适配本项目:不新存一份数据,而是
|
|
6
|
+
作为聚合器 + 事件订阅者,把散落在调度器/进程/记忆/路由里的信号,聚成一张
|
|
7
|
+
"系统当前心智状态"快照。
|
|
8
|
+
|
|
9
|
+
对齐"认知操作系统"定位:OS 有 system state,认知 OS 该有 cognitive state。
|
|
10
|
+
可驱动前端"系统在想什么"实时面板,也用于调试与回放。
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
import time
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class CognitiveState:
|
|
19
|
+
"""系统当前认知状态快照。随事件总线上的活动自动更新。"""
|
|
20
|
+
|
|
21
|
+
active_goal: str = None # 当前活跃目标(最近任务)
|
|
22
|
+
active_agent: str = None # 当前处理的 Agent
|
|
23
|
+
semantic_focus: str = None # 语义焦点(调度意图类别)
|
|
24
|
+
attention_scope: list = field(default_factory=list) # 注意力范围(关键词)
|
|
25
|
+
reasoning_depth: float = 0.0 # 当前推理深度
|
|
26
|
+
confidence_map: dict = field(default_factory=dict) # 置信度图(意图/优先级)
|
|
27
|
+
activated_memories: list = field(default_factory=list) # 激活的记忆(最近召回/沉淀)
|
|
28
|
+
last_route: str = None # 最近路由倾向
|
|
29
|
+
active_processes: int = 0 # 活跃进程数
|
|
30
|
+
temporal_window: str = "current_turn" # 时间窗口
|
|
31
|
+
updated_at: float = field(default_factory=time.time)
|
|
32
|
+
|
|
33
|
+
# ── 信号摄入(由事件驱动)──
|
|
34
|
+
|
|
35
|
+
def on_execution(self, payload: dict):
|
|
36
|
+
"""执行事件 → 更新目标/Agent/注意力。"""
|
|
37
|
+
payload = payload or {}
|
|
38
|
+
task = payload.get("task", "")
|
|
39
|
+
self.active_goal = task[:120] if task else self.active_goal
|
|
40
|
+
self.active_agent = payload.get("agent_id") or self.active_agent
|
|
41
|
+
self.attention_scope = self._keywords(task)
|
|
42
|
+
sched = payload.get("schedule") or {}
|
|
43
|
+
if sched:
|
|
44
|
+
self.semantic_focus = sched.get("intent")
|
|
45
|
+
self.last_route = sched.get("route_hint")
|
|
46
|
+
self.confidence_map["priority"] = sched.get("priority", 0.0)
|
|
47
|
+
self.temporal_window = "current_turn"
|
|
48
|
+
self.updated_at = time.time()
|
|
49
|
+
|
|
50
|
+
def on_memory_updated(self, payload: dict):
|
|
51
|
+
"""记忆更新事件 → 记录激活的记忆(去重,保留最近 5 条)。"""
|
|
52
|
+
payload = payload or {}
|
|
53
|
+
mu = payload.get("memory_update") or {}
|
|
54
|
+
content = mu.get("content")
|
|
55
|
+
if content:
|
|
56
|
+
snippet = content[:80]
|
|
57
|
+
# 去重:已存在则先移除旧的,再插到最前(LRU 式)
|
|
58
|
+
existing = [m for m in self.activated_memories if m != snippet]
|
|
59
|
+
self.activated_memories = [snippet] + existing[:4]
|
|
60
|
+
self.temporal_window = "memory_augmented"
|
|
61
|
+
self.updated_at = time.time()
|
|
62
|
+
|
|
63
|
+
def on_process_change(self, payload: dict, active_count: int = None):
|
|
64
|
+
"""进程状态变更 → 更新活跃进程数。"""
|
|
65
|
+
if active_count is not None:
|
|
66
|
+
self.active_processes = active_count
|
|
67
|
+
self.updated_at = time.time()
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def _keywords(text: str, n: int = 6) -> list:
|
|
71
|
+
import re
|
|
72
|
+
# 先按标点/空格切;若切出的片段仍很长(中文无空格),按 2-4 字滑窗取要点
|
|
73
|
+
rough = [w for w in re.split(r"[\s,,、。!?:;]", text or "") if len(w) >= 2]
|
|
74
|
+
out = []
|
|
75
|
+
for seg in rough:
|
|
76
|
+
if len(seg) <= 4:
|
|
77
|
+
out.append(seg)
|
|
78
|
+
else:
|
|
79
|
+
# 长片段取首尾 + 中部双字,作为注意力要点
|
|
80
|
+
out.append(seg[:3])
|
|
81
|
+
if len(seg) > 6:
|
|
82
|
+
out.append(seg[len(seg)//2:len(seg)//2+2])
|
|
83
|
+
out.append(seg[-2:])
|
|
84
|
+
return out[:n]
|
|
85
|
+
|
|
86
|
+
def snapshot(self) -> dict:
|
|
87
|
+
return {
|
|
88
|
+
"active_goal": self.active_goal,
|
|
89
|
+
"active_agent": self.active_agent,
|
|
90
|
+
"semantic_focus": self.semantic_focus,
|
|
91
|
+
"attention_scope": list(self.attention_scope),
|
|
92
|
+
"reasoning_depth": self.reasoning_depth,
|
|
93
|
+
"confidence_map": dict(self.confidence_map),
|
|
94
|
+
"activated_memories": list(self.activated_memories),
|
|
95
|
+
"last_route": self.last_route,
|
|
96
|
+
"active_processes": self.active_processes,
|
|
97
|
+
"temporal_window": self.temporal_window,
|
|
98
|
+
"updated_at": self.updated_at,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class CognitiveStateTracker:
|
|
103
|
+
"""把 CognitiveState 接到事件总线 + 进程管理器,自动维护快照。"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, bus, process_manager=None):
|
|
106
|
+
import threading
|
|
107
|
+
self.state = CognitiveState()
|
|
108
|
+
self.process_manager = process_manager
|
|
109
|
+
self._lock = threading.Lock() # 事件可能跨线程触发,保护状态更新
|
|
110
|
+
if bus is not None:
|
|
111
|
+
# 【P4-1】前两个是精确 channel 名 → exact=True;task.lifecycle 是刻意的前缀
|
|
112
|
+
# 订阅(收 created/queued/running/... 一族),保持默认前缀匹配。
|
|
113
|
+
bus.subscribe("execution.completed", self._on_exec, exact=True)
|
|
114
|
+
bus.subscribe("memory.updated", self._on_mem, exact=True)
|
|
115
|
+
bus.subscribe("task.lifecycle", self._on_proc) # Step3:统一生命周期词汇(前缀匹配)
|
|
116
|
+
|
|
117
|
+
def _on_exec(self, msg):
|
|
118
|
+
if msg is None:
|
|
119
|
+
return
|
|
120
|
+
with self._lock:
|
|
121
|
+
self.state.on_execution(getattr(msg, "payload", None))
|
|
122
|
+
|
|
123
|
+
def _on_mem(self, msg):
|
|
124
|
+
if msg is None:
|
|
125
|
+
return
|
|
126
|
+
with self._lock:
|
|
127
|
+
self.state.on_memory_updated(getattr(msg, "payload", None))
|
|
128
|
+
|
|
129
|
+
def _on_proc(self, msg):
|
|
130
|
+
if msg is None:
|
|
131
|
+
return
|
|
132
|
+
active = None
|
|
133
|
+
pm = self.process_manager
|
|
134
|
+
if pm is not None:
|
|
135
|
+
# 【深审优化】用 pm 的 O(1) active_count(),免去每次 lifecycle 事件 stats() 遍历
|
|
136
|
+
# 全部进程(O(n))。旧 pm 无此方法则回退到 stats()(scheduled+running)。
|
|
137
|
+
if hasattr(pm, "active_count"):
|
|
138
|
+
active = pm.active_count()
|
|
139
|
+
else:
|
|
140
|
+
st = pm.stats().get("by_state", {})
|
|
141
|
+
active = st.get("scheduled", 0) + st.get("running", 0)
|
|
142
|
+
with self._lock:
|
|
143
|
+
self.state.on_process_change(getattr(msg, "payload", None), active)
|
|
144
|
+
|
|
145
|
+
def snapshot(self) -> dict:
|
|
146
|
+
with self._lock:
|
|
147
|
+
return self.state.snapshot()
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""审计 JSONL 的哈希链:让「改一笔、删一笔」这类事后篡改可被检测。
|
|
2
|
+
|
|
3
|
+
为什么需要
|
|
4
|
+
──────────
|
|
5
|
+
`{data_dir}/audit/*.jsonl` 是纯 append 的明文文件:谁能写这个文件,谁就能改历史
|
|
6
|
+
(把 `allowed=false` 改成 `true`、删掉某次 deny)。审计的价值建立在「事后还能信」上,
|
|
7
|
+
所以每行补两个字段:
|
|
8
|
+
|
|
9
|
+
prev_hash = 上一条的 hash(首条为 GENESIS)
|
|
10
|
+
hash = sha256(canonical(除 hash 外的整行))
|
|
11
|
+
|
|
12
|
+
`verify()` 逐行重算,任何一行被改(hash 不匹配)或被删(后继 prev_hash 断)都会暴露。
|
|
13
|
+
|
|
14
|
+
适用边界(别高估它)
|
|
15
|
+
──────────────────
|
|
16
|
+
· 防**零散篡改**,不防「整文件重算链」——攻击者若能连改 N 行并重算全部 hash,链自洽。
|
|
17
|
+
真要防这个,得把 tail hash 锚到外部(另一台机器 / 时间戳服务);本机单进程场景
|
|
18
|
+
收益不抵复杂度。
|
|
19
|
+
· 删**最后一条**检测不到:没有后继记录指向它。删中间、改任意行都能发现。
|
|
20
|
+
· 老文件(该行无 hash 字段)按 legacy 段跳过校验,不会因为历史数据报错;
|
|
21
|
+
一旦出现过 hash,后面的行就必须接上。
|
|
22
|
+
· 链是**单进程假设**(与两个 sink 的锁一致)。多进程各自 append 会让 prev_hash 冲突,
|
|
23
|
+
表现为 verify 报链断——部署上应避免同目录多进程写审计。
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
import json
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
#: 首条记录的 prev_hash。用 64 个 0 而不是空串:一眼能看出「这是链头」。
|
|
32
|
+
GENESIS = "0" * 64
|
|
33
|
+
|
|
34
|
+
#: 从文件尾部回读的字节数:找 tail hash 只需最后一行,64KB 足够容下超长记录。
|
|
35
|
+
_TAIL_WINDOW = 65536
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _canonical(rec: dict) -> bytes:
|
|
39
|
+
"""稳定序列化:sort_keys + 紧凑分隔符,保证跨进程/跨版本重算一致。"""
|
|
40
|
+
return json.dumps(rec, sort_keys=True, separators=(",", ":"),
|
|
41
|
+
ensure_ascii=False, default=str).encode("utf-8")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def link(rec: dict, prev_hash: str = GENESIS) -> dict:
|
|
45
|
+
"""给一行记录接上链,返回**新** dict(不改入参,便于调用方继续用原始 rec)。"""
|
|
46
|
+
body = dict(rec)
|
|
47
|
+
body["prev_hash"] = prev_hash
|
|
48
|
+
body["hash"] = hashlib.sha256(_canonical(body)).hexdigest()
|
|
49
|
+
return body
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def tail_hash(path) -> str:
|
|
53
|
+
"""读文件最后一条带 hash 的记录的 hash;不存在/空/无链 → GENESIS。
|
|
54
|
+
|
|
55
|
+
倒着找而不是顺着读:审计文件可能很长,重启续链只需要最后一行;
|
|
56
|
+
尾部若有写一半的残行(进程被杀),跳过继续往前找。
|
|
57
|
+
"""
|
|
58
|
+
p = Path(path)
|
|
59
|
+
try:
|
|
60
|
+
with open(p, "rb") as f:
|
|
61
|
+
f.seek(0, 2)
|
|
62
|
+
size = f.tell()
|
|
63
|
+
f.seek(max(0, size - _TAIL_WINDOW))
|
|
64
|
+
chunk = f.read().decode("utf-8", "replace")
|
|
65
|
+
except OSError:
|
|
66
|
+
return GENESIS
|
|
67
|
+
for line in reversed(chunk.splitlines()):
|
|
68
|
+
line = line.strip()
|
|
69
|
+
if not line:
|
|
70
|
+
continue
|
|
71
|
+
try:
|
|
72
|
+
rec = json.loads(line)
|
|
73
|
+
except Exception:
|
|
74
|
+
continue
|
|
75
|
+
h = rec.get("hash")
|
|
76
|
+
if isinstance(h, str) and h:
|
|
77
|
+
return h
|
|
78
|
+
return GENESIS
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def verify(path) -> tuple:
|
|
82
|
+
"""校验整条链 → (ok, 已校验条数, 问题描述)。
|
|
83
|
+
|
|
84
|
+
问题描述为空串表示通过;不通过时给出「第几行 + 断在哪」,便于人工定位。
|
|
85
|
+
"""
|
|
86
|
+
p = Path(path)
|
|
87
|
+
if not p.exists():
|
|
88
|
+
return True, 0, ""
|
|
89
|
+
prev = GENESIS
|
|
90
|
+
n = 0
|
|
91
|
+
try:
|
|
92
|
+
with open(p, encoding="utf-8") as f:
|
|
93
|
+
for i, line in enumerate(f, 1):
|
|
94
|
+
line = line.strip()
|
|
95
|
+
if not line:
|
|
96
|
+
continue
|
|
97
|
+
try:
|
|
98
|
+
rec = json.loads(line)
|
|
99
|
+
except Exception:
|
|
100
|
+
return False, n, f"第 {i} 行不是合法 JSON"
|
|
101
|
+
h = rec.get("hash")
|
|
102
|
+
if not h:
|
|
103
|
+
continue # legacy 段:无链可验,跳过
|
|
104
|
+
if rec.get("prev_hash") != prev:
|
|
105
|
+
return False, n, (f"第 {i} 行链断:prev_hash 与上一条 hash 不符"
|
|
106
|
+
"(中间记录被删或被挪动)")
|
|
107
|
+
body = {k: v for k, v in rec.items() if k != "hash"}
|
|
108
|
+
if hashlib.sha256(_canonical(body)).hexdigest() != h:
|
|
109
|
+
return False, n, f"第 {i} 行内容被改(hash 不匹配)"
|
|
110
|
+
prev = h
|
|
111
|
+
n += 1
|
|
112
|
+
except OSError as e:
|
|
113
|
+
return False, n, f"读取失败: {e}"
|
|
114
|
+
return True, n, ""
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _main(argv: list) -> int:
|
|
118
|
+
if not argv:
|
|
119
|
+
print("用法: python -m foxclaw.core.audit_chain <audit.jsonl> [...]")
|
|
120
|
+
return 2
|
|
121
|
+
bad = 0
|
|
122
|
+
for p in argv:
|
|
123
|
+
ok, n, msg = verify(p)
|
|
124
|
+
print(f"{p}: {'OK' if ok else 'BROKEN'} ({n} 条)" + (f" - {msg}" if msg else ""))
|
|
125
|
+
if not ok:
|
|
126
|
+
bad = 1
|
|
127
|
+
return bad
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
import sys
|
|
132
|
+
|
|
133
|
+
raise SystemExit(_main(sys.argv[1:]))
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
共享层写入审计落盘 sink —— P0-3 配套
|
|
3
|
+
|
|
4
|
+
为什么必须落盘
|
|
5
|
+
──────────────
|
|
6
|
+
EventSpine._history 是 deque(maxlen=1000) 的**进程内环形缓冲**,且全系统事件共享:
|
|
7
|
+
高频事件(agent.cognition.* / knowledge.created)会很快把 memory.shared.write 冲掉,
|
|
8
|
+
进程退出即全丢。而 P0-3 切 enforce 的判据恰恰是「真实流量中有没有**合法的**写入被拒」
|
|
9
|
+
—— 没有落盘,这个样本永远积累不起来(总不能让人蹲守 /api/bus/history),策略表就
|
|
10
|
+
只能停在「自洽的假设」上,灰度推进(audit→warn→enforce)没有事实依据。
|
|
11
|
+
|
|
12
|
+
行为
|
|
13
|
+
────
|
|
14
|
+
订阅 EventSpine 的 memory.shared.write 主题,逐条 JSON 追加到:
|
|
15
|
+
{data_dir}/audit/shared_write.jsonl
|
|
16
|
+
每行:{"ts": ISO 时间, "topic": ..., "correlation_id": ..., "payload": {原始审计字段}}
|
|
17
|
+
payload 即 guard_shared_write 发布的全部字段(agent_id/type/tags/allowed/reason/mode/
|
|
18
|
+
content_sha/content_preview)——正文只留 60 字符预览 + 16 位哈希,不落敏感全文。
|
|
19
|
+
|
|
20
|
+
写入失败一律静默吞掉 —— **审计绝不影响写入主流程**(与 guard_shared_write 同一设计约束)。
|
|
21
|
+
|
|
22
|
+
开关:FOXCLAW_SHARED_WRITE_AUDIT=0 关闭(默认开;每条 <300 字节,量级可忽略)。
|
|
23
|
+
判读:用 read() 或直接看文件 —— allowed=false 的每一行都要逐条判读
|
|
24
|
+
(是真越界,还是策略表误伤),全部为真越界且无一条误伤,方可切 enforce。
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import threading
|
|
32
|
+
from datetime import datetime
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
from . import audit_chain
|
|
36
|
+
|
|
37
|
+
ENV_SWITCH = "FOXCLAW_SHARED_WRITE_AUDIT"
|
|
38
|
+
AUDIT_DIR = "audit"
|
|
39
|
+
AUDIT_FILE = "shared_write.jsonl"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class SharedWriteAuditSink:
|
|
43
|
+
"""把 memory.shared.write 审计事件追加落盘。
|
|
44
|
+
|
|
45
|
+
线程安全:EventSpine 的 handler 在发布线程、锁外被调用(脊柱只锁快照),
|
|
46
|
+
并发写入同一文件必须串行 —— 用进程内锁。多进程部署(FOXCLAW_ALLOW_INSECURE
|
|
47
|
+
反对但允许)各自 append 行,JSONL 每行自包含,不会互相破坏。
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, path: Path):
|
|
51
|
+
self.path = Path(path)
|
|
52
|
+
self._lock = threading.Lock()
|
|
53
|
+
self._last_hash: str | None = None # 链尾,首次写时从文件尾部补齐
|
|
54
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
|
|
56
|
+
def __call__(self, evt): # EventSpine.subscribe 的 handler 形态:handler(event)
|
|
57
|
+
try:
|
|
58
|
+
rec = {
|
|
59
|
+
"ts": datetime.now().isoformat(timespec="seconds"),
|
|
60
|
+
"topic": evt.topic,
|
|
61
|
+
"correlation_id": getattr(evt, "correlation_id", ""),
|
|
62
|
+
"payload": evt.payload,
|
|
63
|
+
}
|
|
64
|
+
with self._lock:
|
|
65
|
+
if self._last_hash is None:
|
|
66
|
+
self._last_hash = audit_chain.tail_hash(self.path)
|
|
67
|
+
rec = audit_chain.link(rec, self._last_hash)
|
|
68
|
+
with open(self.path, "a", encoding="utf-8") as f:
|
|
69
|
+
f.write(json.dumps(rec, ensure_ascii=False, default=str) + "\n")
|
|
70
|
+
self._last_hash = rec["hash"]
|
|
71
|
+
except Exception:
|
|
72
|
+
pass # 审计失败绝不影响写入主流程
|
|
73
|
+
|
|
74
|
+
def read(self, limit: int = 200) -> list:
|
|
75
|
+
"""读最近 limit 条(供诊断/工具用)。坏行跳过 —— 落盘绝不该反过来卡死读取。"""
|
|
76
|
+
try:
|
|
77
|
+
with open(self.path, encoding="utf-8") as f:
|
|
78
|
+
lines = f.readlines()
|
|
79
|
+
except OSError:
|
|
80
|
+
return []
|
|
81
|
+
out = []
|
|
82
|
+
for ln in lines[-limit:]:
|
|
83
|
+
try:
|
|
84
|
+
out.append(json.loads(ln))
|
|
85
|
+
except Exception:
|
|
86
|
+
continue
|
|
87
|
+
return out
|
|
88
|
+
|
|
89
|
+
def verify(self) -> tuple:
|
|
90
|
+
"""校验落盘链 → (ok, 条数, 问题)。"""
|
|
91
|
+
return audit_chain.verify(self.path)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def attach(spine, data_dir):
|
|
95
|
+
"""把 sink 挂到脊柱,返回 sink 实例;FOXCLAW_SHARED_WRITE_AUDIT=0 时不挂(返回 None)。"""
|
|
96
|
+
if str(os.environ.get(ENV_SWITCH, "")).strip() == "0":
|
|
97
|
+
return None
|
|
98
|
+
sink = SharedWriteAuditSink(Path(data_dir) / AUDIT_DIR / AUDIT_FILE)
|
|
99
|
+
spine.subscribe("memory.shared.write", sink)
|
|
100
|
+
return sink
|