@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 +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin.cjs +2 -0
- package/dist/plugin.d.cts +12 -2
- package/dist/plugin.d.ts +12 -2
- package/dist/plugin.js +2 -2
- package/dist/plugin2.cjs +251 -20
- package/dist/plugin2.d.cts +2 -2
- package/dist/plugin2.d.ts +2 -2
- package/dist/plugin2.js +240 -21
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
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
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
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
|
-
|
|
3346
|
-
|
|
3553
|
+
status: "accepted",
|
|
3554
|
+
phase: "started"
|
|
3347
3555
|
});
|
|
3348
|
-
}
|
|
3349
|
-
|
|
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
|
+
});
|
package/dist/plugin2.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
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
|
-
|
|
3346
|
-
|
|
3553
|
+
status: "accepted",
|
|
3554
|
+
phase: "started"
|
|
3347
3555
|
});
|
|
3348
|
-
}
|
|
3349
|
-
|
|
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 {
|
|
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 };
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/openclaw-chat",
|
|
3
|
-
"version": "0.9.
|
|
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/
|
|
32
|
-
"@alfe.ai/
|
|
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"
|