@alfe.ai/openclaw-chat 0.9.14 → 0.9.15

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,2 +1,2 @@
1
- import { A as SessionData, D as AlfePluginConfig, E as AlfeChannelConfig, O as AlfeResolvedAccount, T as createAlfeChannelPlugin, b as plugin, j as SessionSummary, k as ChatMessage } from "./plugin.cjs";
1
+ import { A as AlfeResolvedAccount, D as createAlfeChannelPlugin, M as SessionData, N as SessionSummary, O as AlfeChannelConfig, b as plugin, j as ChatMessage, k as AlfePluginConfig } from "./plugin.cjs";
2
2
  export { type AlfeChannelConfig, type AlfePluginConfig, type AlfeResolvedAccount, type ChatMessage, type SessionData, type SessionSummary, createAlfeChannelPlugin, plugin as default };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as SessionData, D as AlfePluginConfig, E as AlfeChannelConfig, O as AlfeResolvedAccount, T as createAlfeChannelPlugin, b as plugin, j as SessionSummary, k as ChatMessage } from "./plugin.js";
1
+ import { A as AlfeResolvedAccount, D as createAlfeChannelPlugin, M as SessionData, N as SessionSummary, O as AlfeChannelConfig, b as plugin, j as ChatMessage, k as AlfePluginConfig } from "./plugin.js";
2
2
  export { type AlfeChannelConfig, type AlfePluginConfig, type AlfeResolvedAccount, type ChatMessage, type SessionData, type SessionSummary, createAlfeChannelPlugin, plugin as default };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { S as createAlfeChannelPlugin, _ as plugin } from "./plugin2.js";
1
+ import { _ as plugin, w as createAlfeChannelPlugin } from "./plugin2.js";
2
2
  export { createAlfeChannelPlugin, plugin as default };
package/dist/plugin.cjs CHANGED
@@ -22,5 +22,7 @@ exports.default = require_plugin.plugin;
22
22
  exports.joinAssistantText = require_plugin.joinAssistantText;
23
23
  exports.readAttachmentBodyBounded = require_plugin.readAttachmentBodyBounded;
24
24
  exports.resolveAbortTargetKeys = require_plugin.resolveAbortTargetKeys;
25
+ exports.resolveRequestedRunId = require_plugin.resolveRequestedRunId;
25
26
  exports.rewriteAssistantText = require_plugin.rewriteAssistantText;
26
27
  exports.stripLeakedToolSummary = require_plugin.stripLeakedToolSummary;
28
+ exports.withReplyRunId = require_plugin.withReplyRunId;
package/dist/plugin.d.cts CHANGED
@@ -214,6 +214,7 @@ interface AlfeOutboundSessionSummary {
214
214
  interface AlfeOutboundSession {
215
215
  sessionId: string;
216
216
  userId?: string;
217
+ tenantId?: string;
217
218
  }
218
219
  interface AlfeChannelOutboundDeps {
219
220
  getChatClient: () => AlfeOutboundChatClient | null;
@@ -480,6 +481,13 @@ interface RunEventGate {
480
481
  * pinned against real 2026.6.10 event shapes.
481
482
  */
482
483
  declare function admitAgentEvent(gate: RunEventGate, evt: Pick<AgentEventPayload, 'runId' | 'stream' | 'sessionKey'>): boolean;
484
+ /**
485
+ * Seed OpenClaw's runtime/event id with the chat service request that owns the
486
+ * turn. Older callers omit the idempotency key and retain a runtime-owned id.
487
+ */
488
+ declare function resolveRequestedRunId(idempotencyKey: unknown): string | null;
489
+ /** Preserve OpenClaw-provided reply options while seeding this exact run id. */
490
+ declare function withReplyRunId(params: Record<string, unknown>, runId: string): Record<string, unknown>;
483
491
  interface PluginServiceContext {
484
492
  config: Record<string, unknown>;
485
493
  workspaceDir?: string;
@@ -667,6 +675,7 @@ declare function buildAttachmentLocalFilename(att: Pick<AttachmentRef, 'id' | 'f
667
675
  declare function enqueueAgentTurn(task: () => Promise<void>, opts?: {
668
676
  coalesceKey?: string;
669
677
  onSuperseded?: () => void;
678
+ onStarted?: () => void;
670
679
  }): Promise<void>;
671
680
  /**
672
681
  * Test-only: reset the agent-turn backlog between unit tests. Not used at
@@ -681,6 +690,7 @@ declare const __agentTurnQueueForTest: {
681
690
  interface ActiveRunRef {
682
691
  requestSessionKey: string;
683
692
  routeSessionKey: string | null;
693
+ runId?: string;
684
694
  }
685
695
  /** Test-only: set/clear the active-run ref for handleChatAbort unit tests. */
686
696
  declare function __setActiveRunForTest(ref: ActiveRunRef | null): void;
@@ -692,7 +702,7 @@ declare function __setActiveRunForTest(ref: ActiveRunRef | null): void;
692
702
  * sessionKey that does NOT match the running turn yields no candidates — we
693
703
  * never abort another session's run on a shared daemon.
694
704
  */
695
- declare function resolveAbortTargetKeys(active: ActiveRunRef | null, abortSessionKey: string | undefined): string[];
705
+ declare function resolveAbortTargetKeys(active: ActiveRunRef | null, abortSessionKey: string | undefined, abortRunId?: string): string[];
696
706
  /**
697
707
  * Speech-style reply guidance injected into the agent's SYSTEM PROMPT for a
698
708
  * live voice-call turn (`origin:'voice'`). The user is speaking on a call and
@@ -755,4 +765,4 @@ declare const plugin: {
755
765
  deactivate(api: PluginApi): Promise<void>;
756
766
  };
757
767
  //#endregion
758
- export { SessionData as A, rewriteAssistantText as C, AlfePluginConfig as D, AlfeChannelConfig as E, AlfeResolvedAccount as O, resolveAbortTargetKeys as S, createAlfeChannelPlugin as T, computeOpenClawSdkAnchors as _, MAX_INBOUND_ATTACHMENT_SIZE as a, plugin as b, __agentTurnQueueForTest as c, admitAgentEvent as d, buildA2ACompletePayload as f, buildTurnEnvelopeContext as g, buildToolActivity as h, CROSS_AGENT_TOOLS_SYSTEM_PROMPT as i, SessionSummary as j, ChatMessage as k, __resetAgentTurnQueueForTest as l, buildOriginEnvelopeContext as m, ActiveRunRef as n, RunEventGate as o, buildAttachmentLocalFilename as p, AnchorEnv as r, VOICE_REPLY_SYSTEM_PROMPT as s, A2A_REPLY_SYSTEM_PROMPT as t, __setActiveRunForTest as u, decideChatServiceStart as v, stripLeakedToolSummary as w, readAttachmentBodyBounded as x, joinAssistantText as y };
768
+ export { AlfeResolvedAccount as A, resolveRequestedRunId as C, createAlfeChannelPlugin as D, withReplyRunId as E, SessionData as M, SessionSummary as N, AlfeChannelConfig as O, resolveAbortTargetKeys as S, stripLeakedToolSummary as T, computeOpenClawSdkAnchors as _, MAX_INBOUND_ATTACHMENT_SIZE as a, plugin as b, __agentTurnQueueForTest as c, admitAgentEvent as d, buildA2ACompletePayload as f, buildTurnEnvelopeContext as g, buildToolActivity as h, CROSS_AGENT_TOOLS_SYSTEM_PROMPT as i, ChatMessage as j, AlfePluginConfig as k, __resetAgentTurnQueueForTest as l, buildOriginEnvelopeContext as m, ActiveRunRef as n, RunEventGate as o, buildAttachmentLocalFilename as p, AnchorEnv as r, VOICE_REPLY_SYSTEM_PROMPT as s, A2A_REPLY_SYSTEM_PROMPT as t, __setActiveRunForTest as u, decideChatServiceStart as v, rewriteAssistantText as w, readAttachmentBodyBounded as x, joinAssistantText as y };
package/dist/plugin.d.ts CHANGED
@@ -214,6 +214,7 @@ interface AlfeOutboundSessionSummary {
214
214
  interface AlfeOutboundSession {
215
215
  sessionId: string;
216
216
  userId?: string;
217
+ tenantId?: string;
217
218
  }
218
219
  interface AlfeChannelOutboundDeps {
219
220
  getChatClient: () => AlfeOutboundChatClient | null;
@@ -480,6 +481,13 @@ interface RunEventGate {
480
481
  * pinned against real 2026.6.10 event shapes.
481
482
  */
482
483
  declare function admitAgentEvent(gate: RunEventGate, evt: Pick<AgentEventPayload, 'runId' | 'stream' | 'sessionKey'>): boolean;
484
+ /**
485
+ * Seed OpenClaw's runtime/event id with the chat service request that owns the
486
+ * turn. Older callers omit the idempotency key and retain a runtime-owned id.
487
+ */
488
+ declare function resolveRequestedRunId(idempotencyKey: unknown): string | null;
489
+ /** Preserve OpenClaw-provided reply options while seeding this exact run id. */
490
+ declare function withReplyRunId(params: Record<string, unknown>, runId: string): Record<string, unknown>;
483
491
  interface PluginServiceContext {
484
492
  config: Record<string, unknown>;
485
493
  workspaceDir?: string;
@@ -667,6 +675,7 @@ declare function buildAttachmentLocalFilename(att: Pick<AttachmentRef, 'id' | 'f
667
675
  declare function enqueueAgentTurn(task: () => Promise<void>, opts?: {
668
676
  coalesceKey?: string;
669
677
  onSuperseded?: () => void;
678
+ onStarted?: () => void;
670
679
  }): Promise<void>;
671
680
  /**
672
681
  * Test-only: reset the agent-turn backlog between unit tests. Not used at
@@ -681,6 +690,7 @@ declare const __agentTurnQueueForTest: {
681
690
  interface ActiveRunRef {
682
691
  requestSessionKey: string;
683
692
  routeSessionKey: string | null;
693
+ runId?: string;
684
694
  }
685
695
  /** Test-only: set/clear the active-run ref for handleChatAbort unit tests. */
686
696
  declare function __setActiveRunForTest(ref: ActiveRunRef | null): void;
@@ -692,7 +702,7 @@ declare function __setActiveRunForTest(ref: ActiveRunRef | null): void;
692
702
  * sessionKey that does NOT match the running turn yields no candidates — we
693
703
  * never abort another session's run on a shared daemon.
694
704
  */
695
- declare function resolveAbortTargetKeys(active: ActiveRunRef | null, abortSessionKey: string | undefined): string[];
705
+ declare function resolveAbortTargetKeys(active: ActiveRunRef | null, abortSessionKey: string | undefined, abortRunId?: string): string[];
696
706
  /**
697
707
  * Speech-style reply guidance injected into the agent's SYSTEM PROMPT for a
698
708
  * live voice-call turn (`origin:'voice'`). The user is speaking on a call and
@@ -755,4 +765,4 @@ declare const plugin: {
755
765
  deactivate(api: PluginApi): Promise<void>;
756
766
  };
757
767
  //#endregion
758
- export { SessionData as A, rewriteAssistantText as C, AlfePluginConfig as D, AlfeChannelConfig as E, AlfeResolvedAccount as O, resolveAbortTargetKeys as S, createAlfeChannelPlugin as T, computeOpenClawSdkAnchors as _, MAX_INBOUND_ATTACHMENT_SIZE as a, plugin as b, __agentTurnQueueForTest as c, admitAgentEvent as d, buildA2ACompletePayload as f, buildTurnEnvelopeContext as g, buildToolActivity as h, CROSS_AGENT_TOOLS_SYSTEM_PROMPT as i, SessionSummary as j, ChatMessage as k, __resetAgentTurnQueueForTest as l, buildOriginEnvelopeContext as m, ActiveRunRef as n, RunEventGate as o, buildAttachmentLocalFilename as p, AnchorEnv as r, VOICE_REPLY_SYSTEM_PROMPT as s, A2A_REPLY_SYSTEM_PROMPT as t, __setActiveRunForTest as u, decideChatServiceStart as v, stripLeakedToolSummary as w, readAttachmentBodyBounded as x, joinAssistantText as y };
768
+ export { AlfeResolvedAccount as A, resolveRequestedRunId as C, createAlfeChannelPlugin as D, withReplyRunId as E, SessionData as M, SessionSummary as N, AlfeChannelConfig as O, resolveAbortTargetKeys as S, stripLeakedToolSummary as T, computeOpenClawSdkAnchors as _, MAX_INBOUND_ATTACHMENT_SIZE as a, plugin as b, __agentTurnQueueForTest as c, admitAgentEvent as d, buildA2ACompletePayload as f, buildTurnEnvelopeContext as g, buildToolActivity as h, CROSS_AGENT_TOOLS_SYSTEM_PROMPT as i, ChatMessage as j, AlfePluginConfig as k, __resetAgentTurnQueueForTest as l, buildOriginEnvelopeContext as m, ActiveRunRef as n, RunEventGate as o, buildAttachmentLocalFilename as p, AnchorEnv as r, VOICE_REPLY_SYSTEM_PROMPT as s, A2A_REPLY_SYSTEM_PROMPT as t, __setActiveRunForTest as u, decideChatServiceStart as v, rewriteAssistantText as w, readAttachmentBodyBounded as x, joinAssistantText as y };
package/dist/plugin.js CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as plugin, a as __agentTurnQueueForTest, b as rewriteAssistantText, c as admitAgentEvent, d as buildOriginEnvelopeContext, f as buildToolActivity, g as joinAssistantText, h as decideChatServiceStart, i as VOICE_REPLY_SYSTEM_PROMPT, l as buildA2ACompletePayload, m as computeOpenClawSdkAnchors, n as CROSS_AGENT_TOOLS_SYSTEM_PROMPT, o as __resetAgentTurnQueueForTest, p as buildTurnEnvelopeContext, r as MAX_INBOUND_ATTACHMENT_SIZE, s as __setActiveRunForTest, t as A2A_REPLY_SYSTEM_PROMPT, u as buildAttachmentLocalFilename, v as readAttachmentBodyBounded, x as stripLeakedToolSummary, y as resolveAbortTargetKeys } from "./plugin2.js";
2
- export { A2A_REPLY_SYSTEM_PROMPT, CROSS_AGENT_TOOLS_SYSTEM_PROMPT, MAX_INBOUND_ATTACHMENT_SIZE, VOICE_REPLY_SYSTEM_PROMPT, __agentTurnQueueForTest, __resetAgentTurnQueueForTest, __setActiveRunForTest, admitAgentEvent, buildA2ACompletePayload, buildAttachmentLocalFilename, buildOriginEnvelopeContext, buildToolActivity, buildTurnEnvelopeContext, computeOpenClawSdkAnchors, decideChatServiceStart, plugin as default, joinAssistantText, readAttachmentBodyBounded, resolveAbortTargetKeys, rewriteAssistantText, stripLeakedToolSummary };
1
+ import { C as withReplyRunId, S as stripLeakedToolSummary, _ as plugin, a as __agentTurnQueueForTest, b as resolveRequestedRunId, c as admitAgentEvent, d as buildOriginEnvelopeContext, f as buildToolActivity, g as joinAssistantText, h as decideChatServiceStart, i as VOICE_REPLY_SYSTEM_PROMPT, l as buildA2ACompletePayload, m as computeOpenClawSdkAnchors, n as CROSS_AGENT_TOOLS_SYSTEM_PROMPT, o as __resetAgentTurnQueueForTest, p as buildTurnEnvelopeContext, r as MAX_INBOUND_ATTACHMENT_SIZE, s as __setActiveRunForTest, t as A2A_REPLY_SYSTEM_PROMPT, u as buildAttachmentLocalFilename, v as readAttachmentBodyBounded, x as rewriteAssistantText, y as resolveAbortTargetKeys } from "./plugin2.js";
2
+ export { A2A_REPLY_SYSTEM_PROMPT, CROSS_AGENT_TOOLS_SYSTEM_PROMPT, MAX_INBOUND_ATTACHMENT_SIZE, VOICE_REPLY_SYSTEM_PROMPT, __agentTurnQueueForTest, __resetAgentTurnQueueForTest, __setActiveRunForTest, admitAgentEvent, buildA2ACompletePayload, buildAttachmentLocalFilename, buildOriginEnvelopeContext, buildToolActivity, buildTurnEnvelopeContext, computeOpenClawSdkAnchors, decideChatServiceStart, plugin as default, joinAssistantText, readAttachmentBodyBounded, resolveAbortTargetKeys, resolveRequestedRunId, rewriteAssistantText, stripLeakedToolSummary, withReplyRunId };
package/dist/plugin2.cjs CHANGED
@@ -8,8 +8,8 @@ let node_url = require("node:url");
8
8
  let node_os = require("node:os");
9
9
  let _alfe_ai_chat = require("@alfe.ai/chat");
10
10
  let _alfe_ai_agent_api_client = require("@alfe.ai/agent-api-client");
11
- let _alfe_ai_openclaw_plugin_kit = require("@alfe.ai/openclaw-plugin-kit");
12
11
  let _alfe_ai_config = require("@alfe.ai/config");
12
+ let _alfe_ai_openclaw_plugin_kit = require("@alfe.ai/openclaw-plugin-kit");
13
13
  //#region src/outbound-media.ts
14
14
  /**
15
15
  * Outbound media upload — agent → user.
@@ -622,6 +622,21 @@ async function sendViaChat(deps, ctx, mediaUrl, components) {
622
622
  };
623
623
  }
624
624
  /**
625
+ * Send to a server-authorized native identity conversation. The cloud
626
+ * Conversation/Participant rows already exist when this is called; bind the
627
+ * local session to the exact same key before reusing the normal delivery path.
628
+ */
629
+ async function sendIdentityMessageViaChat(deps, target, text) {
630
+ const existing = await deps.getSession(target.conversationId);
631
+ if (existing) {
632
+ if (existing.userId !== target.userId || existing.tenantId !== target.tenantId) throw new Error("identity_direct_session_mismatch");
633
+ } else await deps.createSession(target.conversationId, "", "alfe", target.tenantId, target.userId);
634
+ return sendViaChat(deps, {
635
+ to: `conv:${target.conversationId}`,
636
+ text
637
+ }, void 0);
638
+ }
639
+ /**
625
640
  * Present interactive components (link buttons, quick replies) into a
626
641
  * conversation — the outbound path for the `chat_present_components` tool.
627
642
  * `to` is a `conv:{id}` or `user:{id}` target (same vocabulary as the channel's
@@ -1654,8 +1669,92 @@ async function collectTranscriptActivity(conversationId, routes) {
1654
1669
  return merged.length > MAX_ENTRIES ? merged.slice(merged.length - MAX_ENTRIES) : merged;
1655
1670
  }
1656
1671
  //#endregion
1672
+ //#region src/session-wire-budget.ts
1673
+ /**
1674
+ * Keep daemon -> chat-relay history responses below the relay's WebSocket
1675
+ * frame limit. `sessions.get` is assembled on the daemon, so relay-side
1676
+ * activity thinning happens too late to protect this inbound hop.
1677
+ */
1678
+ /**
1679
+ * The production relay historically accepted 1 MiB daemon frames. Leave
1680
+ * enough headroom for WebSocket/RPC envelope drift and non-history metadata.
1681
+ */
1682
+ const SESSION_GET_FRAME_BUDGET_BYTES = 900 * 1024;
1683
+ const SESSION_GET_FRAME_BUDGET_ERROR = "sessions.get response metadata exceeds frame budget";
1684
+ function responseFrameBytes(requestId, payload) {
1685
+ return Buffer.byteLength(JSON.stringify({
1686
+ id: requestId,
1687
+ ok: true,
1688
+ payload
1689
+ }), "utf8");
1690
+ }
1691
+ function largestFittingSuffix(items, fits) {
1692
+ let low = 0;
1693
+ let high = items.length;
1694
+ while (low < high) {
1695
+ const keep = Math.ceil((low + high) / 2);
1696
+ if (fits(items.slice(items.length - keep))) low = keep;
1697
+ else high = keep - 1;
1698
+ }
1699
+ return items.slice(items.length - low);
1700
+ }
1701
+ /**
1702
+ * Preserve the newest useful history without ever letting one oversized
1703
+ * `sessions.get` response tear down the daemon's shared relay connection.
1704
+ * Activity is shed before messages; if messages alone exceed the budget,
1705
+ * their oldest entries are shed as well. Persistence is untouched.
1706
+ */
1707
+ function boundSessionGetResponse(requestId, payload, maxFrameBytes = SESSION_GET_FRAME_BUDGET_BYTES) {
1708
+ const originalFrameBytes = responseFrameBytes(requestId, payload);
1709
+ if (originalFrameBytes <= maxFrameBytes) return {
1710
+ payload,
1711
+ frameBytes: originalFrameBytes,
1712
+ truncatedMessages: 0,
1713
+ truncatedActivity: 0
1714
+ };
1715
+ const originalActivity = payload.activity;
1716
+ let activity = originalActivity;
1717
+ if (originalActivity) activity = largestFittingSuffix(originalActivity, (suffix) => responseFrameBytes(requestId, {
1718
+ ...payload,
1719
+ activity: suffix
1720
+ }) <= maxFrameBytes);
1721
+ let candidate = {
1722
+ ...payload,
1723
+ ...originalActivity ? { activity } : {}
1724
+ };
1725
+ let frameBytes = responseFrameBytes(requestId, candidate);
1726
+ if (frameBytes <= maxFrameBytes) return {
1727
+ payload: candidate,
1728
+ frameBytes,
1729
+ truncatedMessages: 0,
1730
+ truncatedActivity: (originalActivity?.length ?? 0) - (activity?.length ?? 0)
1731
+ };
1732
+ candidate = {
1733
+ ...payload,
1734
+ messages: payload.messages,
1735
+ ...originalActivity ? { activity: [] } : {}
1736
+ };
1737
+ const messages = largestFittingSuffix(payload.messages, (suffix) => responseFrameBytes(requestId, {
1738
+ ...candidate,
1739
+ messages: suffix
1740
+ }) <= maxFrameBytes);
1741
+ candidate = {
1742
+ ...candidate,
1743
+ messages
1744
+ };
1745
+ frameBytes = responseFrameBytes(requestId, candidate);
1746
+ if (frameBytes > maxFrameBytes) throw new Error(SESSION_GET_FRAME_BUDGET_ERROR);
1747
+ return {
1748
+ payload: candidate,
1749
+ frameBytes,
1750
+ truncatedMessages: payload.messages.length - messages.length,
1751
+ truncatedActivity: originalActivity?.length ?? 0
1752
+ };
1753
+ }
1754
+ //#endregion
1657
1755
  //#region src/a2a-tools.ts
1658
1756
  const MAX_AGENT_ID_CHARS = 256;
1757
+ const MAX_IDENTITY_ID_CHARS$1 = 256;
1659
1758
  const MAX_A2A_MESSAGE_CHARS = 32e3;
1660
1759
  const MAX_THREAD_ID_CHARS = 128;
1661
1760
  const MAX_A2A_DEPTH = 20;
@@ -1692,7 +1791,7 @@ function disarmA2AEndSignal() {
1692
1791
  function isA2AEndSignalled() {
1693
1792
  return conversationEndRequested;
1694
1793
  }
1695
- function buildA2ATools(getChatClient, log, present) {
1794
+ function buildA2ATools(getChatClient, log, present, messageIdentity) {
1696
1795
  const requireClient = () => {
1697
1796
  const client = getChatClient();
1698
1797
  if (!client) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)("chat service not connected yet — try again in a moment");
@@ -1784,6 +1883,39 @@ function buildA2ATools(getChatClient, log, present) {
1784
1883
  }
1785
1884
  })
1786
1885
  ];
1886
+ if (messageIdentity) tools.push((0, _alfe_ai_openclaw_plugin_kit.defineTool)({
1887
+ name: "message_identity",
1888
+ description: "Send a new direct message to a person by universal identity ID. Use list_identities or lookup_identity first. The chat service verifies that the identity maps to exactly one current member of this tenant, creates or reuses the durable direct conversation, and then sends the message.",
1889
+ parameters: {
1890
+ type: "object",
1891
+ properties: {
1892
+ identity_id: {
1893
+ type: "string",
1894
+ minLength: 5,
1895
+ maxLength: MAX_IDENTITY_ID_CHARS$1,
1896
+ pattern: "^idn_[A-Za-z0-9_-]+$",
1897
+ description: "Target universal identity ID (idn_...)"
1898
+ },
1899
+ message: {
1900
+ type: "string",
1901
+ minLength: 1,
1902
+ maxLength: MAX_A2A_MESSAGE_CHARS,
1903
+ description: "Message to send to the person"
1904
+ }
1905
+ },
1906
+ required: ["identity_id", "message"]
1907
+ },
1908
+ handler: async (params) => {
1909
+ const identityId = params.identity_id;
1910
+ const message = params.message;
1911
+ if (typeof identityId !== "string" || identityId.length > MAX_IDENTITY_ID_CHARS$1 || !/^idn_[A-Za-z0-9_-]+$/.test(identityId) || typeof message !== "string" || message.length < 1 || message.length > MAX_A2A_MESSAGE_CHARS) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)("Invalid message_identity parameters");
1912
+ log.info(`message_identity tool: sending to ${identityId}`);
1913
+ return messageIdentity({
1914
+ identityId,
1915
+ message
1916
+ });
1917
+ }
1918
+ }));
1787
1919
  if (present) tools.push((0, _alfe_ai_openclaw_plugin_kit.defineTool)({
1788
1920
  name: "chat_present_components",
1789
1921
  description: "Attach interactive UI components to a chat message so the user can act with a tap instead of typing. PREFER components whenever you offer the user a discrete set of choices — a quick_reply or select reads far better than \"reply 1, 2, or 3\". Use:\n • quick_reply — one-tap canned replies for a short set of options.\n • select — a single choice from a longer list (dropdown).\n • multi_select — let the user pick MANY options, then submit.\n • confirm — an approval / yes-no decision (primary + secondary button).\n • link_button — an actionable LINK (open a dashboard, a browser-takeover deep link). URL must be https on an Alfe-owned host.\n • copy_button — let the user copy a value (token, id, snippet) to their clipboard.\nChoosing/submitting an interactive component sends the chosen value back AS THE USER'S NEXT MESSAGE, so the conversation continues normally — you will see it as ordinary user input. Don't button-spam: components are for real interactions, not decoration, and each message allows at most 10. Components degrade to plain text on clients that don't render them, so keep the essentials in `text` too.\nExamples:\n Approval: { to:\"conv:abc\", text:\"Deploy to production?\", components:[ { type:\"confirm\", id:\"c1\", confirmLabel:\"Deploy\", confirmValue:\"yes, deploy\", cancelLabel:\"Cancel\", cancelValue:\"no, hold off\" } ] }\n Pick one: { to:\"user:u_123\", text:\"Which environment?\", components:[ { type:\"select\", id:\"s1\", placeholder:\"Choose an environment\", options:[ { label:\"Dev\", value:\"dev\" }, { label:\"Staging\", value:\"staging\" }, { label:\"Prod\", value:\"prod\" } ] } ] }\n Link: { to:\"conv:abc\", text:\"I need you to finish the login step.\", components:[ { type:\"link_button\", id:\"b1\", label:\"Open browser session\", url:\"https://app.alfe.ai/agents/x?tab=browser\", target:\"new-tab\", style:\"primary\" } ] }",
@@ -2109,6 +2241,28 @@ function isValidIdentityId(value) {
2109
2241
  */
2110
2242
  const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
2111
2243
  const pkg = require$1("../package.json");
2244
+ let cachedAgentApi = null;
2245
+ function getAgentApiClient(apiKeyOverride) {
2246
+ let resolved;
2247
+ try {
2248
+ resolved = (0, _alfe_ai_config.resolveConfig)();
2249
+ } catch {}
2250
+ const normalizedOverride = apiKeyOverride?.trim();
2251
+ const apiKey = normalizedOverride ?? resolved?.apiKey;
2252
+ if (!apiKey) throw new Error("Alfe API key is not configured");
2253
+ const apiUrl = normalizedOverride === void 0 ? resolved?.apiUrl ?? (0, _alfe_ai_config.getEndpointFromToken)(apiKey) : (0, _alfe_ai_config.getEndpointFromToken)(apiKey);
2254
+ if (cachedAgentApi?.apiKey === apiKey && cachedAgentApi.apiUrl === apiUrl) return cachedAgentApi.client;
2255
+ const client = new _alfe_ai_agent_api_client.AgentApiClient({
2256
+ apiKey,
2257
+ apiUrl
2258
+ });
2259
+ cachedAgentApi = {
2260
+ apiKey,
2261
+ apiUrl,
2262
+ client
2263
+ };
2264
+ return client;
2265
+ }
2112
2266
  /**
2113
2267
  * Decide whether an agent-event belongs to THIS turn's run and should be
2114
2268
  * forwarded as a delta / activity frame.
@@ -2148,6 +2302,25 @@ function admitAgentEvent(gate, evt) {
2148
2302
  if (gate.sessionKey && evt.sessionKey && evt.sessionKey !== gate.sessionKey) return false;
2149
2303
  return true;
2150
2304
  }
2305
+ /**
2306
+ * Seed OpenClaw's runtime/event id with the chat service request that owns the
2307
+ * turn. Older callers omit the idempotency key and retain a runtime-owned id.
2308
+ */
2309
+ function resolveRequestedRunId(idempotencyKey) {
2310
+ return typeof idempotencyKey === "string" && idempotencyKey.length > 0 && idempotencyKey.length <= 256 ? idempotencyKey : null;
2311
+ }
2312
+ /** Preserve OpenClaw-provided reply options while seeding this exact run id. */
2313
+ function withReplyRunId(params, runId) {
2314
+ const existing = params.replyOptions;
2315
+ const replyOptions = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
2316
+ return {
2317
+ ...params,
2318
+ replyOptions: {
2319
+ ...replyOptions,
2320
+ runId
2321
+ }
2322
+ };
2323
+ }
2151
2324
  function asString(v) {
2152
2325
  return typeof v === "string" && v.length > 0 ? v : void 0;
2153
2326
  }
@@ -2713,6 +2886,7 @@ function enqueueAgentTurn(task, opts) {
2713
2886
  run: task,
2714
2887
  coalesceKey: opts?.coalesceKey,
2715
2888
  onSuperseded: opts?.onSuperseded ?? (() => void 0),
2889
+ onStarted: opts?.onStarted ?? (() => void 0),
2716
2890
  resolve,
2717
2891
  reject
2718
2892
  };
@@ -2738,6 +2912,9 @@ async function drainAgentTurns() {
2738
2912
  const entry = agentTurnBacklog.shift();
2739
2913
  if (!entry) break;
2740
2914
  try {
2915
+ try {
2916
+ entry.onStarted();
2917
+ } catch {}
2741
2918
  await entry.run();
2742
2919
  entry.resolve();
2743
2920
  } catch (err) {
@@ -2774,7 +2951,8 @@ function __setActiveRunForTest(ref) {
2774
2951
  * sessionKey that does NOT match the running turn yields no candidates — we
2775
2952
  * never abort another session's run on a shared daemon.
2776
2953
  */
2777
- function resolveAbortTargetKeys(active, abortSessionKey) {
2954
+ function resolveAbortTargetKeys(active, abortSessionKey, abortRunId) {
2955
+ if (abortRunId && (!active?.runId || active.runId !== abortRunId || abortSessionKey !== void 0 && active.requestSessionKey !== abortSessionKey)) return [];
2778
2956
  const keys = [];
2779
2957
  if (active && (!abortSessionKey || active.requestSessionKey === abortSessionKey)) {
2780
2958
  if (active.routeSessionKey) keys.push(active.routeSessionKey);
@@ -2796,8 +2974,8 @@ function resolveAbortTargetKeys(active, abortSessionKey) {
2796
2974
  * persisted.
2797
2975
  */
2798
2976
  function handleChatAbort(request, log) {
2799
- const { sessionKey } = request.params;
2800
- const targetKeys = resolveAbortTargetKeys(activeRun, sessionKey);
2977
+ const { sessionKey, runId } = request.params;
2978
+ const targetKeys = resolveAbortTargetKeys(activeRun, sessionKey, runId);
2801
2979
  let aborted = false;
2802
2980
  if (abortEmbeddedAgentRun && resolveActiveRunSessionId) {
2803
2981
  for (const key of targetKeys) {
@@ -2894,7 +3072,7 @@ async function handleAgentRequest(request, log) {
2894
3072
  chatClient?.sendResponse(request.id, false, { message: "OpenClaw SDK not available — cannot dispatch" });
2895
3073
  return;
2896
3074
  }
2897
- const { message, sessionKey: legacySessionKey, userId, conversationId, conversationType, tenantId, clientType, origin, displayName, identityId, identityProvider, attachments: rawAttachments, a2a, chatMessageId } = request.params;
3075
+ const { message, sessionKey: legacySessionKey, userId, conversationId, conversationType, tenantId, clientType, origin, displayName, identityId, identityProvider, idempotencyKey, attachments: rawAttachments, a2a, chatMessageId } = request.params;
2898
3076
  const isA2A = !!a2a;
2899
3077
  if (!message && !rawAttachments?.length) {
2900
3078
  chatClient?.sendResponse(request.id, false, { message: "Missing message" });
@@ -2905,8 +3083,9 @@ async function handleAgentRequest(request, log) {
2905
3083
  const sessionId = conversationId ?? legacySessionKey;
2906
3084
  if (!await getSession(sessionId)) await createSession(sessionId, "", "alfe", tenantId, userId);
2907
3085
  await addMessage(sessionId, "user", message, userId ?? senderId, displayName ?? senderId, void 0, userAttachmentsToStored(rawAttachments));
3086
+ const requestedRunId = resolveRequestedRunId(idempotencyKey);
2908
3087
  const runGate = {
2909
- runId: null,
3088
+ runId: requestedRunId,
2910
3089
  sessionKey: null
2911
3090
  };
2912
3091
  let lastThinkingLen = 0;
@@ -3091,11 +3270,19 @@ async function handleAgentRequest(request, log) {
3091
3270
  }
3092
3271
  activeRun = {
3093
3272
  requestSessionKey: legacySessionKey,
3094
- routeSessionKey
3273
+ routeSessionKey,
3274
+ ...requestedRunId ? { runId: requestedRunId } : {}
3095
3275
  };
3276
+ const dispatchChannel = requestedRunId ? {
3277
+ ...runtime.channel,
3278
+ reply: {
3279
+ ...runtime.channel.reply,
3280
+ dispatchReplyWithBufferedBlockDispatcher: (params) => runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher(withReplyRunId(params, requestedRunId))
3281
+ }
3282
+ } : runtime.channel;
3096
3283
  const result = await dispatchInbound({
3097
3284
  cfg,
3098
- runtime: { channel: runtime.channel },
3285
+ runtime: { channel: dispatchChannel },
3099
3286
  channel: "alfe",
3100
3287
  channelLabel,
3101
3288
  accountId: "default",
@@ -3242,7 +3429,7 @@ async function handleSessionsGet(request, log) {
3242
3429
  });
3243
3430
  return;
3244
3431
  }
3245
- chatClient?.sendResponse(request.id, true, {
3432
+ const response = boundSessionGetResponse(request.id, {
3246
3433
  sessionId: session.sessionId,
3247
3434
  agentId: session.agentId,
3248
3435
  channel: session.channel,
@@ -3257,6 +3444,8 @@ async function handleSessionsGet(request, log) {
3257
3444
  })),
3258
3445
  activity: await resolveHistoryActivity(session)
3259
3446
  });
3447
+ if (response.truncatedMessages > 0 || response.truncatedActivity > 0) log.warn(`sessions.get history bounded for relay frame: messagesDropped=${String(response.truncatedMessages)} activityDropped=${String(response.truncatedActivity)} frameBytes=${String(response.frameBytes)}`);
3448
+ chatClient?.sendResponse(request.id, true, response.payload);
3260
3449
  } catch (err) {
3261
3450
  const errMsg = err instanceof Error ? err.message : String(err);
3262
3451
  log.error(`sessions.get failed: ${errMsg}`);
@@ -3270,6 +3459,7 @@ const plugin = {
3270
3459
  version: pkg.version,
3271
3460
  activate(api) {
3272
3461
  const log = api.logger;
3462
+ const pluginConfig = (((api.config ?? {}).plugins?.entries)?.["@alfe.ai/openclaw-chat"] ?? {}).config ?? {};
3273
3463
  const outboundDeps = {
3274
3464
  getChatClient: () => chatClient,
3275
3465
  log,
@@ -3292,11 +3482,27 @@ const plugin = {
3292
3482
  components: clean
3293
3483
  });
3294
3484
  };
3295
- const a2aTools = buildA2ATools(() => chatClient, log, present);
3485
+ const messageIdentity = async (args) => {
3486
+ if (!chatClient?.isConnected) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)("chat service not connected yet — try again in a moment");
3487
+ const direct = await getAgentApiClient(pluginConfig.apiKey).ensureDirectConversation(args.identityId);
3488
+ const sent = await sendIdentityMessageViaChat(outboundDeps, {
3489
+ conversationId: direct.conversationId,
3490
+ tenantId: direct.tenantId,
3491
+ userId: direct.userId
3492
+ }, args.message);
3493
+ return {
3494
+ status: "sent",
3495
+ identityId: direct.identityId,
3496
+ displayName: direct.displayName,
3497
+ conversationId: sent.conversationId,
3498
+ messageId: sent.messageId,
3499
+ newConversation: direct.created
3500
+ };
3501
+ };
3502
+ const a2aTools = buildA2ATools(() => chatClient, log, present, messageIdentity);
3296
3503
  for (const tool of a2aTools) api.registerTool(tool);
3297
3504
  log.info(`Registered ${String(a2aTools.length)} agent tools`);
3298
3505
  }
3299
- const pluginConfig = (((api.config ?? {}).plugins?.entries)?.["@alfe.ai/openclaw-chat"] ?? {}).config ?? {};
3300
3506
  const startChatService = async () => {
3301
3507
  const action = decideChatServiceStart({
3302
3508
  connectInFlight: connectingPromise !== null,
@@ -3337,16 +3543,28 @@ const plugin = {
3337
3543
  if (request.method === "agent") {
3338
3544
  const params = request.params;
3339
3545
  const coalesceKey = params.coalesce === "replace" && typeof params.sessionKey === "string" ? params.sessionKey : void 0;
3340
- await enqueueAgentTurn(() => handleAgentRequest(request, log), coalesceKey ? {
3341
- coalesceKey,
3342
- onSuperseded: () => {
3343
- log.info(`Agent turn superseded before start (coalesce=replace): ${coalesceKey}`);
3546
+ chatClient?.sendResponse(request.id, true, {
3547
+ status: "accepted",
3548
+ phase: "queued"
3549
+ });
3550
+ await enqueueAgentTurn(() => handleAgentRequest(request, log), {
3551
+ onStarted: () => {
3344
3552
  chatClient?.sendResponse(request.id, true, {
3345
- superseded: true,
3346
- sessionKey: coalesceKey
3553
+ status: "accepted",
3554
+ phase: "started"
3347
3555
  });
3348
- }
3349
- } : void 0);
3556
+ },
3557
+ ...coalesceKey ? {
3558
+ coalesceKey,
3559
+ onSuperseded: () => {
3560
+ log.info(`Agent turn superseded before start (coalesce=replace): ${coalesceKey}`);
3561
+ chatClient?.sendResponse(request.id, true, {
3562
+ superseded: true,
3563
+ sessionKey: coalesceKey
3564
+ });
3565
+ }
3566
+ } : {}
3567
+ });
3350
3568
  } else if (request.method === "chat.abort") handleChatAbort(request, log);
3351
3569
  else if (request.method === "sessions.list") await handleSessionsList(request, log);
3352
3570
  else if (request.method === "sessions.get") await handleSessionsGet(request, log);
@@ -3481,6 +3699,7 @@ const plugin = {
3481
3699
  }
3482
3700
  pluginRuntime = null;
3483
3701
  dispatchInbound = null;
3702
+ cachedAgentApi = null;
3484
3703
  (0, _alfe_ai_openclaw_plugin_kit.resetActivation)(CHAT_ACTIVATION_KEY);
3485
3704
  log.info("Chat plugin deactivated");
3486
3705
  }
@@ -3606,6 +3825,12 @@ Object.defineProperty(exports, "resolveAbortTargetKeys", {
3606
3825
  return resolveAbortTargetKeys;
3607
3826
  }
3608
3827
  });
3828
+ Object.defineProperty(exports, "resolveRequestedRunId", {
3829
+ enumerable: true,
3830
+ get: function() {
3831
+ return resolveRequestedRunId;
3832
+ }
3833
+ });
3609
3834
  Object.defineProperty(exports, "rewriteAssistantText", {
3610
3835
  enumerable: true,
3611
3836
  get: function() {
@@ -3618,3 +3843,9 @@ Object.defineProperty(exports, "stripLeakedToolSummary", {
3618
3843
  return stripLeakedToolSummary;
3619
3844
  }
3620
3845
  });
3846
+ Object.defineProperty(exports, "withReplyRunId", {
3847
+ enumerable: true,
3848
+ get: function() {
3849
+ return withReplyRunId;
3850
+ }
3851
+ });
@@ -1,2 +1,2 @@
1
- import { C as rewriteAssistantText, S as resolveAbortTargetKeys, _ as computeOpenClawSdkAnchors, a as MAX_INBOUND_ATTACHMENT_SIZE, b as plugin, c as __agentTurnQueueForTest, d as admitAgentEvent, f as buildA2ACompletePayload, g as buildTurnEnvelopeContext, h as buildToolActivity, i as CROSS_AGENT_TOOLS_SYSTEM_PROMPT, l as __resetAgentTurnQueueForTest, m as buildOriginEnvelopeContext, n as ActiveRunRef, o as RunEventGate, p as buildAttachmentLocalFilename, r as AnchorEnv, s as VOICE_REPLY_SYSTEM_PROMPT, t as A2A_REPLY_SYSTEM_PROMPT, u as __setActiveRunForTest, v as decideChatServiceStart, w as stripLeakedToolSummary, x as readAttachmentBodyBounded, y as joinAssistantText } from "./plugin.cjs";
2
- export { A2A_REPLY_SYSTEM_PROMPT, ActiveRunRef, AnchorEnv, CROSS_AGENT_TOOLS_SYSTEM_PROMPT, MAX_INBOUND_ATTACHMENT_SIZE, RunEventGate, VOICE_REPLY_SYSTEM_PROMPT, __agentTurnQueueForTest, __resetAgentTurnQueueForTest, __setActiveRunForTest, admitAgentEvent, buildA2ACompletePayload, buildAttachmentLocalFilename, buildOriginEnvelopeContext, buildToolActivity, buildTurnEnvelopeContext, computeOpenClawSdkAnchors, decideChatServiceStart, plugin as default, joinAssistantText, readAttachmentBodyBounded, resolveAbortTargetKeys, rewriteAssistantText, stripLeakedToolSummary };
1
+ import { C as resolveRequestedRunId, E as withReplyRunId, S as resolveAbortTargetKeys, T as stripLeakedToolSummary, _ as computeOpenClawSdkAnchors, a as MAX_INBOUND_ATTACHMENT_SIZE, b as plugin, c as __agentTurnQueueForTest, d as admitAgentEvent, f as buildA2ACompletePayload, g as buildTurnEnvelopeContext, h as buildToolActivity, i as CROSS_AGENT_TOOLS_SYSTEM_PROMPT, l as __resetAgentTurnQueueForTest, m as buildOriginEnvelopeContext, n as ActiveRunRef, o as RunEventGate, p as buildAttachmentLocalFilename, r as AnchorEnv, s as VOICE_REPLY_SYSTEM_PROMPT, t as A2A_REPLY_SYSTEM_PROMPT, u as __setActiveRunForTest, v as decideChatServiceStart, w as rewriteAssistantText, x as readAttachmentBodyBounded, y as joinAssistantText } from "./plugin.cjs";
2
+ export { A2A_REPLY_SYSTEM_PROMPT, ActiveRunRef, AnchorEnv, CROSS_AGENT_TOOLS_SYSTEM_PROMPT, MAX_INBOUND_ATTACHMENT_SIZE, RunEventGate, VOICE_REPLY_SYSTEM_PROMPT, __agentTurnQueueForTest, __resetAgentTurnQueueForTest, __setActiveRunForTest, admitAgentEvent, buildA2ACompletePayload, buildAttachmentLocalFilename, buildOriginEnvelopeContext, buildToolActivity, buildTurnEnvelopeContext, computeOpenClawSdkAnchors, decideChatServiceStart, plugin as default, joinAssistantText, readAttachmentBodyBounded, resolveAbortTargetKeys, resolveRequestedRunId, rewriteAssistantText, stripLeakedToolSummary, withReplyRunId };
package/dist/plugin2.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { C as rewriteAssistantText, S as resolveAbortTargetKeys, _ as computeOpenClawSdkAnchors, a as MAX_INBOUND_ATTACHMENT_SIZE, b as plugin, c as __agentTurnQueueForTest, d as admitAgentEvent, f as buildA2ACompletePayload, g as buildTurnEnvelopeContext, h as buildToolActivity, i as CROSS_AGENT_TOOLS_SYSTEM_PROMPT, l as __resetAgentTurnQueueForTest, m as buildOriginEnvelopeContext, n as ActiveRunRef, o as RunEventGate, p as buildAttachmentLocalFilename, r as AnchorEnv, s as VOICE_REPLY_SYSTEM_PROMPT, t as A2A_REPLY_SYSTEM_PROMPT, u as __setActiveRunForTest, v as decideChatServiceStart, w as stripLeakedToolSummary, x as readAttachmentBodyBounded, y as joinAssistantText } from "./plugin.js";
2
- export { A2A_REPLY_SYSTEM_PROMPT, ActiveRunRef, AnchorEnv, CROSS_AGENT_TOOLS_SYSTEM_PROMPT, MAX_INBOUND_ATTACHMENT_SIZE, RunEventGate, VOICE_REPLY_SYSTEM_PROMPT, __agentTurnQueueForTest, __resetAgentTurnQueueForTest, __setActiveRunForTest, admitAgentEvent, buildA2ACompletePayload, buildAttachmentLocalFilename, buildOriginEnvelopeContext, buildToolActivity, buildTurnEnvelopeContext, computeOpenClawSdkAnchors, decideChatServiceStart, plugin as default, joinAssistantText, readAttachmentBodyBounded, resolveAbortTargetKeys, rewriteAssistantText, stripLeakedToolSummary };
1
+ import { C as resolveRequestedRunId, E as withReplyRunId, S as resolveAbortTargetKeys, T as stripLeakedToolSummary, _ as computeOpenClawSdkAnchors, a as MAX_INBOUND_ATTACHMENT_SIZE, b as plugin, c as __agentTurnQueueForTest, d as admitAgentEvent, f as buildA2ACompletePayload, g as buildTurnEnvelopeContext, h as buildToolActivity, i as CROSS_AGENT_TOOLS_SYSTEM_PROMPT, l as __resetAgentTurnQueueForTest, m as buildOriginEnvelopeContext, n as ActiveRunRef, o as RunEventGate, p as buildAttachmentLocalFilename, r as AnchorEnv, s as VOICE_REPLY_SYSTEM_PROMPT, t as A2A_REPLY_SYSTEM_PROMPT, u as __setActiveRunForTest, v as decideChatServiceStart, w as rewriteAssistantText, x as readAttachmentBodyBounded, y as joinAssistantText } from "./plugin.js";
2
+ export { A2A_REPLY_SYSTEM_PROMPT, ActiveRunRef, AnchorEnv, CROSS_AGENT_TOOLS_SYSTEM_PROMPT, MAX_INBOUND_ATTACHMENT_SIZE, RunEventGate, VOICE_REPLY_SYSTEM_PROMPT, __agentTurnQueueForTest, __resetAgentTurnQueueForTest, __setActiveRunForTest, admitAgentEvent, buildA2ACompletePayload, buildAttachmentLocalFilename, buildOriginEnvelopeContext, buildToolActivity, buildTurnEnvelopeContext, computeOpenClawSdkAnchors, decideChatServiceStart, plugin as default, joinAssistantText, readAttachmentBodyBounded, resolveAbortTargetKeys, resolveRequestedRunId, rewriteAssistantText, stripLeakedToolSummary, withReplyRunId };
package/dist/plugin2.js CHANGED
@@ -8,8 +8,8 @@ import { pathToFileURL } from "node:url";
8
8
  import { homedir } from "node:os";
9
9
  import { ChatServiceClient, resolveAlfeChat } from "@alfe.ai/chat";
10
10
  import { AgentApiClient, installToolErrorCapture } from "@alfe.ai/agent-api-client";
11
+ import { getEndpointFromToken, resolveConfig } from "@alfe.ai/config";
11
12
  import { defineTool, getActivationKey, publicToolError, resetActivation } from "@alfe.ai/openclaw-plugin-kit";
12
- import { resolveConfig } from "@alfe.ai/config";
13
13
  //#region src/outbound-media.ts
14
14
  /**
15
15
  * Outbound media upload — agent → user.
@@ -622,6 +622,21 @@ async function sendViaChat(deps, ctx, mediaUrl, components) {
622
622
  };
623
623
  }
624
624
  /**
625
+ * Send to a server-authorized native identity conversation. The cloud
626
+ * Conversation/Participant rows already exist when this is called; bind the
627
+ * local session to the exact same key before reusing the normal delivery path.
628
+ */
629
+ async function sendIdentityMessageViaChat(deps, target, text) {
630
+ const existing = await deps.getSession(target.conversationId);
631
+ if (existing) {
632
+ if (existing.userId !== target.userId || existing.tenantId !== target.tenantId) throw new Error("identity_direct_session_mismatch");
633
+ } else await deps.createSession(target.conversationId, "", "alfe", target.tenantId, target.userId);
634
+ return sendViaChat(deps, {
635
+ to: `conv:${target.conversationId}`,
636
+ text
637
+ }, void 0);
638
+ }
639
+ /**
625
640
  * Present interactive components (link buttons, quick replies) into a
626
641
  * conversation — the outbound path for the `chat_present_components` tool.
627
642
  * `to` is a `conv:{id}` or `user:{id}` target (same vocabulary as the channel's
@@ -1654,8 +1669,92 @@ async function collectTranscriptActivity(conversationId, routes) {
1654
1669
  return merged.length > MAX_ENTRIES ? merged.slice(merged.length - MAX_ENTRIES) : merged;
1655
1670
  }
1656
1671
  //#endregion
1672
+ //#region src/session-wire-budget.ts
1673
+ /**
1674
+ * Keep daemon -> chat-relay history responses below the relay's WebSocket
1675
+ * frame limit. `sessions.get` is assembled on the daemon, so relay-side
1676
+ * activity thinning happens too late to protect this inbound hop.
1677
+ */
1678
+ /**
1679
+ * The production relay historically accepted 1 MiB daemon frames. Leave
1680
+ * enough headroom for WebSocket/RPC envelope drift and non-history metadata.
1681
+ */
1682
+ const SESSION_GET_FRAME_BUDGET_BYTES = 900 * 1024;
1683
+ const SESSION_GET_FRAME_BUDGET_ERROR = "sessions.get response metadata exceeds frame budget";
1684
+ function responseFrameBytes(requestId, payload) {
1685
+ return Buffer.byteLength(JSON.stringify({
1686
+ id: requestId,
1687
+ ok: true,
1688
+ payload
1689
+ }), "utf8");
1690
+ }
1691
+ function largestFittingSuffix(items, fits) {
1692
+ let low = 0;
1693
+ let high = items.length;
1694
+ while (low < high) {
1695
+ const keep = Math.ceil((low + high) / 2);
1696
+ if (fits(items.slice(items.length - keep))) low = keep;
1697
+ else high = keep - 1;
1698
+ }
1699
+ return items.slice(items.length - low);
1700
+ }
1701
+ /**
1702
+ * Preserve the newest useful history without ever letting one oversized
1703
+ * `sessions.get` response tear down the daemon's shared relay connection.
1704
+ * Activity is shed before messages; if messages alone exceed the budget,
1705
+ * their oldest entries are shed as well. Persistence is untouched.
1706
+ */
1707
+ function boundSessionGetResponse(requestId, payload, maxFrameBytes = SESSION_GET_FRAME_BUDGET_BYTES) {
1708
+ const originalFrameBytes = responseFrameBytes(requestId, payload);
1709
+ if (originalFrameBytes <= maxFrameBytes) return {
1710
+ payload,
1711
+ frameBytes: originalFrameBytes,
1712
+ truncatedMessages: 0,
1713
+ truncatedActivity: 0
1714
+ };
1715
+ const originalActivity = payload.activity;
1716
+ let activity = originalActivity;
1717
+ if (originalActivity) activity = largestFittingSuffix(originalActivity, (suffix) => responseFrameBytes(requestId, {
1718
+ ...payload,
1719
+ activity: suffix
1720
+ }) <= maxFrameBytes);
1721
+ let candidate = {
1722
+ ...payload,
1723
+ ...originalActivity ? { activity } : {}
1724
+ };
1725
+ let frameBytes = responseFrameBytes(requestId, candidate);
1726
+ if (frameBytes <= maxFrameBytes) return {
1727
+ payload: candidate,
1728
+ frameBytes,
1729
+ truncatedMessages: 0,
1730
+ truncatedActivity: (originalActivity?.length ?? 0) - (activity?.length ?? 0)
1731
+ };
1732
+ candidate = {
1733
+ ...payload,
1734
+ messages: payload.messages,
1735
+ ...originalActivity ? { activity: [] } : {}
1736
+ };
1737
+ const messages = largestFittingSuffix(payload.messages, (suffix) => responseFrameBytes(requestId, {
1738
+ ...candidate,
1739
+ messages: suffix
1740
+ }) <= maxFrameBytes);
1741
+ candidate = {
1742
+ ...candidate,
1743
+ messages
1744
+ };
1745
+ frameBytes = responseFrameBytes(requestId, candidate);
1746
+ if (frameBytes > maxFrameBytes) throw new Error(SESSION_GET_FRAME_BUDGET_ERROR);
1747
+ return {
1748
+ payload: candidate,
1749
+ frameBytes,
1750
+ truncatedMessages: payload.messages.length - messages.length,
1751
+ truncatedActivity: originalActivity?.length ?? 0
1752
+ };
1753
+ }
1754
+ //#endregion
1657
1755
  //#region src/a2a-tools.ts
1658
1756
  const MAX_AGENT_ID_CHARS = 256;
1757
+ const MAX_IDENTITY_ID_CHARS$1 = 256;
1659
1758
  const MAX_A2A_MESSAGE_CHARS = 32e3;
1660
1759
  const MAX_THREAD_ID_CHARS = 128;
1661
1760
  const MAX_A2A_DEPTH = 20;
@@ -1692,7 +1791,7 @@ function disarmA2AEndSignal() {
1692
1791
  function isA2AEndSignalled() {
1693
1792
  return conversationEndRequested;
1694
1793
  }
1695
- function buildA2ATools(getChatClient, log, present) {
1794
+ function buildA2ATools(getChatClient, log, present, messageIdentity) {
1696
1795
  const requireClient = () => {
1697
1796
  const client = getChatClient();
1698
1797
  if (!client) throw publicToolError("chat service not connected yet — try again in a moment");
@@ -1784,6 +1883,39 @@ function buildA2ATools(getChatClient, log, present) {
1784
1883
  }
1785
1884
  })
1786
1885
  ];
1886
+ if (messageIdentity) tools.push(defineTool({
1887
+ name: "message_identity",
1888
+ description: "Send a new direct message to a person by universal identity ID. Use list_identities or lookup_identity first. The chat service verifies that the identity maps to exactly one current member of this tenant, creates or reuses the durable direct conversation, and then sends the message.",
1889
+ parameters: {
1890
+ type: "object",
1891
+ properties: {
1892
+ identity_id: {
1893
+ type: "string",
1894
+ minLength: 5,
1895
+ maxLength: MAX_IDENTITY_ID_CHARS$1,
1896
+ pattern: "^idn_[A-Za-z0-9_-]+$",
1897
+ description: "Target universal identity ID (idn_...)"
1898
+ },
1899
+ message: {
1900
+ type: "string",
1901
+ minLength: 1,
1902
+ maxLength: MAX_A2A_MESSAGE_CHARS,
1903
+ description: "Message to send to the person"
1904
+ }
1905
+ },
1906
+ required: ["identity_id", "message"]
1907
+ },
1908
+ handler: async (params) => {
1909
+ const identityId = params.identity_id;
1910
+ const message = params.message;
1911
+ if (typeof identityId !== "string" || identityId.length > MAX_IDENTITY_ID_CHARS$1 || !/^idn_[A-Za-z0-9_-]+$/.test(identityId) || typeof message !== "string" || message.length < 1 || message.length > MAX_A2A_MESSAGE_CHARS) throw publicToolError("Invalid message_identity parameters");
1912
+ log.info(`message_identity tool: sending to ${identityId}`);
1913
+ return messageIdentity({
1914
+ identityId,
1915
+ message
1916
+ });
1917
+ }
1918
+ }));
1787
1919
  if (present) tools.push(defineTool({
1788
1920
  name: "chat_present_components",
1789
1921
  description: "Attach interactive UI components to a chat message so the user can act with a tap instead of typing. PREFER components whenever you offer the user a discrete set of choices — a quick_reply or select reads far better than \"reply 1, 2, or 3\". Use:\n • quick_reply — one-tap canned replies for a short set of options.\n • select — a single choice from a longer list (dropdown).\n • multi_select — let the user pick MANY options, then submit.\n • confirm — an approval / yes-no decision (primary + secondary button).\n • link_button — an actionable LINK (open a dashboard, a browser-takeover deep link). URL must be https on an Alfe-owned host.\n • copy_button — let the user copy a value (token, id, snippet) to their clipboard.\nChoosing/submitting an interactive component sends the chosen value back AS THE USER'S NEXT MESSAGE, so the conversation continues normally — you will see it as ordinary user input. Don't button-spam: components are for real interactions, not decoration, and each message allows at most 10. Components degrade to plain text on clients that don't render them, so keep the essentials in `text` too.\nExamples:\n Approval: { to:\"conv:abc\", text:\"Deploy to production?\", components:[ { type:\"confirm\", id:\"c1\", confirmLabel:\"Deploy\", confirmValue:\"yes, deploy\", cancelLabel:\"Cancel\", cancelValue:\"no, hold off\" } ] }\n Pick one: { to:\"user:u_123\", text:\"Which environment?\", components:[ { type:\"select\", id:\"s1\", placeholder:\"Choose an environment\", options:[ { label:\"Dev\", value:\"dev\" }, { label:\"Staging\", value:\"staging\" }, { label:\"Prod\", value:\"prod\" } ] } ] }\n Link: { to:\"conv:abc\", text:\"I need you to finish the login step.\", components:[ { type:\"link_button\", id:\"b1\", label:\"Open browser session\", url:\"https://app.alfe.ai/agents/x?tab=browser\", target:\"new-tab\", style:\"primary\" } ] }",
@@ -2109,6 +2241,28 @@ function isValidIdentityId(value) {
2109
2241
  */
2110
2242
  const require = createRequire(import.meta.url);
2111
2243
  const pkg = require("../package.json");
2244
+ let cachedAgentApi = null;
2245
+ function getAgentApiClient(apiKeyOverride) {
2246
+ let resolved;
2247
+ try {
2248
+ resolved = resolveConfig();
2249
+ } catch {}
2250
+ const normalizedOverride = apiKeyOverride?.trim();
2251
+ const apiKey = normalizedOverride ?? resolved?.apiKey;
2252
+ if (!apiKey) throw new Error("Alfe API key is not configured");
2253
+ const apiUrl = normalizedOverride === void 0 ? resolved?.apiUrl ?? getEndpointFromToken(apiKey) : getEndpointFromToken(apiKey);
2254
+ if (cachedAgentApi?.apiKey === apiKey && cachedAgentApi.apiUrl === apiUrl) return cachedAgentApi.client;
2255
+ const client = new AgentApiClient({
2256
+ apiKey,
2257
+ apiUrl
2258
+ });
2259
+ cachedAgentApi = {
2260
+ apiKey,
2261
+ apiUrl,
2262
+ client
2263
+ };
2264
+ return client;
2265
+ }
2112
2266
  /**
2113
2267
  * Decide whether an agent-event belongs to THIS turn's run and should be
2114
2268
  * forwarded as a delta / activity frame.
@@ -2148,6 +2302,25 @@ function admitAgentEvent(gate, evt) {
2148
2302
  if (gate.sessionKey && evt.sessionKey && evt.sessionKey !== gate.sessionKey) return false;
2149
2303
  return true;
2150
2304
  }
2305
+ /**
2306
+ * Seed OpenClaw's runtime/event id with the chat service request that owns the
2307
+ * turn. Older callers omit the idempotency key and retain a runtime-owned id.
2308
+ */
2309
+ function resolveRequestedRunId(idempotencyKey) {
2310
+ return typeof idempotencyKey === "string" && idempotencyKey.length > 0 && idempotencyKey.length <= 256 ? idempotencyKey : null;
2311
+ }
2312
+ /** Preserve OpenClaw-provided reply options while seeding this exact run id. */
2313
+ function withReplyRunId(params, runId) {
2314
+ const existing = params.replyOptions;
2315
+ const replyOptions = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
2316
+ return {
2317
+ ...params,
2318
+ replyOptions: {
2319
+ ...replyOptions,
2320
+ runId
2321
+ }
2322
+ };
2323
+ }
2151
2324
  function asString(v) {
2152
2325
  return typeof v === "string" && v.length > 0 ? v : void 0;
2153
2326
  }
@@ -2713,6 +2886,7 @@ function enqueueAgentTurn(task, opts) {
2713
2886
  run: task,
2714
2887
  coalesceKey: opts?.coalesceKey,
2715
2888
  onSuperseded: opts?.onSuperseded ?? (() => void 0),
2889
+ onStarted: opts?.onStarted ?? (() => void 0),
2716
2890
  resolve,
2717
2891
  reject
2718
2892
  };
@@ -2738,6 +2912,9 @@ async function drainAgentTurns() {
2738
2912
  const entry = agentTurnBacklog.shift();
2739
2913
  if (!entry) break;
2740
2914
  try {
2915
+ try {
2916
+ entry.onStarted();
2917
+ } catch {}
2741
2918
  await entry.run();
2742
2919
  entry.resolve();
2743
2920
  } catch (err) {
@@ -2774,7 +2951,8 @@ function __setActiveRunForTest(ref) {
2774
2951
  * sessionKey that does NOT match the running turn yields no candidates — we
2775
2952
  * never abort another session's run on a shared daemon.
2776
2953
  */
2777
- function resolveAbortTargetKeys(active, abortSessionKey) {
2954
+ function resolveAbortTargetKeys(active, abortSessionKey, abortRunId) {
2955
+ if (abortRunId && (!active?.runId || active.runId !== abortRunId || abortSessionKey !== void 0 && active.requestSessionKey !== abortSessionKey)) return [];
2778
2956
  const keys = [];
2779
2957
  if (active && (!abortSessionKey || active.requestSessionKey === abortSessionKey)) {
2780
2958
  if (active.routeSessionKey) keys.push(active.routeSessionKey);
@@ -2796,8 +2974,8 @@ function resolveAbortTargetKeys(active, abortSessionKey) {
2796
2974
  * persisted.
2797
2975
  */
2798
2976
  function handleChatAbort(request, log) {
2799
- const { sessionKey } = request.params;
2800
- const targetKeys = resolveAbortTargetKeys(activeRun, sessionKey);
2977
+ const { sessionKey, runId } = request.params;
2978
+ const targetKeys = resolveAbortTargetKeys(activeRun, sessionKey, runId);
2801
2979
  let aborted = false;
2802
2980
  if (abortEmbeddedAgentRun && resolveActiveRunSessionId) {
2803
2981
  for (const key of targetKeys) {
@@ -2894,7 +3072,7 @@ async function handleAgentRequest(request, log) {
2894
3072
  chatClient?.sendResponse(request.id, false, { message: "OpenClaw SDK not available — cannot dispatch" });
2895
3073
  return;
2896
3074
  }
2897
- const { message, sessionKey: legacySessionKey, userId, conversationId, conversationType, tenantId, clientType, origin, displayName, identityId, identityProvider, attachments: rawAttachments, a2a, chatMessageId } = request.params;
3075
+ const { message, sessionKey: legacySessionKey, userId, conversationId, conversationType, tenantId, clientType, origin, displayName, identityId, identityProvider, idempotencyKey, attachments: rawAttachments, a2a, chatMessageId } = request.params;
2898
3076
  const isA2A = !!a2a;
2899
3077
  if (!message && !rawAttachments?.length) {
2900
3078
  chatClient?.sendResponse(request.id, false, { message: "Missing message" });
@@ -2905,8 +3083,9 @@ async function handleAgentRequest(request, log) {
2905
3083
  const sessionId = conversationId ?? legacySessionKey;
2906
3084
  if (!await getSession(sessionId)) await createSession(sessionId, "", "alfe", tenantId, userId);
2907
3085
  await addMessage(sessionId, "user", message, userId ?? senderId, displayName ?? senderId, void 0, userAttachmentsToStored(rawAttachments));
3086
+ const requestedRunId = resolveRequestedRunId(idempotencyKey);
2908
3087
  const runGate = {
2909
- runId: null,
3088
+ runId: requestedRunId,
2910
3089
  sessionKey: null
2911
3090
  };
2912
3091
  let lastThinkingLen = 0;
@@ -3091,11 +3270,19 @@ async function handleAgentRequest(request, log) {
3091
3270
  }
3092
3271
  activeRun = {
3093
3272
  requestSessionKey: legacySessionKey,
3094
- routeSessionKey
3273
+ routeSessionKey,
3274
+ ...requestedRunId ? { runId: requestedRunId } : {}
3095
3275
  };
3276
+ const dispatchChannel = requestedRunId ? {
3277
+ ...runtime.channel,
3278
+ reply: {
3279
+ ...runtime.channel.reply,
3280
+ dispatchReplyWithBufferedBlockDispatcher: (params) => runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher(withReplyRunId(params, requestedRunId))
3281
+ }
3282
+ } : runtime.channel;
3096
3283
  const result = await dispatchInbound({
3097
3284
  cfg,
3098
- runtime: { channel: runtime.channel },
3285
+ runtime: { channel: dispatchChannel },
3099
3286
  channel: "alfe",
3100
3287
  channelLabel,
3101
3288
  accountId: "default",
@@ -3242,7 +3429,7 @@ async function handleSessionsGet(request, log) {
3242
3429
  });
3243
3430
  return;
3244
3431
  }
3245
- chatClient?.sendResponse(request.id, true, {
3432
+ const response = boundSessionGetResponse(request.id, {
3246
3433
  sessionId: session.sessionId,
3247
3434
  agentId: session.agentId,
3248
3435
  channel: session.channel,
@@ -3257,6 +3444,8 @@ async function handleSessionsGet(request, log) {
3257
3444
  })),
3258
3445
  activity: await resolveHistoryActivity(session)
3259
3446
  });
3447
+ if (response.truncatedMessages > 0 || response.truncatedActivity > 0) log.warn(`sessions.get history bounded for relay frame: messagesDropped=${String(response.truncatedMessages)} activityDropped=${String(response.truncatedActivity)} frameBytes=${String(response.frameBytes)}`);
3448
+ chatClient?.sendResponse(request.id, true, response.payload);
3260
3449
  } catch (err) {
3261
3450
  const errMsg = err instanceof Error ? err.message : String(err);
3262
3451
  log.error(`sessions.get failed: ${errMsg}`);
@@ -3270,6 +3459,7 @@ const plugin = {
3270
3459
  version: pkg.version,
3271
3460
  activate(api) {
3272
3461
  const log = api.logger;
3462
+ const pluginConfig = (((api.config ?? {}).plugins?.entries)?.["@alfe.ai/openclaw-chat"] ?? {}).config ?? {};
3273
3463
  const outboundDeps = {
3274
3464
  getChatClient: () => chatClient,
3275
3465
  log,
@@ -3292,11 +3482,27 @@ const plugin = {
3292
3482
  components: clean
3293
3483
  });
3294
3484
  };
3295
- const a2aTools = buildA2ATools(() => chatClient, log, present);
3485
+ const messageIdentity = async (args) => {
3486
+ if (!chatClient?.isConnected) throw publicToolError("chat service not connected yet — try again in a moment");
3487
+ const direct = await getAgentApiClient(pluginConfig.apiKey).ensureDirectConversation(args.identityId);
3488
+ const sent = await sendIdentityMessageViaChat(outboundDeps, {
3489
+ conversationId: direct.conversationId,
3490
+ tenantId: direct.tenantId,
3491
+ userId: direct.userId
3492
+ }, args.message);
3493
+ return {
3494
+ status: "sent",
3495
+ identityId: direct.identityId,
3496
+ displayName: direct.displayName,
3497
+ conversationId: sent.conversationId,
3498
+ messageId: sent.messageId,
3499
+ newConversation: direct.created
3500
+ };
3501
+ };
3502
+ const a2aTools = buildA2ATools(() => chatClient, log, present, messageIdentity);
3296
3503
  for (const tool of a2aTools) api.registerTool(tool);
3297
3504
  log.info(`Registered ${String(a2aTools.length)} agent tools`);
3298
3505
  }
3299
- const pluginConfig = (((api.config ?? {}).plugins?.entries)?.["@alfe.ai/openclaw-chat"] ?? {}).config ?? {};
3300
3506
  const startChatService = async () => {
3301
3507
  const action = decideChatServiceStart({
3302
3508
  connectInFlight: connectingPromise !== null,
@@ -3337,16 +3543,28 @@ const plugin = {
3337
3543
  if (request.method === "agent") {
3338
3544
  const params = request.params;
3339
3545
  const coalesceKey = params.coalesce === "replace" && typeof params.sessionKey === "string" ? params.sessionKey : void 0;
3340
- await enqueueAgentTurn(() => handleAgentRequest(request, log), coalesceKey ? {
3341
- coalesceKey,
3342
- onSuperseded: () => {
3343
- log.info(`Agent turn superseded before start (coalesce=replace): ${coalesceKey}`);
3546
+ chatClient?.sendResponse(request.id, true, {
3547
+ status: "accepted",
3548
+ phase: "queued"
3549
+ });
3550
+ await enqueueAgentTurn(() => handleAgentRequest(request, log), {
3551
+ onStarted: () => {
3344
3552
  chatClient?.sendResponse(request.id, true, {
3345
- superseded: true,
3346
- sessionKey: coalesceKey
3553
+ status: "accepted",
3554
+ phase: "started"
3347
3555
  });
3348
- }
3349
- } : void 0);
3556
+ },
3557
+ ...coalesceKey ? {
3558
+ coalesceKey,
3559
+ onSuperseded: () => {
3560
+ log.info(`Agent turn superseded before start (coalesce=replace): ${coalesceKey}`);
3561
+ chatClient?.sendResponse(request.id, true, {
3562
+ superseded: true,
3563
+ sessionKey: coalesceKey
3564
+ });
3565
+ }
3566
+ } : {}
3567
+ });
3350
3568
  } else if (request.method === "chat.abort") handleChatAbort(request, log);
3351
3569
  else if (request.method === "sessions.list") await handleSessionsList(request, log);
3352
3570
  else if (request.method === "sessions.get") await handleSessionsGet(request, log);
@@ -3481,9 +3699,10 @@ const plugin = {
3481
3699
  }
3482
3700
  pluginRuntime = null;
3483
3701
  dispatchInbound = null;
3702
+ cachedAgentApi = null;
3484
3703
  resetActivation(CHAT_ACTIVATION_KEY);
3485
3704
  log.info("Chat plugin deactivated");
3486
3705
  }
3487
3706
  };
3488
3707
  //#endregion
3489
- export { createAlfeChannelPlugin as S, plugin as _, __agentTurnQueueForTest as a, rewriteAssistantText as b, admitAgentEvent as c, buildOriginEnvelopeContext as d, buildToolActivity as f, joinAssistantText as g, decideChatServiceStart as h, VOICE_REPLY_SYSTEM_PROMPT as i, buildA2ACompletePayload as l, computeOpenClawSdkAnchors as m, CROSS_AGENT_TOOLS_SYSTEM_PROMPT as n, __resetAgentTurnQueueForTest as o, buildTurnEnvelopeContext as p, MAX_INBOUND_ATTACHMENT_SIZE as r, __setActiveRunForTest as s, A2A_REPLY_SYSTEM_PROMPT as t, buildAttachmentLocalFilename as u, readAttachmentBodyBounded as v, stripLeakedToolSummary as x, resolveAbortTargetKeys as y };
3708
+ export { withReplyRunId as C, stripLeakedToolSummary as S, plugin as _, __agentTurnQueueForTest as a, resolveRequestedRunId as b, admitAgentEvent as c, buildOriginEnvelopeContext as d, buildToolActivity as f, joinAssistantText as g, decideChatServiceStart as h, VOICE_REPLY_SYSTEM_PROMPT as i, buildA2ACompletePayload as l, computeOpenClawSdkAnchors as m, CROSS_AGENT_TOOLS_SYSTEM_PROMPT as n, __resetAgentTurnQueueForTest as o, buildTurnEnvelopeContext as p, MAX_INBOUND_ATTACHMENT_SIZE as r, __setActiveRunForTest as s, A2A_REPLY_SYSTEM_PROMPT as t, buildAttachmentLocalFilename as u, readAttachmentBodyBounded as v, createAlfeChannelPlugin as w, rewriteAssistantText as x, resolveAbortTargetKeys as y };
@@ -26,6 +26,6 @@
26
26
  }
27
27
  },
28
28
  "contracts": {
29
- "tools": ["list_agents", "message_agent", "end_conversation", "chat_present_components"]
29
+ "tools": ["list_agents", "message_agent", "message_identity", "end_conversation", "chat_present_components"]
30
30
  }
31
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/openclaw-chat",
3
- "version": "0.9.14",
3
+ "version": "0.9.15",
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.17.1",
30
31
  "@alfe.ai/chat": "^0.0.18",
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"
32
+ "@alfe.ai/config": "^0.4.1",
33
+ "@alfe.ai/openclaw-plugin-kit": "0.2.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "openclaw": ">=2026.3.0"