@alfe.ai/openclaw-chat 0.1.5 → 0.2.0

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 AlfeResolvedAccount, i as AlfePluginConfig, n as createAlfeChannelPlugin, r as AlfeChannelConfig, t as plugin } from "./plugin.cjs";
1
+ import { a as AlfePluginConfig, i as AlfeChannelConfig, o as AlfeResolvedAccount, r as createAlfeChannelPlugin, t as plugin } 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 AlfeResolvedAccount, i as AlfePluginConfig, n as createAlfeChannelPlugin, r as AlfeChannelConfig, t as plugin } from "./plugin.js";
1
+ import { a as AlfePluginConfig, i as AlfeChannelConfig, o as AlfeResolvedAccount, r as createAlfeChannelPlugin, t as plugin } 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 { n as createAlfeChannelPlugin, t as plugin } from "./plugin2.js";
1
+ import { r as createAlfeChannelPlugin, t as plugin } from "./plugin2.js";
2
2
  export { createAlfeChannelPlugin, plugin as default };
package/dist/plugin.cjs CHANGED
@@ -1,2 +1,7 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
1
5
  const require_plugin = require("./plugin2.cjs");
2
- module.exports = require_plugin.plugin;
6
+ exports.default = require_plugin.plugin;
7
+ exports.stripLeakedToolSummary = require_plugin.stripLeakedToolSummary;
package/dist/plugin.d.cts CHANGED
@@ -274,6 +274,7 @@ interface PluginServiceContext {
274
274
  stateDir: string;
275
275
  logger: PluginLogger;
276
276
  }
277
+ declare function stripLeakedToolSummary(text: string): string;
277
278
  interface PluginApi {
278
279
  logger: PluginLogger;
279
280
  registrationMode?: 'full' | 'setup-only' | 'setup-runtime' | 'cli-metadata';
@@ -306,4 +307,4 @@ declare const plugin: {
306
307
  deactivate(api: PluginApi): Promise<void>;
307
308
  };
308
309
  //#endregion
309
- export { AlfeResolvedAccount as a, AlfePluginConfig as i, createAlfeChannelPlugin as n, AlfeChannelConfig as r, plugin as t };
310
+ export { AlfePluginConfig as a, AlfeChannelConfig as i, stripLeakedToolSummary as n, AlfeResolvedAccount as o, createAlfeChannelPlugin as r, plugin as t };
package/dist/plugin.d.ts CHANGED
@@ -274,6 +274,7 @@ interface PluginServiceContext {
274
274
  stateDir: string;
275
275
  logger: PluginLogger;
276
276
  }
277
+ declare function stripLeakedToolSummary(text: string): string;
277
278
  interface PluginApi {
278
279
  logger: PluginLogger;
279
280
  registrationMode?: 'full' | 'setup-only' | 'setup-runtime' | 'cli-metadata';
@@ -306,4 +307,4 @@ declare const plugin: {
306
307
  deactivate(api: PluginApi): Promise<void>;
307
308
  };
308
309
  //#endregion
309
- export { AlfeResolvedAccount as a, AlfePluginConfig as i, createAlfeChannelPlugin as n, AlfeChannelConfig as r, plugin as t };
310
+ export { AlfePluginConfig as a, AlfeChannelConfig as i, stripLeakedToolSummary as n, AlfeResolvedAccount as o, createAlfeChannelPlugin as r, plugin as t };
package/dist/plugin.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as plugin } from "./plugin2.js";
2
- export { plugin as default };
1
+ import { n as stripLeakedToolSummary, t as plugin } from "./plugin2.js";
2
+ export { plugin as default, stripLeakedToolSummary };
package/dist/plugin2.cjs CHANGED
@@ -604,6 +604,77 @@ function validateAttachmentUrl(input, opts = {}) {
604
604
  */
605
605
  const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
606
606
  const pkg = require$1("../package.json");
607
+ function asString(v) {
608
+ return typeof v === "string" && v.length > 0 ? v : void 0;
609
+ }
610
+ /**
611
+ * Extract a thinking delta from an OpenClaw `thinking`-stream event.
612
+ * OpenClaw thinking events carry the reasoning text on one of a few
613
+ * fields depending on version; we accept `text` / `delta` / `content`.
614
+ * Returns null when there's nothing to forward.
615
+ */
616
+ function buildThinkingActivity(data) {
617
+ const text = asString(data.delta) ?? asString(data.text) ?? asString(data.content) ?? asString(data.message?.text);
618
+ if (!text) return null;
619
+ return {
620
+ kind: "thinking",
621
+ text,
622
+ delta: true
623
+ };
624
+ }
625
+ /**
626
+ * Extract structured tool status from an OpenClaw `tool`-stream event.
627
+ * We send the emoji-free raw `name` plus a lifecycle `status` and let the
628
+ * web render the `🛠️ <Label>: <summary> ✓/✗` row — never OpenClaw's raw
629
+ * `(+N steps)` string. `summary` carries OpenClaw's short detail when the
630
+ * event already provides one (title/summary/label/description), trimmed.
631
+ */
632
+ function buildToolActivity(data) {
633
+ const toolCallId = asString(data.toolCallId) ?? asString(data.id) ?? asString(data.callId);
634
+ const name = asString(data.name) ?? asString(data.tool) ?? asString(data.toolName);
635
+ if (!toolCallId || !name) return null;
636
+ const phase = asString(data.phase) ?? asString(data.state) ?? asString(data.status);
637
+ const rawStatus = (phase ?? "").toLowerCase();
638
+ let status;
639
+ if (rawStatus === "error" || rawStatus === "failed" || data.error != null || data.isError === true) status = "failed";
640
+ else if (rawStatus === "end" || rawStatus === "done" || rawStatus === "success" || rawStatus === "complete") status = "done";
641
+ else status = "running";
642
+ const summaryRaw = asString(data.summary) ?? asString(data.title) ?? asString(data.label) ?? asString(data.description);
643
+ const summary = summaryRaw ? summaryRaw.slice(0, 200) : void 0;
644
+ return {
645
+ kind: "tool",
646
+ toolCallId,
647
+ name,
648
+ ...phase ? { phase } : {},
649
+ status,
650
+ ...summary ? { summary } : {}
651
+ };
652
+ }
653
+ /**
654
+ * Workstream C, item 5: strip OpenClaw's leaked tool-summary console line
655
+ * from assistant `responseText` before it's stored / notified as a chat
656
+ * bubble. OpenClaw upstream composes a `⚠️ 🛠️ … (+N steps) failed` summary
657
+ * into the assistant text for a non-zero-exit tool run; `failed` there is a
658
+ * TOOL exit status, not a chat-level failure, and it now has a proper
659
+ * structured tool row (see buildToolActivity). This is a BOUNDED, clearly
660
+ * scoped strip of that one known pattern — it must not touch normal prose.
661
+ *
662
+ * Pattern anatomy (per the plan + observed string):
663
+ * ⚠️ 🛠️ <pipeline text> (+8 steps) failed
664
+ * ⚠️ 🛠️ <pipeline text> (+1 step) failed
665
+ * We match a line that LEADS with OpenClaw's warning/tool emoji (`⚠️` or
666
+ * `🛠️`) — REQUIRED, because OpenClaw always generates the leaked line with
667
+ * it — and contains the `(+<N> step[s])` marker followed by a trailing
668
+ * `failed`/`succeeded`/`done` word. The emoji requirement keeps legitimate
669
+ * assistant prose that merely ends in "(+3 steps) done." from being
670
+ * stripped. Only whole matching lines are removed; everything else is
671
+ * intact. Per-line, line-anchored, non-global.
672
+ */
673
+ const LEAKED_TOOL_SUMMARY_LINE = /^\s*(?:⚠️|🛠️).*\(\+\d+\s+steps?\)\s*(?:failed|succeeded|done)\.?\s*$/iu;
674
+ function stripLeakedToolSummary(text) {
675
+ if (!text.includes("step")) return text;
676
+ return text.split("\n").filter((line) => !LEAKED_TOOL_SUMMARY_LINE.test(line)).join("\n").replace(/\n{3,}/g, "\n\n").trim();
677
+ }
607
678
  let dispatchInbound = null;
608
679
  /**
609
680
  * Resolve OpenClaw SDK functions from the running process.
@@ -731,16 +802,48 @@ async function handleAgentRequest(request, log) {
731
802
  let resolvedOpenClawKey = null;
732
803
  const unsubscribe = runtime.events.onAgentEvent((evt) => {
733
804
  if (!evt.sessionKey) return;
734
- if (evt.stream !== "assistant") return;
735
- resolvedOpenClawKey ??= evt.sessionKey;
805
+ if (evt.stream === "assistant" || evt.stream === "thinking" || evt.stream === "tool") resolvedOpenClawKey ??= evt.sessionKey;
736
806
  if (evt.sessionKey !== resolvedOpenClawKey) return;
737
- chatClient?.sendEvent("chat", {
738
- runId: evt.runId,
739
- sessionKey: legacySessionKey,
740
- seq: evt.seq,
741
- state: "delta",
742
- message: evt.data
743
- });
807
+ if (evt.stream === "assistant") {
808
+ chatClient?.sendEvent("chat", {
809
+ runId: evt.runId,
810
+ sessionKey: legacySessionKey,
811
+ seq: evt.seq,
812
+ state: "delta",
813
+ message: evt.data
814
+ });
815
+ return;
816
+ }
817
+ if (evt.stream === "thinking") {
818
+ const activity = buildThinkingActivity(evt.data);
819
+ if (activity) chatClient?.sendEvent("chat-activity", {
820
+ runId: evt.runId,
821
+ sessionKey: legacySessionKey,
822
+ seq: evt.seq,
823
+ state: "activity",
824
+ activity: {
825
+ ...activity,
826
+ ts: evt.ts,
827
+ seq: evt.seq
828
+ }
829
+ });
830
+ return;
831
+ }
832
+ if (evt.stream === "tool") {
833
+ const activity = buildToolActivity(evt.data);
834
+ if (activity) chatClient?.sendEvent("chat-activity", {
835
+ runId: evt.runId,
836
+ sessionKey: legacySessionKey,
837
+ seq: evt.seq,
838
+ state: "activity",
839
+ activity: {
840
+ ...activity,
841
+ ts: evt.ts,
842
+ seq: evt.seq
843
+ }
844
+ });
845
+ return;
846
+ }
744
847
  });
745
848
  try {
746
849
  const downloadedFiles = rawAttachments?.length ? await downloadAttachments(rawAttachments, log) : [];
@@ -802,7 +905,7 @@ async function handleAgentRequest(request, log) {
802
905
  } : {}
803
906
  },
804
907
  deliver: async (payload) => {
805
- const responseText = payload.text ?? "";
908
+ const responseText = stripLeakedToolSummary(payload.text ?? "");
806
909
  const mediaUrls = [...payload.mediaUrl ? [payload.mediaUrl] : [], ...payload.mediaUrls ?? []];
807
910
  await addMessage(sessionId, "assistant", responseText);
808
911
  if (isA2A) {
@@ -1077,3 +1180,9 @@ Object.defineProperty(exports, "plugin", {
1077
1180
  return plugin;
1078
1181
  }
1079
1182
  });
1183
+ Object.defineProperty(exports, "stripLeakedToolSummary", {
1184
+ enumerable: true,
1185
+ get: function() {
1186
+ return stripLeakedToolSummary;
1187
+ }
1188
+ });
@@ -1,2 +1,2 @@
1
- import { t as plugin } from "./plugin.cjs";
2
- export { plugin as default };
1
+ import { n as stripLeakedToolSummary, t as plugin } from "./plugin.cjs";
2
+ export { plugin as default, stripLeakedToolSummary };
package/dist/plugin2.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { t as plugin } from "./plugin.js";
2
- export { plugin as default };
1
+ import { n as stripLeakedToolSummary, t as plugin } from "./plugin.js";
2
+ export { plugin as default, stripLeakedToolSummary };
package/dist/plugin2.js CHANGED
@@ -604,6 +604,77 @@ function validateAttachmentUrl(input, opts = {}) {
604
604
  */
605
605
  const require = createRequire(import.meta.url);
606
606
  const pkg = require("../package.json");
607
+ function asString(v) {
608
+ return typeof v === "string" && v.length > 0 ? v : void 0;
609
+ }
610
+ /**
611
+ * Extract a thinking delta from an OpenClaw `thinking`-stream event.
612
+ * OpenClaw thinking events carry the reasoning text on one of a few
613
+ * fields depending on version; we accept `text` / `delta` / `content`.
614
+ * Returns null when there's nothing to forward.
615
+ */
616
+ function buildThinkingActivity(data) {
617
+ const text = asString(data.delta) ?? asString(data.text) ?? asString(data.content) ?? asString(data.message?.text);
618
+ if (!text) return null;
619
+ return {
620
+ kind: "thinking",
621
+ text,
622
+ delta: true
623
+ };
624
+ }
625
+ /**
626
+ * Extract structured tool status from an OpenClaw `tool`-stream event.
627
+ * We send the emoji-free raw `name` plus a lifecycle `status` and let the
628
+ * web render the `🛠️ <Label>: <summary> ✓/✗` row — never OpenClaw's raw
629
+ * `(+N steps)` string. `summary` carries OpenClaw's short detail when the
630
+ * event already provides one (title/summary/label/description), trimmed.
631
+ */
632
+ function buildToolActivity(data) {
633
+ const toolCallId = asString(data.toolCallId) ?? asString(data.id) ?? asString(data.callId);
634
+ const name = asString(data.name) ?? asString(data.tool) ?? asString(data.toolName);
635
+ if (!toolCallId || !name) return null;
636
+ const phase = asString(data.phase) ?? asString(data.state) ?? asString(data.status);
637
+ const rawStatus = (phase ?? "").toLowerCase();
638
+ let status;
639
+ if (rawStatus === "error" || rawStatus === "failed" || data.error != null || data.isError === true) status = "failed";
640
+ else if (rawStatus === "end" || rawStatus === "done" || rawStatus === "success" || rawStatus === "complete") status = "done";
641
+ else status = "running";
642
+ const summaryRaw = asString(data.summary) ?? asString(data.title) ?? asString(data.label) ?? asString(data.description);
643
+ const summary = summaryRaw ? summaryRaw.slice(0, 200) : void 0;
644
+ return {
645
+ kind: "tool",
646
+ toolCallId,
647
+ name,
648
+ ...phase ? { phase } : {},
649
+ status,
650
+ ...summary ? { summary } : {}
651
+ };
652
+ }
653
+ /**
654
+ * Workstream C, item 5: strip OpenClaw's leaked tool-summary console line
655
+ * from assistant `responseText` before it's stored / notified as a chat
656
+ * bubble. OpenClaw upstream composes a `⚠️ 🛠️ … (+N steps) failed` summary
657
+ * into the assistant text for a non-zero-exit tool run; `failed` there is a
658
+ * TOOL exit status, not a chat-level failure, and it now has a proper
659
+ * structured tool row (see buildToolActivity). This is a BOUNDED, clearly
660
+ * scoped strip of that one known pattern — it must not touch normal prose.
661
+ *
662
+ * Pattern anatomy (per the plan + observed string):
663
+ * ⚠️ 🛠️ <pipeline text> (+8 steps) failed
664
+ * ⚠️ 🛠️ <pipeline text> (+1 step) failed
665
+ * We match a line that LEADS with OpenClaw's warning/tool emoji (`⚠️` or
666
+ * `🛠️`) — REQUIRED, because OpenClaw always generates the leaked line with
667
+ * it — and contains the `(+<N> step[s])` marker followed by a trailing
668
+ * `failed`/`succeeded`/`done` word. The emoji requirement keeps legitimate
669
+ * assistant prose that merely ends in "(+3 steps) done." from being
670
+ * stripped. Only whole matching lines are removed; everything else is
671
+ * intact. Per-line, line-anchored, non-global.
672
+ */
673
+ const LEAKED_TOOL_SUMMARY_LINE = /^\s*(?:⚠️|🛠️).*\(\+\d+\s+steps?\)\s*(?:failed|succeeded|done)\.?\s*$/iu;
674
+ function stripLeakedToolSummary(text) {
675
+ if (!text.includes("step")) return text;
676
+ return text.split("\n").filter((line) => !LEAKED_TOOL_SUMMARY_LINE.test(line)).join("\n").replace(/\n{3,}/g, "\n\n").trim();
677
+ }
607
678
  let dispatchInbound = null;
608
679
  /**
609
680
  * Resolve OpenClaw SDK functions from the running process.
@@ -731,16 +802,48 @@ async function handleAgentRequest(request, log) {
731
802
  let resolvedOpenClawKey = null;
732
803
  const unsubscribe = runtime.events.onAgentEvent((evt) => {
733
804
  if (!evt.sessionKey) return;
734
- if (evt.stream !== "assistant") return;
735
- resolvedOpenClawKey ??= evt.sessionKey;
805
+ if (evt.stream === "assistant" || evt.stream === "thinking" || evt.stream === "tool") resolvedOpenClawKey ??= evt.sessionKey;
736
806
  if (evt.sessionKey !== resolvedOpenClawKey) return;
737
- chatClient?.sendEvent("chat", {
738
- runId: evt.runId,
739
- sessionKey: legacySessionKey,
740
- seq: evt.seq,
741
- state: "delta",
742
- message: evt.data
743
- });
807
+ if (evt.stream === "assistant") {
808
+ chatClient?.sendEvent("chat", {
809
+ runId: evt.runId,
810
+ sessionKey: legacySessionKey,
811
+ seq: evt.seq,
812
+ state: "delta",
813
+ message: evt.data
814
+ });
815
+ return;
816
+ }
817
+ if (evt.stream === "thinking") {
818
+ const activity = buildThinkingActivity(evt.data);
819
+ if (activity) chatClient?.sendEvent("chat-activity", {
820
+ runId: evt.runId,
821
+ sessionKey: legacySessionKey,
822
+ seq: evt.seq,
823
+ state: "activity",
824
+ activity: {
825
+ ...activity,
826
+ ts: evt.ts,
827
+ seq: evt.seq
828
+ }
829
+ });
830
+ return;
831
+ }
832
+ if (evt.stream === "tool") {
833
+ const activity = buildToolActivity(evt.data);
834
+ if (activity) chatClient?.sendEvent("chat-activity", {
835
+ runId: evt.runId,
836
+ sessionKey: legacySessionKey,
837
+ seq: evt.seq,
838
+ state: "activity",
839
+ activity: {
840
+ ...activity,
841
+ ts: evt.ts,
842
+ seq: evt.seq
843
+ }
844
+ });
845
+ return;
846
+ }
744
847
  });
745
848
  try {
746
849
  const downloadedFiles = rawAttachments?.length ? await downloadAttachments(rawAttachments, log) : [];
@@ -802,7 +905,7 @@ async function handleAgentRequest(request, log) {
802
905
  } : {}
803
906
  },
804
907
  deliver: async (payload) => {
805
- const responseText = payload.text ?? "";
908
+ const responseText = stripLeakedToolSummary(payload.text ?? "");
806
909
  const mediaUrls = [...payload.mediaUrl ? [payload.mediaUrl] : [], ...payload.mediaUrls ?? []];
807
910
  await addMessage(sessionId, "assistant", responseText);
808
911
  if (isA2A) {
@@ -1065,4 +1168,4 @@ const plugin = {
1065
1168
  }
1066
1169
  };
1067
1170
  //#endregion
1068
- export { createAlfeChannelPlugin as n, plugin as t };
1171
+ export { stripLeakedToolSummary as n, createAlfeChannelPlugin as r, plugin as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/openclaw-chat",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "description": "OpenClaw chat plugin for Alfe — web widget and mobile app channels",
5
5
  "type": "module",
6
6
  "main": "./dist/plugin.js",
@@ -27,7 +27,7 @@
27
27
  "openclaw.plugin.json"
28
28
  ],
29
29
  "dependencies": {
30
- "@alfe.ai/agent-api-client": "^0.1.4",
30
+ "@alfe.ai/agent-api-client": "^0.2.2",
31
31
  "@alfe.ai/chat": "^0.0.9",
32
32
  "@alfe.ai/config": "^0.0.9"
33
33
  },