dlinkclaw-openclaw 2026.8.24-alpha.1
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.en.md +46 -0
- package/README.md +25 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1 -0
- package/dist/package.json +95 -0
- package/dist/src/account.d.ts +53 -0
- package/dist/src/account.js +394 -0
- package/dist/src/channel.d.ts +17 -0
- package/dist/src/channel.js +153 -0
- package/dist/src/client/client-manager.d.ts +4 -0
- package/dist/src/client/client-manager.js +10 -0
- package/dist/src/client/const.d.ts +37 -0
- package/dist/src/client/const.js +155 -0
- package/dist/src/client/crypto.d.ts +47 -0
- package/dist/src/client/crypto.js +260 -0
- package/dist/src/client/dl-client.d.ts +99 -0
- package/dist/src/client/dl-client.js +535 -0
- package/dist/src/client/dl-http.d.ts +27 -0
- package/dist/src/client/dl-http.js +223 -0
- package/dist/src/client/media-handler.d.ts +21 -0
- package/dist/src/client/media-handler.js +196 -0
- package/dist/src/client/message.d.ts +96 -0
- package/dist/src/client/message.js +1204 -0
- package/dist/src/client/timeout.d.ts +20 -0
- package/dist/src/client/timeout.js +36 -0
- package/dist/src/client/types.d.ts +224 -0
- package/dist/src/client/types.js +1 -0
- package/dist/src/monitor.d.ts +17 -0
- package/dist/src/monitor.js +320 -0
- package/dist/src/outbound.d.ts +4 -0
- package/dist/src/outbound.js +83 -0
- package/dist/src/policy.d.ts +42 -0
- package/dist/src/policy.js +205 -0
- package/dist/src/runtime.d.ts +9 -0
- package/dist/src/runtime.js +65 -0
- package/dist/src/services/index.d.ts +41 -0
- package/dist/src/services/index.js +225 -0
- package/dist/src/services/services.d.ts +152 -0
- package/dist/src/services/services.js +1066 -0
- package/dist/src/services/types.d.ts +391 -0
- package/dist/src/services/types.js +5 -0
- package/dist/src/services/ws-client.d.ts +174 -0
- package/dist/src/services/ws-client.js +866 -0
- package/dist/src/utils.d.ts +20 -0
- package/dist/src/utils.js +128 -0
- package/ext_capabilities/workrelay-speak/CLONE_GUIDE.md +129 -0
- package/ext_capabilities/workrelay-speak/HARDWARE.md +84 -0
- package/ext_capabilities/workrelay-speak/QWEN_GUIDE.md +151 -0
- package/ext_capabilities/workrelay-speak/README.md +79 -0
- package/ext_capabilities/workrelay-speak/__pycache__/patch-kokoro-en.cpython-312.pyc +0 -0
- package/ext_capabilities/workrelay-speak/install.sh +280 -0
- package/ext_capabilities/workrelay-speak/install.sh.bak-20260814 +376 -0
- package/ext_capabilities/workrelay-speak/manifest.json +49 -0
- package/ext_capabilities/workrelay-speak/patch-kokoro-en.py +85 -0
- package/ext_capabilities/workrelay-speak/status.sh +90 -0
- package/ext_capabilities/workrelay-speak/workrelay-tts.sh +118 -0
- package/ext_capabilities/workrelay-speak-luxtts/README.md +44 -0
- package/ext_capabilities/workrelay-speak-luxtts/install.sh +282 -0
- package/ext_capabilities/workrelay-speak-luxtts/manifest.json +49 -0
- package/ext_capabilities/workrelay-speak-luxtts/samples/45ea2e2c-0863-4966-a739-8bff8817f4c0.wav +0 -0
- package/ext_capabilities/workrelay-speak-luxtts/samples/800e5af4-cba5-441e-8003-6d36f35409e6.wav +0 -0
- package/ext_capabilities/workrelay-speak-luxtts/samples/85fafe01-d4d6-4a48-890e-9ae113f21905.wav +0 -0
- package/ext_capabilities/workrelay-speak-luxtts/samples/reference_text.txt +4 -0
- package/ext_capabilities/workrelay-speak-luxtts/samples/stone-sample-6.4s.wav +0 -0
- package/ext_capabilities/workrelay-speak-luxtts/status.sh +94 -0
- package/ext_capabilities/workrelay-speak-luxtts/workrelay-tts.sh +98 -0
- package/ext_capabilities/workrelay-transcribe/README.md +76 -0
- package/ext_capabilities/workrelay-transcribe/__pycache__/transcribe.cpython-312.pyc +0 -0
- package/ext_capabilities/workrelay-transcribe/__pycache__/transcribe_server.cpython-312.pyc +0 -0
- package/ext_capabilities/workrelay-transcribe/install.sh +207 -0
- package/ext_capabilities/workrelay-transcribe/manifest.json +42 -0
- package/ext_capabilities/workrelay-transcribe/status.sh +62 -0
- package/ext_capabilities/workrelay-transcribe/transcribe.py +56 -0
- package/ext_capabilities/workrelay-transcribe/transcribe_server.py +116 -0
- package/ext_capabilities/workrelay-transcribe/voicebox-transcribe.sh +88 -0
- package/openclaw.plugin.json +142 -0
- package/package.json +95 -0
package/README.en.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# workrelay-openclaw
|
|
2
|
+
|
|
3
|
+
> ## ⚠️ IMPORTANT NOTICE (2026-08-05)
|
|
4
|
+
>
|
|
5
|
+
> **All current versions are alpha builds for internal testing only — DO NOT INSTALL!**
|
|
6
|
+
>
|
|
7
|
+
> - Incomplete and unstable; may change or break at any time
|
|
8
|
+
> - Please wait for the official release before installing
|
|
9
|
+
> - If already installed, avoid using it in production
|
|
10
|
+
|
|
11
|
+
Internal debugging in progress — for testing only.
|
|
12
|
+
|
|
13
|
+
#### Description
|
|
14
|
+
openclaw channel 中继,实现内外自由连接,让openclaw部署在家或公司里.
|
|
15
|
+
|
|
16
|
+
#### Software Architecture
|
|
17
|
+
Software architecture description
|
|
18
|
+
|
|
19
|
+
#### Installation
|
|
20
|
+
|
|
21
|
+
1. xxxx
|
|
22
|
+
2. xxxx
|
|
23
|
+
3. xxxx
|
|
24
|
+
|
|
25
|
+
#### Instructions
|
|
26
|
+
|
|
27
|
+
1. xxxx
|
|
28
|
+
2. xxxx
|
|
29
|
+
3. xxxx
|
|
30
|
+
|
|
31
|
+
#### Contribution
|
|
32
|
+
|
|
33
|
+
1. Fork the repository
|
|
34
|
+
2. Create Feat_xxx branch
|
|
35
|
+
3. Commit your code
|
|
36
|
+
4. Create Pull Request
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
#### Gitee Feature
|
|
40
|
+
|
|
41
|
+
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
|
42
|
+
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
|
43
|
+
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
|
44
|
+
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
|
45
|
+
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
|
46
|
+
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# workrelay-openclaw
|
|
2
|
+
|
|
3
|
+
> ## ⚠️ 重要提醒(2026-08-05)
|
|
4
|
+
>
|
|
5
|
+
> **当前所有版本均为 alpha 内测版,仅供开发调试,请勿安装使用!**
|
|
6
|
+
>
|
|
7
|
+
> - 功能不完整、不稳定,可能随时变更或破坏性改动
|
|
8
|
+
> - 请等待正式版本发布后再安装
|
|
9
|
+
> - 已安装的用户建议暂勿在生产环境使用
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
### 特别提示
|
|
14
|
+
内部调试中
|
|
15
|
+
|
|
16
|
+
#### 介绍
|
|
17
|
+
openclaw channel 中继,实现内外自由连接,让openclaw部署在家或公司里.
|
|
18
|
+
|
|
19
|
+
### WorkRelay-Openclaw插件安装步骤
|
|
20
|
+
- openclaw plugins install npm:workrelay-openclaw@alpha //@alpha表示内部测试版,正式版不需要
|
|
21
|
+
- openclaw channels add --channel workrelay //添加到openclaw channels列表
|
|
22
|
+
- openclaw configure //启开配置向导
|
|
23
|
+
|
|
24
|
+
#### 使用说明
|
|
25
|
+
- 整理中...
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
+
declare const plugin: {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
|
|
7
|
+
register(api: OpenClawPluginApi): void;
|
|
8
|
+
};
|
|
9
|
+
export default plugin;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{emptyPluginConfigSchema as e}from"openclaw/plugin-sdk";import{channelMeta as n,dlChannel as t,LOG_TAG as r}from"./src/channel.js";import{setDLLogger as i,setDLRuntime as o,isRunStopped as s}from"./src/runtime.js";const l=n;n.isDebug&&(process.env.NODE_TLS_REJECT_UNAUTHORIZED="0",process.env.NODE_ENV="dev");const a={id:l.id,name:l.label,description:l.blurb,configSchema:e(),register(e){o(e.runtime),i(e.logger,r),e.registerChannel({plugin:t}),e.on("before_tool_call",(e,n)=>{const t=l.id;if(n.channelId===t)return e.runId&&s(e.runId)?{block:!0,blockReason:"用户已点击停止,任务终止执行"}:void 0}),e.on("before_prompt_build",(n,t)=>{const r=l.id;if(e.logger.warn(`ctx.channel=${t.channel}, ctx.channelId=${t.channelId}, channelId=${r}`),t.channel===r||t.channelId===r)return{prependSystemContext:["[AGENT CODE OF CONDUCT - ALWAYS FOLLOW]","1. ASK BEFORE GUESSING: If the task is incomplete or has multiple reasonable interpretations, send a one-line confirmation first. Never guess.",'2. STOP ON FAILURE: If the same problem fails 2-3 times in a row, stop and report: "I am stuck: <reason>. Please advise." Never retry endlessly.','3. ALWAYS REPORT COMPLETION: Every task must end with a completion signal. After sending media/files, always follow up with a short text confirmation (e.g. "✅ Sent 3 files: a.png / b.pdf / c.txt"). The client shows a spinner until it receives the final text reply.'].join("\n")}})}};export default a;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dlinkclaw-openclaw",
|
|
3
|
+
"version": "2026.8.24-alpha.1",
|
|
4
|
+
"description": "DLinkClaw OpenClaw channel plugin — 朵联 OpenClaw 接入通道",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"DLinkClaw",
|
|
7
|
+
"openclaw",
|
|
8
|
+
"朵联",
|
|
9
|
+
"钉钉",
|
|
10
|
+
"QQ",
|
|
11
|
+
"企业微信",
|
|
12
|
+
"飞书"
|
|
13
|
+
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc && terser dist/index.js --output dist/index.js --compress --mangle --module",
|
|
19
|
+
"dev": "tsc --watch",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "rock.chen<szygnet@qq.com>",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"main": "./index.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"skills",
|
|
36
|
+
"ext_capabilities",
|
|
37
|
+
"openclaw.plugin.json",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"axios": "^1.15.2",
|
|
42
|
+
"file-type": "^21.3.1",
|
|
43
|
+
"form-data": "^4.0.5",
|
|
44
|
+
"mime-types": "^3.0.2",
|
|
45
|
+
"mqtt": "^5.10.0",
|
|
46
|
+
"qrcode-terminal": "^0.12.0",
|
|
47
|
+
"uuid": "^14.0.2",
|
|
48
|
+
"ws": "^8.20.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/mime-types": "^3.0.1",
|
|
52
|
+
"@types/ws": "^8.18.1",
|
|
53
|
+
"terser": "^5.46.1",
|
|
54
|
+
"typescript": "^5.3.3"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"openclaw": ">=2026.7.1"
|
|
58
|
+
},
|
|
59
|
+
"openclaw": {
|
|
60
|
+
"extensions": [
|
|
61
|
+
"./index.ts"
|
|
62
|
+
],
|
|
63
|
+
"installDependencies": true,
|
|
64
|
+
"channel": {
|
|
65
|
+
"id": "dlink",
|
|
66
|
+
"label": "朵联 DLinkClaw",
|
|
67
|
+
"selectionLabel": "朵联(DLinkClaw)",
|
|
68
|
+
"docsPath": "/channels/DLinkClaw-openclaw",
|
|
69
|
+
"docsLabel": "DLinkClaw-openclaw",
|
|
70
|
+
"blurb": "朵联 DLinkClaw OpenClaw 接入通道, 本地部署连接朵联客户端, 支持企业微信、QQ、钉钉、飞书, 无需公网IP.",
|
|
71
|
+
"aliases": [
|
|
72
|
+
"dlink",
|
|
73
|
+
"DLinkClaw",
|
|
74
|
+
"朵联"
|
|
75
|
+
],
|
|
76
|
+
"order": 90,
|
|
77
|
+
"quickstartAllowFrom": true,
|
|
78
|
+
"isDebug": false,
|
|
79
|
+
"preferSessionLookupForAnnounceTarget": true
|
|
80
|
+
},
|
|
81
|
+
"install": {
|
|
82
|
+
"npmSpec": "DLinkClaw-openclaw",
|
|
83
|
+
"localPath": "extensions/DLinkClaw-openclaw",
|
|
84
|
+
"defaultChoice": "npm"
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"repository": {
|
|
88
|
+
"type": "git",
|
|
89
|
+
"url": "https://github.com/rockychen2016/DLinkClaw-openclaw.git"
|
|
90
|
+
},
|
|
91
|
+
"homepage": "https://github.com/rockychen2016/DLinkClaw-openclaw#readme",
|
|
92
|
+
"bugs": {
|
|
93
|
+
"url": "https://github.com/rockychen2016/DLinkClaw-openclaw/issues"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ChannelAccountSnapshot, ChannelSetupAdapter, ChannelSetupWizard, OpenClawConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import { DLConfig, DLResolvedAccount } from "./client/types.js";
|
|
3
|
+
import { ChannelSetupWizardAdapter } from "openclaw/plugin-sdk/setup";
|
|
4
|
+
import { ChannelStatusAdapter } from "openclaw/plugin-sdk/channel-runtime";
|
|
5
|
+
type ChannelPluginSetupWizard = ChannelSetupWizard | ChannelSetupWizardAdapter;
|
|
6
|
+
/**
|
|
7
|
+
* List all configured account IDs.
|
|
8
|
+
*/
|
|
9
|
+
export declare function listDLConfiguredAccountIds(cfg: OpenClawConfig, channelId: string): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Resolve a complete account.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveDLAccount(params: {
|
|
14
|
+
cfg: OpenClawConfig;
|
|
15
|
+
accountId?: string | null;
|
|
16
|
+
}, channelId: string): DLResolvedAccount;
|
|
17
|
+
export declare function listDuolinkConfiguredAccounts(cfg: OpenClawConfig, channelId: string): Set<DLResolvedAccount>;
|
|
18
|
+
export declare function resolveDefaultDLAccountId(cfg: OpenClawConfig, channelId: string): string;
|
|
19
|
+
export declare function setDLChannelAccountEnabled(params: {
|
|
20
|
+
cfg: OpenClawConfig;
|
|
21
|
+
accountId: string;
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
}, channelId: string): OpenClawConfig;
|
|
24
|
+
export declare function setDLChannelAccountParamsValue(params: {
|
|
25
|
+
cfg: OpenClawConfig;
|
|
26
|
+
accountId?: string;
|
|
27
|
+
param: 'apiKey' | 'secret' | 'baseUrl';
|
|
28
|
+
value: string;
|
|
29
|
+
}, channelId: string): OpenClawConfig;
|
|
30
|
+
export declare function deleteDLAccount({ cfg, accountId }: {
|
|
31
|
+
cfg: OpenClawConfig;
|
|
32
|
+
accountId: string;
|
|
33
|
+
}, channelId: string): OpenClawConfig;
|
|
34
|
+
export declare function checkConfigured(config: DLConfig): boolean;
|
|
35
|
+
export declare function dlSetupWizard(channelId: string): ChannelPluginSetupWizard;
|
|
36
|
+
export declare function dlSetupAdapter(channelId: string): ChannelSetupAdapter;
|
|
37
|
+
export declare function dlAccountStatus(channelId: string): ChannelStatusAdapter<DLResolvedAccount, unknown, unknown>;
|
|
38
|
+
/**
|
|
39
|
+
* 解释Account
|
|
40
|
+
* 打开WebUI -> 频道时 回调
|
|
41
|
+
* @param params
|
|
42
|
+
* @returns
|
|
43
|
+
*/
|
|
44
|
+
export declare function dlDescribeAccount(params: {
|
|
45
|
+
account: DLResolvedAccount;
|
|
46
|
+
cfg: OpenClawConfig;
|
|
47
|
+
}): ChannelAccountSnapshot;
|
|
48
|
+
export declare function dlInspectAccount(params: {
|
|
49
|
+
cfg: OpenClawConfig;
|
|
50
|
+
accountId?: string | null;
|
|
51
|
+
channelId: string;
|
|
52
|
+
}): void;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/core";
|
|
2
|
+
import { createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/channel-status";
|
|
3
|
+
import { getDLClient } from "./client/client-manager.js";
|
|
4
|
+
import { getDLLogger } from "./runtime.js";
|
|
5
|
+
// 2026-08-23 安装默认媒体上限:写入 openclaw.json 供 OpenClaw 核心出站媒体校验读取(默认仅 5MB,导致 ⚠️ Media failed.)
|
|
6
|
+
import { DEFAULT_INSTALL_MEDIA_MAX_MB } from "./client/const.js";
|
|
7
|
+
const log_prefix = "[Account.ts]";
|
|
8
|
+
function resolveDLConfig(cfg, channelId) {
|
|
9
|
+
const channels = cfg.channels;
|
|
10
|
+
const channelConfig = channels?.[channelId];
|
|
11
|
+
if (channelConfig) {
|
|
12
|
+
channelConfig.accountId = channelConfig.accountId || DEFAULT_ACCOUNT_ID;
|
|
13
|
+
return channelConfig;
|
|
14
|
+
}
|
|
15
|
+
// 首次配置时 cfg.channels 或 channels[channelId] 尚未创建,返回安全默认值
|
|
16
|
+
return {
|
|
17
|
+
enabled: false,
|
|
18
|
+
apiKey: '',
|
|
19
|
+
secret: '',
|
|
20
|
+
baseUrl: '',
|
|
21
|
+
accountId: DEFAULT_ACCOUNT_ID,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* List all configured account IDs.
|
|
26
|
+
*/
|
|
27
|
+
export function listDLConfiguredAccountIds(cfg, channelId) {
|
|
28
|
+
const config = resolveDLConfig(cfg, channelId);
|
|
29
|
+
return [config.accountId];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a complete account.
|
|
33
|
+
*/
|
|
34
|
+
export function resolveDLAccount(params, channelId) {
|
|
35
|
+
const accountId = normalizeAccountId(params.accountId);
|
|
36
|
+
const config = resolveDLConfig(params.cfg, channelId);
|
|
37
|
+
return {
|
|
38
|
+
accountId: accountId,
|
|
39
|
+
accountName: config.accountName || 'DEFAULT',
|
|
40
|
+
enabled: config.enabled,
|
|
41
|
+
config: config
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function listDuolinkConfiguredAccounts(cfg, channelId) {
|
|
45
|
+
const config = resolveDLConfig(cfg, channelId);
|
|
46
|
+
const accounts = new Set();
|
|
47
|
+
accounts.add({
|
|
48
|
+
accountId: config.accountId,
|
|
49
|
+
accountName: config.accountName || config.accountId,
|
|
50
|
+
enabled: config.enabled,
|
|
51
|
+
config: config
|
|
52
|
+
});
|
|
53
|
+
return accounts;
|
|
54
|
+
}
|
|
55
|
+
export function resolveDefaultDLAccountId(cfg, channelId) {
|
|
56
|
+
const ids = listDLConfiguredAccountIds(cfg, channelId);
|
|
57
|
+
if (ids.includes(DEFAULT_ACCOUNT_ID)) {
|
|
58
|
+
return DEFAULT_ACCOUNT_ID;
|
|
59
|
+
}
|
|
60
|
+
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
|
61
|
+
}
|
|
62
|
+
export function setDLChannelAccountEnabled(params, channelId) {
|
|
63
|
+
const { cfg, accountId, enabled } = params;
|
|
64
|
+
const account = resolveDLAccount({ cfg, accountId }, channelId);
|
|
65
|
+
account.enabled = enabled;
|
|
66
|
+
const config = account.config;
|
|
67
|
+
return {
|
|
68
|
+
...cfg,
|
|
69
|
+
channels: {
|
|
70
|
+
...cfg.channels,
|
|
71
|
+
[channelId]: {
|
|
72
|
+
...config,
|
|
73
|
+
enabled: enabled,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export function setDLChannelAccountParamsValue(params, channelId) {
|
|
79
|
+
const { cfg, param, value } = params;
|
|
80
|
+
const account = resolveDLAccount({ cfg, accountId: params.accountId }, channelId);
|
|
81
|
+
const config = account.config;
|
|
82
|
+
// 自动启用:当三个必填项都已填写时
|
|
83
|
+
const hasAll = !!((param === 'apiKey' ? value : config.apiKey) &&
|
|
84
|
+
(param === 'secret' ? value : config.secret) &&
|
|
85
|
+
(param === 'baseUrl' ? value : config.baseUrl));
|
|
86
|
+
// 首次配置且三个必填项全齐时,同时创建默认 main agent
|
|
87
|
+
const existingList = cfg.agents?.list;
|
|
88
|
+
const needsDefaultAgent = (!existingList || (Array.isArray(existingList) && existingList.length === 0)) && hasAll;
|
|
89
|
+
const result = {
|
|
90
|
+
...cfg,
|
|
91
|
+
channels: {
|
|
92
|
+
...cfg.channels,
|
|
93
|
+
[channelId]: {
|
|
94
|
+
...config,
|
|
95
|
+
[param]: value,
|
|
96
|
+
enabled: hasAll ? true : config.enabled,
|
|
97
|
+
// 2026-08-23 安装/配置时自动写入默认媒体上限(MB),供 OpenClaw 核心出站媒体校验读取(默认仅 5MB,会导致 ⚠️ Media failed.)
|
|
98
|
+
// 不覆盖用户已设置的值
|
|
99
|
+
mediaMaxMb: config.mediaMaxMb ?? DEFAULT_INSTALL_MEDIA_MAX_MB,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
if (needsDefaultAgent) {
|
|
104
|
+
result.agents = {
|
|
105
|
+
...(cfg.agents ?? {}),
|
|
106
|
+
list: [
|
|
107
|
+
...(Array.isArray(existingList) ? existingList : []),
|
|
108
|
+
{ id: "main", name: "Main Agent" }
|
|
109
|
+
]
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
export function deleteDLAccount({ cfg, accountId }, channelId) {
|
|
115
|
+
const next = { ...cfg };
|
|
116
|
+
const nextChannels = { ...cfg.channels };
|
|
117
|
+
delete nextChannels[channelId];
|
|
118
|
+
if (Object.keys(nextChannels).length > 0) {
|
|
119
|
+
next.channels = nextChannels;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
delete next.channels;
|
|
123
|
+
}
|
|
124
|
+
return next;
|
|
125
|
+
}
|
|
126
|
+
export function checkConfigured(config) {
|
|
127
|
+
const apiKey = config.apiKey;
|
|
128
|
+
const secret = config.secret;
|
|
129
|
+
const baeUrl = config.baseUrl;
|
|
130
|
+
// 三个必填项齐全、且 enabled 明确为 true,才算"已完成配置"
|
|
131
|
+
if (config.enabled !== true)
|
|
132
|
+
return false;
|
|
133
|
+
if (!apiKey || apiKey.length === 0)
|
|
134
|
+
return false;
|
|
135
|
+
if (!secret || secret.length === 0)
|
|
136
|
+
return false;
|
|
137
|
+
if (!baeUrl || baeUrl.length === 0)
|
|
138
|
+
return false;
|
|
139
|
+
if (!baeUrl.startsWith("https://"))
|
|
140
|
+
return false;
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 生成设置向导引导信息(含二维码预留)
|
|
145
|
+
*
|
|
146
|
+
* 发版步骤:
|
|
147
|
+
* 1. npm install qrcode-terminal
|
|
148
|
+
* 2. 取消顶部 import qrcode 的注释
|
|
149
|
+
* 3. 取消底部 qrcode.generate 的注释
|
|
150
|
+
* 4. 替换 registerUrl 为沃联实际注册地址
|
|
151
|
+
*/
|
|
152
|
+
function getDLIntroLines(registerUrl) {
|
|
153
|
+
const lines = [
|
|
154
|
+
"请选择以下任一方式完成配置:",
|
|
155
|
+
"",
|
|
156
|
+
"━━━ 方式一:我要注册新 APIKEY ━━━",
|
|
157
|
+
];
|
|
158
|
+
// TODO: 发版时取消注释以生成终端二维码
|
|
159
|
+
// try {
|
|
160
|
+
// qrcode.generate(registerUrl, { small: true }, (qr: string) => {
|
|
161
|
+
// lines.push(...qr.split('\n'));
|
|
162
|
+
// });
|
|
163
|
+
// } catch (_) {
|
|
164
|
+
// // 二维码生成失败,降级显示纯文本链接
|
|
165
|
+
// }
|
|
166
|
+
lines.push("📱 扫码或在浏览器打开以下链接注册沃联账号:", registerUrl, "", "━━━ 方式二:已有 APIKEY ━━━", "直接按提示输入即可。");
|
|
167
|
+
return lines;
|
|
168
|
+
}
|
|
169
|
+
export function dlSetupWizard(channelId) {
|
|
170
|
+
return {
|
|
171
|
+
channel: channelId,
|
|
172
|
+
status: {
|
|
173
|
+
configuredLabel: "已配置 ✓",
|
|
174
|
+
unconfiguredLabel: "需要 baseUrl, APIKey 和 Secret",
|
|
175
|
+
configuredHint: "已配置",
|
|
176
|
+
unconfiguredHint: "需要设置",
|
|
177
|
+
resolveConfigured: ({ cfg, accountId }) => {
|
|
178
|
+
const account = resolveDLAccount({ cfg, accountId }, channelId);
|
|
179
|
+
const configured = checkConfigured(account.config);
|
|
180
|
+
return configured;
|
|
181
|
+
},
|
|
182
|
+
resolveStatusLines: ({ configured }) => {
|
|
183
|
+
return [`沃联 WorkRelay: ${configured ? "已配置" : "需要 baseUrl, APIKey 和 Secret"}`];
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
introNote: {
|
|
187
|
+
title: "🔧 沃联 WorkRelay 设置向导",
|
|
188
|
+
lines: getDLIntroLines(process.env.DL_REGISTER_URL || "https://WorkRelay.cn/register"),
|
|
189
|
+
shouldShow: ({ cfg, accountId }) => {
|
|
190
|
+
const account = resolveDLAccount({ cfg, accountId }, channelId);
|
|
191
|
+
return !checkConfigured(account.config);
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
credentials: [
|
|
195
|
+
{
|
|
196
|
+
inputKey: "url",
|
|
197
|
+
providerHint: "沃联回调地址 (BaseUrl)",
|
|
198
|
+
credentialLabel: "BaseUrl",
|
|
199
|
+
envPrompt: "使用环境变量中的 DL_BASEURL?",
|
|
200
|
+
keepPrompt: "BaseUrl 已配置,保留当前值?",
|
|
201
|
+
inputPrompt: "请输入沃联回调地址 (BaseUrl):",
|
|
202
|
+
inspect: ({ cfg, accountId }) => {
|
|
203
|
+
const account = resolveDLAccount({ cfg, accountId }, channelId);
|
|
204
|
+
const hasValue = Boolean(account.config.baseUrl?.trim());
|
|
205
|
+
return {
|
|
206
|
+
accountConfigured: hasValue,
|
|
207
|
+
hasConfiguredValue: hasValue,
|
|
208
|
+
resolvedValue: account.config.baseUrl || undefined,
|
|
209
|
+
};
|
|
210
|
+
},
|
|
211
|
+
applySet: ({ cfg, accountId, resolvedValue }) => setDLChannelAccountParamsValue({ cfg, accountId, 'param': 'baseUrl', value: resolvedValue }, channelId)
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
inputKey: "token",
|
|
215
|
+
providerHint: "沃联 API Key",
|
|
216
|
+
credentialLabel: "API Key",
|
|
217
|
+
envPrompt: "使用环境变量中的 DL_APIKEY?",
|
|
218
|
+
keepPrompt: "API Key 已配置,保留当前值?",
|
|
219
|
+
inputPrompt: "请输入沃联 API Key:",
|
|
220
|
+
inspect: ({ cfg, accountId }) => {
|
|
221
|
+
const account = resolveDLAccount({ cfg, accountId }, channelId);
|
|
222
|
+
const hasValue = Boolean(account.config.apiKey?.trim());
|
|
223
|
+
return {
|
|
224
|
+
accountConfigured: hasValue,
|
|
225
|
+
hasConfiguredValue: hasValue,
|
|
226
|
+
resolvedValue: account.config.apiKey || undefined,
|
|
227
|
+
};
|
|
228
|
+
},
|
|
229
|
+
applySet: ({ cfg, accountId, resolvedValue }) => setDLChannelAccountParamsValue({ cfg, accountId, 'param': 'apiKey', value: resolvedValue }, channelId)
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
inputKey: "privateKey",
|
|
233
|
+
providerHint: "沃联签名密钥 (Secret)",
|
|
234
|
+
credentialLabel: "Secret",
|
|
235
|
+
envPrompt: "使用环境变量中的 DL_SECRET?",
|
|
236
|
+
keepPrompt: "Secret 已配置,保留当前值?",
|
|
237
|
+
inputPrompt: "请输入沃联签名密钥 (Secret):",
|
|
238
|
+
inspect: ({ cfg, accountId }) => {
|
|
239
|
+
const account = resolveDLAccount({ cfg, accountId }, channelId);
|
|
240
|
+
const hasValue = Boolean(account.config.secret?.trim());
|
|
241
|
+
return {
|
|
242
|
+
accountConfigured: hasValue,
|
|
243
|
+
hasConfiguredValue: hasValue,
|
|
244
|
+
resolvedValue: account.config.secret || undefined,
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
applySet: ({ cfg, accountId, resolvedValue }) => setDLChannelAccountParamsValue({ cfg, accountId, 'param': 'secret', value: resolvedValue }, channelId)
|
|
248
|
+
},
|
|
249
|
+
]
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
export function dlSetupAdapter(channelId) {
|
|
253
|
+
return {
|
|
254
|
+
resolveAccountId: ({ cfg, accountId }) => {
|
|
255
|
+
const account = resolveDLAccount({ cfg, accountId }, channelId);
|
|
256
|
+
return account.accountId;
|
|
257
|
+
},
|
|
258
|
+
applyAccountConfig: ({ cfg, accountId, input }) => {
|
|
259
|
+
const patch = {};
|
|
260
|
+
if (input.url !== undefined) {
|
|
261
|
+
patch.baseUrl = String(input.url).trim();
|
|
262
|
+
}
|
|
263
|
+
if (input.token !== undefined) {
|
|
264
|
+
patch.apiKey = String(input.token).trim();
|
|
265
|
+
}
|
|
266
|
+
if (input.privateKey !== undefined) {
|
|
267
|
+
patch.secret = String(input.privateKey).trim();
|
|
268
|
+
}
|
|
269
|
+
// 2026-08-23 安装时自动写入默认媒体上限(MB),供 OpenClaw 核心出站媒体校验读取(默认仅 5MB,会导致 ⚠️ Media failed.)
|
|
270
|
+
// 不覆盖用户已设置的值
|
|
271
|
+
const accountForPatch = resolveDLAccount({ cfg, accountId }, channelId);
|
|
272
|
+
if (accountForPatch.config.mediaMaxMb === undefined || accountForPatch.config.mediaMaxMb === null) {
|
|
273
|
+
patch.mediaMaxMb = DEFAULT_INSTALL_MEDIA_MAX_MB;
|
|
274
|
+
}
|
|
275
|
+
// 只要三个必填项全部提供,自动启用
|
|
276
|
+
const account = resolveDLAccount({ cfg, accountId }, channelId);
|
|
277
|
+
const hasAll = !!((patch.baseUrl ?? account.config.baseUrl) &&
|
|
278
|
+
(patch.apiKey ?? account.config.apiKey) &&
|
|
279
|
+
(patch.secret ?? account.config.secret));
|
|
280
|
+
if (hasAll) {
|
|
281
|
+
patch.enabled = true;
|
|
282
|
+
}
|
|
283
|
+
// 首次配置时,如果 agents.list 不存在,自动创建默认 main agent
|
|
284
|
+
const existingList = cfg.agents?.list;
|
|
285
|
+
const needsDefaultAgent = !existingList || (Array.isArray(existingList) && existingList.length === 0);
|
|
286
|
+
const result = {
|
|
287
|
+
...cfg,
|
|
288
|
+
channels: {
|
|
289
|
+
...cfg.channels,
|
|
290
|
+
[channelId]: {
|
|
291
|
+
...account.config,
|
|
292
|
+
...patch
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
if (needsDefaultAgent) {
|
|
297
|
+
result.agents = {
|
|
298
|
+
...(cfg.agents ?? {}),
|
|
299
|
+
list: [
|
|
300
|
+
...(Array.isArray(existingList) ? existingList : []),
|
|
301
|
+
{ id: "main", name: "Main Agent" }
|
|
302
|
+
]
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
return result;
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
;
|
|
310
|
+
export function dlAccountStatus(channelId) {
|
|
311
|
+
return {
|
|
312
|
+
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID, { port: null }),
|
|
313
|
+
collectStatusIssues: (accounts) => accounts.flatMap((entry) => {
|
|
314
|
+
const accountId = String(entry.accountId ?? DEFAULT_ACCOUNT_ID);
|
|
315
|
+
const enabled = entry.enabled !== false;
|
|
316
|
+
const configured = entry.configured === true;
|
|
317
|
+
if (!enabled) {
|
|
318
|
+
return [];
|
|
319
|
+
}
|
|
320
|
+
const issues = [];
|
|
321
|
+
if (!configured) {
|
|
322
|
+
issues.push({
|
|
323
|
+
channel: channelId,
|
|
324
|
+
accountId,
|
|
325
|
+
kind: "config",
|
|
326
|
+
message: "沃联 baseUrl, apiKey, secret 未配置",
|
|
327
|
+
fix: `Run: openclaw channels add ${channelId} --base-url <baseUrl> --apiKey <apiKey> --secret <secret>`,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return issues;
|
|
331
|
+
}),
|
|
332
|
+
buildChannelSummary: ({ snapshot }) => ({
|
|
333
|
+
configured: snapshot.configured ?? false,
|
|
334
|
+
running: snapshot.running ?? false,
|
|
335
|
+
lastStartAt: snapshot.lastStartAt ?? null,
|
|
336
|
+
lastStopAt: snapshot.lastStopAt ?? null,
|
|
337
|
+
lastError: snapshot.lastError ?? null,
|
|
338
|
+
}),
|
|
339
|
+
buildAccountSnapshot: ({ account, runtime }) => {
|
|
340
|
+
const config = account.config;
|
|
341
|
+
const configured = Boolean(config.baseUrl?.trim() &&
|
|
342
|
+
config.apiKey?.trim() &&
|
|
343
|
+
config.secret?.trim());
|
|
344
|
+
return {
|
|
345
|
+
accountId: account.accountId,
|
|
346
|
+
name: account.accountName,
|
|
347
|
+
enabled: account.enabled,
|
|
348
|
+
configured,
|
|
349
|
+
running: runtime?.running ?? false,
|
|
350
|
+
lastStartAt: runtime?.lastStartAt ?? null,
|
|
351
|
+
lastStopAt: runtime?.lastStopAt ?? null,
|
|
352
|
+
lastError: runtime?.lastError ?? null,
|
|
353
|
+
};
|
|
354
|
+
},
|
|
355
|
+
probeAccount: async ({ account }) => {
|
|
356
|
+
const accountId = account.accountId;
|
|
357
|
+
const client = getDLClient(accountId);
|
|
358
|
+
if (!client || !client.isConnected) {
|
|
359
|
+
return { ok: false, error: `DLClient is not connected` };
|
|
360
|
+
}
|
|
361
|
+
return { ok: true };
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* 解释Account
|
|
367
|
+
* 打开WebUI -> 频道时 回调
|
|
368
|
+
* @param params
|
|
369
|
+
* @returns
|
|
370
|
+
*/
|
|
371
|
+
export function dlDescribeAccount(params) {
|
|
372
|
+
const { account } = params;
|
|
373
|
+
const accountId = account.accountId || DEFAULT_ACCOUNT_ID;
|
|
374
|
+
//const logger = getDLLogger();
|
|
375
|
+
//logger.info(`${log_prefix} DescribeAccount account[${accountId}]...`)
|
|
376
|
+
const config = account.config;
|
|
377
|
+
const configured = checkConfigured(config);
|
|
378
|
+
const dlClient = getDLClient(accountId);
|
|
379
|
+
return {
|
|
380
|
+
"accountId": accountId,
|
|
381
|
+
"name": account.accountName || accountId,
|
|
382
|
+
"enabled": account.enabled,
|
|
383
|
+
"configured": configured,
|
|
384
|
+
"connected": dlClient?.isConnected(),
|
|
385
|
+
"lastInboundAt": dlClient?.getLastInboundAt(),
|
|
386
|
+
"busy": true
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
export function dlInspectAccount(params) {
|
|
390
|
+
const { accountId } = params;
|
|
391
|
+
const accId = accountId || DEFAULT_ACCOUNT_ID;
|
|
392
|
+
const logger = getDLLogger();
|
|
393
|
+
logger.info(`${log_prefix}InspectAccount account[${accId}]..`);
|
|
394
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ChannelPlugin } from "openclaw/plugin-sdk";
|
|
2
|
+
import { DLResolvedAccount } from "./client/types.js";
|
|
3
|
+
export declare const channelMeta: {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
selectionLabel: string;
|
|
7
|
+
docsPath: string;
|
|
8
|
+
docsLabel: string;
|
|
9
|
+
blurb: string;
|
|
10
|
+
aliases: string[];
|
|
11
|
+
order: number;
|
|
12
|
+
quickstartAllowFrom: boolean;
|
|
13
|
+
isDebug: boolean;
|
|
14
|
+
preferSessionLookupForAnnounceTarget: boolean;
|
|
15
|
+
};
|
|
16
|
+
export declare const LOG_TAG: string;
|
|
17
|
+
export declare const dlChannel: ChannelPlugin<DLResolvedAccount>;
|