@alfe.ai/openclaw-chat 0.9.9 → 0.9.11
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/README.md +20 -11
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin.cjs +5 -0
- package/dist/plugin.d.cts +30 -11
- package/dist/plugin.d.ts +30 -11
- package/dist/plugin.js +2 -2
- package/dist/plugin2.cjs +471 -107
- package/dist/plugin2.d.cts +2 -2
- package/dist/plugin2.d.ts +2 -2
- package/dist/plugin2.js +445 -111
- package/package.json +5 -5
package/dist/plugin2.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
let node_module = require("node:module");
|
|
2
|
+
let node_crypto = require("node:crypto");
|
|
2
3
|
let node_fs_promises = require("node:fs/promises");
|
|
3
4
|
let node_fs = require("node:fs");
|
|
4
5
|
let node_child_process = require("node:child_process");
|
|
@@ -8,7 +9,6 @@ let node_os = require("node:os");
|
|
|
8
9
|
let _alfe_ai_chat = require("@alfe.ai/chat");
|
|
9
10
|
let _alfe_ai_agent_api_client = require("@alfe.ai/agent-api-client");
|
|
10
11
|
let _alfe_ai_openclaw_plugin_kit = require("@alfe.ai/openclaw-plugin-kit");
|
|
11
|
-
let node_crypto = require("node:crypto");
|
|
12
12
|
let _alfe_ai_config = require("@alfe.ai/config");
|
|
13
13
|
//#region src/outbound-media.ts
|
|
14
14
|
/**
|
|
@@ -155,29 +155,40 @@ async function uploadLocalFile(localPath, log, deps) {
|
|
|
155
155
|
const client = deps.getClient ? deps.getClient() : getUploadClient(log);
|
|
156
156
|
if (!client) return null;
|
|
157
157
|
let size;
|
|
158
|
+
let body;
|
|
158
159
|
try {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
const handle = await (0, node_fs_promises.open)(localPath, node_fs.constants.O_RDONLY | node_fs.constants.O_NOFOLLOW);
|
|
161
|
+
try {
|
|
162
|
+
const before = await handle.stat();
|
|
163
|
+
if (!before.isFile()) {
|
|
164
|
+
log.warn(`Outbound media ref is not a file — skipping: ${localPath}`);
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
size = before.size;
|
|
168
|
+
if (size <= 0) {
|
|
169
|
+
log.warn(`Outbound media file is empty — skipping: ${localPath}`);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
if (size > 26214400) {
|
|
173
|
+
log.warn(`Outbound media exceeds ${String(MAX_OUTBOUND_MEDIA_SIZE)} bytes (${String(size)}) — skipping: ${localPath}`);
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
body = Uint8Array.from(await handle.readFile());
|
|
177
|
+
const after = await handle.stat();
|
|
178
|
+
if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size || body.length !== before.size) {
|
|
179
|
+
log.warn(`Outbound media changed while being read — skipping: ${localPath}`);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
} finally {
|
|
183
|
+
await handle.close();
|
|
163
184
|
}
|
|
164
|
-
size = st.size;
|
|
165
185
|
} catch {
|
|
166
186
|
log.warn(`Outbound media file not found — skipping: ${localPath}`);
|
|
167
187
|
return null;
|
|
168
188
|
}
|
|
169
|
-
if (size <= 0) {
|
|
170
|
-
log.warn(`Outbound media file is empty — skipping: ${localPath}`);
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
if (size > 26214400) {
|
|
174
|
-
log.warn(`Outbound media exceeds ${String(MAX_OUTBOUND_MEDIA_SIZE)} bytes (${String(size)}) — skipping: ${localPath}`);
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
189
|
const filename = (0, node_path.basename)(localPath);
|
|
178
190
|
const mimeType = mimeFromPath(localPath);
|
|
179
191
|
try {
|
|
180
|
-
const body = await (0, node_fs_promises.readFile)(localPath);
|
|
181
192
|
const { attachments } = await client.presignAttachments([{
|
|
182
193
|
filename,
|
|
183
194
|
mimeType,
|
|
@@ -408,13 +419,17 @@ function isSafeComponentUrl(raw) {
|
|
|
408
419
|
return false;
|
|
409
420
|
}
|
|
410
421
|
if (u.protocol !== "https:") return false;
|
|
422
|
+
if (u.username || u.password) return false;
|
|
411
423
|
const host = u.hostname.toLowerCase();
|
|
412
424
|
return host === "alfe.ai" || host.endsWith(".alfe.ai");
|
|
413
425
|
}
|
|
414
426
|
const MAX_COMPONENTS_PER_MESSAGE = 10;
|
|
415
|
-
const MAX_COMPONENT_LABEL_CHARS = 120;
|
|
416
|
-
const MAX_COMPONENT_VALUE_CHARS = 400;
|
|
427
|
+
const MAX_COMPONENT_LABEL_CHARS$1 = 120;
|
|
428
|
+
const MAX_COMPONENT_VALUE_CHARS$1 = 400;
|
|
417
429
|
const MAX_OPTIONS_PER_COMPONENT = 25;
|
|
430
|
+
const MAX_COMPONENT_ID_CHARS$1 = 128;
|
|
431
|
+
const MAX_CONVERSATION_ID_CHARS = 512;
|
|
432
|
+
const MAX_USER_ID_CHARS = 256;
|
|
418
433
|
/** Cap a maybe-string; returns '' for non-strings so callers can `if (!x)`. */
|
|
419
434
|
function capStr(v, max) {
|
|
420
435
|
return typeof v === "string" ? v.slice(0, max) : "";
|
|
@@ -431,8 +446,8 @@ function sanitizeOptions(raw) {
|
|
|
431
446
|
if (out.length >= MAX_OPTIONS_PER_COMPONENT) break;
|
|
432
447
|
if (!item || typeof item !== "object") continue;
|
|
433
448
|
const o = item;
|
|
434
|
-
const label = capStr(o.label, MAX_COMPONENT_LABEL_CHARS);
|
|
435
|
-
const value = capStr(o.value, MAX_COMPONENT_VALUE_CHARS);
|
|
449
|
+
const label = capStr(o.label, MAX_COMPONENT_LABEL_CHARS$1);
|
|
450
|
+
const value = capStr(o.value, MAX_COMPONENT_VALUE_CHARS$1);
|
|
436
451
|
if (!label || !value) continue;
|
|
437
452
|
out.push({
|
|
438
453
|
label,
|
|
@@ -458,8 +473,8 @@ function sanitizeComponents(raw) {
|
|
|
458
473
|
if (out.length >= MAX_COMPONENTS_PER_MESSAGE) break;
|
|
459
474
|
if (!item || typeof item !== "object") continue;
|
|
460
475
|
const c = item;
|
|
461
|
-
const label = capStr(c.label, MAX_COMPONENT_LABEL_CHARS);
|
|
462
|
-
let id = typeof c.id === "string" && c.id ? c.id : (0, node_crypto.randomUUID)();
|
|
476
|
+
const label = capStr(c.label, MAX_COMPONENT_LABEL_CHARS$1);
|
|
477
|
+
let id = typeof c.id === "string" && c.id ? c.id.slice(0, MAX_COMPONENT_ID_CHARS$1) : (0, node_crypto.randomUUID)();
|
|
463
478
|
if (usedIds.has(id)) id = (0, node_crypto.randomUUID)();
|
|
464
479
|
usedIds.add(id);
|
|
465
480
|
const style = c.style === "primary" || c.style === "secondary" || c.style === "danger" ? c.style : void 0;
|
|
@@ -477,7 +492,7 @@ function sanitizeComponents(raw) {
|
|
|
477
492
|
});
|
|
478
493
|
} else if (c.type === "quick_reply") {
|
|
479
494
|
if (!label) continue;
|
|
480
|
-
const value = capStr(c.value, MAX_COMPONENT_VALUE_CHARS);
|
|
495
|
+
const value = capStr(c.value, MAX_COMPONENT_VALUE_CHARS$1);
|
|
481
496
|
if (!value) continue;
|
|
482
497
|
out.push({
|
|
483
498
|
type: "quick_reply",
|
|
@@ -489,7 +504,7 @@ function sanitizeComponents(raw) {
|
|
|
489
504
|
} else if (c.type === "select") {
|
|
490
505
|
const options = sanitizeOptions(c.options);
|
|
491
506
|
if (options.length === 0) continue;
|
|
492
|
-
const placeholder = capStr(c.placeholder, MAX_COMPONENT_LABEL_CHARS);
|
|
507
|
+
const placeholder = capStr(c.placeholder, MAX_COMPONENT_LABEL_CHARS$1);
|
|
493
508
|
out.push({
|
|
494
509
|
type: "select",
|
|
495
510
|
id,
|
|
@@ -500,7 +515,7 @@ function sanitizeComponents(raw) {
|
|
|
500
515
|
} else if (c.type === "multi_select") {
|
|
501
516
|
const options = sanitizeOptions(c.options);
|
|
502
517
|
if (options.length === 0) continue;
|
|
503
|
-
const submitLabel = capStr(c.submitLabel, MAX_COMPONENT_LABEL_CHARS);
|
|
518
|
+
const submitLabel = capStr(c.submitLabel, MAX_COMPONENT_LABEL_CHARS$1);
|
|
504
519
|
out.push({
|
|
505
520
|
type: "multi_select",
|
|
506
521
|
id,
|
|
@@ -509,11 +524,11 @@ function sanitizeComponents(raw) {
|
|
|
509
524
|
...submitLabel ? { submitLabel } : {}
|
|
510
525
|
});
|
|
511
526
|
} else if (c.type === "confirm") {
|
|
512
|
-
const confirmLabel = capStr(c.confirmLabel, MAX_COMPONENT_LABEL_CHARS);
|
|
513
|
-
const confirmValue = capStr(c.confirmValue, MAX_COMPONENT_VALUE_CHARS);
|
|
527
|
+
const confirmLabel = capStr(c.confirmLabel, MAX_COMPONENT_LABEL_CHARS$1);
|
|
528
|
+
const confirmValue = capStr(c.confirmValue, MAX_COMPONENT_VALUE_CHARS$1);
|
|
514
529
|
if (!confirmLabel || !confirmValue) continue;
|
|
515
|
-
const cancelLabel = capStr(c.cancelLabel, MAX_COMPONENT_LABEL_CHARS);
|
|
516
|
-
const cancelValue = capStr(c.cancelValue, MAX_COMPONENT_VALUE_CHARS);
|
|
530
|
+
const cancelLabel = capStr(c.cancelLabel, MAX_COMPONENT_LABEL_CHARS$1);
|
|
531
|
+
const cancelValue = capStr(c.cancelValue, MAX_COMPONENT_VALUE_CHARS$1);
|
|
517
532
|
out.push({
|
|
518
533
|
type: "confirm",
|
|
519
534
|
id,
|
|
@@ -521,11 +536,11 @@ function sanitizeComponents(raw) {
|
|
|
521
536
|
confirmLabel,
|
|
522
537
|
confirmValue,
|
|
523
538
|
...cancelLabel ? { cancelLabel } : {},
|
|
524
|
-
...cancelValue ? { cancelValue } : {}
|
|
539
|
+
...cancelLabel && cancelValue ? { cancelValue } : {}
|
|
525
540
|
});
|
|
526
541
|
} else if (c.type === "copy_button") {
|
|
527
542
|
if (!label) continue;
|
|
528
|
-
const value = capStr(c.value, MAX_COMPONENT_VALUE_CHARS);
|
|
543
|
+
const value = capStr(c.value, MAX_COMPONENT_VALUE_CHARS$1);
|
|
529
544
|
if (!value) continue;
|
|
530
545
|
out.push({
|
|
531
546
|
type: "copy_button",
|
|
@@ -539,6 +554,23 @@ function sanitizeComponents(raw) {
|
|
|
539
554
|
}
|
|
540
555
|
const CHANNEL_ID = "alfe";
|
|
541
556
|
const DEFAULT_ACCOUNT_ID = "default";
|
|
557
|
+
/** Persist only bucket-backed media; passthrough URLs have no stable id. */
|
|
558
|
+
function mediaToStoredAttachments(media) {
|
|
559
|
+
const stored = [];
|
|
560
|
+
for (const item of media) {
|
|
561
|
+
if (!item.attachmentId) continue;
|
|
562
|
+
const mimeType = item.mimeType ?? "application/octet-stream";
|
|
563
|
+
const type = mimeType.startsWith("image/") ? "image" : mimeType.startsWith("video/") ? "video" : mimeType.startsWith("audio/") ? "audio" : mimeType === "application/pdf" ? "document" : "file";
|
|
564
|
+
stored.push({
|
|
565
|
+
attachmentId: item.attachmentId,
|
|
566
|
+
type,
|
|
567
|
+
mimeType,
|
|
568
|
+
filename: item.filename ?? "file",
|
|
569
|
+
...typeof item.size === "number" && item.size > 0 ? { size: item.size } : {}
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
return stored.length ? stored : void 0;
|
|
573
|
+
}
|
|
542
574
|
async function sendViaChat(deps, ctx, mediaUrl, components) {
|
|
543
575
|
const client = deps.getChatClient();
|
|
544
576
|
if (!client) throw new Error("Chat service not connected — cannot deliver");
|
|
@@ -564,7 +596,8 @@ async function sendViaChat(deps, ctx, mediaUrl, components) {
|
|
|
564
596
|
await deps.createSession(conversationId, "", "alfe", void 0, userId);
|
|
565
597
|
}
|
|
566
598
|
}
|
|
567
|
-
|
|
599
|
+
const storedMedia = mediaToStoredAttachments(media.attachments);
|
|
600
|
+
if (components?.length || storedMedia?.length) await deps.addMessage(conversationId, "assistant", text, void 0, void 0, components, storedMedia);
|
|
568
601
|
else await deps.addMessage(conversationId, "assistant", text);
|
|
569
602
|
const messageId = (0, node_crypto.randomUUID)();
|
|
570
603
|
client.notify("agent-message", {
|
|
@@ -702,17 +735,22 @@ function createAlfeChannelPlugin(deps) {
|
|
|
702
735
|
error: /* @__PURE__ */ new Error("Missing target — use user:{userId} or conv:{conversationId}")
|
|
703
736
|
};
|
|
704
737
|
if (to.startsWith("conv:")) {
|
|
705
|
-
|
|
738
|
+
const convId = to.slice(5);
|
|
739
|
+
if (!convId) return {
|
|
706
740
|
ok: false,
|
|
707
741
|
error: /* @__PURE__ */ new Error("Empty conversation ID")
|
|
708
742
|
};
|
|
743
|
+
if (convId.length > MAX_CONVERSATION_ID_CHARS || /\s/.test(convId)) return {
|
|
744
|
+
ok: false,
|
|
745
|
+
error: /* @__PURE__ */ new Error("Invalid conversation ID")
|
|
746
|
+
};
|
|
709
747
|
return {
|
|
710
748
|
ok: true,
|
|
711
749
|
to
|
|
712
750
|
};
|
|
713
751
|
}
|
|
714
752
|
const userId = to.startsWith("user:") ? to.slice(5) : to;
|
|
715
|
-
if (!userId || userId === "anon") return {
|
|
753
|
+
if (!userId || userId === "anon" || userId.length > MAX_USER_ID_CHARS || /\s/.test(userId)) return {
|
|
716
754
|
ok: false,
|
|
717
755
|
error: /* @__PURE__ */ new Error("Invalid target: userId is required")
|
|
718
756
|
};
|
|
@@ -750,18 +788,27 @@ function createAlfeChannelPlugin(deps) {
|
|
|
750
788
|
return params.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
751
789
|
},
|
|
752
790
|
applyAccountConfig(params) {
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
791
|
+
const previousChannels = params.cfg.channels ?? {};
|
|
792
|
+
const previousSection = previousChannels.alfe ?? {};
|
|
793
|
+
const section = {
|
|
794
|
+
...previousSection,
|
|
795
|
+
...previousSection.accounts ? { accounts: { ...previousSection.accounts } } : {}
|
|
796
|
+
};
|
|
797
|
+
const cfg = {
|
|
798
|
+
...params.cfg,
|
|
799
|
+
channels: {
|
|
800
|
+
...previousChannels,
|
|
801
|
+
alfe: section
|
|
802
|
+
}
|
|
803
|
+
};
|
|
757
804
|
if (params.accountId === DEFAULT_ACCOUNT_ID) section.enabled = true;
|
|
758
|
-
else {
|
|
759
|
-
section.accounts
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
}
|
|
764
|
-
}
|
|
805
|
+
else section.accounts = {
|
|
806
|
+
...section.accounts,
|
|
807
|
+
[params.accountId]: {
|
|
808
|
+
...params.input,
|
|
809
|
+
enabled: true
|
|
810
|
+
}
|
|
811
|
+
};
|
|
765
812
|
return cfg;
|
|
766
813
|
}
|
|
767
814
|
}
|
|
@@ -776,8 +823,7 @@ function createAlfeChannelPlugin(deps) {
|
|
|
776
823
|
function isAlfeSessionKey(key) {
|
|
777
824
|
if (key.startsWith("alfe:")) return true;
|
|
778
825
|
if (key.includes(":alfe:")) return true;
|
|
779
|
-
|
|
780
|
-
return false;
|
|
826
|
+
return /^(?:agent:[^:]+:)?(?:chat-|sms-|wa-)/.test(key);
|
|
781
827
|
}
|
|
782
828
|
/**
|
|
783
829
|
* Extract the channel mode from a standardized session key or conversationId.
|
|
@@ -787,6 +833,25 @@ function extractChannelMode(conversationId, fallback = "chat") {
|
|
|
787
833
|
return /^alfe:(\w+):/.exec(conversationId)?.[1] ?? fallback;
|
|
788
834
|
}
|
|
789
835
|
//#endregion
|
|
836
|
+
//#region src/inbound-hook-routing.ts
|
|
837
|
+
/**
|
|
838
|
+
* Preserve Alfe's authoritative identity route on OpenClaw's canonical inbound
|
|
839
|
+
* fields. Arbitrary `extraContext` values are not copied into plugin message
|
|
840
|
+
* hooks, while these fields become `metadata.provider`, `ctx.channelId`, and
|
|
841
|
+
* `ctx.conversationId` respectively.
|
|
842
|
+
*/
|
|
843
|
+
function buildInboundHookRouting(identityProvider, channelMode, conversationId) {
|
|
844
|
+
const explicitProvider = identityProvider?.trim();
|
|
845
|
+
const modeProvider = channelMode.trim();
|
|
846
|
+
const provider = explicitProvider && explicitProvider.length > 0 ? explicitProvider : modeProvider.length > 0 ? modeProvider : "chat";
|
|
847
|
+
return {
|
|
848
|
+
provider,
|
|
849
|
+
surface: "alfe",
|
|
850
|
+
originatingChannel: provider,
|
|
851
|
+
...conversationId ? { originatingTo: conversationId } : {}
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
//#endregion
|
|
790
855
|
//#region src/session-store.ts
|
|
791
856
|
/**
|
|
792
857
|
* Session Store — persists chat sessions to the local filesystem.
|
|
@@ -813,6 +878,94 @@ async function ensureDir() {
|
|
|
813
878
|
function sessionPath(sessionId) {
|
|
814
879
|
return (0, node_path.join)(SESSIONS_DIR, `${sessionId.replace(/[^a-zA-Z0-9_-]/g, "_")}.json`);
|
|
815
880
|
}
|
|
881
|
+
function isRecord(value) {
|
|
882
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
883
|
+
}
|
|
884
|
+
function isOptionalString(value) {
|
|
885
|
+
return value === void 0 || typeof value === "string";
|
|
886
|
+
}
|
|
887
|
+
function isOptionalBoolean(value) {
|
|
888
|
+
return value === void 0 || typeof value === "boolean";
|
|
889
|
+
}
|
|
890
|
+
function isStoredSelectOption(value) {
|
|
891
|
+
return isRecord(value) && typeof value.label === "string" && typeof value.value === "string";
|
|
892
|
+
}
|
|
893
|
+
function isStoredMessageComponent(value) {
|
|
894
|
+
if (!isRecord(value) || typeof value.type !== "string" || typeof value.id !== "string") return false;
|
|
895
|
+
if (!(value.style === void 0 || typeof value.style === "string" && [
|
|
896
|
+
"primary",
|
|
897
|
+
"secondary",
|
|
898
|
+
"danger"
|
|
899
|
+
].includes(value.style))) return false;
|
|
900
|
+
switch (value.type) {
|
|
901
|
+
case "link_button": return typeof value.label === "string" && typeof value.url === "string" && (value.target === void 0 || typeof value.target === "string" && [
|
|
902
|
+
"same-tab",
|
|
903
|
+
"new-tab",
|
|
904
|
+
"popup"
|
|
905
|
+
].includes(value.target));
|
|
906
|
+
case "quick_reply": return typeof value.label === "string" && typeof value.value === "string";
|
|
907
|
+
case "select": return isOptionalString(value.label) && isOptionalString(value.placeholder) && Array.isArray(value.options) && value.options.every(isStoredSelectOption);
|
|
908
|
+
case "multi_select": return isOptionalString(value.label) && isOptionalString(value.submitLabel) && Array.isArray(value.options) && value.options.every(isStoredSelectOption);
|
|
909
|
+
case "confirm": return isOptionalString(value.label) && typeof value.confirmLabel === "string" && typeof value.confirmValue === "string" && isOptionalString(value.cancelLabel) && isOptionalString(value.cancelValue);
|
|
910
|
+
case "copy_button": return typeof value.label === "string" && typeof value.value === "string";
|
|
911
|
+
default: return false;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
function isStoredAttachment(value) {
|
|
915
|
+
return isRecord(value) && typeof value.attachmentId === "string" && typeof value.type === "string" && [
|
|
916
|
+
"image",
|
|
917
|
+
"video",
|
|
918
|
+
"audio",
|
|
919
|
+
"document",
|
|
920
|
+
"file"
|
|
921
|
+
].includes(value.type) && typeof value.mimeType === "string" && typeof value.filename === "string" && (value.size === void 0 || typeof value.size === "number" && Number.isFinite(value.size) && value.size >= 0);
|
|
922
|
+
}
|
|
923
|
+
function isChatActivityRecord(value) {
|
|
924
|
+
if (!isRecord(value) || typeof value.ts !== "number" || !Number.isFinite(value.ts)) return false;
|
|
925
|
+
if (value.kind === "thinking") return typeof value.text === "string";
|
|
926
|
+
if (value.kind !== "tool") return false;
|
|
927
|
+
if (typeof value.toolCallId !== "string" || typeof value.name !== "string" || typeof value.status !== "string" || ![
|
|
928
|
+
"done",
|
|
929
|
+
"failed",
|
|
930
|
+
"interrupted"
|
|
931
|
+
].includes(value.status) || !isOptionalString(value.summary) || !isOptionalString(value.argsText) || !isOptionalString(value.resultText) || !isOptionalBoolean(value.isError) || value.durationMs !== void 0 && (typeof value.durationMs !== "number" || !Number.isFinite(value.durationMs) || value.durationMs < 0)) return false;
|
|
932
|
+
if (value.truncated === void 0) return true;
|
|
933
|
+
return isRecord(value.truncated) && isOptionalBoolean(value.truncated.args) && isOptionalBoolean(value.truncated.progress) && isOptionalBoolean(value.truncated.result);
|
|
934
|
+
}
|
|
935
|
+
function isStoredRoute(value) {
|
|
936
|
+
return isRecord(value) && typeof value.sessionKey === "string" && typeof value.storePath === "string";
|
|
937
|
+
}
|
|
938
|
+
/** Parse the local persistence boundary without trusting a JSON cast. */
|
|
939
|
+
function asSessionData(value, expectedSessionId) {
|
|
940
|
+
if (!isRecord(value)) return null;
|
|
941
|
+
const record = value;
|
|
942
|
+
if (typeof record.sessionId !== "string" || expectedSessionId !== void 0 && record.sessionId !== expectedSessionId || typeof record.agentId !== "string" || typeof record.channel !== "string" || !isOptionalString(record.tenantId) || !isOptionalString(record.userId) || typeof record.createdAt !== "string" || typeof record.updatedAt !== "string" || !Array.isArray(record.messages)) return null;
|
|
943
|
+
if (!record.messages.every((message) => {
|
|
944
|
+
if (!message || typeof message !== "object" || Array.isArray(message)) return false;
|
|
945
|
+
const item = message;
|
|
946
|
+
return (item.role === "user" || item.role === "assistant") && typeof item.content === "string" && typeof item.timestamp === "number" && Number.isFinite(item.timestamp) && isOptionalString(item.senderId) && isOptionalString(item.senderName) && (item.components === void 0 || Array.isArray(item.components) && item.components.every(isStoredMessageComponent)) && (item.attachments === void 0 || Array.isArray(item.attachments) && item.attachments.every(isStoredAttachment));
|
|
947
|
+
})) return null;
|
|
948
|
+
if (record.activity !== void 0 && (!Array.isArray(record.activity) || !record.activity.every(isChatActivityRecord))) return null;
|
|
949
|
+
if (record.routes !== void 0 && (!Array.isArray(record.routes) || !record.routes.every(isStoredRoute))) return null;
|
|
950
|
+
return record;
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Replace a session file atomically with owner-only permissions. Temporary
|
|
954
|
+
* names never end in `.json`, so list/backfill scanners ignore crash debris.
|
|
955
|
+
*/
|
|
956
|
+
async function writeTextAtomically(path, contents) {
|
|
957
|
+
const temporary = (0, node_path.join)((0, node_path.dirname)(path), `.${(0, node_path.basename)(path)}.${String(process.pid)}.${(0, node_crypto.randomUUID)()}.tmp`);
|
|
958
|
+
try {
|
|
959
|
+
await (0, node_fs_promises.writeFile)(temporary, contents, {
|
|
960
|
+
encoding: "utf-8",
|
|
961
|
+
mode: 384
|
|
962
|
+
});
|
|
963
|
+
await (0, node_fs_promises.rename)(temporary, path);
|
|
964
|
+
} catch (error) {
|
|
965
|
+
await (0, node_fs_promises.unlink)(temporary).catch(() => void 0);
|
|
966
|
+
throw error;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
816
969
|
async function cleanupOldSessions() {
|
|
817
970
|
if (Date.now() - lastCleanupAt < CLEANUP_INTERVAL_MS) return;
|
|
818
971
|
try {
|
|
@@ -823,6 +976,7 @@ async function cleanupOldSessions() {
|
|
|
823
976
|
const filePath = (0, node_path.join)(SESSIONS_DIR, file);
|
|
824
977
|
if (now - (await (0, node_fs_promises.stat)(filePath)).mtimeMs > MAX_AGE_MS) await (0, node_fs_promises.unlink)(filePath);
|
|
825
978
|
} catch {}
|
|
979
|
+
lastCleanupAt = Date.now();
|
|
826
980
|
return;
|
|
827
981
|
}
|
|
828
982
|
const fileStats = [];
|
|
@@ -850,7 +1004,7 @@ async function cleanupOldSessions() {
|
|
|
850
1004
|
async function getSession(sessionId) {
|
|
851
1005
|
try {
|
|
852
1006
|
const data = await (0, node_fs_promises.readFile)(sessionPath(sessionId), "utf-8");
|
|
853
|
-
return JSON.parse(data);
|
|
1007
|
+
return asSessionData(JSON.parse(data), sessionId);
|
|
854
1008
|
} catch {
|
|
855
1009
|
return null;
|
|
856
1010
|
}
|
|
@@ -858,7 +1012,7 @@ async function getSession(sessionId) {
|
|
|
858
1012
|
async function saveSession(session) {
|
|
859
1013
|
await ensureDir();
|
|
860
1014
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
861
|
-
await (
|
|
1015
|
+
await writeTextAtomically(sessionPath(session.sessionId), JSON.stringify(session, null, 2));
|
|
862
1016
|
}
|
|
863
1017
|
async function createSession(sessionId, agentId, channel, tenantId, userId) {
|
|
864
1018
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -954,7 +1108,8 @@ async function listSessions(filters, limit = 50) {
|
|
|
954
1108
|
const summaries = [];
|
|
955
1109
|
for (const file of jsonFiles) try {
|
|
956
1110
|
const data = await (0, node_fs_promises.readFile)((0, node_path.join)(SESSIONS_DIR, file), "utf-8");
|
|
957
|
-
const session = JSON.parse(data);
|
|
1111
|
+
const session = asSessionData(JSON.parse(data));
|
|
1112
|
+
if (!session) continue;
|
|
958
1113
|
if (filters?.agentId && session.agentId !== filters.agentId) continue;
|
|
959
1114
|
if (filters?.channel && session.channel !== filters.channel) continue;
|
|
960
1115
|
if (filters?.tenantId && session.tenantId !== filters.tenantId) continue;
|
|
@@ -976,7 +1131,8 @@ async function listSessions(filters, limit = 50) {
|
|
|
976
1131
|
const aTime = a.lastMessageAt ?? a.createdAt;
|
|
977
1132
|
return (b.lastMessageAt ?? b.createdAt).localeCompare(aTime);
|
|
978
1133
|
});
|
|
979
|
-
|
|
1134
|
+
const boundedLimit = Number.isFinite(limit) ? Math.min(100, Math.max(1, Math.trunc(limit))) : 50;
|
|
1135
|
+
return summaries.slice(0, boundedLimit);
|
|
980
1136
|
}
|
|
981
1137
|
//#endregion
|
|
982
1138
|
//#region src/activity-serialize.ts
|
|
@@ -1337,6 +1493,7 @@ function stripThinkSpans(text) {
|
|
|
1337
1493
|
const MAX_ENTRIES = 500;
|
|
1338
1494
|
const MAX_THINKING_CHARS = 16e3;
|
|
1339
1495
|
const MAX_TRANSCRIPT_BYTES = 4 * 1024 * 1024;
|
|
1496
|
+
const MAX_TRANSCRIPT_INDEX_BYTES = 4 * 1024 * 1024;
|
|
1340
1497
|
/** Default OpenClaw store for the daemon's single agent ("main"). */
|
|
1341
1498
|
function defaultStorePath() {
|
|
1342
1499
|
return (0, node_path.join)((0, node_os.homedir)(), ".openclaw", "agents", "main", "sessions", "sessions.json");
|
|
@@ -1357,8 +1514,8 @@ async function readTranscriptTail(path) {
|
|
|
1357
1514
|
const { size } = await handle.stat();
|
|
1358
1515
|
if (size <= MAX_TRANSCRIPT_BYTES) return await handle.readFile({ encoding: "utf-8" });
|
|
1359
1516
|
const buf = Buffer.alloc(MAX_TRANSCRIPT_BYTES);
|
|
1360
|
-
await handle.read(buf, 0, MAX_TRANSCRIPT_BYTES, size - MAX_TRANSCRIPT_BYTES);
|
|
1361
|
-
const text = buf.toString("utf-8");
|
|
1517
|
+
const { bytesRead } = await handle.read(buf, 0, MAX_TRANSCRIPT_BYTES, size - MAX_TRANSCRIPT_BYTES);
|
|
1518
|
+
const text = buf.subarray(0, bytesRead).toString("utf-8");
|
|
1362
1519
|
const firstNewline = text.indexOf("\n");
|
|
1363
1520
|
return firstNewline === -1 ? "" : text.slice(firstNewline + 1);
|
|
1364
1521
|
} finally {
|
|
@@ -1368,30 +1525,44 @@ async function readTranscriptTail(path) {
|
|
|
1368
1525
|
return null;
|
|
1369
1526
|
}
|
|
1370
1527
|
}
|
|
1528
|
+
/** Read a complete JSON index only when it stays inside the local memory cap. */
|
|
1529
|
+
async function readTranscriptIndex(path) {
|
|
1530
|
+
try {
|
|
1531
|
+
const handle = await (0, node_fs_promises.open)(path, "r");
|
|
1532
|
+
try {
|
|
1533
|
+
const { size } = await handle.stat();
|
|
1534
|
+
if (size < 1 || size > MAX_TRANSCRIPT_INDEX_BYTES) return null;
|
|
1535
|
+
return JSON.parse(await handle.readFile({ encoding: "utf-8" }));
|
|
1536
|
+
} finally {
|
|
1537
|
+
await handle.close();
|
|
1538
|
+
}
|
|
1539
|
+
} catch {
|
|
1540
|
+
return null;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1371
1543
|
/**
|
|
1372
1544
|
* Find the transcript files for a conversation: exact route matches first,
|
|
1373
1545
|
* then an index scan for `:conv:{conversationId}` keys (retroactive path).
|
|
1374
1546
|
* Returns absolute jsonl paths, deduped.
|
|
1375
1547
|
*/
|
|
1376
1548
|
async function resolveTranscriptPaths(conversationId, routes) {
|
|
1377
|
-
const
|
|
1549
|
+
const validRoutes = (routes ?? []).filter((route) => Boolean(route) && typeof route === "object" && typeof route.sessionKey === "string" && route.sessionKey.length > 0 && typeof route.storePath === "string" && route.storePath.length > 0 && route.storePath.length <= 4096);
|
|
1550
|
+
const storePaths = new Set(validRoutes.map((r) => r.storePath));
|
|
1378
1551
|
if (storePaths.size === 0) storePaths.add(defaultStorePath());
|
|
1379
|
-
const routeKeys = new Set(
|
|
1552
|
+
const routeKeys = new Set(validRoutes.map((r) => r.sessionKey));
|
|
1380
1553
|
const convSegment = `:conv:${conversationId}`;
|
|
1381
1554
|
const files = /* @__PURE__ */ new Set();
|
|
1382
1555
|
for (const storePath of storePaths) {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
index = JSON.parse(await (0, node_fs_promises.readFile)(storePath, "utf-8"));
|
|
1386
|
-
} catch {
|
|
1387
|
-
continue;
|
|
1388
|
-
}
|
|
1556
|
+
const parsed = await readTranscriptIndex(storePath);
|
|
1557
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue;
|
|
1389
1558
|
const dir = (0, node_path.dirname)(storePath);
|
|
1390
|
-
for (const [key,
|
|
1391
|
-
if (!
|
|
1559
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1560
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
1561
|
+
const sessionId = value.sessionId;
|
|
1562
|
+
if (typeof sessionId !== "string" || !sessionId) continue;
|
|
1392
1563
|
if (!(routeKeys.has(key) || key.endsWith(convSegment) || key.includes(`${convSegment}:`))) continue;
|
|
1393
|
-
if (!/^[A-Za-z0-9._-]+$/.test(
|
|
1394
|
-
files.add((0, node_path.join)(dir, `${
|
|
1564
|
+
if (!/^[A-Za-z0-9._-]+$/.test(sessionId)) continue;
|
|
1565
|
+
files.add((0, node_path.join)(dir, `${sessionId}.jsonl`));
|
|
1395
1566
|
}
|
|
1396
1567
|
}
|
|
1397
1568
|
return [...files];
|
|
@@ -1481,6 +1652,18 @@ async function collectTranscriptActivity(conversationId, routes) {
|
|
|
1481
1652
|
}
|
|
1482
1653
|
//#endregion
|
|
1483
1654
|
//#region src/a2a-tools.ts
|
|
1655
|
+
const MAX_AGENT_ID_CHARS = 256;
|
|
1656
|
+
const MAX_A2A_MESSAGE_CHARS = 32e3;
|
|
1657
|
+
const MAX_THREAD_ID_CHARS = 128;
|
|
1658
|
+
const MAX_A2A_DEPTH = 20;
|
|
1659
|
+
const MAX_COMPONENT_TARGET_CHARS = 512;
|
|
1660
|
+
const MAX_COMPONENT_TEXT_CHARS = 32e3;
|
|
1661
|
+
const MAX_COMPONENT_ID_CHARS = 128;
|
|
1662
|
+
const MAX_COMPONENT_LABEL_CHARS = 120;
|
|
1663
|
+
const MAX_COMPONENT_VALUE_CHARS = 400;
|
|
1664
|
+
const MAX_COMPONENT_URL_CHARS = 2048;
|
|
1665
|
+
const MAX_COMPONENTS = 10;
|
|
1666
|
+
const MAX_COMPONENT_OPTIONS = 25;
|
|
1484
1667
|
let conversationEndRequested = false;
|
|
1485
1668
|
let a2aTurnArmed = false;
|
|
1486
1669
|
/**
|
|
@@ -1509,7 +1692,7 @@ function isA2AEndSignalled() {
|
|
|
1509
1692
|
function buildA2ATools(getChatClient, log, present) {
|
|
1510
1693
|
const requireClient = () => {
|
|
1511
1694
|
const client = getChatClient();
|
|
1512
|
-
if (!client) throw
|
|
1695
|
+
if (!client) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)("chat service not connected yet — try again in a moment");
|
|
1513
1696
|
return client;
|
|
1514
1697
|
};
|
|
1515
1698
|
const tools = [
|
|
@@ -1528,24 +1711,33 @@ function buildA2ATools(getChatClient, log, present) {
|
|
|
1528
1711
|
}),
|
|
1529
1712
|
(0, _alfe_ai_openclaw_plugin_kit.defineTool)({
|
|
1530
1713
|
name: "message_agent",
|
|
1531
|
-
description: "Send a message to another agent. Starts a new conversation thread, or continues an existing one. The conversation will bounce back and forth automatically until
|
|
1714
|
+
description: "Send a message to another agent. Starts a new conversation thread, or continues an existing one. The conversation will bounce back and forth automatically until an agent calls end_conversation() or the server depth limit is reached.",
|
|
1532
1715
|
parameters: {
|
|
1533
1716
|
type: "object",
|
|
1534
1717
|
properties: {
|
|
1535
1718
|
agent_id: {
|
|
1536
1719
|
type: "string",
|
|
1720
|
+
minLength: 1,
|
|
1721
|
+
maxLength: MAX_AGENT_ID_CHARS,
|
|
1537
1722
|
description: "Target agent ID (use list_agents to find available agents)"
|
|
1538
1723
|
},
|
|
1539
1724
|
message: {
|
|
1540
1725
|
type: "string",
|
|
1726
|
+
minLength: 1,
|
|
1727
|
+
maxLength: MAX_A2A_MESSAGE_CHARS,
|
|
1541
1728
|
description: "Message to send to the agent"
|
|
1542
1729
|
},
|
|
1543
1730
|
thread_id: {
|
|
1544
1731
|
type: "string",
|
|
1732
|
+
minLength: 1,
|
|
1733
|
+
maxLength: MAX_THREAD_ID_CHARS,
|
|
1734
|
+
pattern: "^[A-Za-z0-9_-]+$",
|
|
1545
1735
|
description: "Continue an existing conversation thread (omit to start a new one)"
|
|
1546
1736
|
},
|
|
1547
1737
|
max_depth: {
|
|
1548
|
-
type: "
|
|
1738
|
+
type: "integer",
|
|
1739
|
+
minimum: 1,
|
|
1740
|
+
maximum: MAX_A2A_DEPTH,
|
|
1549
1741
|
description: "Maximum number of back-and-forth exchanges (default: 10)"
|
|
1550
1742
|
}
|
|
1551
1743
|
},
|
|
@@ -1556,7 +1748,7 @@ function buildA2ATools(getChatClient, log, present) {
|
|
|
1556
1748
|
const message = params.message;
|
|
1557
1749
|
const threadId = params.thread_id;
|
|
1558
1750
|
const maxDepth = params.max_depth ?? 10;
|
|
1559
|
-
if (
|
|
1751
|
+
if (typeof agentId !== "string" || agentId.length < 1 || agentId.length > MAX_AGENT_ID_CHARS || typeof message !== "string" || message.length < 1 || message.length > MAX_A2A_MESSAGE_CHARS || threadId !== void 0 && (typeof threadId !== "string" || threadId.length < 1 || threadId.length > MAX_THREAD_ID_CHARS || !/^[A-Za-z0-9_-]+$/.test(threadId)) || !Number.isInteger(maxDepth) || maxDepth < 1 || maxDepth > MAX_A2A_DEPTH) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)("Invalid message_agent parameters");
|
|
1560
1752
|
log.info(`message_agent tool: sending to ${agentId}`);
|
|
1561
1753
|
return await requireClient().sendRequest("a2a.send", {
|
|
1562
1754
|
targetAgentId: agentId,
|
|
@@ -1573,6 +1765,7 @@ function buildA2ATools(getChatClient, log, present) {
|
|
|
1573
1765
|
type: "object",
|
|
1574
1766
|
properties: { summary: {
|
|
1575
1767
|
type: "string",
|
|
1768
|
+
maxLength: 1e3,
|
|
1576
1769
|
description: "Brief summary of what was discussed or resolved"
|
|
1577
1770
|
} },
|
|
1578
1771
|
required: []
|
|
@@ -1596,14 +1789,20 @@ function buildA2ATools(getChatClient, log, present) {
|
|
|
1596
1789
|
properties: {
|
|
1597
1790
|
to: {
|
|
1598
1791
|
type: "string",
|
|
1792
|
+
minLength: 6,
|
|
1793
|
+
maxLength: MAX_COMPONENT_TARGET_CHARS + 5,
|
|
1794
|
+
pattern: "^(conv|user):.+$",
|
|
1599
1795
|
description: "Target conversation: \"conv:{conversationId}\" or \"user:{userId}\"."
|
|
1600
1796
|
},
|
|
1601
1797
|
text: {
|
|
1602
1798
|
type: "string",
|
|
1799
|
+
maxLength: MAX_COMPONENT_TEXT_CHARS,
|
|
1603
1800
|
description: "Optional message text rendered above the components. Keep the key info here too — it is the fallback on clients that cannot render components."
|
|
1604
1801
|
},
|
|
1605
1802
|
components: {
|
|
1606
1803
|
type: "array",
|
|
1804
|
+
minItems: 1,
|
|
1805
|
+
maxItems: MAX_COMPONENTS,
|
|
1607
1806
|
description: "Interactive components to attach (at least one, at most 10).",
|
|
1608
1807
|
items: {
|
|
1609
1808
|
type: "object",
|
|
@@ -1622,14 +1821,18 @@ function buildA2ATools(getChatClient, log, present) {
|
|
|
1622
1821
|
},
|
|
1623
1822
|
id: {
|
|
1624
1823
|
type: "string",
|
|
1824
|
+
minLength: 1,
|
|
1825
|
+
maxLength: MAX_COMPONENT_ID_CHARS,
|
|
1625
1826
|
description: "Stable id (optional — auto-generated when omitted)."
|
|
1626
1827
|
},
|
|
1627
1828
|
label: {
|
|
1628
1829
|
type: "string",
|
|
1830
|
+
maxLength: MAX_COMPONENT_LABEL_CHARS,
|
|
1629
1831
|
description: "Button/control text. Required for link_button, quick_reply, copy_button; optional heading for select / multi_select."
|
|
1630
1832
|
},
|
|
1631
1833
|
url: {
|
|
1632
1834
|
type: "string",
|
|
1835
|
+
maxLength: MAX_COMPONENT_URL_CHARS,
|
|
1633
1836
|
description: "link_button only: https URL on an Alfe-owned host to open when clicked."
|
|
1634
1837
|
},
|
|
1635
1838
|
target: {
|
|
@@ -1643,24 +1846,32 @@ function buildA2ATools(getChatClient, log, present) {
|
|
|
1643
1846
|
},
|
|
1644
1847
|
value: {
|
|
1645
1848
|
type: "string",
|
|
1849
|
+
maxLength: MAX_COMPONENT_VALUE_CHARS,
|
|
1646
1850
|
description: "quick_reply: the text submitted as the user's next message when tapped. copy_button: the value copied to the clipboard."
|
|
1647
1851
|
},
|
|
1648
1852
|
placeholder: {
|
|
1649
1853
|
type: "string",
|
|
1854
|
+
maxLength: MAX_COMPONENT_LABEL_CHARS,
|
|
1650
1855
|
description: "select only: the empty-state prompt shown before a choice is made."
|
|
1651
1856
|
},
|
|
1652
1857
|
options: {
|
|
1653
1858
|
type: "array",
|
|
1859
|
+
minItems: 1,
|
|
1860
|
+
maxItems: MAX_COMPONENT_OPTIONS,
|
|
1654
1861
|
description: "select / multi_select only: the choices (max 25). Each is { label, value }; `value` is what gets submitted (multi_select joins checked values with \", \").",
|
|
1655
1862
|
items: {
|
|
1656
1863
|
type: "object",
|
|
1657
1864
|
properties: {
|
|
1658
1865
|
label: {
|
|
1659
1866
|
type: "string",
|
|
1867
|
+
minLength: 1,
|
|
1868
|
+
maxLength: MAX_COMPONENT_LABEL_CHARS,
|
|
1660
1869
|
description: "Option text shown to the user."
|
|
1661
1870
|
},
|
|
1662
1871
|
value: {
|
|
1663
1872
|
type: "string",
|
|
1873
|
+
minLength: 1,
|
|
1874
|
+
maxLength: MAX_COMPONENT_VALUE_CHARS,
|
|
1664
1875
|
description: "Value submitted when chosen."
|
|
1665
1876
|
}
|
|
1666
1877
|
},
|
|
@@ -1669,22 +1880,27 @@ function buildA2ATools(getChatClient, log, present) {
|
|
|
1669
1880
|
},
|
|
1670
1881
|
submitLabel: {
|
|
1671
1882
|
type: "string",
|
|
1883
|
+
maxLength: MAX_COMPONENT_LABEL_CHARS,
|
|
1672
1884
|
description: "multi_select only: submit button caption (default \"Submit\")."
|
|
1673
1885
|
},
|
|
1674
1886
|
confirmLabel: {
|
|
1675
1887
|
type: "string",
|
|
1888
|
+
maxLength: MAX_COMPONENT_LABEL_CHARS,
|
|
1676
1889
|
description: "confirm only: primary button text (e.g. \"Approve\")."
|
|
1677
1890
|
},
|
|
1678
1891
|
confirmValue: {
|
|
1679
1892
|
type: "string",
|
|
1893
|
+
maxLength: MAX_COMPONENT_VALUE_CHARS,
|
|
1680
1894
|
description: "confirm only: value submitted when the primary button is tapped."
|
|
1681
1895
|
},
|
|
1682
1896
|
cancelLabel: {
|
|
1683
1897
|
type: "string",
|
|
1898
|
+
maxLength: MAX_COMPONENT_LABEL_CHARS,
|
|
1684
1899
|
description: "confirm only: secondary button text (omit to show only the primary)."
|
|
1685
1900
|
},
|
|
1686
1901
|
cancelValue: {
|
|
1687
1902
|
type: "string",
|
|
1903
|
+
maxLength: MAX_COMPONENT_VALUE_CHARS,
|
|
1688
1904
|
description: "confirm only: value submitted when the secondary button is tapped."
|
|
1689
1905
|
},
|
|
1690
1906
|
style: {
|
|
@@ -1705,14 +1921,20 @@ function buildA2ATools(getChatClient, log, present) {
|
|
|
1705
1921
|
},
|
|
1706
1922
|
handler: async (params) => {
|
|
1707
1923
|
const to = params.to;
|
|
1708
|
-
if (!to || !to.startsWith("conv:") && !to.startsWith("user:")) throw
|
|
1924
|
+
if (!to || !to.startsWith("conv:") && !to.startsWith("user:") || to.length > MAX_COMPONENT_TARGET_CHARS + 5 || to.slice(to.indexOf(":") + 1).length < 1) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)("`to` must be \"conv:{conversationId}\" or \"user:{userId}\"");
|
|
1709
1925
|
const text = params.text;
|
|
1926
|
+
if (text !== void 0 && (typeof text !== "string" || text.length > MAX_COMPONENT_TEXT_CHARS)) throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)("`text` exceeds the 32000-character limit");
|
|
1710
1927
|
log.info("chat_present_components tool called");
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1928
|
+
try {
|
|
1929
|
+
return await present({
|
|
1930
|
+
to,
|
|
1931
|
+
text,
|
|
1932
|
+
components: params.components
|
|
1933
|
+
});
|
|
1934
|
+
} catch (error) {
|
|
1935
|
+
if (error instanceof Error && error.message === "At least one valid component is required") throw (0, _alfe_ai_openclaw_plugin_kit.publicToolError)(error.message);
|
|
1936
|
+
throw error;
|
|
1937
|
+
}
|
|
1716
1938
|
}
|
|
1717
1939
|
}));
|
|
1718
1940
|
return tools;
|
|
@@ -1745,14 +1967,27 @@ const DEFAULT_EXACT_HOSTS = [
|
|
|
1745
1967
|
"mmg.whatsapp.net"
|
|
1746
1968
|
];
|
|
1747
1969
|
const DEFAULT_SUFFIX_HOSTS = [
|
|
1748
|
-
".s3.amazonaws.com",
|
|
1749
|
-
".amazonaws.com",
|
|
1750
1970
|
".twiliocdn.com",
|
|
1751
1971
|
".cdn.discordapp.com",
|
|
1752
1972
|
".discordapp.net",
|
|
1753
1973
|
".telegram.org",
|
|
1754
1974
|
".alfe.ai"
|
|
1755
1975
|
];
|
|
1976
|
+
const AWS_REGION = /^(?:[a-z]{2}(?:-gov)?|us-iso[a-z]?|eusc(?:-[a-z]{2})?)-[a-z0-9-]+-\d$/;
|
|
1977
|
+
/** Keep byte-for-byte policy parity with services/chat/src/lib/media-url. */
|
|
1978
|
+
function isAmazonS3Host(host) {
|
|
1979
|
+
if (host === "s3.amazonaws.com" || host.endsWith(".s3.amazonaws.com")) return true;
|
|
1980
|
+
if (!host.endsWith(".amazonaws.com")) return false;
|
|
1981
|
+
const labels = host.slice(0, -14).split(".");
|
|
1982
|
+
return labels.some((service, index) => {
|
|
1983
|
+
const tail = labels.slice(index + 1);
|
|
1984
|
+
if (/^s3-(?:[a-z]{2}(?:-gov)?|us-iso[a-z]?|eusc(?:-[a-z]{2})?)-[a-z0-9-]+-\d$/.test(service)) return tail.length === 0;
|
|
1985
|
+
if (service === "s3-accelerate") return tail.length === 0 || tail.length === 1 && tail[0] === "dualstack";
|
|
1986
|
+
if (service !== "s3" && service !== "s3-fips" && service !== "s3-accesspoint" && service !== "s3-object-lambda" && service !== "s3-outposts") return false;
|
|
1987
|
+
if (tail.length === 1) return AWS_REGION.test(tail[0] ?? "");
|
|
1988
|
+
return tail.length === 2 && tail[0] === "dualstack" && AWS_REGION.test(tail[1] ?? "");
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1756
1991
|
function parseExtraHosts(raw) {
|
|
1757
1992
|
const exact = /* @__PURE__ */ new Set();
|
|
1758
1993
|
const suffix = [];
|
|
@@ -1811,7 +2046,9 @@ function validateAttachmentUrl(input, opts = {}) {
|
|
|
1811
2046
|
reason: "blocked_host"
|
|
1812
2047
|
};
|
|
1813
2048
|
const extra = parseExtraHosts(opts.extraHosts ?? process.env.ALFE_ATTACHMENT_ALLOWED_HOSTS);
|
|
1814
|
-
|
|
2049
|
+
const exact = new Set([...DEFAULT_EXACT_HOSTS.map((h) => h.toLowerCase()), ...extra.exact]);
|
|
2050
|
+
if (isAmazonS3Host(host)) return { ok: true };
|
|
2051
|
+
if (exact.has(host)) return { ok: true };
|
|
1815
2052
|
if ([...DEFAULT_SUFFIX_HOSTS.map((h) => h.toLowerCase()), ...extra.suffix].some((rule) => host === rule.slice(1) || host.endsWith(rule))) return { ok: true };
|
|
1816
2053
|
return {
|
|
1817
2054
|
ok: false,
|
|
@@ -1819,6 +2056,40 @@ function validateAttachmentUrl(input, opts = {}) {
|
|
|
1819
2056
|
};
|
|
1820
2057
|
}
|
|
1821
2058
|
//#endregion
|
|
2059
|
+
//#region src/local-ai-proxy-identity.ts
|
|
2060
|
+
const LOCAL_AI_PROXY_IDENTITY_URL = "http://127.0.0.1:18193/__alfe/set-identity";
|
|
2061
|
+
const LOCAL_AI_PROXY_CONTROL_TIMEOUT_MS = 1e3;
|
|
2062
|
+
const MAX_IDENTITY_ID_CHARS = 256;
|
|
2063
|
+
/**
|
|
2064
|
+
* Best-effort loopback identity handshake for the local AI proxy.
|
|
2065
|
+
*
|
|
2066
|
+
* A turn awaits this before model dispatch so attribution cannot lose a race
|
|
2067
|
+
* with the first LLM request. Failure never rejects the chat turn.
|
|
2068
|
+
*/
|
|
2069
|
+
async function syncLocalAiProxyIdentity(identityId, fetchFn = fetch) {
|
|
2070
|
+
if (identityId !== void 0 && !isValidIdentityId(identityId)) return false;
|
|
2071
|
+
try {
|
|
2072
|
+
const response = await fetchFn(LOCAL_AI_PROXY_IDENTITY_URL, {
|
|
2073
|
+
method: "POST",
|
|
2074
|
+
headers: { "Content-Type": "application/json" },
|
|
2075
|
+
body: JSON.stringify({ identityId: identityId ?? null }),
|
|
2076
|
+
signal: AbortSignal.timeout(LOCAL_AI_PROXY_CONTROL_TIMEOUT_MS)
|
|
2077
|
+
});
|
|
2078
|
+
try {
|
|
2079
|
+
await response.body?.cancel();
|
|
2080
|
+
} catch {}
|
|
2081
|
+
return response.ok;
|
|
2082
|
+
} catch {
|
|
2083
|
+
return false;
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
function isValidIdentityId(value) {
|
|
2087
|
+
return value.length > 0 && value.length <= MAX_IDENTITY_ID_CHARS && !Array.from(value).some((character) => {
|
|
2088
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
2089
|
+
return codePoint < 32 || codePoint === 127;
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
//#endregion
|
|
1822
2093
|
//#region src/plugin.ts
|
|
1823
2094
|
/**
|
|
1824
2095
|
* @alfe.ai/openclaw-chat — OpenClaw chat channel plugin.
|
|
@@ -2310,7 +2581,7 @@ function replayAttachments(atts) {
|
|
|
2310
2581
|
...typeof a.size === "number" ? { size: a.size } : {}
|
|
2311
2582
|
}));
|
|
2312
2583
|
}
|
|
2313
|
-
const
|
|
2584
|
+
const MAX_INBOUND_ATTACHMENT_SIZE = 50 * 1024 * 1024;
|
|
2314
2585
|
const DOWNLOAD_TIMEOUT_MS = 3e4;
|
|
2315
2586
|
const MAX_REDIRECTS = 5;
|
|
2316
2587
|
/**
|
|
@@ -2343,13 +2614,62 @@ async function fetchAttachmentWithValidation(url, signal) {
|
|
|
2343
2614
|
}
|
|
2344
2615
|
throw new Error("too_many_redirects");
|
|
2345
2616
|
}
|
|
2617
|
+
/**
|
|
2618
|
+
* Read a response body without ever retaining more than the attachment cap.
|
|
2619
|
+
* `arrayBuffer()` cannot enforce a cap until after the entire response has
|
|
2620
|
+
* already been allocated, so both declared and streamed sizes are checked.
|
|
2621
|
+
*/
|
|
2622
|
+
async function readAttachmentBodyBounded(response, maxBytes = MAX_INBOUND_ATTACHMENT_SIZE) {
|
|
2623
|
+
const declaredRaw = response.headers.get("content-length");
|
|
2624
|
+
if (declaredRaw !== null) {
|
|
2625
|
+
const declared = Number(declaredRaw);
|
|
2626
|
+
if (Number.isFinite(declared) && declared > maxBytes) throw new Error("attachment_too_large");
|
|
2627
|
+
}
|
|
2628
|
+
if (!response.body) return new Uint8Array();
|
|
2629
|
+
const reader = response.body.getReader();
|
|
2630
|
+
const chunks = [];
|
|
2631
|
+
let total = 0;
|
|
2632
|
+
try {
|
|
2633
|
+
for (;;) {
|
|
2634
|
+
const { done, value } = await reader.read();
|
|
2635
|
+
if (done) break;
|
|
2636
|
+
total += value.byteLength;
|
|
2637
|
+
if (total > maxBytes) {
|
|
2638
|
+
await reader.cancel("attachment_too_large").catch(() => void 0);
|
|
2639
|
+
throw new Error("attachment_too_large");
|
|
2640
|
+
}
|
|
2641
|
+
chunks.push(value);
|
|
2642
|
+
}
|
|
2643
|
+
} finally {
|
|
2644
|
+
reader.releaseLock();
|
|
2645
|
+
}
|
|
2646
|
+
const body = new Uint8Array(total);
|
|
2647
|
+
let offset = 0;
|
|
2648
|
+
for (const chunk of chunks) {
|
|
2649
|
+
body.set(chunk, offset);
|
|
2650
|
+
offset += chunk.byteLength;
|
|
2651
|
+
}
|
|
2652
|
+
return body;
|
|
2653
|
+
}
|
|
2654
|
+
/** Build one safe, collision-resistant filename inside the attachment dir. */
|
|
2655
|
+
function buildAttachmentLocalFilename(att) {
|
|
2656
|
+
const safeId = att.id.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 256) || "attachment";
|
|
2657
|
+
const safeName = Array.from(att.filename ?? "file", (character) => {
|
|
2658
|
+
const code = character.codePointAt(0) ?? 0;
|
|
2659
|
+
return code <= 31 || code === 127 ? "_" : character;
|
|
2660
|
+
}).join("").replace(/[\\/]/g, "_").replace(/\.\./g, "_").slice(0, 512) || "file";
|
|
2661
|
+
return `${safeId}_${(0, node_crypto.randomUUID)()}_${safeName}`;
|
|
2662
|
+
}
|
|
2346
2663
|
async function downloadAttachments(attachments, log) {
|
|
2347
2664
|
const attachDir = (0, node_path.join)((0, node_os.homedir)(), ".alfe", "attachments");
|
|
2348
|
-
await (0, node_fs_promises.mkdir)(attachDir, {
|
|
2665
|
+
await (0, node_fs_promises.mkdir)(attachDir, {
|
|
2666
|
+
recursive: true,
|
|
2667
|
+
mode: 448
|
|
2668
|
+
});
|
|
2349
2669
|
const results = [];
|
|
2350
|
-
for (const att of attachments) {
|
|
2351
|
-
const filename = (att.filename ??
|
|
2352
|
-
const localPath = (0, node_path.join)(attachDir,
|
|
2670
|
+
for (const att of attachments.slice(0, 10)) {
|
|
2671
|
+
const filename = (att.filename ?? "file").slice(0, 512) || "file";
|
|
2672
|
+
const localPath = (0, node_path.join)(attachDir, buildAttachmentLocalFilename(att));
|
|
2353
2673
|
const controller = new AbortController();
|
|
2354
2674
|
const timeout = setTimeout(() => {
|
|
2355
2675
|
controller.abort();
|
|
@@ -2360,12 +2680,11 @@ async function downloadAttachments(attachments, log) {
|
|
|
2360
2680
|
log.warn(`Failed to download attachment ${att.id}: ${String(res.status)}`);
|
|
2361
2681
|
continue;
|
|
2362
2682
|
}
|
|
2363
|
-
const buffer =
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
}
|
|
2368
|
-
await (0, node_fs_promises.writeFile)(localPath, buffer);
|
|
2683
|
+
const buffer = await readAttachmentBodyBounded(res);
|
|
2684
|
+
await (0, node_fs_promises.writeFile)(localPath, buffer, {
|
|
2685
|
+
flag: "wx",
|
|
2686
|
+
mode: 384
|
|
2687
|
+
});
|
|
2369
2688
|
results.push({
|
|
2370
2689
|
localPath,
|
|
2371
2690
|
filename,
|
|
@@ -2373,6 +2692,7 @@ async function downloadAttachments(attachments, log) {
|
|
|
2373
2692
|
});
|
|
2374
2693
|
log.info(`Downloaded attachment: ${localPath} (${String(buffer.length)} bytes)`);
|
|
2375
2694
|
} catch (err) {
|
|
2695
|
+
if ((err instanceof Error && "code" in err && typeof err.code === "string" ? err.code : void 0) !== "EEXIST") await (0, node_fs_promises.unlink)(localPath).catch(() => void 0);
|
|
2376
2696
|
log.error(`Failed to download attachment ${att.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2377
2697
|
} finally {
|
|
2378
2698
|
clearTimeout(timeout);
|
|
@@ -2512,6 +2832,20 @@ const VOICE_REPLY_SYSTEM_PROMPT = [
|
|
|
2512
2832
|
*/
|
|
2513
2833
|
const CROSS_AGENT_TOOLS_SYSTEM_PROMPT = ["Cross-agent messaging: to contact another Alfe agent (IDs like agt_…), use list_agents to discover and message_agent to send.", "The runtime's sessions_send / sessions_spawn tools do NOT know Alfe agent IDs and will fail with \"agent not found\" — never use them to reach another agent."].join("\n");
|
|
2514
2834
|
/**
|
|
2835
|
+
* Rules for an inbound agent-to-agent turn. This must ride the runtime-rendered
|
|
2836
|
+
* `GroupSystemPrompt` field; arbitrary extra-context keys are not shown to the
|
|
2837
|
+
* model. Keep the instruction structural: `end_conversation` is the plugin's
|
|
2838
|
+
* authoritative turn-local completion signal. The relay still accepts the
|
|
2839
|
+
* legacy trailing `[RESOLVED]` sentinel for older plugin versions.
|
|
2840
|
+
*/
|
|
2841
|
+
const A2A_REPLY_SYSTEM_PROMPT = [
|
|
2842
|
+
"This is an agent-to-agent conversation with another Alfe agent.",
|
|
2843
|
+
"Rules:",
|
|
2844
|
+
"- Only respond if you have new information, a question, or an action to coordinate.",
|
|
2845
|
+
"- When the discussion is complete, call the end_conversation tool.",
|
|
2846
|
+
"- Do not respond just to acknowledge receipt; that creates unproductive reply loops."
|
|
2847
|
+
].join("\n");
|
|
2848
|
+
/**
|
|
2515
2849
|
* Per-turn envelope context derived from the RPC `origin` flag, returned as a
|
|
2516
2850
|
* `GroupSystemPrompt` block — the PROVEN runtime-rendered seam for per-turn
|
|
2517
2851
|
* system-prompt guidance: dispatchInbound spreads `extraContext` into
|
|
@@ -2539,6 +2873,11 @@ function buildOriginEnvelopeContext(origin) {
|
|
|
2539
2873
|
if (origin === "voice") return { GroupSystemPrompt: `${VOICE_REPLY_SYSTEM_PROMPT}\n\n${CROSS_AGENT_TOOLS_SYSTEM_PROMPT}` };
|
|
2540
2874
|
return { GroupSystemPrompt: CROSS_AGENT_TOOLS_SYSTEM_PROMPT };
|
|
2541
2875
|
}
|
|
2876
|
+
/** Build the single live per-turn prompt block without competing object keys. */
|
|
2877
|
+
function buildTurnEnvelopeContext(origin, isA2A) {
|
|
2878
|
+
const originPrompt = String(buildOriginEnvelopeContext(origin).GroupSystemPrompt);
|
|
2879
|
+
return { GroupSystemPrompt: isA2A ? `${originPrompt}\n\n${A2A_REPLY_SYSTEM_PROMPT}` : originPrompt };
|
|
2880
|
+
}
|
|
2542
2881
|
async function handleAgentRequest(request, log) {
|
|
2543
2882
|
const runtime = pluginRuntime;
|
|
2544
2883
|
if (!runtime) {
|
|
@@ -2549,7 +2888,7 @@ async function handleAgentRequest(request, log) {
|
|
|
2549
2888
|
chatClient?.sendResponse(request.id, false, { message: "OpenClaw SDK not available — cannot dispatch" });
|
|
2550
2889
|
return;
|
|
2551
2890
|
}
|
|
2552
|
-
const { message, sessionKey: legacySessionKey, userId, conversationId, conversationType, tenantId, clientType, origin, displayName, identityId,
|
|
2891
|
+
const { message, sessionKey: legacySessionKey, userId, conversationId, conversationType, tenantId, clientType, origin, displayName, identityId, identityProvider, attachments: rawAttachments, a2a, chatMessageId } = request.params;
|
|
2553
2892
|
const isA2A = !!a2a;
|
|
2554
2893
|
if (!message && !rawAttachments?.length) {
|
|
2555
2894
|
chatClient?.sendResponse(request.id, false, { message: "Missing message" });
|
|
@@ -2707,16 +3046,16 @@ async function handleAgentRequest(request, log) {
|
|
|
2707
3046
|
return;
|
|
2708
3047
|
}
|
|
2709
3048
|
});
|
|
3049
|
+
let localProxyIdentityActive = false;
|
|
2710
3050
|
try {
|
|
2711
3051
|
const downloadedFiles = rawAttachments?.length ? await downloadAttachments(rawAttachments, log) : [];
|
|
2712
3052
|
const bodyForAgent = downloadedFiles.length ? `${message || ""}\n\n[Attached files:\n${downloadedFiles.map((f) => `- ${f.filename}: ${f.localPath}`).join("\n")}]` : void 0;
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
body: JSON.stringify({ identityId })
|
|
2717
|
-
}).catch(() => {});
|
|
3053
|
+
const identitySynced = await syncLocalAiProxyIdentity(identityId);
|
|
3054
|
+
localProxyIdentityActive = identitySynced && identityId !== void 0;
|
|
3055
|
+
if (!identitySynced) log.debug("Local AI proxy identity attribution unavailable");
|
|
2718
3056
|
const channelMode = isA2A ? "a2a" : extractChannelMode(conversationId ?? "", clientType ?? "chat");
|
|
2719
|
-
const
|
|
3057
|
+
const inboundHookRouting = buildInboundHookRouting(identityProvider, channelMode, conversationId);
|
|
3058
|
+
const channelLabel = channelMode === "mobile" ? "Mobile" : channelMode === "sms" ? "SMS" : channelMode === "whatsapp" ? "WhatsApp" : channelMode === "a2a" ? "Agent" : "Alfe";
|
|
2720
3059
|
const shortConvId = conversationId?.slice(-8) ?? "";
|
|
2721
3060
|
const userLabel = displayName ?? userId ?? senderId;
|
|
2722
3061
|
const conversationLabel = conversationType === "group" ? shortConvId ? `[${channelLabel}] Group (${shortConvId})` : `[${channelLabel}] Group` : shortConvId ? `[${channelLabel}] ${userLabel} (${shortConvId})` : `[${channelLabel}] ${userLabel}`;
|
|
@@ -2762,6 +3101,7 @@ async function handleAgentRequest(request, log) {
|
|
|
2762
3101
|
bodyForAgent,
|
|
2763
3102
|
messageId: request.id,
|
|
2764
3103
|
timestamp: Date.now(),
|
|
3104
|
+
...inboundHookRouting,
|
|
2765
3105
|
extraContext: {
|
|
2766
3106
|
...tenantId ? { TenantId: tenantId } : {},
|
|
2767
3107
|
...clientType ? { ClientType: clientType } : {},
|
|
@@ -2769,22 +3109,13 @@ async function handleAgentRequest(request, log) {
|
|
|
2769
3109
|
...displayName ? { SenderName: displayName } : {},
|
|
2770
3110
|
...identityId ? { IdentityId: identityId } : {},
|
|
2771
3111
|
...userId ? { UserId: userId } : {},
|
|
2772
|
-
...senderPermissions?.length ? { SenderPermissions: senderPermissions } : {},
|
|
2773
3112
|
ChannelMode: channelMode,
|
|
2774
|
-
...
|
|
3113
|
+
...buildTurnEnvelopeContext(origin, isA2A),
|
|
2775
3114
|
...isA2A ? {
|
|
2776
3115
|
CallerType: "agent",
|
|
2777
3116
|
CallerAgentId: a2a.sourceAgentId,
|
|
2778
3117
|
CallerAgentName: a2a.sourceAgentName,
|
|
2779
|
-
InteractionDepth: String(a2a.depth)
|
|
2780
|
-
A2ASystemPrompt: [
|
|
2781
|
-
`This is an agent-to-agent conversation with ${a2a.sourceAgentName}.`,
|
|
2782
|
-
"Rules:",
|
|
2783
|
-
"- Only respond if you have new information, a question, or an action to coordinate.",
|
|
2784
|
-
"- When the discussion is complete, call the end_conversation() tool AND end your final message with the literal token [RESOLVED] (uppercase, in square brackets, as the last thing in the message).",
|
|
2785
|
-
"- Do NOT write the word \"resolved\" in prose unless you mean to end — only the exact token [RESOLVED] ends the thread.",
|
|
2786
|
-
"- Do NOT respond just to acknowledge — that creates infinite loops."
|
|
2787
|
-
].join("\n")
|
|
3118
|
+
InteractionDepth: String(a2a.depth)
|
|
2788
3119
|
} : {}
|
|
2789
3120
|
},
|
|
2790
3121
|
deliver: async (payload) => {
|
|
@@ -2841,6 +3172,9 @@ async function handleAgentRequest(request, log) {
|
|
|
2841
3172
|
activeRun = null;
|
|
2842
3173
|
unsubscribe();
|
|
2843
3174
|
clearAllToolUpdates();
|
|
3175
|
+
if (localProxyIdentityActive) {
|
|
3176
|
+
if (!await syncLocalAiProxyIdentity(void 0)) log.debug("Local AI proxy identity attribution clear failed");
|
|
3177
|
+
}
|
|
2844
3178
|
try {
|
|
2845
3179
|
await flushTurnActivity();
|
|
2846
3180
|
} catch (err) {
|
|
@@ -2984,7 +3318,7 @@ const plugin = {
|
|
|
2984
3318
|
chatWsUrl: pluginConfig.chatWsUrl
|
|
2985
3319
|
});
|
|
2986
3320
|
if (chatWsUrl && apiKey) {
|
|
2987
|
-
log.info(
|
|
3321
|
+
log.info("Connecting to chat service relay");
|
|
2988
3322
|
chatClient = new _alfe_ai_chat.ChatServiceClient({
|
|
2989
3323
|
wsUrl: chatWsUrl,
|
|
2990
3324
|
apiKey,
|
|
@@ -3142,12 +3476,24 @@ const plugin = {
|
|
|
3142
3476
|
}
|
|
3143
3477
|
};
|
|
3144
3478
|
//#endregion
|
|
3479
|
+
Object.defineProperty(exports, "A2A_REPLY_SYSTEM_PROMPT", {
|
|
3480
|
+
enumerable: true,
|
|
3481
|
+
get: function() {
|
|
3482
|
+
return A2A_REPLY_SYSTEM_PROMPT;
|
|
3483
|
+
}
|
|
3484
|
+
});
|
|
3145
3485
|
Object.defineProperty(exports, "CROSS_AGENT_TOOLS_SYSTEM_PROMPT", {
|
|
3146
3486
|
enumerable: true,
|
|
3147
3487
|
get: function() {
|
|
3148
3488
|
return CROSS_AGENT_TOOLS_SYSTEM_PROMPT;
|
|
3149
3489
|
}
|
|
3150
3490
|
});
|
|
3491
|
+
Object.defineProperty(exports, "MAX_INBOUND_ATTACHMENT_SIZE", {
|
|
3492
|
+
enumerable: true,
|
|
3493
|
+
get: function() {
|
|
3494
|
+
return MAX_INBOUND_ATTACHMENT_SIZE;
|
|
3495
|
+
}
|
|
3496
|
+
});
|
|
3151
3497
|
Object.defineProperty(exports, "VOICE_REPLY_SYSTEM_PROMPT", {
|
|
3152
3498
|
enumerable: true,
|
|
3153
3499
|
get: function() {
|
|
@@ -3184,6 +3530,12 @@ Object.defineProperty(exports, "buildA2ACompletePayload", {
|
|
|
3184
3530
|
return buildA2ACompletePayload;
|
|
3185
3531
|
}
|
|
3186
3532
|
});
|
|
3533
|
+
Object.defineProperty(exports, "buildAttachmentLocalFilename", {
|
|
3534
|
+
enumerable: true,
|
|
3535
|
+
get: function() {
|
|
3536
|
+
return buildAttachmentLocalFilename;
|
|
3537
|
+
}
|
|
3538
|
+
});
|
|
3187
3539
|
Object.defineProperty(exports, "buildOriginEnvelopeContext", {
|
|
3188
3540
|
enumerable: true,
|
|
3189
3541
|
get: function() {
|
|
@@ -3196,6 +3548,12 @@ Object.defineProperty(exports, "buildToolActivity", {
|
|
|
3196
3548
|
return buildToolActivity;
|
|
3197
3549
|
}
|
|
3198
3550
|
});
|
|
3551
|
+
Object.defineProperty(exports, "buildTurnEnvelopeContext", {
|
|
3552
|
+
enumerable: true,
|
|
3553
|
+
get: function() {
|
|
3554
|
+
return buildTurnEnvelopeContext;
|
|
3555
|
+
}
|
|
3556
|
+
});
|
|
3199
3557
|
Object.defineProperty(exports, "computeOpenClawSdkAnchors", {
|
|
3200
3558
|
enumerable: true,
|
|
3201
3559
|
get: function() {
|
|
@@ -3226,6 +3584,12 @@ Object.defineProperty(exports, "plugin", {
|
|
|
3226
3584
|
return plugin;
|
|
3227
3585
|
}
|
|
3228
3586
|
});
|
|
3587
|
+
Object.defineProperty(exports, "readAttachmentBodyBounded", {
|
|
3588
|
+
enumerable: true,
|
|
3589
|
+
get: function() {
|
|
3590
|
+
return readAttachmentBodyBounded;
|
|
3591
|
+
}
|
|
3592
|
+
});
|
|
3229
3593
|
Object.defineProperty(exports, "resolveAbortTargetKeys", {
|
|
3230
3594
|
enumerable: true,
|
|
3231
3595
|
get: function() {
|