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,275 @@
|
|
|
1
|
+
"""SM-2 复习面 —— 把知识库里的 KU 直接当复习卡。
|
|
2
|
+
|
|
3
|
+
设计取舍:不新建「卡片表」。复习状态写进 ``KU.extra["review"]``,与 learning
|
|
4
|
+
子系统沿用同一套约定、同一个 ``SpacedRepetition`` 引擎。这样「录入 → 抽取 →
|
|
5
|
+
复习」是一条链:右栏录一句 → KDE 抽出 KU → 立刻可复习,不存在知识库一份、
|
|
6
|
+
学习模块又一份的双写(用户对重复存储明确反感)。
|
|
7
|
+
|
|
8
|
+
Evidence 是原文出处,单独看没有回忆价值,不进队列;其余 KU 都算卡片。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
|
|
13
|
+
from dataclasses import asdict
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from fastapi import HTTPException
|
|
16
|
+
from pydantic import BaseModel
|
|
17
|
+
|
|
18
|
+
import foxclaw.api as _api
|
|
19
|
+
from .. import app
|
|
20
|
+
from foxclaw.learning import ReviewState, SpacedRepetition
|
|
21
|
+
|
|
22
|
+
_SR = SpacedRepetition()
|
|
23
|
+
|
|
24
|
+
_CARD_TYPES = {"Concept", "Fact", "Claim", "Entity"}
|
|
25
|
+
|
|
26
|
+
# 扫描上限(每种卡片类型)。之前统一 query_all(4000),一是把 Evidence 这种绝对不会
|
|
27
|
+
# 成卡的原文出处也一起拉出来(大文档导入后它是绝大多数行),二是 4000 之后的部分
|
|
28
|
+
# 会被静默截断 → 复习统计少算。这里只查四种卡片类型,上限给到实际用不满的量级。
|
|
29
|
+
_SCAN_LIMIT = 50000
|
|
30
|
+
|
|
31
|
+
# ReviewState.to_dict() 会额外塞 mastery / is_due,读回来时必须剔掉才能重建
|
|
32
|
+
_STATE_FIELDS = ("repetitions", "interval", "ease_factor", "due_date",
|
|
33
|
+
"last_review", "total_reviews", "lapses")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _store():
|
|
37
|
+
"""惰性取 HKC 的 graph_store,None 表示知识库没装配好(降级成空队列)。"""
|
|
38
|
+
try:
|
|
39
|
+
hkc = _api._os_instance.hkc
|
|
40
|
+
if hkc is None or not hkc.available:
|
|
41
|
+
return None
|
|
42
|
+
return hkc.container.graph_store
|
|
43
|
+
except Exception:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _has_state(ku) -> bool:
|
|
48
|
+
return bool((ku.extra or {}).get("review"))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _days_left(state) -> int:
|
|
52
|
+
"""按**日历日**算还剩几天,而不是 ``timedelta.days`` 向下取整。
|
|
53
|
+
|
|
54
|
+
``ReviewState.days_until_due`` 用的是后者:明天此刻到期会算成 0,
|
|
55
|
+
于是「未来 7 天负荷」把明天的卡记进今天,而队列的 due 又是 0 —— 两个数字
|
|
56
|
+
互相打架,界面上看起来像 bug。这里统一到日历日,和人对「明天」的直觉一致。
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
due = datetime.fromisoformat(state.due_date).date()
|
|
60
|
+
except Exception:
|
|
61
|
+
return 0
|
|
62
|
+
return max(0, (due - datetime.now().date()).days)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _hours_left(state) -> float:
|
|
66
|
+
"""小时级剩余,给「不到一天」的卡显示得更准(前端据此说「今晚」还是「明天」)。"""
|
|
67
|
+
try:
|
|
68
|
+
delta = datetime.fromisoformat(state.due_date) - datetime.now()
|
|
69
|
+
except Exception:
|
|
70
|
+
return 0.0
|
|
71
|
+
return round(max(0.0, delta.total_seconds() / 3600), 1)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _load7(learned) -> dict:
|
|
75
|
+
"""未来 7 天(0..6)的复习负荷,口径与队列一致。"""
|
|
76
|
+
by_day = {}
|
|
77
|
+
for s in learned:
|
|
78
|
+
d = 0 if s.is_due else _days_left(s)
|
|
79
|
+
if d <= 6:
|
|
80
|
+
by_day[str(d)] = by_day.get(str(d), 0) + 1
|
|
81
|
+
total = sum(by_day.values())
|
|
82
|
+
return {"window_days": 7, "total_due": total,
|
|
83
|
+
"daily_avg": round(total / 7, 2), "by_day": by_day,
|
|
84
|
+
"overdue": sum(1 for s in learned if s.is_due)}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _status_of(ku) -> str:
|
|
88
|
+
"""KU.status 是**普通字符串列**(enums 里没有 KUStatus),不是枚举。
|
|
89
|
+
|
|
90
|
+
踩过的坑:写成 ``getattr(ku.status, "value", "active")`` 时,因为 str 没有
|
|
91
|
+
``.value`` 属性,getattr 会静默回落成默认值 "active" —— 于是「已软删的卡」也能
|
|
92
|
+
通过校验被评分,复习进度写进一条已经消失的知识里。
|
|
93
|
+
"""
|
|
94
|
+
s = getattr(ku, "status", None)
|
|
95
|
+
return str(getattr(s, "value", s) or "").lower()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _state_from(extra, ku_id: str) -> ReviewState:
|
|
99
|
+
raw = (extra or {}).get("review") or {}
|
|
100
|
+
return ReviewState(item_id=ku_id,
|
|
101
|
+
**{k: raw[k] for k in _STATE_FIELDS if k in raw})
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _state_of(ku) -> ReviewState:
|
|
105
|
+
return _state_from(ku.extra, ku.ku_id)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _card(ku, state) -> dict:
|
|
109
|
+
extra = ku.extra or {}
|
|
110
|
+
text = (extra.get("source_text") or extra.get("statement")
|
|
111
|
+
or extra.get("definition") or ku.summary or "")
|
|
112
|
+
return {
|
|
113
|
+
"ku_id": ku.ku_id,
|
|
114
|
+
"name": ku.name,
|
|
115
|
+
"ku_type": ku.ku_type.value,
|
|
116
|
+
"domain": ku.domain or "",
|
|
117
|
+
"summary": ku.summary or "",
|
|
118
|
+
"text": str(text)[:1500],
|
|
119
|
+
"tags": list(ku.tags or []),
|
|
120
|
+
"created_at": getattr(ku, "created_at", "") or "",
|
|
121
|
+
"is_new": not _has_state(ku),
|
|
122
|
+
"mastery": state.mastery,
|
|
123
|
+
"interval": state.interval,
|
|
124
|
+
"ease_factor": state.ease_factor,
|
|
125
|
+
"repetitions": state.repetitions,
|
|
126
|
+
"lapses": state.lapses,
|
|
127
|
+
"days_until_due": _days_left(state) if _has_state(ku) else 0,
|
|
128
|
+
"due_in_hours": _hours_left(state) if _has_state(ku) else 0,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _card_ku_types():
|
|
133
|
+
"""四种卡片类型的 KUType 枚举;拿不到(未 vendor / 未加 sys.path)返回 None。"""
|
|
134
|
+
try:
|
|
135
|
+
from hkc_core.models.enums import KUType
|
|
136
|
+
return [t for t in KUType if t.value in _CARD_TYPES]
|
|
137
|
+
except Exception:
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _pairs(store):
|
|
142
|
+
"""所有可当复习卡的 KU + 它们当前的复习状态。
|
|
143
|
+
|
|
144
|
+
只查四类卡片:Evidence(原文出处)单看没有回忆价值,Ability 是 ACE 编译产物,
|
|
145
|
+
两者都不进队列。之前用 query_all 会把它们一起拉出来再丢掉。
|
|
146
|
+
"""
|
|
147
|
+
types = _card_ku_types()
|
|
148
|
+
if types is None:
|
|
149
|
+
raws = store.query_all(_SCAN_LIMIT)
|
|
150
|
+
else:
|
|
151
|
+
raws = []
|
|
152
|
+
for t in types:
|
|
153
|
+
raws.extend(store.query_by_type(t, status="active", limit=_SCAN_LIMIT))
|
|
154
|
+
|
|
155
|
+
out, seen = [], set()
|
|
156
|
+
for ku in raws:
|
|
157
|
+
if ku.ku_id in seen or _status_of(ku) != "active":
|
|
158
|
+
continue
|
|
159
|
+
if ku.ku_type.value not in _CARD_TYPES:
|
|
160
|
+
continue
|
|
161
|
+
seen.add(ku.ku_id)
|
|
162
|
+
out.append((ku, _state_of(ku)))
|
|
163
|
+
return out
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@app.get("/api/review/stats")
|
|
167
|
+
async def review_stats():
|
|
168
|
+
store = _store()
|
|
169
|
+
if store is None:
|
|
170
|
+
return {"available": False, "cards": 0, "fresh": 0, "due": 0,
|
|
171
|
+
"learned": 0, "avg_mastery": 0.0,
|
|
172
|
+
"load7": {"total_due": 0, "by_day": {}, "daily_avg": 0.0, "overdue": 0},
|
|
173
|
+
"risk": []}
|
|
174
|
+
|
|
175
|
+
def _do():
|
|
176
|
+
pairs = _pairs(store)
|
|
177
|
+
learned = [s for ku, s in pairs if _has_state(ku)]
|
|
178
|
+
due = [s for s in learned if s.is_due]
|
|
179
|
+
risk = _SR.forgetting_risk(learned, 3)[:5]
|
|
180
|
+
|
|
181
|
+
by_id = {ku.ku_id: ku.name for ku, _ in pairs}
|
|
182
|
+
by_state = {s.item_id: s for s in learned}
|
|
183
|
+
for r in risk:
|
|
184
|
+
s = by_state.get(r["item_id"])
|
|
185
|
+
r["name"] = by_id.get(r["item_id"], "")
|
|
186
|
+
if s is not None:
|
|
187
|
+
d = _days_left(s)
|
|
188
|
+
r["days_until_due"] = d
|
|
189
|
+
r["reason"] = f"掌握度仅 {s.mastery}," + ("已到期" if s.is_due else f"{d} 天内到期")
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
"available": True,
|
|
193
|
+
"cards": len(pairs),
|
|
194
|
+
"learned": len(learned),
|
|
195
|
+
"fresh": len(pairs) - len(learned),
|
|
196
|
+
"due": len(due),
|
|
197
|
+
"avg_mastery": round(sum(s.mastery for s in learned) / len(learned), 3) if learned else 0.0,
|
|
198
|
+
"load7": _load7(learned),
|
|
199
|
+
"risk": risk,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return await asyncio.to_thread(_do)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@app.get("/api/review/queue")
|
|
206
|
+
async def review_queue(limit: int = 20, fresh_limit: int = 10):
|
|
207
|
+
"""今日该复习什么。
|
|
208
|
+
|
|
209
|
+
到期卡先上(掌握度低的优先,最弱的先补),不够 ``limit`` 再补新卡(先录的先学)。
|
|
210
|
+
新卡数量单独由 ``fresh_limit`` 控制 —— 新知识全塞一天会让曲线失去意义。
|
|
211
|
+
"""
|
|
212
|
+
store = _store()
|
|
213
|
+
if store is None:
|
|
214
|
+
return {"available": False, "cards": [], "total": 0}
|
|
215
|
+
|
|
216
|
+
def _do():
|
|
217
|
+
pairs = _pairs(store)
|
|
218
|
+
learned = [(ku, s) for ku, s in pairs if _has_state(ku)]
|
|
219
|
+
fresh = [(ku, s) for ku, s in pairs if not _has_state(ku)]
|
|
220
|
+
|
|
221
|
+
learned.sort(key=lambda p: (p[1].mastery, p[1].days_until_due))
|
|
222
|
+
fresh.sort(key=lambda p: getattr(p[0], "created_at", "") or "")
|
|
223
|
+
|
|
224
|
+
due = [(ku, s) for ku, s in learned if s.is_due][:max(0, int(limit))]
|
|
225
|
+
room = max(0, int(limit) - len(due))
|
|
226
|
+
picked = due + fresh[:min(room, max(0, int(fresh_limit)))]
|
|
227
|
+
|
|
228
|
+
return {"available": True,
|
|
229
|
+
"cards": [_card(ku, s) for ku, s in picked],
|
|
230
|
+
"due": len(due),
|
|
231
|
+
"fresh_used": max(0, len(picked) - len(due)),
|
|
232
|
+
"total": len(pairs)}
|
|
233
|
+
|
|
234
|
+
return await asyncio.to_thread(_do)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class GradeIn(BaseModel):
|
|
238
|
+
ku_id: str
|
|
239
|
+
grade: int
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@app.post("/api/review/grade")
|
|
243
|
+
async def review_grade(body: GradeIn):
|
|
244
|
+
"""给一张卡打回忆质量分(0 忘 / 3 勉强 / 4 记住 / 5 轻松),SM-2 推下一次间隔。"""
|
|
245
|
+
store = _store()
|
|
246
|
+
if store is None:
|
|
247
|
+
raise HTTPException(503, "知识库未装配,复习状态无处可存")
|
|
248
|
+
|
|
249
|
+
def _do():
|
|
250
|
+
ku = store.get(body.ku_id)
|
|
251
|
+
if ku is None or _status_of(ku) != "active":
|
|
252
|
+
raise HTTPException(404, f"没有这条知识:{body.ku_id}")
|
|
253
|
+
|
|
254
|
+
# 关键:只更新 extra 这一列,而且「读 extra → SM-2 推演 → 写回」整段在
|
|
255
|
+
# graph_store 的锁里完成。走 put() 是整行 UPSERT,会把整行其它列按写入前的
|
|
256
|
+
# 快照回写 —— 知识星球正在摄入时,KEE 刚合并进去的 sources / confidence /
|
|
257
|
+
# supports 会被这次评分悄悄冲掉(反向亦然:复习进度被 KEE 写回覆盖)。
|
|
258
|
+
# 锁内做还有个好处:连点两次评分不会两次都基于同一份旧状态推演。
|
|
259
|
+
graded: list[ReviewState] = []
|
|
260
|
+
|
|
261
|
+
def _apply(extra: dict) -> dict:
|
|
262
|
+
state = _state_from(extra, body.ku_id)
|
|
263
|
+
_SR.review(state, body.grade)
|
|
264
|
+
extra["review"] = asdict(state)
|
|
265
|
+
graded.append(state)
|
|
266
|
+
return extra
|
|
267
|
+
|
|
268
|
+
new_extra = store.update_extra(body.ku_id, _apply)
|
|
269
|
+
if new_extra is None:
|
|
270
|
+
raise HTTPException(404, f"没有这条知识:{body.ku_id}")
|
|
271
|
+
state = graded[0]
|
|
272
|
+
return {"ok": True, "ku_id": body.ku_id, "name": ku.name,
|
|
273
|
+
"next_in_days": state.interval, "review": state.to_dict()}
|
|
274
|
+
|
|
275
|
+
return await asyncio.to_thread(_do)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""认知搜索(HSR)端点:内部记忆 + 内部知识 + 联网,统一证据融合。
|
|
2
|
+
|
|
3
|
+
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api._os_instance`
|
|
4
|
+
晚绑定读取;路由用 `@app` 装饰器在 import 时注册。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
|
|
9
|
+
from fastapi import HTTPException
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
import foxclaw.api as _api
|
|
13
|
+
from .. import app
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ── 认知搜索(HSR):内部记忆 + 内部知识 + 联网,统一证据融合 ─────────
|
|
17
|
+
# 一次 search() 统一"查 HMR 记忆 / 查 HKC 知识 / 联网(百度)",走 planner→retriever→
|
|
18
|
+
# fusion→strategy 迭代循环(有上限),只产出证据图,不写任何持久化(边界:不代 Agent 决策)。
|
|
19
|
+
|
|
20
|
+
class SearchQueryPayload(BaseModel):
|
|
21
|
+
query: str
|
|
22
|
+
agent_id: str = "supervisor"
|
|
23
|
+
context: dict = {}
|
|
24
|
+
internal_only: bool = False # True=只查内部(HMR+HKC+Agent态势),跳过联网
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.post("/api/search/query")
|
|
28
|
+
async def search_query(p: SearchQueryPayload):
|
|
29
|
+
"""认知搜索(面向 AI/Agent 的证据管线):返回证据图 + 置信度 + 迭代轨迹。
|
|
30
|
+
internal_only=True 时只查内部三源(记忆/知识/Agent),不联网。搜索内核惰性构造。"""
|
|
31
|
+
try:
|
|
32
|
+
rt = _api._os_instance.search_runtime
|
|
33
|
+
result = await asyncio.to_thread(
|
|
34
|
+
rt.search, p.query, p.agent_id, p.context, p.internal_only)
|
|
35
|
+
return result.to_dict()
|
|
36
|
+
except Exception as e:
|
|
37
|
+
raise HTTPException(500, f"搜索失败: {e}")
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"""系统状态 / 模型路由 / 认知调度 / 事件总线 / 真值 / 进程 / 思考链 / Harness / 探活 / 监控与追踪。
|
|
2
|
+
|
|
3
|
+
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api._os_instance`
|
|
4
|
+
晚绑定读取(保持测试可重绑定 `api._os_instance`);路由用 `@app` 装饰器在 import 时注册。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from fastapi import HTTPException
|
|
11
|
+
from fastapi.responses import PlainTextResponse
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
|
|
14
|
+
from foxclaw import __version__
|
|
15
|
+
|
|
16
|
+
import foxclaw.api as _api
|
|
17
|
+
from .. import app
|
|
18
|
+
from ..middleware import _gateway_on, _GATEWAY
|
|
19
|
+
from ...core import settings
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ── 系统状态 ───────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
@app.get("/api/status")
|
|
25
|
+
async def get_system_status():
|
|
26
|
+
"""获取所有 Agent 的状态"""
|
|
27
|
+
return _api._os_instance.get_all_status()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.get("/api/tokens")
|
|
31
|
+
async def get_token_summary():
|
|
32
|
+
"""Token 消耗摘要(成本可观测性):总量、按 Agent、按模型。"""
|
|
33
|
+
meter = getattr(_api._os_instance, "token_meter", None)
|
|
34
|
+
if meter is None:
|
|
35
|
+
return {"error": "token meter 未启用"}
|
|
36
|
+
return {
|
|
37
|
+
"summary": meter.summary(),
|
|
38
|
+
"by_agent": meter.by_agent(),
|
|
39
|
+
"by_provider": meter.by_provider(),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.get("/api/tokens/report")
|
|
44
|
+
async def get_token_report():
|
|
45
|
+
"""Token 消耗的人类可读文本报告。"""
|
|
46
|
+
meter = getattr(_api._os_instance, "token_meter", None)
|
|
47
|
+
if meter is None:
|
|
48
|
+
return {"error": "token meter 未启用"}
|
|
49
|
+
return {"report": meter.report()}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ── 模型路由与系统信息 ─────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
@app.get("/api/model")
|
|
55
|
+
async def get_model():
|
|
56
|
+
"""当前模型路由状态 + 可用 provider 列表。"""
|
|
57
|
+
return _api._os_instance.router.info()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.get("/api/config/amap")
|
|
61
|
+
async def get_amap_config():
|
|
62
|
+
"""下发前端高德 JSAPI Key + 安全密钥(从 .env 读取,不硬编码在前端)。"""
|
|
63
|
+
return {
|
|
64
|
+
"js_key": os.environ.get("AMAP_JS_KEY", ""),
|
|
65
|
+
"js_scode": os.environ.get("AMAP_JS_SCODE", ""),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.get("/api/system/info")
|
|
70
|
+
async def get_system_info():
|
|
71
|
+
"""系统级只读信息:版本、运行参数、各 Agent、模块状态(供设置面板展示)。"""
|
|
72
|
+
agents = {}
|
|
73
|
+
for aid, ag in _api._os_instance._agents.items():
|
|
74
|
+
h = ag.harness
|
|
75
|
+
agents[aid] = {
|
|
76
|
+
"name": getattr(ag, "AGENT_NAME", aid),
|
|
77
|
+
"shadow_mode": h.is_shadow_mode,
|
|
78
|
+
"shadow_rounds": h.shadow_rounds,
|
|
79
|
+
"run_count": getattr(h, "_run_count", 0),
|
|
80
|
+
# 降级可见性(B 类修复):语义维度停用时,漂移检测只剩 tool/scope 两维,
|
|
81
|
+
# 跑题不会被拦截 —— 前端需据此提示,不能只在服务端日志里静默。
|
|
82
|
+
"semantic_degraded": getattr(h, "semantic_degraded", False),
|
|
83
|
+
"embedding_mode": getattr(getattr(h, "sem_drift", None), "mode", "unknown"),
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
"version": __version__,
|
|
87
|
+
"data_dir": os.environ.get("FOXCLAW_DATA_DIR", "./foxclaw_data"),
|
|
88
|
+
"shadow_rounds": settings.get_int("FOXCLAW_SHADOW_ROUNDS"),
|
|
89
|
+
"model": _api._os_instance.router.info(),
|
|
90
|
+
"amap": {
|
|
91
|
+
"web_key": bool(os.environ.get("AMAP_MAPS_API_KEY")),
|
|
92
|
+
"js_key": bool(os.environ.get("AMAP_JS_KEY")),
|
|
93
|
+
"js_scode": bool(os.environ.get("AMAP_JS_SCODE")),
|
|
94
|
+
},
|
|
95
|
+
"agents": agents,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ModelSwitchPayload(BaseModel):
|
|
100
|
+
provider: str
|
|
101
|
+
model: Optional[str] = None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@app.post("/api/model/switch")
|
|
105
|
+
async def switch_model(p: ModelSwitchPayload):
|
|
106
|
+
"""运行时切换模型 provider(多模型路由核心能力)。"""
|
|
107
|
+
try:
|
|
108
|
+
return _api._os_instance.router.switch(p.provider, p.model)
|
|
109
|
+
except Exception as e:
|
|
110
|
+
raise HTTPException(400, f"切换失败: {e}")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@app.get("/api/agents/{agent_id}/status")
|
|
114
|
+
async def get_agent_status(agent_id: str):
|
|
115
|
+
agent = _api._os_instance.get_agent(agent_id)
|
|
116
|
+
if not agent:
|
|
117
|
+
raise HTTPException(404, f"Agent {agent_id} 不存在")
|
|
118
|
+
return agent.get_status()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ── 认知调度 / 事件总线 / 真值 / 进程 / 认知状态 ─────────────
|
|
122
|
+
|
|
123
|
+
class SchedulePreviewPayload(BaseModel):
|
|
124
|
+
task: str
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@app.post("/api/scheduler/preview")
|
|
128
|
+
async def scheduler_preview(payload: SchedulePreviewPayload):
|
|
129
|
+
"""认知调度预览:给一个任务,返回调度决策(优先级/意图/路由/抢占),不实际执行。"""
|
|
130
|
+
from foxclaw.core.scheduler import CognitiveScheduler, SystemContext
|
|
131
|
+
sch = CognitiveScheduler()
|
|
132
|
+
d = sch.schedule(payload.task, SystemContext())
|
|
133
|
+
return d.to_dict()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@app.get("/api/scheduler/stats")
|
|
137
|
+
async def scheduler_stats():
|
|
138
|
+
"""调度器记账:并发配额 + 提交/运行/完成/失败/峰值(可观测性)。"""
|
|
139
|
+
return _api._os_instance.scheduler.stats()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@app.get("/api/bus/history")
|
|
143
|
+
async def bus_history(channel: str = None, limit: int = 30):
|
|
144
|
+
"""系统事件总线历史:观测执行→审计→记忆的真值流转。"""
|
|
145
|
+
try:
|
|
146
|
+
return {"events": _api._os_instance.system_bus.history(channel, limit)}
|
|
147
|
+
except Exception:
|
|
148
|
+
return {"events": []}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@app.get("/api/truth/last")
|
|
152
|
+
async def truth_last():
|
|
153
|
+
"""真值管线最近一次的 trace 与记忆更新。"""
|
|
154
|
+
tp = _api._os_instance.truth_pipeline
|
|
155
|
+
return {"last_trace": tp.last_trace, "last_memory_update": tp.last_memory_update}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@app.get("/api/processes")
|
|
159
|
+
async def list_processes(state: str = None, limit: int = 30):
|
|
160
|
+
"""进程列表:观测任务作为"进程"的生命周期状态。"""
|
|
161
|
+
try:
|
|
162
|
+
return {"processes": _api._os_instance.process_manager.list(state, limit),
|
|
163
|
+
"stats": _api._os_instance.process_manager.stats()}
|
|
164
|
+
except Exception:
|
|
165
|
+
return {"processes": [], "stats": {}}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@app.get("/api/processes/{process_id}")
|
|
169
|
+
async def get_process(process_id: str):
|
|
170
|
+
"""单个进程详情:含完整状态历史(state_history)。"""
|
|
171
|
+
rec = _api._os_instance.process_manager.get(process_id)
|
|
172
|
+
return rec.to_dict() if rec else {"error": "not_found"}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@app.get("/api/cognitive/state")
|
|
176
|
+
async def cognitive_state():
|
|
177
|
+
"""认知状态快照:系统此刻在想什么(目标/焦点/注意力/激活记忆/活跃进程)。"""
|
|
178
|
+
try:
|
|
179
|
+
return _api._os_instance.cognitive_state.snapshot()
|
|
180
|
+
except Exception as e:
|
|
181
|
+
return {"error": str(e)}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ── ThoughtChain 查询 ──────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
@app.get("/api/thoughtchain/{agent_id}/chain")
|
|
187
|
+
async def get_thought_chain(agent_id: str, session_id: str = None):
|
|
188
|
+
"""获取 Agent 的推理链"""
|
|
189
|
+
tc = _api._os_instance.thought_chain
|
|
190
|
+
if session_id:
|
|
191
|
+
nodes = tc.get_chain(session_id, agent_id)
|
|
192
|
+
else:
|
|
193
|
+
# 返回最近 20 个节点
|
|
194
|
+
nodes = tc.get_blocked_nodes(agent_id, limit=20)
|
|
195
|
+
return {"nodes": [n.to_dict() for n in nodes]}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@app.get("/api/thoughtchain/{agent_id}/blocked")
|
|
199
|
+
async def get_blocked_nodes(agent_id: str, limit: int = 20):
|
|
200
|
+
"""获取被 Harness 截断的节点(审计用)"""
|
|
201
|
+
tc = _api._os_instance.thought_chain
|
|
202
|
+
nodes = tc.get_blocked_nodes(agent_id, limit=limit)
|
|
203
|
+
return {"blocked": [n.to_dict() for n in nodes], "count": len(nodes)}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@app.get("/api/thoughtchain/{agent_id}/trend")
|
|
207
|
+
async def get_drift_trend(agent_id: str, limit: int = 100):
|
|
208
|
+
"""获取漂移趋势数据(前端可视化用)"""
|
|
209
|
+
tc = _api._os_instance.thought_chain
|
|
210
|
+
trend = tc.get_delta_trend(agent_id, limit=limit)
|
|
211
|
+
return {"trend": trend, "agent_id": agent_id}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@app.get("/api/thoughtchain/{agent_id}/summary")
|
|
215
|
+
async def get_thought_summary(agent_id: str):
|
|
216
|
+
"""获取 ThoughtChain 健康摘要"""
|
|
217
|
+
return _api._os_instance.thought_chain.get_summary(agent_id)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# ── Harness 状态 ───────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
@app.get("/api/harness/{agent_id}/status")
|
|
223
|
+
async def get_harness_status(agent_id: str):
|
|
224
|
+
"""获取 Harness 状态:阈值、shadow mode、policy 摘要"""
|
|
225
|
+
agent = _api._os_instance.get_agent(agent_id)
|
|
226
|
+
if not agent:
|
|
227
|
+
raise HTTPException(404, f"Agent {agent_id} 不存在")
|
|
228
|
+
return {
|
|
229
|
+
"agent_id": agent_id,
|
|
230
|
+
"thresholds": agent.harness.thresholds,
|
|
231
|
+
"policy": agent.harness.get_policy_summary(),
|
|
232
|
+
"weights": agent.harness.weights,
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@app.get("/api/harness/{agent_id}/policy")
|
|
237
|
+
async def get_policy(agent_id: str):
|
|
238
|
+
"""获取 Agent 的 Policy YAML(完整内容)"""
|
|
239
|
+
agent = _api._os_instance.get_agent(agent_id)
|
|
240
|
+
if not agent:
|
|
241
|
+
raise HTTPException(404, f"Agent {agent_id} 不存在")
|
|
242
|
+
return agent.harness._policy
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# ── 健康探活 ───────────────────────────────────────────────
|
|
246
|
+
# 标准探活端点(开放,不在鉴权前缀内、不触发 HKC 装配)。星球 UI 的 connect() 用它探活,
|
|
247
|
+
# 也供部署/监控用。放在静态挂载("/")之前,否则会被 StaticFiles 的 catch-all 吞成 404。
|
|
248
|
+
@app.get("/health")
|
|
249
|
+
async def health():
|
|
250
|
+
"""存活探针(liveness):浅、快,不触发子系统。"""
|
|
251
|
+
return {"status": "ok", "service": "foxclaw", "version": __version__}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@app.get("/health/ready")
|
|
255
|
+
async def health_ready():
|
|
256
|
+
"""就绪探针(readiness):逐子系统深度检查。**开放**(探针无需鉴权),
|
|
257
|
+
组件只回 ok/stub/down,不泄露 provider 名等细节。不触发 HKC 惰性初始化。"""
|
|
258
|
+
o = _api._os_instance
|
|
259
|
+
comps = {}
|
|
260
|
+
comps["hmr"] = "ok" if getattr(o, "hmr", None) is not None else "down"
|
|
261
|
+
_router = getattr(o, "router", None)
|
|
262
|
+
if _router is None:
|
|
263
|
+
comps["router"] = "down"
|
|
264
|
+
elif getattr(_router, "is_stub", True):
|
|
265
|
+
comps["router"] = "stub"
|
|
266
|
+
else:
|
|
267
|
+
comps["router"] = "ok" # 不回传 provider 名(开放端点,防泄露)
|
|
268
|
+
comps["scheduler"] = "ok" if getattr(o, "scheduler", None) is not None else "down"
|
|
269
|
+
hkc_svc = getattr(o, "_hkc_service", None) # 只看已初始化的,不触发惰性 init
|
|
270
|
+
comps["hkc"] = ("ok" if (hkc_svc is not None and getattr(hkc_svc, "available", False))
|
|
271
|
+
else ("uninitialized" if hkc_svc is None else "degraded"))
|
|
272
|
+
comps["mcp_server"] = "on" if getattr(o, "mcp_manager", None) is not None else "off"
|
|
273
|
+
down = [k for k, v in comps.items() if v == "down"]
|
|
274
|
+
return {"status": "degraded" if down else "ok", "components": comps, "version": __version__}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
# ── 监控 / 追踪 / 网关 ──────────────────────────────────────
|
|
278
|
+
|
|
279
|
+
@app.get("/api/metrics")
|
|
280
|
+
async def metrics_json():
|
|
281
|
+
"""可观测:各端点/LLM 的计数、错误率、延迟分位(p50/p95/avg)+ uptime。"""
|
|
282
|
+
from ...core.metrics import METRICS
|
|
283
|
+
return METRICS.snapshot()
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@app.get("/api/traces")
|
|
287
|
+
async def list_traces(limit: int = 30, persisted: bool = False,
|
|
288
|
+
slow_ms: float = None, errors_only: bool = False):
|
|
289
|
+
"""请求链路。默认看内存里最近若干条;persisted=1 查**已落库的历史**
|
|
290
|
+
(可加 slow_ms / errors_only 过滤)——「找出昨天那次慢请求」走这里。"""
|
|
291
|
+
from ...core.tracing import TRACES
|
|
292
|
+
limit = max(1, min(limit, 200))
|
|
293
|
+
if persisted or slow_ms is not None or errors_only:
|
|
294
|
+
return {"traces": TRACES.query(limit=limit, slow_ms=slow_ms,
|
|
295
|
+
errors_only=errors_only), "source": "persisted"}
|
|
296
|
+
return {"traces": TRACES.list(limit=limit), "source": "memory"}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@app.get("/api/traces/stats")
|
|
300
|
+
async def traces_stats():
|
|
301
|
+
"""OTLP 导出状态(未配 FOXCLAW_OTLP_ENDPOINT 则 enabled=false,完全不启用)。
|
|
302
|
+
注意:本路由必须定义在 /api/traces/{trace_id} **之前**,否则 "stats" 会被当成 trace_id。"""
|
|
303
|
+
from ...core.tracing import OTLP
|
|
304
|
+
return {"otlp": OTLP.stats()}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@app.get("/api/traces/{trace_id}")
|
|
308
|
+
async def get_trace(trace_id: str):
|
|
309
|
+
"""单条链路详情:各 span 的耗时/成败(定位这次慢在哪)。内存未命中则回落到库。"""
|
|
310
|
+
from ...core.tracing import TRACES
|
|
311
|
+
d = TRACES.get_dict(trace_id)
|
|
312
|
+
if d is None:
|
|
313
|
+
raise HTTPException(404, "trace 不存在(内存环已滚出且未被采样落库)")
|
|
314
|
+
return d
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@app.get("/api/gateway/stats")
|
|
318
|
+
async def gateway_stats():
|
|
319
|
+
"""API 网关状态:是否启用 + 每分钟/每小时额度 + 当前追踪的 client 数 + 累计拒绝数。"""
|
|
320
|
+
return {"enabled": _gateway_on(), **_GATEWAY.stats()}
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@app.get("/metrics")
|
|
324
|
+
async def metrics_prometheus():
|
|
325
|
+
"""Prometheus 文本格式(供 scrape)。"""
|
|
326
|
+
from ...core.metrics import METRICS
|
|
327
|
+
return PlainTextResponse(METRICS.prometheus_text())
|