clawtool 0.1.3 → 0.1.4
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/loop.js +10 -2
- package/dist/planner.js +57 -7
- package/package.json +1 -1
- package/web/index.html +39 -4
package/dist/loop.js
CHANGED
|
@@ -15,6 +15,7 @@ class DoctorLoop {
|
|
|
15
15
|
constructor(callback, deps) {
|
|
16
16
|
this.state = 'idle';
|
|
17
17
|
this.stopped = false;
|
|
18
|
+
this.lang = 'en';
|
|
18
19
|
this.userDescription = '';
|
|
19
20
|
this.callback = callback;
|
|
20
21
|
this.deps = { ...defaultDeps, ...deps };
|
|
@@ -22,6 +23,9 @@ class DoctorLoop {
|
|
|
22
23
|
stop() {
|
|
23
24
|
this.stopped = true;
|
|
24
25
|
}
|
|
26
|
+
setLang(lang) {
|
|
27
|
+
this.lang = /^zh/i.test(lang) ? 'zh' : 'en';
|
|
28
|
+
}
|
|
25
29
|
provideInput(field, value) {
|
|
26
30
|
if (field === 'userDescription' && this.pendingDescription) {
|
|
27
31
|
this.pendingDescription(value || '');
|
|
@@ -46,12 +50,16 @@ class DoctorLoop {
|
|
|
46
50
|
const sessionId = Date.now().toString();
|
|
47
51
|
this.emit('session_start', { sessionId });
|
|
48
52
|
this.setState('observing');
|
|
49
|
-
this.emit('progress', {
|
|
53
|
+
this.emit('progress', {
|
|
54
|
+
message: this.lang === 'zh'
|
|
55
|
+
? '正在扫描本地 OpenClaw 环境...'
|
|
56
|
+
: 'Scanning your local OpenClaw setup...'
|
|
57
|
+
});
|
|
50
58
|
const observation = await this.deps.collectObservation();
|
|
51
59
|
const findings = this.deps.runRules(observation);
|
|
52
60
|
if (this.stopped)
|
|
53
61
|
return;
|
|
54
|
-
const steps = this.deps.buildPlanSteps(findings, observation);
|
|
62
|
+
const steps = this.deps.buildPlanSteps(findings, this.lang, observation);
|
|
55
63
|
this.setState('running');
|
|
56
64
|
for (let i = 0; i < steps.length; i++) {
|
|
57
65
|
const step = steps[i];
|
package/dist/planner.js
CHANGED
|
@@ -7,12 +7,62 @@ const severityWeight = {
|
|
|
7
7
|
warning: 2,
|
|
8
8
|
info: 3,
|
|
9
9
|
};
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const findingI18nZh = {
|
|
11
|
+
'gateway-not-running': {
|
|
12
|
+
title: '网关未运行',
|
|
13
|
+
description: 'OpenClaw 网关进程当前处于停止状态。',
|
|
14
|
+
},
|
|
15
|
+
'gateway-service-not-installed': {
|
|
16
|
+
title: '网关服务未安装或未加载',
|
|
17
|
+
description: 'LaunchAgent 服务缺失或未加载,网关无法稳定启动。',
|
|
18
|
+
},
|
|
19
|
+
'gateway-probe-failed': {
|
|
20
|
+
title: '网关 RPC 探测失败',
|
|
21
|
+
description: '网关端点当前无法建立稳定的 RPC 连接。',
|
|
22
|
+
},
|
|
23
|
+
'config-missing': {
|
|
24
|
+
title: '配置文件缺失',
|
|
25
|
+
description: '未找到 OpenClaw 配置文件。',
|
|
26
|
+
},
|
|
27
|
+
'config-json5-error': {
|
|
28
|
+
title: '检测到配置解析错误',
|
|
29
|
+
description: '日志显示 JSON5 语法错误,网关无法加载配置。',
|
|
30
|
+
},
|
|
31
|
+
'proxy-in-plist': {
|
|
32
|
+
title: 'LaunchAgent 中存在代理变量',
|
|
33
|
+
description: '代理环境变量可能导致模型服务连通失败。',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
function localizeFinding(finding, lang) {
|
|
37
|
+
if (lang !== 'zh')
|
|
38
|
+
return finding;
|
|
39
|
+
const mapped = findingI18nZh[finding.id];
|
|
40
|
+
if (!mapped)
|
|
41
|
+
return finding;
|
|
42
|
+
return {
|
|
43
|
+
...finding,
|
|
44
|
+
title: mapped.title,
|
|
45
|
+
description: mapped.description || finding.description,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function buildPlanSteps(findings, lang = 'en') {
|
|
49
|
+
const i18n = {
|
|
50
|
+
readGatewayStatus: lang === 'zh' ? '正在检查网关运行状态' : 'Checking gateway runtime status',
|
|
51
|
+
noCriticalFindings: lang === 'zh' ? '未发现关键问题' : 'No critical findings',
|
|
52
|
+
noFixNeededSummary: lang === 'zh'
|
|
53
|
+
? '未发现需要修复的关键问题,当前本地检查结果正常。'
|
|
54
|
+
: 'No fix was needed. System appears healthy from local checks.',
|
|
55
|
+
planFinished: lang === 'zh' ? '修复计划执行完成' : 'Plan finished',
|
|
56
|
+
appliedSummary: lang === 'zh'
|
|
57
|
+
? '已根据本地规则执行修复计划。'
|
|
58
|
+
: 'Applied local repair plan based on deterministic findings.',
|
|
59
|
+
};
|
|
60
|
+
const localizedFindings = findings.map((f) => localizeFinding(f, lang));
|
|
61
|
+
const sorted = [...localizedFindings].sort((a, b) => severityWeight[a.severity] - severityWeight[b.severity]);
|
|
12
62
|
const steps = [
|
|
13
63
|
{
|
|
14
64
|
type: 'read',
|
|
15
|
-
description:
|
|
65
|
+
description: i18n.readGatewayStatus,
|
|
16
66
|
command: 'openclaw gateway status 2>&1',
|
|
17
67
|
risk: 'low',
|
|
18
68
|
},
|
|
@@ -30,9 +80,9 @@ function buildPlanSteps(findings) {
|
|
|
30
80
|
if (sorted.length === 0) {
|
|
31
81
|
steps.push({
|
|
32
82
|
type: 'done',
|
|
33
|
-
description:
|
|
83
|
+
description: i18n.noCriticalFindings,
|
|
34
84
|
fixed: false,
|
|
35
|
-
summary:
|
|
85
|
+
summary: i18n.noFixNeededSummary,
|
|
36
86
|
warnings: [],
|
|
37
87
|
problem: null,
|
|
38
88
|
fix: null,
|
|
@@ -41,9 +91,9 @@ function buildPlanSteps(findings) {
|
|
|
41
91
|
}
|
|
42
92
|
steps.push({
|
|
43
93
|
type: 'done',
|
|
44
|
-
description:
|
|
94
|
+
description: i18n.planFinished,
|
|
45
95
|
fixed: true,
|
|
46
|
-
summary:
|
|
96
|
+
summary: i18n.appliedSummary,
|
|
47
97
|
warnings: [],
|
|
48
98
|
problem: sorted[0]?.description || null,
|
|
49
99
|
fix: sorted[0]?.fix || null,
|
package/package.json
CHANGED
package/web/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<meta name="google" content="notranslate" />
|
|
7
|
-
<title>
|
|
7
|
+
<title>OpenClaw Repair Assistant</title>
|
|
8
8
|
<style>
|
|
9
9
|
:root {
|
|
10
10
|
--bg: #f5f4ef;
|
|
@@ -163,6 +163,7 @@
|
|
|
163
163
|
<section id="start" class="card">
|
|
164
164
|
<div class="hint" id="start-text">No cloud call. No token required. Local execution only.</div>
|
|
165
165
|
<button id="btn-start" class="btn btn-main">Start Scan</button>
|
|
166
|
+
<button id="btn-rescan" class="btn btn-main hidden" style="margin-top:8px">Scan Again</button>
|
|
166
167
|
</section>
|
|
167
168
|
|
|
168
169
|
<section id="feed" class="feed hidden"></section>
|
|
@@ -180,7 +181,13 @@
|
|
|
180
181
|
allow: '执行',
|
|
181
182
|
skip: '跳过',
|
|
182
183
|
done: '完成',
|
|
183
|
-
error: '发生错误'
|
|
184
|
+
error: '发生错误',
|
|
185
|
+
rescan: '重新扫描',
|
|
186
|
+
badge: {
|
|
187
|
+
read: '检查',
|
|
188
|
+
fix: '修复',
|
|
189
|
+
done: '完成'
|
|
190
|
+
}
|
|
184
191
|
} : {
|
|
185
192
|
tag: 'Local-First Diagnose Engine',
|
|
186
193
|
sub: 'Detect and repair OpenClaw issues locally with explicit fix confirmation.',
|
|
@@ -190,7 +197,13 @@
|
|
|
190
197
|
allow: 'Allow',
|
|
191
198
|
skip: 'Skip',
|
|
192
199
|
done: 'Completed',
|
|
193
|
-
error: 'Error occurred'
|
|
200
|
+
error: 'Error occurred',
|
|
201
|
+
rescan: 'Scan Again',
|
|
202
|
+
badge: {
|
|
203
|
+
read: 'read',
|
|
204
|
+
fix: 'fix',
|
|
205
|
+
done: 'done'
|
|
206
|
+
}
|
|
194
207
|
};
|
|
195
208
|
|
|
196
209
|
const el = {
|
|
@@ -199,6 +212,7 @@
|
|
|
199
212
|
startText: document.getElementById('start-text'),
|
|
200
213
|
startWrap: document.getElementById('start'),
|
|
201
214
|
btnStart: document.getElementById('btn-start'),
|
|
215
|
+
btnRescan: document.getElementById('btn-rescan'),
|
|
202
216
|
feed: document.getElementById('feed')
|
|
203
217
|
};
|
|
204
218
|
|
|
@@ -206,6 +220,7 @@
|
|
|
206
220
|
el.sub.textContent = T.sub;
|
|
207
221
|
el.startText.textContent = T.startText;
|
|
208
222
|
el.btnStart.textContent = T.start;
|
|
223
|
+
el.btnRescan.textContent = T.rescan;
|
|
209
224
|
|
|
210
225
|
let eventSource = null;
|
|
211
226
|
let sessionId = null;
|
|
@@ -224,6 +239,15 @@
|
|
|
224
239
|
return 'b-read';
|
|
225
240
|
}
|
|
226
241
|
|
|
242
|
+
function badgeText(type) {
|
|
243
|
+
const map = {
|
|
244
|
+
read: isZh ? '检查' : 'read',
|
|
245
|
+
fix: isZh ? '修复' : 'fix',
|
|
246
|
+
done: isZh ? '完成' : 'done'
|
|
247
|
+
};
|
|
248
|
+
return map[type] || map.read;
|
|
249
|
+
}
|
|
250
|
+
|
|
227
251
|
async function postConfirm(confirmed) {
|
|
228
252
|
if (!sessionId) return;
|
|
229
253
|
await fetch('/api/confirm', {
|
|
@@ -255,7 +279,7 @@
|
|
|
255
279
|
const card = addCard(
|
|
256
280
|
'<div class="step-title">'
|
|
257
281
|
+ '<span>' + (step.description || 'step') + '</span>'
|
|
258
|
-
+ '<span class="badge ' + typeBadge(step.type) + '">' + (step.type || 'read') + '</span>'
|
|
282
|
+
+ '<span class="badge ' + typeBadge(step.type) + '">' + badgeText(step.type || 'read') + '</span>'
|
|
259
283
|
+ '</div>'
|
|
260
284
|
+ (step.type !== 'read' && step.command ? '<div class="mono" translate="no">' + step.command + '</div>' : '')
|
|
261
285
|
);
|
|
@@ -279,12 +303,14 @@
|
|
|
279
303
|
|
|
280
304
|
if (type === 'complete') {
|
|
281
305
|
addCard('<h3 class="ok">' + T.done + '</h3><p>' + (data.summary || '') + '</p>');
|
|
306
|
+
el.btnRescan.classList.remove('hidden');
|
|
282
307
|
if (eventSource) eventSource.close();
|
|
283
308
|
return;
|
|
284
309
|
}
|
|
285
310
|
|
|
286
311
|
if (type === 'error') {
|
|
287
312
|
addCard('<h3 class="err">' + T.error + '</h3><p>' + (data.message || '') + '</p>');
|
|
313
|
+
el.btnRescan.classList.remove('hidden');
|
|
288
314
|
if (eventSource) eventSource.close();
|
|
289
315
|
}
|
|
290
316
|
};
|
|
@@ -293,6 +319,15 @@
|
|
|
293
319
|
el.btnStart.addEventListener('click', function () {
|
|
294
320
|
el.startWrap.classList.add('hidden');
|
|
295
321
|
el.feed.classList.remove('hidden');
|
|
322
|
+
el.btnRescan.classList.add('hidden');
|
|
323
|
+
connect();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
el.btnRescan.addEventListener('click', function () {
|
|
327
|
+
el.feed.innerHTML = '';
|
|
328
|
+
el.feed.classList.remove('hidden');
|
|
329
|
+
el.btnRescan.classList.add('hidden');
|
|
330
|
+
if (eventSource) eventSource.close();
|
|
296
331
|
connect();
|
|
297
332
|
});
|
|
298
333
|
|