@workclaw/openclaw-workchat 1.0.0 → 1.0.3
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 +210 -210
- package/index.js +49600 -0
- package/index.ts +19 -19
- package/openclaw.plugin.json +25 -25
- package/package.json +64 -63
- package/src/api/api.ts +260 -260
- package/src/api/types.ts +56 -56
- package/src/auth/accounts.ts +290 -290
- package/src/auth/bind-agent.ts +70 -70
- package/src/auth/login-qr-terminal.ts +16 -16
- package/src/auth/login-qr.ts +475 -475
- package/src/auth/pairing.ts +133 -133
- package/src/channel.ts +282 -282
- package/src/compat.ts +75 -75
- package/src/config/config-schema.ts +29 -29
- package/src/messaging/debug-mode.ts +57 -57
- package/src/messaging/error-notice.ts +30 -30
- package/src/messaging/inbound.ts +174 -174
- package/src/messaging/markdown-filter.ts +361 -361
- package/src/messaging/outbound-hooks.ts +84 -84
- package/src/messaging/process-message.ts +530 -530
- package/src/messaging/send.ts +119 -119
- package/src/messaging/slash-commands.ts +105 -105
- package/src/monitor/monitor.ts +261 -261
- package/src/storage/state-dir.ts +11 -11
- package/src/util/logger.ts +147 -147
- package/src/util/random.ts +9 -9
- package/src/util/redact.ts +39 -39
- package/src/vendor.d.ts +10 -10
package/src/messaging/send.ts
CHANGED
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
import { WorkChatMsgType } from "../api/types.js";
|
|
2
|
-
import type { SendMessageReq } from "../api/types.js";
|
|
3
|
-
import {
|
|
4
|
-
normalizeBaseUrl,
|
|
5
|
-
sendWorkChatMessage,
|
|
6
|
-
sendWorkChatReplyMessage,
|
|
7
|
-
} from "../api/api.js";
|
|
8
|
-
import type { WorkChatApiOptions } from "../api/api.js";
|
|
9
|
-
import type { ResolvedWorkChatAccount } from "../auth/accounts.js";
|
|
10
|
-
import { logger } from "../util/logger.js";
|
|
11
|
-
|
|
12
|
-
/** Build the request body for a text message. */
|
|
13
|
-
function buildTextMessageReq(params: {
|
|
14
|
-
agentId: string | number;
|
|
15
|
-
receiveId: string | number;
|
|
16
|
-
text: string;
|
|
17
|
-
conversationId?: string;
|
|
18
|
-
last?: boolean;
|
|
19
|
-
}): SendMessageReq {
|
|
20
|
-
const body: SendMessageReq = {
|
|
21
|
-
agentId: params.agentId,
|
|
22
|
-
receiveId: params.receiveId,
|
|
23
|
-
msgType: WorkChatMsgType.Text,
|
|
24
|
-
content: params.text,
|
|
25
|
-
};
|
|
26
|
-
if (params.conversationId) body.conversationId = params.conversationId;
|
|
27
|
-
if (params.last === true) body.is_last = true;
|
|
28
|
-
return body;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function buildApiOptions(account: ResolvedWorkChatAccount): WorkChatApiOptions {
|
|
32
|
-
return {
|
|
33
|
-
baseUrl: normalizeBaseUrl(account.baseUrl),
|
|
34
|
-
appKey: account.appKey,
|
|
35
|
-
appSecret: account.appSecret,
|
|
36
|
-
timeoutMs: account.requestTimeout,
|
|
37
|
-
allowInsecureTls: account.allowInsecureTls,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Send a reply to a specific inbound message.
|
|
43
|
-
* POST /open-apis/im/v1/messages/{messageId}/reply
|
|
44
|
-
*/
|
|
45
|
-
export async function replyWorkChatMessage(params: {
|
|
46
|
-
account: ResolvedWorkChatAccount;
|
|
47
|
-
to: string;
|
|
48
|
-
text: string;
|
|
49
|
-
replyToMessageId: string;
|
|
50
|
-
conversationId?: string;
|
|
51
|
-
agentId?: string;
|
|
52
|
-
last?: boolean;
|
|
53
|
-
}): Promise<{ messageId: string }> {
|
|
54
|
-
const { account, to, text, replyToMessageId } = params;
|
|
55
|
-
const agentId = params.agentId ?? account.agentId;
|
|
56
|
-
if (!agentId) {
|
|
57
|
-
throw new Error("workchat: agentId is required — complete the binding QR flow first");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const body = buildTextMessageReq({
|
|
61
|
-
agentId,
|
|
62
|
-
receiveId: account.userId ?? to,
|
|
63
|
-
text,
|
|
64
|
-
conversationId: params.conversationId,
|
|
65
|
-
last: params.last,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
const result = await sendWorkChatReplyMessage({
|
|
70
|
-
opts: buildApiOptions(account),
|
|
71
|
-
messageId: replyToMessageId,
|
|
72
|
-
body,
|
|
73
|
-
});
|
|
74
|
-
logger.info(`reply sent to=${to} messageId=${result.msgId}`);
|
|
75
|
-
return { messageId: result.msgId };
|
|
76
|
-
} catch (err) {
|
|
77
|
-
logger.error(`replyWorkChatMessage: failed to=${to} err=${String(err)}`);
|
|
78
|
-
throw err;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Send a proactive message (not tied to an inbound message).
|
|
84
|
-
* POST /open-apis/im/v1/messages
|
|
85
|
-
*/
|
|
86
|
-
export async function pushWorkChatMessage(params: {
|
|
87
|
-
account: ResolvedWorkChatAccount;
|
|
88
|
-
to: string;
|
|
89
|
-
text: string;
|
|
90
|
-
conversationId?: string;
|
|
91
|
-
agentId?: string;
|
|
92
|
-
last?: boolean;
|
|
93
|
-
}): Promise<{ messageId: string }> {
|
|
94
|
-
const { account, to, text } = params;
|
|
95
|
-
const agentId = params.agentId ?? account.agentId;
|
|
96
|
-
if (!agentId) {
|
|
97
|
-
throw new Error("workchat: agentId is required — complete the binding QR flow first");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const body = buildTextMessageReq({
|
|
101
|
-
agentId,
|
|
102
|
-
receiveId: account.userId ?? to,
|
|
103
|
-
text,
|
|
104
|
-
conversationId: params.conversationId,
|
|
105
|
-
last: params.last,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
const result = await sendWorkChatMessage({
|
|
110
|
-
opts: buildApiOptions(account),
|
|
111
|
-
body,
|
|
112
|
-
});
|
|
113
|
-
logger.info(`push sent to=${to} messageId=${result.msgId}`);
|
|
114
|
-
return { messageId: result.msgId };
|
|
115
|
-
} catch (err) {
|
|
116
|
-
logger.error(`pushWorkChatMessage: failed to=${to} err=${String(err)}`);
|
|
117
|
-
throw err;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
1
|
+
import { WorkChatMsgType } from "../api/types.js";
|
|
2
|
+
import type { SendMessageReq } from "../api/types.js";
|
|
3
|
+
import {
|
|
4
|
+
normalizeBaseUrl,
|
|
5
|
+
sendWorkChatMessage,
|
|
6
|
+
sendWorkChatReplyMessage,
|
|
7
|
+
} from "../api/api.js";
|
|
8
|
+
import type { WorkChatApiOptions } from "../api/api.js";
|
|
9
|
+
import type { ResolvedWorkChatAccount } from "../auth/accounts.js";
|
|
10
|
+
import { logger } from "../util/logger.js";
|
|
11
|
+
|
|
12
|
+
/** Build the request body for a text message. */
|
|
13
|
+
function buildTextMessageReq(params: {
|
|
14
|
+
agentId: string | number;
|
|
15
|
+
receiveId: string | number;
|
|
16
|
+
text: string;
|
|
17
|
+
conversationId?: string;
|
|
18
|
+
last?: boolean;
|
|
19
|
+
}): SendMessageReq {
|
|
20
|
+
const body: SendMessageReq = {
|
|
21
|
+
agentId: params.agentId,
|
|
22
|
+
receiveId: params.receiveId,
|
|
23
|
+
msgType: WorkChatMsgType.Text,
|
|
24
|
+
content: params.text,
|
|
25
|
+
};
|
|
26
|
+
if (params.conversationId) body.conversationId = params.conversationId;
|
|
27
|
+
if (params.last === true) body.is_last = true;
|
|
28
|
+
return body;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildApiOptions(account: ResolvedWorkChatAccount): WorkChatApiOptions {
|
|
32
|
+
return {
|
|
33
|
+
baseUrl: normalizeBaseUrl(account.baseUrl),
|
|
34
|
+
appKey: account.appKey,
|
|
35
|
+
appSecret: account.appSecret,
|
|
36
|
+
timeoutMs: account.requestTimeout,
|
|
37
|
+
allowInsecureTls: account.allowInsecureTls,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Send a reply to a specific inbound message.
|
|
43
|
+
* POST /open-apis/im/v1/messages/{messageId}/reply
|
|
44
|
+
*/
|
|
45
|
+
export async function replyWorkChatMessage(params: {
|
|
46
|
+
account: ResolvedWorkChatAccount;
|
|
47
|
+
to: string;
|
|
48
|
+
text: string;
|
|
49
|
+
replyToMessageId: string;
|
|
50
|
+
conversationId?: string;
|
|
51
|
+
agentId?: string;
|
|
52
|
+
last?: boolean;
|
|
53
|
+
}): Promise<{ messageId: string }> {
|
|
54
|
+
const { account, to, text, replyToMessageId } = params;
|
|
55
|
+
const agentId = params.agentId ?? account.agentId;
|
|
56
|
+
if (!agentId) {
|
|
57
|
+
throw new Error("workchat: agentId is required — complete the binding QR flow first");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const body = buildTextMessageReq({
|
|
61
|
+
agentId,
|
|
62
|
+
receiveId: account.userId ?? to,
|
|
63
|
+
text,
|
|
64
|
+
conversationId: params.conversationId,
|
|
65
|
+
last: params.last,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const result = await sendWorkChatReplyMessage({
|
|
70
|
+
opts: buildApiOptions(account),
|
|
71
|
+
messageId: replyToMessageId,
|
|
72
|
+
body,
|
|
73
|
+
});
|
|
74
|
+
logger.info(`reply sent to=${to} messageId=${result.msgId}`);
|
|
75
|
+
return { messageId: result.msgId };
|
|
76
|
+
} catch (err) {
|
|
77
|
+
logger.error(`replyWorkChatMessage: failed to=${to} err=${String(err)}`);
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Send a proactive message (not tied to an inbound message).
|
|
84
|
+
* POST /open-apis/im/v1/messages
|
|
85
|
+
*/
|
|
86
|
+
export async function pushWorkChatMessage(params: {
|
|
87
|
+
account: ResolvedWorkChatAccount;
|
|
88
|
+
to: string;
|
|
89
|
+
text: string;
|
|
90
|
+
conversationId?: string;
|
|
91
|
+
agentId?: string;
|
|
92
|
+
last?: boolean;
|
|
93
|
+
}): Promise<{ messageId: string }> {
|
|
94
|
+
const { account, to, text } = params;
|
|
95
|
+
const agentId = params.agentId ?? account.agentId;
|
|
96
|
+
if (!agentId) {
|
|
97
|
+
throw new Error("workchat: agentId is required — complete the binding QR flow first");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const body = buildTextMessageReq({
|
|
101
|
+
agentId,
|
|
102
|
+
receiveId: account.userId ?? to,
|
|
103
|
+
text,
|
|
104
|
+
conversationId: params.conversationId,
|
|
105
|
+
last: params.last,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const result = await sendWorkChatMessage({
|
|
110
|
+
opts: buildApiOptions(account),
|
|
111
|
+
body,
|
|
112
|
+
});
|
|
113
|
+
logger.info(`push sent to=${to} messageId=${result.msgId}`);
|
|
114
|
+
return { messageId: result.msgId };
|
|
115
|
+
} catch (err) {
|
|
116
|
+
logger.error(`pushWorkChatMessage: failed to=${to} err=${String(err)}`);
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WorkChat 斜杠指令处理模块
|
|
3
|
-
*
|
|
4
|
-
* 支持的指令:
|
|
5
|
-
* - /echo <message> 直接回复消息(不经过 AI),并附带通道耗时统计
|
|
6
|
-
* - /toggle-debug 开关 debug 模式,启用后每条 AI 回复追加全链路耗时
|
|
7
|
-
*/
|
|
8
|
-
import { logger } from "../util/logger.js";
|
|
9
|
-
|
|
10
|
-
import type { ResolvedWorkChatAccount } from "../auth/accounts.js";
|
|
11
|
-
|
|
12
|
-
import { toggleDebugMode, isDebugMode } from "./debug-mode.js";
|
|
13
|
-
import { pushWorkChatMessage } from "./send.js";
|
|
14
|
-
|
|
15
|
-
export interface SlashCommandResult {
|
|
16
|
-
/** 是否是斜杠指令(true 表示已处理,不需要继续走 AI) */
|
|
17
|
-
handled: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface SlashCommandContext {
|
|
21
|
-
account: ResolvedWorkChatAccount;
|
|
22
|
-
to: string;
|
|
23
|
-
agentId?: string;
|
|
24
|
-
log: (msg: string) => void;
|
|
25
|
-
errLog: (m: string) => void;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** 发送回复消息 */
|
|
29
|
-
async function sendReply(ctx: SlashCommandContext, text: string): Promise<void> {
|
|
30
|
-
await pushWorkChatMessage({
|
|
31
|
-
account: ctx.account,
|
|
32
|
-
to: ctx.to,
|
|
33
|
-
text,
|
|
34
|
-
agentId: ctx.agentId,
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** 处理 /echo 指令 */
|
|
39
|
-
async function handleEcho(
|
|
40
|
-
ctx: SlashCommandContext,
|
|
41
|
-
args: string,
|
|
42
|
-
receivedAt: number,
|
|
43
|
-
): Promise<void> {
|
|
44
|
-
const message = args.trim();
|
|
45
|
-
if (message) {
|
|
46
|
-
await sendReply(ctx, message);
|
|
47
|
-
}
|
|
48
|
-
const timing = [
|
|
49
|
-
"⏱ 通道耗时",
|
|
50
|
-
`└ 插件处理: ${Date.now() - receivedAt}ms`,
|
|
51
|
-
].join("\n");
|
|
52
|
-
await sendReply(ctx, timing);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* 尝试处理斜杠指令
|
|
57
|
-
*
|
|
58
|
-
* @returns handled=true 表示该消息已作为指令处理,不需要继续走 AI 管道
|
|
59
|
-
*/
|
|
60
|
-
export async function handleSlashCommand(
|
|
61
|
-
content: string,
|
|
62
|
-
ctx: SlashCommandContext,
|
|
63
|
-
receivedAt: number,
|
|
64
|
-
): Promise<SlashCommandResult> {
|
|
65
|
-
const trimmed = content.trim();
|
|
66
|
-
if (!trimmed.startsWith("/")) {
|
|
67
|
-
return { handled: false };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const spaceIdx = trimmed.indexOf(" ");
|
|
71
|
-
const command = spaceIdx === -1 ? trimmed.toLowerCase() : trimmed.slice(0, spaceIdx).toLowerCase();
|
|
72
|
-
const args = spaceIdx === -1 ? "" : trimmed.slice(spaceIdx + 1);
|
|
73
|
-
|
|
74
|
-
logger.info(`[workchat] Slash command: ${command}, args=${args.slice(0, 50)}`);
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
switch (command) {
|
|
78
|
-
case "/echo":
|
|
79
|
-
await handleEcho(ctx, args, receivedAt);
|
|
80
|
-
return { handled: true };
|
|
81
|
-
case "/toggle-debug": {
|
|
82
|
-
const enabled = toggleDebugMode(ctx.account.accountId);
|
|
83
|
-
await sendReply(
|
|
84
|
-
ctx,
|
|
85
|
-
enabled
|
|
86
|
-
? "Debug 模式已开启"
|
|
87
|
-
: "Debug 模式已关闭",
|
|
88
|
-
);
|
|
89
|
-
return { handled: true };
|
|
90
|
-
}
|
|
91
|
-
default:
|
|
92
|
-
return { handled: false };
|
|
93
|
-
}
|
|
94
|
-
} catch (err) {
|
|
95
|
-
logger.error(`[workchat] Slash command error: ${String(err)}`);
|
|
96
|
-
try {
|
|
97
|
-
await sendReply(ctx, `❌ 指令执行失败: ${String(err).slice(0, 200)}`);
|
|
98
|
-
} catch {
|
|
99
|
-
// 发送错误消息也失败了,只能记日志
|
|
100
|
-
}
|
|
101
|
-
return { handled: true };
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export { isDebugMode };
|
|
1
|
+
/**
|
|
2
|
+
* WorkChat 斜杠指令处理模块
|
|
3
|
+
*
|
|
4
|
+
* 支持的指令:
|
|
5
|
+
* - /echo <message> 直接回复消息(不经过 AI),并附带通道耗时统计
|
|
6
|
+
* - /toggle-debug 开关 debug 模式,启用后每条 AI 回复追加全链路耗时
|
|
7
|
+
*/
|
|
8
|
+
import { logger } from "../util/logger.js";
|
|
9
|
+
|
|
10
|
+
import type { ResolvedWorkChatAccount } from "../auth/accounts.js";
|
|
11
|
+
|
|
12
|
+
import { toggleDebugMode, isDebugMode } from "./debug-mode.js";
|
|
13
|
+
import { pushWorkChatMessage } from "./send.js";
|
|
14
|
+
|
|
15
|
+
export interface SlashCommandResult {
|
|
16
|
+
/** 是否是斜杠指令(true 表示已处理,不需要继续走 AI) */
|
|
17
|
+
handled: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface SlashCommandContext {
|
|
21
|
+
account: ResolvedWorkChatAccount;
|
|
22
|
+
to: string;
|
|
23
|
+
agentId?: string;
|
|
24
|
+
log: (msg: string) => void;
|
|
25
|
+
errLog: (m: string) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 发送回复消息 */
|
|
29
|
+
async function sendReply(ctx: SlashCommandContext, text: string): Promise<void> {
|
|
30
|
+
await pushWorkChatMessage({
|
|
31
|
+
account: ctx.account,
|
|
32
|
+
to: ctx.to,
|
|
33
|
+
text,
|
|
34
|
+
agentId: ctx.agentId,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 处理 /echo 指令 */
|
|
39
|
+
async function handleEcho(
|
|
40
|
+
ctx: SlashCommandContext,
|
|
41
|
+
args: string,
|
|
42
|
+
receivedAt: number,
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
const message = args.trim();
|
|
45
|
+
if (message) {
|
|
46
|
+
await sendReply(ctx, message);
|
|
47
|
+
}
|
|
48
|
+
const timing = [
|
|
49
|
+
"⏱ 通道耗时",
|
|
50
|
+
`└ 插件处理: ${Date.now() - receivedAt}ms`,
|
|
51
|
+
].join("\n");
|
|
52
|
+
await sendReply(ctx, timing);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 尝试处理斜杠指令
|
|
57
|
+
*
|
|
58
|
+
* @returns handled=true 表示该消息已作为指令处理,不需要继续走 AI 管道
|
|
59
|
+
*/
|
|
60
|
+
export async function handleSlashCommand(
|
|
61
|
+
content: string,
|
|
62
|
+
ctx: SlashCommandContext,
|
|
63
|
+
receivedAt: number,
|
|
64
|
+
): Promise<SlashCommandResult> {
|
|
65
|
+
const trimmed = content.trim();
|
|
66
|
+
if (!trimmed.startsWith("/")) {
|
|
67
|
+
return { handled: false };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
71
|
+
const command = spaceIdx === -1 ? trimmed.toLowerCase() : trimmed.slice(0, spaceIdx).toLowerCase();
|
|
72
|
+
const args = spaceIdx === -1 ? "" : trimmed.slice(spaceIdx + 1);
|
|
73
|
+
|
|
74
|
+
logger.info(`[workchat] Slash command: ${command}, args=${args.slice(0, 50)}`);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
switch (command) {
|
|
78
|
+
case "/echo":
|
|
79
|
+
await handleEcho(ctx, args, receivedAt);
|
|
80
|
+
return { handled: true };
|
|
81
|
+
case "/toggle-debug": {
|
|
82
|
+
const enabled = toggleDebugMode(ctx.account.accountId);
|
|
83
|
+
await sendReply(
|
|
84
|
+
ctx,
|
|
85
|
+
enabled
|
|
86
|
+
? "Debug 模式已开启"
|
|
87
|
+
: "Debug 模式已关闭",
|
|
88
|
+
);
|
|
89
|
+
return { handled: true };
|
|
90
|
+
}
|
|
91
|
+
default:
|
|
92
|
+
return { handled: false };
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
logger.error(`[workchat] Slash command error: ${String(err)}`);
|
|
96
|
+
try {
|
|
97
|
+
await sendReply(ctx, `❌ 指令执行失败: ${String(err).slice(0, 200)}`);
|
|
98
|
+
} catch {
|
|
99
|
+
// 发送错误消息也失败了,只能记日志
|
|
100
|
+
}
|
|
101
|
+
return { handled: true };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export { isDebugMode };
|