@workclaw/openclaw-workclaw 1.0.16 → 1.0.18
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 +21 -1
- package/index.ts +210 -210
- package/openclaw.plugin.json +1 -0
- package/package.json +12 -5
- package/setup-entry.ts +6 -0
- package/skills/openclaw-workclaw-cron/SKILL.md +45 -28
- package/src/accounts.ts +62 -37
- package/src/api/accounts-api.ts +88 -89
- package/src/api/prompts-api.ts +70 -77
- package/src/api/session-api.ts +99 -108
- package/src/api/skills-api.ts +35 -37
- package/src/api/workspace.ts +27 -29
- package/src/channel.ts +200 -202
- package/src/config-schema.ts +9 -9
- package/src/connection/workclaw-client.ts +554 -567
- package/src/gateway/agent-handlers.ts +392 -426
- package/src/gateway/config-writer.ts +228 -243
- package/src/gateway/message-context.ts +534 -362
- package/src/gateway/message-dispatcher.ts +529 -489
- package/src/gateway/reconnect.ts +217 -113
- package/src/gateway/skills-handler.ts +408 -472
- package/src/gateway/skills-list-handler.ts +9 -9
- package/src/gateway/tools-list-handler.ts +70 -72
- package/src/gateway/workclaw-gateway.ts +328 -486
- package/src/media/upload.ts +83 -94
- package/src/outbound/index.ts +57 -55
- package/src/outbound/workclaw-sender.ts +134 -133
- package/src/runtime.ts +291 -194
- package/src/send.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/api/index.ts +6 -6
- package/src/tools/openclaw-workclaw-cron/src/add/params.ts +20 -19
- package/src/tools/openclaw-workclaw-cron/src/add/sync.ts +2 -2
- package/src/tools/openclaw-workclaw-cron/src/disable/params.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/src/disable/sync.ts +3 -3
- package/src/tools/openclaw-workclaw-cron/src/enable/params.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/src/enable/sync.ts +3 -3
- package/src/tools/openclaw-workclaw-cron/src/notify/sync.ts +2 -2
- package/src/tools/openclaw-workclaw-cron/src/remove/params.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/src/remove/sync.ts +3 -3
- package/src/tools/openclaw-workclaw-cron/src/update/params.ts +195 -197
- package/src/tools/openclaw-workclaw-cron/src/update/sync.ts +4 -4
- package/src/tools/openclaw-workclaw-system/src/get/index.ts +2 -2
- package/src/tools/openclaw-workclaw-system/src/token/index.ts +4 -4
- package/src/types.ts +38 -40
- package/src/utils/content.ts +16 -21
- package/tests/accounts.test.ts +285 -0
- package/tests/message-context.test.ts +313 -0
- package/tests/reconnect.test.ts +257 -0
- package/tests/workclaw-client.test.ts +112 -0
- package/tsconfig.json +8 -5
- package/vitest.config.ts +8 -0
package/src/api/workspace.ts
CHANGED
|
@@ -1,43 +1,41 @@
|
|
|
1
|
-
import type { OpenClawPluginApi } from
|
|
2
|
-
import os from
|
|
3
|
-
import path from
|
|
4
|
-
import process from 'node:process'
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
5
4
|
|
|
6
5
|
export function resolveWorkspaceDir(api: OpenClawPluginApi): string {
|
|
7
|
-
const agents = api.config.agents?.list ?? []
|
|
8
|
-
const defaultAgent
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
const agents = api.config.agents?.list ?? [];
|
|
7
|
+
const defaultAgent =
|
|
8
|
+
agents.find((agent) => agent?.default === true) ??
|
|
9
|
+
agents.find((agent) => String(agent?.id ?? "").toLowerCase() === "main");
|
|
11
10
|
|
|
12
|
-
const workspaceRaw
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
const workspaceRaw =
|
|
12
|
+
(typeof defaultAgent?.workspace === "string" && defaultAgent.workspace.trim()) ||
|
|
13
|
+
(typeof api.config.agents?.defaults?.workspace === "string" &&
|
|
14
|
+
api.config.agents.defaults.workspace.trim());
|
|
16
15
|
|
|
17
16
|
if (workspaceRaw) {
|
|
18
|
-
return api.resolvePath(workspaceRaw)
|
|
17
|
+
return api.resolvePath(workspaceRaw);
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
const profile = process.env.OPENCLAW_PROFILE?.trim()
|
|
22
|
-
const suffix
|
|
23
|
-
|
|
24
|
-
return path.join(os.homedir(),
|
|
20
|
+
const profile = process.env.OPENCLAW_PROFILE?.trim();
|
|
21
|
+
const suffix =
|
|
22
|
+
profile && profile.toLowerCase() !== "default" ? `workspace-${profile}` : "workspace";
|
|
23
|
+
return path.join(os.homedir(), ".openclaw", suffix);
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
export function isDefaultWorkspace(api: OpenClawPluginApi): boolean {
|
|
28
|
-
const agents = api.config.agents?.list ?? []
|
|
29
|
-
const defaultAgent
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
const agents = api.config.agents?.list ?? [];
|
|
28
|
+
const defaultAgent =
|
|
29
|
+
agents.find((agent) => agent?.default === true) ??
|
|
30
|
+
agents.find((agent) => String(agent?.id ?? "").toLowerCase() === "main");
|
|
32
31
|
|
|
33
|
-
const workspaceRaw
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
const workspaceRaw =
|
|
33
|
+
(typeof defaultAgent?.workspace === "string" && defaultAgent.workspace.trim()) ||
|
|
34
|
+
(typeof api.config.agents?.defaults?.workspace === "string" &&
|
|
35
|
+
api.config.agents.defaults.workspace.trim());
|
|
37
36
|
|
|
38
|
-
if (workspaceRaw)
|
|
39
|
-
return false
|
|
37
|
+
if (workspaceRaw) return false;
|
|
40
38
|
|
|
41
|
-
const profile = process.env.OPENCLAW_PROFILE?.trim()
|
|
42
|
-
return !profile || profile.toLowerCase() ===
|
|
39
|
+
const profile = process.env.OPENCLAW_PROFILE?.trim();
|
|
40
|
+
return !profile || profile.toLowerCase() === "default";
|
|
43
41
|
}
|
package/src/channel.ts
CHANGED
|
@@ -1,227 +1,225 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
2
|
+
ChannelGatewayContext,
|
|
3
|
+
ChannelPlugin,
|
|
4
|
+
OpenClawConfig,
|
|
5
|
+
} from "openclaw/plugin-sdk";
|
|
6
|
+
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
|
7
|
+
import { formatPairingApproveHint } from "openclaw/plugin-sdk/core";
|
|
8
|
+
import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
9
|
+
import type { ResolvedWorkclawAccount, WorkclawConfig } from "./types.js";
|
|
10
10
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
function buildChannelConfigSchema(schema: unknown): any {
|
|
23
|
-
return schema
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function formatPairingApproveHint(_channelId: string): string {
|
|
27
|
-
return '请回复验证码完成配对'
|
|
28
|
-
}
|
|
11
|
+
isWorkclawAccountConfigured,
|
|
12
|
+
listWorkclawAccountIds,
|
|
13
|
+
resolveWorkclawAccount,
|
|
14
|
+
} from "./accounts.js";
|
|
15
|
+
import { WorkclawConfigSchema } from "./config-schema.js";
|
|
16
|
+
import { sendMessageWorkclaw } from "./outbound/index.js";
|
|
17
|
+
import { setWorkclawRuntime, getWorkclawRuntime, getWorkclawWsConnection, getLastInboundAt, getLastOutboundAt } from "./runtime.js";
|
|
18
|
+
import { startWorkclawGateway, stopWorkclawGateway } from "./gateway/workclaw-gateway.js";
|
|
19
|
+
import { appendFileSync } from "fs";
|
|
20
|
+
import { join } from "path";
|
|
21
|
+
import { homedir } from "os";
|
|
29
22
|
|
|
30
23
|
const meta = {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
24
|
+
id: "openclaw-workclaw",
|
|
25
|
+
label: "智小途",
|
|
26
|
+
selectionLabel: "智小途",
|
|
27
|
+
docsPath: "/channels/openclaw-workclaw",
|
|
28
|
+
docsLabel: "智小途",
|
|
29
|
+
blurb: "智小途 OpenClaw通道",
|
|
30
|
+
aliases: [],
|
|
31
|
+
order: 90,
|
|
32
|
+
};
|
|
40
33
|
|
|
41
34
|
/**
|
|
42
35
|
* 写入文件日志到磁盘
|
|
43
36
|
* @param message 日志消息
|
|
44
37
|
*/
|
|
45
38
|
function writeFileLog(message: string): void {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
39
|
+
const timestamp = new Date().toISOString();
|
|
40
|
+
const logLine = `[${timestamp}] ${message}\n`;
|
|
41
|
+
const logDir = join(homedir(), ".openclaw", "logs");
|
|
42
|
+
const logFile = join(logDir, "sendtext-outbound.log");
|
|
50
43
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
44
|
+
try {
|
|
45
|
+
appendFileSync(logFile, logLine);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error(`[writeFileLog] Failed to write log: ${err}`);
|
|
48
|
+
}
|
|
57
49
|
}
|
|
58
50
|
|
|
59
|
-
export const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
},
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export const openclawWorkclawPlugin: ChannelPlugin<ResolvedOpenclawWorkclawAccount> = {
|
|
69
|
-
id: 'openclaw-workclaw',
|
|
70
|
-
meta,
|
|
71
|
-
capabilities: {
|
|
72
|
-
chatTypes: ['direct', 'group'],
|
|
73
|
-
media: true,
|
|
74
|
-
reactions: false,
|
|
75
|
-
threads: false,
|
|
76
|
-
polls: false,
|
|
77
|
-
nativeCommands: false,
|
|
78
|
-
blockStreaming: true,
|
|
79
|
-
},
|
|
80
|
-
reload: { configPrefixes: ['channels.openclaw-workclaw'] },
|
|
81
|
-
configSchema: buildChannelConfigSchema(OpenclawWorkclawConfigSchema),
|
|
82
|
-
config: {
|
|
83
|
-
listAccountIds: cfg => listOpenclawWorkclawAccountIds(cfg as OpenClawConfig),
|
|
84
|
-
resolveAccount: (cfg, accountId) =>
|
|
85
|
-
resolveOpenclawWorkclawAccount({ cfg: cfg as OpenClawConfig, accountId }),
|
|
86
|
-
isConfigured: account => isOpenclawWorkclawAccountConfigured(account.config),
|
|
87
|
-
describeAccount: account => ({
|
|
88
|
-
accountId: account.accountId,
|
|
89
|
-
name: account.name,
|
|
90
|
-
enabled: account.enabled,
|
|
91
|
-
configured: isOpenclawWorkclawAccountConfigured(account.config),
|
|
92
|
-
}),
|
|
93
|
-
},
|
|
94
|
-
security: {
|
|
95
|
-
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
|
96
|
-
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID
|
|
97
|
-
const channelConfig = cfg.channels?.['openclaw-workclaw'] as OpenclawWorkclawConfig | undefined
|
|
98
|
-
const useAccountPath = Boolean(channelConfig?.accounts?.[resolvedAccountId])
|
|
99
|
-
const basePath = useAccountPath
|
|
100
|
-
? `channels.openclaw-workclaw.accounts.${resolvedAccountId}.`
|
|
101
|
-
: 'channels.openclaw-workclaw.'
|
|
102
|
-
return {
|
|
103
|
-
policy: account.config.dmPolicy ?? 'pairing',
|
|
104
|
-
allowFrom: account.config.allowFrom ?? [],
|
|
105
|
-
policyPath: `${basePath}dmPolicy`,
|
|
106
|
-
allowFromPath: `${basePath}allowFrom`,
|
|
107
|
-
approveHint: formatPairingApproveHint('openclaw-workclaw'),
|
|
108
|
-
normalizeEntry: raw => raw.replace(/^openclaw-workclaw:/i, ''),
|
|
109
|
-
}
|
|
51
|
+
export const workclawDock = {
|
|
52
|
+
id: "openclaw-workclaw",
|
|
53
|
+
capabilities: {
|
|
54
|
+
chatTypes: ["direct", "group"],
|
|
55
|
+
media: true,
|
|
56
|
+
blockStreaming: true,
|
|
110
57
|
},
|
|
111
|
-
|
|
112
|
-
status: {
|
|
113
|
-
buildAccountSnapshot: async ({ account }) => {
|
|
114
|
-
const { getOpenclawWorkclawWsConnection } = await import('./runtime.js')
|
|
115
|
-
const ws = getOpenclawWorkclawWsConnection(account.accountId)
|
|
116
|
-
const isConnected = ws && ws.readyState === 1 // WebSocket.OPEN = 1
|
|
58
|
+
};
|
|
117
59
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
60
|
+
export const workclawPlugin: ChannelPlugin<ResolvedWorkclawAccount> = {
|
|
61
|
+
id: "openclaw-workclaw",
|
|
62
|
+
meta,
|
|
63
|
+
capabilities: {
|
|
64
|
+
chatTypes: ["direct", "group"],
|
|
65
|
+
media: true,
|
|
66
|
+
reactions: false,
|
|
67
|
+
threads: false,
|
|
68
|
+
polls: false,
|
|
69
|
+
nativeCommands: false,
|
|
70
|
+
blockStreaming: true,
|
|
71
|
+
},
|
|
72
|
+
reload: { configPrefixes: ["channels.openclaw-workclaw"] },
|
|
73
|
+
configSchema: buildChannelConfigSchema(WorkclawConfigSchema),
|
|
74
|
+
config: {
|
|
75
|
+
listAccountIds: (cfg) => listWorkclawAccountIds(cfg as OpenClawConfig),
|
|
76
|
+
resolveAccount: (cfg, accountId) =>
|
|
77
|
+
resolveWorkclawAccount({ cfg: cfg as OpenClawConfig, accountId }),
|
|
78
|
+
isConfigured: (account) => isWorkclawAccountConfigured(account.config),
|
|
79
|
+
describeAccount: (account) => ({
|
|
80
|
+
accountId: account.accountId,
|
|
81
|
+
name: account.name,
|
|
82
|
+
enabled: account.enabled,
|
|
83
|
+
configured: isWorkclawAccountConfigured(account.config),
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
outbound: {
|
|
87
|
+
deliveryMode: "direct",
|
|
88
|
+
chunker: (text: string, _limit: number) => [text],
|
|
89
|
+
textChunkLimit: 4096,
|
|
90
|
+
sendText: async ({ to, text, accountId, cfg, replyToId }) => {
|
|
91
|
+
writeFileLog(`outbound sendText called: replyToId=${replyToId}, accountId=${accountId}, to=${to}, text=${text.substring(0, 100)}`);
|
|
92
|
+
const result = await sendMessageWorkclaw({
|
|
93
|
+
accountId: accountId ?? undefined,
|
|
94
|
+
cfg: cfg as OpenClawConfig,
|
|
95
|
+
to,
|
|
96
|
+
text,
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
channel: "openclaw-workclaw",
|
|
100
|
+
ok: true,
|
|
101
|
+
messageId: result.messageId,
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
sendMedia: async ({ to, text, mediaUrl, accountId, cfg }) => {
|
|
105
|
+
if (!mediaUrl && !text.trim()) {
|
|
106
|
+
throw new Error("mediaUrl is required for outbound media");
|
|
107
|
+
}
|
|
108
|
+
const log = (getWorkclawRuntime() as any).log ?? console.log;
|
|
109
|
+
if (typeof log === "function") {
|
|
110
|
+
log(`outbound sendMedia called for ${to} with text ${text} and mediaUrl ${mediaUrl}`);
|
|
111
|
+
}
|
|
112
|
+
else if (log?.info) {
|
|
113
|
+
log.info(`outbound sendMedia called for ${to} with text ${text} and mediaUrl ${mediaUrl}`);
|
|
114
|
+
}
|
|
115
|
+
const messageText = text.trim() ? text : mediaUrl ?? "";
|
|
116
|
+
const result = await sendMessageWorkclaw({
|
|
117
|
+
accountId: accountId ?? undefined,
|
|
118
|
+
cfg: cfg as OpenClawConfig,
|
|
119
|
+
to,
|
|
120
|
+
text: messageText,
|
|
121
|
+
mediaUrl: mediaUrl ?? undefined,
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
channel: "openclaw-workclaw",
|
|
125
|
+
ok: true,
|
|
126
|
+
messageId: result.messageId,
|
|
127
|
+
};
|
|
128
|
+
},
|
|
127
129
|
},
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
130
|
+
security: {
|
|
131
|
+
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
|
132
|
+
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
133
|
+
const channelConfig = cfg.channels?.["openclaw-workclaw"] as WorkclawConfig | undefined;
|
|
134
|
+
const useAccountPath = Boolean(channelConfig?.accounts?.[resolvedAccountId]);
|
|
135
|
+
const basePath = useAccountPath
|
|
136
|
+
? `channels.openclaw-workclaw.accounts.${resolvedAccountId}.`
|
|
137
|
+
: "channels.openclaw-workclaw.";
|
|
138
|
+
return {
|
|
139
|
+
policy: account.config.dmPolicy ?? "pairing",
|
|
140
|
+
allowFrom: account.config.allowFrom ?? [],
|
|
141
|
+
policyPath: `${basePath}dmPolicy`,
|
|
142
|
+
allowFromPath: `${basePath}allowFrom`,
|
|
143
|
+
approveHint: formatPairingApproveHint("openclaw-workclaw"),
|
|
144
|
+
normalizeEntry: (raw) => raw.replace(/^openclaw-workclaw:/i, ""),
|
|
145
|
+
};
|
|
146
|
+
},
|
|
146
147
|
},
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
messageId: result.messageId,
|
|
171
|
-
}
|
|
148
|
+
status: {
|
|
149
|
+
buildAccountSnapshot: async ({ account, cfg }) => {
|
|
150
|
+
// per-appKey 模式下 WebSocket 用 appKey 存储,per-account 用 accountId
|
|
151
|
+
const wsKey = account.config.wsConnectionStrategy === "per-appKey"
|
|
152
|
+
? account.config.appKey
|
|
153
|
+
: account.accountId;
|
|
154
|
+
const ws = wsKey ? getWorkclawWsConnection(wsKey) : undefined;
|
|
155
|
+
const isConnected = ws && ws.readyState === 1; // WebSocket.OPEN = 1
|
|
156
|
+
const lastInboundAt = account.accountId ? getLastInboundAt(account.accountId) : undefined;
|
|
157
|
+
const lastOutboundAt = account.accountId ? getLastOutboundAt(account.accountId) : undefined;
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
accountId: account.accountId,
|
|
161
|
+
name: account.name,
|
|
162
|
+
enabled: account.enabled,
|
|
163
|
+
configured: account.configured,
|
|
164
|
+
connected: isConnected,
|
|
165
|
+
running: account.enabled && account.configured,
|
|
166
|
+
mode: account.config.wsConnectionStrategy || "per-account",
|
|
167
|
+
lastInboundAt: lastInboundAt ?? null,
|
|
168
|
+
lastOutboundAt: lastOutboundAt ?? null,
|
|
169
|
+
};
|
|
170
|
+
},
|
|
172
171
|
},
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
log?.info?.(`startAccount called for ${accountId}`)
|
|
172
|
+
gateway: {
|
|
173
|
+
startAccount: async (ctx: ChannelGatewayContext<ResolvedWorkclawAccount>) => {
|
|
174
|
+
const { setStatus, getStatus, account, accountId, cfg, log } = ctx;
|
|
175
|
+
log?.info?.(`startAccount called for ${accountId}`);
|
|
178
176
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
177
|
+
await startWorkclawGateway({
|
|
178
|
+
accountId,
|
|
179
|
+
account,
|
|
180
|
+
cfg,
|
|
181
|
+
log,
|
|
182
|
+
});
|
|
185
183
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
184
|
+
setStatus({
|
|
185
|
+
...getStatus(),
|
|
186
|
+
running: true,
|
|
187
|
+
lastStartAt: Date.now(),
|
|
188
|
+
lastError: null,
|
|
189
|
+
});
|
|
190
|
+
log?.info?.(`startAccount completed for ${accountId}, keeping pending until abort`);
|
|
193
191
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
192
|
+
// 保持 pending 状态直到 abortSignal 触发(防止 OpenClaw 3.8 认为启动立即退出)
|
|
193
|
+
await new Promise<void>((resolve) => {
|
|
194
|
+
if (ctx.abortSignal.aborted) {
|
|
195
|
+
resolve();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
ctx.abortSignal.addEventListener("abort", () => resolve(), { once: true });
|
|
199
|
+
});
|
|
202
200
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
201
|
+
// abort 触发后,清理资源
|
|
202
|
+
log?.info?.(`startAccount abort signal received for ${accountId}`);
|
|
203
|
+
const wsStrategy = (account.config as any)?.wsConnectionStrategy || "per-account";
|
|
204
|
+
stopWorkclawGateway(accountId, wsStrategy);
|
|
205
|
+
setStatus({
|
|
206
|
+
...getStatus(),
|
|
207
|
+
running: false,
|
|
208
|
+
lastStopAt: Date.now(),
|
|
209
|
+
});
|
|
210
|
+
},
|
|
211
|
+
stopAccount: async (ctx: ChannelGatewayContext<ResolvedWorkclawAccount>) => {
|
|
212
|
+
const { setStatus, getStatus, accountId, account } = ctx;
|
|
215
213
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
214
|
+
// Stop WorkClaw gateway
|
|
215
|
+
const wsStrategy = (account.config as any)?.wsConnectionStrategy || "per-account";
|
|
216
|
+
stopWorkclawGateway(accountId, wsStrategy);
|
|
219
217
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
218
|
+
setStatus({
|
|
219
|
+
...getStatus(),
|
|
220
|
+
running: false,
|
|
221
|
+
lastStopAt: Date.now(),
|
|
222
|
+
});
|
|
223
|
+
},
|
|
225
224
|
},
|
|
226
|
-
|
|
227
|
-
}
|
|
225
|
+
};
|
package/src/config-schema.ts
CHANGED
|
@@ -2,9 +2,9 @@ import { z } from 'zod'
|
|
|
2
2
|
|
|
3
3
|
export { z }
|
|
4
4
|
|
|
5
|
-
const DmPolicySchema = z.enum([
|
|
6
|
-
const
|
|
7
|
-
const WsConnectionStrategySchema = z.enum([
|
|
5
|
+
const DmPolicySchema = z.enum(["open", "pairing", "allowlist"]);
|
|
6
|
+
const WorkclawConnectionModeSchema = z.enum(["websocket"]);
|
|
7
|
+
const WsConnectionStrategySchema = z.enum(["per-account", "per-appKey"]);
|
|
8
8
|
|
|
9
9
|
const DmConfigSchema = z
|
|
10
10
|
.object({
|
|
@@ -28,7 +28,7 @@ const ChannelHeartbeatVisibilitySchema = z
|
|
|
28
28
|
* Per-account config schema
|
|
29
29
|
* Only fields that make sense to configure per-account.
|
|
30
30
|
*/
|
|
31
|
-
export const
|
|
31
|
+
export const workclawAccountConfigSchema = z
|
|
32
32
|
.object({
|
|
33
33
|
enabled: z.boolean().optional(),
|
|
34
34
|
name: z.string().optional(),
|
|
@@ -54,11 +54,11 @@ export const openclawWorkclawAccountConfigSchema = z
|
|
|
54
54
|
/**
|
|
55
55
|
* Top-level plugin config schema
|
|
56
56
|
*/
|
|
57
|
-
export const
|
|
57
|
+
export const WorkclawConfigSchema = z
|
|
58
58
|
.object({
|
|
59
59
|
enabled: z.boolean().optional(),
|
|
60
|
-
connectionMode:
|
|
61
|
-
wsConnectionStrategy: WsConnectionStrategySchema.optional().default(
|
|
60
|
+
connectionMode: WorkclawConnectionModeSchema.optional().default("websocket"),
|
|
61
|
+
wsConnectionStrategy: WsConnectionStrategySchema.optional().default("per-account"),
|
|
62
62
|
baseUrl: z.string().url().optional(),
|
|
63
63
|
websocketUrl: z.string().url().optional(),
|
|
64
64
|
appKey: z.string().optional(),
|
|
@@ -89,7 +89,7 @@ export const OpenclawWorkclawConfigSchema = z
|
|
|
89
89
|
mediaMaxMb: z.number().positive().optional(),
|
|
90
90
|
heartbeat: ChannelHeartbeatVisibilitySchema,
|
|
91
91
|
renderMode: RenderModeSchema,
|
|
92
|
-
accounts: z.record(z.string(),
|
|
92
|
+
accounts: z.record(z.string(), workclawAccountConfigSchema.optional()).optional(),
|
|
93
93
|
})
|
|
94
94
|
.strict()
|
|
95
95
|
.superRefine((value, ctx) => {
|
|
@@ -107,4 +107,4 @@ export const OpenclawWorkclawConfigSchema = z
|
|
|
107
107
|
}
|
|
108
108
|
})
|
|
109
109
|
|
|
110
|
-
export const
|
|
110
|
+
export const workclawConfigSchema = WorkclawConfigSchema;
|