@alfe.ai/openclaw-chat 0.2.0 → 0.2.2
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/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin.cjs +1 -0
- package/dist/plugin.d.cts +22 -1
- package/dist/plugin.d.ts +22 -1
- package/dist/plugin.js +2 -2
- package/dist/plugin2.cjs +88 -23
- package/dist/plugin2.d.cts +2 -2
- package/dist/plugin2.d.ts +2 -2
- package/dist/plugin2.js +83 -24
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as AlfeChannelConfig, i as createAlfeChannelPlugin, n as plugin, o as AlfePluginConfig, s as AlfeResolvedAccount } from "./plugin.cjs";
|
|
2
2
|
|
|
3
3
|
//#region src/session-store.d.ts
|
|
4
4
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as AlfeChannelConfig, i as createAlfeChannelPlugin, n as plugin, o as AlfePluginConfig, s as AlfeResolvedAccount } from "./plugin.js";
|
|
2
2
|
|
|
3
3
|
//#region src/session-store.d.ts
|
|
4
4
|
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as createAlfeChannelPlugin, n as plugin } from "./plugin2.js";
|
|
2
2
|
export { createAlfeChannelPlugin, plugin as default };
|
package/dist/plugin.cjs
CHANGED
|
@@ -3,5 +3,6 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_plugin = require("./plugin2.cjs");
|
|
6
|
+
exports.buildA2ACompletePayload = require_plugin.buildA2ACompletePayload;
|
|
6
7
|
exports.default = require_plugin.plugin;
|
|
7
8
|
exports.stripLeakedToolSummary = require_plugin.stripLeakedToolSummary;
|
package/dist/plugin.d.cts
CHANGED
|
@@ -275,6 +275,27 @@ interface PluginServiceContext {
|
|
|
275
275
|
logger: PluginLogger;
|
|
276
276
|
}
|
|
277
277
|
declare function stripLeakedToolSummary(text: string): string;
|
|
278
|
+
/**
|
|
279
|
+
* Build the `a2a-complete` notify payload. ALWAYS fired for an A2A turn —
|
|
280
|
+
* even when the dispatch produced no relayable text — so the chat service can
|
|
281
|
+
* emit an explicit terminal/stop frame instead of the thread dead-ending
|
|
282
|
+
* silently. `terminated`/`reason` are set when there's nothing to relay
|
|
283
|
+
* (`empty_turn`) or the agent ended the thread via end_conversation
|
|
284
|
+
* (`resolved`).
|
|
285
|
+
*/
|
|
286
|
+
declare function buildA2ACompletePayload<A>(params: {
|
|
287
|
+
conversationId: string;
|
|
288
|
+
fullText: string;
|
|
289
|
+
a2a: A;
|
|
290
|
+
resolved: boolean;
|
|
291
|
+
}): {
|
|
292
|
+
conversationId: string;
|
|
293
|
+
fullText: string;
|
|
294
|
+
a2a: A;
|
|
295
|
+
resolved: boolean;
|
|
296
|
+
terminated?: boolean;
|
|
297
|
+
reason?: string;
|
|
298
|
+
};
|
|
278
299
|
interface PluginApi {
|
|
279
300
|
logger: PluginLogger;
|
|
280
301
|
registrationMode?: 'full' | 'setup-only' | 'setup-runtime' | 'cli-metadata';
|
|
@@ -307,4 +328,4 @@ declare const plugin: {
|
|
|
307
328
|
deactivate(api: PluginApi): Promise<void>;
|
|
308
329
|
};
|
|
309
330
|
//#endregion
|
|
310
|
-
export {
|
|
331
|
+
export { AlfeChannelConfig as a, createAlfeChannelPlugin as i, plugin as n, AlfePluginConfig as o, stripLeakedToolSummary as r, AlfeResolvedAccount as s, buildA2ACompletePayload as t };
|
package/dist/plugin.d.ts
CHANGED
|
@@ -275,6 +275,27 @@ interface PluginServiceContext {
|
|
|
275
275
|
logger: PluginLogger;
|
|
276
276
|
}
|
|
277
277
|
declare function stripLeakedToolSummary(text: string): string;
|
|
278
|
+
/**
|
|
279
|
+
* Build the `a2a-complete` notify payload. ALWAYS fired for an A2A turn —
|
|
280
|
+
* even when the dispatch produced no relayable text — so the chat service can
|
|
281
|
+
* emit an explicit terminal/stop frame instead of the thread dead-ending
|
|
282
|
+
* silently. `terminated`/`reason` are set when there's nothing to relay
|
|
283
|
+
* (`empty_turn`) or the agent ended the thread via end_conversation
|
|
284
|
+
* (`resolved`).
|
|
285
|
+
*/
|
|
286
|
+
declare function buildA2ACompletePayload<A>(params: {
|
|
287
|
+
conversationId: string;
|
|
288
|
+
fullText: string;
|
|
289
|
+
a2a: A;
|
|
290
|
+
resolved: boolean;
|
|
291
|
+
}): {
|
|
292
|
+
conversationId: string;
|
|
293
|
+
fullText: string;
|
|
294
|
+
a2a: A;
|
|
295
|
+
resolved: boolean;
|
|
296
|
+
terminated?: boolean;
|
|
297
|
+
reason?: string;
|
|
298
|
+
};
|
|
278
299
|
interface PluginApi {
|
|
279
300
|
logger: PluginLogger;
|
|
280
301
|
registrationMode?: 'full' | 'setup-only' | 'setup-runtime' | 'cli-metadata';
|
|
@@ -307,4 +328,4 @@ declare const plugin: {
|
|
|
307
328
|
deactivate(api: PluginApi): Promise<void>;
|
|
308
329
|
};
|
|
309
330
|
//#endregion
|
|
310
|
-
export {
|
|
331
|
+
export { AlfeChannelConfig as a, createAlfeChannelPlugin as i, plugin as n, AlfePluginConfig as o, stripLeakedToolSummary as r, AlfeResolvedAccount as s, buildA2ACompletePayload as t };
|
package/dist/plugin.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as stripLeakedToolSummary, t as
|
|
2
|
-
export { plugin as default, stripLeakedToolSummary };
|
|
1
|
+
import { n as plugin, r as stripLeakedToolSummary, t as buildA2ACompletePayload } from "./plugin2.js";
|
|
2
|
+
export { buildA2ACompletePayload, plugin as default, stripLeakedToolSummary };
|
package/dist/plugin2.cjs
CHANGED
|
@@ -382,6 +382,31 @@ async function listSessions(filters, limit = 50) {
|
|
|
382
382
|
}
|
|
383
383
|
//#endregion
|
|
384
384
|
//#region src/a2a-tools.ts
|
|
385
|
+
let conversationEndRequested = false;
|
|
386
|
+
let a2aTurnArmed = false;
|
|
387
|
+
/**
|
|
388
|
+
* Clear the end signal AND arm the gate. Call immediately before an A2A
|
|
389
|
+
* dispatch. Arming is what permits `end_conversation` to set the flag for the
|
|
390
|
+
* duration of this turn.
|
|
391
|
+
*/
|
|
392
|
+
function resetA2AEndSignal() {
|
|
393
|
+
conversationEndRequested = false;
|
|
394
|
+
a2aTurnArmed = true;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Clear the end signal AND disarm the gate. Call after an A2A dispatch (in a
|
|
398
|
+
* `finally`, so it also runs on error). Disarming closes the gate so a later
|
|
399
|
+
* NON-A2A turn invoking `end_conversation` can't set a flag the next A2A turn
|
|
400
|
+
* would read.
|
|
401
|
+
*/
|
|
402
|
+
function disarmA2AEndSignal() {
|
|
403
|
+
conversationEndRequested = false;
|
|
404
|
+
a2aTurnArmed = false;
|
|
405
|
+
}
|
|
406
|
+
/** True if `end_conversation` was invoked since the last reset. */
|
|
407
|
+
function isA2AEndSignalled() {
|
|
408
|
+
return conversationEndRequested;
|
|
409
|
+
}
|
|
385
410
|
function ok(result) {
|
|
386
411
|
return { content: [{
|
|
387
412
|
type: "text",
|
|
@@ -409,7 +434,12 @@ function defineTool(def) {
|
|
|
409
434
|
}
|
|
410
435
|
};
|
|
411
436
|
}
|
|
412
|
-
function buildA2ATools(
|
|
437
|
+
function buildA2ATools(getChatClient, log) {
|
|
438
|
+
const requireClient = () => {
|
|
439
|
+
const client = getChatClient();
|
|
440
|
+
if (!client) throw new Error("chat service not connected yet — try again in a moment");
|
|
441
|
+
return client;
|
|
442
|
+
};
|
|
413
443
|
return [
|
|
414
444
|
defineTool({
|
|
415
445
|
name: "list_agents",
|
|
@@ -421,7 +451,7 @@ function buildA2ATools(chatClient, log) {
|
|
|
421
451
|
},
|
|
422
452
|
handler: async () => {
|
|
423
453
|
log.info("list_agents tool called");
|
|
424
|
-
return await
|
|
454
|
+
return await requireClient().sendRequest("a2a.list-agents", {});
|
|
425
455
|
}
|
|
426
456
|
}),
|
|
427
457
|
defineTool({
|
|
@@ -456,7 +486,7 @@ function buildA2ATools(chatClient, log) {
|
|
|
456
486
|
const maxDepth = params.max_depth ?? 10;
|
|
457
487
|
if (!agentId || !message) throw new Error("agent_id and message are required");
|
|
458
488
|
log.info(`message_agent tool: sending to ${agentId}`);
|
|
459
|
-
return await
|
|
489
|
+
return await requireClient().sendRequest("a2a.send", {
|
|
460
490
|
targetAgentId: agentId,
|
|
461
491
|
message,
|
|
462
492
|
threadId,
|
|
@@ -478,6 +508,7 @@ function buildA2ATools(chatClient, log) {
|
|
|
478
508
|
handler: (params) => {
|
|
479
509
|
const summary = params.summary;
|
|
480
510
|
log.info(`end_conversation tool called${summary ? `: ${summary}` : ""}`);
|
|
511
|
+
if (a2aTurnArmed) conversationEndRequested = true;
|
|
481
512
|
return Promise.resolve({
|
|
482
513
|
status: "conversation_ended",
|
|
483
514
|
summary
|
|
@@ -675,6 +706,28 @@ function stripLeakedToolSummary(text) {
|
|
|
675
706
|
if (!text.includes("step")) return text;
|
|
676
707
|
return text.split("\n").filter((line) => !LEAKED_TOOL_SUMMARY_LINE.test(line)).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
677
708
|
}
|
|
709
|
+
/**
|
|
710
|
+
* Build the `a2a-complete` notify payload. ALWAYS fired for an A2A turn —
|
|
711
|
+
* even when the dispatch produced no relayable text — so the chat service can
|
|
712
|
+
* emit an explicit terminal/stop frame instead of the thread dead-ending
|
|
713
|
+
* silently. `terminated`/`reason` are set when there's nothing to relay
|
|
714
|
+
* (`empty_turn`) or the agent ended the thread via end_conversation
|
|
715
|
+
* (`resolved`).
|
|
716
|
+
*/
|
|
717
|
+
function buildA2ACompletePayload(params) {
|
|
718
|
+
const hasText = params.fullText.trim().length > 0;
|
|
719
|
+
const terminal = params.resolved || !hasText;
|
|
720
|
+
return {
|
|
721
|
+
conversationId: params.conversationId,
|
|
722
|
+
fullText: params.fullText,
|
|
723
|
+
a2a: params.a2a,
|
|
724
|
+
resolved: params.resolved,
|
|
725
|
+
...terminal ? {
|
|
726
|
+
terminated: true,
|
|
727
|
+
reason: params.resolved ? "resolved" : "empty_turn"
|
|
728
|
+
} : {}
|
|
729
|
+
};
|
|
730
|
+
}
|
|
678
731
|
let dispatchInbound = null;
|
|
679
732
|
/**
|
|
680
733
|
* Resolve OpenClaw SDK functions from the running process.
|
|
@@ -778,6 +831,12 @@ async function downloadAttachments(attachments, log) {
|
|
|
778
831
|
}
|
|
779
832
|
return results;
|
|
780
833
|
}
|
|
834
|
+
let agentTurnQueue = Promise.resolve();
|
|
835
|
+
function enqueueAgentTurn(task) {
|
|
836
|
+
const run = agentTurnQueue.then(task, task);
|
|
837
|
+
agentTurnQueue = run.then(() => void 0, () => void 0);
|
|
838
|
+
return run;
|
|
839
|
+
}
|
|
781
840
|
async function handleAgentRequest(request, log) {
|
|
782
841
|
const runtime = pluginRuntime;
|
|
783
842
|
if (!runtime) {
|
|
@@ -859,7 +918,7 @@ async function handleAgentRequest(request, log) {
|
|
|
859
918
|
const userLabel = displayName ?? userId ?? senderId;
|
|
860
919
|
const conversationLabel = conversationType === "group" ? shortConvId ? `[${channelLabel}] Group (${shortConvId})` : `[${channelLabel}] Group` : shortConvId ? `[${channelLabel}] ${userLabel} (${shortConvId})` : `[${channelLabel}] ${userLabel}`;
|
|
861
920
|
let a2aResponseBuffer = "";
|
|
862
|
-
|
|
921
|
+
if (isA2A) resetA2AEndSignal();
|
|
863
922
|
resolvedOpenClawKey = (await dispatchInbound({
|
|
864
923
|
cfg,
|
|
865
924
|
runtime: { channel: runtime.channel },
|
|
@@ -899,7 +958,8 @@ async function handleAgentRequest(request, log) {
|
|
|
899
958
|
`This is an agent-to-agent conversation with ${a2a.sourceAgentName}.`,
|
|
900
959
|
"Rules:",
|
|
901
960
|
"- Only respond if you have new information, a question, or an action to coordinate.",
|
|
902
|
-
"-
|
|
961
|
+
"- When the discussion is complete, call the end_conversation() tool AND end your final message with the literal token [RESOLVED] (uppercase, in square brackets, as the last thing in the message).",
|
|
962
|
+
"- Do NOT write the word \"resolved\" in prose unless you mean to end — only the exact token [RESOLVED] ends the thread.",
|
|
903
963
|
"- Do NOT respond just to acknowledge — that creates infinite loops."
|
|
904
964
|
].join("\n")
|
|
905
965
|
} : {}
|
|
@@ -908,10 +968,7 @@ async function handleAgentRequest(request, log) {
|
|
|
908
968
|
const responseText = stripLeakedToolSummary(payload.text ?? "");
|
|
909
969
|
const mediaUrls = [...payload.mediaUrl ? [payload.mediaUrl] : [], ...payload.mediaUrls ?? []];
|
|
910
970
|
await addMessage(sessionId, "assistant", responseText);
|
|
911
|
-
if (isA2A)
|
|
912
|
-
a2aResponseBuffer += responseText;
|
|
913
|
-
if (responseText.includes("\"status\":\"conversation_ended\"") || responseText.includes("\"status\": \"conversation_ended\"")) a2aResolved = true;
|
|
914
|
-
}
|
|
971
|
+
if (isA2A) a2aResponseBuffer += responseText;
|
|
915
972
|
chatClient?.notify("agent-message", {
|
|
916
973
|
conversationId: conversationId ?? legacySessionKey,
|
|
917
974
|
text: responseText,
|
|
@@ -927,12 +984,15 @@ async function handleAgentRequest(request, log) {
|
|
|
927
984
|
log.error(`Dispatch error (${info.kind}): ${err instanceof Error ? err.message : String(err)}`);
|
|
928
985
|
}
|
|
929
986
|
})).route.sessionKey;
|
|
930
|
-
if (isA2A
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
987
|
+
if (isA2A) {
|
|
988
|
+
const a2aResolved = isA2AEndSignalled();
|
|
989
|
+
chatClient?.notify("a2a-complete", buildA2ACompletePayload({
|
|
990
|
+
conversationId: conversationId ?? legacySessionKey,
|
|
991
|
+
fullText: a2aResponseBuffer,
|
|
992
|
+
a2a,
|
|
993
|
+
resolved: a2aResolved
|
|
994
|
+
}));
|
|
995
|
+
}
|
|
936
996
|
chatClient?.sendResponse(request.id, true, { sessionKey: resolvedOpenClawKey });
|
|
937
997
|
log.info(`Agent dispatch complete: sessionKey=${resolvedOpenClawKey}`);
|
|
938
998
|
} catch (err) {
|
|
@@ -941,6 +1001,7 @@ async function handleAgentRequest(request, log) {
|
|
|
941
1001
|
chatClient?.sendResponse(request.id, false, { message: errMsg });
|
|
942
1002
|
} finally {
|
|
943
1003
|
unsubscribe();
|
|
1004
|
+
if (isA2A) disarmA2AEndSignal();
|
|
944
1005
|
}
|
|
945
1006
|
}
|
|
946
1007
|
async function handleSessionsList(request, log) {
|
|
@@ -1010,6 +1071,11 @@ const plugin = {
|
|
|
1010
1071
|
});
|
|
1011
1072
|
api.registerChannel(alfeChannel);
|
|
1012
1073
|
log.info(`Registered channel: ${alfeChannel.id}`);
|
|
1074
|
+
if (typeof api.registerTool === "function") {
|
|
1075
|
+
const a2aTools = buildA2ATools(() => chatClient, log);
|
|
1076
|
+
for (const tool of a2aTools) api.registerTool(tool);
|
|
1077
|
+
log.info(`Registered ${String(a2aTools.length)} agent-to-agent tools`);
|
|
1078
|
+
}
|
|
1013
1079
|
const pluginConfig = (((api.config ?? {}).plugins?.entries)?.["@alfe.ai/openclaw-chat"] ?? {}).config ?? {};
|
|
1014
1080
|
const startChatService = () => {
|
|
1015
1081
|
if (globalThis.__alfeChatPluginActivated === true) {
|
|
@@ -1033,7 +1099,7 @@ const plugin = {
|
|
|
1033
1099
|
apiKey,
|
|
1034
1100
|
onRequest: (request) => {
|
|
1035
1101
|
const handle = async () => {
|
|
1036
|
-
if (request.method === "agent") await handleAgentRequest(request, log);
|
|
1102
|
+
if (request.method === "agent") await enqueueAgentTurn(() => handleAgentRequest(request, log));
|
|
1037
1103
|
else if (request.method === "sessions.list") await handleSessionsList(request, log);
|
|
1038
1104
|
else if (request.method === "sessions.get") await handleSessionsGet(request, log);
|
|
1039
1105
|
else chatClient?.sendResponse(request.id, false, { message: `Unknown method: ${request.method}` });
|
|
@@ -1056,13 +1122,6 @@ const plugin = {
|
|
|
1056
1122
|
log.error(`Failed to initialize chat service: ${err instanceof Error ? err.message : String(err)}`);
|
|
1057
1123
|
}
|
|
1058
1124
|
});
|
|
1059
|
-
if (typeof api.registerTool === "function") connectingPromise.then(() => {
|
|
1060
|
-
if (chatClient && typeof api.registerTool === "function") {
|
|
1061
|
-
const a2aTools = buildA2ATools(chatClient, log);
|
|
1062
|
-
for (const tool of a2aTools) api.registerTool(tool);
|
|
1063
|
-
log.info(`Registered ${String(a2aTools.length)} agent-to-agent tools`);
|
|
1064
|
-
}
|
|
1065
|
-
}).catch(() => {});
|
|
1066
1125
|
};
|
|
1067
1126
|
const stopChatService = async () => {
|
|
1068
1127
|
globalThis.__alfeChatPluginActivated = false;
|
|
@@ -1168,6 +1227,12 @@ const plugin = {
|
|
|
1168
1227
|
}
|
|
1169
1228
|
};
|
|
1170
1229
|
//#endregion
|
|
1230
|
+
Object.defineProperty(exports, "buildA2ACompletePayload", {
|
|
1231
|
+
enumerable: true,
|
|
1232
|
+
get: function() {
|
|
1233
|
+
return buildA2ACompletePayload;
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1171
1236
|
Object.defineProperty(exports, "createAlfeChannelPlugin", {
|
|
1172
1237
|
enumerable: true,
|
|
1173
1238
|
get: function() {
|
package/dist/plugin2.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as stripLeakedToolSummary, t as
|
|
2
|
-
export { plugin as default, stripLeakedToolSummary };
|
|
1
|
+
import { n as plugin, r as stripLeakedToolSummary, t as buildA2ACompletePayload } from "./plugin.cjs";
|
|
2
|
+
export { buildA2ACompletePayload, plugin as default, stripLeakedToolSummary };
|
package/dist/plugin2.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as stripLeakedToolSummary, t as
|
|
2
|
-
export { plugin as default, stripLeakedToolSummary };
|
|
1
|
+
import { n as plugin, r as stripLeakedToolSummary, t as buildA2ACompletePayload } from "./plugin.js";
|
|
2
|
+
export { buildA2ACompletePayload, plugin as default, stripLeakedToolSummary };
|
package/dist/plugin2.js
CHANGED
|
@@ -382,6 +382,31 @@ async function listSessions(filters, limit = 50) {
|
|
|
382
382
|
}
|
|
383
383
|
//#endregion
|
|
384
384
|
//#region src/a2a-tools.ts
|
|
385
|
+
let conversationEndRequested = false;
|
|
386
|
+
let a2aTurnArmed = false;
|
|
387
|
+
/**
|
|
388
|
+
* Clear the end signal AND arm the gate. Call immediately before an A2A
|
|
389
|
+
* dispatch. Arming is what permits `end_conversation` to set the flag for the
|
|
390
|
+
* duration of this turn.
|
|
391
|
+
*/
|
|
392
|
+
function resetA2AEndSignal() {
|
|
393
|
+
conversationEndRequested = false;
|
|
394
|
+
a2aTurnArmed = true;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Clear the end signal AND disarm the gate. Call after an A2A dispatch (in a
|
|
398
|
+
* `finally`, so it also runs on error). Disarming closes the gate so a later
|
|
399
|
+
* NON-A2A turn invoking `end_conversation` can't set a flag the next A2A turn
|
|
400
|
+
* would read.
|
|
401
|
+
*/
|
|
402
|
+
function disarmA2AEndSignal() {
|
|
403
|
+
conversationEndRequested = false;
|
|
404
|
+
a2aTurnArmed = false;
|
|
405
|
+
}
|
|
406
|
+
/** True if `end_conversation` was invoked since the last reset. */
|
|
407
|
+
function isA2AEndSignalled() {
|
|
408
|
+
return conversationEndRequested;
|
|
409
|
+
}
|
|
385
410
|
function ok(result) {
|
|
386
411
|
return { content: [{
|
|
387
412
|
type: "text",
|
|
@@ -409,7 +434,12 @@ function defineTool(def) {
|
|
|
409
434
|
}
|
|
410
435
|
};
|
|
411
436
|
}
|
|
412
|
-
function buildA2ATools(
|
|
437
|
+
function buildA2ATools(getChatClient, log) {
|
|
438
|
+
const requireClient = () => {
|
|
439
|
+
const client = getChatClient();
|
|
440
|
+
if (!client) throw new Error("chat service not connected yet — try again in a moment");
|
|
441
|
+
return client;
|
|
442
|
+
};
|
|
413
443
|
return [
|
|
414
444
|
defineTool({
|
|
415
445
|
name: "list_agents",
|
|
@@ -421,7 +451,7 @@ function buildA2ATools(chatClient, log) {
|
|
|
421
451
|
},
|
|
422
452
|
handler: async () => {
|
|
423
453
|
log.info("list_agents tool called");
|
|
424
|
-
return await
|
|
454
|
+
return await requireClient().sendRequest("a2a.list-agents", {});
|
|
425
455
|
}
|
|
426
456
|
}),
|
|
427
457
|
defineTool({
|
|
@@ -456,7 +486,7 @@ function buildA2ATools(chatClient, log) {
|
|
|
456
486
|
const maxDepth = params.max_depth ?? 10;
|
|
457
487
|
if (!agentId || !message) throw new Error("agent_id and message are required");
|
|
458
488
|
log.info(`message_agent tool: sending to ${agentId}`);
|
|
459
|
-
return await
|
|
489
|
+
return await requireClient().sendRequest("a2a.send", {
|
|
460
490
|
targetAgentId: agentId,
|
|
461
491
|
message,
|
|
462
492
|
threadId,
|
|
@@ -478,6 +508,7 @@ function buildA2ATools(chatClient, log) {
|
|
|
478
508
|
handler: (params) => {
|
|
479
509
|
const summary = params.summary;
|
|
480
510
|
log.info(`end_conversation tool called${summary ? `: ${summary}` : ""}`);
|
|
511
|
+
if (a2aTurnArmed) conversationEndRequested = true;
|
|
481
512
|
return Promise.resolve({
|
|
482
513
|
status: "conversation_ended",
|
|
483
514
|
summary
|
|
@@ -675,6 +706,28 @@ function stripLeakedToolSummary(text) {
|
|
|
675
706
|
if (!text.includes("step")) return text;
|
|
676
707
|
return text.split("\n").filter((line) => !LEAKED_TOOL_SUMMARY_LINE.test(line)).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
677
708
|
}
|
|
709
|
+
/**
|
|
710
|
+
* Build the `a2a-complete` notify payload. ALWAYS fired for an A2A turn —
|
|
711
|
+
* even when the dispatch produced no relayable text — so the chat service can
|
|
712
|
+
* emit an explicit terminal/stop frame instead of the thread dead-ending
|
|
713
|
+
* silently. `terminated`/`reason` are set when there's nothing to relay
|
|
714
|
+
* (`empty_turn`) or the agent ended the thread via end_conversation
|
|
715
|
+
* (`resolved`).
|
|
716
|
+
*/
|
|
717
|
+
function buildA2ACompletePayload(params) {
|
|
718
|
+
const hasText = params.fullText.trim().length > 0;
|
|
719
|
+
const terminal = params.resolved || !hasText;
|
|
720
|
+
return {
|
|
721
|
+
conversationId: params.conversationId,
|
|
722
|
+
fullText: params.fullText,
|
|
723
|
+
a2a: params.a2a,
|
|
724
|
+
resolved: params.resolved,
|
|
725
|
+
...terminal ? {
|
|
726
|
+
terminated: true,
|
|
727
|
+
reason: params.resolved ? "resolved" : "empty_turn"
|
|
728
|
+
} : {}
|
|
729
|
+
};
|
|
730
|
+
}
|
|
678
731
|
let dispatchInbound = null;
|
|
679
732
|
/**
|
|
680
733
|
* Resolve OpenClaw SDK functions from the running process.
|
|
@@ -778,6 +831,12 @@ async function downloadAttachments(attachments, log) {
|
|
|
778
831
|
}
|
|
779
832
|
return results;
|
|
780
833
|
}
|
|
834
|
+
let agentTurnQueue = Promise.resolve();
|
|
835
|
+
function enqueueAgentTurn(task) {
|
|
836
|
+
const run = agentTurnQueue.then(task, task);
|
|
837
|
+
agentTurnQueue = run.then(() => void 0, () => void 0);
|
|
838
|
+
return run;
|
|
839
|
+
}
|
|
781
840
|
async function handleAgentRequest(request, log) {
|
|
782
841
|
const runtime = pluginRuntime;
|
|
783
842
|
if (!runtime) {
|
|
@@ -859,7 +918,7 @@ async function handleAgentRequest(request, log) {
|
|
|
859
918
|
const userLabel = displayName ?? userId ?? senderId;
|
|
860
919
|
const conversationLabel = conversationType === "group" ? shortConvId ? `[${channelLabel}] Group (${shortConvId})` : `[${channelLabel}] Group` : shortConvId ? `[${channelLabel}] ${userLabel} (${shortConvId})` : `[${channelLabel}] ${userLabel}`;
|
|
861
920
|
let a2aResponseBuffer = "";
|
|
862
|
-
|
|
921
|
+
if (isA2A) resetA2AEndSignal();
|
|
863
922
|
resolvedOpenClawKey = (await dispatchInbound({
|
|
864
923
|
cfg,
|
|
865
924
|
runtime: { channel: runtime.channel },
|
|
@@ -899,7 +958,8 @@ async function handleAgentRequest(request, log) {
|
|
|
899
958
|
`This is an agent-to-agent conversation with ${a2a.sourceAgentName}.`,
|
|
900
959
|
"Rules:",
|
|
901
960
|
"- Only respond if you have new information, a question, or an action to coordinate.",
|
|
902
|
-
"-
|
|
961
|
+
"- When the discussion is complete, call the end_conversation() tool AND end your final message with the literal token [RESOLVED] (uppercase, in square brackets, as the last thing in the message).",
|
|
962
|
+
"- Do NOT write the word \"resolved\" in prose unless you mean to end — only the exact token [RESOLVED] ends the thread.",
|
|
903
963
|
"- Do NOT respond just to acknowledge — that creates infinite loops."
|
|
904
964
|
].join("\n")
|
|
905
965
|
} : {}
|
|
@@ -908,10 +968,7 @@ async function handleAgentRequest(request, log) {
|
|
|
908
968
|
const responseText = stripLeakedToolSummary(payload.text ?? "");
|
|
909
969
|
const mediaUrls = [...payload.mediaUrl ? [payload.mediaUrl] : [], ...payload.mediaUrls ?? []];
|
|
910
970
|
await addMessage(sessionId, "assistant", responseText);
|
|
911
|
-
if (isA2A)
|
|
912
|
-
a2aResponseBuffer += responseText;
|
|
913
|
-
if (responseText.includes("\"status\":\"conversation_ended\"") || responseText.includes("\"status\": \"conversation_ended\"")) a2aResolved = true;
|
|
914
|
-
}
|
|
971
|
+
if (isA2A) a2aResponseBuffer += responseText;
|
|
915
972
|
chatClient?.notify("agent-message", {
|
|
916
973
|
conversationId: conversationId ?? legacySessionKey,
|
|
917
974
|
text: responseText,
|
|
@@ -927,12 +984,15 @@ async function handleAgentRequest(request, log) {
|
|
|
927
984
|
log.error(`Dispatch error (${info.kind}): ${err instanceof Error ? err.message : String(err)}`);
|
|
928
985
|
}
|
|
929
986
|
})).route.sessionKey;
|
|
930
|
-
if (isA2A
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
987
|
+
if (isA2A) {
|
|
988
|
+
const a2aResolved = isA2AEndSignalled();
|
|
989
|
+
chatClient?.notify("a2a-complete", buildA2ACompletePayload({
|
|
990
|
+
conversationId: conversationId ?? legacySessionKey,
|
|
991
|
+
fullText: a2aResponseBuffer,
|
|
992
|
+
a2a,
|
|
993
|
+
resolved: a2aResolved
|
|
994
|
+
}));
|
|
995
|
+
}
|
|
936
996
|
chatClient?.sendResponse(request.id, true, { sessionKey: resolvedOpenClawKey });
|
|
937
997
|
log.info(`Agent dispatch complete: sessionKey=${resolvedOpenClawKey}`);
|
|
938
998
|
} catch (err) {
|
|
@@ -941,6 +1001,7 @@ async function handleAgentRequest(request, log) {
|
|
|
941
1001
|
chatClient?.sendResponse(request.id, false, { message: errMsg });
|
|
942
1002
|
} finally {
|
|
943
1003
|
unsubscribe();
|
|
1004
|
+
if (isA2A) disarmA2AEndSignal();
|
|
944
1005
|
}
|
|
945
1006
|
}
|
|
946
1007
|
async function handleSessionsList(request, log) {
|
|
@@ -1010,6 +1071,11 @@ const plugin = {
|
|
|
1010
1071
|
});
|
|
1011
1072
|
api.registerChannel(alfeChannel);
|
|
1012
1073
|
log.info(`Registered channel: ${alfeChannel.id}`);
|
|
1074
|
+
if (typeof api.registerTool === "function") {
|
|
1075
|
+
const a2aTools = buildA2ATools(() => chatClient, log);
|
|
1076
|
+
for (const tool of a2aTools) api.registerTool(tool);
|
|
1077
|
+
log.info(`Registered ${String(a2aTools.length)} agent-to-agent tools`);
|
|
1078
|
+
}
|
|
1013
1079
|
const pluginConfig = (((api.config ?? {}).plugins?.entries)?.["@alfe.ai/openclaw-chat"] ?? {}).config ?? {};
|
|
1014
1080
|
const startChatService = () => {
|
|
1015
1081
|
if (globalThis.__alfeChatPluginActivated === true) {
|
|
@@ -1033,7 +1099,7 @@ const plugin = {
|
|
|
1033
1099
|
apiKey,
|
|
1034
1100
|
onRequest: (request) => {
|
|
1035
1101
|
const handle = async () => {
|
|
1036
|
-
if (request.method === "agent") await handleAgentRequest(request, log);
|
|
1102
|
+
if (request.method === "agent") await enqueueAgentTurn(() => handleAgentRequest(request, log));
|
|
1037
1103
|
else if (request.method === "sessions.list") await handleSessionsList(request, log);
|
|
1038
1104
|
else if (request.method === "sessions.get") await handleSessionsGet(request, log);
|
|
1039
1105
|
else chatClient?.sendResponse(request.id, false, { message: `Unknown method: ${request.method}` });
|
|
@@ -1056,13 +1122,6 @@ const plugin = {
|
|
|
1056
1122
|
log.error(`Failed to initialize chat service: ${err instanceof Error ? err.message : String(err)}`);
|
|
1057
1123
|
}
|
|
1058
1124
|
});
|
|
1059
|
-
if (typeof api.registerTool === "function") connectingPromise.then(() => {
|
|
1060
|
-
if (chatClient && typeof api.registerTool === "function") {
|
|
1061
|
-
const a2aTools = buildA2ATools(chatClient, log);
|
|
1062
|
-
for (const tool of a2aTools) api.registerTool(tool);
|
|
1063
|
-
log.info(`Registered ${String(a2aTools.length)} agent-to-agent tools`);
|
|
1064
|
-
}
|
|
1065
|
-
}).catch(() => {});
|
|
1066
1125
|
};
|
|
1067
1126
|
const stopChatService = async () => {
|
|
1068
1127
|
globalThis.__alfeChatPluginActivated = false;
|
|
@@ -1168,4 +1227,4 @@ const plugin = {
|
|
|
1168
1227
|
}
|
|
1169
1228
|
};
|
|
1170
1229
|
//#endregion
|
|
1171
|
-
export {
|
|
1230
|
+
export { createAlfeChannelPlugin as i, plugin as n, stripLeakedToolSummary as r, buildA2ACompletePayload as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/openclaw-chat",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "OpenClaw chat plugin for Alfe — web widget and mobile app channels",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/plugin.js",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@alfe.ai/agent-api-client": "^0.2.2",
|
|
31
|
-
"@alfe.ai/
|
|
32
|
-
"@alfe.ai/
|
|
31
|
+
"@alfe.ai/config": "^0.0.9",
|
|
32
|
+
"@alfe.ai/chat": "^0.0.10"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"openclaw": ">=2026.3.0"
|