homegraph 1.1.1 → 1.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/dist/installer/targets/deveco.d.ts +7 -2
- package/dist/installer/targets/deveco.d.ts.map +1 -1
- package/dist/installer/targets/deveco.js +62 -32
- package/dist/installer/targets/deveco.js.map +1 -1
- package/package.json +1 -1
- package/scripts/qa_eval/README.md +10 -6
- package/scripts/qa_eval/_test_deveco_probe.py +41 -0
- package/scripts/qa_eval/external_agent.py +142 -48
- package/scripts/qa_eval/run_pipeline.py +10 -1
|
@@ -11,12 +11,17 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Config file resolution (per DevEco Code docs):
|
|
13
13
|
* - local: `.deveco/deveco.jsonc` → `deveco.jsonc` (defaults to `.deveco/`)
|
|
14
|
-
* - global: `~/.config/deveco/deveco.jsonc` (XDG-style
|
|
15
|
-
*
|
|
14
|
+
* - global: `~/.config/deveco/deveco.jsonc` (XDG-style on EVERY platform,
|
|
15
|
+
* Windows included — DevEco uses the same `xdg-basedir` resolution
|
|
16
|
+
* as opencode, not `%APPDATA%`)
|
|
16
17
|
*
|
|
17
18
|
* Like Cursor, DevEco Code may launch MCP servers with a cwd that
|
|
18
19
|
* isn't the workspace root, so we inject `--path` into the command
|
|
19
20
|
* array.
|
|
21
|
+
*
|
|
22
|
+
* Early homegraph installs wrote the global entry to `%APPDATA%/deveco` on
|
|
23
|
+
* Windows; DevEco never reads it. install/uninstall sweep stale entries
|
|
24
|
+
* from that legacy location.
|
|
20
25
|
*/
|
|
21
26
|
import { AgentTarget } from './types';
|
|
22
27
|
export declare const devecoTarget: AgentTarget;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deveco.d.ts","sourceRoot":"","sources":["../../../src/installer/targets/deveco.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"deveco.d.ts","sourceRoot":"","sources":["../../../src/installer/targets/deveco.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAMH,OAAO,EACL,WAAW,EAKZ,MAAM,SAAS,CAAC;AAgNjB,eAAO,MAAM,YAAY,EAAE,WAAgC,CAAC"}
|
|
@@ -12,12 +12,17 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Config file resolution (per DevEco Code docs):
|
|
14
14
|
* - local: `.deveco/deveco.jsonc` → `deveco.jsonc` (defaults to `.deveco/`)
|
|
15
|
-
* - global: `~/.config/deveco/deveco.jsonc` (XDG-style
|
|
16
|
-
*
|
|
15
|
+
* - global: `~/.config/deveco/deveco.jsonc` (XDG-style on EVERY platform,
|
|
16
|
+
* Windows included — DevEco uses the same `xdg-basedir` resolution
|
|
17
|
+
* as opencode, not `%APPDATA%`)
|
|
17
18
|
*
|
|
18
19
|
* Like Cursor, DevEco Code may launch MCP servers with a cwd that
|
|
19
20
|
* isn't the workspace root, so we inject `--path` into the command
|
|
20
21
|
* array.
|
|
22
|
+
*
|
|
23
|
+
* Early homegraph installs wrote the global entry to `%APPDATA%/deveco` on
|
|
24
|
+
* Windows; DevEco never reads it. install/uninstall sweep stale entries
|
|
25
|
+
* from that legacy location.
|
|
21
26
|
*/
|
|
22
27
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
23
28
|
if (k2 === undefined) k2 = k;
|
|
@@ -61,15 +66,23 @@ const jsonc_parser_1 = require("jsonc-parser");
|
|
|
61
66
|
const shared_1 = require("./shared");
|
|
62
67
|
const instructions_template_1 = require("../instructions-template");
|
|
63
68
|
function globalConfigDir() {
|
|
64
|
-
if (process.platform === 'win32') {
|
|
65
|
-
const appData = process.env.APPDATA ?? path.join(os.homedir(), 'AppData', 'Roaming');
|
|
66
|
-
return path.join(appData, 'deveco');
|
|
67
|
-
}
|
|
68
69
|
const xdg = process.env.XDG_CONFIG_HOME && process.env.XDG_CONFIG_HOME.trim().length > 0
|
|
69
70
|
? process.env.XDG_CONFIG_HOME
|
|
70
71
|
: path.join(os.homedir(), '.config');
|
|
71
72
|
return path.join(xdg, 'deveco');
|
|
72
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Early installs wrote the global entry to `%APPDATA%/deveco` on Windows — a
|
|
76
|
+
* dir DevEco never reads. Returns that legacy dir when it could hold stale
|
|
77
|
+
* state (APPDATA set and resolving somewhere other than the real config dir).
|
|
78
|
+
*/
|
|
79
|
+
function legacyWindowsConfigDir() {
|
|
80
|
+
const appData = process.env.APPDATA;
|
|
81
|
+
if (!appData || !appData.trim())
|
|
82
|
+
return null;
|
|
83
|
+
const legacy = path.join(appData, 'deveco');
|
|
84
|
+
return path.resolve(legacy) === path.resolve(globalConfigDir()) ? null : legacy;
|
|
85
|
+
}
|
|
73
86
|
function configBaseDir(loc) {
|
|
74
87
|
return loc === 'global' ? globalConfigDir() : process.cwd();
|
|
75
88
|
}
|
|
@@ -123,8 +136,9 @@ class DevecoTarget {
|
|
|
123
136
|
const file = configPath(loc);
|
|
124
137
|
const config = parseConfig(readConfigText(file));
|
|
125
138
|
const alreadyConfigured = !!config.mcp?.homegraph;
|
|
139
|
+
const legacy = legacyWindowsConfigDir();
|
|
126
140
|
const installed = loc === 'global'
|
|
127
|
-
? fs.existsSync(globalConfigDir())
|
|
141
|
+
? fs.existsSync(globalConfigDir()) || (!!legacy && fs.existsSync(legacy))
|
|
128
142
|
: fs.existsSync(path.join(process.cwd(), '.deveco'))
|
|
129
143
|
|| fs.existsSync(path.join(process.cwd(), 'deveco.jsonc'));
|
|
130
144
|
return { installed, alreadyConfigured, configPath: file };
|
|
@@ -135,6 +149,8 @@ class DevecoTarget {
|
|
|
135
149
|
const instrCleanup = removeInstructionsEntry(loc);
|
|
136
150
|
if (instrCleanup.action === 'removed')
|
|
137
151
|
files.push(instrCleanup);
|
|
152
|
+
if (loc === 'global')
|
|
153
|
+
files.push(...cleanupLegacyWindowsState());
|
|
138
154
|
return {
|
|
139
155
|
files,
|
|
140
156
|
notes: ['Restart DevEco Code for MCP changes to take effect.'],
|
|
@@ -142,32 +158,10 @@ class DevecoTarget {
|
|
|
142
158
|
}
|
|
143
159
|
uninstall(loc) {
|
|
144
160
|
const files = [];
|
|
145
|
-
|
|
146
|
-
if (!fs.existsSync(file)) {
|
|
147
|
-
files.push({ path: file, action: 'not-found' });
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
const text = readConfigText(file);
|
|
151
|
-
const config = parseConfig(text);
|
|
152
|
-
if (!config.mcp?.homegraph) {
|
|
153
|
-
files.push({ path: file, action: 'not-found' });
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
let edits = (0, jsonc_parser_1.modify)(text, ['mcp', 'homegraph'], undefined, {
|
|
157
|
-
formattingOptions: FORMATTING,
|
|
158
|
-
});
|
|
159
|
-
let updated = (0, jsonc_parser_1.applyEdits)(text, edits);
|
|
160
|
-
const afterParsed = parseConfig(updated);
|
|
161
|
-
if (afterParsed.mcp && typeof afterParsed.mcp === 'object' &&
|
|
162
|
-
Object.keys(afterParsed.mcp).length === 0) {
|
|
163
|
-
edits = (0, jsonc_parser_1.modify)(updated, ['mcp'], undefined, { formattingOptions: FORMATTING });
|
|
164
|
-
updated = (0, jsonc_parser_1.applyEdits)(updated, edits);
|
|
165
|
-
}
|
|
166
|
-
(0, shared_1.atomicWriteFileSync)(file, updated);
|
|
167
|
-
files.push({ path: file, action: 'removed' });
|
|
168
|
-
}
|
|
169
|
-
}
|
|
161
|
+
files.push(removeMcpEntryAt(configPath(loc)));
|
|
170
162
|
files.push(removeInstructionsEntry(loc));
|
|
163
|
+
if (loc === 'global')
|
|
164
|
+
files.push(...cleanupLegacyWindowsState());
|
|
171
165
|
return { files };
|
|
172
166
|
}
|
|
173
167
|
printConfig(loc) {
|
|
@@ -208,6 +202,42 @@ function writeMcpEntry(loc) {
|
|
|
208
202
|
(0, shared_1.atomicWriteFileSync)(file, updated);
|
|
209
203
|
return { path: file, action: existed ? 'updated' : 'created' };
|
|
210
204
|
}
|
|
205
|
+
function removeMcpEntryAt(file) {
|
|
206
|
+
if (!fs.existsSync(file))
|
|
207
|
+
return { path: file, action: 'not-found' };
|
|
208
|
+
const text = readConfigText(file);
|
|
209
|
+
const config = parseConfig(text);
|
|
210
|
+
if (!config.mcp?.homegraph)
|
|
211
|
+
return { path: file, action: 'not-found' };
|
|
212
|
+
let edits = (0, jsonc_parser_1.modify)(text, ['mcp', 'homegraph'], undefined, {
|
|
213
|
+
formattingOptions: FORMATTING,
|
|
214
|
+
});
|
|
215
|
+
let updated = (0, jsonc_parser_1.applyEdits)(text, edits);
|
|
216
|
+
const afterParsed = parseConfig(updated);
|
|
217
|
+
if (afterParsed.mcp && typeof afterParsed.mcp === 'object' &&
|
|
218
|
+
Object.keys(afterParsed.mcp).length === 0) {
|
|
219
|
+
edits = (0, jsonc_parser_1.modify)(updated, ['mcp'], undefined, { formattingOptions: FORMATTING });
|
|
220
|
+
updated = (0, jsonc_parser_1.applyEdits)(updated, edits);
|
|
221
|
+
}
|
|
222
|
+
(0, shared_1.atomicWriteFileSync)(file, updated);
|
|
223
|
+
return { path: file, action: 'removed' };
|
|
224
|
+
}
|
|
225
|
+
function cleanupLegacyWindowsState() {
|
|
226
|
+
const dir = legacyWindowsConfigDir();
|
|
227
|
+
if (!dir || !fs.existsSync(dir))
|
|
228
|
+
return [];
|
|
229
|
+
const out = [];
|
|
230
|
+
for (const name of ['deveco.jsonc', 'deveco.json']) {
|
|
231
|
+
const res = removeMcpEntryAt(path.join(dir, name));
|
|
232
|
+
if (res.action === 'removed')
|
|
233
|
+
out.push(res);
|
|
234
|
+
}
|
|
235
|
+
const agents = path.join(dir, 'AGENTS.md');
|
|
236
|
+
const action = (0, shared_1.removeMarkedSection)(agents, instructions_template_1.HOMEGRAPH_SECTION_START, instructions_template_1.HOMEGRAPH_SECTION_END);
|
|
237
|
+
if (action === 'removed')
|
|
238
|
+
out.push({ path: agents, action });
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
211
241
|
function removeInstructionsEntry(loc) {
|
|
212
242
|
const file = instructionsPath(loc);
|
|
213
243
|
const action = (0, shared_1.removeMarkedSection)(file, instructions_template_1.HOMEGRAPH_SECTION_START, instructions_template_1.HOMEGRAPH_SECTION_END);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deveco.js","sourceRoot":"","sources":["../../../src/installer/targets/deveco.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"deveco.js","sourceRoot":"","sources":["../../../src/installer/targets/deveco.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homegraph",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Supercharge AI coding agents with semantic code intelligence — surgical context, fewer tool calls, faster answers. 100% local.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -88,15 +88,17 @@ python scripts/qa_eval/run_pipeline.py ab -r /path/to/your/repo
|
|
|
88
88
|
|
|
89
89
|
**注意**:`--provider zhipu` 只影响 **builtin Agent** 和 **Judge**;`deveco-code` / `claude-code` 使用各自 CLI 的账号与凭证,不会读取 `ZHIPU_API_KEY`。
|
|
90
90
|
|
|
91
|
-
DevEco
|
|
91
|
+
DevEco 首次使用需配置模型(凭证保存在用户目录,**不是** DEVECO_HOME):
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
|
-
deveco providers
|
|
95
|
-
|
|
96
|
-
# 或
|
|
97
|
-
deveco providers login
|
|
94
|
+
deveco providers login # 选 Zhipu AI,输入 API Key
|
|
95
|
+
# 凭证目录(Windows): C:\Users\<你>\.config\deveco
|
|
98
96
|
```
|
|
99
97
|
|
|
98
|
+
TUI 若提示 **DEVECO_HOME**,那是 DevEco **Studio** 安装路径(需 6.1+),仅编译/推包需要;**跑 qa_eval 可跳过**。你当前 Studio 为 5.1,填 `C:\Program Files\Huawei\DevEco Studio` 会校验失败,属正常。
|
|
99
|
+
|
|
100
|
+
homegraph MCP 写在**被测仓库**的 `.deveco/deveco.jsonc`;智谱 Key 仍读 `~/.config/deveco`。
|
|
101
|
+
|
|
100
102
|
**Judge 打分**(Stage 2)三种宿主共用,仍需 `ZHIPU_API_KEY` 或 `DASHSCOPE_API_KEY`(与 `--provider` 一致)。
|
|
101
103
|
|
|
102
104
|
#### 怎么跑
|
|
@@ -121,6 +123,8 @@ where deveco
|
|
|
121
123
|
deveco providers reset # 仅凭证损坏/换机时需要
|
|
122
124
|
deveco # TUI 里配置智谱等模型后再跑评测
|
|
123
125
|
python scripts/qa_eval/run_pipeline.py ab -r /path/to/your/repo --provider zhipu --agent-host deveco-code
|
|
126
|
+
# 指定 DevEco 模型(可选,格式 provider/model):
|
|
127
|
+
# python scripts/qa_eval/run_pipeline.py ab -r /path/to/your/repo --provider zhipu --agent-host deveco-code --deveco-model zhipuai/glm-4.5-flash
|
|
124
128
|
# → report-deveco.txt
|
|
125
129
|
|
|
126
130
|
# ④ 三种依次跑(各写各的报告,不会互相覆盖)
|
|
@@ -380,7 +384,7 @@ python scripts/qa_eval/run_pipeline.py score \
|
|
|
380
384
|
| 现象 | 处理 |
|
|
381
385
|
|------|------|
|
|
382
386
|
| Claude 准确率全 < 0.1 / `Not logged in` | Agent 未登录:`claude login` 后重跑;pipeline 现会在首题探测并 fail-fast |
|
|
383
|
-
| `deveco
|
|
387
|
+
| `deveco-code` exit 1 / `Model not found: glm-4-flash` | `--provider zhipu` 的模型只给 Judge/builtin;deveco 用 `deveco models` 里的 `zhipuai/...` 名,或加 `--deveco-model zhipuai/glm-4.5-flash` |
|
|
384
388
|
| `401` / Judge 全失败 | 检查 API Key:`DASHSCOPE_API_KEY`(DashScope)或 `ZHIPU_API_KEY`(智谱,格式 `id.secret`);智谱请加 `--provider zhipu`,勿把智谱 Key 设到 `DASHSCOPE_API_KEY` |
|
|
385
389
|
| `Cannot find module 'arkanalyzer'` | `npm run build`,用 `node dist/bin/homegraph.js sync` |
|
|
386
390
|
| `rg not found on PATH` | `sudo apt install ripgrep`(without 臂需要) |
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""One-off probe: parser + single run_deveco_query. Run from repo root."""
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
7
|
+
from external_agent import DEFAULT_DEVECO_MODEL, parse_opencode_json_events, run_deveco_query
|
|
8
|
+
|
|
9
|
+
SAMPLE = (
|
|
10
|
+
'{"type":"step_start","part":{"type":"step-start"}}\n'
|
|
11
|
+
'{"type":"text","part":{"type":"text","text":"\\nOK"}}\n'
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
def main() -> int:
|
|
15
|
+
parsed = parse_opencode_json_events(SAMPLE)
|
|
16
|
+
assert parsed["agent_status"] == "success" and "OK" in parsed["output_answer"], parsed
|
|
17
|
+
print("parser OK:", parsed["output_answer"])
|
|
18
|
+
|
|
19
|
+
repo = Path(r"D:\code\benchmark")
|
|
20
|
+
if not repo.is_dir():
|
|
21
|
+
print("skip run: benchmark repo missing")
|
|
22
|
+
return 0
|
|
23
|
+
|
|
24
|
+
print(f"probing run_deveco_query (model={DEFAULT_DEVECO_MODEL}, timeout=90s)...")
|
|
25
|
+
r = run_deveco_query(
|
|
26
|
+
repo,
|
|
27
|
+
"reply OK only",
|
|
28
|
+
arm="with",
|
|
29
|
+
hg_bin=None,
|
|
30
|
+
log_file=None,
|
|
31
|
+
task_id=0,
|
|
32
|
+
timeout_sec=90,
|
|
33
|
+
)
|
|
34
|
+
print("status:", r.get("agent_status"))
|
|
35
|
+
print("error:", r.get("agent_error"))
|
|
36
|
+
print("answer:", (r.get("output_answer") or "")[:200])
|
|
37
|
+
return 0 if r.get("agent_status") == "success" else 1
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
raise SystemExit(main())
|
|
@@ -29,8 +29,35 @@ def _strip_ansi(text: str) -> str:
|
|
|
29
29
|
return re.sub(r"\x1b\[[0-9;]*m", "", text or "")
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
def _extract_deveco_json_errors(text: str) -> str | None:
|
|
33
|
+
"""Parse deveco --format json error lines into a short message."""
|
|
34
|
+
messages: list[str] = []
|
|
35
|
+
for line in _strip_ansi(text).splitlines():
|
|
36
|
+
line = line.strip()
|
|
37
|
+
if not line.startswith("{"):
|
|
38
|
+
continue
|
|
39
|
+
try:
|
|
40
|
+
ev = json.loads(line)
|
|
41
|
+
except json.JSONDecodeError:
|
|
42
|
+
continue
|
|
43
|
+
if ev.get("type") != "error":
|
|
44
|
+
continue
|
|
45
|
+
err = ev.get("error") or {}
|
|
46
|
+
data = err.get("data") or {}
|
|
47
|
+
msg = data.get("message") or err.get("message") or err.get("name")
|
|
48
|
+
if msg and msg not in messages:
|
|
49
|
+
messages.append(str(msg))
|
|
50
|
+
if not messages:
|
|
51
|
+
return None
|
|
52
|
+
return "; ".join(messages)
|
|
53
|
+
|
|
54
|
+
|
|
32
55
|
def _cli_error_summary(stderr: str, stdout: str, *, max_len: int = 400) -> str:
|
|
33
56
|
"""Pick a short user-facing error from CLI stderr/stdout."""
|
|
57
|
+
for raw in (stderr, stdout):
|
|
58
|
+
deveco_err = _extract_deveco_json_errors(raw)
|
|
59
|
+
if deveco_err:
|
|
60
|
+
return deveco_err[:max_len]
|
|
34
61
|
for raw in (stderr, stdout):
|
|
35
62
|
clean = _strip_ansi(raw).strip()
|
|
36
63
|
if not clean:
|
|
@@ -63,7 +90,11 @@ def auth_failure_reason(text: str) -> str | None:
|
|
|
63
90
|
"DevEco Code 未配置或凭证无效。请运行: deveco providers reset,"
|
|
64
91
|
"然后重新登录/配置 provider"
|
|
65
92
|
)
|
|
66
|
-
if "
|
|
93
|
+
if "model not found" in t:
|
|
94
|
+
return (
|
|
95
|
+
"DevEco 模型名无效。deveco 需要 provider/model 格式(如 zhipuai/glm-4.5-flash),"
|
|
96
|
+
"可用 `deveco models` 查看;勿把 Judge 的 --model 直接传给 deveco"
|
|
97
|
+
)
|
|
67
98
|
return "API Key 无效"
|
|
68
99
|
if "authentication required" in t or "unauthorized" in t:
|
|
69
100
|
return "未授权,请检查登录或 API Key"
|
|
@@ -104,6 +135,19 @@ def find_deveco_cli() -> str:
|
|
|
104
135
|
)
|
|
105
136
|
|
|
106
137
|
|
|
138
|
+
def _popen_capture(cmd: list[str], *, cwd: str) -> subprocess.Popen:
|
|
139
|
+
"""Run CLI with UTF-8 stdout/stderr (Windows default locale is often GBK)."""
|
|
140
|
+
return subprocess.Popen(
|
|
141
|
+
cmd,
|
|
142
|
+
cwd=cwd,
|
|
143
|
+
stdout=subprocess.PIPE,
|
|
144
|
+
stderr=subprocess.PIPE,
|
|
145
|
+
text=True,
|
|
146
|
+
encoding="utf-8",
|
|
147
|
+
errors="replace",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
107
151
|
def write_mcp_config(path: Path, *, hg_command: str, hg_args: list[str]) -> None:
|
|
108
152
|
path.write_text(
|
|
109
153
|
json.dumps({"mcpServers": {"homegraph": {"command": hg_command, "args": hg_args}}}, indent=2)
|
|
@@ -188,6 +232,7 @@ def parse_opencode_json_events(raw: str) -> dict[str, Any]:
|
|
|
188
232
|
total_tokens = 0
|
|
189
233
|
max_turn = 0
|
|
190
234
|
duration_ms = 0
|
|
235
|
+
stream_errors: list[str] = []
|
|
191
236
|
|
|
192
237
|
for line in raw.splitlines():
|
|
193
238
|
line = line.strip()
|
|
@@ -199,7 +244,37 @@ def parse_opencode_json_events(raw: str) -> dict[str, Any]:
|
|
|
199
244
|
continue
|
|
200
245
|
|
|
201
246
|
ev_type = ev.get("type") or ev.get("event")
|
|
202
|
-
|
|
247
|
+
part = ev.get("part") if isinstance(ev.get("part"), dict) else {}
|
|
248
|
+
|
|
249
|
+
if ev_type == "error":
|
|
250
|
+
err = ev.get("error") or {}
|
|
251
|
+
data = err.get("data") or {}
|
|
252
|
+
msg = data.get("message") or err.get("message") or err.get("name")
|
|
253
|
+
if msg:
|
|
254
|
+
stream_errors.append(str(msg))
|
|
255
|
+
|
|
256
|
+
# DevEco / opencode stream-json: {type:"text", part:{type:"text", text:"..."}}
|
|
257
|
+
if ev_type == "text" and part.get("text"):
|
|
258
|
+
text = str(part["text"]).strip()
|
|
259
|
+
if text:
|
|
260
|
+
answer_parts.append(text)
|
|
261
|
+
max_turn += 1
|
|
262
|
+
|
|
263
|
+
part_type = part.get("type") or ""
|
|
264
|
+
if ev_type in ("tool", "tool_call", "tool_use") or part_type in (
|
|
265
|
+
"tool",
|
|
266
|
+
"tool-invocation",
|
|
267
|
+
"tool_use",
|
|
268
|
+
"tool-call",
|
|
269
|
+
):
|
|
270
|
+
max_turn += 1
|
|
271
|
+
name = part.get("tool") or part.get("name") or ev.get("tool") or "?"
|
|
272
|
+
inp = part.get("input") or part.get("args") or part.get("state") or ev.get("input") or {}
|
|
273
|
+
tool_trace.append(
|
|
274
|
+
f"---\n{name}\nargs: {json.dumps(inp, ensure_ascii=False)[:500]}\n---"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
if ev_type in ("message", "assistant"):
|
|
203
278
|
content = ev.get("content") or ev.get("text") or ev.get("message")
|
|
204
279
|
if isinstance(content, str) and content.strip():
|
|
205
280
|
answer_parts.append(content.strip())
|
|
@@ -225,6 +300,10 @@ def parse_opencode_json_events(raw: str) -> dict[str, Any]:
|
|
|
225
300
|
if ev.get("duration_ms"):
|
|
226
301
|
duration_ms = max(duration_ms, int(ev["duration_ms"]))
|
|
227
302
|
|
|
303
|
+
part_time = part.get("time") if isinstance(part.get("time"), dict) else {}
|
|
304
|
+
if part_time.get("start") and part_time.get("end"):
|
|
305
|
+
duration_ms = max(duration_ms, int(part_time["end"]) - int(part_time["start"]))
|
|
306
|
+
|
|
228
307
|
answer = "\n".join(answer_parts).strip()
|
|
229
308
|
output = "\n\n".join(tool_trace + ([answer] if answer else []))
|
|
230
309
|
auth_err = auth_failure_reason(answer) or auth_failure_reason(raw)
|
|
@@ -237,9 +316,19 @@ def parse_opencode_json_events(raw: str) -> dict[str, Any]:
|
|
|
237
316
|
"agent_duration_ms": duration_ms,
|
|
238
317
|
"agent_usage": {"total_tokens": total_tokens} if total_tokens else {},
|
|
239
318
|
}
|
|
319
|
+
if stream_errors and not answer:
|
|
320
|
+
return {
|
|
321
|
+
"output_answer": output,
|
|
322
|
+
"agent_status": "error",
|
|
323
|
+
"agent_error": "; ".join(stream_errors),
|
|
324
|
+
"agent_turns": max_turn,
|
|
325
|
+
"agent_duration_ms": duration_ms,
|
|
326
|
+
"agent_usage": {"total_tokens": total_tokens} if total_tokens else {},
|
|
327
|
+
}
|
|
240
328
|
return {
|
|
241
329
|
"output_answer": output,
|
|
242
330
|
"agent_status": "success" if answer else "error",
|
|
331
|
+
"agent_error": None if answer else "deveco 未返回可解析的文本回答",
|
|
243
332
|
"agent_turns": max_turn,
|
|
244
333
|
"agent_duration_ms": duration_ms,
|
|
245
334
|
"agent_usage": {"total_tokens": total_tokens} if total_tokens else {},
|
|
@@ -305,13 +394,7 @@ def run_claude_query(
|
|
|
305
394
|
_log_line(log_file, "the 1 turn")
|
|
306
395
|
t0 = time.time()
|
|
307
396
|
|
|
308
|
-
proc =
|
|
309
|
-
cmd,
|
|
310
|
-
cwd=str(repo),
|
|
311
|
-
stdout=subprocess.PIPE,
|
|
312
|
-
stderr=subprocess.PIPE,
|
|
313
|
-
text=True,
|
|
314
|
-
)
|
|
397
|
+
proc = _popen_capture(cmd, cwd=str(repo))
|
|
315
398
|
with sample_memory(proc.pid) as sampler:
|
|
316
399
|
try:
|
|
317
400
|
stdout, stderr = proc.communicate(timeout=timeout_sec)
|
|
@@ -329,7 +412,7 @@ def run_claude_query(
|
|
|
329
412
|
}
|
|
330
413
|
mem = sampler.last_stats
|
|
331
414
|
|
|
332
|
-
if proc.returncode != 0 and not stdout.strip():
|
|
415
|
+
if proc.returncode != 0 and not (stdout or "").strip():
|
|
333
416
|
_log_memory(log_file, mem)
|
|
334
417
|
return {
|
|
335
418
|
"output_answer": (stderr or "")[:2000],
|
|
@@ -352,7 +435,7 @@ def run_claude_query(
|
|
|
352
435
|
"agent_host": HOST_CLAUDE,
|
|
353
436
|
"agent_memory_mb": mem,
|
|
354
437
|
}
|
|
355
|
-
parsed = parse_claude_stream_json(stdout)
|
|
438
|
+
parsed = parse_claude_stream_json(stdout or "")
|
|
356
439
|
tokens = (parsed.get("agent_usage") or {}).get("total_tokens") or 0
|
|
357
440
|
if tokens:
|
|
358
441
|
_log_line(log_file, f"totalTokenCount = {tokens}")
|
|
@@ -369,6 +452,41 @@ def run_claude_query(
|
|
|
369
452
|
}
|
|
370
453
|
|
|
371
454
|
|
|
455
|
+
def _deveco_project_config_dir(repo: Path) -> Path:
|
|
456
|
+
"""Project-level deveco config (read before ~/.config/deveco/deveco.jsonc)."""
|
|
457
|
+
return repo / ".deveco"
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _write_deveco_project_mcp(repo: Path, *, arm: str, hg_bin: str) -> Path:
|
|
461
|
+
"""Write repo/.deveco/deveco.jsonc for MCP only; credentials stay in ~/.config/deveco."""
|
|
462
|
+
config_dir = _deveco_project_config_dir(repo)
|
|
463
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
464
|
+
config_path = config_dir / "deveco.jsonc"
|
|
465
|
+
if arm == "with":
|
|
466
|
+
if not hg_bin:
|
|
467
|
+
from agent_runner import find_homegraph_bin
|
|
468
|
+
|
|
469
|
+
hg_bin = find_homegraph_bin(None)
|
|
470
|
+
cmd, base_args = _split_hg_bin(hg_bin)
|
|
471
|
+
body = {
|
|
472
|
+
"$schema": "https://opencode.ai/config.json",
|
|
473
|
+
"mcp": {
|
|
474
|
+
"homegraph": {
|
|
475
|
+
"type": "local",
|
|
476
|
+
"command": [cmd, *base_args, "--path", str(repo.resolve())],
|
|
477
|
+
"enabled": True,
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
}
|
|
481
|
+
else:
|
|
482
|
+
body = {"$schema": "https://opencode.ai/config.json", "mcp": {}}
|
|
483
|
+
config_path.write_text(json.dumps(body, indent=2) + "\n", encoding="utf-8")
|
|
484
|
+
return config_path
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
DEFAULT_DEVECO_MODEL = "zhipuai/glm-4.5-flash"
|
|
488
|
+
|
|
489
|
+
|
|
372
490
|
def run_deveco_query(
|
|
373
491
|
repo: Path,
|
|
374
492
|
query: str,
|
|
@@ -383,33 +501,7 @@ def run_deveco_query(
|
|
|
383
501
|
cli = find_deveco_cli()
|
|
384
502
|
backend = f"deveco-code-{'with' if arm == 'with' else 'without'}-homegraph"
|
|
385
503
|
|
|
386
|
-
|
|
387
|
-
config_dir.mkdir(exist_ok=True)
|
|
388
|
-
config_path = config_dir / "opencode.jsonc"
|
|
389
|
-
if arm == "with":
|
|
390
|
-
cmd, base_args = _split_hg_bin(hg_bin)
|
|
391
|
-
config_path.write_text(
|
|
392
|
-
json.dumps(
|
|
393
|
-
{
|
|
394
|
-
"$schema": "https://opencode.ai/config.json",
|
|
395
|
-
"mcp": {
|
|
396
|
-
"homegraph": {
|
|
397
|
-
"type": "local",
|
|
398
|
-
"command": [cmd, *base_args, "--path", str(repo.resolve())],
|
|
399
|
-
"enabled": True,
|
|
400
|
-
}
|
|
401
|
-
},
|
|
402
|
-
},
|
|
403
|
-
indent=2,
|
|
404
|
-
)
|
|
405
|
-
+ "\n",
|
|
406
|
-
encoding="utf-8",
|
|
407
|
-
)
|
|
408
|
-
else:
|
|
409
|
-
config_path.write_text(
|
|
410
|
-
'{"$schema":"https://opencode.ai/config.json","mcp":{}}\n',
|
|
411
|
-
encoding="utf-8",
|
|
412
|
-
)
|
|
504
|
+
_write_deveco_project_mcp(repo, arm=arm, hg_bin=hg_bin)
|
|
413
505
|
|
|
414
506
|
run_cmd = [
|
|
415
507
|
cli,
|
|
@@ -423,19 +515,14 @@ def run_deveco_query(
|
|
|
423
515
|
]
|
|
424
516
|
if model:
|
|
425
517
|
run_cmd.extend(["--model", model])
|
|
518
|
+
else:
|
|
519
|
+
run_cmd.extend(["--model", DEFAULT_DEVECO_MODEL])
|
|
426
520
|
|
|
427
521
|
_log_line(log_file, f"Evaluate {task_id}:")
|
|
428
522
|
_log_line(log_file, "the 1 turn")
|
|
429
523
|
t0 = time.time()
|
|
430
524
|
|
|
431
|
-
proc =
|
|
432
|
-
run_cmd,
|
|
433
|
-
cwd=str(repo),
|
|
434
|
-
stdout=subprocess.PIPE,
|
|
435
|
-
stderr=subprocess.PIPE,
|
|
436
|
-
text=True,
|
|
437
|
-
env={**os.environ, "XDG_CONFIG_HOME": str(config_dir)},
|
|
438
|
-
)
|
|
525
|
+
proc = _popen_capture(run_cmd, cwd=str(repo))
|
|
439
526
|
with sample_memory(proc.pid) as sampler:
|
|
440
527
|
try:
|
|
441
528
|
stdout, stderr = proc.communicate(timeout=timeout_sec)
|
|
@@ -469,14 +556,16 @@ def run_deveco_query(
|
|
|
469
556
|
}
|
|
470
557
|
|
|
471
558
|
_log_line(log_file, "first token")
|
|
472
|
-
|
|
559
|
+
out = (stdout or "").strip()
|
|
560
|
+
err = (stderr or "").strip()
|
|
561
|
+
parsed = parse_opencode_json_events(out if out else err)
|
|
473
562
|
tokens = (parsed.get("agent_usage") or {}).get("total_tokens") or 0
|
|
474
563
|
if tokens:
|
|
475
564
|
_log_line(log_file, f"totalTokenCount = {tokens}")
|
|
476
565
|
_log_memory(log_file, mem)
|
|
477
566
|
|
|
478
567
|
duration_ms = parsed.get("agent_duration_ms") or int((time.time() - t0) * 1000)
|
|
479
|
-
|
|
568
|
+
result = {
|
|
480
569
|
**parsed,
|
|
481
570
|
"agent_duration_ms": duration_ms,
|
|
482
571
|
"agent_backend": backend,
|
|
@@ -484,6 +573,9 @@ def run_deveco_query(
|
|
|
484
573
|
"ab_arm": "with-homegraph" if arm == "with" else "without-homegraph",
|
|
485
574
|
"agent_memory_mb": mem,
|
|
486
575
|
}
|
|
576
|
+
if result.get("agent_status") != "success" and not result.get("agent_error"):
|
|
577
|
+
result["agent_error"] = _extract_deveco_json_errors(combined) or "deveco 未返回可解析的文本回答"
|
|
578
|
+
return result
|
|
487
579
|
|
|
488
580
|
|
|
489
581
|
def run_external_dataset(
|
|
@@ -569,6 +661,8 @@ def run_external_dataset(
|
|
|
569
661
|
if i == 1 and meta.get("agent_error") and (
|
|
570
662
|
"未登录" in str(meta["agent_error"])
|
|
571
663
|
or "DevEco Code" in str(meta["agent_error"])
|
|
664
|
+
or "DevEco 模型" in str(meta["agent_error"])
|
|
665
|
+
or "Model not found" in str(meta["agent_error"])
|
|
572
666
|
):
|
|
573
667
|
auth_abort = str(meta["agent_error"])
|
|
574
668
|
print(f"\n ✗ {auth_abort} — 后续题目跳过", flush=True)
|
|
@@ -396,6 +396,7 @@ def run_agent_stage(
|
|
|
396
396
|
llm,
|
|
397
397
|
hg_bin: str | None,
|
|
398
398
|
max_turns: int,
|
|
399
|
+
deveco_model: str | None = None,
|
|
399
400
|
) -> None:
|
|
400
401
|
if agent_host == HOST_CLAUDE:
|
|
401
402
|
verify_claude_login()
|
|
@@ -428,7 +429,7 @@ def run_agent_stage(
|
|
|
428
429
|
output=out,
|
|
429
430
|
log_file=log,
|
|
430
431
|
hg_bin=hg_bin or "",
|
|
431
|
-
model=
|
|
432
|
+
model=deveco_model if agent_host == HOST_DEVECO else None,
|
|
432
433
|
)
|
|
433
434
|
|
|
434
435
|
|
|
@@ -497,6 +498,7 @@ def cmd_ab(args: argparse.Namespace) -> int:
|
|
|
497
498
|
llm=llm,
|
|
498
499
|
hg_bin=args.homegraph_bin,
|
|
499
500
|
max_turns=args.max_turns,
|
|
501
|
+
deveco_model=getattr(args, "deveco_model", None),
|
|
500
502
|
)
|
|
501
503
|
|
|
502
504
|
if not args.no_judge:
|
|
@@ -605,6 +607,7 @@ def cmd_hosts(args: argparse.Namespace) -> int:
|
|
|
605
607
|
no_judge=args.no_judge,
|
|
606
608
|
report=None,
|
|
607
609
|
agent_host=host,
|
|
610
|
+
deveco_model=getattr(args, "deveco_model", None),
|
|
608
611
|
)
|
|
609
612
|
if cmd_ab(host_args) != 0:
|
|
610
613
|
rc = 1
|
|
@@ -653,6 +656,11 @@ def main() -> int:
|
|
|
653
656
|
p_ab.add_argument("--base-url", default=None, help="OpenAI 兼容 API 端点(默认随 provider)")
|
|
654
657
|
p_ab.add_argument("--homegraph-bin", default=None)
|
|
655
658
|
p_ab.add_argument("--max-turns", type=int, default=8, help="Agent 最多工具轮次")
|
|
659
|
+
p_ab.add_argument(
|
|
660
|
+
"--deveco-model",
|
|
661
|
+
default=None,
|
|
662
|
+
help="DevEco Agent 模型(provider/model,如 zhipuai/glm-4.5-flash);默认用 DevEco 自身配置,不用 --model",
|
|
663
|
+
)
|
|
656
664
|
p_ab.add_argument("--no-agent", action="store_true", help="不跑 Agent,沿用已有 JSONL")
|
|
657
665
|
p_ab.add_argument("--no-judge", action="store_true", help="不跑 Judge,沿用已有 scored")
|
|
658
666
|
p_ab.add_argument(
|
|
@@ -676,6 +684,7 @@ def main() -> int:
|
|
|
676
684
|
p_hosts.add_argument("--base-url", default=None)
|
|
677
685
|
p_hosts.add_argument("--homegraph-bin", default=None)
|
|
678
686
|
p_hosts.add_argument("--max-turns", type=int, default=8)
|
|
687
|
+
p_hosts.add_argument("--deveco-model", default=None)
|
|
679
688
|
p_hosts.add_argument("--no-agent", action="store_true")
|
|
680
689
|
p_hosts.add_argument("--no-judge", action="store_true")
|
|
681
690
|
p_hosts.add_argument(
|