foxclaw 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -29
- package/bin/foxclaw.cjs +51 -0
- package/index.js +4 -10
- package/package.json +46 -38
- package/python/.dockerignore +35 -0
- package/python/.env.example +173 -0
- package/python/.gitattributes +46 -0
- package/python/.github/workflows/ci.yml +167 -0
- package/python/.github/workflows/release.yml +183 -0
- package/python/CHANGELOG.md +57 -0
- package/python/CHANGELOG_CN.md +57 -0
- package/python/Dockerfile +64 -0
- package/python/LICENSE +21 -0
- package/python/MANIFEST.in +14 -0
- package/python/README.md +201 -0
- package/python/README_CN.md +336 -0
- package/python/bench/bm25_audit.py +186 -0
- package/python/bench/bm25_regression.py +177 -0
- package/python/bench/bm25_stress.py +155 -0
- package/python/bench/cd_audit.py +215 -0
- package/python/bench/chaos.py +140 -0
- package/python/bench/concurrency.py +103 -0
- package/python/bench/e2e_real_models.py +94 -0
- package/python/bench/llm_retry_test.py +241 -0
- package/python/bench/load_test.py +193 -0
- package/python/bench/vector_ivf_upgrade.py +128 -0
- package/python/docs/CHANNELS.md +195 -0
- package/python/docs/GOVERNANCE.md +164 -0
- package/python/docs/RELEASE.md +110 -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 +115 -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 +368 -0
- package/python/examples/governance_middleware_demo.py +72 -0
- package/python/examples/langgraph_governance_demo.py +104 -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 +408 -0
- package/python/foxclaw/agents/__init__.py +12 -0
- package/python/foxclaw/agents/agents.py +35 -0
- package/python/foxclaw/agents/base.py +794 -0
- package/python/foxclaw/agents/impl/__init__.py +1 -0
- package/python/foxclaw/agents/impl/_helpers.py +70 -0
- package/python/foxclaw/agents/impl/finance.py +231 -0
- package/python/foxclaw/agents/impl/office.py +350 -0
- package/python/foxclaw/agents/impl/supervisor.py +592 -0
- package/python/foxclaw/agents/mixins/_context.py +128 -0
- package/python/foxclaw/agents/mixins/augment.py +143 -0
- package/python/foxclaw/agents/mixins/llm.py +67 -0
- package/python/foxclaw/agents/mixins/memory.py +41 -0
- package/python/foxclaw/agents/mixins/preference.py +131 -0
- package/python/foxclaw/agents/mixins/prompt.py +71 -0
- package/python/foxclaw/agents/mixins/skillbox.py +58 -0
- package/python/foxclaw/agents/mixins/tool_exec.py +417 -0
- package/python/foxclaw/api/__init__.py +224 -0
- package/python/foxclaw/api/_state.py +43 -0
- package/python/foxclaw/api/bootstrap.py +96 -0
- package/python/foxclaw/api/middleware.py +294 -0
- package/python/foxclaw/api/routes/ahs.py +404 -0
- package/python/foxclaw/api/routes/attachments.py +116 -0
- package/python/foxclaw/api/routes/browser.py +35 -0
- package/python/foxclaw/api/routes/domain.py +306 -0
- package/python/foxclaw/api/routes/knowledge.py +145 -0
- package/python/foxclaw/api/routes/llm_admin.py +449 -0
- package/python/foxclaw/api/routes/memory.py +105 -0
- package/python/foxclaw/api/routes/orchestration.py +39 -0
- package/python/foxclaw/api/routes/recovery.py +44 -0
- package/python/foxclaw/api/routes/review.py +266 -0
- package/python/foxclaw/api/routes/search.py +33 -0
- package/python/foxclaw/api/routes/system.py +311 -0
- package/python/foxclaw/api/routes/task.py +263 -0
- package/python/foxclaw/api/routes/ws.py +64 -0
- package/python/foxclaw/api/spine.py +52 -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 +50 -0
- package/python/foxclaw/browser/action/action_types.py +44 -0
- package/python/foxclaw/browser/action/action_verifier.py +68 -0
- package/python/foxclaw/browser/action/url_policy.py +143 -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 +170 -0
- package/python/foxclaw/browser/perception/__init__.py +1 -0
- package/python/foxclaw/browser/perception/dom_extractor.py +42 -0
- package/python/foxclaw/browser/perception/element_indexer.py +56 -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 +144 -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 +65 -0
- package/python/foxclaw/channels/__init__.py +138 -0
- package/python/foxclaw/channels/discord.py +175 -0
- package/python/foxclaw/channels/dispatcher.py +179 -0
- package/python/foxclaw/channels/feishu.py +215 -0
- package/python/foxclaw/channels/openclaw_bridge.py +151 -0
- package/python/foxclaw/channels/signing.py +187 -0
- package/python/foxclaw/channels/telegram.py +120 -0
- package/python/foxclaw/channels/weixin.py +215 -0
- package/python/foxclaw/cli.py +183 -0
- package/python/foxclaw/cognitive/__init__.py +4 -0
- package/python/foxclaw/cognitive/reasoner.py +81 -0
- package/python/foxclaw/cognitive/state.py +141 -0
- package/python/foxclaw/core/__init__.py +9 -0
- package/python/foxclaw/core/audit_chain.py +131 -0
- package/python/foxclaw/core/audit_sink.py +100 -0
- package/python/foxclaw/core/compaction.py +265 -0
- package/python/foxclaw/core/config.py +65 -0
- package/python/foxclaw/core/confirm_nonce.py +151 -0
- package/python/foxclaw/core/dispatch.py +27 -0
- package/python/foxclaw/core/envfile.py +48 -0
- package/python/foxclaw/core/failure_policy.py +142 -0
- package/python/foxclaw/core/gateway.py +81 -0
- package/python/foxclaw/core/governance_audit.py +171 -0
- package/python/foxclaw/core/idempotency.py +282 -0
- package/python/foxclaw/core/injection_guard.py +202 -0
- package/python/foxclaw/core/memory_adapter.py +489 -0
- package/python/foxclaw/core/metrics.py +88 -0
- package/python/foxclaw/core/model_router.py +985 -0
- package/python/foxclaw/core/paths.py +103 -0
- package/python/foxclaw/core/recovery.py +349 -0
- package/python/foxclaw/core/scheduler.py +141 -0
- package/python/foxclaw/core/secrets.py +131 -0
- package/python/foxclaw/core/sessions.py +155 -0
- package/python/foxclaw/core/settings.py +414 -0
- package/python/foxclaw/core/shared_write.py +186 -0
- package/python/foxclaw/core/steering_queue.py +154 -0
- package/python/foxclaw/core/system_bus.py +84 -0
- package/python/foxclaw/core/thought_chain.py +316 -0
- package/python/foxclaw/core/token_meter.py +231 -0
- package/python/foxclaw/core/tools.py +164 -0
- package/python/foxclaw/core/tracing.py +375 -0
- package/python/foxclaw/core/truth_pipeline.py +110 -0
- package/python/foxclaw/core/update_check.py +149 -0
- package/python/foxclaw/core/vec_recall_adapter.py +477 -0
- package/python/foxclaw/core/verify.py +173 -0
- package/python/foxclaw/core/web_search.py +218 -0
- package/python/foxclaw/finance/__init__.py +15 -0
- package/python/foxclaw/finance/evidence.py +149 -0
- package/python/foxclaw/finance/fx.py +98 -0
- package/python/foxclaw/finance/graph.py +179 -0
- package/python/foxclaw/finance/models.py +146 -0
- package/python/foxclaw/finance/nlparse.py +515 -0
- package/python/foxclaw/finance/policy.py +301 -0
- package/python/foxclaw/finance/reasoner.py +121 -0
- package/python/foxclaw/finance/reasoning.py +319 -0
- package/python/foxclaw/finance/reconcile.py +175 -0
- package/python/foxclaw/finance/runtime.py +500 -0
- package/python/foxclaw/governance/__init__.py +24 -0
- package/python/foxclaw/governance/middleware.py +176 -0
- package/python/foxclaw/harness/__init__.py +17 -0
- package/python/foxclaw/harness/_base.py +66 -0
- package/python/foxclaw/harness/_policy.py +340 -0
- package/python/foxclaw/harness/drift.py +77 -0
- package/python/foxclaw/harness/event_bus.py +28 -0
- package/python/foxclaw/harness/harness.py +358 -0
- package/python/foxclaw/harness/loop_detection.py +145 -0
- package/python/foxclaw/itinerary/__init__.py +10 -0
- package/python/foxclaw/itinerary/amap_client.py +114 -0
- package/python/foxclaw/itinerary/models.py +80 -0
- package/python/foxclaw/itinerary/reasoner.py +96 -0
- package/python/foxclaw/itinerary/reasoning.py +168 -0
- package/python/foxclaw/itinerary/runtime.py +144 -0
- package/python/foxclaw/kernel/__init__.py +13 -0
- package/python/foxclaw/kernel/capabilities.py +72 -0
- package/python/foxclaw/kernel/policy_gate.py +108 -0
- package/python/foxclaw/kernel/process_manager.py +186 -0
- package/python/foxclaw/kernel/scheduler.py +125 -0
- package/python/foxclaw/kernel/sentinel.py +443 -0
- package/python/foxclaw/kernel/sentinel_client.py +243 -0
- package/python/foxclaw/kernel/spine.py +97 -0
- package/python/foxclaw/knowledge/__init__.py +360 -0
- package/python/foxclaw/knowledge/truth_crystallizer.py +89 -0
- package/python/foxclaw/learning/__init__.py +12 -0
- package/python/foxclaw/learning/knowledge_graph.py +144 -0
- package/python/foxclaw/learning/models.py +93 -0
- package/python/foxclaw/learning/reasoner.py +67 -0
- package/python/foxclaw/learning/runtime.py +351 -0
- package/python/foxclaw/learning/spaced_repetition.py +91 -0
- package/python/foxclaw/mcp_server.py +188 -0
- package/python/foxclaw/orchestration/__init__.py +167 -0
- package/python/foxclaw/pipeline/__init__.py +23 -0
- package/python/foxclaw/pipeline/cognitive_pipeline.py +429 -0
- package/python/foxclaw/pipeline/stage_meta.py +21 -0
- package/python/foxclaw/pipeline/supervisor_stages.py +201 -0
- package/python/foxclaw/planning/__init__.py +17 -0
- package/python/foxclaw/planning/llm_planner.py +232 -0
- package/python/foxclaw/planning/planner.py +94 -0
- package/python/foxclaw/search/__init__.py +24 -0
- package/python/foxclaw/search/agent_port.py +190 -0
- package/python/foxclaw/search/evidence.py +109 -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 +71 -0
- package/python/foxclaw/search/models.py +127 -0
- package/python/foxclaw/search/planner.py +44 -0
- package/python/foxclaw/search/retriever.py +39 -0
- package/python/foxclaw/search/runtime.py +127 -0
- package/python/foxclaw/search/strategy.py +44 -0
- package/python/foxclaw/skills/__init__.py +559 -0
- package/python/foxclaw/skills/archive.py +111 -0
- package/python/foxclaw/skills/fetch.py +226 -0
- package/python/foxclaw/skills/hooks_spec.py +69 -0
- package/python/foxclaw/skills/install_cmd.py +216 -0
- package/python/foxclaw/skills/lifecycle.py +252 -0
- package/python/foxclaw/skills/markdown_skill.py +175 -0
- package/python/foxclaw/skills/markdown_store.py +39 -0
- package/python/foxclaw/skills/mcp_client.py +184 -0
- package/python/foxclaw/skills/mcp_store.py +40 -0
- package/python/foxclaw/skills/permission.py +145 -0
- package/python/foxclaw/skills/pipeline.py +354 -0
- package/python/foxclaw/skills/policy.py +74 -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 +25 -0
- package/python/frontend/deck/chat_model_picker.js +483 -0
- package/python/frontend/deck/domain.js +276 -0
- package/python/frontend/deck/main.js +1806 -0
- package/python/frontend/deck/markdown.js +44 -0
- package/python/frontend/deck/settings.js +307 -0
- package/python/frontend/deck/state.js +5 -0
- package/python/frontend/deck/util.js +4 -0
- package/python/frontend/deck/visuals.js +190 -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 +1436 -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-CQSBdxk4.js +34 -0
- package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
- package/python/frontend/v3/index.html +15 -0
- package/python/frontend/widget_runtime.js +252 -0
- package/python/frontend/widgets.js +639 -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 +58 -0
- package/python/frontend_src/src/api.js +82 -0
- package/python/frontend_src/src/components/ChatDock.vue +365 -0
- package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
- package/python/frontend_src/src/components/DocViewer.vue +162 -0
- package/python/frontend_src/src/components/Inspector.vue +235 -0
- package/python/frontend_src/src/components/LeftRail.vue +415 -0
- package/python/frontend_src/src/components/MenuPopover.vue +128 -0
- package/python/frontend_src/src/components/MessageStream.vue +389 -0
- package/python/frontend_src/src/components/ModelPicker.vue +350 -0
- package/python/frontend_src/src/components/QuickIngest.vue +112 -0
- package/python/frontend_src/src/components/Stage.vue +272 -0
- package/python/frontend_src/src/components/ToolMenu.vue +141 -0
- package/python/frontend_src/src/components/TopBar.vue +118 -0
- package/python/frontend_src/src/components/panels/ActivityPanel.vue +82 -0
- package/python/frontend_src/src/components/panels/AgentsPanel.vue +83 -0
- package/python/frontend_src/src/components/panels/KnowledgePanel.vue +425 -0
- package/python/frontend_src/src/components/panels/MemoryPanel.vue +117 -0
- package/python/frontend_src/src/components/panels/PanelBox.vue +50 -0
- package/python/frontend_src/src/components/panels/ReviewPanel.vue +292 -0
- package/python/frontend_src/src/components/panels/SettingsPanel.vue +358 -0
- package/python/frontend_src/src/components/panels/TasksPanel.vue +73 -0
- package/python/frontend_src/src/components/panels/ToolsPanel.vue +746 -0
- package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
- package/python/frontend_src/src/lib/fmt.js +44 -0
- package/python/frontend_src/src/lib/i18n.js +53 -0
- package/python/frontend_src/src/lib/locales/en.js +173 -0
- package/python/frontend_src/src/lib/locales/zh.js +174 -0
- package/python/frontend_src/src/lib/md.js +190 -0
- package/python/frontend_src/src/main.js +9 -0
- package/python/frontend_src/src/stores/app.js +91 -0
- package/python/frontend_src/src/stores/chat.js +450 -0
- package/python/frontend_src/src/stores/model.js +96 -0
- package/python/frontend_src/src/stores/panels.js +289 -0
- package/python/frontend_src/src/stores/profile.js +31 -0
- package/python/frontend_src/src/stores/system.js +25 -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 +39 -0
- package/python/pyproject.toml +268 -0
- package/python/requirements.txt +311 -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 +54 -0
- package/python/tests/run_all.py +200 -0
- package/python/tests/test_agent_lifecycle.py +326 -0
- package/python/tests/test_ahs_p2.py +393 -0
- package/python/tests/test_anthropic_tools.py +192 -0
- package/python/tests/test_api.py +97 -0
- package/python/tests/test_api_auth.py +79 -0
- package/python/tests/test_api_host_guard.py +69 -0
- package/python/tests/test_attachments.py +151 -0
- package/python/tests/test_audit_chain.py +116 -0
- package/python/tests/test_audit_sink.py +130 -0
- package/python/tests/test_browser.py +149 -0
- package/python/tests/test_browser_request_guard.py +180 -0
- package/python/tests/test_capabilities.py +152 -0
- package/python/tests/test_channel_signing.py +185 -0
- package/python/tests/test_channel_trust.py +109 -0
- package/python/tests/test_channels.py +174 -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 +61 -0
- package/python/tests/test_confirm_nonce.py +186 -0
- package/python/tests/test_context_compaction_wiring.py +124 -0
- package/python/tests/test_dispatch.py +89 -0
- package/python/tests/test_domain_evals.py +334 -0
- package/python/tests/test_domain_import.py +72 -0
- package/python/tests/test_domain_reasoners.py +134 -0
- package/python/tests/test_e2e.py +161 -0
- package/python/tests/test_emb_cache.py +97 -0
- package/python/tests/test_embedding_singleton.py +87 -0
- package/python/tests/test_envfile.py +83 -0
- package/python/tests/test_evals_smoke.py +90 -0
- package/python/tests/test_event_spine.py +174 -0
- package/python/tests/test_evidence_semantic.py +88 -0
- package/python/tests/test_evidence_sse.py +94 -0
- package/python/tests/test_file_ingest.py +135 -0
- package/python/tests/test_finance.py +144 -0
- package/python/tests/test_finance_ledger.py +245 -0
- package/python/tests/test_finance_persist.py +174 -0
- package/python/tests/test_gateway.py +125 -0
- package/python/tests/test_governance.py +421 -0
- package/python/tests/test_governance_audit.py +324 -0
- package/python/tests/test_governance_middleware.py +137 -0
- package/python/tests/test_hkc.py +268 -0
- package/python/tests/test_hkc_index_build_manager.py +306 -0
- package/python/tests/test_hkc_manifest_reuse.py +294 -0
- package/python/tests/test_hkc_search_event_sync.py +482 -0
- package/python/tests/test_import_path_guard.py +155 -0
- package/python/tests/test_injection_guard.py +193 -0
- package/python/tests/test_integration.py +117 -0
- package/python/tests/test_itinerary.py +100 -0
- package/python/tests/test_kee_semantic.py +84 -0
- package/python/tests/test_kernel.py +212 -0
- package/python/tests/test_learning.py +101 -0
- package/python/tests/test_llm_admin.py +145 -0
- package/python/tests/test_llm_planner.py +344 -0
- package/python/tests/test_markdown_skills.py +62 -0
- package/python/tests/test_markdown_skills_openclaw.py +66 -0
- package/python/tests/test_mcp_connectors.py +97 -0
- package/python/tests/test_mcp_guardian_discovery.py +87 -0
- package/python/tests/test_mcp_no_memory.py +43 -0
- package/python/tests/test_mcp_policy_gate.py +76 -0
- package/python/tests/test_mcp_server.py +92 -0
- package/python/tests/test_memory_recall.py +92 -0
- package/python/tests/test_metrics.py +58 -0
- package/python/tests/test_multi_chain.py +90 -0
- package/python/tests/test_multimodal.py +122 -0
- package/python/tests/test_negotiation.py +136 -0
- package/python/tests/test_orchestration.py +109 -0
- package/python/tests/test_orchestration_decoupled.py +103 -0
- package/python/tests/test_p3_p5_hardening.py +324 -0
- package/python/tests/test_parallel_dispatch.py +207 -0
- package/python/tests/test_pipeline.py +492 -0
- package/python/tests/test_plan_visible.py +86 -0
- package/python/tests/test_planner.py +88 -0
- package/python/tests/test_policy_gate.py +108 -0
- package/python/tests/test_profile.py +67 -0
- package/python/tests/test_readme_claims.py +163 -0
- package/python/tests/test_reasoner.py +170 -0
- package/python/tests/test_release_check.py +252 -0
- package/python/tests/test_resume_checkpoint.py +148 -0
- package/python/tests/test_routing.py +85 -0
- package/python/tests/test_scheduler.py +138 -0
- package/python/tests/test_search.py +194 -0
- package/python/tests/test_secrets.py +153 -0
- package/python/tests/test_semantic.py +69 -0
- package/python/tests/test_sessions.py +90 -0
- package/python/tests/test_settings.py +265 -0
- package/python/tests/test_shadow_learning.py +290 -0
- package/python/tests/test_shared_write.py +200 -0
- package/python/tests/test_skill_taint_gate.py +145 -0
- package/python/tests/test_skills.py +117 -0
- package/python/tests/test_skills_fetch.py +282 -0
- package/python/tests/test_stats_and_events.py +162 -0
- package/python/tests/test_streaming.py +55 -0
- package/python/tests/test_supervisor_pipeline.py +176 -0
- package/python/tests/test_supervisor_triage.py +102 -0
- package/python/tests/test_taint.py +112 -0
- package/python/tests/test_task_stream.py +74 -0
- package/python/tests/test_thinking_sse.py +66 -0
- package/python/tests/test_thinking_stream.py +76 -0
- package/python/tests/test_tool_center.py +113 -0
- package/python/tests/test_tool_parallel.py +113 -0
- package/python/tests/test_tool_params.py +99 -0
- package/python/tests/test_tool_use.py +229 -0
- package/python/tests/test_toplevel_concurrency.py +116 -0
- package/python/tests/test_tracing.py +256 -0
- package/python/tests/test_truth_crystallizer.py +125 -0
- package/python/tests/test_truth_flow.py +88 -0
- package/python/tests/test_update_check.py +95 -0
- package/python/tests/test_url_policy.py +53 -0
- package/python/tests/test_verify_evidence.py +212 -0
- package/python/tests/test_version_sync.py +100 -0
- package/python/tests/test_web_trigger.py +82 -0
- package/python/tools/check_faiss_type.py +30 -0
- package/python/tools/check_log.ps1 +38 -0
- package/python/tools/ivf_status.py +72 -0
- package/python/tools/mypy_baseline.txt +13 -0
- package/python/tools/mypy_ratchet.py +143 -0
- package/python/tools/pack.py +173 -0
- package/python/tools/release_check.py +349 -0
- package/python/tools/restart_foxclaw.ps1 +71 -0
- package/python/tools/shared_write_audit.py +190 -0
- package/python/tools/size_baseline.txt +28 -0
- package/python/tools/size_check.py +102 -0
- package/python/tools/strip_comments.py +228 -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,183 @@
|
|
|
1
|
+
"""命令行入口 `foxclaw`。
|
|
2
|
+
|
|
3
|
+
与 PyPI 上 `foxclaw` 0.1.0 占位包发布的入口保持一致:包名 `foxclaw`,
|
|
4
|
+
命令名 `foxclaw`。
|
|
5
|
+
|
|
6
|
+
foxclaw # 打印版本与提示
|
|
7
|
+
foxclaw serve # 启动 API 服务(默认 http://127.0.0.1:8000)
|
|
8
|
+
foxclaw config check # 检查 .env / 环境变量:类型写错、拼错的键、互相矛盾的组合
|
|
9
|
+
foxclaw config show # 打印当前生效配置(密钥默认脱敏)
|
|
10
|
+
foxclaw skills install @owner/slug # 装 SKILL.md 技能(ClawHub/GitHub/URL/本地包)
|
|
11
|
+
foxclaw skills list # 列出已装技能
|
|
12
|
+
foxclaw --version # 打印版本
|
|
13
|
+
|
|
14
|
+
`serve` 用字符串目标 `foxclaw.api:app` 交给 uvicorn 导入,避免 CLI 进程
|
|
15
|
+
自己先把整个应用栈加载一遍(含 HKC/HMR 与模型);`--reload` 时 uvicorn 也需要
|
|
16
|
+
字符串目标才能重建子进程。
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from foxclaw import __version__
|
|
27
|
+
|
|
28
|
+
APP_TARGET = "foxclaw.api:app"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _repo_root() -> Path:
|
|
32
|
+
"""仓库根目录(可编辑安装时即包含 foxclaw/ 的那一层)。"""
|
|
33
|
+
return Path(__file__).resolve().parents[1]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _cmd_serve(args: argparse.Namespace) -> int:
|
|
37
|
+
from foxclaw.core import settings
|
|
38
|
+
|
|
39
|
+
issues = settings.check()
|
|
40
|
+
fatal = settings.fatal_issues(issues)
|
|
41
|
+
if fatal:
|
|
42
|
+
print(settings.format_issues(issues), file=sys.stderr)
|
|
43
|
+
print("\n以上配置会让启动直接失败,已中止。改好后重试。", file=sys.stderr)
|
|
44
|
+
return 2
|
|
45
|
+
if issues:
|
|
46
|
+
print(settings.format_issues(issues, limit=8), file=sys.stderr)
|
|
47
|
+
print("(以上不阻断启动;`foxclaw config check` 看完整诊断)", file=sys.stderr)
|
|
48
|
+
|
|
49
|
+
import uvicorn
|
|
50
|
+
|
|
51
|
+
uvicorn.run(
|
|
52
|
+
APP_TARGET,
|
|
53
|
+
host=args.host,
|
|
54
|
+
port=args.port,
|
|
55
|
+
log_level=args.log_level,
|
|
56
|
+
reload=bool(args.reload),
|
|
57
|
+
reload_dirs=[str(_repo_root() / "foxclaw")] if args.reload else None,
|
|
58
|
+
)
|
|
59
|
+
return 0
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _cmd_config_check(args: argparse.Namespace) -> int:
|
|
63
|
+
from foxclaw.core import settings
|
|
64
|
+
|
|
65
|
+
issues = settings.check()
|
|
66
|
+
if args.json:
|
|
67
|
+
import json as _json
|
|
68
|
+
print(_json.dumps(
|
|
69
|
+
[{"level": i.level, "code": i.code, "name": i.name,
|
|
70
|
+
"message": i.message, "hint": i.hint} for i in issues],
|
|
71
|
+
ensure_ascii=False, indent=2))
|
|
72
|
+
else:
|
|
73
|
+
print(settings.format_issues(issues))
|
|
74
|
+
return 1 if any(i.level == "error" for i in issues) else 0
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _cmd_config_show(args: argparse.Namespace) -> int:
|
|
78
|
+
from foxclaw.core import settings
|
|
79
|
+
|
|
80
|
+
current = ""
|
|
81
|
+
for opt in settings.SPECS:
|
|
82
|
+
if opt.group != current:
|
|
83
|
+
current = opt.group
|
|
84
|
+
print(f"[{current}]")
|
|
85
|
+
raw = os.environ.get(opt.name)
|
|
86
|
+
if raw is None or raw.strip() == "":
|
|
87
|
+
shown = f"{settings.mask(opt.default) if opt.secret else opt.default}" \
|
|
88
|
+
if opt.default not in ("", None) else "(未设置)"
|
|
89
|
+
suffix = " 默认"
|
|
90
|
+
else:
|
|
91
|
+
shown = settings.mask(raw) if (opt.secret and not args.reveal) else raw
|
|
92
|
+
suffix = " 已设置"
|
|
93
|
+
print(f" {opt.name} = {shown}{suffix}")
|
|
94
|
+
return 0
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _cmd_skills(args: argparse.Namespace) -> int:
|
|
98
|
+
from foxclaw.skills.install_cmd import run
|
|
99
|
+
|
|
100
|
+
return run(args)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
104
|
+
parser = argparse.ArgumentParser(
|
|
105
|
+
prog="foxclaw",
|
|
106
|
+
description="Foxclaw —— 认知 Agent 操作系统",
|
|
107
|
+
)
|
|
108
|
+
parser.add_argument(
|
|
109
|
+
"-V",
|
|
110
|
+
"--version",
|
|
111
|
+
action="version",
|
|
112
|
+
version=f"foxclaw {__version__}",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
subparsers = parser.add_subparsers(dest="command", metavar="<command>")
|
|
116
|
+
|
|
117
|
+
serve = subparsers.add_parser("serve", help="启动 API 服务")
|
|
118
|
+
serve.add_argument("--host", default="127.0.0.1", help="监听地址,默认仅本机回环")
|
|
119
|
+
serve.add_argument("--port", type=int, default=8000, help="监听端口,默认 8000")
|
|
120
|
+
serve.add_argument("--reload", action="store_true", help="改代码自动重启(开发用)")
|
|
121
|
+
serve.add_argument("--log-level", default="info", help="uvicorn 日志级别")
|
|
122
|
+
serve.set_defaults(handler=_cmd_serve)
|
|
123
|
+
|
|
124
|
+
config = subparsers.add_parser("config", help="查看/检查配置(.env 与环境变量)")
|
|
125
|
+
config_sub = config.add_subparsers(dest="config_action", metavar="<action>")
|
|
126
|
+
|
|
127
|
+
c_check = config_sub.add_parser(
|
|
128
|
+
"check", help="检查配置:类型、取值范围、拼错的键、互相矛盾的组合")
|
|
129
|
+
c_check.add_argument("--json", action="store_true", help="输出 JSON(便于脚本/前端消费)")
|
|
130
|
+
c_check.set_defaults(handler=_cmd_config_check)
|
|
131
|
+
|
|
132
|
+
c_show = config_sub.add_parser("show", help="打印当前生效配置")
|
|
133
|
+
c_show.add_argument("--reveal", action="store_true",
|
|
134
|
+
help="显示密钥原文(默认脱敏;别在截图/日志里用)")
|
|
135
|
+
c_show.set_defaults(handler=_cmd_config_show)
|
|
136
|
+
|
|
137
|
+
skills = subparsers.add_parser("skills", help="安装/查看/卸载指令技能(SKILL.md)")
|
|
138
|
+
skills_sub = skills.add_subparsers(dest="skills_action", metavar="<action>")
|
|
139
|
+
|
|
140
|
+
s_install = skills_sub.add_parser("install", help="从 ClawHub/GitHub/URL/本地包装一个技能")
|
|
141
|
+
s_install.add_argument(
|
|
142
|
+
"ref",
|
|
143
|
+
help="@owner/slug(ClawHub) | github:owner/repo[/子路径][@ref] | "
|
|
144
|
+
"http(s)://包地址 | 本地 .zip/.tar.gz/SKILL.md/目录")
|
|
145
|
+
s_install.add_argument("--yes", action="store_true",
|
|
146
|
+
help="跳过确认(脚本/CI 必填;非终端环境没有它一定失败)")
|
|
147
|
+
s_install.add_argument("--id", default="", help="覆盖技能 id(默认取 SKILL.md 里的 name)")
|
|
148
|
+
s_install.add_argument("--json", action="store_true", help="输出 JSON")
|
|
149
|
+
s_install.set_defaults(handler=_cmd_skills)
|
|
150
|
+
|
|
151
|
+
s_list = skills_sub.add_parser("list", help="列出已安装的指令技能")
|
|
152
|
+
s_list.add_argument("--json", action="store_true", help="输出 JSON")
|
|
153
|
+
s_list.set_defaults(handler=_cmd_skills)
|
|
154
|
+
|
|
155
|
+
s_remove = skills_sub.add_parser("remove", help="卸载一个指令技能")
|
|
156
|
+
s_remove.add_argument("id", help="技能 id(`foxclaw skills list` 第一列)")
|
|
157
|
+
s_remove.set_defaults(handler=_cmd_skills)
|
|
158
|
+
|
|
159
|
+
return parser
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def main(argv: list[str] | None = None) -> int:
|
|
163
|
+
parser = build_parser()
|
|
164
|
+
args = parser.parse_args(argv)
|
|
165
|
+
|
|
166
|
+
handler = getattr(args, "handler", None)
|
|
167
|
+
if handler is None:
|
|
168
|
+
if getattr(args, "command", None) == "config":
|
|
169
|
+
print("用法:`foxclaw config check` 或 `foxclaw config show`")
|
|
170
|
+
return 2
|
|
171
|
+
if getattr(args, "command", None) == "skills":
|
|
172
|
+
print("用法:`foxclaw skills install <ref>` / `foxclaw skills list` / "
|
|
173
|
+
"`foxclaw skills remove <id>`")
|
|
174
|
+
return 2
|
|
175
|
+
print(f"foxclaw {__version__}")
|
|
176
|
+
print("用 `foxclaw serve` 启动本地服务;`foxclaw config check` 检查配置;"
|
|
177
|
+
"`foxclaw --help` 查看全部子命令。")
|
|
178
|
+
return 0
|
|
179
|
+
return int(handler(args))
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
if __name__ == "__main__":
|
|
183
|
+
sys.exit(main())
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
|
19
|
+
detail: str = ""
|
|
20
|
+
weight: float = 1.0
|
|
21
|
+
raw: Any = None
|
|
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
|
|
30
|
+
rationale: str
|
|
31
|
+
evidence: list = field(default_factory=list)
|
|
32
|
+
hypotheses: list = field(default_factory=list)
|
|
33
|
+
source: str = "rules"
|
|
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
|
+
@staticmethod
|
|
65
|
+
def llm_available(llm, enabled: bool) -> bool:
|
|
66
|
+
"""LLM 是否真正可用:启用 + 非空 + 非 stub(无真实模型时视为不可用)。"""
|
|
67
|
+
return bool(enabled) and llm is not None and not getattr(llm, "is_stub", True)
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def llm_should_escalate(llm, system: str, user: str) -> bool:
|
|
71
|
+
"""让 LLM 就"规则未发现硬问题、但情形微妙"的情况投票是否升级为人工确认。
|
|
72
|
+
**结构上只增强、不推翻**:只返回 True(建议 confirm)/False(维持规则裁决),
|
|
73
|
+
无从产生"推翻规则"的结果。出错/超时/响应不明 → False(维持规则裁决;
|
|
74
|
+
顾问域无治理闸口,不因 LLM 抖动制造确认噪声——与守门域 finance 的
|
|
75
|
+
error→confirm 区别正在于此)。"""
|
|
76
|
+
try:
|
|
77
|
+
resp = (llm.complete(system, [{"role": "user", "content": user}],
|
|
78
|
+
max_tokens=16) or "").upper()
|
|
79
|
+
except Exception:
|
|
80
|
+
return False
|
|
81
|
+
return "CONFIRM" in resp or "ESCALATE" in resp
|
|
@@ -0,0 +1,141 @@
|
|
|
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
|
+
|
|
14
|
+
from typing import Optional
|
|
15
|
+
import time
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class CognitiveState:
|
|
21
|
+
"""系统当前认知状态快照。随事件总线上的活动自动更新。"""
|
|
22
|
+
|
|
23
|
+
active_goal: Optional[str] = None
|
|
24
|
+
active_agent: Optional[str] = None
|
|
25
|
+
semantic_focus: Optional[str] = None
|
|
26
|
+
attention_scope: list = field(default_factory=list)
|
|
27
|
+
reasoning_depth: float = 0.0
|
|
28
|
+
confidence_map: dict = field(default_factory=dict)
|
|
29
|
+
activated_memories: list = field(default_factory=list)
|
|
30
|
+
last_route: Optional[str] = None
|
|
31
|
+
active_processes: int = 0
|
|
32
|
+
temporal_window: str = "current_turn"
|
|
33
|
+
updated_at: float = field(default_factory=time.time)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def on_execution(self, payload: dict):
|
|
37
|
+
"""执行事件 → 更新目标/Agent/注意力。"""
|
|
38
|
+
payload = payload or {}
|
|
39
|
+
task = payload.get("task", "")
|
|
40
|
+
self.active_goal = task[:120] if task else self.active_goal
|
|
41
|
+
self.active_agent = payload.get("agent_id") or self.active_agent
|
|
42
|
+
self.attention_scope = self._keywords(task)
|
|
43
|
+
sched = payload.get("schedule") or {}
|
|
44
|
+
if sched:
|
|
45
|
+
self.semantic_focus = sched.get("intent")
|
|
46
|
+
self.last_route = sched.get("route_hint")
|
|
47
|
+
self.confidence_map["priority"] = sched.get("priority", 0.0)
|
|
48
|
+
self.temporal_window = "current_turn"
|
|
49
|
+
self.updated_at = time.time()
|
|
50
|
+
|
|
51
|
+
def on_memory_updated(self, payload: dict):
|
|
52
|
+
"""记忆更新事件 → 记录激活的记忆(去重,保留最近 5 条)。"""
|
|
53
|
+
payload = payload or {}
|
|
54
|
+
mu = payload.get("memory_update") or {}
|
|
55
|
+
content = mu.get("content")
|
|
56
|
+
if content:
|
|
57
|
+
snippet = content[:80]
|
|
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 = 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
|
+
rough = [w for w in re.split(r"[\s,,、。!?:;]", text or "") if len(w) >= 2]
|
|
73
|
+
out = []
|
|
74
|
+
for seg in rough:
|
|
75
|
+
if len(seg) <= 4:
|
|
76
|
+
out.append(seg)
|
|
77
|
+
else:
|
|
78
|
+
out.append(seg[:3])
|
|
79
|
+
if len(seg) > 6:
|
|
80
|
+
out.append(seg[len(seg)//2:len(seg)//2+2])
|
|
81
|
+
out.append(seg[-2:])
|
|
82
|
+
return out[:n]
|
|
83
|
+
|
|
84
|
+
def snapshot(self) -> dict:
|
|
85
|
+
return {
|
|
86
|
+
"active_goal": self.active_goal,
|
|
87
|
+
"active_agent": self.active_agent,
|
|
88
|
+
"semantic_focus": self.semantic_focus,
|
|
89
|
+
"attention_scope": list(self.attention_scope),
|
|
90
|
+
"reasoning_depth": self.reasoning_depth,
|
|
91
|
+
"confidence_map": dict(self.confidence_map),
|
|
92
|
+
"activated_memories": list(self.activated_memories),
|
|
93
|
+
"last_route": self.last_route,
|
|
94
|
+
"active_processes": self.active_processes,
|
|
95
|
+
"temporal_window": self.temporal_window,
|
|
96
|
+
"updated_at": self.updated_at,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class CognitiveStateTracker:
|
|
101
|
+
"""把 CognitiveState 接到事件总线 + 进程管理器,自动维护快照。"""
|
|
102
|
+
|
|
103
|
+
def __init__(self, bus, process_manager=None):
|
|
104
|
+
import threading
|
|
105
|
+
self.state = CognitiveState()
|
|
106
|
+
self.process_manager = process_manager
|
|
107
|
+
self._lock = threading.Lock()
|
|
108
|
+
if bus is not None:
|
|
109
|
+
bus.subscribe("execution.completed", self._on_exec, exact=True)
|
|
110
|
+
bus.subscribe("memory.updated", self._on_mem, exact=True)
|
|
111
|
+
bus.subscribe("task.lifecycle", self._on_proc)
|
|
112
|
+
|
|
113
|
+
def _on_exec(self, msg):
|
|
114
|
+
if msg is None:
|
|
115
|
+
return
|
|
116
|
+
with self._lock:
|
|
117
|
+
self.state.on_execution(getattr(msg, "payload", None) or {})
|
|
118
|
+
|
|
119
|
+
def _on_mem(self, msg):
|
|
120
|
+
if msg is None:
|
|
121
|
+
return
|
|
122
|
+
with self._lock:
|
|
123
|
+
self.state.on_memory_updated(getattr(msg, "payload", None) or {})
|
|
124
|
+
|
|
125
|
+
def _on_proc(self, msg):
|
|
126
|
+
if msg is None:
|
|
127
|
+
return
|
|
128
|
+
active = None
|
|
129
|
+
pm = self.process_manager
|
|
130
|
+
if pm is not None:
|
|
131
|
+
if hasattr(pm, "active_count"):
|
|
132
|
+
active = pm.active_count()
|
|
133
|
+
else:
|
|
134
|
+
st = pm.stats().get("by_state", {})
|
|
135
|
+
active = st.get("scheduled", 0) + st.get("running", 0)
|
|
136
|
+
with self._lock:
|
|
137
|
+
self.state.on_process_change(getattr(msg, "payload", None) or {}, active)
|
|
138
|
+
|
|
139
|
+
def snapshot(self) -> dict:
|
|
140
|
+
with self._lock:
|
|
141
|
+
return self.state.snapshot()
|
|
@@ -0,0 +1,131 @@
|
|
|
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
|
+
GENESIS = "0" * 64
|
|
32
|
+
|
|
33
|
+
_TAIL_WINDOW = 65536
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _canonical(rec: dict) -> bytes:
|
|
37
|
+
"""稳定序列化:sort_keys + 紧凑分隔符,保证跨进程/跨版本重算一致。"""
|
|
38
|
+
return json.dumps(rec, sort_keys=True, separators=(",", ":"),
|
|
39
|
+
ensure_ascii=False, default=str).encode("utf-8")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def link(rec: dict, prev_hash: str = GENESIS) -> dict:
|
|
43
|
+
"""给一行记录接上链,返回**新** dict(不改入参,便于调用方继续用原始 rec)。"""
|
|
44
|
+
body = dict(rec)
|
|
45
|
+
body["prev_hash"] = prev_hash
|
|
46
|
+
body["hash"] = hashlib.sha256(_canonical(body)).hexdigest()
|
|
47
|
+
return body
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def tail_hash(path) -> str:
|
|
51
|
+
"""读文件最后一条带 hash 的记录的 hash;不存在/空/无链 → GENESIS。
|
|
52
|
+
|
|
53
|
+
倒着找而不是顺着读:审计文件可能很长,重启续链只需要最后一行;
|
|
54
|
+
尾部若有写一半的残行(进程被杀),跳过继续往前找。
|
|
55
|
+
"""
|
|
56
|
+
p = Path(path)
|
|
57
|
+
try:
|
|
58
|
+
with open(p, "rb") as f:
|
|
59
|
+
f.seek(0, 2)
|
|
60
|
+
size = f.tell()
|
|
61
|
+
f.seek(max(0, size - _TAIL_WINDOW))
|
|
62
|
+
chunk = f.read().decode("utf-8", "replace")
|
|
63
|
+
except OSError:
|
|
64
|
+
return GENESIS
|
|
65
|
+
for line in reversed(chunk.splitlines()):
|
|
66
|
+
line = line.strip()
|
|
67
|
+
if not line:
|
|
68
|
+
continue
|
|
69
|
+
try:
|
|
70
|
+
rec = json.loads(line)
|
|
71
|
+
except Exception:
|
|
72
|
+
continue
|
|
73
|
+
h = rec.get("hash")
|
|
74
|
+
if isinstance(h, str) and h:
|
|
75
|
+
return h
|
|
76
|
+
return GENESIS
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def verify(path) -> tuple:
|
|
80
|
+
"""校验整条链 → (ok, 已校验条数, 问题描述)。
|
|
81
|
+
|
|
82
|
+
问题描述为空串表示通过;不通过时给出「第几行 + 断在哪」,便于人工定位。
|
|
83
|
+
"""
|
|
84
|
+
p = Path(path)
|
|
85
|
+
if not p.exists():
|
|
86
|
+
return True, 0, ""
|
|
87
|
+
prev = GENESIS
|
|
88
|
+
n = 0
|
|
89
|
+
try:
|
|
90
|
+
with open(p, encoding="utf-8") as f:
|
|
91
|
+
for i, line in enumerate(f, 1):
|
|
92
|
+
line = line.strip()
|
|
93
|
+
if not line:
|
|
94
|
+
continue
|
|
95
|
+
try:
|
|
96
|
+
rec = json.loads(line)
|
|
97
|
+
except Exception:
|
|
98
|
+
return False, n, f"第 {i} 行不是合法 JSON"
|
|
99
|
+
h = rec.get("hash")
|
|
100
|
+
if not h:
|
|
101
|
+
continue
|
|
102
|
+
if rec.get("prev_hash") != prev:
|
|
103
|
+
return False, n, (f"第 {i} 行链断:prev_hash 与上一条 hash 不符"
|
|
104
|
+
"(中间记录被删或被挪动)")
|
|
105
|
+
body = {k: v for k, v in rec.items() if k != "hash"}
|
|
106
|
+
if hashlib.sha256(_canonical(body)).hexdigest() != h:
|
|
107
|
+
return False, n, f"第 {i} 行内容被改(hash 不匹配)"
|
|
108
|
+
prev = h
|
|
109
|
+
n += 1
|
|
110
|
+
except OSError as e:
|
|
111
|
+
return False, n, f"读取失败: {e}"
|
|
112
|
+
return True, n, ""
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _main(argv: list) -> int:
|
|
116
|
+
if not argv:
|
|
117
|
+
print("用法: python -m foxclaw.core.audit_chain <audit.jsonl> [...]")
|
|
118
|
+
return 2
|
|
119
|
+
bad = 0
|
|
120
|
+
for p in argv:
|
|
121
|
+
ok, n, msg = verify(p)
|
|
122
|
+
print(f"{p}: {'OK' if ok else 'BROKEN'} ({n} 条)" + (f" - {msg}" if msg else ""))
|
|
123
|
+
if not ok:
|
|
124
|
+
bad = 1
|
|
125
|
+
return bad
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
if __name__ == "__main__":
|
|
129
|
+
import sys
|
|
130
|
+
|
|
131
|
+
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):
|
|
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
|