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,151 @@
|
|
|
1
|
+
"""失败策略表:把「工具失败了」映射成「下一步做什么」。
|
|
2
|
+
|
|
3
|
+
病根:工具轮的失败此前只有两种归宿——原样回注给模型,或直接丢掉。于是「超时了该重试
|
|
4
|
+
一次还是放弃」「没配搜索服务该降级还是换参数」全靠模型临场发挥;而模型最常见的反应是
|
|
5
|
+
**换个说法把没做成的事说成做成了**(静默假成功)。这张表把那类决策从模型手里拿走:
|
|
6
|
+
错误特征 → 动作,查表即得,和 recover 层「由策略表决定,不猜」同一原则。
|
|
7
|
+
|
|
8
|
+
设计取舍:
|
|
9
|
+
- **按文本特征分类,不按异常类型**。工具跑在守护线程里(见 agents/mixins/tool_exec),
|
|
10
|
+
跨线程、跨工具之后剩下的只有一句回注文本;异常类型在这里已经丢失。
|
|
11
|
+
- **只有只读工具才自动重试**。写/发送/支付类动作重试一次 = 可能重复扣款、重复发消息;
|
|
12
|
+
这类一律交人。
|
|
13
|
+
- 分类是**启发式**的,因此:匹配不到 → 当作未知失败(不重试、交人),绝不乐观当成功。
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
|
|
19
|
+
# ── 失败类型 ────────────────────────────────────────────────────────────
|
|
20
|
+
OK = "ok"
|
|
21
|
+
TIMEOUT = "timeout" # 单工具硬超时(工具轮主动放弃)
|
|
22
|
+
TRANSIENT = "transient" # 网络抖动/限流/5xx —— 重试有意义
|
|
23
|
+
NOT_CONFIGURED = "not_configured" # 没配 key/服务 —— 重试一万次也没用
|
|
24
|
+
EMPTY = "empty" # 调通了但没内容(搜不到/没召回)
|
|
25
|
+
INVALID_ARGS = "invalid_args" # 模型给的参数不合法 —— 让模型自己改
|
|
26
|
+
DENIED = "denied" # 策略禁用/未知工具 —— 不许绕过
|
|
27
|
+
NEEDS_HUMAN = "needs_human" # 红线动作需确认 —— 只能人来做
|
|
28
|
+
EXCEPTION = "exception" # 工具内部抛异常
|
|
29
|
+
UNKNOWN = "unknown" # 认不出来:保守(不重试、交人)
|
|
30
|
+
|
|
31
|
+
#: 动作
|
|
32
|
+
NONE = "none"
|
|
33
|
+
RETRY = "retry" # 自动再试一次(仅只读)
|
|
34
|
+
REPARAM = "reparam" # 让模型换参数/换查询词
|
|
35
|
+
DEGRADE = "degrade" # 降级如实告知,不重试
|
|
36
|
+
HAND_TO_HUMAN = "hand_to_human" # 交人
|
|
37
|
+
STOP = "stop" # 终止,不许再试
|
|
38
|
+
|
|
39
|
+
#: 特征表。**顺序敏感**:先命中先返回。顺序上的两个坑都踩过:
|
|
40
|
+
#: · "未返回结果"(empty)不能先被 "未返回"(timeout) 吃掉;
|
|
41
|
+
#: · "不合法"(invalid_args)要在 "拒绝"(denied) 之前,否则参数错误被当成越权。
|
|
42
|
+
_RULES: tuple[tuple[str, tuple[str, ...]], ...] = (
|
|
43
|
+
(DENIED, ("已被策略禁用", "被权限策略拒绝", "未知工具")),
|
|
44
|
+
(NEEDS_HUMAN, ("需确认的动作", "需要你确认", "请用户确认", "只能由你亲自确认")),
|
|
45
|
+
(INVALID_ARGS, ("入参不合法", "缺少必填", "类型应为")),
|
|
46
|
+
(TIMEOUT, ("超过", "超时", "timed out", "timeout")),
|
|
47
|
+
(NOT_CONFIGURED, ("未配置", "not configured", "未设置", "缺少 api", "缺少 key")),
|
|
48
|
+
(EMPTY, ("未返回结果", "未召回到", "no result", "未命中", "没有找到")),
|
|
49
|
+
(TRANSIENT, ("网络", "连接失败", "connection", "502", "503", "504", "429", "rate limit")),
|
|
50
|
+
(EXCEPTION, ("执行异常",)),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
#: 类型 → (动作, 是否可重试, 给模型的一句硬提示)。提示写进回注文本,模型没法装作没看见。
|
|
54
|
+
_ACTIONS: dict[str, tuple[str, bool, str]] = {
|
|
55
|
+
TIMEOUT: (RETRY, True,
|
|
56
|
+
"这一步超时未完成,已自动重试一次仍有问题。请如实说明这一步没做成,不要编造结果。"),
|
|
57
|
+
TRANSIENT: (RETRY, True,
|
|
58
|
+
"这次失败像是临时的网络/服务问题,已自动重试一次。若仍失败,如实说明当前无法获取。"),
|
|
59
|
+
EXCEPTION: (RETRY, True,
|
|
60
|
+
"工具内部异常,已自动重试一次。若仍失败,如实说明该步骤未能执行。"),
|
|
61
|
+
EMPTY: (REPARAM, False,
|
|
62
|
+
"调用成功但没有拿到内容。可以换一个更具体的查询词或放宽条件再试一次;"
|
|
63
|
+
"仍为空就如实回答「没有查到」,不要用常识补全成确定结论。"),
|
|
64
|
+
INVALID_ARGS: (REPARAM, False,
|
|
65
|
+
"参数不合法,这一步没有执行。请按工具声明修正参数后再调用;"
|
|
66
|
+
"不要把失败当成已完成。"),
|
|
67
|
+
NOT_CONFIGURED: (DEGRADE, False,
|
|
68
|
+
"该能力当前未配置(服务/密钥缺失),不是暂时故障,重试无用。"
|
|
69
|
+
"请告知用户这项能力暂不可用,不要假装查到了。"),
|
|
70
|
+
NEEDS_HUMAN: (HAND_TO_HUMAN, False,
|
|
71
|
+
"这是需要用户确认的动作,我没有执行。把要做的操作拟成草稿交用户确认。"),
|
|
72
|
+
DENIED: (STOP, False, "该工具被策略禁止,不要换个名字或换个参数再试。"),
|
|
73
|
+
UNKNOWN: (HAND_TO_HUMAN, False,
|
|
74
|
+
"这一步的结果不可信(无法判定成功还是失败)。不要向用户断言它已完成。"),
|
|
75
|
+
OK: (NONE, False, ""),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(frozen=True)
|
|
80
|
+
class Decision:
|
|
81
|
+
"""一次失败判定:类型 → 动作 + 给模型看的提示。"""
|
|
82
|
+
|
|
83
|
+
kind: str
|
|
84
|
+
action: str
|
|
85
|
+
retryable: bool
|
|
86
|
+
hint: str
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def is_ok(self) -> bool:
|
|
90
|
+
return self.kind == OK
|
|
91
|
+
|
|
92
|
+
def to_dict(self) -> dict:
|
|
93
|
+
return {"kind": self.kind, "action": self.action,
|
|
94
|
+
"retryable": self.retryable, "hint": self.hint}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
#: 失败回注的形态标记。工具执行侧的失败文本统一以 "(" 开头(见 tool_exec 的兜底),
|
|
98
|
+
#: 另有少数异常文本不带括号。**不能**用"含 未/失败 就算失败"这种宽松判据:
|
|
99
|
+
#: 搜索正文里"未来""失败案例"都会误伤,把成功结果标成失败。
|
|
100
|
+
_FAILURE_MARKERS = ("执行异常", "入参不合法", "被权限策略拒绝", "缺少必填")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def looks_like_failure(text: str) -> bool:
|
|
104
|
+
t = (text or "").strip()
|
|
105
|
+
if not t:
|
|
106
|
+
return True
|
|
107
|
+
return t.startswith("(") or any(m in t for m in _FAILURE_MARKERS)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def classify(text: str) -> str:
|
|
111
|
+
"""把回注文本归类:``ok`` / 具体失败类型 / ``unknown``(认不出来,保守交人)。"""
|
|
112
|
+
t = (text or "").strip()
|
|
113
|
+
if not t:
|
|
114
|
+
return UNKNOWN # 空结果:不能当成功,也不确定成因
|
|
115
|
+
if not looks_like_failure(t):
|
|
116
|
+
return OK
|
|
117
|
+
low = t.lower()
|
|
118
|
+
for kind, needles in _RULES:
|
|
119
|
+
if any(n in low for n in needles):
|
|
120
|
+
return kind
|
|
121
|
+
return UNKNOWN
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def decide(text: str, tool: str = "", capability: dict | None = None,
|
|
125
|
+
attempt: int = 1, max_attempts: int = 2) -> Decision:
|
|
126
|
+
"""查表得到动作。
|
|
127
|
+
|
|
128
|
+
``attempt`` 是**已尝试次数**(第一次调用传 1)。可重试类型在 ``attempt < max_attempts``
|
|
129
|
+
时给 ``retry``;只读工具才真的会被重试 —— 这里只判定,是否执行由调用方决定,
|
|
130
|
+
因为"这个工具能不能安全重复执行"属于能力声明(capability),不是文本语义。
|
|
131
|
+
"""
|
|
132
|
+
kind = classify(text)
|
|
133
|
+
action, retryable, hint = _ACTIONS.get(kind, _ACTIONS[UNKNOWN])
|
|
134
|
+
read_only = bool((capability or {}).get("read_only"))
|
|
135
|
+
if action == RETRY and (not read_only or attempt >= max_attempts):
|
|
136
|
+
action = HAND_TO_HUMAN
|
|
137
|
+
if not read_only:
|
|
138
|
+
hint = ("这一步有副作用,重试可能重复执行,因此没有自动重试。"
|
|
139
|
+
"请如实说明它没有完成。")
|
|
140
|
+
return Decision(kind=kind, action=action, retryable=retryable, hint=hint)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def annotate(text: str, decision: Decision) -> str:
|
|
144
|
+
"""把动作提示拼到回注文本后面(仅失败时;成功文本一个字都不改)。
|
|
145
|
+
|
|
146
|
+
为什么要拼在文本里而不是另走一路:回注给模型的是**纯文本**,多一条通道意味着
|
|
147
|
+
模型可能只读到其中一条;拼在末尾让"结果"和"该怎么对待这个结果"不可分割。
|
|
148
|
+
"""
|
|
149
|
+
if decision.is_ok or not decision.hint:
|
|
150
|
+
return text or ""
|
|
151
|
+
return f"{text or ''}\n[执行策略] {decision.hint}"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
统一 API 网关限流(gateway.py)
|
|
3
|
+
|
|
4
|
+
把散落在各处(MCP、/chat/stream)的单点限流收敛成一层**统一网关**:
|
|
5
|
+
· per-client 令牌桶——突发限流(每分钟额度,允许瞬时打满桶)
|
|
6
|
+
· 固定窗口配额——持续限流(每小时总额度)
|
|
7
|
+
· 空闲客户端淘汰——防攻击者轮换 key/IP 把 per-client 字典撑爆内存(与 metrics「unmatched」同款教训)
|
|
8
|
+
|
|
9
|
+
client 身份 = X-API-Key(有则)否则来源 IP。线程安全(单锁,check 内无 await/无阻塞 I/O)。
|
|
10
|
+
默认宽松:本地单用户近乎无感;对外暴露时经环境变量收紧。
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import time
|
|
15
|
+
import threading
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Gateway:
|
|
19
|
+
def __init__(self, rate_per_min: int = 600, quota_per_hour: int = 20000,
|
|
20
|
+
max_clients: int = 4096):
|
|
21
|
+
self.rate = max(1, int(rate_per_min))
|
|
22
|
+
self.capacity = float(self.rate) # 桶容量 = 每分钟额度(允许突发到满桶)
|
|
23
|
+
self.refill = self.capacity / 60.0 # 令牌/秒
|
|
24
|
+
self.quota = max(1, int(quota_per_hour))
|
|
25
|
+
self.max_clients = max(64, int(max_clients))
|
|
26
|
+
self._lock = threading.Lock()
|
|
27
|
+
self._buckets: dict = {} # client -> [tokens, last_ts]
|
|
28
|
+
self._quota: dict = {} # client -> [count, window_start]
|
|
29
|
+
self._seen: dict = {} # client -> last_ts(用于空闲淘汰)
|
|
30
|
+
self.rejected = 0 # 累计拒绝数(可观测)
|
|
31
|
+
|
|
32
|
+
def check(self, client: str):
|
|
33
|
+
"""判定一次请求。返回 (allowed: bool, retry_after: int, reason: str)。
|
|
34
|
+
reason ∈ {'', 'rate', 'quota'}。allowed=False 时 retry_after 为建议重试秒数。"""
|
|
35
|
+
now = time.time()
|
|
36
|
+
with self._lock:
|
|
37
|
+
self._evict_locked(now)
|
|
38
|
+
self._seen[client] = now
|
|
39
|
+
|
|
40
|
+
# ① 配额(固定 1 小时窗口)
|
|
41
|
+
q = self._quota.get(client)
|
|
42
|
+
if q is None or now - q[1] >= 3600.0:
|
|
43
|
+
q = [0, now]
|
|
44
|
+
self._quota[client] = q
|
|
45
|
+
if q[0] >= self.quota:
|
|
46
|
+
self.rejected += 1
|
|
47
|
+
return False, int(3600.0 - (now - q[1])) + 1, "quota"
|
|
48
|
+
|
|
49
|
+
# ② 令牌桶(突发限流)
|
|
50
|
+
b = self._buckets.get(client)
|
|
51
|
+
if b is None:
|
|
52
|
+
b = [self.capacity, now]
|
|
53
|
+
self._buckets[client] = b
|
|
54
|
+
b[0] = min(self.capacity, b[0] + (now - b[1]) * self.refill)
|
|
55
|
+
b[1] = now
|
|
56
|
+
if b[0] < 1.0:
|
|
57
|
+
self.rejected += 1
|
|
58
|
+
return False, int((1.0 - b[0]) / self.refill) + 1, "rate"
|
|
59
|
+
|
|
60
|
+
# 放行:扣 1 令牌 + 计 1 配额
|
|
61
|
+
b[0] -= 1.0
|
|
62
|
+
q[0] += 1
|
|
63
|
+
return True, 0, ""
|
|
64
|
+
|
|
65
|
+
def _evict_locked(self, now: float):
|
|
66
|
+
"""超过 max_clients 时淘汰最久未见的一批(降到 ~90%)。必须在锁内调用。"""
|
|
67
|
+
if len(self._seen) <= self.max_clients:
|
|
68
|
+
return
|
|
69
|
+
n_drop = len(self._seen) - self.max_clients + max(1, self.max_clients // 10)
|
|
70
|
+
# 按 last_ts 升序取最旧的 n_drop 个
|
|
71
|
+
victims = sorted(self._seen.items(), key=lambda kv: kv[1])[:n_drop]
|
|
72
|
+
for c, _ in victims:
|
|
73
|
+
self._seen.pop(c, None)
|
|
74
|
+
self._buckets.pop(c, None)
|
|
75
|
+
self._quota.pop(c, None)
|
|
76
|
+
|
|
77
|
+
def stats(self) -> dict:
|
|
78
|
+
with self._lock:
|
|
79
|
+
return {
|
|
80
|
+
"tracked_clients": len(self._seen),
|
|
81
|
+
"rate_per_min": self.rate,
|
|
82
|
+
"quota_per_hour": self.quota,
|
|
83
|
+
"max_clients": self.max_clients,
|
|
84
|
+
"rejected_total": self.rejected,
|
|
85
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
治理决策统一审计(P1-2 基础 / P3-4 decision_id)。
|
|
3
|
+
|
|
4
|
+
系统的裁决点分散在四层,各说各话:
|
|
5
|
+
· Harness(推理漂移:语义/工具/范围)—— emit harness_pass/warn/block
|
|
6
|
+
· Permission(动作权限:deny>ask>allow 四阶段)—— 经 PolicyGate emit policy.decision.*
|
|
7
|
+
· Orchestrator(跨 Agent 委派裁决)—— 原本**无任何事件**
|
|
8
|
+
· SkillAdapter(执行终态:成功/失败/断路器/封杀)—— 原本**无任何事件**
|
|
9
|
+
|
|
10
|
+
本模块提供一条**统一治理审计流**,不改变任何裁决行为,只做记录:
|
|
11
|
+
|
|
12
|
+
事件通道:脊柱广播 `governance.decision`(不在 WS 静音列表 → 前端可见)
|
|
13
|
+
落盘通道:{data_dir}/audit/governance.jsonl(环形缓冲之外的持久样本)
|
|
14
|
+
|
|
15
|
+
每条记录带 **decision_id**(本次裁决的唯一 ID)与 **correlation_id**(跨层关联键):
|
|
16
|
+
· permission 裁决生成 decision_id → 同一 SkillAdapter.invoke 的执行审计用它做
|
|
17
|
+
correlation_id ⇒ 「谁批准的这个动作、执行结果如何」可join
|
|
18
|
+
· Orchestrator 用 req.id 做 correlation_id ⇒ 多轮协商的历次裁决串成一条链
|
|
19
|
+
|
|
20
|
+
verdict 词汇(两类,查询时注意区分):
|
|
21
|
+
· 裁决类:allow / warn / deny / confirm(与 PolicyGate 归一词汇一致)
|
|
22
|
+
· 执行类:success / failed(SkillAdapter 的执行终态)
|
|
23
|
+
|
|
24
|
+
记录策略(防止高频正常路径淹没审计流):
|
|
25
|
+
· harness / permission 层只记**非 allow**(warn/deny/confirm)——PASS 是每步推理的常态
|
|
26
|
+
· orchestration 层全量(跨 Agent 委派本来就低频)
|
|
27
|
+
· skill 层全量(执行审计是 P2-0 的核心价值;一次任务仅个位数调用)
|
|
28
|
+
|
|
29
|
+
与 shared_write 审计(audit_sink.py)的关系:那是「记忆写入准入」专项,这是「治理裁决」
|
|
30
|
+
总流;两者并存、各自落盘,互不替代。
|
|
31
|
+
|
|
32
|
+
开关:FOXCLAW_GOVERNANCE_AUDIT=0 关闭(默认开;每条 <500 字节,量级可忽略)。
|
|
33
|
+
审计一律 fail-safe:任何记录异常都静默吞掉,绝不影响裁决主流程(与闸门同约束)。
|
|
34
|
+
"""
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import json
|
|
38
|
+
import os
|
|
39
|
+
import threading
|
|
40
|
+
import uuid
|
|
41
|
+
from datetime import datetime, timezone
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
|
|
44
|
+
from . import audit_chain
|
|
45
|
+
|
|
46
|
+
ENV_SWITCH = "FOXCLAW_GOVERNANCE_AUDIT"
|
|
47
|
+
TOPIC = "governance.decision"
|
|
48
|
+
|
|
49
|
+
# verdict 合法取值(文档用,不做硬校验——审计不该因词汇笔误崩掉主流程)
|
|
50
|
+
VERDICTS_RULING = ("allow", "warn", "deny", "confirm") # 裁决类
|
|
51
|
+
VERDICTS_EXECUTION = ("success", "failed") # 执行类
|
|
52
|
+
|
|
53
|
+
# 已知层名(同上,仅文档)
|
|
54
|
+
LAYERS = ("harness", "permission", "orchestration", "skill")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def new_decision_id() -> str:
|
|
58
|
+
"""生成一次治理裁决的唯一 ID。短 hex 够用(单机审计,非全局唯一约束)。"""
|
|
59
|
+
return f"dec_{uuid.uuid4().hex[:12]}"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class GovernanceAuditor:
|
|
63
|
+
"""统一治理审计记录器:发脊柱事件 + 可选 JSONL 落盘。
|
|
64
|
+
|
|
65
|
+
线程安全:记录可能来自任意 Agent 的任意线程(事件脊柱 handler 在锁外执行,
|
|
66
|
+
但 invoke/route_request 本身可被并发调用)——落盘用进程内锁串行。
|
|
67
|
+
多进程部署(FOXCLAW_ALLOW_INSECURE 的多 worker)不在本机开发场景内,从略。
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, spine=None, sink_path: Path | None = None):
|
|
71
|
+
self._spine = spine
|
|
72
|
+
self._path = Path(sink_path) if sink_path else None
|
|
73
|
+
self._lock = threading.Lock()
|
|
74
|
+
# 链尾 hash:首次落盘时从文件尾部补齐(重启后接着上一条链,而不是重开 GENESIS)
|
|
75
|
+
self._last_hash: str | None = None
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def path(self) -> Path | None:
|
|
79
|
+
return self._path
|
|
80
|
+
|
|
81
|
+
def record(self, *, layer: str, actor: str, action: str, verdict: str,
|
|
82
|
+
reason: str = "", detail: dict | None = None,
|
|
83
|
+
decision_id: str | None = None,
|
|
84
|
+
correlation_id: str | None = None) -> str:
|
|
85
|
+
"""记录一条治理裁决/执行审计,返回 decision_id(显式传入则原样返回)。
|
|
86
|
+
|
|
87
|
+
全参数关键字化:调用点必须写明每个字段是什么,避免位置参数错位造成
|
|
88
|
+
「审计记错层」这种难以察觉的污染。
|
|
89
|
+
"""
|
|
90
|
+
did = decision_id or new_decision_id()
|
|
91
|
+
rec = {
|
|
92
|
+
"decision_id": did,
|
|
93
|
+
"correlation_id": correlation_id or "",
|
|
94
|
+
"layer": layer,
|
|
95
|
+
"actor": actor,
|
|
96
|
+
"action": action,
|
|
97
|
+
"verdict": verdict,
|
|
98
|
+
"reason": (reason or "")[:200], # 理由截断,防巨型异常串灌爆审计
|
|
99
|
+
"detail": detail or {},
|
|
100
|
+
"ts": datetime.now(timezone.utc).isoformat(),
|
|
101
|
+
}
|
|
102
|
+
self._emit(rec)
|
|
103
|
+
self._persist(rec)
|
|
104
|
+
return did
|
|
105
|
+
|
|
106
|
+
# ── 内部:两个通道,各自独立 fail-safe ─────────────────────
|
|
107
|
+
|
|
108
|
+
def _emit(self, rec: dict) -> None:
|
|
109
|
+
if self._spine is None:
|
|
110
|
+
return
|
|
111
|
+
try:
|
|
112
|
+
self._spine.publish(TOPIC, dict(rec), source="governance_audit",
|
|
113
|
+
correlation_id=rec["correlation_id"] or None)
|
|
114
|
+
except Exception:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
def _persist(self, rec: dict) -> None:
|
|
118
|
+
"""落盘时接哈希链(见 core/audit_chain.py):改一行/删中间一行都能被 verify 发现。
|
|
119
|
+
|
|
120
|
+
事件通道仍发**不带链字段**的原始 rec —— 前端只关心裁决内容,而链是落盘证据;
|
|
121
|
+
两个通道内容不同是有意的,别拿 WS 事件当真伪校验的依据。
|
|
122
|
+
"""
|
|
123
|
+
if self._path is None:
|
|
124
|
+
return
|
|
125
|
+
try:
|
|
126
|
+
with self._lock:
|
|
127
|
+
self._path.parent.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
if self._last_hash is None:
|
|
129
|
+
self._last_hash = audit_chain.tail_hash(self._path)
|
|
130
|
+
chained = audit_chain.link(rec, self._last_hash)
|
|
131
|
+
with open(self._path, "a", encoding="utf-8") as f:
|
|
132
|
+
f.write(json.dumps(chained, ensure_ascii=False) + "\n")
|
|
133
|
+
self._last_hash = chained["hash"]
|
|
134
|
+
except Exception:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
def read(self, limit: int = 200) -> list:
|
|
138
|
+
"""读落盘记录(新→旧),坏行跳过。供工具/测试/未来 API 用。"""
|
|
139
|
+
if self._path is None or not self._path.exists():
|
|
140
|
+
return []
|
|
141
|
+
out = []
|
|
142
|
+
try:
|
|
143
|
+
with open(self._path, encoding="utf-8") as f:
|
|
144
|
+
for line in f:
|
|
145
|
+
line = line.strip()
|
|
146
|
+
if not line:
|
|
147
|
+
continue
|
|
148
|
+
try:
|
|
149
|
+
out.append(json.loads(line))
|
|
150
|
+
except Exception:
|
|
151
|
+
continue
|
|
152
|
+
except Exception:
|
|
153
|
+
return []
|
|
154
|
+
return out[-limit:][::-1]
|
|
155
|
+
|
|
156
|
+
def verify(self) -> tuple:
|
|
157
|
+
"""校验落盘链 → (ok, 条数, 问题)。无落盘时视为通过。
|
|
158
|
+
|
|
159
|
+
用法:`python -m foxclaw.core.audit_chain {data_dir}/audit/governance.jsonl`。
|
|
160
|
+
"""
|
|
161
|
+
if self._path is None:
|
|
162
|
+
return True, 0, ""
|
|
163
|
+
return audit_chain.verify(self._path)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def attach(spine, data_dir) -> GovernanceAuditor | None:
|
|
167
|
+
"""装配工厂:FOXCLAW_GOVERNANCE_AUDIT=0 时返回 None(各层按 None 跳过记录)。
|
|
168
|
+
|
|
169
|
+
落盘到 {data_dir}/audit/governance.jsonl(该目录已被 .gitignore 覆盖)。
|
|
170
|
+
spine 为 None 时仍可落盘(standalone/测试场景),只是没有事件通道。
|
|
171
|
+
"""
|
|
172
|
+
if os.environ.get(ENV_SWITCH, "1") == "0":
|
|
173
|
+
return None
|
|
174
|
+
sink = Path(data_dir) / "audit" / "governance.jsonl" if data_dir else None
|
|
175
|
+
return GovernanceAuditor(spine=spine, sink_path=sink)
|