@hupan56/wlkj 3.0.0 → 3.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/bin/cli.js +1435 -1373
- package/package.json +1 -1
- package/templates/qoder/commands/optional/wl-insight.md +6 -4
- package/templates/qoder/commands/optional/wl-report.md +20 -39
- package/templates/qoder/commands/optional/wl-status.md +4 -4
- package/templates/qoder/commands/wl-code.md +2 -2
- package/templates/qoder/commands/wl-design.md +5 -5
- package/templates/qoder/commands/wl-init.md +3 -3
- package/templates/qoder/commands/wl-prd.md +26 -13
- package/templates/qoder/commands/wl-req.md +3 -3
- package/templates/qoder/commands/wl-search.md +78 -47
- package/templates/qoder/commands/wl-task.md +343 -40
- package/templates/qoder/commands/wl-test.md +10 -10
- package/templates/qoder/config.yaml +35 -13
- package/templates/qoder/hooks/post-tool-use.py +181 -0
- package/templates/qoder/hooks/session-start.py +82 -1
- package/templates/qoder/hooks/stop-eval.py +207 -0
- package/templates/qoder/hooks/user-prompt-submit.py +139 -0
- package/templates/qoder/rules/wl-pipeline.md +70 -8
- package/templates/qoder/scripts/README.md +78 -115
- package/templates/qoder/scripts/capability/__init__.py +26 -0
- package/templates/qoder/scripts/capability/__main__.py +72 -0
- package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
- package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
- package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
- package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
- package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
- package/templates/qoder/scripts/capability/caps/context.py +36 -0
- package/templates/qoder/scripts/capability/caps/cron.py +50 -0
- package/templates/qoder/scripts/capability/caps/identity.py +43 -0
- package/templates/qoder/scripts/capability/caps/memory.py +71 -0
- package/templates/qoder/scripts/capability/caps/notify.py +41 -0
- package/templates/qoder/scripts/capability/caps/present.py +48 -0
- package/templates/qoder/scripts/capability/caps/repo.py +31 -0
- package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
- package/templates/qoder/scripts/capability/chain.py +92 -0
- package/templates/qoder/scripts/capability/memory_chain.py +41 -0
- package/templates/qoder/scripts/capability/registry.py +246 -0
- package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
- package/templates/qoder/scripts/capability/smoke_test.py +211 -0
- package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
- package/templates/qoder/scripts/{report → deployment/setup}/__init__.py +11 -11
- package/templates/qoder/scripts/{setup → deployment/setup}/carriers.py +33 -26
- package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
- package/templates/qoder/scripts/{setup → deployment/setup}/init_doctor.py +40 -16
- package/templates/qoder/scripts/{setup → deployment/setup}/install_qoderwork.py +20 -12
- package/templates/qoder/scripts/{setup → deployment/setup}/platform_doctor.py +8 -2
- package/templates/qoder/scripts/{setup → deployment/setup}/repo_root.py +113 -112
- package/templates/qoder/scripts/{setup → deployment/setup}/setup.py +31 -11
- package/templates/qoder/scripts/{setup → deployment/setup}/setup_lanhu.py +13 -7
- package/templates/qoder/scripts/domain/__init__.py +0 -0
- package/templates/qoder/scripts/domain/design/__init__.py +0 -0
- package/templates/qoder/scripts/{report → domain/design}/fill_prototype.py +12 -6
- package/templates/qoder/scripts/{report → domain/design}/gen_design_doc.py +8 -2
- package/templates/qoder/scripts/{setup → domain/kg}/__init__.py +11 -11
- package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_entity_registry.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_relations.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_style_index.py +13 -7
- package/templates/qoder/scripts/{kg → domain/kg/build}/build_workflows.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build.py +76 -9
- package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build_db.py +7 -2
- package/templates/qoder/scripts/{kg → domain/kg/build}/kg_incremental.py +11 -6
- package/templates/qoder/scripts/{report → domain/kg/build}/learn_aggregate.py +9 -4
- package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
- package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
- package/templates/qoder/scripts/{common → domain/kg/extract}/test_extract.py +115 -115
- package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
- package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
- package/templates/qoder/scripts/{kg → domain/kg/graph}/kg_link_db.py +7 -2
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
- package/templates/qoder/scripts/{kg → domain/kg}/kg.py +263 -30
- package/templates/qoder/scripts/{common → domain/kg}/kg_capabilities.py +182 -182
- package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
- package/templates/qoder/scripts/{kg → domain/kg/search}/context_pack.py +30 -9
- package/templates/qoder/scripts/{kg → domain/kg/search}/enrich_prompt.py +10 -4
- package/templates/qoder/scripts/{kg → domain/kg/search}/prefetch.py +33 -8
- package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
- package/templates/qoder/scripts/{kg → domain/kg/search}/search_index.py +143 -77
- package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
- package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
- package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
- package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
- package/templates/qoder/scripts/{kg → domain/kg/storage}/kg_duckdb.py +38 -14
- package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
- package/templates/qoder/scripts/{report → domain/learning}/learn.py +8 -3
- package/templates/qoder/scripts/{mcp → domain/report}/__init__.py +11 -11
- package/templates/qoder/scripts/{report → domain/report}/add_session.py +9 -3
- package/templates/qoder/scripts/{report → domain/report}/export.py +10 -5
- package/templates/qoder/scripts/{report → domain/report}/report.py +9 -4
- package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
- package/templates/qoder/scripts/{report → domain/report}/role.py +10 -4
- package/templates/qoder/scripts/{report → domain/report}/status.py +112 -9
- package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
- package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
- package/templates/qoder/scripts/{report → domain/requirement}/archive_prd.py +8 -2
- package/templates/qoder/scripts/{report → domain/requirement}/req.py +228 -222
- package/templates/qoder/scripts/{kg → domain/task}/__init__.py +11 -11
- package/templates/qoder/scripts/{task → domain/task}/git_sync.py +40 -21
- package/templates/qoder/scripts/{task → domain/task}/syncgate.py +13 -8
- package/templates/qoder/scripts/{task → domain/task}/task.py +13 -5
- package/templates/qoder/scripts/{task → domain/task}/task_lifecycle.py +606 -596
- package/templates/qoder/scripts/{task → domain/task}/task_query.py +162 -161
- package/templates/qoder/scripts/{task → domain/task}/task_relations.py +425 -424
- package/templates/qoder/scripts/{task → domain/task}/team_sync.py +9 -4
- package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
- package/templates/qoder/scripts/foundation/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation}/bootstrap.py +10 -10
- package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/core}/cmd_registry.py +1 -1
- package/templates/qoder/scripts/{common → foundation/core}/config.py +1 -1
- package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -586
- package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/data}/contract.py +2 -2
- package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
- package/templates/qoder/scripts/{common → foundation/data}/result.py +1 -1
- package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -404
- package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/identity}/check_publish.py +103 -98
- package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -239
- package/templates/qoder/scripts/{common → foundation/identity}/guard.py +2 -2
- package/templates/qoder/scripts/{common → foundation/identity}/identity.py +14 -10
- package/templates/qoder/scripts/{common → foundation/identity}/roles.py +1 -1
- package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
- package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +2 -2
- package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
- package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
- package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
- package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
- package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
- package/templates/qoder/scripts/{common → foundation/protocol}/mcp_base.py +1 -1
- package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
- package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
- package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
- package/templates/qoder/scripts/orchestration/__init__.py +0 -0
- package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
- package/templates/qoder/scripts/protocol/__init__.py +0 -0
- package/templates/qoder/scripts/protocol/browser/README.md +207 -0
- package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
- package/templates/qoder/scripts/protocol/browser/config.env +9 -0
- package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
- package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
- package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
- package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
- package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
- package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
- package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
- package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
- package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
- package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
- package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
- package/templates/qoder/scripts/{mcp → protocol/mcp}/lanhu_stdio_wrapper.py +11 -5
- package/templates/qoder/scripts/{mcp → protocol/mcp}/mcp_doctor.py +47 -14
- package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
- package/templates/qoder/scripts/{mcp → protocol/mcp}/mysql_mcp_server.py +181 -67
- package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
- package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
- package/templates/qoder/scripts/protocol/transports/base.py +87 -0
- package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
- package/templates/qoder/scripts/protocol/transports/http.py +141 -0
- package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
- package/templates/qoder/scripts/run_weekly_update.bat +27 -18
- package/templates/qoder/scripts/run_weekly_update.sh +22 -13
- package/templates/qoder/scripts/validation/__init__.py +0 -0
- package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
- package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
- package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
- package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
- package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
- package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
- package/templates/qoder/scripts/{report → validation/metrics}/eval_prd.py +17 -10
- package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
- package/templates/qoder/scripts/validation/test/__init__.py +11 -0
- package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
- package/templates/qoder/scripts/{test → validation/test}/autotest.py +64 -4
- package/templates/qoder/scripts/{common → validation/test}/autotest_auth.py +1 -1
- package/templates/qoder/scripts/{test → validation/test}/autotest_batch.py +16 -3
- package/templates/qoder/scripts/{test → validation/test}/autotest_data.py +17 -12
- package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
- package/templates/qoder/scripts/{test → validation/test}/autotest_run.py +17 -3
- package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
- package/templates/qoder/scripts/{test → validation/test}/benchmark.py +12 -6
- package/templates/qoder/scripts/{test → validation/test}/kg_auto_login.py +8 -2
- package/templates/qoder/scripts/{test → validation/test}/kg_test_runner.py +7 -2
- package/templates/qoder/scripts/{test → validation/test}/page_probe.py +8 -3
- package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
- package/templates/qoder/settings.json +30 -0
- package/templates/qoder/skills/design-import/SKILL.md +22 -13
- package/templates/qoder/skills/design-review/SKILL.md +19 -10
- package/templates/qoder/skills/prd-generator/SKILL.md +22 -18
- package/templates/qoder/skills/prd-review/SKILL.md +6 -4
- package/templates/qoder/skills/prototype-generator/SKILL.md +63 -14
- package/templates/qoder/skills/spec-coder/SKILL.md +22 -13
- package/templates/qoder/skills/spec-generator/SKILL.md +22 -10
- package/templates/qoder/skills/test-generator/SKILL.md +7 -5
- package/templates/qoder/skills/wl-code/SKILL.md +23 -11
- package/templates/qoder/skills/wl-commit/SKILL.md +9 -8
- package/templates/qoder/skills/wl-design/SKILL.md +5 -3
- package/templates/qoder/skills/wl-init/SKILL.md +6 -6
- package/templates/qoder/skills/wl-insight/SKILL.md +23 -10
- package/templates/qoder/skills/wl-prd-full/SKILL.md +57 -6
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +3 -3
- package/templates/qoder/skills/wl-prd-review/SKILL.md +16 -4
- package/templates/qoder/skills/wl-report/SKILL.md +25 -23
- package/templates/qoder/skills/wl-search/SKILL.md +149 -78
- package/templates/qoder/skills/wl-spec/SKILL.md +7 -5
- package/templates/qoder/skills/wl-status/SKILL.md +48 -16
- package/templates/qoder/skills/wl-task/SKILL.md +92 -10
- package/templates/qoder/skills/wl-test/SKILL.md +75 -29
- package/templates/qoder/templates/prd-full-template.md +7 -0
- package/templates/root/AGENTS.md +275 -236
- package/templates/root/requirements.txt +3 -0
- package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
- package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
- package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +0 -23
- package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +0 -23
- package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +0 -1
- package/templates/.qoder/learning/eval-history.jsonl +0 -28
- package/templates/data/index/wiki-index.json +0 -8
- package/templates/qoder/nul +0 -4
- package/templates/qoder/scripts/kg/extract.py +0 -398
- package/templates/qoder/scripts/kg/kg_semantic.py +0 -156
- package/templates/qoder/scripts/mcp/kg_mcp_server.py +0 -606
- package/templates/qoder/scripts/mcp/mcp_launcher.py +0 -442
- package/templates/qoder/scripts/mcp/zentao_mcp_server.py +0 -384
- package/templates/qoder/scripts/task/__init__.py +0 -11
- package/templates/qoder/scripts/test/__init__.py +0 -11
- package/templates/qoder/scripts/wlkj.py +0 -116
- /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
- /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
- /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
- /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
- /package/templates/qoder/scripts/{test → validation/test}/secure-ls.js +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
nexus.transports —— 能力传输策略层。
|
|
4
|
+
|
|
5
|
+
设计模式: Strategy + Factory
|
|
6
|
+
|
|
7
|
+
三态对应部署形态:
|
|
8
|
+
- stdio: 本地 spawn MCP server(现状默认)
|
|
9
|
+
- cli: 非 MCP 宿主兜底(直接跑脚本)— P2
|
|
10
|
+
- http: 远程服务器(未来)— P3
|
|
11
|
+
|
|
12
|
+
工作流只通过 McpCap 调用,McpCap 经 McpRegistry 路由到具体 Transport。
|
|
13
|
+
配置驱动 transport 选择 —— 改一行配置,本地→远程切换,工作流零改动。
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
from .base import Transport, ToolResult, NoOpTransport
|
|
20
|
+
from .stdio import StdioTransport
|
|
21
|
+
from .http import RemoteHttpTransport
|
|
22
|
+
from .cli import CliTransport
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_transport(server_name: str, kind: str = "stdio",
|
|
26
|
+
repo_root=None, **kwargs) -> Transport:
|
|
27
|
+
"""传输策略工厂。
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
server_name: MCP server 名(诊断 + launcher 参数)。
|
|
31
|
+
kind: 传输方式 —— "stdio" | "cli" | "http"。
|
|
32
|
+
repo_root: 仓库根(stdio 用,定位 mcp_launcher)。
|
|
33
|
+
**kwargs: 各 transport 特定参数(http 的 url/token 等)。
|
|
34
|
+
Returns:
|
|
35
|
+
Transport 实例;未知 kind 返回 NoOpTransport(兜底不抛错)。
|
|
36
|
+
"""
|
|
37
|
+
if kind == "stdio":
|
|
38
|
+
return StdioTransport(server_name, repo_root=repo_root)
|
|
39
|
+
if kind == "http":
|
|
40
|
+
return RemoteHttpTransport(
|
|
41
|
+
url=kwargs.get("url", ""),
|
|
42
|
+
token=kwargs.get("token"),
|
|
43
|
+
server_name=server_name,
|
|
44
|
+
)
|
|
45
|
+
if kind == "cli":
|
|
46
|
+
script = kwargs.get("script")
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
return CliTransport(script=Path(script) if script else Path(), **{k: v for k, v in kwargs.items() if k != "script"})
|
|
49
|
+
return NoOpTransport()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"Transport", "ToolResult", "NoOpTransport",
|
|
54
|
+
"StdioTransport", "RemoteHttpTransport", "CliTransport",
|
|
55
|
+
"get_transport",
|
|
56
|
+
]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
transports.base —— 能力传输策略抽象。
|
|
4
|
+
|
|
5
|
+
设计模式: Strategy(策略模式)
|
|
6
|
+
|
|
7
|
+
一个能力(如知识图谱)的"传输方式"是可替换策略:
|
|
8
|
+
- StdioTransport (现状: 本地 spawn MCP server, stdin/stdout 传 JSON-RPC)
|
|
9
|
+
- CliTransport (兜底: 直接跑 kg.py 等脚本, 解析 stdout) — P2
|
|
10
|
+
- RemoteHttpTransport (未来: HTTP POST 到服务器) — P3
|
|
11
|
+
|
|
12
|
+
工作流面向"能力"编程 (cap.mcp.call("table_impact")),不感知传输细节。
|
|
13
|
+
配置驱动切换 transport —— 知识图谱从本地 stdio 迁到服务器 http,
|
|
14
|
+
只需改 McpRegistry 配置 + 填 url,工作流代码零改动。
|
|
15
|
+
|
|
16
|
+
Transport 与具体 MCP server 解耦:Transport 是"一种传输方式",
|
|
17
|
+
McpRegistry 负责"哪个 tool 走哪个 server 的哪个 transport"。
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from typing import Any, Protocol, runtime_checkable
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class ToolResult:
|
|
27
|
+
"""工具调用结果(所有 Transport 统一返回类型)。
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
text: 工具返回的文本内容(MCP 的 content[0].text,或 CLI 的 stdout)。
|
|
31
|
+
is_error: 是否为错误结果(MCP 的 isError,或异常捕获)。
|
|
32
|
+
raw: 原始响应(诊断用,调用方一般不直接用)。
|
|
33
|
+
"""
|
|
34
|
+
text: str = ""
|
|
35
|
+
is_error: bool = False
|
|
36
|
+
raw: Any = field(default=None)
|
|
37
|
+
|
|
38
|
+
def __str__(self) -> str:
|
|
39
|
+
return self.text
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@runtime_checkable
|
|
43
|
+
class Transport(Protocol):
|
|
44
|
+
"""能力传输策略契约。
|
|
45
|
+
|
|
46
|
+
所有 transport(stdio/http/cli)必须实现此接口。
|
|
47
|
+
McpCap 只通过此接口与底层通信,不感知传输细节。
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
available: bool
|
|
51
|
+
|
|
52
|
+
def list_tools(self) -> list[dict]:
|
|
53
|
+
"""枚举此 transport 后端提供的工具(tools/list 语义)。"""
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
def has_tool(self, name: str) -> bool:
|
|
57
|
+
"""某工具是否可用。"""
|
|
58
|
+
...
|
|
59
|
+
|
|
60
|
+
def call_tool(self, name: str, args: dict | None = None) -> ToolResult:
|
|
61
|
+
"""调用工具,返回 ToolResult。失败时 is_error=True 但不抛异常(让调用方决定降级)。"""
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
def close(self) -> None:
|
|
65
|
+
"""清理连接/子进程。"""
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class NoOpTransport:
|
|
70
|
+
"""Transport 空实现 —— Null Object。
|
|
71
|
+
|
|
72
|
+
无可用 transport 时兜底:available=False,方法静默返回空,绝不抛错。
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
available = False
|
|
76
|
+
|
|
77
|
+
def list_tools(self) -> list[dict]:
|
|
78
|
+
return []
|
|
79
|
+
|
|
80
|
+
def has_tool(self, name: str) -> bool:
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
def call_tool(self, name: str, args: dict | None = None) -> ToolResult:
|
|
84
|
+
return ToolResult(text="", is_error=True)
|
|
85
|
+
|
|
86
|
+
def close(self) -> None:
|
|
87
|
+
pass
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
transports.cli —— CLI 传输策略(兜底形态)。
|
|
4
|
+
|
|
5
|
+
设计模式: Strategy(与 StdioTransport 同接口,可替换)
|
|
6
|
+
|
|
7
|
+
定位:非 QoderWork 宿主(ZCode/Codex/纯 CLI)没有 MCP 客户端时,
|
|
8
|
+
通过直接跑 kg.py 等脚本子进程获得等价能力。
|
|
9
|
+
复用 kg_capabilities.py(kg.py 与 kg_mcp_server.py 的共享单一真源),
|
|
10
|
+
保证输出与 MCP 一致。
|
|
11
|
+
|
|
12
|
+
机制:
|
|
13
|
+
command_map 映射 tool_name → (subcommand, args_adapter):
|
|
14
|
+
- subcommand: CLI 子命令名(如 "search_code")
|
|
15
|
+
- args_adapter: callable(dict) -> list[str] 参数翻译函数(可选)
|
|
16
|
+
CliTransport 用 subprocess.run 跑脚本 + 子命令,解析 stdout JSON。
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Callable, Optional
|
|
25
|
+
|
|
26
|
+
from .base import ToolResult
|
|
27
|
+
|
|
28
|
+
# 默认超时(秒)
|
|
29
|
+
_TIMEOUT = 60
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CliTransport:
|
|
33
|
+
"""CLI 传输策略(子进程跑脚本,解析 stdout)。
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
script: 脚本路径(如 .qoder/scripts/domain/kg/kg.py)。
|
|
37
|
+
command_map: tool 名 → (subcommand, args_adapter) 映射。
|
|
38
|
+
subcommand: CLI 子命令名。
|
|
39
|
+
args_adapter: callable(dict) -> list[str](可选,None 表示无额外参数)。
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, script: Path, command_map: Optional[dict] = None):
|
|
43
|
+
self._script = script
|
|
44
|
+
self._command_map = command_map or {}
|
|
45
|
+
self._tools_cache: Optional[list[dict]] = None
|
|
46
|
+
self._tool_index: Optional[set[str]] = None
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def available(self) -> bool:
|
|
50
|
+
return self._script.is_file()
|
|
51
|
+
|
|
52
|
+
def list_tools(self) -> list[dict]:
|
|
53
|
+
# CLI 无统一 tools/list;按 command_map 的 key 作为可用工具清单
|
|
54
|
+
if self._tools_cache is None:
|
|
55
|
+
self._tools_cache = [{"name": k} for k in self._command_map]
|
|
56
|
+
self._tool_index = set(self._command_map.keys())
|
|
57
|
+
return self._tools_cache
|
|
58
|
+
|
|
59
|
+
def has_tool(self, name: str) -> bool:
|
|
60
|
+
if self._tool_index is None:
|
|
61
|
+
self.list_tools()
|
|
62
|
+
return name in (self._tool_index or set())
|
|
63
|
+
|
|
64
|
+
def call_tool(self, name: str, args: dict | None = None) -> ToolResult:
|
|
65
|
+
if name not in self._command_map:
|
|
66
|
+
return ToolResult(text="CLI 未映射工具: %s" % name, is_error=True)
|
|
67
|
+
|
|
68
|
+
entry = self._command_map[name]
|
|
69
|
+
if isinstance(entry, tuple):
|
|
70
|
+
subcommand = entry[0]
|
|
71
|
+
adapter = entry[1] if len(entry) > 1 else None
|
|
72
|
+
else:
|
|
73
|
+
subcommand = entry
|
|
74
|
+
adapter = None
|
|
75
|
+
|
|
76
|
+
# 构建 CLI 命令: python script.py subcommand [adapted_args...]
|
|
77
|
+
cmd = [sys.executable, str(self._script), subcommand]
|
|
78
|
+
if adapter and args:
|
|
79
|
+
try:
|
|
80
|
+
cmd.extend(adapter(args))
|
|
81
|
+
except Exception as e:
|
|
82
|
+
return ToolResult(text="参数翻译失败: %s" % str(e)[:120], is_error=True)
|
|
83
|
+
elif args:
|
|
84
|
+
# 默认: key=value 格式
|
|
85
|
+
for k, v in args.items():
|
|
86
|
+
if v is not None:
|
|
87
|
+
cmd.extend(["--%s" % k, str(v)])
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
proc = subprocess.run(
|
|
91
|
+
cmd,
|
|
92
|
+
capture_output=True,
|
|
93
|
+
text=True,
|
|
94
|
+
timeout=_TIMEOUT,
|
|
95
|
+
encoding="utf-8",
|
|
96
|
+
errors="replace",
|
|
97
|
+
)
|
|
98
|
+
except subprocess.TimeoutExpired:
|
|
99
|
+
return ToolResult(text="CLI 超时 (%ds): %s" % (_TIMEOUT, name), is_error=True)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
return ToolResult(text="CLI 执行失败: %s" % str(e)[:120], is_error=True)
|
|
102
|
+
|
|
103
|
+
# 解析 stdout:优先 JSON,降级纯文本
|
|
104
|
+
stdout = (proc.stdout or "").strip()
|
|
105
|
+
stderr = (proc.stderr or "").strip()
|
|
106
|
+
|
|
107
|
+
if proc.returncode != 0 and not stdout:
|
|
108
|
+
return ToolResult(
|
|
109
|
+
text="CLI 错误 (exit=%d): %s" % (proc.returncode, stderr[:200]),
|
|
110
|
+
is_error=True,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# 尝试 JSON 解析
|
|
114
|
+
if stdout:
|
|
115
|
+
try:
|
|
116
|
+
data = json.loads(stdout)
|
|
117
|
+
# MCP server 输出格式: {"content": [{"text": "..."}]}
|
|
118
|
+
if isinstance(data, dict) and "content" in data:
|
|
119
|
+
text = data["content"][0].get("text", "") if data["content"] else ""
|
|
120
|
+
return ToolResult(text=text, is_error=data.get("isError", False))
|
|
121
|
+
# 纯 JSON 字符串或列表
|
|
122
|
+
if isinstance(data, str):
|
|
123
|
+
return ToolResult(text=data)
|
|
124
|
+
return ToolResult(text=json.dumps(data, ensure_ascii=False))
|
|
125
|
+
except (json.JSONDecodeError, IndexError, KeyError):
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
# 降级: 纯文本输出
|
|
129
|
+
return ToolResult(text=stdout or stderr or "(无输出)")
|
|
130
|
+
|
|
131
|
+
def close(self) -> None:
|
|
132
|
+
pass # CLI 无状态,每次调用 spawn 子进程
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
transports.http —— 远程 HTTP MCP 传输策略。
|
|
4
|
+
|
|
5
|
+
设计模式: Strategy(与 StdioTransport 同接口,可替换)
|
|
6
|
+
|
|
7
|
+
定位:满足现在的本地 stdio,留扩展给未来的服务器化。
|
|
8
|
+
知识图谱/禅道等能力未来可上服务器,以 HTTP MCP (Streamable HTTP) 暴露。
|
|
9
|
+
届时只需在 McpRegistry 配置把 server 的 transport 从 "stdio" 改成 "http" + 填 url,
|
|
10
|
+
工作流代码 (cap.mcp.call) 零改动。
|
|
11
|
+
|
|
12
|
+
机制:
|
|
13
|
+
POST {url}/mcp 发 JSON-RPC 2.0 请求,解析 JSON 响应。
|
|
14
|
+
支持 Bearer token 认证。
|
|
15
|
+
超时可配置,失败自动分类(网络/超时/协议错误)。
|
|
16
|
+
|
|
17
|
+
依赖:仅 stdlib(urllib),无第三方包。
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import urllib.error
|
|
23
|
+
import urllib.request
|
|
24
|
+
from typing import Optional
|
|
25
|
+
|
|
26
|
+
from .base import ToolResult
|
|
27
|
+
|
|
28
|
+
# 默认超时(秒)
|
|
29
|
+
_TIMEOUT = 30
|
|
30
|
+
# MCP 协议版本
|
|
31
|
+
_PROTOCOL_VERSION = "2024-11-05"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RemoteHttpTransport:
|
|
35
|
+
"""远程 HTTP MCP 传输策略。
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
url: 远程 MCP endpoint(如 https://kg.corp/mcp)。
|
|
39
|
+
token: 认证 token(Bearer)。可选。
|
|
40
|
+
server_name: server 名(诊断用)。
|
|
41
|
+
timeout: 请求超时秒数。
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, url: str, token: Optional[str] = None,
|
|
45
|
+
server_name: str = "", timeout: int = _TIMEOUT):
|
|
46
|
+
self._url = (url or "").rstrip("/")
|
|
47
|
+
self._token = token
|
|
48
|
+
self._server = server_name
|
|
49
|
+
self._timeout = timeout
|
|
50
|
+
self._tools_cache: Optional[list[dict]] = None
|
|
51
|
+
self._tool_index: Optional[set[str]] = None
|
|
52
|
+
self._req_id = 0
|
|
53
|
+
self._last_error: Optional[str] = None
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def available(self) -> bool:
|
|
57
|
+
"""有 url 即视为可尝试(真实可达性在首次调用时检验)。"""
|
|
58
|
+
return bool(self._url)
|
|
59
|
+
|
|
60
|
+
def _next_id(self) -> int:
|
|
61
|
+
self._req_id += 1
|
|
62
|
+
return self._req_id
|
|
63
|
+
|
|
64
|
+
def _post_jsonrpc(self, method: str, params: dict | None = None) -> dict:
|
|
65
|
+
"""发 JSON-RPC 请求,返回响应 dict。
|
|
66
|
+
|
|
67
|
+
失败抛 RuntimeError(调用方捕获转 ToolResult)。
|
|
68
|
+
"""
|
|
69
|
+
rid = self._next_id()
|
|
70
|
+
body: dict = {"jsonrpc": "2.0", "id": rid, "method": method}
|
|
71
|
+
if params is not None:
|
|
72
|
+
body["params"] = params
|
|
73
|
+
|
|
74
|
+
data = json.dumps(body, ensure_ascii=False).encode("utf-8")
|
|
75
|
+
endpoint = self._url + "/mcp"
|
|
76
|
+
|
|
77
|
+
req = urllib.request.Request(
|
|
78
|
+
endpoint,
|
|
79
|
+
data=data,
|
|
80
|
+
headers={"Content-Type": "application/json"},
|
|
81
|
+
method="POST",
|
|
82
|
+
)
|
|
83
|
+
if self._token:
|
|
84
|
+
req.add_header("Authorization", "Bearer %s" % self._token)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
with urllib.request.urlopen(req, timeout=self._timeout) as resp:
|
|
88
|
+
raw = resp.read().decode("utf-8", errors="replace")
|
|
89
|
+
except urllib.error.HTTPError as e:
|
|
90
|
+
raise RuntimeError("HTTP %d: %s" % (e.code, e.read().decode("utf-8", errors="replace")[:200]))
|
|
91
|
+
except urllib.error.URLError as e:
|
|
92
|
+
raise RuntimeError("连接失败: %s" % str(e.reason)[:120])
|
|
93
|
+
except TimeoutError:
|
|
94
|
+
raise RuntimeError("超时 (%ds)" % self._timeout)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
result = json.loads(raw)
|
|
98
|
+
except json.JSONDecodeError:
|
|
99
|
+
raise RuntimeError("非 JSON 响应: %s" % raw[:120])
|
|
100
|
+
|
|
101
|
+
if "error" in result:
|
|
102
|
+
msg = result["error"].get("message", "unknown")
|
|
103
|
+
raise RuntimeError("MCP error: %s" % str(msg)[:200])
|
|
104
|
+
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
def list_tools(self) -> list[dict]:
|
|
108
|
+
"""POST tools/list,解析工具清单。"""
|
|
109
|
+
if self._tools_cache is not None:
|
|
110
|
+
return self._tools_cache
|
|
111
|
+
try:
|
|
112
|
+
resp = self._post_jsonrpc("tools/list")
|
|
113
|
+
tools = resp.get("result", {}).get("tools", [])
|
|
114
|
+
self._tools_cache = tools
|
|
115
|
+
self._tool_index = {t.get("name", "") for t in tools}
|
|
116
|
+
except Exception as e:
|
|
117
|
+
self._last_error = str(e)[:120]
|
|
118
|
+
self._tools_cache = []
|
|
119
|
+
self._tool_index = set()
|
|
120
|
+
return self._tools_cache
|
|
121
|
+
|
|
122
|
+
def has_tool(self, name: str) -> bool:
|
|
123
|
+
if self._tool_index is None:
|
|
124
|
+
self.list_tools()
|
|
125
|
+
return name in (self._tool_index or set())
|
|
126
|
+
|
|
127
|
+
def call_tool(self, name: str, args: dict | None = None) -> ToolResult:
|
|
128
|
+
"""POST tools/call,解析 content[0].text。"""
|
|
129
|
+
try:
|
|
130
|
+
resp = self._post_jsonrpc("tools/call", {"name": name, "arguments": args or {}})
|
|
131
|
+
except Exception as e:
|
|
132
|
+
return ToolResult(text="HTTP transport 错误: %s" % str(e)[:120], is_error=True)
|
|
133
|
+
|
|
134
|
+
result = resp.get("result", {})
|
|
135
|
+
content = result.get("content", [])
|
|
136
|
+
text = content[0].get("text", "") if content else ""
|
|
137
|
+
return ToolResult(text=text, is_error=result.get("isError", False), raw=resp)
|
|
138
|
+
|
|
139
|
+
def close(self) -> None:
|
|
140
|
+
# HTTP 无长连接需关(urllib 无连接池)
|
|
141
|
+
pass
|