@hupan56/wlkj 3.3.1 → 3.3.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/package.json +29 -29
- package/templates/qoder/scripts/capability/registry_mcp.py +312 -312
- package/templates/qoder/scripts/domain/integration/return_to_platform.py +394 -395
- package/templates/qoder/scripts/domain/integration/spec_upload.py +208 -208
- package/templates/qoder/scripts/domain/kg/switch_project.py +158 -158
- package/templates/qoder/scripts/engine/poller.py +219 -219
|
@@ -1,219 +1,219 @@
|
|
|
1
|
-
"""引擎轮询器:后台轮询平台 pending 触发 → 认领 → 执行 → 回流 → 回填结果。
|
|
2
|
-
|
|
3
|
-
这是"平台→引擎→平台"真闭环的引擎侧执行器:
|
|
4
|
-
1. 轮询 GET /trigger/pending(带项目令牌)
|
|
5
|
-
2. 认领 POST /trigger/{tid}/claim(原子锁)
|
|
6
|
-
3. 执行:根据 command 调 MCP 取知识 + 生成产出(简化版:rag_search + 模板拼接)
|
|
7
|
-
4. 回流:create_prd/submit_return
|
|
8
|
-
5. 回填:PATCH /trigger/{tid}(mcp_tools_called/knowledge_used)
|
|
9
|
-
|
|
10
|
-
用法:
|
|
11
|
-
python poller.py --config ../mcp_config.json --interval 10
|
|
12
|
-
|
|
13
|
-
注:完整版需要接 LLM(qwen-plus)生成 PRD/原型。当前是简化版(rag_search + 模板),
|
|
14
|
-
验证闭环链路。接 LLM 后把 _execute 里的模板拼接换成 cap.mcp.call("ask_corpus",...) 生成。
|
|
15
|
-
"""
|
|
16
|
-
import json
|
|
17
|
-
import time
|
|
18
|
-
import urllib.request
|
|
19
|
-
import urllib.error
|
|
20
|
-
import argparse
|
|
21
|
-
import os
|
|
22
|
-
import sys
|
|
23
|
-
from pathlib import Path
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def _load_config(config_path: str) -> dict:
|
|
27
|
-
with open(config_path, encoding="utf-8") as f:
|
|
28
|
-
return json.load(f)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _get_token(cfg: dict) -> str:
|
|
32
|
-
for srv in (cfg.get("mcpServers") or {}).values():
|
|
33
|
-
return srv.get("token", "")
|
|
34
|
-
return ""
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _get_pid(cfg: dict) -> str:
|
|
38
|
-
for srv in (cfg.get("mcpServers") or {}).values():
|
|
39
|
-
env = srv.get("env") or {}
|
|
40
|
-
return env.get("WLKJ_PROJECT_ID", "")
|
|
41
|
-
return ""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _get_base(cfg: dict) -> str:
|
|
45
|
-
ep = cfg.get("return_endpoint") or {}
|
|
46
|
-
url = ep.get("url", "")
|
|
47
|
-
# 从 return_endpoint.url 提取 base(/api/projects 前面那段)
|
|
48
|
-
if "/api/" in url:
|
|
49
|
-
return url.split("/api/")[0]
|
|
50
|
-
for srv in (cfg.get("mcpServers") or {}).values():
|
|
51
|
-
u = srv.get("url", "")
|
|
52
|
-
if "/mcp" in u:
|
|
53
|
-
return u.split("/mcp")[0]
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def _api(base, method, path, token, body=None):
|
|
58
|
-
url = base + path
|
|
59
|
-
data = json.dumps(body).encode() if body else None
|
|
60
|
-
req = urllib.request.Request(url, data=data, method=method)
|
|
61
|
-
req.add_header("Content-Type", "application/json")
|
|
62
|
-
if token:
|
|
63
|
-
req.add_header("X-Engine-Token", token)
|
|
64
|
-
try:
|
|
65
|
-
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
66
|
-
return resp.status, json.loads(resp.read().decode())
|
|
67
|
-
except urllib.error.HTTPError as e:
|
|
68
|
-
return e.code, json.loads(e.read().decode() or "{}")
|
|
69
|
-
except Exception as e:
|
|
70
|
-
return 0, {"error": str(e)}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def _mcp_call(base, token, tool, args):
|
|
74
|
-
"""通过 MCP JSON-RPC 调工具。"""
|
|
75
|
-
body = {"jsonrpc": "2.0", "id": 1, "method": "tools/call",
|
|
76
|
-
"params": {"name": tool, "arguments": args}}
|
|
77
|
-
data = json.dumps(body).encode()
|
|
78
|
-
req = urllib.request.Request(base + "/mcp", data=data, method="POST")
|
|
79
|
-
req.add_header("Content-Type", "application/json")
|
|
80
|
-
req.add_header("X-MCP-Token", token)
|
|
81
|
-
try:
|
|
82
|
-
with urllib.request.urlopen(req, timeout=60) as resp:
|
|
83
|
-
r = json.loads(resp.read().decode())
|
|
84
|
-
text = r.get("result", {}).get("content", [{}])[0].get("text", "")
|
|
85
|
-
return json.loads(text) if text else {}
|
|
86
|
-
except Exception as e:
|
|
87
|
-
return {"error": str(e)}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def _execute(base, token, pid, command, args):
|
|
91
|
-
"""执行触发的工作流(简化版:rag_search 取知识 + 模板拼 PRD + learn 写回)。
|
|
92
|
-
完整版应接 LLM(ask_corpus)生成自然语言 PRD。"""
|
|
93
|
-
query = (args or {}).get("query", "")
|
|
94
|
-
tools_called = []
|
|
95
|
-
knowledge_used = []
|
|
96
|
-
|
|
97
|
-
# 1) rag_search 取知识
|
|
98
|
-
if query:
|
|
99
|
-
r = _mcp_call(base, token, "rag_search", {"query": query, "top_k": 8})
|
|
100
|
-
tools_called.append("rag_search")
|
|
101
|
-
knowledge_used = [it.get("entity_id", "") for it in r.get("items", [])[:5]]
|
|
102
|
-
knowledge_text = "\n".join(
|
|
103
|
-
"- %s (%s)" % (it.get("text", "")[:60], it.get("entity_id", "")[:30])
|
|
104
|
-
for it in r.get("items", [])[:8]
|
|
105
|
-
)
|
|
106
|
-
# 1b) get_learnings 查之前学到的(反哺)
|
|
107
|
-
lr = _mcp_call(base, token, "get_learnings", {"project_id": pid, "limit": 5})
|
|
108
|
-
tools_called.append("get_learnings")
|
|
109
|
-
learnings_text = "\n".join(
|
|
110
|
-
"- [%s] %s" % (l.get("category",""), (l.get("pattern","")[:60]))
|
|
111
|
-
for l in (lr.get("items") or [])[:5]
|
|
112
|
-
) or "(暂无学习记录)"
|
|
113
|
-
else:
|
|
114
|
-
knowledge_text = "(无关键词)"
|
|
115
|
-
learnings_text = ""
|
|
116
|
-
|
|
117
|
-
# 2) 根据 command 生成产出
|
|
118
|
-
if "prd" in command.lower():
|
|
119
|
-
prd_md = f"# {query} PRD(引擎自动生成)\n\n## 概述\n基于知识层自动生成的「{query}」需求文档。\n\n## 相关知识\n{knowledge_text}\n\n## 历史学习\n{learnings_text}\n\n## 功能\n(根据上述知识补充)"
|
|
120
|
-
# 回流
|
|
121
|
-
r = _mcp_call(base, token, "create_prd", {
|
|
122
|
-
"project_id": pid, "title": f"{query}PRD(引擎触发)", "content_md": prd_md, "status": "planning"
|
|
123
|
-
})
|
|
124
|
-
tools_called.append("create_prd")
|
|
125
|
-
# 学习写回:把这次的关键认知沉淀
|
|
126
|
-
_mcp_call(base, token, "learn", {"project_id": pid, "category": "rule",
|
|
127
|
-
"pattern": f"{query}: PRD已生成,涉及知识{len(knowledge_used)}条", "source": "wlkj-poller"})
|
|
128
|
-
tools_called.append("learn")
|
|
129
|
-
output = f"生成PRD: {query}PRD(引擎触发) + 学习沉淀"
|
|
130
|
-
elif "design" in command.lower():
|
|
131
|
-
html = f"<html><body><h1>{query}</h1><p>引擎自动生成原型</p></body></html>"
|
|
132
|
-
r = _mcp_call(base, token, "create_prototype", {
|
|
133
|
-
"project_id": pid, "feature": f"{query}(引擎触发)", "platform": "web", "html": html
|
|
134
|
-
})
|
|
135
|
-
tools_called.append("create_prototype")
|
|
136
|
-
output = f"生成原型: {query}(引擎触发)"
|
|
137
|
-
else:
|
|
138
|
-
# learn 写回
|
|
139
|
-
_mcp_call(base, token, "learn", {
|
|
140
|
-
"project_id": pid, "category": "rule", "pattern": f"{query}相关规则(引擎触发)",
|
|
141
|
-
"source": "wlkj-poller"
|
|
142
|
-
})
|
|
143
|
-
tools_called.append("learn")
|
|
144
|
-
output = f"执行命令: {command}"
|
|
145
|
-
|
|
146
|
-
return {"output": output, "tools": tools_called, "knowledge": knowledge_used}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def poll_once(cfg):
|
|
150
|
-
"""轮询一次:取 pending → 认领 → 执行 → 回填。"""
|
|
151
|
-
token = _get_token(cfg)
|
|
152
|
-
pid = _get_pid(cfg)
|
|
153
|
-
base = _get_base(cfg)
|
|
154
|
-
if not token or not pid:
|
|
155
|
-
print("[poller] 配置缺 token/project_id,跳过")
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
# 1) 查 pending
|
|
159
|
-
_, pending = _api(base, "GET", f"/api/projects/{pid}/trigger/pending", token)
|
|
160
|
-
items = pending.get("pending", [])
|
|
161
|
-
if not items:
|
|
162
|
-
return # 无待执行
|
|
163
|
-
|
|
164
|
-
for item in items[:1]: # 一次只处理 1 条
|
|
165
|
-
tid = item.get("triggerId")
|
|
166
|
-
cmd = item.get("command", "")
|
|
167
|
-
args = item.get("args", {})
|
|
168
|
-
print(f"[poller] 认领: {tid} {cmd} {args}")
|
|
169
|
-
|
|
170
|
-
# 2) claim
|
|
171
|
-
code, resp = _api(base, "POST", f"/api/projects/{pid}/trigger/workflow/{tid}/claim",
|
|
172
|
-
token, {"engine_id": "poller-001"})
|
|
173
|
-
if code != 200:
|
|
174
|
-
print(f"[poller] 认领失败({code}): {resp}")
|
|
175
|
-
continue
|
|
176
|
-
|
|
177
|
-
# 3) 执行
|
|
178
|
-
t0 = time.time()
|
|
179
|
-
try:
|
|
180
|
-
result = _execute(base, token, pid, cmd, args)
|
|
181
|
-
status = "success"
|
|
182
|
-
except Exception as e:
|
|
183
|
-
result = {"output": str(e), "tools": [], "knowledge": []}
|
|
184
|
-
status = "failed"
|
|
185
|
-
|
|
186
|
-
# 4) patch 回填
|
|
187
|
-
_api(base, "PATCH", f"/api/projects/{pid}/trigger/workflow/{tid}", token, {
|
|
188
|
-
"status": status,
|
|
189
|
-
"output_summary": result["output"],
|
|
190
|
-
"knowledge_used": result["knowledge"],
|
|
191
|
-
"mcp_tools_called": result["tools"],
|
|
192
|
-
"duration_sec": int(time.time() - t0),
|
|
193
|
-
})
|
|
194
|
-
print(f"[poller] 完成: {status} → {result['output']} (tools={result['tools']})")
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def main():
|
|
198
|
-
parser = argparse.ArgumentParser(description="wlkj 引擎轮询器")
|
|
199
|
-
parser.add_argument("--config", default=str(Path(__file__).parent.parent.parent / "mcp_config.json"))
|
|
200
|
-
parser.add_argument("--interval", type=int, default=10, help="轮询间隔(秒)")
|
|
201
|
-
parser.add_argument("--once", action="store_true", help="只跑一次")
|
|
202
|
-
args = parser.parse_args()
|
|
203
|
-
|
|
204
|
-
cfg = _load_config(args.config)
|
|
205
|
-
print(f"[poller] 启动: project={_get_pid(cfg)} interval={args.interval}s")
|
|
206
|
-
print(f"[poller] 按 Ctrl+C 停止")
|
|
207
|
-
|
|
208
|
-
while True:
|
|
209
|
-
try:
|
|
210
|
-
poll_once(cfg)
|
|
211
|
-
except Exception as e:
|
|
212
|
-
print(f"[poller] 异常: {e}")
|
|
213
|
-
if args.once:
|
|
214
|
-
break
|
|
215
|
-
time.sleep(args.interval)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if __name__ == "__main__":
|
|
219
|
-
main()
|
|
1
|
+
"""引擎轮询器:后台轮询平台 pending 触发 → 认领 → 执行 → 回流 → 回填结果。
|
|
2
|
+
|
|
3
|
+
这是"平台→引擎→平台"真闭环的引擎侧执行器:
|
|
4
|
+
1. 轮询 GET /trigger/pending(带项目令牌)
|
|
5
|
+
2. 认领 POST /trigger/{tid}/claim(原子锁)
|
|
6
|
+
3. 执行:根据 command 调 MCP 取知识 + 生成产出(简化版:rag_search + 模板拼接)
|
|
7
|
+
4. 回流:create_prd/submit_return
|
|
8
|
+
5. 回填:PATCH /trigger/{tid}(mcp_tools_called/knowledge_used)
|
|
9
|
+
|
|
10
|
+
用法:
|
|
11
|
+
python poller.py --config ../mcp_config.json --interval 10
|
|
12
|
+
|
|
13
|
+
注:完整版需要接 LLM(qwen-plus)生成 PRD/原型。当前是简化版(rag_search + 模板),
|
|
14
|
+
验证闭环链路。接 LLM 后把 _execute 里的模板拼接换成 cap.mcp.call("ask_corpus",...) 生成。
|
|
15
|
+
"""
|
|
16
|
+
import json
|
|
17
|
+
import time
|
|
18
|
+
import urllib.request
|
|
19
|
+
import urllib.error
|
|
20
|
+
import argparse
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _load_config(config_path: str) -> dict:
|
|
27
|
+
with open(config_path, encoding="utf-8") as f:
|
|
28
|
+
return json.load(f)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _get_token(cfg: dict) -> str:
|
|
32
|
+
for srv in (cfg.get("mcpServers") or {}).values():
|
|
33
|
+
return srv.get("token", "")
|
|
34
|
+
return ""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _get_pid(cfg: dict) -> str:
|
|
38
|
+
for srv in (cfg.get("mcpServers") or {}).values():
|
|
39
|
+
env = srv.get("env") or {}
|
|
40
|
+
return env.get("WLKJ_PROJECT_ID", "")
|
|
41
|
+
return ""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _get_base(cfg: dict) -> str:
|
|
45
|
+
ep = cfg.get("return_endpoint") or {}
|
|
46
|
+
url = ep.get("url", "")
|
|
47
|
+
# 从 return_endpoint.url 提取 base(/api/projects 前面那段)
|
|
48
|
+
if "/api/" in url:
|
|
49
|
+
return url.split("/api/")[0]
|
|
50
|
+
for srv in (cfg.get("mcpServers") or {}).values():
|
|
51
|
+
u = srv.get("url", "")
|
|
52
|
+
if "/mcp" in u:
|
|
53
|
+
return u.split("/mcp")[0]
|
|
54
|
+
return "http://127.0.0.1:10010"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _api(base, method, path, token, body=None):
|
|
58
|
+
url = base + path
|
|
59
|
+
data = json.dumps(body).encode() if body else None
|
|
60
|
+
req = urllib.request.Request(url, data=data, method=method)
|
|
61
|
+
req.add_header("Content-Type", "application/json")
|
|
62
|
+
if token:
|
|
63
|
+
req.add_header("X-Engine-Token", token)
|
|
64
|
+
try:
|
|
65
|
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
66
|
+
return resp.status, json.loads(resp.read().decode())
|
|
67
|
+
except urllib.error.HTTPError as e:
|
|
68
|
+
return e.code, json.loads(e.read().decode() or "{}")
|
|
69
|
+
except Exception as e:
|
|
70
|
+
return 0, {"error": str(e)}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _mcp_call(base, token, tool, args):
|
|
74
|
+
"""通过 MCP JSON-RPC 调工具。"""
|
|
75
|
+
body = {"jsonrpc": "2.0", "id": 1, "method": "tools/call",
|
|
76
|
+
"params": {"name": tool, "arguments": args}}
|
|
77
|
+
data = json.dumps(body).encode()
|
|
78
|
+
req = urllib.request.Request(base + "/mcp", data=data, method="POST")
|
|
79
|
+
req.add_header("Content-Type", "application/json")
|
|
80
|
+
req.add_header("X-MCP-Token", token)
|
|
81
|
+
try:
|
|
82
|
+
with urllib.request.urlopen(req, timeout=60) as resp:
|
|
83
|
+
r = json.loads(resp.read().decode())
|
|
84
|
+
text = r.get("result", {}).get("content", [{}])[0].get("text", "")
|
|
85
|
+
return json.loads(text) if text else {}
|
|
86
|
+
except Exception as e:
|
|
87
|
+
return {"error": str(e)}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _execute(base, token, pid, command, args):
|
|
91
|
+
"""执行触发的工作流(简化版:rag_search 取知识 + 模板拼 PRD + learn 写回)。
|
|
92
|
+
完整版应接 LLM(ask_corpus)生成自然语言 PRD。"""
|
|
93
|
+
query = (args or {}).get("query", "")
|
|
94
|
+
tools_called = []
|
|
95
|
+
knowledge_used = []
|
|
96
|
+
|
|
97
|
+
# 1) rag_search 取知识
|
|
98
|
+
if query:
|
|
99
|
+
r = _mcp_call(base, token, "rag_search", {"query": query, "top_k": 8})
|
|
100
|
+
tools_called.append("rag_search")
|
|
101
|
+
knowledge_used = [it.get("entity_id", "") for it in r.get("items", [])[:5]]
|
|
102
|
+
knowledge_text = "\n".join(
|
|
103
|
+
"- %s (%s)" % (it.get("text", "")[:60], it.get("entity_id", "")[:30])
|
|
104
|
+
for it in r.get("items", [])[:8]
|
|
105
|
+
)
|
|
106
|
+
# 1b) get_learnings 查之前学到的(反哺)
|
|
107
|
+
lr = _mcp_call(base, token, "get_learnings", {"project_id": pid, "limit": 5})
|
|
108
|
+
tools_called.append("get_learnings")
|
|
109
|
+
learnings_text = "\n".join(
|
|
110
|
+
"- [%s] %s" % (l.get("category",""), (l.get("pattern","")[:60]))
|
|
111
|
+
for l in (lr.get("items") or [])[:5]
|
|
112
|
+
) or "(暂无学习记录)"
|
|
113
|
+
else:
|
|
114
|
+
knowledge_text = "(无关键词)"
|
|
115
|
+
learnings_text = ""
|
|
116
|
+
|
|
117
|
+
# 2) 根据 command 生成产出
|
|
118
|
+
if "prd" in command.lower():
|
|
119
|
+
prd_md = f"# {query} PRD(引擎自动生成)\n\n## 概述\n基于知识层自动生成的「{query}」需求文档。\n\n## 相关知识\n{knowledge_text}\n\n## 历史学习\n{learnings_text}\n\n## 功能\n(根据上述知识补充)"
|
|
120
|
+
# 回流
|
|
121
|
+
r = _mcp_call(base, token, "create_prd", {
|
|
122
|
+
"project_id": pid, "title": f"{query}PRD(引擎触发)", "content_md": prd_md, "status": "planning"
|
|
123
|
+
})
|
|
124
|
+
tools_called.append("create_prd")
|
|
125
|
+
# 学习写回:把这次的关键认知沉淀
|
|
126
|
+
_mcp_call(base, token, "learn", {"project_id": pid, "category": "rule",
|
|
127
|
+
"pattern": f"{query}: PRD已生成,涉及知识{len(knowledge_used)}条", "source": "wlkj-poller"})
|
|
128
|
+
tools_called.append("learn")
|
|
129
|
+
output = f"生成PRD: {query}PRD(引擎触发) + 学习沉淀"
|
|
130
|
+
elif "design" in command.lower():
|
|
131
|
+
html = f"<html><body><h1>{query}</h1><p>引擎自动生成原型</p></body></html>"
|
|
132
|
+
r = _mcp_call(base, token, "create_prototype", {
|
|
133
|
+
"project_id": pid, "feature": f"{query}(引擎触发)", "platform": "web", "html": html
|
|
134
|
+
})
|
|
135
|
+
tools_called.append("create_prototype")
|
|
136
|
+
output = f"生成原型: {query}(引擎触发)"
|
|
137
|
+
else:
|
|
138
|
+
# learn 写回
|
|
139
|
+
_mcp_call(base, token, "learn", {
|
|
140
|
+
"project_id": pid, "category": "rule", "pattern": f"{query}相关规则(引擎触发)",
|
|
141
|
+
"source": "wlkj-poller"
|
|
142
|
+
})
|
|
143
|
+
tools_called.append("learn")
|
|
144
|
+
output = f"执行命令: {command}"
|
|
145
|
+
|
|
146
|
+
return {"output": output, "tools": tools_called, "knowledge": knowledge_used}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def poll_once(cfg):
|
|
150
|
+
"""轮询一次:取 pending → 认领 → 执行 → 回填。"""
|
|
151
|
+
token = _get_token(cfg)
|
|
152
|
+
pid = _get_pid(cfg)
|
|
153
|
+
base = _get_base(cfg)
|
|
154
|
+
if not token or not pid:
|
|
155
|
+
print("[poller] 配置缺 token/project_id,跳过")
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
# 1) 查 pending
|
|
159
|
+
_, pending = _api(base, "GET", f"/api/projects/{pid}/trigger/pending", token)
|
|
160
|
+
items = pending.get("pending", [])
|
|
161
|
+
if not items:
|
|
162
|
+
return # 无待执行
|
|
163
|
+
|
|
164
|
+
for item in items[:1]: # 一次只处理 1 条
|
|
165
|
+
tid = item.get("triggerId")
|
|
166
|
+
cmd = item.get("command", "")
|
|
167
|
+
args = item.get("args", {})
|
|
168
|
+
print(f"[poller] 认领: {tid} {cmd} {args}")
|
|
169
|
+
|
|
170
|
+
# 2) claim
|
|
171
|
+
code, resp = _api(base, "POST", f"/api/projects/{pid}/trigger/workflow/{tid}/claim",
|
|
172
|
+
token, {"engine_id": "poller-001"})
|
|
173
|
+
if code != 200:
|
|
174
|
+
print(f"[poller] 认领失败({code}): {resp}")
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
# 3) 执行
|
|
178
|
+
t0 = time.time()
|
|
179
|
+
try:
|
|
180
|
+
result = _execute(base, token, pid, cmd, args)
|
|
181
|
+
status = "success"
|
|
182
|
+
except Exception as e:
|
|
183
|
+
result = {"output": str(e), "tools": [], "knowledge": []}
|
|
184
|
+
status = "failed"
|
|
185
|
+
|
|
186
|
+
# 4) patch 回填
|
|
187
|
+
_api(base, "PATCH", f"/api/projects/{pid}/trigger/workflow/{tid}", token, {
|
|
188
|
+
"status": status,
|
|
189
|
+
"output_summary": result["output"],
|
|
190
|
+
"knowledge_used": result["knowledge"],
|
|
191
|
+
"mcp_tools_called": result["tools"],
|
|
192
|
+
"duration_sec": int(time.time() - t0),
|
|
193
|
+
})
|
|
194
|
+
print(f"[poller] 完成: {status} → {result['output']} (tools={result['tools']})")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def main():
|
|
198
|
+
parser = argparse.ArgumentParser(description="wlkj 引擎轮询器")
|
|
199
|
+
parser.add_argument("--config", default=str(Path(__file__).parent.parent.parent / "mcp_config.json"))
|
|
200
|
+
parser.add_argument("--interval", type=int, default=10, help="轮询间隔(秒)")
|
|
201
|
+
parser.add_argument("--once", action="store_true", help="只跑一次")
|
|
202
|
+
args = parser.parse_args()
|
|
203
|
+
|
|
204
|
+
cfg = _load_config(args.config)
|
|
205
|
+
print(f"[poller] 启动: project={_get_pid(cfg)} interval={args.interval}s")
|
|
206
|
+
print(f"[poller] 按 Ctrl+C 停止")
|
|
207
|
+
|
|
208
|
+
while True:
|
|
209
|
+
try:
|
|
210
|
+
poll_once(cfg)
|
|
211
|
+
except Exception as e:
|
|
212
|
+
print(f"[poller] 异常: {e}")
|
|
213
|
+
if args.once:
|
|
214
|
+
break
|
|
215
|
+
time.sleep(args.interval)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
if __name__ == "__main__":
|
|
219
|
+
main()
|