@alfe.ai/openclaw-chat 0.9.12 → 0.9.14

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/plugin2.cjs CHANGED
@@ -34,8 +34,8 @@ let _alfe_ai_config = require("@alfe.ai/config");
34
34
  * - The size cap mirrors the presign endpoint's 25 MB / 10-files limits —
35
35
  * NOT the plugin's 50 MB inbound download cap — so oversize files fail
36
36
  * fast locally instead of as an opaque presign 400.
37
- * - The presigned PUT is signed with `ContentType`; the raw fetch MUST send
38
- * a matching `Content-Type` header. (This raw fetch to a presigned S3 URL
37
+ * - The presigned PUT is signed with `ContentType` and encryption headers;
38
+ * the raw fetch MUST send both. (This raw fetch to a presigned S3 URL
39
39
  * is the sanctioned exception to the no-raw-fetch rule.)
40
40
  * - `resolveConfig()` throws on self-hosted agents without env/config.toml
41
41
  * (and on unknown token prefixes). With no client, local refs resolve to
@@ -207,7 +207,10 @@ async function uploadLocalFile(localPath, log, deps) {
207
207
  try {
208
208
  const res = await fetchFn(presigned.uploadUrl, {
209
209
  method: "PUT",
210
- headers: { "Content-Type": mimeType },
210
+ headers: {
211
+ ...presigned.uploadHeaders,
212
+ "Content-Type": mimeType
213
+ },
211
214
  body,
212
215
  signal: controller.signal
213
216
  });
@@ -2263,8 +2266,9 @@ function buildToolActivity(data) {
2263
2266
  /**
2264
2267
  * Workstream C, item 5: strip OpenClaw's leaked tool-summary console line
2265
2268
  * from assistant `responseText` before it's stored / notified as a chat
2266
- * bubble. OpenClaw upstream composes a `⚠️ 🛠️ … (+N steps) failed` summary
2267
- * into the assistant text for a non-zero-exit tool run; `failed` there is a
2269
+ * bubble. OpenClaw upstream composes either a `⚠️ 🛠️ … (+N steps) failed`
2270
+ * summary or a step-count-free `⚠️ 🛠️ … → … failed` pipeline into the
2271
+ * assistant text for a non-zero-exit tool run; `failed` there is a
2268
2272
  * TOOL exit status, not a chat-level failure, and it now has a proper
2269
2273
  * structured tool row (see buildToolActivity). This is a BOUNDED, clearly
2270
2274
  * scoped strip of that one known pattern — it must not touch normal prose.
@@ -2281,9 +2285,10 @@ function buildToolActivity(data) {
2281
2285
  * intact. Per-line, line-anchored, non-global.
2282
2286
  */
2283
2287
  const LEAKED_TOOL_SUMMARY_LINE = /^\s*(?:⚠️|🛠️).*\(\+\d+\s+steps?\)\s*(?:failed|succeeded|done)\.?\s*$/iu;
2288
+ const LEAKED_TOOL_PIPELINE_LINE = /^\s*⚠️\s*🛠️\s+.+(?:\s+→\s+.+)+\s+(?:failed|succeeded|done)\.?\s*$/iu;
2284
2289
  function stripLeakedToolSummary(text) {
2285
- if (!text.includes("step")) return text;
2286
- return text.split("\n").filter((line) => !LEAKED_TOOL_SUMMARY_LINE.test(line)).join("\n").replace(/\n{3,}/g, "\n\n").trim();
2290
+ if (!text.includes("step") && !(text.includes("⚠️") && text.includes("🛠️") && text.includes("→"))) return text;
2291
+ return text.split("\n").filter((line) => !LEAKED_TOOL_SUMMARY_LINE.test(line) && !LEAKED_TOOL_PIPELINE_LINE.test(line)).join("\n").replace(/\n{3,}/g, "\n\n").trim();
2287
2292
  }
2288
2293
  /**
2289
2294
  * Build the `a2a-complete` notify payload. ALWAYS fired for an A2A turn —
@@ -2778,16 +2783,17 @@ function resolveAbortTargetKeys(active, abortSessionKey) {
2778
2783
  return keys;
2779
2784
  }
2780
2785
  /**
2781
- * `chat.abort` handler — genuinely cancels the RUNNING turn on the daemon.
2786
+ * `chat.abort` handler — attempts to cancel the RUNNING turn on the daemon.
2782
2787
  *
2783
2788
  * Invoked OUT-OF-BAND (never through the serialised agent-turn queue) so it
2784
2789
  * executes immediately while a turn is mid-flight. Resolves the live run's
2785
2790
  * sessionId from the target session key and fires the run handle's
2786
2791
  * AbortController via `abortEmbeddedAgentRun`. The in-flight dispatch then
2787
2792
  * settles (AbortError), so `handleAgentRequest` sends its RPC response and the
2788
- * chat service seals the UI. When the abort primitives are unavailable (older
2789
- * openclaw), this degrades to a logged no-op: the UI is still sealed by the
2790
- * chat service's optimistic idle broadcast, but the turn completes server-side.
2793
+ * chat service can settle the UI. When the abort primitives are unavailable
2794
+ * (older OpenClaw), this returns `{aborted:false}`. The service must keep the
2795
+ * turn active until its real terminal frame because the response may still be
2796
+ * persisted.
2791
2797
  */
2792
2798
  function handleChatAbort(request, log) {
2793
2799
  const { sessionKey } = request.params;
@@ -3061,6 +3067,7 @@ async function handleAgentRequest(request, log) {
3061
3067
  const conversationLabel = conversationType === "group" ? shortConvId ? `[${channelLabel}] Group (${shortConvId})` : `[${channelLabel}] Group` : shortConvId ? `[${channelLabel}] ${userLabel} (${shortConvId})` : `[${channelLabel}] ${userLabel}`;
3062
3068
  let a2aResponseBuffer = "";
3063
3069
  const outboundMediaCtx = createOutboundMediaContext();
3070
+ let visibleDeliveryIndex = 0;
3064
3071
  if (isA2A) resetA2AEndSignal();
3065
3072
  const peer = conversationType === "group" ? {
3066
3073
  kind: "group",
@@ -3126,6 +3133,8 @@ async function handleAgentRequest(request, log) {
3126
3133
  const note = formatMediaFailureNote(media.newFailures);
3127
3134
  responseText = responseText ? `${responseText}\n\n${note}` : note;
3128
3135
  }
3136
+ if (!responseText && media.mediaUrls.length === 0) return;
3137
+ const deliveryIndex = visibleDeliveryIndex++;
3129
3138
  await addMessage(sessionId, "assistant", responseText, void 0, void 0, void 0, assistantMediaToStored(media.attachments));
3130
3139
  if (isA2A) a2aResponseBuffer += (a2aResponseBuffer && responseText ? "\n\n" : "") + responseText;
3131
3140
  chatClient?.notify("agent-message", {
@@ -3133,6 +3142,7 @@ async function handleAgentRequest(request, log) {
3133
3142
  text: responseText,
3134
3143
  sessionKey: runGate.sessionKey ?? legacySessionKey,
3135
3144
  messageId: chatMessageId ?? request.id,
3145
+ deliveryIndex,
3136
3146
  ...media.mediaUrls.length ? {
3137
3147
  mediaUrls: media.mediaUrls,
3138
3148
  attachments: media.attachments
package/dist/plugin2.js CHANGED
@@ -34,8 +34,8 @@ import { resolveConfig } from "@alfe.ai/config";
34
34
  * - The size cap mirrors the presign endpoint's 25 MB / 10-files limits —
35
35
  * NOT the plugin's 50 MB inbound download cap — so oversize files fail
36
36
  * fast locally instead of as an opaque presign 400.
37
- * - The presigned PUT is signed with `ContentType`; the raw fetch MUST send
38
- * a matching `Content-Type` header. (This raw fetch to a presigned S3 URL
37
+ * - The presigned PUT is signed with `ContentType` and encryption headers;
38
+ * the raw fetch MUST send both. (This raw fetch to a presigned S3 URL
39
39
  * is the sanctioned exception to the no-raw-fetch rule.)
40
40
  * - `resolveConfig()` throws on self-hosted agents without env/config.toml
41
41
  * (and on unknown token prefixes). With no client, local refs resolve to
@@ -207,7 +207,10 @@ async function uploadLocalFile(localPath, log, deps) {
207
207
  try {
208
208
  const res = await fetchFn(presigned.uploadUrl, {
209
209
  method: "PUT",
210
- headers: { "Content-Type": mimeType },
210
+ headers: {
211
+ ...presigned.uploadHeaders,
212
+ "Content-Type": mimeType
213
+ },
211
214
  body,
212
215
  signal: controller.signal
213
216
  });
@@ -2263,8 +2266,9 @@ function buildToolActivity(data) {
2263
2266
  /**
2264
2267
  * Workstream C, item 5: strip OpenClaw's leaked tool-summary console line
2265
2268
  * from assistant `responseText` before it's stored / notified as a chat
2266
- * bubble. OpenClaw upstream composes a `⚠️ 🛠️ … (+N steps) failed` summary
2267
- * into the assistant text for a non-zero-exit tool run; `failed` there is a
2269
+ * bubble. OpenClaw upstream composes either a `⚠️ 🛠️ … (+N steps) failed`
2270
+ * summary or a step-count-free `⚠️ 🛠️ … → … failed` pipeline into the
2271
+ * assistant text for a non-zero-exit tool run; `failed` there is a
2268
2272
  * TOOL exit status, not a chat-level failure, and it now has a proper
2269
2273
  * structured tool row (see buildToolActivity). This is a BOUNDED, clearly
2270
2274
  * scoped strip of that one known pattern — it must not touch normal prose.
@@ -2281,9 +2285,10 @@ function buildToolActivity(data) {
2281
2285
  * intact. Per-line, line-anchored, non-global.
2282
2286
  */
2283
2287
  const LEAKED_TOOL_SUMMARY_LINE = /^\s*(?:⚠️|🛠️).*\(\+\d+\s+steps?\)\s*(?:failed|succeeded|done)\.?\s*$/iu;
2288
+ const LEAKED_TOOL_PIPELINE_LINE = /^\s*⚠️\s*🛠️\s+.+(?:\s+→\s+.+)+\s+(?:failed|succeeded|done)\.?\s*$/iu;
2284
2289
  function stripLeakedToolSummary(text) {
2285
- if (!text.includes("step")) return text;
2286
- return text.split("\n").filter((line) => !LEAKED_TOOL_SUMMARY_LINE.test(line)).join("\n").replace(/\n{3,}/g, "\n\n").trim();
2290
+ if (!text.includes("step") && !(text.includes("⚠️") && text.includes("🛠️") && text.includes("→"))) return text;
2291
+ return text.split("\n").filter((line) => !LEAKED_TOOL_SUMMARY_LINE.test(line) && !LEAKED_TOOL_PIPELINE_LINE.test(line)).join("\n").replace(/\n{3,}/g, "\n\n").trim();
2287
2292
  }
2288
2293
  /**
2289
2294
  * Build the `a2a-complete` notify payload. ALWAYS fired for an A2A turn —
@@ -2778,16 +2783,17 @@ function resolveAbortTargetKeys(active, abortSessionKey) {
2778
2783
  return keys;
2779
2784
  }
2780
2785
  /**
2781
- * `chat.abort` handler — genuinely cancels the RUNNING turn on the daemon.
2786
+ * `chat.abort` handler — attempts to cancel the RUNNING turn on the daemon.
2782
2787
  *
2783
2788
  * Invoked OUT-OF-BAND (never through the serialised agent-turn queue) so it
2784
2789
  * executes immediately while a turn is mid-flight. Resolves the live run's
2785
2790
  * sessionId from the target session key and fires the run handle's
2786
2791
  * AbortController via `abortEmbeddedAgentRun`. The in-flight dispatch then
2787
2792
  * settles (AbortError), so `handleAgentRequest` sends its RPC response and the
2788
- * chat service seals the UI. When the abort primitives are unavailable (older
2789
- * openclaw), this degrades to a logged no-op: the UI is still sealed by the
2790
- * chat service's optimistic idle broadcast, but the turn completes server-side.
2793
+ * chat service can settle the UI. When the abort primitives are unavailable
2794
+ * (older OpenClaw), this returns `{aborted:false}`. The service must keep the
2795
+ * turn active until its real terminal frame because the response may still be
2796
+ * persisted.
2791
2797
  */
2792
2798
  function handleChatAbort(request, log) {
2793
2799
  const { sessionKey } = request.params;
@@ -3061,6 +3067,7 @@ async function handleAgentRequest(request, log) {
3061
3067
  const conversationLabel = conversationType === "group" ? shortConvId ? `[${channelLabel}] Group (${shortConvId})` : `[${channelLabel}] Group` : shortConvId ? `[${channelLabel}] ${userLabel} (${shortConvId})` : `[${channelLabel}] ${userLabel}`;
3062
3068
  let a2aResponseBuffer = "";
3063
3069
  const outboundMediaCtx = createOutboundMediaContext();
3070
+ let visibleDeliveryIndex = 0;
3064
3071
  if (isA2A) resetA2AEndSignal();
3065
3072
  const peer = conversationType === "group" ? {
3066
3073
  kind: "group",
@@ -3126,6 +3133,8 @@ async function handleAgentRequest(request, log) {
3126
3133
  const note = formatMediaFailureNote(media.newFailures);
3127
3134
  responseText = responseText ? `${responseText}\n\n${note}` : note;
3128
3135
  }
3136
+ if (!responseText && media.mediaUrls.length === 0) return;
3137
+ const deliveryIndex = visibleDeliveryIndex++;
3129
3138
  await addMessage(sessionId, "assistant", responseText, void 0, void 0, void 0, assistantMediaToStored(media.attachments));
3130
3139
  if (isA2A) a2aResponseBuffer += (a2aResponseBuffer && responseText ? "\n\n" : "") + responseText;
3131
3140
  chatClient?.notify("agent-message", {
@@ -3133,6 +3142,7 @@ async function handleAgentRequest(request, log) {
3133
3142
  text: responseText,
3134
3143
  sessionKey: runGate.sessionKey ?? legacySessionKey,
3135
3144
  messageId: chatMessageId ?? request.id,
3145
+ deliveryIndex,
3136
3146
  ...media.mediaUrls.length ? {
3137
3147
  mediaUrls: media.mediaUrls,
3138
3148
  attachments: media.attachments
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/openclaw-chat",
3
- "version": "0.9.12",
3
+ "version": "0.9.14",
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,10 +27,10 @@
27
27
  "openclaw.plugin.json"
28
28
  ],
29
29
  "dependencies": {
30
- "@alfe.ai/agent-api-client": "^0.16.0",
31
30
  "@alfe.ai/chat": "^0.0.18",
32
- "@alfe.ai/config": "^0.4.1",
33
- "@alfe.ai/openclaw-plugin-kit": "0.2.0"
31
+ "@alfe.ai/openclaw-plugin-kit": "0.2.0",
32
+ "@alfe.ai/agent-api-client": "^0.17.0",
33
+ "@alfe.ai/config": "^0.4.1"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "openclaw": ">=2026.3.0"