@lotaber_wang/openclaw-dc-plugin 0.1.14 → 0.1.16
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/README.md +7 -5
- package/index.js +150 -12
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -3
- package/scripts/postinstall.js +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
请这样安装:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
openclaw plugins install @lotaber_wang/openclaw-dc-plugin@0.1.
|
|
12
|
+
openclaw plugins install @lotaber_wang/openclaw-dc-plugin@0.1.16
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
不要这样安装:
|
|
@@ -67,7 +67,7 @@ openclaw gateway restart
|
|
|
67
67
|
## 推荐安装流程
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
|
-
openclaw plugins install @lotaber_wang/openclaw-dc-plugin@0.1.
|
|
70
|
+
openclaw plugins install @lotaber_wang/openclaw-dc-plugin@0.1.16
|
|
71
71
|
openclaw plugins inspect openclaw-dc-plugin
|
|
72
72
|
echo 'TapTap DC 插件已安装完成。OpenClaw 接下来如需重启 Gateway,应先通过飞书或其他通信插件通知用户;重启完成后即可使用 taptap_dc_* 工具。'
|
|
73
73
|
openclaw gateway restart
|
|
@@ -77,7 +77,7 @@ openclaw gateway restart
|
|
|
77
77
|
|
|
78
78
|
- `install` 负责把插件包装进 OpenClaw
|
|
79
79
|
- `inspect` 用来确认插件元信息、skills、tools 是否已被 Gateway 识别
|
|
80
|
-
- 从 `0.1.
|
|
80
|
+
- 从 `0.1.16` 开始,插件包不再声明 `openclaw` 的 `peerDependencies`,避免误用 `npm install` 时把 OpenClaw 宿主大依赖树一起拉下来
|
|
81
81
|
- 如果有人误用 `npm install` 或 `npm install -g`,插件包会在 `postinstall` 阶段打印醒目的纠正提示
|
|
82
82
|
- 如果宿主判断需要重启 Gateway,必须先通过飞书或其他通信插件通知用户,再重启
|
|
83
83
|
- 如果 10-30 秒后还没生效,再手动执行 `gateway restart`
|
|
@@ -147,7 +147,7 @@ openclaw gateway restart
|
|
|
147
147
|
|
|
148
148
|
如果安装后看不到 `taptap_dc_*` 工具,优先按这个顺序排查:
|
|
149
149
|
|
|
150
|
-
1. 确认安装的是 `@lotaber_wang/openclaw-dc-plugin@0.1.
|
|
150
|
+
1. 确认安装的是 `@lotaber_wang/openclaw-dc-plugin@0.1.16` 或更新版本
|
|
151
151
|
2. 执行 `openclaw plugins inspect openclaw-dc-plugin`
|
|
152
152
|
3. 如果宿主要求重启 Gateway,先通过飞书或其他通信插件向用户发送安装完成提示
|
|
153
153
|
4. 再执行 `openclaw gateway restart`
|
|
@@ -175,7 +175,7 @@ openclaw gateway restart
|
|
|
175
175
|
- OpenClaw 进程是否有临时目录写权限
|
|
176
176
|
- OpenClaw 是否已经完成 Gateway 重启
|
|
177
177
|
|
|
178
|
-
从 `0.1.
|
|
178
|
+
从 `0.1.16` 开始,插件安装链路与 bridge 会额外做这些兼容处理:
|
|
179
179
|
|
|
180
180
|
- 在 `package.json` 中补充 `openclaw.install.npmSpec`,让安装链路更稳定
|
|
181
181
|
- 在 `package.json` 的 `openclaw.install` 中补充 `preferredCommand` / `doNotUseCommands` / `wrongInstallHint`
|
|
@@ -194,3 +194,5 @@ openclaw gateway restart
|
|
|
194
194
|
- 如果拿到的是半截 JSON,会先继续等待后续分片,而不是立刻按失败处理
|
|
195
195
|
- 过滤 PTY 场景下被回显到 stdout 的请求消息
|
|
196
196
|
- 授权结果会把裸授权直链放在最前面,并在 `details.preferred_auth_url` / `details.auth_links` 中重复返回,降低宿主偶发吞链路时的影响
|
|
197
|
+
- 如果原始 tool 调用直接抛出“缺少认证信息”错误,bridge 也会从错误文本里提取 `wrapped_auth_url`,并补出 `preferred_auth_url`、手机端点击链接,以及“从 code 参数 decodeURIComponent 得到直链”的解析提示
|
|
198
|
+
- 从这版开始,授权提示会同时保留 `desktop_scan_url` 和 `mobile_click_url`;只要发生转发就默认保留这两条,因为飞书消息也可能在 PC 端打开。同时明确要求在飞书等 IM 中优先使用 markdown 链接格式,避免 `_` 等字符导致裸链接被截断
|
package/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { TapTapMcpBridge } from './lib/mcp-bridge.js';
|
|
|
5
5
|
const RESTART_NOTICE =
|
|
6
6
|
'TapTap DC 插件已安装完成。OpenClaw 接下来如需重启 Gateway,应先通过飞书或其他通信插件通知用户;重启完成后即可使用 taptap_dc_* 工具。';
|
|
7
7
|
const PREFERRED_INSTALL_COMMAND =
|
|
8
|
-
'openclaw plugins install @lotaber_wang/openclaw-dc-plugin@0.1.
|
|
8
|
+
'openclaw plugins install @lotaber_wang/openclaw-dc-plugin@0.1.16';
|
|
9
9
|
const WRONG_INSTALL_HINT =
|
|
10
10
|
'这是 OpenClaw 插件包,不是普通 npm CLI 包。请不要直接用 npm install,改用 openclaw plugins install。';
|
|
11
11
|
|
|
@@ -74,6 +74,14 @@ function buildMarkdownLink(label, url) {
|
|
|
74
74
|
return `[${label}](${url})`;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
function buildAutolink(url) {
|
|
78
|
+
if (!url) {
|
|
79
|
+
return '-';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return `<${url}>`;
|
|
83
|
+
}
|
|
84
|
+
|
|
77
85
|
function tryParseUrl(value) {
|
|
78
86
|
if (!value || typeof value !== 'string') {
|
|
79
87
|
return null;
|
|
@@ -100,6 +108,38 @@ function decodeWrappedAuthUrl(value) {
|
|
|
100
108
|
}
|
|
101
109
|
}
|
|
102
110
|
|
|
111
|
+
function extractUrlsFromText(value) {
|
|
112
|
+
if (!value || typeof value !== 'string') {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const matches = value.match(/https?:\/\/[^\s"'<>]+/g) || [];
|
|
117
|
+
return matches.map((item) => item.replace(/[),.;]+$/g, ''));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function buildAuthLinkParseHint(wrappedAuthUrl, directAuthUrl, preferredAuthUrl) {
|
|
121
|
+
const desktopScanUrl = wrappedAuthUrl || null;
|
|
122
|
+
const mobileClickUrl = preferredAuthUrl || directAuthUrl || wrappedAuthUrl || null;
|
|
123
|
+
return {
|
|
124
|
+
desktop_scan_url: desktopScanUrl,
|
|
125
|
+
mobile_click_url: mobileClickUrl,
|
|
126
|
+
wrapped_auth_url: wrappedAuthUrl || null,
|
|
127
|
+
direct_auth_url: directAuthUrl || null,
|
|
128
|
+
desktop_scan_markdown: desktopScanUrl ? buildMarkdownLink('PC 扫码授权页', desktopScanUrl) : null,
|
|
129
|
+
mobile_click_markdown: mobileClickUrl
|
|
130
|
+
? buildMarkdownLink('手机直接授权', mobileClickUrl)
|
|
131
|
+
: null,
|
|
132
|
+
parse_rule:
|
|
133
|
+
wrappedAuthUrl && directAuthUrl
|
|
134
|
+
? '如果只有 wrapped_auth_url,请读取其中的 code 参数,并执行一次 decodeURIComponent(code),得到 direct_auth_url。手机端优先打开 direct_auth_url。'
|
|
135
|
+
: '如果当前是手机端会话,请优先展示 preferred_auth_url;如果宿主吞掉超链接,就直接输出这条裸 URL。',
|
|
136
|
+
display_rule:
|
|
137
|
+
'如果需要转发给用户,默认同时保留 desktop_scan_url 和 mobile_click_url 两条链接;即使是飞书消息也这样处理,因为飞书可能在 PC 端打开。手机端对话时可以额外再单独发一次 mobile_click_url。',
|
|
138
|
+
lark_render_rule:
|
|
139
|
+
'飞书或其他 IM 中,转发时默认同时给出 [PC 扫码授权页](desktop_scan_url) 与 [手机直接授权](mobile_click_url) 两条 markdown 链接;如果 markdown 被吞掉,再分别单独发送两行 <URL>。',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
103
143
|
function enrichAuthPayload(authPayload) {
|
|
104
144
|
if (!authPayload || typeof authPayload !== 'object') {
|
|
105
145
|
return authPayload;
|
|
@@ -154,6 +194,11 @@ function enrichAuthPayload(authPayload) {
|
|
|
154
194
|
authorization_url: preferredAuthUrl,
|
|
155
195
|
mobile_auth_url: preferredAuthUrl,
|
|
156
196
|
auth_links: authLinks,
|
|
197
|
+
auth_link_parse_hint: buildAuthLinkParseHint(
|
|
198
|
+
wrappedAuthUrl,
|
|
199
|
+
directAuthUrl,
|
|
200
|
+
preferredAuthUrl
|
|
201
|
+
),
|
|
157
202
|
next_action:
|
|
158
203
|
'优先打开 preferred_auth_url 完成授权;完成后调用 taptap_dc_complete_authorization。',
|
|
159
204
|
};
|
|
@@ -317,30 +362,33 @@ function buildAuthGuideText(authPayload) {
|
|
|
317
362
|
const directAuthUrl = enriched?.direct_auth_url;
|
|
318
363
|
const wrappedAuthUrl = enriched?.wrapped_auth_url;
|
|
319
364
|
const qrcodeUrl = enriched?.qrcode_url;
|
|
365
|
+
const desktopScanUrl = wrappedAuthUrl || qrcodeUrl || null;
|
|
366
|
+
const mobileClickUrl = preferredAuthUrl || directAuthUrl || wrappedAuthUrl || null;
|
|
320
367
|
const lines = ['当前还没有完成 TapTap 授权,请先完成一次授权。'];
|
|
321
368
|
|
|
322
|
-
if (
|
|
369
|
+
if (desktopScanUrl) {
|
|
323
370
|
lines.push(
|
|
324
371
|
'',
|
|
325
|
-
'
|
|
326
|
-
|
|
327
|
-
|
|
372
|
+
'PC / 桌面端请保留下面这条扫码授权页链接,避免宿主只保留手机直链后导致电脑端链路断掉:',
|
|
373
|
+
buildMarkdownLink('PC 扫码授权页', desktopScanUrl),
|
|
374
|
+
buildAutolink(desktopScanUrl)
|
|
328
375
|
);
|
|
329
376
|
}
|
|
330
377
|
|
|
331
|
-
if (
|
|
378
|
+
if (mobileClickUrl) {
|
|
332
379
|
lines.push(
|
|
333
380
|
'',
|
|
334
|
-
'
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
`- ${buildMarkdownLink('打开授权页直链', qrcodeUrl || directAuthUrl || preferredAuthUrl)}`
|
|
381
|
+
'手机端请额外再给用户一条可直接点击的授权链接:',
|
|
382
|
+
buildMarkdownLink('手机直接授权', mobileClickUrl),
|
|
383
|
+
buildAutolink(mobileClickUrl)
|
|
338
384
|
);
|
|
339
385
|
}
|
|
340
386
|
|
|
341
387
|
lines.push(
|
|
342
388
|
'',
|
|
343
|
-
'
|
|
389
|
+
'如果需要把授权信息转发给用户,请默认同时保留“PC 扫码授权页”和“手机直接授权”两条链接,因为飞书消息也可能在 PC 端打开。',
|
|
390
|
+
'如果你当前就是手机端对话,可以额外强调“手机直接授权”;但不要把 PC 扫码授权页删掉。',
|
|
391
|
+
'如果超链接显示异常,优先使用 markdown 链接;如果 markdown 仍被吞掉,再单独输出一行 `<URL>`。',
|
|
344
392
|
'完成授权后,请继续调用 `taptap_dc_complete_authorization`,然后再次调用 `taptap_dc_quick_brief`。'
|
|
345
393
|
);
|
|
346
394
|
|
|
@@ -352,6 +400,14 @@ function buildAuthGuideText(authPayload) {
|
|
|
352
400
|
lines.push('', `preferred_auth_url:${preferredAuthUrl}`);
|
|
353
401
|
}
|
|
354
402
|
|
|
403
|
+
if (desktopScanUrl) {
|
|
404
|
+
lines.push(`desktop_scan_url:${desktopScanUrl}`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (mobileClickUrl) {
|
|
408
|
+
lines.push(`mobile_click_url:${mobileClickUrl}`);
|
|
409
|
+
}
|
|
410
|
+
|
|
355
411
|
if (directAuthUrl && directAuthUrl !== preferredAuthUrl) {
|
|
356
412
|
lines.push('', `直接授权链接:${directAuthUrl}`);
|
|
357
413
|
}
|
|
@@ -364,9 +420,81 @@ function buildAuthGuideText(authPayload) {
|
|
|
364
420
|
lines.push(`授权页直链:${qrcodeUrl}`);
|
|
365
421
|
}
|
|
366
422
|
|
|
423
|
+
lines.push(
|
|
424
|
+
'',
|
|
425
|
+
'给 Agent / 龙虾的额外处理提示:',
|
|
426
|
+
'- 只要发生转发,就默认同时展示 `desktop_scan_url` 和 `mobile_click_url`,不要只保留其中一条,因为飞书也可能在 PC 端打开。',
|
|
427
|
+
'- 如果当前是手机端对话,可以把 `mobile_click_url` 单独再发一条消息给用户,方便直接点击授权,但仍要保留 `desktop_scan_url`。',
|
|
428
|
+
'- 通过飞书或其他通信插件转发时,优先使用 markdown 链接格式:`[PC 扫码授权页](desktop_scan_url)` 与 `[手机直接授权](mobile_click_url)`;如果 markdown 被吞掉,再分别发两条仅包含 `<URL>` 的消息。',
|
|
429
|
+
'- 如果你只拿到了 TapTap 包装授权页(`wrapped_auth_url`),请取其中的 `code` 参数,再执行一次 `decodeURIComponent(code)`,即可得到更适合手机端直接点击的 `direct_auth_url`。',
|
|
430
|
+
'- 如果宿主把 markdown 链接吞掉了,请直接输出 `details.preferred_auth_url` 这条裸 URL,不要只说“请扫码”。'
|
|
431
|
+
);
|
|
432
|
+
|
|
367
433
|
return lines.join('\n');
|
|
368
434
|
}
|
|
369
435
|
|
|
436
|
+
function looksLikeAuthErrorMessage(message) {
|
|
437
|
+
if (!message || typeof message !== 'string') {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return (
|
|
442
|
+
message.includes('缺少认证信息') ||
|
|
443
|
+
message.includes('需要 TapTap 授权') ||
|
|
444
|
+
message.includes('当前没有有效的 TapTap 认证信息') ||
|
|
445
|
+
message.includes('complete_oauth_authorization') ||
|
|
446
|
+
extractUrlsFromText(message).some((url) => url.includes('tap-qrcode'))
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function extractAuthPayloadFromErrorMessage(message) {
|
|
451
|
+
if (!looksLikeAuthErrorMessage(message)) {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const urls = extractUrlsFromText(message);
|
|
456
|
+
if (urls.length === 0) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const wrappedAuthUrl =
|
|
461
|
+
urls.find((url) => url.includes('tap-qrcode')) ||
|
|
462
|
+
urls.find((url) => tryParseUrl(url)?.searchParams?.has('code')) ||
|
|
463
|
+
null;
|
|
464
|
+
|
|
465
|
+
const decodedDirectUrl = decodeWrappedAuthUrl(wrappedAuthUrl);
|
|
466
|
+
const directAuthUrl =
|
|
467
|
+
urls.find((url) => url !== wrappedAuthUrl && !url.includes('tap-qrcode')) || decodedDirectUrl;
|
|
468
|
+
|
|
469
|
+
return enrichAuthPayload({
|
|
470
|
+
auth_url: wrappedAuthUrl,
|
|
471
|
+
wrapped_auth_url: wrappedAuthUrl,
|
|
472
|
+
direct_auth_url: directAuthUrl,
|
|
473
|
+
preferred_auth_url: directAuthUrl || wrappedAuthUrl,
|
|
474
|
+
source: 'bridge_error_message',
|
|
475
|
+
original_error_message: message,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function buildAuthRecoveryTextFromError(message) {
|
|
480
|
+
const authPayload = extractAuthPayloadFromErrorMessage(message);
|
|
481
|
+
if (!authPayload) {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
text: [
|
|
487
|
+
'当前工具调用失败,但桥接层已经识别到这是“缺少 TapTap 授权信息”的场景。',
|
|
488
|
+
'',
|
|
489
|
+
buildAuthGuideText(authPayload),
|
|
490
|
+
'',
|
|
491
|
+
'原始错误摘要:',
|
|
492
|
+
message,
|
|
493
|
+
].join('\n'),
|
|
494
|
+
details: authPayload,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
370
498
|
async function callJsonTool(bridge, name, args = {}) {
|
|
371
499
|
const text = await bridge.callTool(name, args);
|
|
372
500
|
const normalized = normalizeJsonText(text);
|
|
@@ -452,12 +580,22 @@ function registerProxyTool(api, bridge, definition) {
|
|
|
452
580
|
parsed: normalizedParsed,
|
|
453
581
|
});
|
|
454
582
|
} catch (error) {
|
|
583
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
584
|
+
const authRecovery = buildAuthRecoveryTextFromError(message);
|
|
585
|
+
if (authRecovery) {
|
|
586
|
+
return toolResult(authRecovery.text, {
|
|
587
|
+
mcpToolName: definition.mcpToolName,
|
|
588
|
+
auth_recovery: authRecovery.details,
|
|
589
|
+
original_error_message: message,
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
455
593
|
return toolResult(
|
|
456
594
|
JSON.stringify(
|
|
457
595
|
{
|
|
458
596
|
ok: false,
|
|
459
597
|
error: 'PLUGIN_PROXY_ERROR',
|
|
460
|
-
message
|
|
598
|
+
message,
|
|
461
599
|
},
|
|
462
600
|
null,
|
|
463
601
|
2
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "openclaw-dc-plugin",
|
|
3
3
|
"name": "TapTap DC",
|
|
4
4
|
"description": "面向 OpenClaw 的 TapTap DC 插件。请使用 openclaw plugins install 安装,不要直接 npm install;如需重启 Gateway,应先通过飞书或其他通信插件通知用户。",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.16",
|
|
6
6
|
"enabledByDefault": true,
|
|
7
7
|
"skills": ["./skills"],
|
|
8
8
|
"configSchema": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lotaber_wang/openclaw-dc-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "TapTap DC 的 OpenClaw 插件。请使用 openclaw plugins install 安装,不要直接 npm install;如需重启 Gateway,应先通过飞书或其他通信插件通知用户。",
|
|
6
6
|
"main": "index.js",
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
],
|
|
14
14
|
"install": {
|
|
15
15
|
"npmSpec": "@lotaber_wang/openclaw-dc-plugin",
|
|
16
|
-
"preferredCommand": "openclaw plugins install @lotaber_wang/openclaw-dc-plugin@0.1.
|
|
16
|
+
"preferredCommand": "openclaw plugins install @lotaber_wang/openclaw-dc-plugin@0.1.16",
|
|
17
17
|
"doNotUseCommands": [
|
|
18
18
|
"npm install @lotaber_wang/openclaw-dc-plugin",
|
|
19
19
|
"npm install -g @lotaber_wang/openclaw-dc-plugin"
|
|
20
20
|
],
|
|
21
|
-
"wrongInstallHint": "这是 OpenClaw 插件包,不是普通 npm CLI 包。请改用 openclaw plugins install @lotaber_wang/openclaw-dc-plugin@0.1.
|
|
21
|
+
"wrongInstallHint": "这是 OpenClaw 插件包,不是普通 npm CLI 包。请改用 openclaw plugins install @lotaber_wang/openclaw-dc-plugin@0.1.16。",
|
|
22
22
|
"postInstallMessage": "TapTap DC 插件已安装完成。OpenClaw 接下来如需重启 Gateway,应先通过飞书或其他通信插件通知用户;重启完成后即可使用 taptap_dc_* 工具。",
|
|
23
23
|
"restartRequired": true,
|
|
24
24
|
"restartCommand": "openclaw gateway restart",
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
|
|
3
3
|
const PACKAGE_NAME = '@lotaber_wang/openclaw-dc-plugin';
|
|
4
|
-
const PACKAGE_VERSION = '0.1.
|
|
4
|
+
const PACKAGE_VERSION = '0.1.16';
|
|
5
5
|
const PREFERRED_COMMAND = `openclaw plugins install ${PACKAGE_NAME}@${PACKAGE_VERSION}`;
|
|
6
6
|
const DISCOURAGED_COMMANDS = [
|
|
7
7
|
`npm install ${PACKAGE_NAME}`,
|