@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,271 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
adapters.qw —— QoderWork 原生能力 Adapter。
|
|
4
|
+
|
|
5
|
+
设计模式: Adapter(适配 QoderWork 原生工具到 Cap 契约)
|
|
6
|
+
|
|
7
|
+
实现 QoderWork 提供的 L2 宿主原生能力。其他宿主(ZCode/Codex)不适用,
|
|
8
|
+
未实现的 Cap 由 NoOp 兜底。
|
|
9
|
+
|
|
10
|
+
QoderWork 提供的相关工具(参考):
|
|
11
|
+
memory / memory_search / memory_get → QwMemoryCap(转发到 MCP 层)
|
|
12
|
+
qoder_cron → QwCronCap(文件 + 系统 crontab)
|
|
13
|
+
qw_mcp_list/get/call → 走 McpCap(L1)
|
|
14
|
+
delegate_to_im → QwNotifyCap(后续补)
|
|
15
|
+
qoder_show_widget / present_files → QwPresentCap(后续补)
|
|
16
|
+
|
|
17
|
+
本模块实现 QwMemoryCap + QwCronCap,其余后续补。
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def is_qoderwork() -> bool:
|
|
25
|
+
"""探测当前是否运行在 QoderWork 宿主内。
|
|
26
|
+
|
|
27
|
+
QoderWork 注入 QODER_SESSION_ID(会话)或 QODER_MCP_AVAILABLE(MCP 网关)。
|
|
28
|
+
"""
|
|
29
|
+
if os.environ.get("QODER_SESSION_ID"):
|
|
30
|
+
return True
|
|
31
|
+
return os.environ.get("QODER_MCP_AVAILABLE", "").lower() in ("1", "true", "yes")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class QwMemoryCap:
|
|
35
|
+
"""QoderWork memory 工具适配器。
|
|
36
|
+
|
|
37
|
+
设计模式: Adapter(QoderWork → MemoryCap 契约)
|
|
38
|
+
|
|
39
|
+
策略: 优先使用 QoderWork 原生 memory 工具,降级到 MCP MemoryProvider。
|
|
40
|
+
当前实现: 转发到 MCP 层的 McpMemoryProvider(接通 kg.duckdb)。
|
|
41
|
+
未来: QoderWork 原生 memory 工具可用时,优先用原生。
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self):
|
|
45
|
+
self._env = is_qoderwork()
|
|
46
|
+
self._mcp_provider = None
|
|
47
|
+
|
|
48
|
+
def _get_mcp_provider(self):
|
|
49
|
+
"""延迟获取 MCP MemoryProvider(避免循环导入)。"""
|
|
50
|
+
if self._mcp_provider is None:
|
|
51
|
+
try:
|
|
52
|
+
from .mcp import McpMemoryProvider
|
|
53
|
+
self._mcp_provider = McpMemoryProvider()
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
return self._mcp_provider
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def available(self) -> bool:
|
|
60
|
+
"""QoderWork 环境下可用(降级到 MCP 层)。"""
|
|
61
|
+
if self._env:
|
|
62
|
+
return True
|
|
63
|
+
# 非 QoderWork 环境,看 MCP provider 是否可用
|
|
64
|
+
provider = self._get_mcp_provider()
|
|
65
|
+
return bool(provider and provider.available)
|
|
66
|
+
|
|
67
|
+
def recall(self, query: str) -> list[dict]:
|
|
68
|
+
"""语义检索记忆:优先 QoderWork 原生,降级 MCP。"""
|
|
69
|
+
# TODO: QoderWork 原生 memory_search 工具可用时优先调用
|
|
70
|
+
provider = self._get_mcp_provider()
|
|
71
|
+
if provider and provider.available:
|
|
72
|
+
return provider.recall(query)
|
|
73
|
+
return []
|
|
74
|
+
|
|
75
|
+
def remember(self, key: str, content: str) -> None:
|
|
76
|
+
"""写入记忆:优先 QoderWork 原生,降级 MCP。"""
|
|
77
|
+
# TODO: QoderWork 原生 memory 工具可用时优先调用
|
|
78
|
+
provider = self._get_mcp_provider()
|
|
79
|
+
if provider and provider.available:
|
|
80
|
+
provider.remember(key, content)
|
|
81
|
+
|
|
82
|
+
def get_fact(self, key: str) -> str | None:
|
|
83
|
+
"""按 key 取事实:优先 QoderWork 原生,降级 MCP。"""
|
|
84
|
+
provider = self._get_mcp_provider()
|
|
85
|
+
if provider and provider.available:
|
|
86
|
+
return provider.get_fact(key)
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
def list_recent(self, limit: int = 10) -> list[dict]:
|
|
90
|
+
"""最近 N 条记忆:优先 QoderWork 原生,降级 MCP。"""
|
|
91
|
+
provider = self._get_mcp_provider()
|
|
92
|
+
if provider and provider.available:
|
|
93
|
+
return provider.list_recent(limit)
|
|
94
|
+
return []
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class QwCronCap:
|
|
98
|
+
"""QoderWork cron 工具适配器。
|
|
99
|
+
|
|
100
|
+
设计模式: Adapter(QoderWork → CronCap 契约)
|
|
101
|
+
|
|
102
|
+
当前实现: 使用 common.events 的 cron 注册文件(.qoder/data/cron/)。
|
|
103
|
+
未来: QoderWork 原生 qoder_cron 工具可用时优先用原生。
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
CRON_DIR = ".qoder" + os.sep + "data" + os.sep + "cron"
|
|
107
|
+
|
|
108
|
+
def __init__(self):
|
|
109
|
+
self._env = is_qoderwork()
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def available(self) -> bool:
|
|
113
|
+
"""文件系统可写即可用。"""
|
|
114
|
+
try:
|
|
115
|
+
from pathlib import Path
|
|
116
|
+
repo_root = Path(__file__).resolve().parent.parent.parent.parent
|
|
117
|
+
cron_dir = repo_root / self.CRON_DIR
|
|
118
|
+
cron_dir.mkdir(parents=True, exist_ok=True)
|
|
119
|
+
return cron_dir.is_dir()
|
|
120
|
+
except Exception:
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
def add(self, cron_expr: str, command: str, label: str = "") -> str:
|
|
124
|
+
"""添加定时任务,返回 job_id。"""
|
|
125
|
+
import json
|
|
126
|
+
import time
|
|
127
|
+
from pathlib import Path
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
repo_root = Path(__file__).resolve().parent.parent.parent.parent
|
|
131
|
+
cron_dir = repo_root / self.CRON_DIR
|
|
132
|
+
cron_dir.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
|
|
134
|
+
job_id = "%s-%s" % (label or "job", int(time.time()))
|
|
135
|
+
job = {
|
|
136
|
+
"id": job_id,
|
|
137
|
+
"cron": cron_expr,
|
|
138
|
+
"command": command,
|
|
139
|
+
"label": label,
|
|
140
|
+
"enabled": True,
|
|
141
|
+
}
|
|
142
|
+
job_file = cron_dir / ("%s.json" % job_id)
|
|
143
|
+
with open(job_file, "w", encoding="utf-8") as f:
|
|
144
|
+
json.dump(job, f, ensure_ascii=False, indent=2)
|
|
145
|
+
return job_id
|
|
146
|
+
except Exception as e:
|
|
147
|
+
return "error: %s" % str(e)[:80]
|
|
148
|
+
|
|
149
|
+
def list(self) -> list[dict]:
|
|
150
|
+
"""列出所有定时任务。"""
|
|
151
|
+
import json
|
|
152
|
+
from pathlib import Path
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
repo_root = Path(__file__).resolve().parent.parent.parent.parent
|
|
156
|
+
cron_dir = repo_root / self.CRON_DIR
|
|
157
|
+
if not cron_dir.is_dir():
|
|
158
|
+
return []
|
|
159
|
+
jobs = []
|
|
160
|
+
for f in cron_dir.glob("*.json"):
|
|
161
|
+
try:
|
|
162
|
+
with open(f, encoding="utf-8") as fh:
|
|
163
|
+
jobs.append(json.load(fh))
|
|
164
|
+
except Exception:
|
|
165
|
+
continue
|
|
166
|
+
return sorted(jobs, key=lambda j: j.get("id", ""))
|
|
167
|
+
except Exception:
|
|
168
|
+
return []
|
|
169
|
+
|
|
170
|
+
def remove(self, job_id: str) -> bool:
|
|
171
|
+
"""删除定时任务。"""
|
|
172
|
+
from pathlib import Path
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
repo_root = Path(__file__).resolve().parent.parent.parent.parent
|
|
176
|
+
job_file = repo_root / self.CRON_DIR / ("%s.json" % job_id)
|
|
177
|
+
if job_file.is_file():
|
|
178
|
+
job_file.unlink()
|
|
179
|
+
return True
|
|
180
|
+
except Exception:
|
|
181
|
+
pass
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class QwPresentCap:
|
|
186
|
+
"""QoderWork 富展示能力适配器。
|
|
187
|
+
|
|
188
|
+
设计模式: Adapter(QoderWork qoder_show_widget → PresentCap 契约)
|
|
189
|
+
|
|
190
|
+
把"渲染交互 HTML 到对话流"从宿主原生工具适配到 PresentCap.widget()。
|
|
191
|
+
降级链: QoderWork 原生 qoder_show_widget → 落盘 HTML 文件(纯 CLI 兜底)。
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
def __init__(self):
|
|
195
|
+
self._env = is_qoderwork()
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def available(self) -> bool:
|
|
199
|
+
"""QoderWork 环境且 qoder_show_widget 工具可达时可用。"""
|
|
200
|
+
if not self._env:
|
|
201
|
+
return False
|
|
202
|
+
# 探测宿主工具是否可达 (走 McpCap, 失败则降级)
|
|
203
|
+
try:
|
|
204
|
+
from .mcp import _get_registry
|
|
205
|
+
reg = _get_registry()
|
|
206
|
+
return reg.has_tool("qoder_show_widget")
|
|
207
|
+
except Exception:
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
def _try_host_widget(self, html: str) -> bool:
|
|
211
|
+
"""尝试调宿主 qoder_show_widget 把 HTML 嵌入对话流。成功返回 True。"""
|
|
212
|
+
try:
|
|
213
|
+
from .mcp import _get_registry
|
|
214
|
+
reg = _get_registry()
|
|
215
|
+
result = reg.call("qoder_show_widget", {"html": html})
|
|
216
|
+
return not result.is_error
|
|
217
|
+
except Exception:
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
def widget(self, html: str) -> bool:
|
|
221
|
+
"""展示交互 widget。优先嵌入对话流, 降级落盘 HTML 文件。"""
|
|
222
|
+
if self._try_host_widget(html):
|
|
223
|
+
return True
|
|
224
|
+
# 降级: 落盘到 journal (纯 CLI / 工具不可达时)
|
|
225
|
+
try:
|
|
226
|
+
from datetime import datetime
|
|
227
|
+
from pathlib import Path
|
|
228
|
+
repo_root = Path(__file__).resolve().parent.parent.parent.parent
|
|
229
|
+
# 找当前 dev
|
|
230
|
+
dev = "default"
|
|
231
|
+
dev_file = repo_root / ".qoder" / ".developer"
|
|
232
|
+
if dev_file.is_file():
|
|
233
|
+
for line in dev_file.read_text(encoding="utf-8").splitlines():
|
|
234
|
+
if "name=" in line:
|
|
235
|
+
dev = line.split("name=", 1)[1].strip().split()[0]
|
|
236
|
+
break
|
|
237
|
+
out_dir = repo_root / "workspace" / "members" / dev / "journal"
|
|
238
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
239
|
+
out = out_dir / ("dashboard-%s.html" % datetime.now().strftime("%Y%m%d-%H%M%S"))
|
|
240
|
+
out.write_text(html, encoding="utf-8")
|
|
241
|
+
print("[PresentCap] qoder_show_widget 不可达, 看板已落盘: %s" % out)
|
|
242
|
+
return False # 通知调用方: 未嵌入对话流(但已落盘)
|
|
243
|
+
except Exception:
|
|
244
|
+
return False
|
|
245
|
+
|
|
246
|
+
def table(self, rows: list, headers: list | None = None) -> str:
|
|
247
|
+
"""渲染 markdown 表格 (兜底, 所有宿主都能看)。"""
|
|
248
|
+
if not rows:
|
|
249
|
+
return ""
|
|
250
|
+
headers = headers or (list(rows[0].keys()) if rows and isinstance(rows[0], dict) else [])
|
|
251
|
+
lines = ["| " + " | ".join(str(h) for h in headers) + " |",
|
|
252
|
+
"| " + " | ".join("---" for _ in headers) + " |"]
|
|
253
|
+
for r in rows:
|
|
254
|
+
if isinstance(r, dict):
|
|
255
|
+
lines.append("| " + " | ".join(str(r.get(h, "")) for h in headers) + " |")
|
|
256
|
+
else:
|
|
257
|
+
lines.append("| " + " | ".join(str(x) for x in r) + " |")
|
|
258
|
+
return "\n".join(lines)
|
|
259
|
+
|
|
260
|
+
def files(self, paths: list) -> bool:
|
|
261
|
+
"""以文件卡片展示。降级: 打印路径列表。"""
|
|
262
|
+
try:
|
|
263
|
+
from .mcp import _get_registry
|
|
264
|
+
reg = _get_registry()
|
|
265
|
+
if reg.has_tool("present_files"):
|
|
266
|
+
result = reg.call("present_files", {"paths": paths})
|
|
267
|
+
return not result.is_error
|
|
268
|
+
except Exception:
|
|
269
|
+
pass
|
|
270
|
+
print("[PresentCap] present_files 不可达, 文件列表: " + ", ".join(str(p) for p in paths))
|
|
271
|
+
return False
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
nexus.caps —— L2 宿主原生能力契约层。
|
|
4
|
+
|
|
5
|
+
每个能力 = Protocol(契约)+ NoOp(默认空实现)。
|
|
6
|
+
新增能力(扩展场景 B)= 新建一个文件定义 Protocol + NoOp,在此导出,
|
|
7
|
+
再在 registry.Nexus 加属性、_resolve_xxx 加装配。
|
|
8
|
+
"""
|
|
9
|
+
from .memory import MemoryCap, NoOpMemoryCap
|
|
10
|
+
from .repo import RepoCap, NoOpRepoCap
|
|
11
|
+
from .identity import IdentityCap, NoOpIdentityCap
|
|
12
|
+
from .context import ContextCap, NoOpContextCap
|
|
13
|
+
from .cron import CronCap, NoOpCronCap
|
|
14
|
+
from .notify import NotifyCap, NoOpNotifyCap
|
|
15
|
+
from .present import PresentCap, NoOpPresentCap
|
|
16
|
+
from .sandbox import SandboxCap, NoOpSandboxCap
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"MemoryCap", "NoOpMemoryCap",
|
|
20
|
+
"RepoCap", "NoOpRepoCap",
|
|
21
|
+
"IdentityCap", "NoOpIdentityCap",
|
|
22
|
+
"ContextCap", "NoOpContextCap",
|
|
23
|
+
"CronCap", "NoOpCronCap",
|
|
24
|
+
"NotifyCap", "NoOpNotifyCap",
|
|
25
|
+
"PresentCap", "NoOpPresentCap",
|
|
26
|
+
"SandboxCap", "NoOpSandboxCap",
|
|
27
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
ContextCap —— 上下文/偏好能力契约。
|
|
4
|
+
|
|
5
|
+
核心能力(必有降级)。读取 USER 偏好、最近学到的经验等。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Protocol, runtime_checkable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class ContextCap(Protocol):
|
|
14
|
+
"""上下文/偏好能力。"""
|
|
15
|
+
|
|
16
|
+
available: bool
|
|
17
|
+
|
|
18
|
+
def preferences(self) -> dict:
|
|
19
|
+
"""USER 偏好字典。"""
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
def recent_learnings(self, limit: int = 5) -> list[dict]:
|
|
23
|
+
"""最近学到的经验/事实。"""
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class NoOpContextCap:
|
|
28
|
+
"""ContextCap 空实现。"""
|
|
29
|
+
|
|
30
|
+
available = False
|
|
31
|
+
|
|
32
|
+
def preferences(self) -> dict:
|
|
33
|
+
return {}
|
|
34
|
+
|
|
35
|
+
def recent_learnings(self, limit: int = 5) -> list[dict]:
|
|
36
|
+
return []
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
CronCap —— 定时任务能力契约。
|
|
4
|
+
|
|
5
|
+
增强能力(NoOp 兜底即可)。
|
|
6
|
+
工作流场景: 每周自动拉禅道任务排优先级、每日 git 同步等。
|
|
7
|
+
|
|
8
|
+
各宿主语义不同(场景 E):
|
|
9
|
+
- QoderWork: qoder_cron 工具
|
|
10
|
+
- CLI: 系统 crontab
|
|
11
|
+
- 未来: MCP cron server
|
|
12
|
+
通过 Provider Chain 统一,各 provider 自治。
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Protocol, runtime_checkable
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@runtime_checkable
|
|
20
|
+
class CronCap(Protocol):
|
|
21
|
+
"""定时任务能力。"""
|
|
22
|
+
|
|
23
|
+
available: bool
|
|
24
|
+
|
|
25
|
+
def add(self, name: str, schedule: str, prompt: str) -> str:
|
|
26
|
+
"""添加定时任务,返回 job_id(失败返回空串)。"""
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
def list(self) -> list[dict]:
|
|
30
|
+
"""列出所有定时任务。"""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
def remove(self, job_id: str) -> bool:
|
|
34
|
+
"""删除定时任务。"""
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class NoOpCronCap:
|
|
39
|
+
"""CronCap 空实现 —— 无定时能力时静默返回。"""
|
|
40
|
+
|
|
41
|
+
available = False
|
|
42
|
+
|
|
43
|
+
def add(self, name: str, schedule: str, prompt: str) -> str:
|
|
44
|
+
return ""
|
|
45
|
+
|
|
46
|
+
def list(self) -> list[dict]:
|
|
47
|
+
return []
|
|
48
|
+
|
|
49
|
+
def remove(self, job_id: str) -> bool:
|
|
50
|
+
return False
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
IdentityCap —— 操作者身份能力契约。
|
|
4
|
+
|
|
5
|
+
核心能力(必有降级)。工作流需要知道"谁在操作"(whoami / 角色 / 禅道账号)。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Protocol, runtime_checkable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class IdentityCap(Protocol):
|
|
14
|
+
"""操作者身份能力。"""
|
|
15
|
+
|
|
16
|
+
available: bool
|
|
17
|
+
|
|
18
|
+
def who(self) -> str | None:
|
|
19
|
+
"""developer 名(.developer 文件)。"""
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
def role(self) -> str | None:
|
|
23
|
+
"""角色: pm / dev / qa。"""
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
def zentao_account(self) -> str | None:
|
|
27
|
+
"""禅道账号映射(后续接入禅道时用)。"""
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class NoOpIdentityCap:
|
|
32
|
+
"""IdentityCap 空实现。仅占位 —— 核心能力总有文件降级。"""
|
|
33
|
+
|
|
34
|
+
available = False
|
|
35
|
+
|
|
36
|
+
def who(self) -> str | None:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
def role(self) -> str | None:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
def zentao_account(self) -> str | None:
|
|
43
|
+
return None
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
MemoryCap —— 跨会话持久记忆能力契约。
|
|
4
|
+
|
|
5
|
+
设计模式: Capability Protocol + Null Object
|
|
6
|
+
|
|
7
|
+
工作流面向能力编程:
|
|
8
|
+
cap = nexus.resolve()
|
|
9
|
+
if cap.memory.available:
|
|
10
|
+
facts = cap.memory.recall("本周执行任务")
|
|
11
|
+
|
|
12
|
+
L2 宿主原生能力。各宿主实现见 nexus/adapters/。
|
|
13
|
+
未实现的宿主 → NoOpMemoryCap(available=False,方法静默返回空)。
|
|
14
|
+
|
|
15
|
+
演进: 未来 mem0/Zep MCP server 成熟后,记忆能力 MCP 化,
|
|
16
|
+
McpMemoryProvider 抢占职责链链头,本契约不变。
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Protocol, runtime_checkable
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@runtime_checkable
|
|
24
|
+
class MemoryCap(Protocol):
|
|
25
|
+
"""跨会话记忆能力契约。
|
|
26
|
+
|
|
27
|
+
各宿主实现:
|
|
28
|
+
- QwMemoryCap: QoderWork 的 qw memory 工具(adapters/qw.py)
|
|
29
|
+
- FileMemoryCap: 文件降级 journal(adapters/cli.py)
|
|
30
|
+
- McpMemoryProvider:未来 mem0/Zep MCP server(adapters/mcp.py)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
available: bool
|
|
34
|
+
|
|
35
|
+
def recall(self, query: str) -> list[dict]:
|
|
36
|
+
"""语义检索记忆,返回相关条目列表。"""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
def remember(self, key: str, content: str) -> None:
|
|
40
|
+
"""写入一条记忆。"""
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
def get_fact(self, key: str) -> str | None:
|
|
44
|
+
"""按 key 取一条事实,无则 None。"""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
def list_recent(self, limit: int = 10) -> list[dict]:
|
|
48
|
+
"""最近 N 条记忆。"""
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class NoOpMemoryCap:
|
|
53
|
+
"""MemoryCap 空实现 —— Null Object 模式。
|
|
54
|
+
|
|
55
|
+
available=False,所有方法静默返回空值,绝不抛错。
|
|
56
|
+
作为 memory 职责链链尾兜底,保证无记忆能力时工作流不崩。
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
available = False
|
|
60
|
+
|
|
61
|
+
def recall(self, query: str) -> list[dict]:
|
|
62
|
+
return []
|
|
63
|
+
|
|
64
|
+
def remember(self, key: str, content: str) -> None:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
def get_fact(self, key: str) -> str | None:
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
def list_recent(self, limit: int = 10) -> list[dict]:
|
|
71
|
+
return []
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
NotifyCap —— IM 通知能力契约。
|
|
4
|
+
|
|
5
|
+
增强能力(NoOp 兜底)。
|
|
6
|
+
工作流场景: 日报/任务变更/评审结果推送 IM。
|
|
7
|
+
|
|
8
|
+
各宿主实现:
|
|
9
|
+
- QoderWork: 微信/飞书连接器
|
|
10
|
+
- CLI: 飞书 webhook(已有降级)
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import Protocol, runtime_checkable
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@runtime_checkable
|
|
18
|
+
class NotifyCap(Protocol):
|
|
19
|
+
"""IM 通知能力。"""
|
|
20
|
+
|
|
21
|
+
available: bool
|
|
22
|
+
|
|
23
|
+
def send(self, channel: str, message: str) -> bool:
|
|
24
|
+
"""发送消息到指定渠道,返回是否成功。"""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
def list_channels(self) -> list[dict]:
|
|
28
|
+
"""列出可用渠道。"""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class NoOpNotifyCap:
|
|
33
|
+
"""NotifyCap 空实现。"""
|
|
34
|
+
|
|
35
|
+
available = False
|
|
36
|
+
|
|
37
|
+
def send(self, channel: str, message: str) -> bool:
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
def list_channels(self) -> list[dict]:
|
|
41
|
+
return []
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
PresentCap —— 富展示能力契约。
|
|
4
|
+
|
|
5
|
+
增强能力(NoOp 兜底)。
|
|
6
|
+
工作流场景: 任务看板、甘特图、文件卡片、交互 widget。
|
|
7
|
+
|
|
8
|
+
各宿主实现:
|
|
9
|
+
- QoderWork: show_widget / present_files
|
|
10
|
+
- CLI: markdown 表格降级
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import Protocol, runtime_checkable
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@runtime_checkable
|
|
18
|
+
class PresentCap(Protocol):
|
|
19
|
+
"""富展示能力。"""
|
|
20
|
+
|
|
21
|
+
available: bool
|
|
22
|
+
|
|
23
|
+
def table(self, rows: list, headers: list | None = None) -> str:
|
|
24
|
+
"""渲染表格,返回可展示文本。"""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
def widget(self, html: str) -> bool:
|
|
28
|
+
"""展示交互 widget(仅富宿主),返回是否成功。"""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
def files(self, paths: list) -> bool:
|
|
32
|
+
"""以文件卡片形式展示。"""
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class NoOpPresentCap:
|
|
37
|
+
"""PresentCap 空实现 —— 无富展示时退化为空。"""
|
|
38
|
+
|
|
39
|
+
available = False
|
|
40
|
+
|
|
41
|
+
def table(self, rows: list, headers: list | None = None) -> str:
|
|
42
|
+
return ""
|
|
43
|
+
|
|
44
|
+
def widget(self, html: str) -> bool:
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
def files(self, paths: list) -> bool:
|
|
48
|
+
return False
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
RepoCap —— 仓库根发现能力契约。
|
|
4
|
+
|
|
5
|
+
核心能力(必有降级)。替代散落在 skill 里的 ~/.qoderwork/repo_root.py 硬编码。
|
|
6
|
+
让"仓库根发现"宿主无关:向上找含 .qoder/scripts/ 的目录即可。
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Protocol, runtime_checkable
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@runtime_checkable
|
|
15
|
+
class RepoCap(Protocol):
|
|
16
|
+
"""仓库根发现能力。"""
|
|
17
|
+
|
|
18
|
+
available: bool
|
|
19
|
+
|
|
20
|
+
def root(self) -> Path:
|
|
21
|
+
"""返回项目仓库根路径。"""
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class NoOpRepoCap:
|
|
26
|
+
"""RepoCap 空实现。仅占位 —— 核心能力总有文件降级,不会真用到 NoOp。"""
|
|
27
|
+
|
|
28
|
+
available = False
|
|
29
|
+
|
|
30
|
+
def root(self) -> Path:
|
|
31
|
+
return Path.cwd()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
SandboxCap —— 安全执行能力契约。
|
|
4
|
+
|
|
5
|
+
执行安全类(宿主差异大,NoOp 兜底)。
|
|
6
|
+
工作流场景: 在隔离环境跑命令/测试/构建。
|
|
7
|
+
|
|
8
|
+
各宿主实现:
|
|
9
|
+
- Claude Code: container / seatbelt
|
|
10
|
+
- Codex CLI: Docker + seatbelt
|
|
11
|
+
- QoderWork: (暂无原生沙箱)
|
|
12
|
+
- CLI: 直接执行(无沙箱,approval 把关)
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Protocol, runtime_checkable
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@runtime_checkable
|
|
20
|
+
class SandboxCap(Protocol):
|
|
21
|
+
"""安全执行能力。"""
|
|
22
|
+
|
|
23
|
+
available: bool
|
|
24
|
+
|
|
25
|
+
def run(self, command: str, cwd: str | None = None, timeout: int = 60) -> dict:
|
|
26
|
+
"""在沙箱内执行命令,返回 {ok, stdout, stderr, returncode}。"""
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
def approval_mode(self) -> str:
|
|
30
|
+
"""当前自主等级: suggest / auto-edit / full-auto / plan。"""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class NoOpSandboxCap:
|
|
35
|
+
"""SandboxCap 空实现 —— 无沙箱时拒绝执行。"""
|
|
36
|
+
|
|
37
|
+
available = False
|
|
38
|
+
|
|
39
|
+
def run(self, command: str, cwd: str | None = None, timeout: int = 60) -> dict:
|
|
40
|
+
return {"ok": False, "reason": "no sandbox available"}
|
|
41
|
+
|
|
42
|
+
def approval_mode(self) -> str:
|
|
43
|
+
return "suggest"
|