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,360 @@
|
|
|
1
|
+
"""
|
|
2
|
+
统一任务生命周期(P1:5 个 run_task 收敛为 1)守卫测试 —— 全离线。
|
|
3
|
+
|
|
4
|
+
覆盖:
|
|
5
|
+
1. 结构收敛:4 个领域 Agent 复用 base 的模板 run_task(不各自重写),各自实现 _execute;
|
|
6
|
+
SupervisorAgent 作为路由器合法地保留自己的 run_task。
|
|
7
|
+
2. 统一异常兜底:_execute 抛 HarnessBlockedError → 模板统一返回结构化错误(一处定义,全 Agent 生效)。
|
|
8
|
+
3. 领域钩子生效:各 Agent 的 entities/recall 前缀/召回条数按覆盖生效(行为逐字保留)。
|
|
9
|
+
"""
|
|
10
|
+
import os, sys, asyncio, tempfile, shutil, types
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
os.environ.setdefault("HF_HUB_OFFLINE", "1")
|
|
13
|
+
os.environ.setdefault("TRANSFORMERS_OFFLINE", "1")
|
|
14
|
+
os.environ["FOXCLAW_EMBEDDING_MODEL"] = "foxclaw-nonexistent-fast"
|
|
15
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
16
|
+
|
|
17
|
+
from foxclaw.agents.base import CognitiveAgent, HarnessBlockedError
|
|
18
|
+
from foxclaw.agents.agents import (
|
|
19
|
+
FinanceAgent, OfficeAgent, SupervisorAgent)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_structural_convergence():
|
|
23
|
+
print("\n【1】结构收敛:全部 Agent 复用模板 run_task(主管经 P1-1 pipeline 化收敛)")
|
|
24
|
+
for A in (FinanceAgent, OfficeAgent):
|
|
25
|
+
assert A.run_task is CognitiveAgent.run_task, f"{A.__name__} 不应重写 run_task"
|
|
26
|
+
assert A._execute is not CognitiveAgent._execute, f"{A.__name__} 应实现 _execute"
|
|
27
|
+
# 【P1-1】主管也不再重写 run_task:路由分发拆成 SUPERVISOR_STAGES 七个 pipeline
|
|
28
|
+
# stage,走 base 统一模板(获得 hooks/流式/中断/统一 emit)。主管的差异收敛为
|
|
29
|
+
# _cognitive_pipeline 覆盖 + 领域钩子,而非另起生命周期。(本断言此前锁的是
|
|
30
|
+
# "主管保留自身 run_task" 的旧结构,P1-1 落地时同步翻转——又一处"测试锁旧行为"。)
|
|
31
|
+
assert SupervisorAgent.run_task is CognitiveAgent.run_task, \
|
|
32
|
+
"P1-1:主管不再重写 run_task(路由分发在 pipeline stage 里)"
|
|
33
|
+
assert SupervisorAgent._cognitive_pipeline is not CognitiveAgent._cognitive_pipeline, \
|
|
34
|
+
"主管应覆盖 _cognitive_pipeline 使用 SUPERVISOR_STAGES"
|
|
35
|
+
print(" ✓ 5 个 Agent 共用唯一 run_task 模板;主管差异收敛为 pipeline 覆盖(P1-1)")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_domain_hooks():
|
|
39
|
+
print("\n【2】领域钩子:entities / recall 前缀 / 召回条数 逐字保留")
|
|
40
|
+
# 钩子是纯函数,可脱离实例校验(lambda(self,task))
|
|
41
|
+
assert FinanceAgent._task_entities(None, "x") == ["财务", "收支", "储蓄"]
|
|
42
|
+
assert FinanceAgent._recall_query(None, "买车") == "财务 买车"
|
|
43
|
+
# 行政并入办公:同一 Agent 同时覆盖学习 + 日程两个域
|
|
44
|
+
assert OfficeAgent._recall_query(None, "开会") == "学习 知识 日程 行程 开会"
|
|
45
|
+
assert OfficeAgent._task_entities(None, "x") == ["学习", "知识", "文档", "日程", "行程", "会议"]
|
|
46
|
+
assert OfficeAgent._recall_topk == 8, "办公学习 Agent 覆盖为广泛召回 8"
|
|
47
|
+
assert CognitiveAgent._recall_topk == 5, "基类默认仍是 5"
|
|
48
|
+
print(" ✓ finance/admin/office 的 entities、recall 前缀、top_k 均按覆盖生效")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_unified_block_handling():
|
|
52
|
+
print("\n【3】统一异常兜底:_execute 抛 HarnessBlockedError → 结构化错误")
|
|
53
|
+
d = tempfile.mkdtemp(prefix="life_")
|
|
54
|
+
try:
|
|
55
|
+
from foxclaw import Foxclaw
|
|
56
|
+
os_ = Foxclaw(data_dir=d, shadow_rounds=50)
|
|
57
|
+
|
|
58
|
+
async def boom(self, task, context, session_id, memories):
|
|
59
|
+
raise HarnessBlockedError(types.SimpleNamespace(reason="模拟越界"), None)
|
|
60
|
+
|
|
61
|
+
# 只替换 finance 的领域执行,模板的兜底应把它转成结构化错误
|
|
62
|
+
os_.finance._execute = types.MethodType(boom, os_.finance)
|
|
63
|
+
r = asyncio.run(os_.finance.run_task("危险任务"))
|
|
64
|
+
assert r.get("error") == "harness_blocked", f"应兜底为 harness_blocked,实得 {r}"
|
|
65
|
+
assert r.get("reason") == "模拟越界" and "session_id" in r
|
|
66
|
+
print(f" ✓ 模板统一兜底: {r}")
|
|
67
|
+
asyncio.run(os_.aclose())
|
|
68
|
+
finally:
|
|
69
|
+
shutil.rmtree(d, ignore_errors=True)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_finance_verify_before_recommend():
|
|
73
|
+
"""【4·P5】记忆验证优先:财务事实以**当前账本(live analyze)**为准,召回记忆仅作历史上下文。
|
|
74
|
+
锁定 finance._execute 的 verify-before-recommend 设计——即便召回了**过时**记忆,权威事实源
|
|
75
|
+
仍是当前交易(`fin.analyze()`),记忆被显式降级为"历史财务记录"。防未来重构把过时记忆当事实源
|
|
76
|
+
(呼应范式"记忆是快照,非事实")。评估结论见方案 P5:该设计已存在,本测试是把它锁死的回归护栏。"""
|
|
77
|
+
print("\n【4】P5 记忆验证优先:财务事实以当前账本为准、记忆仅历史上下文")
|
|
78
|
+
d = tempfile.mkdtemp(prefix="p5_")
|
|
79
|
+
os_ = None
|
|
80
|
+
try:
|
|
81
|
+
from foxclaw import Foxclaw
|
|
82
|
+
os_ = Foxclaw(data_dir=d, shadow_rounds=50)
|
|
83
|
+
fin = os_.finance
|
|
84
|
+
# 给账本内核灌入真实交易 → analyze() 有据可依(当前数据源存在)
|
|
85
|
+
os_.finance_runtime.import_csv(
|
|
86
|
+
str(Path(__file__).parent.parent / "examples" / "sample_transactions.csv"))
|
|
87
|
+
# 捕获传给 LLM 的 prompt(不真调模型)
|
|
88
|
+
captured = {}
|
|
89
|
+
async def _capture(self, task, messages):
|
|
90
|
+
captured["prompt"] = messages[-1]["content"]
|
|
91
|
+
return "(模拟回答)"
|
|
92
|
+
fin._call_llm_web = types.MethodType(_capture, fin)
|
|
93
|
+
# 一条**过时**召回记忆:若被当作事实会误导用户
|
|
94
|
+
stale = [{"content": "历史:上月预算结余 8000 元", "id": "m1"}]
|
|
95
|
+
asyncio.run(fin._execute("这个月还能花多少?", {}, "s1", stale))
|
|
96
|
+
p = captured["prompt"]
|
|
97
|
+
assert "基于真实交易,请据此回答" in p, "缺 live analyze() 权威事实源(verify-before-recommend 退化)"
|
|
98
|
+
assert "历史财务记录" in p and "8000" in p, "召回记忆应作历史上下文出现,而非当前事实"
|
|
99
|
+
assert p.index("历史财务记录") < p.index("基于真实交易"), \
|
|
100
|
+
"结构应为:历史记忆在前、当前账本权威在后(措辞上 live『请据此回答』压过历史记忆)"
|
|
101
|
+
print(" ✓ live analyze() 作权威源『请据此回答』;过时记忆降级为『历史财务记录』(P5 已锁定)")
|
|
102
|
+
finally:
|
|
103
|
+
if os_ is not None:
|
|
104
|
+
asyncio.run(os_.aclose())
|
|
105
|
+
shutil.rmtree(d, ignore_errors=True)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_run_task_stream():
|
|
109
|
+
"""【5·D1】run_task 流式化:同步 generator 逐 stage 进度 + act 逐 token + 最终结果,且与非流式
|
|
110
|
+
run_task **行为一致**(共享 `_run_task_core`)。关键验证:`_act_stream` contextvar 真的跨
|
|
111
|
+
worker 线程 / asyncio 边界传到 `_call_llm`(否则一个 delta token 都不会有)。"""
|
|
112
|
+
print("\n【5】D1 run_task 流式化:stage 进度 + act token + 结果")
|
|
113
|
+
d = tempfile.mkdtemp(prefix="d1_")
|
|
114
|
+
os_ = None
|
|
115
|
+
try:
|
|
116
|
+
from foxclaw import Foxclaw
|
|
117
|
+
os_ = Foxclaw(data_dir=d, shadow_rounds=50)
|
|
118
|
+
# 单 Agent 认知管线(7 stage)用领域 Agent 验证:主管走的是 meta/schedule/dispatch
|
|
119
|
+
# 另一条管线,不能拿它当"标准 stage 序列"的样本。
|
|
120
|
+
ag = os_.office
|
|
121
|
+
ag.router.provider = "stub" # 强制 stub(is_stub = provider=='stub'):离线确定性(不受本机
|
|
122
|
+
# 是否配了 LLM key 影响),且 stub 的 router.stream 走 _chunk_text
|
|
123
|
+
# 无损分块 → 可断言"流式 token 拼接 == 最终答案"
|
|
124
|
+
|
|
125
|
+
# (a) 正常流式:collect 所有事件
|
|
126
|
+
events = list(ag.run_task_stream("帮我总结一下今天"))
|
|
127
|
+
stages = [e["stage"] for e in events if "stage" in e]
|
|
128
|
+
deltas = [e["delta"] for e in events if "delta" in e]
|
|
129
|
+
results = [e["result"] for e in events if "result" in e]
|
|
130
|
+
assert stages == ["perceive", "recall", "plan", "route", "act", "verify", "reflect"], stages
|
|
131
|
+
# delta 非空 = _act_stream contextvar 成功跨线程/asyncio 传到了 _call_llm(核心正确性证明)
|
|
132
|
+
assert len(deltas) > 0, "act 应逐 token 出字;为空说明 contextvar 没传到 _call_llm"
|
|
133
|
+
assert len(results) == 1, f"应恰有一个最终 result,实得 {len(results)}"
|
|
134
|
+
res = results[0]
|
|
135
|
+
assert res.get("agent") == ag.AGENT_ID and "session_id" in res and "result" in res
|
|
136
|
+
# 拼接的流式 token == 最终答案(token 透传忠实;_chunk_text 无损)
|
|
137
|
+
assert "".join(deltas) == res["result"], "流式 token 拼接应等于最终答案"
|
|
138
|
+
|
|
139
|
+
# (b) 行为保持:非流式 run_task 仍正常、返回体无 delta 泄漏
|
|
140
|
+
plain = asyncio.run(ag.run_task("帮我总结一下今天"))
|
|
141
|
+
assert "result" in plain and "delta" not in plain and plain.get("agent") == ag.AGENT_ID
|
|
142
|
+
|
|
143
|
+
# (c) stop_event 预置 → 流式在起步即停:无 stage、无 delta、result.interrupted
|
|
144
|
+
import threading as _th
|
|
145
|
+
ev = _th.Event(); ev.set()
|
|
146
|
+
ev_events = list(ag.run_task_stream("这个任务会被中断", stop_event=ev))
|
|
147
|
+
ev_stages = [e for e in ev_events if "stage" in e]
|
|
148
|
+
ev_results = [e["result"] for e in ev_events if "result" in e]
|
|
149
|
+
assert ev_stages == [], f"预置 stop → 0 个 stage 事件,实得 {ev_stages}"
|
|
150
|
+
assert len(ev_results) == 1 and ev_results[0].get("interrupted") is True, ev_results
|
|
151
|
+
|
|
152
|
+
# (d) fail-safe:act 流式若抛异常 → 降级非流式 complete、返回正常(不崩、不中断任务)。
|
|
153
|
+
# 锁住 _call_llm docstring 承诺的"流式任何异常 → fail-safe 降级 complete"。
|
|
154
|
+
def _boom(*a, **k):
|
|
155
|
+
raise RuntimeError("流式炸了")
|
|
156
|
+
ag.router.stream = _boom
|
|
157
|
+
from foxclaw.agents.base import _act_stream as _AS
|
|
158
|
+
async def _one_call():
|
|
159
|
+
tok = _AS.set({"on_token": lambda c: None, "stop_event": None})
|
|
160
|
+
try:
|
|
161
|
+
return await ag._call_llm([{"role": "user", "content": "x"}])
|
|
162
|
+
finally:
|
|
163
|
+
_AS.reset(tok)
|
|
164
|
+
out = asyncio.run(_one_call())
|
|
165
|
+
assert isinstance(out, str) and out, "流式失败应 fail-safe 降级 complete(返回非空串),而非崩"
|
|
166
|
+
|
|
167
|
+
print(f" ✓ stage={len(stages)} delta={len(deltas)} result=1;token 拼接==答案;"
|
|
168
|
+
f"非流式不受影响;预置 stop 起步即停;流式异常 fail-safe 降级 complete")
|
|
169
|
+
finally:
|
|
170
|
+
if os_ is not None:
|
|
171
|
+
asyncio.run(os_.aclose())
|
|
172
|
+
shutil.rmtree(d, ignore_errors=True)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_interrupted_task_not_sunk():
|
|
176
|
+
"""【6·B3 深审 G2】被 stop_event 中断的任务 = 未完成,绝不沉淀为已完成真值(blocked=True)。
|
|
177
|
+
深审发现:_emit_execution 的 blocked 原只看 skill_status,中断任务(skill_status=None)会
|
|
178
|
+
blocked=False → 部分/空结果被 TruthPipeline 当完成写进 HMR 共享记忆(违反 P5/Principle07)。
|
|
179
|
+
修复后 blocked 联动 ctx.interrupted。这条路径由 B3 的 /api/task/stream(断连 set stop_event)
|
|
180
|
+
首次在生产可达,故 happy-path 测不到、深审才暴露。"""
|
|
181
|
+
print("\n【6】B3 深审 G2:中断任务不沉淀真值(blocked=True)")
|
|
182
|
+
d = tempfile.mkdtemp(prefix="g2_")
|
|
183
|
+
os_ = None
|
|
184
|
+
try:
|
|
185
|
+
import threading as _th
|
|
186
|
+
from foxclaw import Foxclaw
|
|
187
|
+
os_ = Foxclaw(data_dir=d, shadow_rounds=50)
|
|
188
|
+
ag = os_.supervisor
|
|
189
|
+
ag.router.provider = "stub"
|
|
190
|
+
captured = {}
|
|
191
|
+
orig = ag._emit_execution
|
|
192
|
+
def _spy(task, result, blocked, context):
|
|
193
|
+
captured["blocked"] = blocked
|
|
194
|
+
return orig(task, result, blocked=blocked, context=context)
|
|
195
|
+
ag._emit_execution = _spy
|
|
196
|
+
# (a) 预置 stop → 中断 → blocked=True(不沉淀部分/空结果为真值)
|
|
197
|
+
ev = _th.Event(); ev.set()
|
|
198
|
+
r = asyncio.run(ag.run_task("会被中断的任务", stop_event=ev))
|
|
199
|
+
assert r.get("interrupted") is True, r
|
|
200
|
+
assert captured.get("blocked") is True, "中断任务必须 blocked=True(不把部分/空结果当完成真值沉淀)"
|
|
201
|
+
# (b) 正常任务 → blocked=False(behavior-preserving:照常沉淀)
|
|
202
|
+
captured.clear()
|
|
203
|
+
r2 = asyncio.run(ag.run_task("正常任务"))
|
|
204
|
+
assert "interrupted" not in r2 and captured.get("blocked") is False, \
|
|
205
|
+
"正常完成任务 blocked=False(行为逐字不变,照常沉淀真值)"
|
|
206
|
+
print(" ✓ 中断→blocked=True(不沉淀);正常→blocked=False(照常沉淀,行为不变)")
|
|
207
|
+
finally:
|
|
208
|
+
if os_ is not None:
|
|
209
|
+
asyncio.run(os_.aclose())
|
|
210
|
+
shutil.rmtree(d, ignore_errors=True)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_reasoning_lifecycle_hooks():
|
|
214
|
+
"""【7·E2】认知管线生命周期钩子:pre_reasoning(agent_start)可注入推理上下文(前插进 LLM);
|
|
215
|
+
task_complete(agent_end)只读观测;默认无钩子=行为逐字不变。这两个语义名是此前从未 fire 的
|
|
216
|
+
孤儿 agent_start/agent_end 的别名——E2 顺带复活了它们并给认知管线 run_task 装上钩子。"""
|
|
217
|
+
print("\n【7】E2 生命周期钩子:pre_reasoning 注入 + task_complete 观测")
|
|
218
|
+
d = tempfile.mkdtemp(prefix="e2_")
|
|
219
|
+
os_ = None
|
|
220
|
+
try:
|
|
221
|
+
from foxclaw import Foxclaw
|
|
222
|
+
os_ = Foxclaw(data_dir=d, shadow_rounds=50)
|
|
223
|
+
ag = os_.office # 单 Agent 认知管线(见上一条注释)
|
|
224
|
+
ag.router.provider = "stub"
|
|
225
|
+
seen = {}
|
|
226
|
+
def _cap_complete(system, messages, max_tokens, agent_id):
|
|
227
|
+
seen["messages"] = messages
|
|
228
|
+
return "(stub 回答)"
|
|
229
|
+
ag.router.complete = _cap_complete
|
|
230
|
+
|
|
231
|
+
# (a) 无钩子基线:不注入(behavior-preserving)
|
|
232
|
+
asyncio.run(ag.run_task("基线任务"))
|
|
233
|
+
base_msgs = seen.get("messages", [])
|
|
234
|
+
assert base_msgs and "【E2注入】" not in base_msgs[0].get("content", ""), \
|
|
235
|
+
f"无 pre_reasoning 钩子时不应注入,实得 {base_msgs[:1]}"
|
|
236
|
+
|
|
237
|
+
# (b) 注册 pre_reasoning(注入)+ task_complete(观测)
|
|
238
|
+
completed = []
|
|
239
|
+
ag.hooks.register("pre_reasoning", lambda c: {"additional_context": "【E2注入】优先考虑省钱"})
|
|
240
|
+
ag.hooks.register("task_complete", lambda c: completed.append(c.get("result")))
|
|
241
|
+
asyncio.run(ag.run_task("帮我看看要不要买"))
|
|
242
|
+
msgs = seen.get("messages", [])
|
|
243
|
+
joined = " ".join(m.get("content", "") for m in msgs)
|
|
244
|
+
assert "【E2注入】优先考虑省钱" in joined, f"pre_reasoning 注入应进入 LLM messages,实得 {msgs}"
|
|
245
|
+
# A3:注入须合并进 user 消息,**不得产生连续两条 user**(模型无关铁律,某些端点会拒连续 user)
|
|
246
|
+
roles = [m.get("role") for m in msgs]
|
|
247
|
+
assert not any(roles[i] == roles[i + 1] == "user" for i in range(len(roles) - 1)), \
|
|
248
|
+
f"注入不应产生连续两条 user 消息(违反模型无关铁律),roles={roles}"
|
|
249
|
+
assert len(completed) == 1, f"task_complete 应触发一次并收到 result,实得 {len(completed)}"
|
|
250
|
+
|
|
251
|
+
# (c) HarnessBlock 结束路径:task_complete 也触发(观测被拦截任务,带 error)
|
|
252
|
+
import types
|
|
253
|
+
async def _boom(self, task, context, session_id, memories):
|
|
254
|
+
raise HarnessBlockedError(types.SimpleNamespace(reason="E2越界"), None)
|
|
255
|
+
ag._execute = types.MethodType(_boom, ag)
|
|
256
|
+
completed.clear()
|
|
257
|
+
rb = asyncio.run(ag.run_task("越界任务"))
|
|
258
|
+
assert rb.get("error") == "harness_blocked", rb
|
|
259
|
+
assert len(completed) == 1, "task_complete 应在 HarnessBlock 结束路径也触发(观测被拦截任务)"
|
|
260
|
+
print(" ✓ 无钩子不注入(behavior-preserving);pre_reasoning 注入前插;task_complete 正常+拦截路径都观测")
|
|
261
|
+
finally:
|
|
262
|
+
if os_ is not None:
|
|
263
|
+
asyncio.run(os_.aclose())
|
|
264
|
+
shutil.rmtree(d, ignore_errors=True)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def test_preference_priority_recall():
|
|
268
|
+
"""【8·偏好优先召回】明确偏好标 tag=preference(shared 持久)+ **独立于语义 query** 优先注入。
|
|
269
|
+
核心对照:偏好与当前任务**语义完全不相关**时,纯语义 recall 召不到,但偏好召回仍注入——
|
|
270
|
+
修复"越用越懂你"断在召回链的洞。默认开;FOXCLAW_PREF_CAPTURE=0 关采集。"""
|
|
271
|
+
print("\n【8】偏好优先召回:语义不相关也注入")
|
|
272
|
+
d = tempfile.mkdtemp(prefix="pref_")
|
|
273
|
+
os_ = None
|
|
274
|
+
try:
|
|
275
|
+
from foxclaw import Foxclaw
|
|
276
|
+
os_ = Foxclaw(data_dir=d, shadow_rounds=50)
|
|
277
|
+
ag = os_.office # 单 Agent 认知管线(见上一条注释)
|
|
278
|
+
ag.router.provider = "stub"
|
|
279
|
+
seen = {}
|
|
280
|
+
def _cap(system, messages, max_tokens, agent_id):
|
|
281
|
+
seen["messages"] = messages; return "(stub)"
|
|
282
|
+
ag.router.complete = _cap
|
|
283
|
+
|
|
284
|
+
# (a) 明确偏好表达 → 存成带 preference tag(shared)
|
|
285
|
+
asyncio.run(ag.run_task("我喜欢简洁直接的回答,不要长篇大论"))
|
|
286
|
+
prefs = ag.hmr.recall_by_tag(agent_id="supervisor", tag="preference", limit=8)
|
|
287
|
+
assert any("简洁" in p.get("content", "") for p in prefs), f"偏好应带 preference tag 存下,实得 {prefs}"
|
|
288
|
+
|
|
289
|
+
# (b) 发**语义完全不相关**的任务 → 偏好仍注入 prompt(纯语义召回此时召不到)
|
|
290
|
+
seen.clear()
|
|
291
|
+
asyncio.run(ag.run_task("解释一下光合作用的原理"))
|
|
292
|
+
joined = " ".join(m.get("content", "") for m in seen.get("messages", []))
|
|
293
|
+
assert "用户偏好" in joined and "简洁" in joined, \
|
|
294
|
+
f"语义不相关任务也应注入偏好(越用越懂你),实得 {seen.get('messages')}"
|
|
295
|
+
|
|
296
|
+
# (c) 无偏好信号的普通任务不误存(保守采集)
|
|
297
|
+
before = len(ag.hmr.recall_by_tag(agent_id="assistant", tag="preference"))
|
|
298
|
+
asyncio.run(ag.run_task("光合作用发生在植物的哪个部位"))
|
|
299
|
+
after = len(ag.hmr.recall_by_tag(agent_id="assistant", tag="preference"))
|
|
300
|
+
assert after == before, "无偏好信号的任务不应新增偏好"
|
|
301
|
+
|
|
302
|
+
# (d) 关采集开关 → 偏好句也不存(可关)
|
|
303
|
+
os.environ["FOXCLAW_PREF_CAPTURE"] = "0"
|
|
304
|
+
try:
|
|
305
|
+
asyncio.run(ag.run_task("我喜欢用表格展示数据"))
|
|
306
|
+
got = ag.hmr.recall_by_tag(agent_id="assistant", tag="preference", limit=20)
|
|
307
|
+
assert not any("表格" in p.get("content", "") for p in got), "关采集后不应存偏好"
|
|
308
|
+
finally:
|
|
309
|
+
os.environ.pop("FOXCLAW_PREF_CAPTURE", None)
|
|
310
|
+
|
|
311
|
+
# (e) 对话路径(stream_reply)也注入偏好——之前只测了任务路径(深审 F2:claim 必测)
|
|
312
|
+
seen_stream = []
|
|
313
|
+
def _cap_stream(system, messages, *a, **k):
|
|
314
|
+
seen_stream.append(messages); yield "(stub)"
|
|
315
|
+
ag.router.stream = _cap_stream
|
|
316
|
+
list(ag.stream_reply("随便讲点什么")) # 语义与"简洁"偏好不相关
|
|
317
|
+
js = " ".join(m.get("content", "") for m in (seen_stream[0] if seen_stream else []))
|
|
318
|
+
assert "用户偏好" in js and "简洁" in js, f"对话路径(stream_reply)也应注入偏好,实得 {seen_stream}"
|
|
319
|
+
|
|
320
|
+
# (f) 去重:同一偏好存多条,召回只返一条(深审 F1)
|
|
321
|
+
for _ in range(3):
|
|
322
|
+
ag._store_memory("用户偏好:我喜欢简洁直接的回答,不要长篇大论",
|
|
323
|
+
mem_type="preference", tags=["preference"], shared=True)
|
|
324
|
+
got = ag.hmr.recall_by_tag(agent_id="assistant", tag="preference", limit=8)
|
|
325
|
+
same = [p for p in got if "简洁直接的回答" in p.get("content", "")]
|
|
326
|
+
assert len(same) == 1, f"重复偏好召回应去重为 1 条,实得 {len(same)}"
|
|
327
|
+
|
|
328
|
+
# (g) 删偏好途径(F3):存唯一偏好 → list 找到(带 id)→ delete → 确认删除
|
|
329
|
+
ag._store_memory("用户偏好:请用英文回复", mem_type="preference", tags=["preference"], shared=True)
|
|
330
|
+
target = [p for p in ag.list_preferences() if "英文" in p.get("content", "")]
|
|
331
|
+
assert target and target[0].get("id"), f"唯一偏好应可列出带 id,实得 {target}"
|
|
332
|
+
assert ag.delete_preference(target[0]["id"]), "删偏好应成功"
|
|
333
|
+
gone = [p for p in ag.list_preferences() if "英文" in p.get("content", "")]
|
|
334
|
+
assert not gone, "删除后该偏好不应再出现(F3:误存可清理)"
|
|
335
|
+
print(" ✓ 偏好存 tag;两路径注入;去重;不误存;可关;可 list/删除(F3)")
|
|
336
|
+
finally:
|
|
337
|
+
if os_ is not None:
|
|
338
|
+
asyncio.run(os_.aclose())
|
|
339
|
+
shutil.rmtree(d, ignore_errors=True)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def main():
|
|
343
|
+
print("=" * 60)
|
|
344
|
+
print("统一任务生命周期 · run_task 收敛(P1)")
|
|
345
|
+
print("=" * 60)
|
|
346
|
+
test_structural_convergence()
|
|
347
|
+
test_domain_hooks()
|
|
348
|
+
test_unified_block_handling()
|
|
349
|
+
test_finance_verify_before_recommend()
|
|
350
|
+
test_run_task_stream()
|
|
351
|
+
test_interrupted_task_not_sunk()
|
|
352
|
+
test_reasoning_lifecycle_hooks()
|
|
353
|
+
test_preference_priority_recall()
|
|
354
|
+
print("\n" + "=" * 60)
|
|
355
|
+
print("✓ P1 验证完成:5 run_task→1 模板 + 领域 _execute 钩子 + 统一兜底 + P5 记忆验证优先")
|
|
356
|
+
print("=" * 60)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
if __name__ == "__main__":
|
|
360
|
+
main()
|