@chrysb/alphaclaw 0.8.3-beta.3 → 0.8.3-beta.5
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/lib/public/css/chat.css +8 -4
- package/lib/public/css/explorer.css +65 -22
- package/lib/public/dist/app.bundle.js +1830 -1801
- package/lib/public/js/app.js +0 -7
- package/lib/public/js/components/cron-tab/cron-job-settings-card.js +9 -2
- package/lib/public/js/components/google/gmail-setup-wizard.js +6 -2
- package/lib/public/js/components/icons.js +13 -0
- package/lib/public/js/components/routes/chat-route.js +57 -29
- package/lib/public/js/components/session-select-field.js +9 -2
- package/lib/public/js/components/sidebar.js +128 -25
- package/lib/public/js/components/webhooks/webhook-detail/index.js +5 -2
- package/lib/public/js/lib/session-keys.js +74 -0
- package/lib/public/js/lib/storage-keys.js +2 -1
- package/lib/server/chat-ws.js +93 -7
- package/lib/server/routes/system.js +36 -85
- package/package.json +1 -1
package/lib/server/chat-ws.js
CHANGED
|
@@ -354,6 +354,10 @@ const createChatWsService = ({
|
|
|
354
354
|
let gatewayConnectPromise = null;
|
|
355
355
|
const pendingGatewayRequests = new Map();
|
|
356
356
|
const runTargets = new Map();
|
|
357
|
+
/** While `chat.send` is in flight, agent events can arrive before we have `runId` + runTargets — match by session. */
|
|
358
|
+
const pendingSessionBySessionKey = new Map();
|
|
359
|
+
/** Agent events keyed by runId when the event references a run not yet in runTargets (race with chat.send response). */
|
|
360
|
+
const pendingAgentEventsByRunId = new Map();
|
|
357
361
|
const browserRuns = new WeakMap();
|
|
358
362
|
|
|
359
363
|
const sendJson = (ws, payload = {}) => {
|
|
@@ -382,6 +386,9 @@ const createChatWsService = ({
|
|
|
382
386
|
};
|
|
383
387
|
|
|
384
388
|
const clearRunTargetsForBrowser = (ws) => {
|
|
389
|
+
for (const [sk, pending] of pendingSessionBySessionKey.entries()) {
|
|
390
|
+
if (pending.ws === ws) pendingSessionBySessionKey.delete(sk);
|
|
391
|
+
}
|
|
385
392
|
const runs = browserRuns.get(ws);
|
|
386
393
|
if (!runs) return;
|
|
387
394
|
for (const runId of runs) runTargets.delete(runId);
|
|
@@ -416,6 +423,7 @@ const createChatWsService = ({
|
|
|
416
423
|
const markGatewayDisconnected = (reason = "Gateway disconnected") => {
|
|
417
424
|
gatewaySocket = null;
|
|
418
425
|
gatewayConnectPromise = null;
|
|
426
|
+
pendingAgentEventsByRunId.clear();
|
|
419
427
|
rejectAllGatewayRequests(reason);
|
|
420
428
|
};
|
|
421
429
|
|
|
@@ -436,6 +444,17 @@ const createChatWsService = ({
|
|
|
436
444
|
sessionTarget = targetRow;
|
|
437
445
|
}
|
|
438
446
|
if (sessionTarget) return { runId: "", target: sessionTarget };
|
|
447
|
+
const pending = pendingSessionBySessionKey.get(sessionKey);
|
|
448
|
+
if (pending) {
|
|
449
|
+
return {
|
|
450
|
+
runId: resolveRunIdFromPayload(payload),
|
|
451
|
+
target: {
|
|
452
|
+
ws: pending.ws,
|
|
453
|
+
messageId: pending.messageId,
|
|
454
|
+
sessionKey,
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
}
|
|
439
458
|
}
|
|
440
459
|
if (runTargets.size === 1) {
|
|
441
460
|
for (const [singleRunId, singleTarget] of runTargets.entries()) {
|
|
@@ -446,9 +465,60 @@ const createChatWsService = ({
|
|
|
446
465
|
};
|
|
447
466
|
if (eventName === "agent") {
|
|
448
467
|
const { runId, target } = resolveTargetForPayload();
|
|
449
|
-
if (!target)
|
|
468
|
+
if (!target) {
|
|
469
|
+
const runIdEarly = resolveRunIdFromPayload(payload);
|
|
470
|
+
if (runIdEarly && !runTargets.get(runIdEarly)) {
|
|
471
|
+
const list = pendingAgentEventsByRunId.get(runIdEarly) || [];
|
|
472
|
+
list.push(eventPayload);
|
|
473
|
+
pendingAgentEventsByRunId.set(runIdEarly, list);
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
450
477
|
const stream = String(payload?.stream || "");
|
|
451
478
|
const data = payload?.data || {};
|
|
479
|
+
if (stream === "tool") {
|
|
480
|
+
const toolPhase = String(data?.phase || "");
|
|
481
|
+
const toolName = String(data?.name || "unknown");
|
|
482
|
+
const toolCallId = String(data?.toolCallId || "");
|
|
483
|
+
if (toolPhase === "start") {
|
|
484
|
+
sendJson(target.ws, {
|
|
485
|
+
type: "tool",
|
|
486
|
+
phase: "call",
|
|
487
|
+
messageId: target.messageId,
|
|
488
|
+
sessionKey: target.sessionKey,
|
|
489
|
+
timestamp: Number(payload?.ts) || Date.now(),
|
|
490
|
+
toolCall: {
|
|
491
|
+
id: toolCallId,
|
|
492
|
+
name: toolName,
|
|
493
|
+
arguments: data?.args || null,
|
|
494
|
+
partialJson: "",
|
|
495
|
+
},
|
|
496
|
+
toolResult: null,
|
|
497
|
+
rawEvent: eventPayload || null,
|
|
498
|
+
});
|
|
499
|
+
} else if (toolPhase === "result") {
|
|
500
|
+
const resultText = collectTextFromUnknownShape(data?.result);
|
|
501
|
+
sendJson(target.ws, {
|
|
502
|
+
type: "tool",
|
|
503
|
+
phase: "result",
|
|
504
|
+
messageId: target.messageId,
|
|
505
|
+
sessionKey: target.sessionKey,
|
|
506
|
+
timestamp: Number(payload?.ts) || Date.now(),
|
|
507
|
+
toolCall: null,
|
|
508
|
+
toolResult: {
|
|
509
|
+
role: "toolResult",
|
|
510
|
+
toolCallId,
|
|
511
|
+
toolName,
|
|
512
|
+
content: resultText
|
|
513
|
+
? [{ type: "text", text: resultText }]
|
|
514
|
+
: [],
|
|
515
|
+
isError: data?.isError === true,
|
|
516
|
+
},
|
|
517
|
+
rawEvent: eventPayload || null,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
452
522
|
const toolCall =
|
|
453
523
|
extractToolCallFromUnknownShape(payload) ||
|
|
454
524
|
extractToolCallFromUnknownShape(data);
|
|
@@ -557,7 +627,7 @@ const createChatWsService = ({
|
|
|
557
627
|
},
|
|
558
628
|
role: "operator",
|
|
559
629
|
scopes: kGatewayChatBridgeScopes,
|
|
560
|
-
caps: [],
|
|
630
|
+
caps: ["tool-events"],
|
|
561
631
|
commands: [],
|
|
562
632
|
permissions: {},
|
|
563
633
|
auth: { token: getGatewayToken() },
|
|
@@ -689,13 +759,21 @@ const createChatWsService = ({
|
|
|
689
759
|
});
|
|
690
760
|
return;
|
|
691
761
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
762
|
+
pendingSessionBySessionKey.set(sessionKey, { ws, messageId });
|
|
763
|
+
let result;
|
|
764
|
+
try {
|
|
765
|
+
result = await requestGateway("chat.send", {
|
|
766
|
+
sessionKey,
|
|
767
|
+
message: content,
|
|
768
|
+
idempotencyKey: crypto.randomUUID(),
|
|
769
|
+
});
|
|
770
|
+
} catch (err) {
|
|
771
|
+
pendingSessionBySessionKey.delete(sessionKey);
|
|
772
|
+
throw err;
|
|
773
|
+
}
|
|
697
774
|
const runId = String(result?.runId || "").trim();
|
|
698
775
|
if (!runId) {
|
|
776
|
+
pendingSessionBySessionKey.delete(sessionKey);
|
|
699
777
|
sendJson(ws, {
|
|
700
778
|
type: "error",
|
|
701
779
|
message: "Something went wrong connecting to the agent.",
|
|
@@ -706,12 +784,20 @@ const createChatWsService = ({
|
|
|
706
784
|
}
|
|
707
785
|
runTargets.set(runId, { ws, messageId, sessionKey });
|
|
708
786
|
registerRunForBrowser(ws, runId);
|
|
787
|
+
pendingSessionBySessionKey.delete(sessionKey);
|
|
709
788
|
sendJson(ws, {
|
|
710
789
|
type: "started",
|
|
711
790
|
sessionKey,
|
|
712
791
|
runId,
|
|
713
792
|
messageId,
|
|
714
793
|
});
|
|
794
|
+
const buffered = pendingAgentEventsByRunId.get(runId);
|
|
795
|
+
if (buffered && buffered.length) {
|
|
796
|
+
pendingAgentEventsByRunId.delete(runId);
|
|
797
|
+
for (const stored of buffered) {
|
|
798
|
+
handleGatewayEvent(stored);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
715
801
|
};
|
|
716
802
|
|
|
717
803
|
const handleStop = async ({ ws, payload }) => {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const { buildManagedPaths } = require("../internal-files-migration");
|
|
2
2
|
const { readOpenclawConfig } = require("../openclaw-config");
|
|
3
|
-
const { hasScopedBindingFields } = require("../utils/channels");
|
|
4
3
|
const https = require("https");
|
|
5
4
|
|
|
6
5
|
const registerSystemRoutes = ({
|
|
@@ -117,40 +116,6 @@ const registerSystemRoutes = ({
|
|
|
117
116
|
.filter(Boolean)
|
|
118
117
|
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
119
118
|
.join(" ");
|
|
120
|
-
const resolveTelegramAccountIdForAgent = ({ config, agentId }) => {
|
|
121
|
-
const normalizedAgentId = String(agentId || "").trim();
|
|
122
|
-
if (!normalizedAgentId) return "default";
|
|
123
|
-
const bindings = Array.isArray(config?.bindings) ? config.bindings : [];
|
|
124
|
-
for (const binding of bindings) {
|
|
125
|
-
if (String(binding?.agentId || "").trim() !== normalizedAgentId) continue;
|
|
126
|
-
const match = binding?.match || {};
|
|
127
|
-
if (String(match.channel || "").trim() !== "telegram") continue;
|
|
128
|
-
if (hasScopedBindingFields(match)) continue;
|
|
129
|
-
return String(match.accountId || "").trim() || "default";
|
|
130
|
-
}
|
|
131
|
-
return normalizedAgentId === "main" ? "default" : "";
|
|
132
|
-
};
|
|
133
|
-
const resolveTelegramChannelNameForAgent = ({ config, agentId }) => {
|
|
134
|
-
const telegramConfig =
|
|
135
|
-
config?.channels?.telegram && typeof config.channels.telegram === "object"
|
|
136
|
-
? config.channels.telegram
|
|
137
|
-
: {};
|
|
138
|
-
const accountId = resolveTelegramAccountIdForAgent({ config, agentId });
|
|
139
|
-
const hasAccounts =
|
|
140
|
-
telegramConfig.accounts && typeof telegramConfig.accounts === "object";
|
|
141
|
-
if (hasAccounts) {
|
|
142
|
-
const accountConfig =
|
|
143
|
-
accountId && telegramConfig.accounts?.[accountId]
|
|
144
|
-
? telegramConfig.accounts[accountId]
|
|
145
|
-
: {};
|
|
146
|
-
const accountName = String(accountConfig?.name || "").trim();
|
|
147
|
-
if (accountName) return accountName;
|
|
148
|
-
} else if (accountId === "default") {
|
|
149
|
-
const legacyName = String(telegramConfig?.name || "").trim();
|
|
150
|
-
if (legacyName) return legacyName;
|
|
151
|
-
}
|
|
152
|
-
return "Telegram";
|
|
153
|
-
};
|
|
154
119
|
const getDefaultAgentLabel = (config = {}) => {
|
|
155
120
|
return "Main Agent";
|
|
156
121
|
};
|
|
@@ -182,44 +147,31 @@ const registerSystemRoutes = ({
|
|
|
182
147
|
if (!agentId) return "Agent";
|
|
183
148
|
return getConfiguredAgentLabel(config, agentId);
|
|
184
149
|
};
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
/:telegram:group:([^:]+):topic:([^:]+)$/,
|
|
201
|
-
);
|
|
202
|
-
if (telegramTopicMatch) {
|
|
203
|
-
const [, groupId, topicId] = telegramTopicMatch;
|
|
204
|
-
let groupEntry = null;
|
|
205
|
-
try {
|
|
206
|
-
groupEntry = topicRegistry?.getGroup?.(groupId) || null;
|
|
207
|
-
} catch {}
|
|
208
|
-
const groupName = String(groupEntry?.name || "").trim();
|
|
209
|
-
const topicName = String(
|
|
210
|
-
groupEntry?.topics?.[topicId]?.name || "",
|
|
211
|
-
).trim();
|
|
212
|
-
if (groupName && topicName) {
|
|
213
|
-
return `${agentLabel} - ${telegramChannelName} ${groupName} · ${topicName}`;
|
|
214
|
-
}
|
|
215
|
-
if (topicName) return `${agentLabel} - ${telegramChannelName} Topic ${topicName}`;
|
|
216
|
-
return `${agentLabel} - ${telegramChannelName} Topic ${topicId}`;
|
|
217
|
-
}
|
|
218
|
-
const directMatch = key.match(/:direct:([^:]+)$/);
|
|
219
|
-
if (directMatch) {
|
|
220
|
-
return `${agentLabel} - Direct ${directMatch[1]}`;
|
|
150
|
+
const parseChannelFromSessionKey = (key = "") => {
|
|
151
|
+
const k = String(key || "");
|
|
152
|
+
if (k.includes(":telegram:")) return "telegram";
|
|
153
|
+
if (k.includes(":discord:")) return "discord";
|
|
154
|
+
if (k.includes(":slack:")) return "slack";
|
|
155
|
+
return "";
|
|
156
|
+
};
|
|
157
|
+
const getSessionTopicContext = (sessionKey = "") => {
|
|
158
|
+
const key = String(sessionKey || "");
|
|
159
|
+
const topicMatch = key.match(/:telegram:group:([^:]+):topic:([^:]+)$/);
|
|
160
|
+
if (!topicMatch) {
|
|
161
|
+
return {
|
|
162
|
+
groupName: "",
|
|
163
|
+
topicName: "",
|
|
164
|
+
};
|
|
221
165
|
}
|
|
222
|
-
|
|
166
|
+
const [, groupId, topicId] = topicMatch;
|
|
167
|
+
let groupEntry = null;
|
|
168
|
+
try {
|
|
169
|
+
groupEntry = topicRegistry?.getGroup?.(groupId) || null;
|
|
170
|
+
} catch {}
|
|
171
|
+
return {
|
|
172
|
+
groupName: String(groupEntry?.name || "").trim(),
|
|
173
|
+
topicName: String(groupEntry?.topics?.[topicId]?.name || "").trim(),
|
|
174
|
+
};
|
|
223
175
|
};
|
|
224
176
|
const syncApiKeyAuthProfilesFromEnvVars = (nextEnvVars) => {
|
|
225
177
|
if (!authProfiles) return;
|
|
@@ -280,21 +232,15 @@ const registerSystemRoutes = ({
|
|
|
280
232
|
fallback: {},
|
|
281
233
|
});
|
|
282
234
|
return sessions
|
|
283
|
-
.filter((sessionRow) => {
|
|
284
|
-
const key = getRawSessionKey(sessionRow).toLowerCase();
|
|
285
|
-
if (!key) return false;
|
|
286
|
-
if (
|
|
287
|
-
key.includes(":hook:") ||
|
|
288
|
-
key.includes(":cron:") ||
|
|
289
|
-
key.includes(":doctor:")
|
|
290
|
-
) {
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
return true;
|
|
294
|
-
})
|
|
295
235
|
.map((sessionRow) => {
|
|
296
236
|
const key = getRawSessionKey(sessionRow);
|
|
237
|
+
if (!key) return null;
|
|
297
238
|
const replyTarget = getSessionReplyTarget(key);
|
|
239
|
+
const agentKeyMatch = key.match(/^agent:([^:]+):/);
|
|
240
|
+
const agentId = String(agentKeyMatch?.[1] || "").trim();
|
|
241
|
+
const channel =
|
|
242
|
+
parseChannelFromSessionKey(key) || replyTarget.replyChannel || "";
|
|
243
|
+
const topicContext = getSessionTopicContext(key);
|
|
298
244
|
return {
|
|
299
245
|
key,
|
|
300
246
|
sessionId: String(sessionRow?.sessionId || sessionRow?.id || ""),
|
|
@@ -304,11 +250,16 @@ const registerSystemRoutes = ({
|
|
|
304
250
|
sessionRow?.lastActivityAt ||
|
|
305
251
|
sessionRow?.lastActiveAt,
|
|
306
252
|
) || 0,
|
|
307
|
-
|
|
253
|
+
agentId,
|
|
254
|
+
agentLabel: getAgentLabelFromSessionKey(key, config),
|
|
255
|
+
channel,
|
|
256
|
+
groupName: topicContext.groupName,
|
|
257
|
+
topicName: topicContext.topicName,
|
|
308
258
|
replyChannel: replyTarget.replyChannel,
|
|
309
259
|
replyTo: replyTarget.replyTo,
|
|
310
260
|
};
|
|
311
261
|
})
|
|
262
|
+
.filter(Boolean)
|
|
312
263
|
.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
313
264
|
};
|
|
314
265
|
const readSystemCronConfig = () => {
|