@alfe.ai/openclaw-chat 0.2.0 → 0.2.1

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 CHANGED
@@ -1,4 +1,4 @@
1
- import { a as AlfePluginConfig, i as AlfeChannelConfig, o as AlfeResolvedAccount, r as createAlfeChannelPlugin, t as plugin } from "./plugin.cjs";
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 AlfePluginConfig, i as AlfeChannelConfig, o as AlfeResolvedAccount, r as createAlfeChannelPlugin, t as plugin } from "./plugin.js";
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 { r as createAlfeChannelPlugin, t as plugin } from "./plugin2.js";
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 { AlfePluginConfig as a, AlfeChannelConfig as i, stripLeakedToolSummary as n, AlfeResolvedAccount as o, createAlfeChannelPlugin as r, plugin as t };
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 { AlfePluginConfig as a, AlfeChannelConfig as i, stripLeakedToolSummary as n, AlfeResolvedAccount as o, createAlfeChannelPlugin as r, plugin as t };
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 plugin } from "./plugin2.js";
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",
@@ -478,6 +503,7 @@ function buildA2ATools(chatClient, log) {
478
503
  handler: (params) => {
479
504
  const summary = params.summary;
480
505
  log.info(`end_conversation tool called${summary ? `: ${summary}` : ""}`);
506
+ if (a2aTurnArmed) conversationEndRequested = true;
481
507
  return Promise.resolve({
482
508
  status: "conversation_ended",
483
509
  summary
@@ -675,6 +701,28 @@ function stripLeakedToolSummary(text) {
675
701
  if (!text.includes("step")) return text;
676
702
  return text.split("\n").filter((line) => !LEAKED_TOOL_SUMMARY_LINE.test(line)).join("\n").replace(/\n{3,}/g, "\n\n").trim();
677
703
  }
704
+ /**
705
+ * Build the `a2a-complete` notify payload. ALWAYS fired for an A2A turn —
706
+ * even when the dispatch produced no relayable text — so the chat service can
707
+ * emit an explicit terminal/stop frame instead of the thread dead-ending
708
+ * silently. `terminated`/`reason` are set when there's nothing to relay
709
+ * (`empty_turn`) or the agent ended the thread via end_conversation
710
+ * (`resolved`).
711
+ */
712
+ function buildA2ACompletePayload(params) {
713
+ const hasText = params.fullText.trim().length > 0;
714
+ const terminal = params.resolved || !hasText;
715
+ return {
716
+ conversationId: params.conversationId,
717
+ fullText: params.fullText,
718
+ a2a: params.a2a,
719
+ resolved: params.resolved,
720
+ ...terminal ? {
721
+ terminated: true,
722
+ reason: params.resolved ? "resolved" : "empty_turn"
723
+ } : {}
724
+ };
725
+ }
678
726
  let dispatchInbound = null;
679
727
  /**
680
728
  * Resolve OpenClaw SDK functions from the running process.
@@ -778,6 +826,12 @@ async function downloadAttachments(attachments, log) {
778
826
  }
779
827
  return results;
780
828
  }
829
+ let agentTurnQueue = Promise.resolve();
830
+ function enqueueAgentTurn(task) {
831
+ const run = agentTurnQueue.then(task, task);
832
+ agentTurnQueue = run.then(() => void 0, () => void 0);
833
+ return run;
834
+ }
781
835
  async function handleAgentRequest(request, log) {
782
836
  const runtime = pluginRuntime;
783
837
  if (!runtime) {
@@ -859,7 +913,7 @@ async function handleAgentRequest(request, log) {
859
913
  const userLabel = displayName ?? userId ?? senderId;
860
914
  const conversationLabel = conversationType === "group" ? shortConvId ? `[${channelLabel}] Group (${shortConvId})` : `[${channelLabel}] Group` : shortConvId ? `[${channelLabel}] ${userLabel} (${shortConvId})` : `[${channelLabel}] ${userLabel}`;
861
915
  let a2aResponseBuffer = "";
862
- let a2aResolved = false;
916
+ if (isA2A) resetA2AEndSignal();
863
917
  resolvedOpenClawKey = (await dispatchInbound({
864
918
  cfg,
865
919
  runtime: { channel: runtime.channel },
@@ -899,7 +953,8 @@ async function handleAgentRequest(request, log) {
899
953
  `This is an agent-to-agent conversation with ${a2a.sourceAgentName}.`,
900
954
  "Rules:",
901
955
  "- Only respond if you have new information, a question, or an action to coordinate.",
902
- "- If the conversation is complete, call the end_conversation() tool OR end your message with [RESOLVED].",
956
+ "- 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).",
957
+ "- Do NOT write the word \"resolved\" in prose unless you mean to end — only the exact token [RESOLVED] ends the thread.",
903
958
  "- Do NOT respond just to acknowledge — that creates infinite loops."
904
959
  ].join("\n")
905
960
  } : {}
@@ -908,10 +963,7 @@ async function handleAgentRequest(request, log) {
908
963
  const responseText = stripLeakedToolSummary(payload.text ?? "");
909
964
  const mediaUrls = [...payload.mediaUrl ? [payload.mediaUrl] : [], ...payload.mediaUrls ?? []];
910
965
  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
- }
966
+ if (isA2A) a2aResponseBuffer += responseText;
915
967
  chatClient?.notify("agent-message", {
916
968
  conversationId: conversationId ?? legacySessionKey,
917
969
  text: responseText,
@@ -927,12 +979,15 @@ async function handleAgentRequest(request, log) {
927
979
  log.error(`Dispatch error (${info.kind}): ${err instanceof Error ? err.message : String(err)}`);
928
980
  }
929
981
  })).route.sessionKey;
930
- if (isA2A && a2aResponseBuffer) chatClient?.notify("a2a-complete", {
931
- conversationId: conversationId ?? legacySessionKey,
932
- fullText: a2aResponseBuffer,
933
- a2a,
934
- resolved: a2aResolved
935
- });
982
+ if (isA2A) {
983
+ const a2aResolved = isA2AEndSignalled();
984
+ chatClient?.notify("a2a-complete", buildA2ACompletePayload({
985
+ conversationId: conversationId ?? legacySessionKey,
986
+ fullText: a2aResponseBuffer,
987
+ a2a,
988
+ resolved: a2aResolved
989
+ }));
990
+ }
936
991
  chatClient?.sendResponse(request.id, true, { sessionKey: resolvedOpenClawKey });
937
992
  log.info(`Agent dispatch complete: sessionKey=${resolvedOpenClawKey}`);
938
993
  } catch (err) {
@@ -941,6 +996,7 @@ async function handleAgentRequest(request, log) {
941
996
  chatClient?.sendResponse(request.id, false, { message: errMsg });
942
997
  } finally {
943
998
  unsubscribe();
999
+ if (isA2A) disarmA2AEndSignal();
944
1000
  }
945
1001
  }
946
1002
  async function handleSessionsList(request, log) {
@@ -1033,7 +1089,7 @@ const plugin = {
1033
1089
  apiKey,
1034
1090
  onRequest: (request) => {
1035
1091
  const handle = async () => {
1036
- if (request.method === "agent") await handleAgentRequest(request, log);
1092
+ if (request.method === "agent") await enqueueAgentTurn(() => handleAgentRequest(request, log));
1037
1093
  else if (request.method === "sessions.list") await handleSessionsList(request, log);
1038
1094
  else if (request.method === "sessions.get") await handleSessionsGet(request, log);
1039
1095
  else chatClient?.sendResponse(request.id, false, { message: `Unknown method: ${request.method}` });
@@ -1168,6 +1224,12 @@ const plugin = {
1168
1224
  }
1169
1225
  };
1170
1226
  //#endregion
1227
+ Object.defineProperty(exports, "buildA2ACompletePayload", {
1228
+ enumerable: true,
1229
+ get: function() {
1230
+ return buildA2ACompletePayload;
1231
+ }
1232
+ });
1171
1233
  Object.defineProperty(exports, "createAlfeChannelPlugin", {
1172
1234
  enumerable: true,
1173
1235
  get: function() {
@@ -1,2 +1,2 @@
1
- import { n as stripLeakedToolSummary, t as plugin } from "./plugin.cjs";
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 plugin } from "./plugin.js";
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",
@@ -478,6 +503,7 @@ function buildA2ATools(chatClient, log) {
478
503
  handler: (params) => {
479
504
  const summary = params.summary;
480
505
  log.info(`end_conversation tool called${summary ? `: ${summary}` : ""}`);
506
+ if (a2aTurnArmed) conversationEndRequested = true;
481
507
  return Promise.resolve({
482
508
  status: "conversation_ended",
483
509
  summary
@@ -675,6 +701,28 @@ function stripLeakedToolSummary(text) {
675
701
  if (!text.includes("step")) return text;
676
702
  return text.split("\n").filter((line) => !LEAKED_TOOL_SUMMARY_LINE.test(line)).join("\n").replace(/\n{3,}/g, "\n\n").trim();
677
703
  }
704
+ /**
705
+ * Build the `a2a-complete` notify payload. ALWAYS fired for an A2A turn —
706
+ * even when the dispatch produced no relayable text — so the chat service can
707
+ * emit an explicit terminal/stop frame instead of the thread dead-ending
708
+ * silently. `terminated`/`reason` are set when there's nothing to relay
709
+ * (`empty_turn`) or the agent ended the thread via end_conversation
710
+ * (`resolved`).
711
+ */
712
+ function buildA2ACompletePayload(params) {
713
+ const hasText = params.fullText.trim().length > 0;
714
+ const terminal = params.resolved || !hasText;
715
+ return {
716
+ conversationId: params.conversationId,
717
+ fullText: params.fullText,
718
+ a2a: params.a2a,
719
+ resolved: params.resolved,
720
+ ...terminal ? {
721
+ terminated: true,
722
+ reason: params.resolved ? "resolved" : "empty_turn"
723
+ } : {}
724
+ };
725
+ }
678
726
  let dispatchInbound = null;
679
727
  /**
680
728
  * Resolve OpenClaw SDK functions from the running process.
@@ -778,6 +826,12 @@ async function downloadAttachments(attachments, log) {
778
826
  }
779
827
  return results;
780
828
  }
829
+ let agentTurnQueue = Promise.resolve();
830
+ function enqueueAgentTurn(task) {
831
+ const run = agentTurnQueue.then(task, task);
832
+ agentTurnQueue = run.then(() => void 0, () => void 0);
833
+ return run;
834
+ }
781
835
  async function handleAgentRequest(request, log) {
782
836
  const runtime = pluginRuntime;
783
837
  if (!runtime) {
@@ -859,7 +913,7 @@ async function handleAgentRequest(request, log) {
859
913
  const userLabel = displayName ?? userId ?? senderId;
860
914
  const conversationLabel = conversationType === "group" ? shortConvId ? `[${channelLabel}] Group (${shortConvId})` : `[${channelLabel}] Group` : shortConvId ? `[${channelLabel}] ${userLabel} (${shortConvId})` : `[${channelLabel}] ${userLabel}`;
861
915
  let a2aResponseBuffer = "";
862
- let a2aResolved = false;
916
+ if (isA2A) resetA2AEndSignal();
863
917
  resolvedOpenClawKey = (await dispatchInbound({
864
918
  cfg,
865
919
  runtime: { channel: runtime.channel },
@@ -899,7 +953,8 @@ async function handleAgentRequest(request, log) {
899
953
  `This is an agent-to-agent conversation with ${a2a.sourceAgentName}.`,
900
954
  "Rules:",
901
955
  "- Only respond if you have new information, a question, or an action to coordinate.",
902
- "- If the conversation is complete, call the end_conversation() tool OR end your message with [RESOLVED].",
956
+ "- 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).",
957
+ "- Do NOT write the word \"resolved\" in prose unless you mean to end — only the exact token [RESOLVED] ends the thread.",
903
958
  "- Do NOT respond just to acknowledge — that creates infinite loops."
904
959
  ].join("\n")
905
960
  } : {}
@@ -908,10 +963,7 @@ async function handleAgentRequest(request, log) {
908
963
  const responseText = stripLeakedToolSummary(payload.text ?? "");
909
964
  const mediaUrls = [...payload.mediaUrl ? [payload.mediaUrl] : [], ...payload.mediaUrls ?? []];
910
965
  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
- }
966
+ if (isA2A) a2aResponseBuffer += responseText;
915
967
  chatClient?.notify("agent-message", {
916
968
  conversationId: conversationId ?? legacySessionKey,
917
969
  text: responseText,
@@ -927,12 +979,15 @@ async function handleAgentRequest(request, log) {
927
979
  log.error(`Dispatch error (${info.kind}): ${err instanceof Error ? err.message : String(err)}`);
928
980
  }
929
981
  })).route.sessionKey;
930
- if (isA2A && a2aResponseBuffer) chatClient?.notify("a2a-complete", {
931
- conversationId: conversationId ?? legacySessionKey,
932
- fullText: a2aResponseBuffer,
933
- a2a,
934
- resolved: a2aResolved
935
- });
982
+ if (isA2A) {
983
+ const a2aResolved = isA2AEndSignalled();
984
+ chatClient?.notify("a2a-complete", buildA2ACompletePayload({
985
+ conversationId: conversationId ?? legacySessionKey,
986
+ fullText: a2aResponseBuffer,
987
+ a2a,
988
+ resolved: a2aResolved
989
+ }));
990
+ }
936
991
  chatClient?.sendResponse(request.id, true, { sessionKey: resolvedOpenClawKey });
937
992
  log.info(`Agent dispatch complete: sessionKey=${resolvedOpenClawKey}`);
938
993
  } catch (err) {
@@ -941,6 +996,7 @@ async function handleAgentRequest(request, log) {
941
996
  chatClient?.sendResponse(request.id, false, { message: errMsg });
942
997
  } finally {
943
998
  unsubscribe();
999
+ if (isA2A) disarmA2AEndSignal();
944
1000
  }
945
1001
  }
946
1002
  async function handleSessionsList(request, log) {
@@ -1033,7 +1089,7 @@ const plugin = {
1033
1089
  apiKey,
1034
1090
  onRequest: (request) => {
1035
1091
  const handle = async () => {
1036
- if (request.method === "agent") await handleAgentRequest(request, log);
1092
+ if (request.method === "agent") await enqueueAgentTurn(() => handleAgentRequest(request, log));
1037
1093
  else if (request.method === "sessions.list") await handleSessionsList(request, log);
1038
1094
  else if (request.method === "sessions.get") await handleSessionsGet(request, log);
1039
1095
  else chatClient?.sendResponse(request.id, false, { message: `Unknown method: ${request.method}` });
@@ -1168,4 +1224,4 @@ const plugin = {
1168
1224
  }
1169
1225
  };
1170
1226
  //#endregion
1171
- export { stripLeakedToolSummary as n, createAlfeChannelPlugin as r, plugin as t };
1227
+ 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.0",
3
+ "version": "0.2.1",
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,7 +28,7 @@
28
28
  ],
29
29
  "dependencies": {
30
30
  "@alfe.ai/agent-api-client": "^0.2.2",
31
- "@alfe.ai/chat": "^0.0.9",
31
+ "@alfe.ai/chat": "^0.0.10",
32
32
  "@alfe.ai/config": "^0.0.9"
33
33
  },
34
34
  "peerDependencies": {