@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/media/upload.ts
CHANGED
|
@@ -1,43 +1,42 @@
|
|
|
1
|
-
import { readFile, stat } from
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import { fileURLToPath } from
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
5
6
|
|
|
6
7
|
export function isLocalMediaSource(value: string): boolean {
|
|
7
|
-
const trimmed = value.trim()
|
|
8
|
+
const trimmed = value.trim();
|
|
8
9
|
return (
|
|
9
|
-
trimmed.startsWith(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
)
|
|
10
|
+
trimmed.startsWith("/") ||
|
|
11
|
+
trimmed.startsWith("./") ||
|
|
12
|
+
trimmed.startsWith("../") ||
|
|
13
|
+
trimmed.startsWith("~") ||
|
|
14
|
+
trimmed.startsWith("file://")
|
|
15
|
+
);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export function resolveLocalPath(input: string): string {
|
|
18
|
-
if (input.startsWith(
|
|
19
|
-
return fileURLToPath(input)
|
|
19
|
+
if (input.startsWith("file://")) {
|
|
20
|
+
return fileURLToPath(input);
|
|
20
21
|
}
|
|
21
|
-
if (input.startsWith(
|
|
22
|
-
return path.join(os.homedir(), input.slice(1))
|
|
22
|
+
if (input.startsWith("~")) {
|
|
23
|
+
return path.join(os.homedir(), input.slice(1));
|
|
23
24
|
}
|
|
24
|
-
return path.resolve(input)
|
|
25
|
+
return path.resolve(input);
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
function readResponseUrlPath(
|
|
28
29
|
data: Record<string, unknown>,
|
|
29
30
|
pathValue?: string,
|
|
30
31
|
): string | undefined {
|
|
31
|
-
if (!pathValue)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
let current: unknown = data
|
|
32
|
+
if (!pathValue) return undefined;
|
|
33
|
+
const parts = pathValue.split(".").filter(Boolean);
|
|
34
|
+
let current: unknown = data;
|
|
35
35
|
for (const part of parts) {
|
|
36
|
-
if (!current || typeof current !==
|
|
37
|
-
|
|
38
|
-
current = (current as Record<string, unknown>)[part]
|
|
36
|
+
if (!current || typeof current !== "object") return undefined;
|
|
37
|
+
current = (current as Record<string, unknown>)[part];
|
|
39
38
|
}
|
|
40
|
-
return typeof current ===
|
|
39
|
+
return typeof current === "string" ? current : undefined;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
function extractUploadedUrl(
|
|
@@ -45,110 +44,101 @@ function extractUploadedUrl(
|
|
|
45
44
|
responseData: Record<string, unknown>,
|
|
46
45
|
pathValue?: string,
|
|
47
46
|
): string | undefined {
|
|
48
|
-
const fromPath = readResponseUrlPath(responseData, pathValue)
|
|
49
|
-
if (fromPath)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (typeof dataUrl === 'string' && dataUrl.trim())
|
|
63
|
-
return dataUrl
|
|
64
|
-
if (typeof dataMedia === 'string' && dataMedia.trim())
|
|
65
|
-
return dataMedia
|
|
47
|
+
const fromPath = readResponseUrlPath(responseData, pathValue);
|
|
48
|
+
if (fromPath) return fromPath;
|
|
49
|
+
|
|
50
|
+
const direct =
|
|
51
|
+
(responseData.url as string | undefined) ??
|
|
52
|
+
(responseData.mediaUrl as string | undefined);
|
|
53
|
+
if (typeof direct === "string" && direct.trim()) return direct;
|
|
54
|
+
|
|
55
|
+
const dataObj = responseData.data;
|
|
56
|
+
if (dataObj && typeof dataObj === "object") {
|
|
57
|
+
const dataUrl = (dataObj as Record<string, unknown>).url;
|
|
58
|
+
const dataMedia = (dataObj as Record<string, unknown>).mediaUrl;
|
|
59
|
+
if (typeof dataUrl === "string" && dataUrl.trim()) return dataUrl;
|
|
60
|
+
if (typeof dataMedia === "string" && dataMedia.trim()) return dataMedia;
|
|
66
61
|
}
|
|
67
62
|
|
|
68
|
-
const resultObj = responseData.result
|
|
69
|
-
if (resultObj && typeof resultObj ===
|
|
70
|
-
const resultUrl = (resultObj as Record<string, unknown>).url
|
|
71
|
-
const resultMedia = (resultObj as Record<string, unknown>).mediaUrl
|
|
72
|
-
if (typeof resultUrl ===
|
|
73
|
-
|
|
74
|
-
if (typeof resultMedia === 'string' && resultMedia.trim())
|
|
75
|
-
return resultMedia
|
|
63
|
+
const resultObj = responseData.result;
|
|
64
|
+
if (resultObj && typeof resultObj === "object") {
|
|
65
|
+
const resultUrl = (resultObj as Record<string, unknown>).url;
|
|
66
|
+
const resultMedia = (resultObj as Record<string, unknown>).mediaUrl;
|
|
67
|
+
if (typeof resultUrl === "string" && resultUrl.trim()) return resultUrl;
|
|
68
|
+
if (typeof resultMedia === "string" && resultMedia.trim()) return resultMedia;
|
|
76
69
|
}
|
|
77
70
|
|
|
78
71
|
if (responseText.trim() && /^https?:\/\//i.test(responseText.trim())) {
|
|
79
|
-
return responseText.trim()
|
|
72
|
+
return responseText.trim();
|
|
80
73
|
}
|
|
81
|
-
return undefined
|
|
74
|
+
return undefined;
|
|
82
75
|
}
|
|
83
76
|
|
|
84
77
|
export interface UploadLocalMediaParams {
|
|
85
|
-
uploadUrl: string
|
|
86
|
-
filePath: string
|
|
87
|
-
uploadFieldName?: string
|
|
88
|
-
uploadHeaders?: Record<string, string
|
|
89
|
-
uploadFormFields?: Record<string, string | number | boolean
|
|
90
|
-
uploadResponseUrlPath?: string
|
|
91
|
-
requestTimeout: number
|
|
92
|
-
allowInsecureTls?: boolean
|
|
78
|
+
uploadUrl: string;
|
|
79
|
+
filePath: string;
|
|
80
|
+
uploadFieldName?: string;
|
|
81
|
+
uploadHeaders?: Record<string, string>;
|
|
82
|
+
uploadFormFields?: Record<string, string | number | boolean>;
|
|
83
|
+
uploadResponseUrlPath?: string;
|
|
84
|
+
requestTimeout: number;
|
|
85
|
+
allowInsecureTls?: boolean;
|
|
93
86
|
}
|
|
94
87
|
|
|
95
88
|
export async function uploadLocalMedia(params: UploadLocalMediaParams): Promise<string> {
|
|
96
|
-
const stats = await stat(params.filePath)
|
|
89
|
+
const stats = await stat(params.filePath);
|
|
97
90
|
if (!stats.isFile()) {
|
|
98
|
-
throw new Error(`mediaUrl is not a file: ${params.filePath}`)
|
|
91
|
+
throw new Error(`mediaUrl is not a file: ${params.filePath}`);
|
|
99
92
|
}
|
|
100
93
|
|
|
101
|
-
const content = await readFile(params.filePath)
|
|
102
|
-
const form = new FormData()
|
|
103
|
-
const fieldName = params.uploadFieldName?.trim() ||
|
|
104
|
-
const fileName = path.basename(params.filePath)
|
|
105
|
-
form.set(fieldName, new Blob([content], { type:
|
|
94
|
+
const content = await readFile(params.filePath);
|
|
95
|
+
const form = new FormData();
|
|
96
|
+
const fieldName = params.uploadFieldName?.trim() || "file";
|
|
97
|
+
const fileName = path.basename(params.filePath);
|
|
98
|
+
form.set(fieldName, new Blob([content], { type: "application/octet-stream" }), fileName);
|
|
106
99
|
|
|
107
100
|
if (params.uploadFormFields) {
|
|
108
101
|
for (const [key, value] of Object.entries(params.uploadFormFields)) {
|
|
109
|
-
form.set(key, String(value))
|
|
102
|
+
form.set(key, String(value));
|
|
110
103
|
}
|
|
111
104
|
}
|
|
112
105
|
|
|
113
|
-
const controller = new AbortController()
|
|
114
|
-
const timeoutId = setTimeout(() => controller.abort(), params.requestTimeout)
|
|
106
|
+
const controller = new AbortController();
|
|
107
|
+
const timeoutId = setTimeout(() => controller.abort(), params.requestTimeout);
|
|
115
108
|
|
|
116
|
-
let dispatcher: unknown
|
|
117
|
-
let doFetch: any = globalThis.fetch.bind(globalThis)
|
|
109
|
+
let dispatcher: unknown = undefined;
|
|
110
|
+
let doFetch: any = globalThis.fetch.bind(globalThis);
|
|
118
111
|
|
|
119
112
|
if (params.allowInsecureTls) {
|
|
120
113
|
try {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
127
|
-
throw new Error(`allowInsecureTls requires undici. ${message}`)
|
|
114
|
+
dispatcher = new Agent({ connect: { rejectUnauthorized: false } });
|
|
115
|
+
doFetch = undiciFetch as any;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
118
|
+
throw new Error(`allowInsecureTls requires undici. ${message}`);
|
|
128
119
|
}
|
|
129
120
|
}
|
|
130
121
|
|
|
131
122
|
try {
|
|
132
123
|
const response = await doFetch(params.uploadUrl, {
|
|
133
|
-
method:
|
|
124
|
+
method: "POST",
|
|
134
125
|
headers: params.uploadHeaders ?? {},
|
|
135
126
|
body: form,
|
|
136
127
|
signal: controller.signal,
|
|
137
128
|
...(dispatcher ? { dispatcher } : {}),
|
|
138
|
-
})
|
|
129
|
+
});
|
|
139
130
|
|
|
140
|
-
const responseText = await response.text().catch(() =>
|
|
131
|
+
const responseText = await response.text().catch(() => "");
|
|
141
132
|
if (!response.ok) {
|
|
142
|
-
throw new Error(`Upload failed: ${response.status} ${responseText}`)
|
|
133
|
+
throw new Error(`Upload failed: ${response.status} ${responseText}`);
|
|
143
134
|
}
|
|
144
135
|
|
|
145
|
-
let data: Record<string, unknown> = {}
|
|
136
|
+
let data: Record<string, unknown> = {};
|
|
146
137
|
if (responseText) {
|
|
147
138
|
try {
|
|
148
|
-
data = JSON.parse(responseText) as Record<string, unknown
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
data = {}
|
|
139
|
+
data = JSON.parse(responseText) as Record<string, unknown>;
|
|
140
|
+
} catch {
|
|
141
|
+
data = {};
|
|
152
142
|
}
|
|
153
143
|
}
|
|
154
144
|
|
|
@@ -156,13 +146,12 @@ export async function uploadLocalMedia(params: UploadLocalMediaParams): Promise<
|
|
|
156
146
|
responseText,
|
|
157
147
|
data,
|
|
158
148
|
params.uploadResponseUrlPath,
|
|
159
|
-
)
|
|
149
|
+
);
|
|
160
150
|
if (!uploadedUrl) {
|
|
161
|
-
throw new Error(
|
|
151
|
+
throw new Error("Upload response missing file URL");
|
|
162
152
|
}
|
|
163
|
-
return uploadedUrl
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
clearTimeout(timeoutId)
|
|
153
|
+
return uploadedUrl;
|
|
154
|
+
} finally {
|
|
155
|
+
clearTimeout(timeoutId);
|
|
167
156
|
}
|
|
168
157
|
}
|
package/src/outbound/index.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import type { OpenClawConfig } from
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import { isLocalMediaSource, resolveLocalPath, uploadLocalMedia } from
|
|
6
|
-
import { getOpenclawWorkclawLogger } from '../runtime.js'
|
|
1
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { WorkclawSendResult, WorkclawConfig } from "../types.js";
|
|
3
|
+
import { resolveWorkclawAccountWithCache } from "../accounts.js";
|
|
4
|
+
import { resolveAccountByUserIdAndAgentId } from "../accounts.js";
|
|
5
|
+
import { isLocalMediaSource, resolveLocalPath, uploadLocalMedia } from "../media/upload.js";
|
|
7
6
|
import {
|
|
8
|
-
|
|
9
|
-
} from
|
|
7
|
+
sendWorkclawOutboundMessage,
|
|
8
|
+
} from "./workclaw-sender.js";
|
|
9
|
+
import { getWorkclawLogger, setLastOutboundAt } from "../runtime.js";
|
|
10
|
+
import { stat } from "node:fs/promises";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* 根据 target (userId) 和可选的 agentId 查找匹配的账户
|
|
@@ -28,15 +29,15 @@ function findAccountIdByTarget(
|
|
|
28
29
|
return undefined
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
export
|
|
32
|
-
cfg: OpenClawConfig
|
|
33
|
-
to: string
|
|
34
|
-
text: string
|
|
35
|
-
mediaUrl?: string
|
|
36
|
-
replyToMessageId?: string
|
|
37
|
-
accountId?: string
|
|
38
|
-
openConversationId?: string
|
|
39
|
-
agentId?: string | number
|
|
32
|
+
export type SendWorkclawMessageParams = {
|
|
33
|
+
cfg: OpenClawConfig;
|
|
34
|
+
to: string;
|
|
35
|
+
text: string;
|
|
36
|
+
mediaUrl?: string;
|
|
37
|
+
replyToMessageId?: string;
|
|
38
|
+
accountId?: string;
|
|
39
|
+
openConversationId?: string;
|
|
40
|
+
agentId?: string | number;
|
|
40
41
|
/**
|
|
41
42
|
* 消息类型:
|
|
42
43
|
* - "reply": 回复消息(用户触发)
|
|
@@ -49,7 +50,7 @@ export interface SendOpenclawWorkclawMessageParams {
|
|
|
49
50
|
|
|
50
51
|
async function resolveMediaUrl(
|
|
51
52
|
mediaUrl: string | undefined,
|
|
52
|
-
config:
|
|
53
|
+
config: WorkclawConfig,
|
|
53
54
|
): Promise<string | undefined> {
|
|
54
55
|
if (!mediaUrl)
|
|
55
56
|
return undefined
|
|
@@ -63,10 +64,9 @@ async function resolveMediaUrl(
|
|
|
63
64
|
|
|
64
65
|
const filePath = resolveLocalPath(mediaUrl)
|
|
65
66
|
|
|
66
|
-
if (typeof config.mediaMaxMb ===
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const maxBytes = config.mediaMaxMb * 1024 * 1024
|
|
67
|
+
if (typeof config.mediaMaxMb === "number" && config.mediaMaxMb > 0) {
|
|
68
|
+
const stats = await stat(filePath);
|
|
69
|
+
const maxBytes = config.mediaMaxMb * 1024 * 1024;
|
|
70
70
|
if (stats.size > maxBytes) {
|
|
71
71
|
throw new Error(`mediaUrl exceeds limit (${config.mediaMaxMb} MB)`)
|
|
72
72
|
}
|
|
@@ -86,11 +86,11 @@ async function resolveMediaUrl(
|
|
|
86
86
|
})
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
export async function
|
|
90
|
-
params:
|
|
91
|
-
): Promise<
|
|
92
|
-
const { cfg, to, text, mediaUrl, replyToMessageId, accountId, openConversationId, agentId, messageType, msgType, last }
|
|
93
|
-
|
|
89
|
+
export async function sendMessageWorkclaw(
|
|
90
|
+
params: SendWorkclawMessageParams,
|
|
91
|
+
): Promise<WorkclawSendResult> {
|
|
92
|
+
const { cfg, to, text, mediaUrl, replyToMessageId, accountId, openConversationId, agentId, messageType, msgType, last } =
|
|
93
|
+
params;
|
|
94
94
|
|
|
95
95
|
// 如果没有指定 messageType,根据是否有 replyToMessageId 来判断
|
|
96
96
|
// - 有 replyToMessageId: 回复消息
|
|
@@ -106,30 +106,26 @@ export async function sendMessageOpenclawWorkclaw(
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
throw new Error('无法确定账户ID')
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const account = resolveOpenclawWorkclawAccount({ cfg, accountId: resolvedAccountId })
|
|
109
|
+
const account = resolveWorkclawAccountWithCache({ cfg, accountId: resolvedAccountId });
|
|
114
110
|
if (!account.configured) {
|
|
115
|
-
throw new Error(`
|
|
111
|
+
throw new Error(`Workclaw account "${account.accountId}" not configured`);
|
|
116
112
|
}
|
|
117
113
|
|
|
118
114
|
const resolvedMediaUrl = await resolveMediaUrl(mediaUrl, account.config)
|
|
119
115
|
|
|
120
116
|
// 优先使用传入的 openConversationId,其次从 cfg 中直接读取(绕过 account.config 快照问题)
|
|
121
117
|
// 这样可以获取到运行时动态保存的 openConversationId
|
|
122
|
-
|
|
123
|
-
const cfgChannel = (cfg.channels?.['openclaw-workclaw'] as unknown as ChannelWithAccounts) ?? {}
|
|
124
|
-
const cfgAccounts = cfgChannel.accounts ?? {}
|
|
125
|
-
const cfgAccount = cfgAccounts[resolvedAccountId] ?? {}
|
|
126
|
-
const cfgOpenConversationId = cfgAccount?.openConversationId ?? cfgChannel?.openConversationId
|
|
127
|
-
const effectiveOpenConversationId = openConversationId ?? cfgOpenConversationId
|
|
128
|
-
|
|
118
|
+
type ChannelWithAccounts = { accounts?: Record<string, any>; openConversationId?: string };
|
|
119
|
+
const cfgChannel = (cfg.channels?.['openclaw-workclaw'] as unknown as ChannelWithAccounts) ?? {};
|
|
120
|
+
const cfgAccounts = cfgChannel.accounts ?? {};
|
|
121
|
+
const cfgAccount = cfgAccounts[resolvedAccountId] ?? {};
|
|
122
|
+
const cfgOpenConversationId = cfgAccount?.openConversationId ?? cfgChannel?.openConversationId;
|
|
123
|
+
const effectiveOpenConversationId = openConversationId ?? cfgOpenConversationId;
|
|
124
|
+
getWorkclawLogger().info(`sendMessageWorkclaw - resolvedAccountId: ${resolvedAccountId}, openConversationId param: ${openConversationId}, cfgOpenConversationId: ${cfgOpenConversationId}, effectiveOpenConversationId: ${effectiveOpenConversationId}, messageType: ${resolvedMessageType}, replyToMessageId: ${replyToMessageId}`);
|
|
129
125
|
|
|
130
126
|
// 使用 appKey 作为缓存键,让所有账户共享同一个 token
|
|
131
|
-
const tokenCacheKey = account.config.appKey || account.accountId
|
|
132
|
-
const result = await
|
|
127
|
+
const tokenCacheKey = account.config.appKey || account.accountId;
|
|
128
|
+
const result = await sendWorkclawOutboundMessage({
|
|
133
129
|
cacheKey: tokenCacheKey,
|
|
134
130
|
to,
|
|
135
131
|
text,
|
|
@@ -141,17 +137,23 @@ export async function sendMessageOpenclawWorkclaw(
|
|
|
141
137
|
config: account.config,
|
|
142
138
|
messageType: resolvedMessageType,
|
|
143
139
|
last,
|
|
144
|
-
})
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// 更新上次出站时间
|
|
143
|
+
if (resolvedAccountId) {
|
|
144
|
+
setLastOutboundAt(resolvedAccountId, Date.now());
|
|
145
|
+
}
|
|
146
|
+
|
|
145
147
|
return {
|
|
146
148
|
messageId: result.messageId,
|
|
147
149
|
chatId: to,
|
|
148
150
|
}
|
|
149
151
|
}
|
|
150
152
|
|
|
151
|
-
export async function
|
|
152
|
-
cfg: OpenClawConfig
|
|
153
|
-
messageId: string
|
|
154
|
-
accountId?: string
|
|
153
|
+
export async function getMessageWorkclaw(_params: {
|
|
154
|
+
cfg: OpenClawConfig;
|
|
155
|
+
messageId: string;
|
|
156
|
+
accountId?: string;
|
|
155
157
|
}): Promise<null> {
|
|
156
158
|
return null
|
|
157
159
|
}
|
|
@@ -160,10 +162,10 @@ export async function getMessageOpenclawWorkclaw(_params: {
|
|
|
160
162
|
* 发送主动推送消息(定时任务使用)
|
|
161
163
|
* 使用 pushEndpoint 配置的端口
|
|
162
164
|
*/
|
|
163
|
-
export async function
|
|
164
|
-
params: Omit<
|
|
165
|
-
): Promise<
|
|
166
|
-
return
|
|
165
|
+
export async function sendPushMessageWorkclaw(
|
|
166
|
+
params: Omit<SendWorkclawMessageParams, "messageType">,
|
|
167
|
+
): Promise<WorkclawSendResult> {
|
|
168
|
+
return sendMessageWorkclaw({
|
|
167
169
|
...params,
|
|
168
170
|
messageType: 'push',
|
|
169
171
|
})
|
|
@@ -173,10 +175,10 @@ export async function sendPushMessageOpenclawWorkclaw(
|
|
|
173
175
|
* 发送回复消息(用户触发)
|
|
174
176
|
* 使用 replyEndpoint 配置的端口
|
|
175
177
|
*/
|
|
176
|
-
export async function
|
|
177
|
-
params: Omit<
|
|
178
|
-
): Promise<
|
|
179
|
-
return
|
|
178
|
+
export async function sendReplyMessageWorkclaw(
|
|
179
|
+
params: Omit<SendWorkclawMessageParams, "messageType">,
|
|
180
|
+
): Promise<WorkclawSendResult> {
|
|
181
|
+
return sendMessageWorkclaw({
|
|
180
182
|
...params,
|
|
181
183
|
messageType: 'reply',
|
|
182
184
|
})
|