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,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Foxclaw 行程子系统 — Itinerary Runtime
|
|
3
|
+
行程行为操作系统:采集行程点 → 地理编码(高德或手动) → 路线优化 →
|
|
4
|
+
冲突/可达检测 → 行程守门人。高德可用时自动用真实路线替换估算。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import csv
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .models import Waypoint, ItineraryVerdict
|
|
11
|
+
from .reasoning import ItineraryReasoning
|
|
12
|
+
from .amap_client import AMapClient
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ItineraryRuntime:
|
|
16
|
+
"""行政 Agent 的行程内核。"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, amap_key: str = None, hmr=None):
|
|
19
|
+
self.waypoints: list = []
|
|
20
|
+
self.reasoning = ItineraryReasoning()
|
|
21
|
+
self.amap = AMapClient(amap_key)
|
|
22
|
+
self.hmr = hmr
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def amap_enabled(self) -> bool:
|
|
26
|
+
return self.amap.enabled
|
|
27
|
+
|
|
28
|
+
# ── 采集行程点 ────────────────────────────────────────
|
|
29
|
+
def add_waypoint(self, name: str, address: str = "", lng: float = None,
|
|
30
|
+
lat: float = None, start_time: str = None,
|
|
31
|
+
duration_min: int = 0, city: str = "") -> dict:
|
|
32
|
+
wp = Waypoint(name=name, address=address, lng=lng, lat=lat,
|
|
33
|
+
start_time=start_time, duration_min=duration_min, city=city)
|
|
34
|
+
# 没坐标但有地址 → 尝试高德地理编码
|
|
35
|
+
if not wp.has_coord and address and self.amap_enabled:
|
|
36
|
+
geo = self.amap.geocode(address, city)
|
|
37
|
+
if geo:
|
|
38
|
+
wp.lng, wp.lat = geo
|
|
39
|
+
self.waypoints.append(wp)
|
|
40
|
+
if self.hmr:
|
|
41
|
+
try:
|
|
42
|
+
self.hmr.ingest(content=f"行程:{name} @ {address or wp.coord_str} "
|
|
43
|
+
f"{start_time or ''}",
|
|
44
|
+
type="task", agent_id="office",
|
|
45
|
+
title=f"行程_{name}", tags=["itinerary"])
|
|
46
|
+
except Exception:
|
|
47
|
+
pass
|
|
48
|
+
return wp.to_dict()
|
|
49
|
+
|
|
50
|
+
def import_csv(self, path: str) -> int:
|
|
51
|
+
"""CSV 导入。列:name,address,lng,lat,start_time,duration_min,city"""
|
|
52
|
+
count = 0
|
|
53
|
+
with open(Path(path), encoding="utf-8") as f:
|
|
54
|
+
for row in csv.DictReader(f):
|
|
55
|
+
self.add_waypoint(
|
|
56
|
+
name=row["name"], address=row.get("address", ""),
|
|
57
|
+
lng=float(row["lng"]) if row.get("lng") else None,
|
|
58
|
+
lat=float(row["lat"]) if row.get("lat") else None,
|
|
59
|
+
start_time=row.get("start_time") or None,
|
|
60
|
+
duration_min=int(row.get("duration_min") or 0),
|
|
61
|
+
city=row.get("city", ""))
|
|
62
|
+
count += 1
|
|
63
|
+
return count
|
|
64
|
+
|
|
65
|
+
# ── 真实路线(高德优先,否则估算)─────────────────────
|
|
66
|
+
def real_leg(self, a: Waypoint, b: Waypoint, mode: str = "driving"):
|
|
67
|
+
"""两点路线:高德可用且有坐标时用真实数据,否则直线估算。"""
|
|
68
|
+
if self.amap_enabled and a.has_coord and b.has_coord:
|
|
69
|
+
r = self.amap.route(a.coord_str, b.coord_str, mode, a.city)
|
|
70
|
+
if r:
|
|
71
|
+
from .models import RouteLeg
|
|
72
|
+
return RouteLeg(from_id=a.id, to_id=b.id, from_name=a.name,
|
|
73
|
+
to_name=b.name, mode=mode,
|
|
74
|
+
distance_km=r["distance_km"],
|
|
75
|
+
duration_min=r["duration_min"], source="amap")
|
|
76
|
+
return self.reasoning.estimate_leg(a, b, mode)
|
|
77
|
+
|
|
78
|
+
# ── 综合分析(行政 Agent 核心输出)────────────────────
|
|
79
|
+
def plan(self, mode: str = "driving", optimize: bool = True) -> dict:
|
|
80
|
+
"""完整行程规划:优化顺序 + 冲突检测 + 聚类 + 守门人裁决。"""
|
|
81
|
+
opt = (self.reasoning.optimize_order(self.waypoints, mode)
|
|
82
|
+
if optimize else None)
|
|
83
|
+
conflicts = self.reasoning.detect_conflicts(self.waypoints, mode)
|
|
84
|
+
clusters = self.reasoning.cluster_by_proximity(self.waypoints)
|
|
85
|
+
dens = self.reasoning.density(self.waypoints, mode)
|
|
86
|
+
verdicts = self._gatekeeper(conflicts, clusters, dens)
|
|
87
|
+
return {
|
|
88
|
+
"waypoint_count": len(self.waypoints),
|
|
89
|
+
"amap_enabled": self.amap_enabled,
|
|
90
|
+
"optimized_route": opt,
|
|
91
|
+
"conflicts": conflicts,
|
|
92
|
+
"proximity_clusters": clusters,
|
|
93
|
+
"density": dens,
|
|
94
|
+
"gatekeeper_verdicts": [v.to_dict() for v in verdicts],
|
|
95
|
+
"map_data": self._to_amap_mapdata(opt),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# ── 行程守门人 ────────────────────────────────────────
|
|
99
|
+
def _gatekeeper(self, conflicts, clusters, dens) -> list:
|
|
100
|
+
verdicts = []
|
|
101
|
+
for c in conflicts:
|
|
102
|
+
if c["type"] == "time_conflict":
|
|
103
|
+
verdicts.append(ItineraryVerdict(
|
|
104
|
+
rule_id="time_conflict", action="time_conflict",
|
|
105
|
+
severity="critical", message=c["reason"], waypoint_ids=c["ids"]))
|
|
106
|
+
elif c["type"] == "unreachable":
|
|
107
|
+
verdicts.append(ItineraryVerdict(
|
|
108
|
+
rule_id="unreachable", action="unreachable",
|
|
109
|
+
severity="critical", message=c["reason"], waypoint_ids=c["ids"]))
|
|
110
|
+
if dens["cross_city"]:
|
|
111
|
+
verdicts.append(ItineraryVerdict(
|
|
112
|
+
rule_id="cross_city", action="cross_city", severity="warn",
|
|
113
|
+
message=f"行程跨城市:{','.join(dens['cities'])},注意城际交通时间",
|
|
114
|
+
payload={"cities": dens["cities"]}))
|
|
115
|
+
if dens["total_commute_min"] > 180:
|
|
116
|
+
verdicts.append(ItineraryVerdict(
|
|
117
|
+
rule_id="too_dense", action="too_dense", severity="warn",
|
|
118
|
+
message=f"总通勤 {dens['total_commute_min']:.0f} 分钟过长,建议精简或分天",
|
|
119
|
+
payload=dens))
|
|
120
|
+
for cl in clusters:
|
|
121
|
+
verdicts.append(ItineraryVerdict(
|
|
122
|
+
rule_id="reorder", action="suggest_reorder", severity="info",
|
|
123
|
+
message=f"这几处相近,建议顺路一起办:{' · '.join(cl)}",
|
|
124
|
+
payload={"cluster": cl}))
|
|
125
|
+
return verdicts
|
|
126
|
+
|
|
127
|
+
# ── 导出高德可视化格式(MapTaskData)──────────────────
|
|
128
|
+
def _to_amap_mapdata(self, opt) -> list:
|
|
129
|
+
"""转成高德 MapTaskData(POI + route),可喂给高德生成可视化地图。"""
|
|
130
|
+
data = []
|
|
131
|
+
for wp in self.waypoints:
|
|
132
|
+
if wp.has_coord:
|
|
133
|
+
data.append({"type": "poi", "lnglat": [wp.lng, wp.lat],
|
|
134
|
+
"text": wp.name, "remark": wp.address or wp.note})
|
|
135
|
+
if opt and opt.get("legs"):
|
|
136
|
+
id2wp = {w.id: w for w in self.waypoints}
|
|
137
|
+
for leg in opt["legs"]:
|
|
138
|
+
a, b = id2wp.get(leg["from_id"]), id2wp.get(leg["to_id"])
|
|
139
|
+
if a and b and a.has_coord and b.has_coord:
|
|
140
|
+
data.append({"type": "route", "routeType": leg["mode"],
|
|
141
|
+
"start": [a.lng, a.lat], "end": [b.lng, b.lat],
|
|
142
|
+
"remark": f"{a.name}→{b.name}"})
|
|
143
|
+
return data
|
|
144
|
+
|
|
145
|
+
def status(self) -> dict:
|
|
146
|
+
return {"waypoints": len(self.waypoints),
|
|
147
|
+
"amap_enabled": self.amap_enabled,
|
|
148
|
+
"with_coord": sum(1 for w in self.waypoints if w.has_coord)}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Foxclaw 内核层:进程管理、调度、事件脊柱、能力注册等 OS 式抽象。"""
|
|
2
|
+
from .process_manager import ProcessManager, ProcessRecord
|
|
3
|
+
from .spine import EventSpine, Event
|
|
4
|
+
from .capabilities import CapabilityRegistry
|
|
5
|
+
from .scheduler import Scheduler
|
|
6
|
+
from .policy_gate import PolicyGate, Verdict
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"ProcessManager", "ProcessRecord",
|
|
10
|
+
"EventSpine", "Event",
|
|
11
|
+
"CapabilityRegistry", "Scheduler",
|
|
12
|
+
"PolicyGate", "Verdict",
|
|
13
|
+
]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CapabilityRegistry —— 能力注册中枢(P1-3:God Object → Composition Root)。
|
|
3
|
+
|
|
4
|
+
审计结论:Foxclaw.__init__ 是个 God Object,子系统之间还形成两处对象环
|
|
5
|
+
(Supervisor⇄Orchestrator、agents⇄search_runtime⇄agent_port⇄agents)——组件直接
|
|
6
|
+
互相持有具体引用,违反 Clean Architecture。
|
|
7
|
+
|
|
8
|
+
方案:引入单一注册中枢。组件不再直接持有"同伴组件"的具体引用,而是持有本注册表、
|
|
9
|
+
按名字**惰性解析**(依赖倒置:依赖注册表抽象,而非具体同伴)。于是:
|
|
10
|
+
· 直接的"A→B 且 B→A"互持消失(两处直接环被打断);
|
|
11
|
+
· 解析统一走一个 hub(Composition Root 模式),构造顺序不再产生初始化环;
|
|
12
|
+
· 支持 register_lazy:重/可选能力(HKC、SearchRuntime)首次被解析时才构造。
|
|
13
|
+
|
|
14
|
+
注:hub 与组件之间仍是可达环(注册表持有组件、组件持有注册表),这是 Service Locator
|
|
15
|
+
的固有形态,由 GC 正常回收;本次要消除的是"具体同伴之间的直接互持",而非对象图可达性。
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
import threading
|
|
19
|
+
from typing import Any, Callable
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CapabilityRegistry:
|
|
23
|
+
"""按名字注册/解析能力。支持惰性(首次解析才构造)。线程安全。"""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self._caps: dict[str, Any] = {}
|
|
27
|
+
self._lazy: dict[str, Callable[[], Any]] = {}
|
|
28
|
+
self._lock = threading.RLock() # 只护 _caps/_lazy 表(快照,持锁时间极短)
|
|
29
|
+
self._build_locks: dict[str, threading.Lock] = {} # name -> 构造锁(per-name)
|
|
30
|
+
|
|
31
|
+
def register(self, name: str, handle: Any) -> None:
|
|
32
|
+
"""注册一个已构造好的能力实例(覆盖同名)。"""
|
|
33
|
+
with self._lock:
|
|
34
|
+
self._caps[name] = handle
|
|
35
|
+
|
|
36
|
+
def register_lazy(self, name: str, factory: Callable[[], Any]) -> None:
|
|
37
|
+
"""注册惰性能力:首次 get(name) 时调用 factory 构造并缓存。"""
|
|
38
|
+
with self._lock:
|
|
39
|
+
self._lazy[name] = factory
|
|
40
|
+
|
|
41
|
+
def get(self, name: str, default: Any = None) -> Any:
|
|
42
|
+
"""解析能力。已注册直接返回;惰性则首次构造并缓存;都没有返回 default。
|
|
43
|
+
|
|
44
|
+
【F1 修复】慢惰性构造(如 search_runtime 首建要装 HKC+载模型)**不持全局锁**,
|
|
45
|
+
改用 per-name 构造锁:同一能力串行构造(只建一次、无双重构造),不同能力互不阻塞
|
|
46
|
+
(避免"首建 search 时,并发的 cross_request 解析 agents 被卡住")。factory 内部
|
|
47
|
+
可安全再 get 其它能力(全局锁此时未被本调用持有)。"""
|
|
48
|
+
with self._lock:
|
|
49
|
+
if name in self._caps:
|
|
50
|
+
return self._caps[name]
|
|
51
|
+
factory = self._lazy.get(name)
|
|
52
|
+
if factory is None:
|
|
53
|
+
return default
|
|
54
|
+
blk = self._build_locks.get(name)
|
|
55
|
+
if blk is None:
|
|
56
|
+
blk = self._build_locks[name] = threading.Lock()
|
|
57
|
+
# 出全局锁,用 per-name 锁串行化"同一能力"的构造
|
|
58
|
+
with blk:
|
|
59
|
+
with self._lock:
|
|
60
|
+
if name in self._caps: # 双检:构造期间可能已被别的线程建好
|
|
61
|
+
return self._caps[name]
|
|
62
|
+
inst = factory() # 慢构造:不持全局锁,不阻塞其它能力解析
|
|
63
|
+
with self._lock:
|
|
64
|
+
self._caps[name] = inst
|
|
65
|
+
return inst
|
|
66
|
+
|
|
67
|
+
def has(self, name: str) -> bool:
|
|
68
|
+
with self._lock:
|
|
69
|
+
return name in self._caps or name in self._lazy
|
|
70
|
+
|
|
71
|
+
def names(self) -> list[str]:
|
|
72
|
+
with self._lock:
|
|
73
|
+
return sorted(set(self._caps) | set(self._lazy))
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PolicyGate —— 统一准入门(v2 ① Step4)。
|
|
3
|
+
|
|
4
|
+
审计:系统有两个"门",分属不同策略域、返回不同裁决形状:
|
|
5
|
+
· Harness(推理漂移:语义/工具/范围)—— 内联在 _think,返回 HarnessResult(PASS/WARN/BLOCK);
|
|
6
|
+
· skill-guard(动作权限:destructive/trust/deny>ask>allow)—— 在 SkillAdapter.invoke,返回 PermissionDecision。
|
|
7
|
+
|
|
8
|
+
PolicyGate 把它们统一到**一套 Verdict 词汇(allow/warn/deny/confirm)+ 一个接口**下:
|
|
9
|
+
引擎实现不动(它们是策略实现),门面只做**归一化** + 动作路径的**统一 policy.decision 审计事件**。
|
|
10
|
+
两条路都经门面走(pass-through:底层同一次 check/evaluate、同一结果 → 行为逐字不变)。
|
|
11
|
+
|
|
12
|
+
不 import Harness/skills(避免 kernel→上层环):harness 作参数传入(鸭子类型);
|
|
13
|
+
动作评估器 evaluate 由 Foxclaw **注入**。
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from typing import Any, Callable, Optional
|
|
18
|
+
|
|
19
|
+
from ..core.governance_audit import new_decision_id
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Verdict:
|
|
24
|
+
allowed: bool
|
|
25
|
+
level: str # allow | warn | deny | confirm
|
|
26
|
+
reason: str = ""
|
|
27
|
+
source: str = "" # harness | permission
|
|
28
|
+
raw: Any = None # 原始 HarnessResult / PermissionDecision(调用方按需取用)
|
|
29
|
+
# 【P3-4】每次裁决的唯一 ID(P1-2 治理审计的关联键)。放最后+默认值:既有位置参数
|
|
30
|
+
# 构造(如 Verdict(True,"allow",...,"permission",None))不受影响。
|
|
31
|
+
decision_id: str = field(default="")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PolicyGate:
|
|
35
|
+
def __init__(self, spine=None, action_evaluator: Optional[Callable] = None,
|
|
36
|
+
auditor=None):
|
|
37
|
+
self._spine = spine
|
|
38
|
+
self._eval = action_evaluator # skills.permission.evaluate,注入(避免 kernel→skills 环)
|
|
39
|
+
# 【P1-2】统一治理审计(可空:standalone 无审计)。鸭子类型注入,避免 kernel→core 之外
|
|
40
|
+
# 的装配环;core.governance_audit.GovernanceAuditor 是唯一预期实现。
|
|
41
|
+
self._auditor = auditor
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def auditor(self):
|
|
45
|
+
"""SkillAdapter 经此取审计器,把执行终态关联到裁决 decision_id(P3-4 跨层关联)。"""
|
|
46
|
+
return self._auditor
|
|
47
|
+
|
|
48
|
+
# ── 推理准入(Harness)──────────────────────────────────────
|
|
49
|
+
def check_reasoning(self, harness, node) -> Verdict:
|
|
50
|
+
"""经统一门做推理漂移准入。底层仍是那一次 harness.check(同一 HarnessResult),
|
|
51
|
+
Harness 自身已 emit harness_block/warn 事件,门面**不重复 emit**。行为一致。
|
|
52
|
+
|
|
53
|
+
【P1-2】非 allow(warn/deny)时另记一条 governance.decision(layer=harness)——
|
|
54
|
+
那是**统一审计流**的新增记录,不是重复 emit 既有 harness_* 事件;PASS 高频常态不记。"""
|
|
55
|
+
r = harness.check(node)
|
|
56
|
+
level = "deny" if r.is_blocked else ("warn" if r.is_warned else "allow")
|
|
57
|
+
v = Verdict(allowed=not r.is_blocked, level=level,
|
|
58
|
+
reason=(r.reason or ""), source="harness", raw=r,
|
|
59
|
+
decision_id=new_decision_id())
|
|
60
|
+
if level != "allow":
|
|
61
|
+
self._audit(v, actor=getattr(node, "agent_id", ""),
|
|
62
|
+
action=f"reasoning:{getattr(node, 'type', '')}",
|
|
63
|
+
detail={"node_id": getattr(node, "id", ""),
|
|
64
|
+
"dominant_dim": getattr(r, "dominant_dim", "")})
|
|
65
|
+
return v
|
|
66
|
+
|
|
67
|
+
# ── 动作准入(skill-guard)──────────────────────────────────
|
|
68
|
+
def check_action(self, skill, args: dict, rules=None,
|
|
69
|
+
confirmed: bool = False, mode: str = "strict",
|
|
70
|
+
taint_level: int = 0, taint_threshold: int = 0) -> Verdict:
|
|
71
|
+
"""经统一门做动作权限准入。底层仍是 permission.evaluate(同一 PermissionDecision);
|
|
72
|
+
归一化为 Verdict,并对**非 allow**发统一 policy.decision.* 审计事件(动作路径原本无统一事件)。
|
|
73
|
+
|
|
74
|
+
【P3-4】每次裁决都带 decision_id(allow 也带——调用方 SkillAdapter 拿它做执行审计的
|
|
75
|
+
correlation_id,把「谁批准的」与「执行结果」join 起来)。
|
|
76
|
+
|
|
77
|
+
【污点门槛透传】taint_level/threshold 原样交给评估器。此前门面没接这两个参数,导致
|
|
78
|
+
permission.evaluate 的 Stage 3.5(碰过敏感数据 → 外发/副作用须确认)只有工具路径能触发,
|
|
79
|
+
技能路径永不触发。注入的评估器(permission.evaluate / sentinel.evaluate)签名都带这两个
|
|
80
|
+
kwarg;默认 0 = 关闭,既有调用方行为不变。"""
|
|
81
|
+
if self._eval is None:
|
|
82
|
+
return Verdict(True, "allow", "无动作评估器(降级放行)", "permission", None,
|
|
83
|
+
decision_id=new_decision_id())
|
|
84
|
+
d = self._eval(skill, args, rules=rules, confirmed=confirmed, mode=mode,
|
|
85
|
+
taint_level=taint_level, taint_threshold=taint_threshold)
|
|
86
|
+
level = {"allow": "allow", "ask": "confirm", "deny": "deny"}.get(d.decision, "deny")
|
|
87
|
+
v = Verdict(allowed=(d.decision == "allow"), level=level,
|
|
88
|
+
reason=(d.reason or ""), source="permission", raw=d,
|
|
89
|
+
decision_id=new_decision_id())
|
|
90
|
+
if level != "allow":
|
|
91
|
+
self._emit(v, getattr(skill, "id", ""))
|
|
92
|
+
self._audit(v, actor="", action=f"action:{getattr(skill, 'id', '')}",
|
|
93
|
+
detail={"stage": getattr(d, "stage", 0),
|
|
94
|
+
"trust_downgrade": getattr(d, "trust_downgrade", False)})
|
|
95
|
+
return v
|
|
96
|
+
|
|
97
|
+
def _emit(self, v: Verdict, skill_id: str) -> None:
|
|
98
|
+
if self._spine is None:
|
|
99
|
+
return
|
|
100
|
+
try:
|
|
101
|
+
self._spine.publish(f"policy.decision.{v.level}", {
|
|
102
|
+
"source": v.source, "level": v.level,
|
|
103
|
+
"reason": v.reason, "skill": skill_id,
|
|
104
|
+
"decision_id": v.decision_id}, source="policy_gate")
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
def _audit(self, v: Verdict, actor: str, action: str, detail: dict) -> None:
|
|
109
|
+
"""记统一治理审计流(P1-2)。审计器可空;记录失败绝不影响裁决(审计器内部已兜)。"""
|
|
110
|
+
if self._auditor is None:
|
|
111
|
+
return
|
|
112
|
+
self._auditor.record(layer=v.source, actor=actor, action=action,
|
|
113
|
+
verdict=v.level, reason=v.reason,
|
|
114
|
+
detail=detail, decision_id=v.decision_id)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""
|
|
2
|
+
进程管理器 (ProcessManager)
|
|
3
|
+
|
|
4
|
+
把每个任务建模成操作系统的"进程",管理其完整生命周期:
|
|
5
|
+
created → queued → scheduled → running → completed / failed
|
|
6
|
+
|
|
7
|
+
借鉴 Codex 版 Foxclaw 的 ProcessManager,适配本项目:
|
|
8
|
+
- 与认知调度器结合:进程携带调度决策(优先级/路由)
|
|
9
|
+
- 与真值管线结合:状态变更可发事件
|
|
10
|
+
- 保留 state_history:每个进程的完整状态轨迹可追溯(对齐可观测原则)
|
|
11
|
+
|
|
12
|
+
这让"任务执行"从一次性函数调用,变成可观测、可追溯的"进程生命周期"。
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
import threading
|
|
16
|
+
import time
|
|
17
|
+
import uuid
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# 合法状态与允许的流转(状态机约束:防止非法跳转)
|
|
22
|
+
_VALID_TRANSITIONS = {
|
|
23
|
+
"created": {"queued", "failed"},
|
|
24
|
+
"queued": {"scheduled", "failed"},
|
|
25
|
+
"scheduled": {"running", "failed"},
|
|
26
|
+
"running": {"completed", "failed"},
|
|
27
|
+
"completed": set(), # 终态
|
|
28
|
+
"failed": set(), # 终态
|
|
29
|
+
}
|
|
30
|
+
_TERMINAL = {"completed", "failed"}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class ProcessRecord:
|
|
35
|
+
task: str
|
|
36
|
+
agent_id: str = None
|
|
37
|
+
state: str = "created"
|
|
38
|
+
process_id: str = field(default_factory=lambda: f"proc_{uuid.uuid4().hex[:8]}")
|
|
39
|
+
priority: float = 0.0
|
|
40
|
+
route_hint: str = None
|
|
41
|
+
created_at: float = field(default_factory=time.time)
|
|
42
|
+
updated_at: float = field(default_factory=time.time)
|
|
43
|
+
state_history: list = field(default_factory=list)
|
|
44
|
+
fail_reason: str = None
|
|
45
|
+
|
|
46
|
+
def to_dict(self) -> dict:
|
|
47
|
+
return {
|
|
48
|
+
"process_id": self.process_id, "task": self.task,
|
|
49
|
+
"agent_id": self.agent_id, "state": self.state,
|
|
50
|
+
"priority": self.priority, "route_hint": self.route_hint,
|
|
51
|
+
"state_history": self.state_history, "fail_reason": self.fail_reason,
|
|
52
|
+
"created_at": self.created_at, "updated_at": self.updated_at,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ProcessManager:
|
|
57
|
+
"""进程生命周期管理。每个任务一个进程,状态受状态机约束。"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, bus=None, history_limit: int = 1000):
|
|
60
|
+
self._processes: dict[str, ProcessRecord] = {}
|
|
61
|
+
self.bus = bus # 可选:状态变更发事件
|
|
62
|
+
self._history_limit = history_limit
|
|
63
|
+
# 【深审优化】活跃(非终态)进程数的 O(1) 增量计数:create 时 +1、进入终态时 -1
|
|
64
|
+
# (集中在 _transition)。供 CognitiveState 每次 lifecycle 事件 O(1) 取用,
|
|
65
|
+
# 免去原先每事件 stats() 遍历全部进程(O(n),n≤1000)。
|
|
66
|
+
self._active = 0
|
|
67
|
+
# 并行子 Agent 会让多个线程并发 create/mark_*/_evict 同一个 _processes dict,
|
|
68
|
+
# 且状态机流转是"读 state → 校验 → 写 state"的非原子序列。RLock 保证:
|
|
69
|
+
# · dict 增删与遍历(list/stats/_evict)不撞"dict changed size";
|
|
70
|
+
# · 同一进程的并发二次流转不会都通过终态校验后重复 transition。
|
|
71
|
+
# 可重入:_emit→bus.publish 的处理器可能回调 pm 的方法(同线程重入安全)。
|
|
72
|
+
self._lock = threading.RLock()
|
|
73
|
+
|
|
74
|
+
def create(self, task: str, agent_id: str = None,
|
|
75
|
+
schedule: dict = None) -> ProcessRecord:
|
|
76
|
+
"""创建进程(任务进入系统)。携带调度决策。"""
|
|
77
|
+
rec = ProcessRecord(
|
|
78
|
+
task=task, agent_id=agent_id,
|
|
79
|
+
priority=(schedule or {}).get("priority", 0.0),
|
|
80
|
+
route_hint=(schedule or {}).get("route_hint"),
|
|
81
|
+
)
|
|
82
|
+
with self._lock:
|
|
83
|
+
rec.state_history.append("created")
|
|
84
|
+
# 创建后立即入队 → 已调度(调度决策此时已有)
|
|
85
|
+
self._transition(rec, "queued")
|
|
86
|
+
if schedule:
|
|
87
|
+
self._transition(rec, "scheduled")
|
|
88
|
+
self._processes[rec.process_id] = rec
|
|
89
|
+
self._active += 1 # 新进程为活跃(此刻处于 queued/scheduled 非终态)
|
|
90
|
+
self._evict_if_needed()
|
|
91
|
+
self._emit(rec)
|
|
92
|
+
return rec
|
|
93
|
+
|
|
94
|
+
def mark_running(self, process_id: str) -> ProcessRecord:
|
|
95
|
+
with self._lock:
|
|
96
|
+
rec = self._processes.get(process_id)
|
|
97
|
+
if rec is None:
|
|
98
|
+
return None
|
|
99
|
+
# 若仍在 queued(create 未带 schedule),先补 scheduled 再 running
|
|
100
|
+
if rec.state == "queued":
|
|
101
|
+
self._transition(rec, "scheduled")
|
|
102
|
+
return self._safe_transition(process_id, "running")
|
|
103
|
+
|
|
104
|
+
def mark_completed(self, process_id: str) -> ProcessRecord:
|
|
105
|
+
with self._lock:
|
|
106
|
+
return self._safe_transition(process_id, "completed")
|
|
107
|
+
|
|
108
|
+
def mark_failed(self, process_id: str, reason: str = "") -> ProcessRecord:
|
|
109
|
+
with self._lock:
|
|
110
|
+
rec = self._processes.get(process_id)
|
|
111
|
+
if rec is None:
|
|
112
|
+
return None
|
|
113
|
+
# 已是终态 → 幂等忽略(重试/并发下的二次标记不算错误,不崩溃)
|
|
114
|
+
if rec.state in _TERMINAL:
|
|
115
|
+
return rec
|
|
116
|
+
self._transition(rec, "failed")
|
|
117
|
+
rec.fail_reason = reason
|
|
118
|
+
self._emit(rec)
|
|
119
|
+
return rec
|
|
120
|
+
|
|
121
|
+
def get(self, process_id: str) -> ProcessRecord:
|
|
122
|
+
with self._lock:
|
|
123
|
+
return self._processes.get(process_id)
|
|
124
|
+
|
|
125
|
+
def list(self, state: str = None, limit: int = 50) -> list:
|
|
126
|
+
with self._lock:
|
|
127
|
+
items = list(self._processes.values())
|
|
128
|
+
if state:
|
|
129
|
+
items = [r for r in items if r.state == state]
|
|
130
|
+
items.sort(key=lambda r: r.created_at, reverse=True)
|
|
131
|
+
return [r.to_dict() for r in items[:limit]]
|
|
132
|
+
|
|
133
|
+
def active_count(self) -> int:
|
|
134
|
+
"""活跃(非终态:queued/scheduled/running/created)进程数,O(1) 增量维护。
|
|
135
|
+
供高频 lifecycle 事件消费(免去每次 stats() 遍历全部进程)。"""
|
|
136
|
+
with self._lock:
|
|
137
|
+
return self._active
|
|
138
|
+
|
|
139
|
+
def stats(self) -> dict:
|
|
140
|
+
with self._lock:
|
|
141
|
+
counts = {}
|
|
142
|
+
for r in self._processes.values():
|
|
143
|
+
counts[r.state] = counts.get(r.state, 0) + 1
|
|
144
|
+
return {"total": len(self._processes), "by_state": counts,
|
|
145
|
+
"active": self._active}
|
|
146
|
+
|
|
147
|
+
# ── 内部 ──
|
|
148
|
+
def _safe_transition(self, process_id: str, next_state: str) -> ProcessRecord:
|
|
149
|
+
rec = self._processes.get(process_id)
|
|
150
|
+
if rec is None:
|
|
151
|
+
return None
|
|
152
|
+
# 已是终态 → 幂等忽略(不崩溃)
|
|
153
|
+
if rec.state in _TERMINAL:
|
|
154
|
+
return rec
|
|
155
|
+
# 已在目标状态 → 幂等忽略(重复标记不报错)
|
|
156
|
+
if rec.state == next_state:
|
|
157
|
+
return rec
|
|
158
|
+
self._transition(rec, next_state)
|
|
159
|
+
self._emit(rec)
|
|
160
|
+
return rec
|
|
161
|
+
|
|
162
|
+
def _transition(self, rec: ProcessRecord, next_state: str):
|
|
163
|
+
"""受状态机约束的状态流转。真正非法的跳转(如跳过中间态)抛错 fail-fast。
|
|
164
|
+
|
|
165
|
+
【P5-3】终态守卫:已是终态的进程再流转 → 幂等忽略(不改状态、不重复 -1)。
|
|
166
|
+
此前"不会重复 -1"完全依赖上游 _safe_transition/mark_failed 的守卫——若未来
|
|
167
|
+
新增调用点直接调本方法,活跃计数会被重复扣减(可扣成负数,stats 失真)。
|
|
168
|
+
这里是双保险。注意行为变更:终态 → 任何状态,从抛 ValueError 变为静默忽略。
|
|
169
|
+
"""
|
|
170
|
+
# 已是终态 → 幂等忽略(不再 -1,不再改状态)
|
|
171
|
+
if rec.state in _TERMINAL:
|
|
172
|
+
return
|
|
173
|
+
allowed = _VALID_TRANSITIONS.get(rec.state, set())
|
|
174
|
+
if next_state not in allowed:
|
|
175
|
+
raise ValueError(
|
|
176
|
+
f"非法状态流转: {rec.state} → {next_state}(进程 {rec.process_id})")
|
|
177
|
+
# 进入终态 → 活跃计数 -1(每进程恰好 create 时 +1、首次入终态时 -1;
|
|
178
|
+
# 上面的终态守卫保证不会重复扣减)。
|
|
179
|
+
if next_state in _TERMINAL:
|
|
180
|
+
self._active -= 1
|
|
181
|
+
rec.state = next_state
|
|
182
|
+
rec.updated_at = time.time()
|
|
183
|
+
rec.state_history.append(next_state)
|
|
184
|
+
|
|
185
|
+
def _emit(self, rec: ProcessRecord):
|
|
186
|
+
# 【v2 ① Step3·Lifecycle 统一】emit 唯一生命周期词汇 task.lifecycle.<state>
|
|
187
|
+
# (取代旧 process.state_changed;Scheduler 委托本 pm,不再自发另一套词汇)。
|
|
188
|
+
if self.bus is not None:
|
|
189
|
+
try:
|
|
190
|
+
self.bus.publish(f"task.lifecycle.{rec.state}", {
|
|
191
|
+
"process_id": rec.process_id, "state": rec.state, "phase": rec.state,
|
|
192
|
+
"task": rec.task, "agent_id": rec.agent_id,
|
|
193
|
+
})
|
|
194
|
+
except Exception:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
def _evict_if_needed(self):
|
|
198
|
+
"""超出上限时,清理最老的终态进程(保留活跃进程)。"""
|
|
199
|
+
if len(self._processes) <= self._history_limit:
|
|
200
|
+
return
|
|
201
|
+
terminal = [(pid, r) for pid, r in self._processes.items()
|
|
202
|
+
if r.state in _TERMINAL]
|
|
203
|
+
terminal.sort(key=lambda x: x[1].created_at)
|
|
204
|
+
for pid, _ in terminal[:len(self._processes) - self._history_limit]:
|
|
205
|
+
del self._processes[pid]
|