foxclaw 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +1 -8
- package/package.json +1 -1
- package/python/.env.example +8 -0
- package/python/.github/workflows/ci.yml +47 -2
- package/python/CHANGELOG.md +57 -0
- package/python/CHANGELOG_CN.md +57 -0
- package/python/README.md +11 -2
- package/python/README_CN.md +32 -20
- package/python/bench/bm25_audit.py +1 -14
- package/python/bench/bm25_regression.py +0 -1
- package/python/bench/bm25_stress.py +0 -4
- package/python/bench/cd_audit.py +0 -23
- package/python/bench/chaos.py +6 -8
- package/python/bench/e2e_real_models.py +0 -1
- package/python/bench/llm_retry_test.py +2 -9
- package/python/bench/load_test.py +5 -8
- package/python/bench/vector_ivf_upgrade.py +1 -7
- package/python/docs/RELEASE.md +4 -3
- package/python/docs/RUN_CN.md +2 -2
- package/python/evals/governance/cases.py +0 -37
- package/python/evals/governance/run_evals.py +5 -37
- package/python/examples/governance_middleware_demo.py +1 -6
- package/python/examples/langgraph_governance_demo.py +7 -11
- package/python/foxclaw/__init__.py +34 -152
- package/python/foxclaw/agents/base.py +65 -136
- package/python/foxclaw/agents/impl/_helpers.py +2 -10
- package/python/foxclaw/agents/impl/finance.py +4 -21
- package/python/foxclaw/agents/impl/office.py +7 -24
- package/python/foxclaw/agents/impl/supervisor.py +12 -79
- package/python/foxclaw/agents/mixins/_context.py +0 -23
- package/python/foxclaw/agents/mixins/augment.py +10 -16
- package/python/foxclaw/agents/mixins/llm.py +7 -6
- package/python/foxclaw/agents/mixins/memory.py +7 -1
- package/python/foxclaw/agents/mixins/preference.py +58 -8
- package/python/foxclaw/agents/mixins/prompt.py +0 -6
- package/python/foxclaw/agents/mixins/skillbox.py +6 -6
- package/python/foxclaw/agents/mixins/tool_exec.py +16 -42
- package/python/foxclaw/api/__init__.py +18 -77
- package/python/foxclaw/api/_state.py +43 -0
- package/python/foxclaw/api/{app.py → bootstrap.py} +5 -32
- package/python/foxclaw/api/middleware.py +5 -28
- package/python/foxclaw/api/routes/ahs.py +60 -73
- package/python/foxclaw/api/routes/attachments.py +5 -6
- package/python/foxclaw/api/routes/browser.py +7 -11
- package/python/foxclaw/api/routes/domain.py +18 -26
- package/python/foxclaw/api/routes/knowledge.py +3 -21
- package/python/foxclaw/api/routes/llm_admin.py +21 -33
- package/python/foxclaw/api/routes/memory.py +28 -12
- package/python/foxclaw/api/routes/orchestration.py +3 -5
- package/python/foxclaw/api/routes/recovery.py +4 -3
- package/python/foxclaw/api/routes/review.py +2 -11
- package/python/foxclaw/api/routes/search.py +3 -7
- package/python/foxclaw/api/routes/system.py +33 -49
- package/python/foxclaw/api/routes/task.py +31 -49
- package/python/foxclaw/api/routes/ws.py +2 -6
- package/python/foxclaw/api/spine.py +1 -4
- package/python/foxclaw/browser/action/action_executor.py +1 -5
- package/python/foxclaw/browser/action/action_types.py +0 -1
- package/python/foxclaw/browser/action/url_policy.py +1 -5
- package/python/foxclaw/browser/adapter/web_adapter.py +1 -1
- package/python/foxclaw/browser/driver/playwright_driver.py +20 -16
- package/python/foxclaw/browser/perception/dom_extractor.py +0 -1
- package/python/foxclaw/browser/perception/element_indexer.py +1 -2
- package/python/foxclaw/browser/service.py +7 -11
- package/python/foxclaw/capability/embedding_service.py +4 -6
- package/python/foxclaw/channels/__init__.py +9 -11
- package/python/foxclaw/channels/discord.py +4 -13
- package/python/foxclaw/channels/dispatcher.py +2 -27
- package/python/foxclaw/channels/feishu.py +10 -20
- package/python/foxclaw/channels/openclaw_bridge.py +4 -9
- package/python/foxclaw/channels/signing.py +9 -9
- package/python/foxclaw/channels/telegram.py +3 -4
- package/python/foxclaw/channels/weixin.py +7 -17
- package/python/foxclaw/cli.py +42 -13
- package/python/foxclaw/cognitive/reasoner.py +9 -10
- package/python/foxclaw/cognitive/state.py +18 -24
- package/python/foxclaw/core/audit_chain.py +1 -3
- package/python/foxclaw/core/audit_sink.py +3 -3
- package/python/foxclaw/core/compaction.py +8 -26
- package/python/foxclaw/core/config.py +2 -2
- package/python/foxclaw/core/confirm_nonce.py +6 -12
- package/python/foxclaw/core/envfile.py +2 -2
- package/python/foxclaw/core/failure_policy.py +17 -26
- package/python/foxclaw/core/gateway.py +6 -10
- package/python/foxclaw/core/governance_audit.py +3 -7
- package/python/foxclaw/core/idempotency.py +15 -22
- package/python/foxclaw/core/injection_guard.py +10 -29
- package/python/foxclaw/core/memory_adapter.py +30 -57
- package/python/foxclaw/core/metrics.py +4 -5
- package/python/foxclaw/core/model_router.py +36 -148
- package/python/foxclaw/core/paths.py +1 -1
- package/python/foxclaw/core/recovery.py +21 -34
- package/python/foxclaw/core/scheduler.py +23 -26
- package/python/foxclaw/core/secrets.py +0 -3
- package/python/foxclaw/core/sessions.py +4 -6
- package/python/foxclaw/core/settings.py +16 -32
- package/python/foxclaw/core/shared_write.py +11 -23
- package/python/foxclaw/core/steering_queue.py +12 -11
- package/python/foxclaw/core/system_bus.py +1 -1
- package/python/foxclaw/core/thought_chain.py +30 -32
- package/python/foxclaw/core/token_meter.py +9 -13
- package/python/foxclaw/core/tools.py +4 -12
- package/python/foxclaw/core/tracing.py +25 -40
- package/python/foxclaw/core/truth_pipeline.py +5 -9
- package/python/foxclaw/core/update_check.py +149 -0
- package/python/foxclaw/core/vec_recall_adapter.py +11 -37
- package/python/foxclaw/core/verify.py +6 -14
- package/python/foxclaw/core/web_search.py +4 -28
- package/python/foxclaw/finance/evidence.py +14 -18
- package/python/foxclaw/finance/fx.py +1 -3
- package/python/foxclaw/finance/graph.py +9 -18
- package/python/foxclaw/finance/models.py +20 -24
- package/python/foxclaw/finance/nlparse.py +12 -72
- package/python/foxclaw/finance/policy.py +8 -36
- package/python/foxclaw/finance/reasoner.py +3 -11
- package/python/foxclaw/finance/reasoning.py +13 -34
- package/python/foxclaw/finance/reconcile.py +5 -10
- package/python/foxclaw/finance/runtime.py +19 -46
- package/python/foxclaw/governance/middleware.py +13 -21
- package/python/foxclaw/harness/_base.py +2 -8
- package/python/foxclaw/harness/_policy.py +19 -38
- package/python/foxclaw/harness/drift.py +1 -13
- package/python/foxclaw/harness/event_bus.py +2 -5
- package/python/foxclaw/harness/harness.py +9 -97
- package/python/foxclaw/harness/loop_detection.py +11 -18
- package/python/foxclaw/itinerary/amap_client.py +2 -5
- package/python/foxclaw/itinerary/models.py +7 -7
- package/python/foxclaw/itinerary/reasoner.py +1 -6
- package/python/foxclaw/itinerary/reasoning.py +15 -21
- package/python/foxclaw/itinerary/runtime.py +8 -12
- package/python/foxclaw/kernel/capabilities.py +5 -6
- package/python/foxclaw/kernel/policy_gate.py +4 -10
- package/python/foxclaw/kernel/process_manager.py +17 -36
- package/python/foxclaw/kernel/scheduler.py +3 -9
- package/python/foxclaw/kernel/sentinel.py +13 -23
- package/python/foxclaw/kernel/sentinel_client.py +12 -15
- package/python/foxclaw/kernel/spine.py +9 -9
- package/python/foxclaw/knowledge/__init__.py +4 -20
- package/python/foxclaw/knowledge/truth_crystallizer.py +5 -8
- package/python/foxclaw/learning/knowledge_graph.py +4 -9
- package/python/foxclaw/learning/models.py +13 -13
- package/python/foxclaw/learning/reasoner.py +1 -4
- package/python/foxclaw/learning/runtime.py +19 -31
- package/python/foxclaw/learning/spaced_repetition.py +2 -7
- package/python/foxclaw/mcp_server.py +2 -3
- package/python/foxclaw/orchestration/__init__.py +16 -33
- package/python/foxclaw/pipeline/cognitive_pipeline.py +45 -97
- package/python/foxclaw/pipeline/stage_meta.py +21 -0
- package/python/foxclaw/pipeline/supervisor_stages.py +13 -42
- package/python/foxclaw/planning/llm_planner.py +3 -3
- package/python/foxclaw/planning/planner.py +6 -11
- package/python/foxclaw/search/agent_port.py +7 -11
- package/python/foxclaw/search/evidence.py +0 -4
- package/python/foxclaw/search/internal_search.py +3 -5
- package/python/foxclaw/search/models.py +11 -13
- package/python/foxclaw/search/planner.py +3 -3
- package/python/foxclaw/search/retriever.py +1 -1
- package/python/foxclaw/search/runtime.py +1 -8
- package/python/foxclaw/skills/__init__.py +46 -100
- package/python/foxclaw/skills/archive.py +111 -0
- package/python/foxclaw/skills/fetch.py +226 -0
- package/python/foxclaw/skills/hooks_spec.py +21 -25
- package/python/foxclaw/skills/install_cmd.py +216 -0
- package/python/foxclaw/skills/lifecycle.py +16 -42
- package/python/foxclaw/skills/markdown_skill.py +10 -9
- package/python/foxclaw/skills/mcp_client.py +5 -11
- package/python/foxclaw/skills/mcp_store.py +0 -3
- package/python/foxclaw/skills/permission.py +13 -48
- package/python/foxclaw/skills/pipeline.py +21 -53
- package/python/foxclaw/skills/policy.py +4 -10
- package/python/foxclaw/skills/review.py +3 -3
- package/python/frontend/deck/auth.js +0 -7
- package/python/frontend/deck/chat_model_picker.js +0 -54
- package/python/frontend/deck/domain.js +0 -20
- package/python/frontend/deck/main.js +0 -144
- package/python/frontend/deck/markdown.js +0 -4
- package/python/frontend/deck/settings.js +1 -18
- package/python/frontend/deck/state.js +0 -1
- package/python/frontend/deck/util.js +0 -2
- package/python/frontend/deck/visuals.js +0 -22
- package/python/frontend/planet/planet.js +0 -201
- package/python/frontend/v3/assets/index-CQSBdxk4.js +34 -0
- package/python/frontend/v3/assets/index-D5nIsjms.css +1 -0
- package/python/frontend/v3/index.html +3 -3
- package/python/frontend/widget_runtime.js +0 -43
- package/python/frontend/widgets.js +0 -57
- package/python/frontend_src/index.html +1 -1
- package/python/frontend_src/package-lock.json +2 -2
- package/python/frontend_src/package.json +1 -1
- package/python/frontend_src/src/App.vue +3 -7
- package/python/frontend_src/src/api.js +0 -7
- package/python/frontend_src/src/components/ChatDock.vue +27 -39
- package/python/frontend_src/src/components/DocViewer.vue +0 -9
- package/python/frontend_src/src/components/Inspector.vue +0 -6
- package/python/frontend_src/src/components/LeftRail.vue +60 -39
- package/python/frontend_src/src/components/MenuPopover.vue +9 -11
- package/python/frontend_src/src/components/MessageStream.vue +18 -30
- package/python/frontend_src/src/components/ModelPicker.vue +0 -5
- package/python/frontend_src/src/components/QuickIngest.vue +0 -6
- package/python/frontend_src/src/components/Stage.vue +71 -25
- package/python/frontend_src/src/components/ToolMenu.vue +0 -3
- package/python/frontend_src/src/components/TopBar.vue +38 -10
- package/python/frontend_src/src/components/panels/ActivityPanel.vue +0 -2
- package/python/frontend_src/src/components/panels/AgentsPanel.vue +2 -5
- package/python/frontend_src/src/components/panels/KnowledgePanel.vue +0 -16
- package/python/frontend_src/src/components/panels/MemoryPanel.vue +0 -1
- package/python/frontend_src/src/components/panels/PanelBox.vue +6 -7
- package/python/frontend_src/src/components/panels/ReviewPanel.vue +0 -16
- package/python/frontend_src/src/components/panels/SettingsPanel.vue +142 -38
- package/python/frontend_src/src/components/panels/TasksPanel.vue +0 -1
- package/python/frontend_src/src/components/panels/ToolsPanel.vue +125 -19
- package/python/frontend_src/src/lib/fmt.js +0 -4
- package/python/frontend_src/src/lib/i18n.js +53 -0
- package/python/frontend_src/src/lib/locales/en.js +173 -0
- package/python/frontend_src/src/lib/locales/zh.js +174 -0
- package/python/frontend_src/src/lib/md.js +0 -14
- package/python/frontend_src/src/main.js +2 -0
- package/python/frontend_src/src/stores/app.js +17 -32
- package/python/frontend_src/src/stores/chat.js +0 -34
- package/python/frontend_src/src/stores/model.js +0 -8
- package/python/frontend_src/src/stores/panels.js +0 -41
- package/python/frontend_src/src/stores/profile.js +31 -0
- package/python/frontend_src/src/stores/system.js +25 -0
- package/python/frontend_src/vite.config.js +0 -7
- package/python/pyproject.toml +6 -3
- package/python/requirements.txt +311 -0
- package/python/tests/conftest.py +0 -22
- package/python/tests/run_all.py +3 -66
- package/python/tests/test_agent_lifecycle.py +4 -38
- package/python/tests/test_ahs_p2.py +4 -25
- package/python/tests/test_anthropic_tools.py +1 -15
- package/python/tests/test_api.py +19 -9
- package/python/tests/test_api_auth.py +1 -5
- package/python/tests/test_attachments.py +9 -25
- package/python/tests/test_audit_chain.py +2 -7
- package/python/tests/test_audit_sink.py +0 -6
- package/python/tests/test_browser.py +2 -6
- package/python/tests/test_browser_request_guard.py +0 -4
- package/python/tests/test_capabilities.py +0 -6
- package/python/tests/test_channel_signing.py +2 -9
- package/python/tests/test_channels.py +1 -11
- package/python/tests/test_config_layering.py +0 -4
- package/python/tests/test_confirm_nonce.py +1 -12
- package/python/tests/test_context_compaction_wiring.py +1 -7
- package/python/tests/test_dispatch.py +0 -6
- package/python/tests/test_domain_evals.py +11 -80
- package/python/tests/test_domain_import.py +0 -6
- package/python/tests/test_domain_reasoners.py +0 -14
- package/python/tests/test_e2e.py +0 -17
- package/python/tests/test_emb_cache.py +7 -17
- package/python/tests/test_embedding_singleton.py +1 -3
- package/python/tests/test_evals_smoke.py +0 -24
- package/python/tests/test_event_spine.py +10 -18
- package/python/tests/test_evidence_semantic.py +1 -14
- package/python/tests/test_evidence_sse.py +3 -6
- package/python/tests/test_file_ingest.py +0 -11
- package/python/tests/test_finance.py +0 -9
- package/python/tests/test_finance_ledger.py +5 -12
- package/python/tests/test_finance_persist.py +8 -12
- package/python/tests/test_gateway.py +3 -22
- package/python/tests/test_governance.py +5 -52
- package/python/tests/test_governance_audit.py +4 -39
- package/python/tests/test_governance_middleware.py +1 -7
- package/python/tests/test_hkc.py +6 -24
- package/python/tests/test_hkc_index_build_manager.py +0 -33
- package/python/tests/test_hkc_manifest_reuse.py +4 -27
- package/python/tests/test_hkc_search_event_sync.py +3 -54
- package/python/tests/test_import_path_guard.py +0 -6
- package/python/tests/test_injection_guard.py +18 -36
- package/python/tests/test_integration.py +5 -29
- package/python/tests/test_itinerary.py +0 -3
- package/python/tests/test_kee_semantic.py +0 -5
- package/python/tests/test_kernel.py +1 -11
- package/python/tests/test_learning.py +3 -12
- package/python/tests/test_llm_admin.py +4 -19
- package/python/tests/test_llm_planner.py +1 -23
- package/python/tests/test_markdown_skills.py +3 -14
- package/python/tests/test_markdown_skills_openclaw.py +4 -9
- package/python/tests/test_mcp_connectors.py +3 -15
- package/python/tests/test_mcp_guardian_discovery.py +2 -6
- package/python/tests/test_mcp_no_memory.py +3 -3
- package/python/tests/test_mcp_policy_gate.py +0 -1
- package/python/tests/test_mcp_server.py +6 -9
- package/python/tests/test_memory_recall.py +1 -4
- package/python/tests/test_metrics.py +0 -8
- package/python/tests/test_multi_chain.py +1 -13
- package/python/tests/test_multimodal.py +5 -18
- package/python/tests/test_negotiation.py +3 -13
- package/python/tests/test_orchestration.py +1 -11
- package/python/tests/test_orchestration_decoupled.py +0 -6
- package/python/tests/test_p3_p5_hardening.py +3 -17
- package/python/tests/test_parallel_dispatch.py +4 -12
- package/python/tests/test_pipeline.py +17 -58
- package/python/tests/test_plan_visible.py +4 -4
- package/python/tests/test_planner.py +0 -7
- package/python/tests/test_policy_gate.py +2 -3
- package/python/tests/test_profile.py +67 -0
- package/python/tests/test_readme_claims.py +163 -0
- package/python/tests/test_reasoner.py +2 -17
- package/python/tests/test_release_check.py +2 -13
- package/python/tests/test_resume_checkpoint.py +7 -15
- package/python/tests/test_routing.py +2 -12
- package/python/tests/test_scheduler.py +7 -10
- package/python/tests/test_search.py +1 -11
- package/python/tests/test_secrets.py +4 -10
- package/python/tests/test_semantic.py +1 -10
- package/python/tests/test_sessions.py +3 -4
- package/python/tests/test_settings.py +2 -18
- package/python/tests/test_shadow_learning.py +3 -43
- package/python/tests/test_shared_write.py +0 -7
- package/python/tests/test_skill_taint_gate.py +1 -10
- package/python/tests/test_skills.py +2 -18
- package/python/tests/test_skills_fetch.py +282 -0
- package/python/tests/test_stats_and_events.py +2 -5
- package/python/tests/test_streaming.py +2 -7
- package/python/tests/test_supervisor_pipeline.py +4 -5
- package/python/tests/test_supervisor_triage.py +4 -10
- package/python/tests/test_taint.py +2 -7
- package/python/tests/test_task_stream.py +1 -12
- package/python/tests/test_thinking_sse.py +0 -1
- package/python/tests/test_tool_center.py +1 -17
- package/python/tests/test_tool_parallel.py +2 -2
- package/python/tests/test_tool_params.py +1 -5
- package/python/tests/test_tool_use.py +0 -13
- package/python/tests/test_toplevel_concurrency.py +3 -4
- package/python/tests/test_tracing.py +7 -41
- package/python/tests/test_truth_crystallizer.py +4 -6
- package/python/tests/test_truth_flow.py +2 -7
- package/python/tests/test_update_check.py +95 -0
- package/python/tests/test_verify_evidence.py +8 -21
- package/python/tests/test_version_sync.py +35 -6
- package/python/tests/test_web_trigger.py +0 -17
- package/python/tools/check_faiss_type.py +0 -1
- package/python/tools/check_log.ps1 +0 -8
- package/python/tools/mypy_baseline.txt +12 -4
- package/python/tools/mypy_ratchet.py +52 -6
- package/python/tools/pack.py +0 -5
- package/python/tools/release_check.py +7 -7
- package/python/tools/restart_foxclaw.ps1 +5 -8
- package/python/tools/shared_write_audit.py +1 -15
- package/python/tools/size_check.py +1 -1
- package/python/tools/strip_comments.py +228 -0
- package/python/frontend/v3/assets/index-BD7VPbMV.js +0 -33
- package/python/frontend/v3/assets/index-CAvnYWGH.css +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""知识核心(HKC)端点:同进程 DirectClient,惰性装配,缺依赖降级。
|
|
2
2
|
|
|
3
|
-
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.
|
|
3
|
+
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.current_os()`
|
|
4
4
|
晚绑定读取;路由用 `@app` 装饰器在 import 时注册。
|
|
5
5
|
"""
|
|
6
6
|
|
|
@@ -13,13 +13,9 @@ import foxclaw.api as _api
|
|
|
13
13
|
from .. import app
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
# ── 知识核心(HKC):同进程 DirectClient,惰性装配,缺依赖降级 ─────────
|
|
17
|
-
# HKC = Foxclaw Knowledge Core:KDE 抽取 → KEE 演化 → 混合检索(BM25+向量+图)。
|
|
18
|
-
# 首次命中任一路由时才装配(见 Foxclaw.hkc 惰性属性);未 vendor/缺依赖时 available=False。
|
|
19
|
-
|
|
20
16
|
class KnowledgeSearchPayload(BaseModel):
|
|
21
17
|
query: str
|
|
22
|
-
mode: str = "hybrid"
|
|
18
|
+
mode: str = "hybrid"
|
|
23
19
|
top_k: int = 10
|
|
24
20
|
domain: str = ""
|
|
25
21
|
|
|
@@ -33,15 +29,11 @@ class KnowledgeIngestPayload(BaseModel):
|
|
|
33
29
|
def _hkc_or_none():
|
|
34
30
|
"""惰性取 HKCService;None 表示降级(未 vendor/缺依赖/装配失败)。"""
|
|
35
31
|
try:
|
|
36
|
-
return _api.
|
|
32
|
+
return _api.os_instance().hkc
|
|
37
33
|
except Exception:
|
|
38
34
|
return None
|
|
39
35
|
|
|
40
36
|
|
|
41
|
-
# 【P3-7】异常分类用 isinstance 优先:hasattr(e, "missing_skills") 是鸭子判断,
|
|
42
|
-
# 任何恰好带了同名属性的无关异常都会被误吞成「覆盖不足」证据 gate(真实错误被掩盖)。
|
|
43
|
-
# 真实类型在 vendor 的 hkc_sdk.exceptions(test_hkc.py 同源引用);缺 vendor/hkc 时
|
|
44
|
-
# import 失败降级 None,运行时回退 hasattr 判断,行为与旧版一致。
|
|
45
37
|
try:
|
|
46
38
|
from hkc_sdk.exceptions import HKCInsufficientCoverage as _HKCInsuffCov
|
|
47
39
|
except Exception:
|
|
@@ -76,9 +68,6 @@ async def knowledge_ingest(p: KnowledgeIngestPayload):
|
|
|
76
68
|
hkc.client.ingest_text, p.text, p.source_title, p.domain)
|
|
77
69
|
return {"ku_count": res.ku_count, "ku_ids": res.ku_ids, "counts": res.counts}
|
|
78
70
|
except Exception as e:
|
|
79
|
-
# 抽取全段失败时 KDE 现在会抛错(带 provider/model/最后一次 LLM 报错原文)。
|
|
80
|
-
# 不能像旧版那样返回 ku_count=0 + 200 —— 那会让「库里其实什么都没有」看起来
|
|
81
|
-
# 像「录入成功只是没内容」,复习板块于是永远 0 卡。
|
|
82
71
|
msg = str(e).strip() or e.__class__.__name__
|
|
83
72
|
raise HTTPException(502, f"摄入失败:{msg}")
|
|
84
73
|
|
|
@@ -103,7 +92,6 @@ async def knowledge_recent(limit: int = 10):
|
|
|
103
92
|
except Exception as e:
|
|
104
93
|
return {"available": True, "total": 0, "kus": [], "error": str(e)}
|
|
105
94
|
|
|
106
|
-
# ISO 字符串,字典序即时间序
|
|
107
95
|
kus.sort(key=lambda k: getattr(k, "created_at", "") or "", reverse=True)
|
|
108
96
|
n = max(1, min(int(limit or 10), 200))
|
|
109
97
|
return {"available": True, "total": len(kus), "kus": [
|
|
@@ -114,7 +102,6 @@ async def knowledge_recent(limit: int = 10):
|
|
|
114
102
|
for ku in kus[:n]]}
|
|
115
103
|
|
|
116
104
|
|
|
117
|
-
# ── HKC 融合第 2 档 Step1:ACE 能力(只读查询面,无 LLM、零成本)─────────
|
|
118
105
|
@app.get("/api/knowledge/abilities")
|
|
119
106
|
async def knowledge_abilities():
|
|
120
107
|
"""列出可编译的能力 key(来自 ACE skill_taxonomy)。只读、确定性、无 LLM。"""
|
|
@@ -139,11 +126,6 @@ async def knowledge_ability(ability_key: str):
|
|
|
139
126
|
try:
|
|
140
127
|
ab = await asyncio.to_thread(hkc.client.compile_ability, ability_key)
|
|
141
128
|
except Exception as e:
|
|
142
|
-
# 覆盖不足(HKCInsufficientCoverage 带 coverage/missing_skills)→ 证据 gate,非错误。
|
|
143
|
-
# 【P3-7】isinstance 可用时**只**用 isinstance(精确判断:带 missing_skills 属性的
|
|
144
|
-
# 无关异常不再被误吞成「覆盖不足」);import 失败(缺 vendor)才回退 hasattr 鸭子判断。
|
|
145
|
-
# 文稿骨架写的是 isinstance OR hasattr 叠加,与其自身设计决策(「失败才回退」=二选一)
|
|
146
|
-
# 不一致——OR 下鸭子判断永远生效,精确化形同虚设,故按设计决策实现。
|
|
147
129
|
if _HKCInsuffCov is not None:
|
|
148
130
|
is_insufficient = isinstance(e, _HKCInsuffCov)
|
|
149
131
|
else:
|
|
@@ -28,7 +28,6 @@ CUSTOM_MODELS_PATH = _DATA_DIR / "custom_models.json"
|
|
|
28
28
|
CURRENT_MODEL_PATH = _DATA_DIR / ".current_model"
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
# 预置 provider(展示用:标签 + 配色 + 探测 key 名)
|
|
32
31
|
_PRESET_PROVIDERS = [
|
|
33
32
|
{"provider": "generic", "label": "通用 OpenAI 兼容", "color": "#7e93b8"},
|
|
34
33
|
{"provider": "anthropic", "label": "Claude", "color": "#ff9d00"},
|
|
@@ -52,8 +51,6 @@ _PROVIDER_KEY_ENV = {
|
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
|
|
55
|
-
# ── 工具 ─────────────────────────────────────────────────
|
|
56
|
-
|
|
57
54
|
def _save_key(name: str, key: str) -> dict:
|
|
58
55
|
"""把 key 存到最安全的地方,返回该写进 custom_models.json 的字段。
|
|
59
56
|
|
|
@@ -98,7 +95,8 @@ def _sync_hkc_llm() -> None:
|
|
|
98
95
|
extractor 自带 ``reconfigure``(HKC 自己的界面也用它),这里复用同一条路径。
|
|
99
96
|
"""
|
|
100
97
|
try:
|
|
101
|
-
|
|
98
|
+
os_inst = _api.current_os()
|
|
99
|
+
hkc = os_inst.hkc if os_inst is not None else None
|
|
102
100
|
if hkc is None or not getattr(hkc, "available", False):
|
|
103
101
|
return
|
|
104
102
|
from foxclaw.knowledge import _detect_llm
|
|
@@ -109,12 +107,10 @@ def _sync_hkc_llm() -> None:
|
|
|
109
107
|
model=model or None,
|
|
110
108
|
base_url=base or None,
|
|
111
109
|
)
|
|
112
|
-
except Exception as e:
|
|
110
|
+
except Exception as e:
|
|
113
111
|
print(f"[llm_admin] 同步知识抽取(HKC)配置失败: {e}")
|
|
114
112
|
|
|
115
113
|
|
|
116
|
-
# ── 端点 ─────────────────────────────────────────────────
|
|
117
|
-
|
|
118
114
|
@app.get("/api/llm/models")
|
|
119
115
|
async def list_models():
|
|
120
116
|
"""列出所有可用模型:预置(按 .env 探测) + 自定义(从 custom_models.json) + 当前选中。"""
|
|
@@ -143,7 +139,7 @@ async def list_models():
|
|
|
143
139
|
|
|
144
140
|
|
|
145
141
|
class _SelectReq(BaseModel):
|
|
146
|
-
source: str
|
|
142
|
+
source: str
|
|
147
143
|
provider: Optional[str] = None
|
|
148
144
|
name: Optional[str] = None
|
|
149
145
|
|
|
@@ -155,7 +151,8 @@ async def select_model(req: _SelectReq):
|
|
|
155
151
|
(key 空 / BASE URL 非法 / 记录已被删),前端显示"已切换",聊天却继续走旧 provider,
|
|
156
152
|
用户看到的是"填了填不上"。现在选中前先自检,失败直接 400 带原因。
|
|
157
153
|
"""
|
|
158
|
-
|
|
154
|
+
os_inst = _api.current_os()
|
|
155
|
+
router = os_inst.router if os_inst is not None else None
|
|
159
156
|
if req.source == "auto":
|
|
160
157
|
if CURRENT_MODEL_PATH.exists():
|
|
161
158
|
CURRENT_MODEL_PATH.unlink()
|
|
@@ -171,24 +168,25 @@ async def select_model(req: _SelectReq):
|
|
|
171
168
|
raise HTTPException(400, f"{req.provider} 未在 .env 配置 Key({env_key})")
|
|
172
169
|
data = {"source": "preset", "provider": req.provider}
|
|
173
170
|
elif req.source == "custom":
|
|
171
|
+
name = req.name
|
|
172
|
+
if not name:
|
|
173
|
+
raise HTTPException(400, "缺少自定义模型名称 name")
|
|
174
174
|
models = _load_custom()
|
|
175
|
-
match = next((m for m in models if m.get("name") ==
|
|
175
|
+
match = next((m for m in models if m.get("name") == name), None)
|
|
176
176
|
if not match:
|
|
177
|
-
raise HTTPException(404, f"未找到自定义模型: {
|
|
178
|
-
# 预检:能不能真构造出 client(否则"切换成功"是假象)
|
|
177
|
+
raise HTTPException(404, f"未找到自定义模型: {name}")
|
|
179
178
|
if router is not None:
|
|
180
|
-
client, _model, err = router._build_custom_client(
|
|
179
|
+
client, _model, err = router._build_custom_client(name)
|
|
181
180
|
if client is None:
|
|
182
181
|
raise HTTPException(400, f"该自定义模型不可用: {err}")
|
|
183
|
-
data = {"source": "custom", "name":
|
|
182
|
+
data = {"source": "custom", "name": name, "model_id": match.get("model", "")}
|
|
184
183
|
else:
|
|
185
184
|
raise HTTPException(400, f"未知 source: {req.source}")
|
|
186
185
|
CURRENT_MODEL_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
187
186
|
CURRENT_MODEL_PATH.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
|
|
188
|
-
# 立即 reload(mtime 变了);正常聊天路径也会 lazy reload,这里只是让 /runtime 立刻正确
|
|
189
187
|
if router is not None:
|
|
190
188
|
router._maybe_reload_selection()
|
|
191
|
-
_sync_hkc_llm()
|
|
189
|
+
_sync_hkc_llm()
|
|
192
190
|
return {"ok": True, "current": data,
|
|
193
191
|
"runtime": ({"provider": router.provider,
|
|
194
192
|
"model": getattr(router, "model", ""),
|
|
@@ -248,7 +246,7 @@ class _CustomUpdateReq(BaseModel):
|
|
|
248
246
|
api_key: Optional[str] = None
|
|
249
247
|
model: Optional[str] = None
|
|
250
248
|
extra_headers: Optional[Dict[str, str]] = None
|
|
251
|
-
group_id: Optional[str] = None
|
|
249
|
+
group_id: Optional[str] = None
|
|
252
250
|
note: Optional[str] = None
|
|
253
251
|
|
|
254
252
|
|
|
@@ -262,7 +260,6 @@ async def get_custom_model(name: str):
|
|
|
262
260
|
raise HTTPException(404, f"未找到: {name}")
|
|
263
261
|
extra = dict(match.get("extra_headers") or {})
|
|
264
262
|
gid = extra.get("MiniMax-Group-Id", "")
|
|
265
|
-
# extra_headers 拆成 group_id + 其它(避免两边都返回重复键)
|
|
266
263
|
extra_only = {k: v for k, v in extra.items() if k != "MiniMax-Group-Id"}
|
|
267
264
|
return {
|
|
268
265
|
"name": match.get("name", ""),
|
|
@@ -291,18 +288,15 @@ async def update_custom_model(name: str, req: _CustomUpdateReq):
|
|
|
291
288
|
raise HTTPException(404, f"未找到: {name}")
|
|
292
289
|
target = models[idx]
|
|
293
290
|
|
|
294
|
-
# 改名冲突检测
|
|
295
291
|
new_name = (req.name or "").strip() or target["name"]
|
|
296
292
|
if new_name != name and any(m.get("name") == new_name for m in models):
|
|
297
293
|
raise HTTPException(409, f"已存在同名模型: {new_name}")
|
|
298
294
|
|
|
299
|
-
# 必填校验
|
|
300
295
|
if req.base_url is not None and not req.base_url.strip():
|
|
301
296
|
raise HTTPException(400, "base_url 不能为空")
|
|
302
297
|
if req.model is not None and not req.model.strip():
|
|
303
298
|
raise HTTPException(400, "model 不能为空")
|
|
304
299
|
|
|
305
|
-
# 应用普通字段
|
|
306
300
|
if req.base_url is not None:
|
|
307
301
|
try:
|
|
308
302
|
target["base_url"] = normalize_base_url(req.base_url)
|
|
@@ -311,12 +305,10 @@ async def update_custom_model(name: str, req: _CustomUpdateReq):
|
|
|
311
305
|
if req.model is not None:
|
|
312
306
|
target["model"] = req.model.strip()
|
|
313
307
|
if req.api_key is not None and req.api_key.strip():
|
|
314
|
-
# 留空 = 保留原 key(防误清);非空 = 覆盖(按新名字存,改名时不会留下孤儿条目)
|
|
315
308
|
target.update(_save_key(new_name, req.api_key))
|
|
316
309
|
if req.note is not None:
|
|
317
310
|
target["note"] = req.note
|
|
318
311
|
|
|
319
|
-
# extra_headers + group_id 合并处理
|
|
320
312
|
if req.extra_headers is not None or req.group_id is not None:
|
|
321
313
|
extra = dict(target.get("extra_headers") or {})
|
|
322
314
|
if req.extra_headers is not None:
|
|
@@ -329,10 +321,7 @@ async def update_custom_model(name: str, req: _CustomUpdateReq):
|
|
|
329
321
|
extra.pop("MiniMax-Group-Id", None)
|
|
330
322
|
target["extra_headers"] = extra
|
|
331
323
|
|
|
332
|
-
# 改名:同步 .current_model
|
|
333
324
|
if new_name != name:
|
|
334
|
-
# 凭据按模型名存放 → 不改名搬迁就再也 load 不到(key 静默丢失)。
|
|
335
|
-
# 已换新 key 时新条目已在 new_name 下,这里只需清掉旧的。
|
|
336
325
|
if req.api_key is not None and req.api_key.strip():
|
|
337
326
|
_secrets.delete(name)
|
|
338
327
|
else:
|
|
@@ -361,8 +350,7 @@ async def delete_custom_model(name: str):
|
|
|
361
350
|
if len(new_models) == len(models):
|
|
362
351
|
raise HTTPException(404, f"未找到: {name}")
|
|
363
352
|
_save_custom(new_models)
|
|
364
|
-
_secrets.delete(name)
|
|
365
|
-
# 如果当前选的就是它 → 清空选择
|
|
353
|
+
_secrets.delete(name)
|
|
366
354
|
if CURRENT_MODEL_PATH.exists():
|
|
367
355
|
try:
|
|
368
356
|
cur = json.loads(CURRENT_MODEL_PATH.read_text(encoding="utf-8"))
|
|
@@ -374,7 +362,7 @@ async def delete_custom_model(name: str):
|
|
|
374
362
|
|
|
375
363
|
|
|
376
364
|
class _TestReq(BaseModel):
|
|
377
|
-
source: str
|
|
365
|
+
source: str
|
|
378
366
|
provider: Optional[str] = None
|
|
379
367
|
name: Optional[str] = None
|
|
380
368
|
|
|
@@ -391,7 +379,8 @@ async def test_model(req: _TestReq):
|
|
|
391
379
|
if req.source == "preset":
|
|
392
380
|
if not req.provider:
|
|
393
381
|
raise HTTPException(400, "preset 必须给 provider")
|
|
394
|
-
|
|
382
|
+
os_inst = _api.current_os()
|
|
383
|
+
router = os_inst.router if os_inst is not None else None
|
|
395
384
|
if router is None:
|
|
396
385
|
raise HTTPException(503, "OS 未就绪")
|
|
397
386
|
client, err = router._build_client(req.provider)
|
|
@@ -444,11 +433,10 @@ async def test_model(req: _TestReq):
|
|
|
444
433
|
@app.get("/api/llm/runtime")
|
|
445
434
|
async def get_runtime():
|
|
446
435
|
"""当前真正生效的 provider/model(供前端高亮 + 调试)。"""
|
|
447
|
-
|
|
436
|
+
os_inst = _api.current_os()
|
|
437
|
+
router = os_inst.router if os_inst is not None else None
|
|
448
438
|
if router is None:
|
|
449
439
|
return {"ok": False, "error": "OS 未就绪"}
|
|
450
|
-
# runtime 端点也走 lazy reload,让前端选完模型 → 立刻看到生效值
|
|
451
|
-
# (complete() 入口也会调;这里额外调一次,GET 路径无副作用)
|
|
452
440
|
try:
|
|
453
441
|
router._maybe_reload_selection()
|
|
454
442
|
except Exception:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""记忆统计 / 会话历史 / 用户偏好 路由。
|
|
2
2
|
|
|
3
|
-
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.
|
|
4
|
-
晚绑定读取(
|
|
3
|
+
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.current_os()`
|
|
4
|
+
晚绑定读取(状态在 `_state`,测试可重绑 `_state._os_instance`);路由用 `@app` 装饰器在 import 时注册。
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from fastapi import HTTPException
|
|
@@ -15,12 +15,11 @@ from .. import app
|
|
|
15
15
|
async def get_memory_stats():
|
|
16
16
|
"""记忆中心统计:供前端 vecRecall 面板展示真实数字。"""
|
|
17
17
|
try:
|
|
18
|
-
return _api.
|
|
18
|
+
return _api.os_instance().hmr.stats()
|
|
19
19
|
except Exception:
|
|
20
20
|
return {"backend": "?", "total_memories": 0, "relations": 0}
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
# ── 会话历史 ───────────────────────────────────────────────
|
|
24
23
|
class SessionCreate(BaseModel):
|
|
25
24
|
title: str = ""
|
|
26
25
|
agent_id: str = "supervisor"
|
|
@@ -34,7 +33,7 @@ class SessionRename(BaseModel):
|
|
|
34
33
|
async def list_sessions(limit: int = 50):
|
|
35
34
|
"""会话列表(按更新时间倒序,轻字段不含 messages)。"""
|
|
36
35
|
try:
|
|
37
|
-
return {"sessions": _api.
|
|
36
|
+
return {"sessions": _api.os_instance().sessions.list(limit)}
|
|
38
37
|
except Exception:
|
|
39
38
|
return {"sessions": []}
|
|
40
39
|
|
|
@@ -42,13 +41,13 @@ async def list_sessions(limit: int = 50):
|
|
|
42
41
|
@app.post("/api/sessions")
|
|
43
42
|
async def create_session(req: SessionCreate):
|
|
44
43
|
"""新建会话。"""
|
|
45
|
-
return _api.
|
|
44
|
+
return _api.os_instance().sessions.create(req.title, req.agent_id)
|
|
46
45
|
|
|
47
46
|
|
|
48
47
|
@app.get("/api/sessions/{sid}")
|
|
49
48
|
async def get_session(sid: str):
|
|
50
49
|
"""取单个会话(含完整 messages),供前端恢复对话。"""
|
|
51
|
-
d = _api.
|
|
50
|
+
d = _api.os_instance().sessions.get(sid)
|
|
52
51
|
if d is None:
|
|
53
52
|
raise HTTPException(404, "会话不存在")
|
|
54
53
|
return d
|
|
@@ -56,19 +55,19 @@ async def get_session(sid: str):
|
|
|
56
55
|
|
|
57
56
|
@app.post("/api/sessions/{sid}/rename")
|
|
58
57
|
async def rename_session(sid: str, req: SessionRename):
|
|
59
|
-
return {"ok": _api.
|
|
58
|
+
return {"ok": _api.os_instance().sessions.rename(sid, req.title)}
|
|
60
59
|
|
|
61
60
|
|
|
62
61
|
@app.delete("/api/sessions/{sid}")
|
|
63
62
|
async def delete_session(sid: str):
|
|
64
|
-
return {"ok": _api.
|
|
63
|
+
return {"ok": _api.os_instance().sessions.delete(sid)}
|
|
65
64
|
|
|
66
65
|
|
|
67
66
|
@app.get("/api/memory/recent")
|
|
68
67
|
async def get_recent_memories(limit: int = 20):
|
|
69
68
|
"""最近的记忆条目(倒序),供前端右栏"记忆信息条"。轻字段,不含 embedding。"""
|
|
70
69
|
try:
|
|
71
|
-
fn = getattr(_api.
|
|
70
|
+
fn = getattr(_api.os_instance().hmr, "recent", None)
|
|
72
71
|
return {"memories": fn(limit) if callable(fn) else []}
|
|
73
72
|
except Exception:
|
|
74
73
|
return {"memories": []}
|
|
@@ -78,12 +77,29 @@ async def get_recent_memories(limit: int = 20):
|
|
|
78
77
|
async def list_preferences():
|
|
79
78
|
"""列出已记住的用户偏好(供查看/清理误存)。偏好是 shared,任一 Agent 视角一致,
|
|
80
79
|
这里用主管取全量(偏好是 shared,任一 Agent 视角一致)。返回 [{id, content, ...}]。"""
|
|
81
|
-
ag = _api.
|
|
80
|
+
ag = _api.os_instance().supervisor
|
|
82
81
|
return {"preferences": ag.list_preferences()}
|
|
83
82
|
|
|
84
83
|
|
|
85
84
|
@app.delete("/api/preferences/{pref_id}")
|
|
86
85
|
async def delete_preference(pref_id: str):
|
|
87
86
|
"""删一条误存的用户偏好(F3:防误报偏好持久注入污染)。返回 {ok}。"""
|
|
88
|
-
ag = _api.
|
|
87
|
+
ag = _api.os_instance().supervisor
|
|
89
88
|
return {"ok": ag.delete_preference(pref_id)}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ProfilePayload(BaseModel):
|
|
92
|
+
nickname: str = ""
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@app.get("/api/profile")
|
|
96
|
+
async def get_profile():
|
|
97
|
+
"""当前称呼。没设置过返回空串(前端据此决定要不要显示首次引导)。"""
|
|
98
|
+
return {"nickname": _api.os_instance().supervisor.get_nickname()}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@app.put("/api/profile")
|
|
102
|
+
async def update_profile(payload: ProfilePayload):
|
|
103
|
+
"""设置称呼。传空串 = 清除。返回生效后的值(已 trim/截断)。"""
|
|
104
|
+
nick = _api.os_instance().supervisor.set_nickname(payload.nickname)
|
|
105
|
+
return {"ok": True, "nickname": nick}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""主管编排端点(全局态势 + 跨 Agent)。
|
|
2
2
|
|
|
3
|
-
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.
|
|
3
|
+
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.current_os()`
|
|
4
4
|
晚绑定读取;路由用 `@app` 装饰器在 import 时注册。
|
|
5
5
|
"""
|
|
6
6
|
|
|
@@ -11,13 +11,11 @@ import foxclaw.api as _api
|
|
|
11
11
|
from .. import app
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
# ── 主管编排端点(全局态势 + 跨 Agent)──────────────────────
|
|
15
|
-
|
|
16
14
|
@app.get("/api/orchestration/situation")
|
|
17
15
|
async def global_situation():
|
|
18
16
|
"""主管全局态势:三内核状态汇总 + 跨域告警(驾驶舱视图)。"""
|
|
19
17
|
try:
|
|
20
|
-
return _api.
|
|
18
|
+
return _api.os_instance().supervisor.global_situation()
|
|
21
19
|
except Exception as e:
|
|
22
20
|
raise HTTPException(500, f"态势汇总失败: {e}")
|
|
23
21
|
|
|
@@ -34,7 +32,7 @@ class CrossReqPayload(BaseModel):
|
|
|
34
32
|
async def cross_request(p: CrossReqPayload):
|
|
35
33
|
"""跨 Agent 请求:目标 Agent 用守门人裁决(如行程申请差旅预算)。"""
|
|
36
34
|
try:
|
|
37
|
-
return _api.
|
|
35
|
+
return _api.os_instance().supervisor.cross_request(
|
|
38
36
|
from_agent=p.from_agent, to_agent=p.to_agent,
|
|
39
37
|
request_type=p.request_type, payload=p.payload, reason=p.reason)
|
|
40
38
|
except Exception as e:
|
|
@@ -16,7 +16,7 @@ from .. import app
|
|
|
16
16
|
@app.get("/api/recovery/status")
|
|
17
17
|
async def recovery_status():
|
|
18
18
|
"""中断任务清单 + 每任务最近步骤 + 续跑所需 context(直接可回灌 /api/task)。"""
|
|
19
|
-
os_ = _api.
|
|
19
|
+
os_ = _api.current_os()
|
|
20
20
|
if os_ is None:
|
|
21
21
|
return {"enabled": False, "interrupted": [], "plan": {}}
|
|
22
22
|
rec = getattr(os_, "recovery", None)
|
|
@@ -34,10 +34,11 @@ async def recovery_status():
|
|
|
34
34
|
"channel": t.channel,
|
|
35
35
|
"status": t.status,
|
|
36
36
|
"steps_count": len(steps),
|
|
37
|
-
"last_steps": steps[-10:],
|
|
37
|
+
"last_steps": steps[-10:],
|
|
38
38
|
"resume_hint": {"endpoint": "/api/task",
|
|
39
39
|
"context": {"_resume_task_id": t.task_id}},
|
|
40
40
|
})
|
|
41
41
|
plan = getattr(os_, "recovery_plan", None)
|
|
42
|
+
as_dict = getattr(plan, "to_dict", None)
|
|
42
43
|
return {"enabled": rec is not None, "interrupted": out,
|
|
43
|
-
"plan":
|
|
44
|
+
"plan": as_dict() if callable(as_dict) else {}}
|
|
@@ -23,12 +23,8 @@ _SR = SpacedRepetition()
|
|
|
23
23
|
|
|
24
24
|
_CARD_TYPES = {"Concept", "Fact", "Claim", "Entity"}
|
|
25
25
|
|
|
26
|
-
# 扫描上限(每种卡片类型)。之前统一 query_all(4000),一是把 Evidence 这种绝对不会
|
|
27
|
-
# 成卡的原文出处也一起拉出来(大文档导入后它是绝大多数行),二是 4000 之后的部分
|
|
28
|
-
# 会被静默截断 → 复习统计少算。这里只查四种卡片类型,上限给到实际用不满的量级。
|
|
29
26
|
_SCAN_LIMIT = 50000
|
|
30
27
|
|
|
31
|
-
# ReviewState.to_dict() 会额外塞 mastery / is_due,读回来时必须剔掉才能重建
|
|
32
28
|
_STATE_FIELDS = ("repetitions", "interval", "ease_factor", "due_date",
|
|
33
29
|
"last_review", "total_reviews", "lapses")
|
|
34
30
|
|
|
@@ -36,7 +32,7 @@ _STATE_FIELDS = ("repetitions", "interval", "ease_factor", "due_date",
|
|
|
36
32
|
def _store():
|
|
37
33
|
"""惰性取 HKC 的 graph_store,None 表示知识库没装配好(降级成空队列)。"""
|
|
38
34
|
try:
|
|
39
|
-
hkc = _api.
|
|
35
|
+
hkc = _api.os_instance().hkc
|
|
40
36
|
if hkc is None or not hkc.available:
|
|
41
37
|
return None
|
|
42
38
|
return hkc.container.graph_store
|
|
@@ -73,7 +69,7 @@ def _hours_left(state) -> float:
|
|
|
73
69
|
|
|
74
70
|
def _load7(learned) -> dict:
|
|
75
71
|
"""未来 7 天(0..6)的复习负荷,口径与队列一致。"""
|
|
76
|
-
by_day = {}
|
|
72
|
+
by_day: dict[str, int] = {}
|
|
77
73
|
for s in learned:
|
|
78
74
|
d = 0 if s.is_due else _days_left(s)
|
|
79
75
|
if d <= 6:
|
|
@@ -251,11 +247,6 @@ async def review_grade(body: GradeIn):
|
|
|
251
247
|
if ku is None or _status_of(ku) != "active":
|
|
252
248
|
raise HTTPException(404, f"没有这条知识:{body.ku_id}")
|
|
253
249
|
|
|
254
|
-
# 关键:只更新 extra 这一列,而且「读 extra → SM-2 推演 → 写回」整段在
|
|
255
|
-
# graph_store 的锁里完成。走 put() 是整行 UPSERT,会把整行其它列按写入前的
|
|
256
|
-
# 快照回写 —— 知识星球正在摄入时,KEE 刚合并进去的 sources / confidence /
|
|
257
|
-
# supports 会被这次评分悄悄冲掉(反向亦然:复习进度被 KEE 写回覆盖)。
|
|
258
|
-
# 锁内做还有个好处:连点两次评分不会两次都基于同一份旧状态推演。
|
|
259
250
|
graded: list[ReviewState] = []
|
|
260
251
|
|
|
261
252
|
def _apply(extra: dict) -> dict:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""认知搜索(HSR)端点:内部记忆 + 内部知识 + 联网,统一证据融合。
|
|
2
2
|
|
|
3
|
-
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.
|
|
3
|
+
从 foxclaw/api.py 原样搬移(纯搬移,零行为变更)。共享 OS 实例经 `_api.current_os()`
|
|
4
4
|
晚绑定读取;路由用 `@app` 装饰器在 import 时注册。
|
|
5
5
|
"""
|
|
6
6
|
|
|
@@ -13,15 +13,11 @@ import foxclaw.api as _api
|
|
|
13
13
|
from .. import app
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
# ── 认知搜索(HSR):内部记忆 + 内部知识 + 联网,统一证据融合 ─────────
|
|
17
|
-
# 一次 search() 统一"查 HMR 记忆 / 查 HKC 知识 / 联网(百度)",走 planner→retriever→
|
|
18
|
-
# fusion→strategy 迭代循环(有上限),只产出证据图,不写任何持久化(边界:不代 Agent 决策)。
|
|
19
|
-
|
|
20
16
|
class SearchQueryPayload(BaseModel):
|
|
21
17
|
query: str
|
|
22
18
|
agent_id: str = "supervisor"
|
|
23
19
|
context: dict = {}
|
|
24
|
-
internal_only: bool = False
|
|
20
|
+
internal_only: bool = False
|
|
25
21
|
|
|
26
22
|
|
|
27
23
|
@app.post("/api/search/query")
|
|
@@ -29,7 +25,7 @@ async def search_query(p: SearchQueryPayload):
|
|
|
29
25
|
"""认知搜索(面向 AI/Agent 的证据管线):返回证据图 + 置信度 + 迭代轨迹。
|
|
30
26
|
internal_only=True 时只查内部三源(记忆/知识/Agent),不联网。搜索内核惰性构造。"""
|
|
31
27
|
try:
|
|
32
|
-
rt = _api.
|
|
28
|
+
rt = _api.os_instance().search_runtime
|
|
33
29
|
result = await asyncio.to_thread(
|
|
34
30
|
rt.search, p.query, p.agent_id, p.context, p.internal_only)
|
|
35
31
|
return result.to_dict()
|