@workclaw/openclaw-workchat 1.0.0 → 1.0.4
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 +49568 -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 +279 -290
- package/src/auth/bind-agent.ts +52 -70
- package/src/auth/login-qr-terminal.ts +16 -16
- package/src/auth/login-qr.ts +477 -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/inbound.ts
CHANGED
|
@@ -1,174 +1,174 @@
|
|
|
1
|
-
import type { AgentMessagePayload, AgentEventPayload } from "../api/types.js";
|
|
2
|
-
import { generateId } from "../util/random.js";
|
|
3
|
-
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
// Frame parsing
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
|
|
8
|
-
export type WorkChatFrameType =
|
|
9
|
-
| "agent_message"
|
|
10
|
-
| "ping"
|
|
11
|
-
| "disconnect"
|
|
12
|
-
| "agent_created"
|
|
13
|
-
| "agent_updated"
|
|
14
|
-
| "agent_deleted"
|
|
15
|
-
| "init_agent"
|
|
16
|
-
| "ignored";
|
|
17
|
-
|
|
18
|
-
export type ParseWorkChatFrameResult = {
|
|
19
|
-
type: WorkChatFrameType;
|
|
20
|
-
message?: {
|
|
21
|
-
text: string;
|
|
22
|
-
userId: string;
|
|
23
|
-
openConversationId: string;
|
|
24
|
-
messageId: string;
|
|
25
|
-
agentId?: string;
|
|
26
|
-
};
|
|
27
|
-
pongData?: unknown;
|
|
28
|
-
eventData?: AgentEventPayload;
|
|
29
|
-
/** Raw event/topic name for ignored frames (observability). */
|
|
30
|
-
ignoredKind?: string;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
function parseMaybeJson(value: unknown): unknown {
|
|
34
|
-
if (typeof value !== "string") return value;
|
|
35
|
-
try {
|
|
36
|
-
return JSON.parse(value);
|
|
37
|
-
} catch {
|
|
38
|
-
return value;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Parse one raw WebSocket frame into a typed result. */
|
|
43
|
-
export function parseWorkChatFrame(data: string): ParseWorkChatFrameResult | null {
|
|
44
|
-
let input: unknown;
|
|
45
|
-
try {
|
|
46
|
-
input = data ? JSON.parse(data) : {};
|
|
47
|
-
} catch {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (typeof input === "string" && input.toLowerCase() === "ping") {
|
|
52
|
-
return { type: "ping", pongData: input };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const envelope = (input && typeof input === "object" ? input : { data: input }) as Record<string, any>;
|
|
56
|
-
const type = String(envelope?.type || "").trim().toUpperCase();
|
|
57
|
-
const topicLower = String(envelope?.metadata?.topic || "").trim().toLowerCase();
|
|
58
|
-
|
|
59
|
-
if (type === "SYSTEM") {
|
|
60
|
-
if (topicLower === "ping") return { type: "ping", pongData: envelope?.data };
|
|
61
|
-
if (topicLower === "disconnect") return { type: "disconnect" };
|
|
62
|
-
return { type: "ignored", ignoredKind: `SYSTEM/${topicLower}` };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (type === "EVENT") {
|
|
66
|
-
const eventType = String(envelope?.metadata?.event || "").trim().toUpperCase();
|
|
67
|
-
let eventData = parseMaybeJson(envelope?.data) as AgentEventPayload | undefined;
|
|
68
|
-
// The platform also sends INIT_AGENT / AGENT_* events whose data is a bare
|
|
69
|
-
// JSON-encoded string/number (the agent id itself, zhixiaotu shape) —
|
|
70
|
-
// normalize to { id } so downstream id extraction works either way.
|
|
71
|
-
if ((eventType === "INIT_AGENT" || eventType.startsWith("AGENT_"))
|
|
72
|
-
&& eventData !== null && eventData !== undefined
|
|
73
|
-
&& typeof eventData !== "object") {
|
|
74
|
-
eventData = { id: String(eventData) };
|
|
75
|
-
}
|
|
76
|
-
if (eventType === "AGENT_CREATED") return { type: "agent_created", eventData };
|
|
77
|
-
if (eventType === "AGENT_UPDATED") return { type: "agent_updated", eventData };
|
|
78
|
-
if (eventType === "AGENT_DELETED") return { type: "agent_deleted", eventData };
|
|
79
|
-
if (eventType === "INIT_AGENT") return { type: "init_agent", eventData };
|
|
80
|
-
return { type: "ignored", ignoredKind: `EVENT/${eventType}` };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (type === "CALLBACK" && String(envelope?.metadata?.topic || "").trim().toUpperCase() === "AGENT_MESSAGE") {
|
|
84
|
-
const payload = (parseMaybeJson(envelope?.data) ?? {}) as AgentMessagePayload;
|
|
85
|
-
const message = payload?.message;
|
|
86
|
-
const text = typeof message === "string"
|
|
87
|
-
? message
|
|
88
|
-
: typeof message?.content === "string"
|
|
89
|
-
? message.content
|
|
90
|
-
: "";
|
|
91
|
-
return {
|
|
92
|
-
type: "agent_message",
|
|
93
|
-
message: {
|
|
94
|
-
text,
|
|
95
|
-
userId: String(payload?.userId ?? ""),
|
|
96
|
-
openConversationId: String(payload?.conversationId ?? ""),
|
|
97
|
-
messageId: String(payload?.msgId ?? ""),
|
|
98
|
-
...(payload?.agentId !== undefined ? { agentId: String(payload.agentId) } : {}),
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return { type: "ignored", ignoredKind: `${type}/${topicLower || "(no topic)"}` };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Build the pong response frame for a SYSTEM ping. */
|
|
107
|
-
export function buildPongFrame(pongData: unknown): string {
|
|
108
|
-
return JSON.stringify({
|
|
109
|
-
code: 200,
|
|
110
|
-
message: "pong",
|
|
111
|
-
metadata: { contentType: "application/json" },
|
|
112
|
-
data: JSON.stringify(pongData ?? {}),
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ---------------------------------------------------------------------------
|
|
117
|
-
// Inbound context
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
|
-
|
|
120
|
-
/** Inbound context passed to the OpenClaw core pipeline (matches MsgContext shape). */
|
|
121
|
-
export type WorkChatMsgContext = {
|
|
122
|
-
Body: string;
|
|
123
|
-
From: string;
|
|
124
|
-
To: string;
|
|
125
|
-
AccountId: string;
|
|
126
|
-
OriginatingChannel: "openclaw-workchat";
|
|
127
|
-
OriginatingTo: string;
|
|
128
|
-
MessageSid: string;
|
|
129
|
-
Timestamp?: number;
|
|
130
|
-
Provider: "openclaw-workchat";
|
|
131
|
-
ChatType: "direct";
|
|
132
|
-
/** Set after resolveAgentRoute so dispatchReplyFromConfig uses the correct session. */
|
|
133
|
-
SessionKey?: string;
|
|
134
|
-
/** Raw message body for framework command authorization. */
|
|
135
|
-
CommandBody?: string;
|
|
136
|
-
/** Whether the sender is authorized to execute slash commands. */
|
|
137
|
-
CommandAuthorized?: boolean;
|
|
138
|
-
/** Conversation id echoed in outbound replies. */
|
|
139
|
-
ConversationId?: string;
|
|
140
|
-
/** Cloud agent id this message was addressed to. */
|
|
141
|
-
AgentId?: string;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
function generateMessageSid(): string {
|
|
145
|
-
return generateId("openclaw-workchat");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/** Convert an AGENT_MESSAGE payload into the inbound MsgContext for the core pipeline. */
|
|
149
|
-
export function workChatMessageToMsgContext(
|
|
150
|
-
message: NonNullable<ParseWorkChatFrameResult["message"]>,
|
|
151
|
-
accountId: string,
|
|
152
|
-
): WorkChatMsgContext {
|
|
153
|
-
const fromUserId = message.userId ?? "";
|
|
154
|
-
const ctx: WorkChatMsgContext = {
|
|
155
|
-
Body: message.text ?? "",
|
|
156
|
-
From: fromUserId,
|
|
157
|
-
To: fromUserId,
|
|
158
|
-
AccountId: accountId,
|
|
159
|
-
OriginatingChannel: "openclaw-workchat",
|
|
160
|
-
OriginatingTo: fromUserId,
|
|
161
|
-
MessageSid: message.messageId || generateMessageSid(),
|
|
162
|
-
Timestamp: Date.now(),
|
|
163
|
-
Provider: "openclaw-workchat",
|
|
164
|
-
ChatType: "direct",
|
|
165
|
-
CommandBody: message.text ?? "",
|
|
166
|
-
};
|
|
167
|
-
if (message.openConversationId) {
|
|
168
|
-
ctx.ConversationId = message.openConversationId;
|
|
169
|
-
}
|
|
170
|
-
if (message.agentId) {
|
|
171
|
-
ctx.AgentId = message.agentId;
|
|
172
|
-
}
|
|
173
|
-
return ctx;
|
|
174
|
-
}
|
|
1
|
+
import type { AgentMessagePayload, AgentEventPayload } from "../api/types.js";
|
|
2
|
+
import { generateId } from "../util/random.js";
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Frame parsing
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
export type WorkChatFrameType =
|
|
9
|
+
| "agent_message"
|
|
10
|
+
| "ping"
|
|
11
|
+
| "disconnect"
|
|
12
|
+
| "agent_created"
|
|
13
|
+
| "agent_updated"
|
|
14
|
+
| "agent_deleted"
|
|
15
|
+
| "init_agent"
|
|
16
|
+
| "ignored";
|
|
17
|
+
|
|
18
|
+
export type ParseWorkChatFrameResult = {
|
|
19
|
+
type: WorkChatFrameType;
|
|
20
|
+
message?: {
|
|
21
|
+
text: string;
|
|
22
|
+
userId: string;
|
|
23
|
+
openConversationId: string;
|
|
24
|
+
messageId: string;
|
|
25
|
+
agentId?: string;
|
|
26
|
+
};
|
|
27
|
+
pongData?: unknown;
|
|
28
|
+
eventData?: AgentEventPayload;
|
|
29
|
+
/** Raw event/topic name for ignored frames (observability). */
|
|
30
|
+
ignoredKind?: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function parseMaybeJson(value: unknown): unknown {
|
|
34
|
+
if (typeof value !== "string") return value;
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(value);
|
|
37
|
+
} catch {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Parse one raw WebSocket frame into a typed result. */
|
|
43
|
+
export function parseWorkChatFrame(data: string): ParseWorkChatFrameResult | null {
|
|
44
|
+
let input: unknown;
|
|
45
|
+
try {
|
|
46
|
+
input = data ? JSON.parse(data) : {};
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (typeof input === "string" && input.toLowerCase() === "ping") {
|
|
52
|
+
return { type: "ping", pongData: input };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const envelope = (input && typeof input === "object" ? input : { data: input }) as Record<string, any>;
|
|
56
|
+
const type = String(envelope?.type || "").trim().toUpperCase();
|
|
57
|
+
const topicLower = String(envelope?.metadata?.topic || "").trim().toLowerCase();
|
|
58
|
+
|
|
59
|
+
if (type === "SYSTEM") {
|
|
60
|
+
if (topicLower === "ping") return { type: "ping", pongData: envelope?.data };
|
|
61
|
+
if (topicLower === "disconnect") return { type: "disconnect" };
|
|
62
|
+
return { type: "ignored", ignoredKind: `SYSTEM/${topicLower}` };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (type === "EVENT") {
|
|
66
|
+
const eventType = String(envelope?.metadata?.event || "").trim().toUpperCase();
|
|
67
|
+
let eventData = parseMaybeJson(envelope?.data) as AgentEventPayload | undefined;
|
|
68
|
+
// The platform also sends INIT_AGENT / AGENT_* events whose data is a bare
|
|
69
|
+
// JSON-encoded string/number (the agent id itself, zhixiaotu shape) —
|
|
70
|
+
// normalize to { id } so downstream id extraction works either way.
|
|
71
|
+
if ((eventType === "INIT_AGENT" || eventType.startsWith("AGENT_"))
|
|
72
|
+
&& eventData !== null && eventData !== undefined
|
|
73
|
+
&& typeof eventData !== "object") {
|
|
74
|
+
eventData = { id: String(eventData) };
|
|
75
|
+
}
|
|
76
|
+
if (eventType === "AGENT_CREATED") return { type: "agent_created", eventData };
|
|
77
|
+
if (eventType === "AGENT_UPDATED") return { type: "agent_updated", eventData };
|
|
78
|
+
if (eventType === "AGENT_DELETED") return { type: "agent_deleted", eventData };
|
|
79
|
+
if (eventType === "INIT_AGENT") return { type: "init_agent", eventData };
|
|
80
|
+
return { type: "ignored", ignoredKind: `EVENT/${eventType}` };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (type === "CALLBACK" && String(envelope?.metadata?.topic || "").trim().toUpperCase() === "AGENT_MESSAGE") {
|
|
84
|
+
const payload = (parseMaybeJson(envelope?.data) ?? {}) as AgentMessagePayload;
|
|
85
|
+
const message = payload?.message;
|
|
86
|
+
const text = typeof message === "string"
|
|
87
|
+
? message
|
|
88
|
+
: typeof message?.content === "string"
|
|
89
|
+
? message.content
|
|
90
|
+
: "";
|
|
91
|
+
return {
|
|
92
|
+
type: "agent_message",
|
|
93
|
+
message: {
|
|
94
|
+
text,
|
|
95
|
+
userId: String(payload?.userId ?? ""),
|
|
96
|
+
openConversationId: String(payload?.conversationId ?? ""),
|
|
97
|
+
messageId: String(payload?.msgId ?? ""),
|
|
98
|
+
...(payload?.agentId !== undefined ? { agentId: String(payload.agentId) } : {}),
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { type: "ignored", ignoredKind: `${type}/${topicLower || "(no topic)"}` };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Build the pong response frame for a SYSTEM ping. */
|
|
107
|
+
export function buildPongFrame(pongData: unknown): string {
|
|
108
|
+
return JSON.stringify({
|
|
109
|
+
code: 200,
|
|
110
|
+
message: "pong",
|
|
111
|
+
metadata: { contentType: "application/json" },
|
|
112
|
+
data: JSON.stringify(pongData ?? {}),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Inbound context
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
/** Inbound context passed to the OpenClaw core pipeline (matches MsgContext shape). */
|
|
121
|
+
export type WorkChatMsgContext = {
|
|
122
|
+
Body: string;
|
|
123
|
+
From: string;
|
|
124
|
+
To: string;
|
|
125
|
+
AccountId: string;
|
|
126
|
+
OriginatingChannel: "openclaw-workchat";
|
|
127
|
+
OriginatingTo: string;
|
|
128
|
+
MessageSid: string;
|
|
129
|
+
Timestamp?: number;
|
|
130
|
+
Provider: "openclaw-workchat";
|
|
131
|
+
ChatType: "direct";
|
|
132
|
+
/** Set after resolveAgentRoute so dispatchReplyFromConfig uses the correct session. */
|
|
133
|
+
SessionKey?: string;
|
|
134
|
+
/** Raw message body for framework command authorization. */
|
|
135
|
+
CommandBody?: string;
|
|
136
|
+
/** Whether the sender is authorized to execute slash commands. */
|
|
137
|
+
CommandAuthorized?: boolean;
|
|
138
|
+
/** Conversation id echoed in outbound replies. */
|
|
139
|
+
ConversationId?: string;
|
|
140
|
+
/** Cloud agent id this message was addressed to. */
|
|
141
|
+
AgentId?: string;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
function generateMessageSid(): string {
|
|
145
|
+
return generateId("openclaw-workchat");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Convert an AGENT_MESSAGE payload into the inbound MsgContext for the core pipeline. */
|
|
149
|
+
export function workChatMessageToMsgContext(
|
|
150
|
+
message: NonNullable<ParseWorkChatFrameResult["message"]>,
|
|
151
|
+
accountId: string,
|
|
152
|
+
): WorkChatMsgContext {
|
|
153
|
+
const fromUserId = message.userId ?? "";
|
|
154
|
+
const ctx: WorkChatMsgContext = {
|
|
155
|
+
Body: message.text ?? "",
|
|
156
|
+
From: fromUserId,
|
|
157
|
+
To: fromUserId,
|
|
158
|
+
AccountId: accountId,
|
|
159
|
+
OriginatingChannel: "openclaw-workchat",
|
|
160
|
+
OriginatingTo: fromUserId,
|
|
161
|
+
MessageSid: message.messageId || generateMessageSid(),
|
|
162
|
+
Timestamp: Date.now(),
|
|
163
|
+
Provider: "openclaw-workchat",
|
|
164
|
+
ChatType: "direct",
|
|
165
|
+
CommandBody: message.text ?? "",
|
|
166
|
+
};
|
|
167
|
+
if (message.openConversationId) {
|
|
168
|
+
ctx.ConversationId = message.openConversationId;
|
|
169
|
+
}
|
|
170
|
+
if (message.agentId) {
|
|
171
|
+
ctx.AgentId = message.agentId;
|
|
172
|
+
}
|
|
173
|
+
return ctx;
|
|
174
|
+
}
|