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,443 @@
|
|
|
1
|
+
"""ToolExecMixin:LLM 自主工具选择的分发执行 + 工具调用循环。
|
|
2
|
+
|
|
3
|
+
从 `foxclaw/agents/base.py` 搬移出来的工具执行相关方法。
|
|
4
|
+
|
|
5
|
+
实现要点:
|
|
6
|
+
- 闸门/执行拆成**模块级函数**(`_guard_tool(agent, ...)`),而不是自调用 `self.xxx`。
|
|
7
|
+
原因:`_dispatch_tool` 一直被单测以 `CognitiveAgent._dispatch_tool(fake_agent, ...)` 的
|
|
8
|
+
方式用 SimpleNamespace 当 self 调用(鸭子类型),自调用新方法会 AttributeError。
|
|
9
|
+
- 每个工具调用跑在守护线程里,带单工具硬超时;只读且声明 parallel_safe 的工具同轮并发。
|
|
10
|
+
- 会话污点在 worker 线程里只收集、回主线程合并(contextvar 按线程隔离,直接 set 会丢)。
|
|
11
|
+
"""
|
|
12
|
+
import threading
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
from ._context import _taint_get, _taint_raise, _untrusted_get
|
|
16
|
+
from ...core import settings
|
|
17
|
+
from ...core.injection_guard import redact_obj, redact_secrets
|
|
18
|
+
|
|
19
|
+
#: 同轮最多并发几个工具(再多也是网络等待,收益递减;避免打爆 provider/搜索服务)
|
|
20
|
+
MAX_PARALLEL = 4
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _tool_timeout() -> float:
|
|
24
|
+
"""单工具硬超时(秒),FOXCLAW_TOOL_TIMEOUT 可调;默认 45s。
|
|
25
|
+
|
|
26
|
+
值写错不拖垮工具执行(退默认),但会 WARN 一次指出是哪个变量错了 ——
|
|
27
|
+
此前是 try/except 静默退默认,用户的设置有错也永远不知道。
|
|
28
|
+
"""
|
|
29
|
+
v = float(settings.get_safe("FOXCLAW_TOOL_TIMEOUT"))
|
|
30
|
+
return v if v > 0 else 45.0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _Spawn:
|
|
34
|
+
"""把一次工具调用扔进守护线程:超时后工具轮照常继续,不被一个慢工具整轮拖死。
|
|
35
|
+
|
|
36
|
+
用 daemon 线程而非 ThreadPoolExecutor:被放弃的慢调用不会在解释器退出时阻塞进程
|
|
37
|
+
(线程杀不掉,但至少不挡收尾)。
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
__slots__ = ("done", "box", "thread")
|
|
41
|
+
|
|
42
|
+
def __init__(self, fn):
|
|
43
|
+
self.done = threading.Event()
|
|
44
|
+
self.box: dict = {}
|
|
45
|
+
|
|
46
|
+
def _run():
|
|
47
|
+
try:
|
|
48
|
+
self.box["v"] = fn()
|
|
49
|
+
except BaseException as e: # 统一交主线程降级成错误文本
|
|
50
|
+
self.box["e"] = e
|
|
51
|
+
finally:
|
|
52
|
+
self.done.set()
|
|
53
|
+
|
|
54
|
+
self.thread = threading.Thread(target=_run, daemon=True, name="sf-tool")
|
|
55
|
+
self.thread.start()
|
|
56
|
+
|
|
57
|
+
def wait(self, timeout: float):
|
|
58
|
+
"""返回 (是否按时完成, 结果);超时 (False, None),异常在主线程重抛。"""
|
|
59
|
+
if not self.done.wait(timeout):
|
|
60
|
+
return False, None
|
|
61
|
+
if "e" in self.box:
|
|
62
|
+
raise self.box["e"]
|
|
63
|
+
return True, self.box.get("v")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _guard_tool(agent, name: str, args: dict):
|
|
67
|
+
"""跑权限管线,返回 (提前结束文本 | None, 能力标签 dict)。
|
|
68
|
+
|
|
69
|
+
动作级安全阀(真正的兜底,不指望 prompt 隔离挡住弱模型):
|
|
70
|
+
① per-tool deny 闸(kill-switch);
|
|
71
|
+
② 未知工具 → 不执行;
|
|
72
|
+
③ **四阶段权限管线**(permission.evaluate):按能力分级裁决 allow/ask/deny——
|
|
73
|
+
只读放行;写/发送/不可逆/外部副作用/未声明的新工具 → **人在回路(HITL)**,只出草稿不自动执行;
|
|
74
|
+
④ web_search 的个人数据否决留到执行阶段(需要 query)。
|
|
75
|
+
纯函数式、无副作用,因此并发调度前可在主线程一次性裁决完。
|
|
76
|
+
"""
|
|
77
|
+
import types as _types
|
|
78
|
+
from ...core import tools as _tools
|
|
79
|
+
from ...core import injection_guard as _ig
|
|
80
|
+
from ...skills import permission as _perm
|
|
81
|
+
if _tools.is_tool_denied(name):
|
|
82
|
+
return f"(工具 {name} 已被策略禁用,未执行)", {}
|
|
83
|
+
_reg = _tools.default_registry()
|
|
84
|
+
if not _reg.has(name):
|
|
85
|
+
return f"(未知工具 {name},未执行)", {}
|
|
86
|
+
# ── 参数校验(用注册表里同一份 JSON Schema)────────────────────
|
|
87
|
+
# 修复前:schema 只发给模型,执行侧不校验 → 模型给错类型/漏参数会直接流到
|
|
88
|
+
# 下游。且下方 permission Stage 1 因 params={} 恒不触发,只靠手工 if-not 兜。
|
|
89
|
+
# 新增校验闸后,错误**回注模型**让其自修正,而不是静默用空参数继续
|
|
90
|
+
# (那会让模型误以为工具已执行,进而编造基于"工具结果"的结论)。
|
|
91
|
+
_errs = _reg.validate_args(name, args or {})
|
|
92
|
+
if _errs:
|
|
93
|
+
agent._think(content=f"工具 {name} 入参校验失败: {'; '.join(_errs)[:120]}",
|
|
94
|
+
node_type="tool_intent", conf=0.9)
|
|
95
|
+
return (f"(工具 {name} 入参不合法: {'; '.join(_errs)}。"
|
|
96
|
+
f"请按工具声明修正参数后重试。)"), {}
|
|
97
|
+
# ── 权限管线:动作级闸门。构造 tool-spec 交四阶段裁决 ──
|
|
98
|
+
cap = _tools.tool_capability(name)
|
|
99
|
+
# 传入真实 params schema,使 permission Stage 1(必填校验)真正生效,
|
|
100
|
+
# 不再依赖下游手工检查兜底。兼容 permission 的两种 params 格式。
|
|
101
|
+
_schema = _reg.param_schema(name)
|
|
102
|
+
_req = set(_schema.get("required") or [])
|
|
103
|
+
_params = {k: {"required": k in _req, "type": v.get("type")}
|
|
104
|
+
for k, v in (_schema.get("properties") or {}).items()
|
|
105
|
+
if isinstance(v, dict)}
|
|
106
|
+
spec = _types.SimpleNamespace(id=f"tool:{name}", params=_params, **cap)
|
|
107
|
+
# 来源不可信(外部聊天渠道 / 远程驱动)→ 按「已接触敏感数据」处理:写/发送/外发强制 HITL。
|
|
108
|
+
# 只读工具不受影响(污点分支只作用于副作用动作),所以读操作不会被这一条拖慢。
|
|
109
|
+
_taint = _taint_get(agent)
|
|
110
|
+
if _untrusted_get(agent):
|
|
111
|
+
_taint = max(_taint, _ig.TAINT_PERSONAL)
|
|
112
|
+
decision = _perm.evaluate(spec, args or {}, confirmed=False,
|
|
113
|
+
taint_level=_taint,
|
|
114
|
+
taint_threshold=_ig.TAINT_PERSONAL)
|
|
115
|
+
if decision.decision == "deny":
|
|
116
|
+
return f"(工具 {name} 被权限策略拒绝:{decision.reason},未执行)", cap
|
|
117
|
+
if decision.decision == "ask":
|
|
118
|
+
# 红线/副作用/未声明工具 → 人在回路:不自动执行,交回由用户确认(KERNEL §6 同旨)
|
|
119
|
+
agent._think(content=f"工具 {name} 需用户确认({decision.reason}),已暂停执行",
|
|
120
|
+
node_type="decision", conf=0.9)
|
|
121
|
+
return (f"(工具 {name} 属于需确认的动作:{decision.reason}。按安全策略我不会自动执行——"
|
|
122
|
+
f"这类涉及发送/转账/删除/修改/对外调用的红线动作只能由你亲自确认后进行;"
|
|
123
|
+
f"我可以先把要做的操作拟成草稿供你审。"), cap
|
|
124
|
+
return None, cap
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _invoke_tool(agent, name: str, args: dict, taint_sink: list | None = None) -> str:
|
|
128
|
+
"""执行工具本体,返回回注模型的文本。
|
|
129
|
+
|
|
130
|
+
taint_sink 非空 = 当前在 worker 线程:会话污点在 contextvar 里按线程隔离,
|
|
131
|
+
直接 `_taint_raise` 会写进线程自己的副本然后丢失 → 先收集,交调用方在主线程合并。
|
|
132
|
+
"""
|
|
133
|
+
from ...core import injection_guard as _ig
|
|
134
|
+
|
|
135
|
+
def _raise(level: int) -> None:
|
|
136
|
+
if taint_sink is not None:
|
|
137
|
+
taint_sink.append(level)
|
|
138
|
+
else:
|
|
139
|
+
_taint_raise(agent, level)
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
if name == "web_search":
|
|
143
|
+
from ...core import web_search as _ws
|
|
144
|
+
q = str((args or {}).get("query", "")).strip()
|
|
145
|
+
if not q:
|
|
146
|
+
return "(web_search 缺少 query 参数)"
|
|
147
|
+
if _ws.is_personal_query(q): # 私人账本/行程/记忆 → 绝不外发,让模型用本地信息答
|
|
148
|
+
return "(该问题属于用户本地数据,已跳过联网;请依据本地领域信息回答)"
|
|
149
|
+
payload = _ws.search_answer(q)
|
|
150
|
+
if not payload or (not payload.get("answer") and not payload.get("references")):
|
|
151
|
+
return ("联网搜索未返回结果(可能未配置搜索服务或网络不可用);"
|
|
152
|
+
"请如实告知用户暂时无法查询实时信息,切勿编造版本号/新闻/价格等。")
|
|
153
|
+
agent._think(content=f"工具 web_search「{q}」命中", node_type="tool_use", conf=0.9)
|
|
154
|
+
_raise(_ig.taint_level("web_search")) # 外部不可控数据 → 会话污点
|
|
155
|
+
return _ws.format_for_prompt(q, payload)
|
|
156
|
+
if name == "recall_memory":
|
|
157
|
+
q = str((args or {}).get("query", "")).strip()
|
|
158
|
+
# top_k 此前硬编码 5,模型传值无效 → 加边界校验后尊重模型意图
|
|
159
|
+
# (schema 已声明 top_k,1-20;越界回落默认,防一次召回拉爆上下文)
|
|
160
|
+
try:
|
|
161
|
+
top_k = max(1, min(20, int((args or {}).get("top_k", 5))))
|
|
162
|
+
except Exception:
|
|
163
|
+
top_k = 5
|
|
164
|
+
res = agent._recall_memory(q, top_k=top_k) if q else []
|
|
165
|
+
if not res:
|
|
166
|
+
return "(未召回到相关记忆)"
|
|
167
|
+
lines = "\n".join(f"- {r.get('content', '')}" for r in res[:top_k])
|
|
168
|
+
# 记忆可能被投毒(此前注入固化) → 当作不可信数据回注,防记忆投毒二次生效
|
|
169
|
+
_raise(_ig.taint_level("memory")) # 个人记忆 → 会话污点(高敏感)
|
|
170
|
+
return "【召回的相关记忆】\n" + _ig.wrap_untrusted(lines, source="memory")
|
|
171
|
+
except Exception as e:
|
|
172
|
+
return f"(工具 {name} 执行异常:{type(e).__name__})"
|
|
173
|
+
return f"(未知工具 {name},未执行)"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _timeout_text(name: str) -> str:
|
|
177
|
+
return (f"(工具 {name} 超过 {_tool_timeout():.0f}s 未返回,已跳过本次调用。"
|
|
178
|
+
f"请如实告知用户这一步没完成,不要编造结果。)")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _merge_taint(agent, sink: list) -> None:
|
|
182
|
+
for lvl in sink: # 只在主线程动会话污点(单调取 max)
|
|
183
|
+
try:
|
|
184
|
+
_taint_raise(agent, lvl)
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _exec_guarded(agent, name: str, args: dict) -> str:
|
|
190
|
+
"""已过闸门的单个工具:带超时执行 + 主线程合并污点。"""
|
|
191
|
+
sink: list = []
|
|
192
|
+
sp = _Spawn(lambda: _invoke_tool(agent, name, args, sink))
|
|
193
|
+
try:
|
|
194
|
+
ok, out = sp.wait(_tool_timeout())
|
|
195
|
+
except Exception as e:
|
|
196
|
+
return f"(工具 {name} 执行异常:{type(e).__name__})"
|
|
197
|
+
if not ok:
|
|
198
|
+
return _timeout_text(name)
|
|
199
|
+
_merge_taint(agent, sink)
|
|
200
|
+
return out
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _run_tools_batch(agent, calls: list) -> list:
|
|
204
|
+
"""按能力调度一批 tool_calls,返回与 calls **同序**的结果文本。
|
|
205
|
+
|
|
206
|
+
只读 + 显式声明 parallel_safe + 全部过闸 → 并发(典型:web_search + recall_memory
|
|
207
|
+
同轮发出,不再一个等一个);其余(写/发送/需确认/未声明)一律串行,避免同一批里
|
|
208
|
+
副作用动作互相覆盖 HITL 裁决。
|
|
209
|
+
"""
|
|
210
|
+
plans = []
|
|
211
|
+
for c in calls:
|
|
212
|
+
n = c.get("name", "")
|
|
213
|
+
a = c.get("args") or {}
|
|
214
|
+
early, cap = _guard_tool(agent, n, a)
|
|
215
|
+
plans.append((n, a, early, cap))
|
|
216
|
+
parallel = (len(plans) > 1 and len(plans) <= MAX_PARALLEL
|
|
217
|
+
and all(p[2] is None and p[3].get("parallel_safe") for p in plans))
|
|
218
|
+
if not parallel:
|
|
219
|
+
out = []
|
|
220
|
+
for p in plans:
|
|
221
|
+
if p[2] is not None:
|
|
222
|
+
out.append(p[2])
|
|
223
|
+
continue
|
|
224
|
+
# 串行走 agent._dispatch_tool(保留「实例级覆盖」这一扩展点,单测常这么替身);
|
|
225
|
+
# 没有该方法时才直接执行。
|
|
226
|
+
_disp = getattr(agent, "_dispatch_tool", None)
|
|
227
|
+
out.append(_disp(p[0], p[1]) if callable(_disp)
|
|
228
|
+
else _exec_guarded(agent, p[0], p[1]))
|
|
229
|
+
return out
|
|
230
|
+
spawns = []
|
|
231
|
+
for n, a, _early, _cap in plans:
|
|
232
|
+
sink: list = []
|
|
233
|
+
spawns.append((n, _Spawn(lambda n=n, a=a, s=sink: _invoke_tool(agent, n, a, s)), sink))
|
|
234
|
+
started = time.monotonic()
|
|
235
|
+
timeout = _tool_timeout()
|
|
236
|
+
out = []
|
|
237
|
+
for n, sp, sink in spawns:
|
|
238
|
+
left = max(0.0, timeout - (time.monotonic() - started))
|
|
239
|
+
try:
|
|
240
|
+
ok, val = sp.wait(left) # 各自独立计时:一个超时不拖累已完成的
|
|
241
|
+
except Exception as e:
|
|
242
|
+
out.append(f"(工具 {n} 执行异常:{type(e).__name__})")
|
|
243
|
+
continue
|
|
244
|
+
if not ok:
|
|
245
|
+
out.append(_timeout_text(n))
|
|
246
|
+
continue
|
|
247
|
+
_merge_taint(agent, sink)
|
|
248
|
+
out.append(val)
|
|
249
|
+
return out
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
#: checkpoint 里单步结果最多存多少字符(超出即标记 truncated,续跑时不复用、改为重跑)
|
|
253
|
+
_STEP_RESULT_MAX = 4000
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _step_key(name: str, args: dict) -> str:
|
|
257
|
+
"""步骤指纹:同名 + 同参(与字典序无关)= 同一步。续跑靠它匹配「这步之前做过」。"""
|
|
258
|
+
import hashlib
|
|
259
|
+
import json
|
|
260
|
+
try:
|
|
261
|
+
payload = json.dumps(args or {}, sort_keys=True, ensure_ascii=False, default=str)
|
|
262
|
+
except Exception:
|
|
263
|
+
payload = repr(args)
|
|
264
|
+
return f"{name}:{hashlib.sha256(payload.encode('utf-8')).hexdigest()[:16]}"
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _step_ok(result: str) -> bool:
|
|
268
|
+
"""这一步是否值得记进 checkpoint(只有「真的做成了」的步才可复用)。
|
|
269
|
+
|
|
270
|
+
工具失败/超时/需确认/未执行的回注文本全部以 "(" 开头(见 _guard_tool/_timeout_text/
|
|
271
|
+
_invoke_tool 的兜底),据此判定;复用它们等于把一次失败当成已完成,绝不能干。
|
|
272
|
+
"""
|
|
273
|
+
r = (result or "").strip()
|
|
274
|
+
return bool(r) and not r.startswith("(")
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _record_step(agent, call: dict, result: str) -> None:
|
|
278
|
+
"""把一个已完成的工具步骤写进 recovery 日志(fail-safe:异常一律吞掉)。
|
|
279
|
+
|
|
280
|
+
落盘前对 args/result 过 `redact_secrets`:recovery 日志会被回放读回,长期留在
|
|
281
|
+
`{data_dir}/recovery/` 下,而工具参数里经常出现 token/密钥(如鉴权类 CLI、带 key 的
|
|
282
|
+
HTTP 请求)。`_step_key` 仍用**未脱敏**的 args 计算,否则同一步骤的 key 前后不一致,
|
|
283
|
+
缓存去重和回放对不上。
|
|
284
|
+
"""
|
|
285
|
+
rec = getattr(agent, "_recovery", None)
|
|
286
|
+
tid = getattr(agent, "_session_id", "") or ""
|
|
287
|
+
if rec is None or not tid:
|
|
288
|
+
return
|
|
289
|
+
try:
|
|
290
|
+
text = redact_secrets(result or "")
|
|
291
|
+
rec.record_step(tid, {
|
|
292
|
+
"kind": "tool",
|
|
293
|
+
"name": call.get("name", ""),
|
|
294
|
+
"key": _step_key(call.get("name", ""), call.get("args") or {}),
|
|
295
|
+
"args": redact_obj(call.get("args") or {}),
|
|
296
|
+
"result": text[:_STEP_RESULT_MAX],
|
|
297
|
+
"truncated": len(result or "") > _STEP_RESULT_MAX,
|
|
298
|
+
})
|
|
299
|
+
except Exception:
|
|
300
|
+
pass
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _apply_failure_policy(agent, call: dict, result: str) -> tuple[str, str]:
|
|
304
|
+
"""按策略表决定「这次失败怎么处理」,返回 (回注文本, 失败类型)。
|
|
305
|
+
|
|
306
|
+
· 只读工具遇到超时/网络/异常 → **自动重试一次**(读操作重复执行无副作用);
|
|
307
|
+
· 写/发送/支付类一律不重试(重试 = 可能重复扣款/重复发消息),交人;
|
|
308
|
+
· 其余(未配置/参数错/红线)只注入动作提示,不重试。
|
|
309
|
+
重试只是把「要不要再来一次」从模型手里拿走,并不隐瞒失败:两次都失败时文本里
|
|
310
|
+
仍写明未完成。
|
|
311
|
+
"""
|
|
312
|
+
from ...core import failure_policy as _policy
|
|
313
|
+
from ...core import tools as _tools
|
|
314
|
+
name = call.get("name", "")
|
|
315
|
+
cap = _tools.tool_capability(name)
|
|
316
|
+
d = _policy.decide(result, tool=name, capability=cap, attempt=1)
|
|
317
|
+
if d.action == _policy.RETRY:
|
|
318
|
+
try:
|
|
319
|
+
retry = _run_tools_batch(agent, [call])[0]
|
|
320
|
+
except Exception:
|
|
321
|
+
retry = result
|
|
322
|
+
d2 = _policy.decide(retry, tool=name, capability=cap, attempt=2)
|
|
323
|
+
return _policy.annotate(retry, d2), d2.kind
|
|
324
|
+
return _policy.annotate(result, d), d.kind
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _settle(agent, call: dict, result: str) -> str:
|
|
328
|
+
"""一个工具步的收尾:核验 → 记账证据 → 只把「真的可用」的步写进 checkpoint。
|
|
329
|
+
|
|
330
|
+
返回回注给模型的最终文本(失败时附动作提示,成功时原样返回)。
|
|
331
|
+
"""
|
|
332
|
+
from ...core import tools as _tools
|
|
333
|
+
from ...core import verify as _verify
|
|
334
|
+
from ._context import _evidence_add
|
|
335
|
+
name = call.get("name", "")
|
|
336
|
+
args = call.get("args") or {}
|
|
337
|
+
text, kind = _apply_failure_policy(agent, call, result)
|
|
338
|
+
try:
|
|
339
|
+
cap = _tools.tool_capability(name)
|
|
340
|
+
# 核验看的是**工具原始返回**,不是我们追加过提示的文本 —— 否则提示里的
|
|
341
|
+
# "超时/未完成"字样会反过来影响判定。
|
|
342
|
+
v = _verify.verify(name, args, result, cap)
|
|
343
|
+
note = _verify.note_for_model(v, read_only=bool(cap.get("read_only")))
|
|
344
|
+
if note:
|
|
345
|
+
text = f"{text}\n{note}"
|
|
346
|
+
_evidence_add(agent, {
|
|
347
|
+
"tool": name, "kind": kind, "status": v.status,
|
|
348
|
+
"reason": v.reason, "evidence": [e.to_dict() for e in v.evidence],
|
|
349
|
+
})
|
|
350
|
+
# 判定"可复用"以核验结论为准,而不是文本开头是不是 "(" ——
|
|
351
|
+
# 失败文本追加提示后就不再以 "(" 开头,靠形态判会把它当成功存进断点。
|
|
352
|
+
if v.status != _verify.FAILED and _step_ok(result):
|
|
353
|
+
_record_step(agent, call, result)
|
|
354
|
+
except Exception:
|
|
355
|
+
pass # 核验/记账失败不影响工具结果本身
|
|
356
|
+
return text
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _run_tool_calls(agent, calls: list) -> list:
|
|
360
|
+
"""执行一批 tool_calls,并按断点续跑语义处理(返回与 calls 同序的结果文本):
|
|
361
|
+
· 命中 `_resume_steps` 里的同指纹步骤 → 复用上次结果,不再执行(断连/崩溃后重跑不重复);
|
|
362
|
+
· 其余照常执行(只读并发 / 写串行,单工具带超时);
|
|
363
|
+
· 每个结果过一遍失败策略表(只读可重试一次)+ 后置核验,证据挂到会话槽。
|
|
364
|
+
"""
|
|
365
|
+
from ._context import _resume_steps
|
|
366
|
+
cache = _resume_steps.get() or {}
|
|
367
|
+
out: list = [None] * len(calls)
|
|
368
|
+
todo: list = []
|
|
369
|
+
for i, c in enumerate(calls):
|
|
370
|
+
hit = cache.get(_step_key(c.get("name", ""), c.get("args") or {}))
|
|
371
|
+
if isinstance(hit, dict) and isinstance(hit.get("result"), str):
|
|
372
|
+
out[i] = (f"(断点续跑:{c.get('name', '')} 这一步此前已完成,"
|
|
373
|
+
f"以下为上次的结果,不必重做)\n{hit['result']}")
|
|
374
|
+
else:
|
|
375
|
+
todo.append(i)
|
|
376
|
+
if todo:
|
|
377
|
+
results = _run_tools_batch(agent, [calls[i] for i in todo])
|
|
378
|
+
for i, r in zip(todo, results):
|
|
379
|
+
out[i] = _settle(agent, calls[i], r)
|
|
380
|
+
return out
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class ToolExecMixin:
|
|
384
|
+
# 以下四个方法均为薄包装:真实 agent 走 self.xxx;单测可用 SimpleNamespace 直接调
|
|
385
|
+
# 模块级函数或 `CognitiveAgent._dispatch_tool(fake, ...)`(鸭子类型)。
|
|
386
|
+
def _guard_tool(self, name: str, args: dict):
|
|
387
|
+
return _guard_tool(self, name, args)
|
|
388
|
+
|
|
389
|
+
def _invoke_tool(self, name: str, args: dict, taint_sink: list | None = None) -> str:
|
|
390
|
+
return _invoke_tool(self, name, args, taint_sink)
|
|
391
|
+
|
|
392
|
+
def _exec_guarded(self, name: str, args: dict) -> str:
|
|
393
|
+
return _exec_guarded(self, name, args)
|
|
394
|
+
|
|
395
|
+
def _dispatch_tool(self, name: str, args: dict) -> str:
|
|
396
|
+
"""执行一个被 LLM 选中的工具,返回结果文本(注回模型)。"""
|
|
397
|
+
early, _cap = _guard_tool(self, name, args)
|
|
398
|
+
if early is not None:
|
|
399
|
+
return early
|
|
400
|
+
return _exec_guarded(self, name, args)
|
|
401
|
+
|
|
402
|
+
def _run_tools(self, calls: list) -> list:
|
|
403
|
+
return _run_tools_batch(self, calls)
|
|
404
|
+
|
|
405
|
+
def _reply_with_tools(self, task: str, messages: list, stop_event=None, max_rounds: int = 4):
|
|
406
|
+
"""LLM 自主工具调用循环(取代'调 LLM 前用关键词猜要不要联网/召回')。
|
|
407
|
+
返回最终答案文本;若 provider 实际不支持 tools → 返回 None(上层回退关键词增强链)。
|
|
408
|
+
工具轮用非流式解析,最终文本由上层 chunk 输出。"""
|
|
409
|
+
from ...core import tools as _tools
|
|
410
|
+
if not self.router.supports_tools():
|
|
411
|
+
return None
|
|
412
|
+
schemas = _tools.default_registry().schemas()
|
|
413
|
+
if not schemas:
|
|
414
|
+
return None
|
|
415
|
+
conv = list(messages)
|
|
416
|
+
for _ in range(max_rounds):
|
|
417
|
+
# 上下文预算:工具轮每轮追加 assistant.tool_calls + tool 结果,不压会撑爆窗口
|
|
418
|
+
# (Compactor 保证 tool_calls 与其结果同进同出,缺口见 core/compaction.py:100)。
|
|
419
|
+
# getattr 兜底:本 mixin 也被只实现 router 的轻量对象复用(_compact_context
|
|
420
|
+
# 定义在 CognitiveAgent 上),缺该方法时退化为不压缩。
|
|
421
|
+
_compact = getattr(self, "_compact_context", None)
|
|
422
|
+
if _compact is not None:
|
|
423
|
+
conv = _compact(conv, "tool_loop")
|
|
424
|
+
if stop_event is not None and stop_event.is_set():
|
|
425
|
+
return None # 断连 → None(回退;fallback 也会因 stop 立即终止)
|
|
426
|
+
res = self.router.call_tools(self.system_prompt, conv, schemas,
|
|
427
|
+
agent_id=self.AGENT_ID)
|
|
428
|
+
if not res.get("supported"):
|
|
429
|
+
return None # 端点实际不支持 tools → fallback
|
|
430
|
+
calls = res.get("tool_calls") or []
|
|
431
|
+
if not calls:
|
|
432
|
+
return res.get("text") or None # 模型直接给出最终答案;空答 → None → fallback(不再空回复不回退)
|
|
433
|
+
conv.append(res["assistant_msg"])
|
|
434
|
+
# 只读并发 / 写串行 / 单工具带超时 + 每步 checkpoint + 续跑复用已完成步骤
|
|
435
|
+
results = _run_tool_calls(self, calls)
|
|
436
|
+
for c, result in zip(calls, results):
|
|
437
|
+
conv.append(self.router.format_tool_result(c, result))
|
|
438
|
+
# 轮数超限 → 强制不再调工具、收敛出文本答案
|
|
439
|
+
final = self.router.call_tools(self.system_prompt, conv, schemas,
|
|
440
|
+
tool_choice="none", agent_id=self.AGENT_ID)
|
|
441
|
+
if not final.get("supported"):
|
|
442
|
+
return None # 收敛调用失败 → fallback(不再空回复不回退)
|
|
443
|
+
return final.get("text") or None # 收敛出空答 → None → fallback
|