@elizaos/agent 2.0.0-alpha.437 → 2.0.0-alpha.438
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/apps/app-lifeops/src/lifeops/discord-browser-scraper.d.ts +1 -0
- package/apps/app-lifeops/src/lifeops/discord-browser-scraper.d.ts.map +1 -1
- package/apps/app-lifeops/src/lifeops/discord-browser-scraper.js +57 -21
- package/apps/app-lifeops/src/lifeops/imessage-bridge.d.ts +1 -0
- package/apps/app-lifeops/src/lifeops/imessage-bridge.d.ts.map +1 -1
- package/apps/app-lifeops/src/lifeops/imessage-bridge.js +41 -36
- package/apps/app-lifeops/src/lifeops/service-mixin-signal.js +7 -0
- package/apps/app-lifeops/src/lifeops/service-mixin-telegram.js +8 -1
- package/apps/app-lifeops/src/lifeops/signal-local-client.d.ts +4 -4
- package/apps/app-lifeops/src/lifeops/signal-local-client.d.ts.map +1 -1
- package/apps/app-lifeops/src/lifeops/signal-local-client.js +40 -15
- package/apps/app-lifeops/src/lifeops/telegram-auth.d.ts.map +1 -1
- package/apps/app-lifeops/src/lifeops/telegram-auth.js +21 -1
- package/apps/app-lifeops/src/lifeops/telegram-local-client.d.ts +3 -0
- package/apps/app-lifeops/src/lifeops/telegram-local-client.d.ts.map +1 -1
- package/apps/app-lifeops/src/lifeops/telegram-local-client.js +61 -18
- package/apps/app-lifeops/src/lifeops/whatsapp-client.d.ts +7 -0
- package/apps/app-lifeops/src/lifeops/whatsapp-client.d.ts.map +1 -1
- package/apps/app-lifeops/src/lifeops/whatsapp-client.js +47 -16
- package/package.json +4 -4
- package/packages/agent/src/actions/terminal.d.ts +3 -3
- package/packages/agent/src/actions/terminal.d.ts.map +1 -1
- package/packages/agent/src/actions/terminal.js +80 -157
- package/packages/shared/src/contracts/lifeops.d.ts +14 -0
- package/packages/shared/src/contracts/lifeops.d.ts.map +1 -1
- package/packages/typescript/src/features/advanced-capabilities/clipboard/actions/save-attachment-to-clipboard.d.ts +4 -0
- package/packages/typescript/src/features/advanced-capabilities/clipboard/actions/save-attachment-to-clipboard.d.ts.map +1 -0
- package/packages/typescript/src/features/advanced-capabilities/clipboard/actions/save-attachment-to-clipboard.js +127 -0
- package/packages/typescript/src/features/advanced-capabilities/clipboard/index.d.ts +2 -0
- package/packages/typescript/src/features/advanced-capabilities/clipboard/index.d.ts.map +1 -1
- package/packages/typescript/src/features/advanced-capabilities/clipboard/index.js +4 -0
- package/packages/typescript/src/runtime.d.ts.map +1 -1
- package/packages/typescript/src/runtime.js +10 -2
- package/packages/typescript/src/services/message.d.ts +7 -0
- package/packages/typescript/src/services/message.d.ts.map +1 -1
- package/packages/typescript/src/services/message.js +22 -18
|
@@ -198,6 +198,33 @@ function parseNumericId(value) {
|
|
|
198
198
|
function readOutboxMaxId(dialog) {
|
|
199
199
|
return parseNumericId(dialog?.dialog?.readOutboxMaxId);
|
|
200
200
|
}
|
|
201
|
+
function firstNonEmptyTelegramId(values) {
|
|
202
|
+
for (const value of values) {
|
|
203
|
+
const serialized = serializeTelegramId(value);
|
|
204
|
+
if (serialized.length > 0) {
|
|
205
|
+
return serialized;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
function messagePeerId(message) {
|
|
211
|
+
const peer = message.peerId;
|
|
212
|
+
return firstNonEmptyTelegramId([
|
|
213
|
+
peer?.userId,
|
|
214
|
+
peer?.chatId,
|
|
215
|
+
peer?.channelId,
|
|
216
|
+
]);
|
|
217
|
+
}
|
|
218
|
+
function messageSenderId(message) {
|
|
219
|
+
return firstNonEmptyTelegramId([message.fromId?.userId]);
|
|
220
|
+
}
|
|
221
|
+
function findDialogForMessage(message, dialogs) {
|
|
222
|
+
const peerId = messagePeerId(message);
|
|
223
|
+
if (!peerId) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
return (dialogs.find((dialog) => serializeTelegramId(dialog.id) === peerId) ?? null);
|
|
227
|
+
}
|
|
201
228
|
function isGlobalSearchScope(scope) {
|
|
202
229
|
const normalized = scope?.trim().toLowerCase();
|
|
203
230
|
return (!normalized ||
|
|
@@ -225,8 +252,12 @@ export async function sendTelegramAccountMessage(args) {
|
|
|
225
252
|
const dialogs = Array.from(await client.getDialogs({ limit: MAX_TARGET_LOOKUP_DIALOGS }));
|
|
226
253
|
const entity = await resolveTelegramTarget(client, args.target, dialogs);
|
|
227
254
|
const sent = await client.sendMessage(entity, { message: args.message });
|
|
255
|
+
const messageId = sent?.id !== undefined ? serializeTelegramId(sent.id) : "";
|
|
256
|
+
if (messageId.length === 0) {
|
|
257
|
+
throw new Error("Telegram send did not return a message id.");
|
|
258
|
+
}
|
|
228
259
|
return {
|
|
229
|
-
messageId
|
|
260
|
+
messageId,
|
|
230
261
|
};
|
|
231
262
|
});
|
|
232
263
|
}
|
|
@@ -249,22 +280,30 @@ export async function searchTelegramMessages(args) {
|
|
|
249
280
|
return messages
|
|
250
281
|
.filter((message) => Boolean(message))
|
|
251
282
|
.slice(0, limit)
|
|
252
|
-
.map((message) =>
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
283
|
+
.map((message) => {
|
|
284
|
+
const messageDialog = dialog ?? findDialogForMessage(message, dialogs);
|
|
285
|
+
const dialogId = messageDialog?.id !== undefined
|
|
286
|
+
? serializeTelegramId(messageDialog.id) || null
|
|
287
|
+
: null;
|
|
288
|
+
const username = typeof messageDialog?.entity?.username === "string" &&
|
|
289
|
+
messageDialog.entity.username.trim().length > 0
|
|
290
|
+
? messageDialog.entity.username.trim()
|
|
291
|
+
: null;
|
|
292
|
+
return {
|
|
293
|
+
id: message.id !== undefined
|
|
294
|
+
? serializeTelegramId(message.id) || null
|
|
295
|
+
: null,
|
|
296
|
+
dialogId,
|
|
297
|
+
threadId: dialogId,
|
|
298
|
+
dialogTitle: messageDialog ? normalizeDialogTitle(messageDialog) : null,
|
|
299
|
+
username,
|
|
300
|
+
peerId: messagePeerId(message),
|
|
301
|
+
senderId: messageSenderId(message),
|
|
302
|
+
content: normalizeMessageContent(message),
|
|
303
|
+
timestamp: toIsoDate(message.date),
|
|
304
|
+
outgoing: message.out === true,
|
|
305
|
+
};
|
|
306
|
+
});
|
|
268
307
|
});
|
|
269
308
|
}
|
|
270
309
|
export async function getTelegramReadReceipts(args) {
|
|
@@ -359,7 +398,11 @@ export async function verifyTelegramLocalConnector(args) {
|
|
|
359
398
|
try {
|
|
360
399
|
const entity = await resolveTelegramTarget(client, target, dialogs);
|
|
361
400
|
const sent = await client.sendMessage(entity, { message });
|
|
362
|
-
|
|
401
|
+
const sentMessageId = sent?.id !== undefined ? serializeTelegramId(sent.id) : "";
|
|
402
|
+
if (sentMessageId.length === 0) {
|
|
403
|
+
throw new Error("Telegram send did not return a message id.");
|
|
404
|
+
}
|
|
405
|
+
messageId = sentMessageId;
|
|
363
406
|
}
|
|
364
407
|
catch (error) {
|
|
365
408
|
sendError = error instanceof Error ? error.message : String(error);
|
|
@@ -6,10 +6,17 @@ export interface WhatsAppCredentials {
|
|
|
6
6
|
export interface WhatsAppMessage {
|
|
7
7
|
id: string;
|
|
8
8
|
from: string;
|
|
9
|
+
channelId: string;
|
|
9
10
|
timestamp: string;
|
|
10
11
|
type: "text" | "image" | "audio" | "document" | "unknown";
|
|
11
12
|
text?: string;
|
|
12
13
|
mediaId?: string;
|
|
14
|
+
metadata?: {
|
|
15
|
+
displayPhoneNumber?: string;
|
|
16
|
+
phoneNumberId?: string;
|
|
17
|
+
contactName?: string;
|
|
18
|
+
waId?: string;
|
|
19
|
+
};
|
|
13
20
|
}
|
|
14
21
|
export interface WhatsAppSendRequest {
|
|
15
22
|
to: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whatsapp-client.d.ts","sourceRoot":"","sources":["../../../../../../../apps/app-lifeops/src/lifeops/whatsapp-client.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"whatsapp-client.d.ts","sourceRoot":"","sources":["../../../../../../../apps/app-lifeops/src/lifeops/whatsapp-client.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE;QACT,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,aAAc,SAAQ,KAAK;aAGpB,MAAM,EAAE,MAAM;aACd,IAAI,CAAC,EAAE,OAAO;gBAF9B,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,OAAO;CAKjC;AAOD,wBAAgB,8BAA8B,CAC5C,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,mBAAmB,GAAG,IAAI,CAY5B;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,mBAAmB,EAC1B,GAAG,EAAE,mBAAmB,GACvB,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAsD1C;AA4DD;;;;GAIG;AACH,wBAAgB,qCAAqC,CACnD,OAAO,EAAE,OAAO,GACf,eAAe,EAAE,CAMnB;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,eAAe,EAAE,CAI9D;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,eAAe,EAAE,CAE7D;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,OAAO,GACf,eAAe,EAAE,CAuGnB"}
|
|
@@ -49,7 +49,8 @@ export async function sendWhatsAppMessage(creds, req) {
|
|
|
49
49
|
});
|
|
50
50
|
const body = (await response.json().catch(() => ({})));
|
|
51
51
|
if (!response.ok) {
|
|
52
|
-
const errorMessage = body.error?.message ??
|
|
52
|
+
const errorMessage = body.error?.message ??
|
|
53
|
+
`WhatsApp request failed with HTTP ${response.status}`;
|
|
53
54
|
logger.warn({
|
|
54
55
|
boundary: "lifeops",
|
|
55
56
|
integration: "whatsapp",
|
|
@@ -65,11 +66,24 @@ export async function sendWhatsAppMessage(creds, req) {
|
|
|
65
66
|
return { ok: true, messageId };
|
|
66
67
|
}
|
|
67
68
|
function mapMessageType(raw) {
|
|
68
|
-
if (raw === "text" ||
|
|
69
|
+
if (raw === "text" ||
|
|
70
|
+
raw === "image" ||
|
|
71
|
+
raw === "audio" ||
|
|
72
|
+
raw === "document") {
|
|
69
73
|
return raw;
|
|
70
74
|
}
|
|
71
75
|
return "unknown";
|
|
72
76
|
}
|
|
77
|
+
function whatsappTimestampToIso(raw) {
|
|
78
|
+
const value = typeof raw === "string" && raw.trim().length > 0 ? Number(raw) : raw;
|
|
79
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return new Date(value * 1000).toISOString();
|
|
83
|
+
}
|
|
84
|
+
function optionalString(value) {
|
|
85
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
86
|
+
}
|
|
73
87
|
// ---------------------------------------------------------------------------
|
|
74
88
|
// Inbound message buffer
|
|
75
89
|
// ---------------------------------------------------------------------------
|
|
@@ -151,6 +165,25 @@ export function parseWhatsAppWebhookMessages(payload) {
|
|
|
151
165
|
const value = change.value;
|
|
152
166
|
if (!value || typeof value !== "object")
|
|
153
167
|
continue;
|
|
168
|
+
const webhookValue = value;
|
|
169
|
+
const rawContacts = webhookValue.contacts;
|
|
170
|
+
const contactByWaId = new Map();
|
|
171
|
+
if (Array.isArray(rawContacts)) {
|
|
172
|
+
for (const contact of rawContacts) {
|
|
173
|
+
if (!contact || typeof contact !== "object")
|
|
174
|
+
continue;
|
|
175
|
+
const c = contact;
|
|
176
|
+
const waId = optionalString(c.wa_id);
|
|
177
|
+
if (!waId)
|
|
178
|
+
continue;
|
|
179
|
+
contactByWaId.set(waId, {
|
|
180
|
+
name: optionalString(c.profile?.name),
|
|
181
|
+
waId,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const displayPhoneNumber = optionalString(webhookValue.metadata?.display_phone_number);
|
|
186
|
+
const phoneNumberId = optionalString(webhookValue.metadata?.phone_number_id);
|
|
154
187
|
const rawMessages = value.messages;
|
|
155
188
|
if (!Array.isArray(rawMessages))
|
|
156
189
|
continue;
|
|
@@ -160,20 +193,9 @@ export function parseWhatsAppWebhookMessages(payload) {
|
|
|
160
193
|
const m = msg;
|
|
161
194
|
if (typeof m.id !== "string" || typeof m.from !== "string")
|
|
162
195
|
continue;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const asNumber = Number(m.timestamp);
|
|
167
|
-
isoTimestamp = Number.isFinite(asNumber)
|
|
168
|
-
? new Date(asNumber * 1000).toISOString()
|
|
169
|
-
: new Date().toISOString();
|
|
170
|
-
}
|
|
171
|
-
else if (typeof m.timestamp === "number") {
|
|
172
|
-
isoTimestamp = new Date(m.timestamp * 1000).toISOString();
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
isoTimestamp = new Date().toISOString();
|
|
176
|
-
}
|
|
196
|
+
const isoTimestamp = whatsappTimestampToIso(m.timestamp);
|
|
197
|
+
if (!isoTimestamp)
|
|
198
|
+
continue;
|
|
177
199
|
const type = mapMessageType(m.type);
|
|
178
200
|
const text = type === "text" && typeof m.text?.body === "string"
|
|
179
201
|
? m.text.body
|
|
@@ -185,13 +207,22 @@ export function parseWhatsAppWebhookMessages(payload) {
|
|
|
185
207
|
: type === "document" && typeof m.document?.id === "string"
|
|
186
208
|
? m.document.id
|
|
187
209
|
: undefined;
|
|
210
|
+
const contact = contactByWaId.get(m.from);
|
|
211
|
+
const metadata = {
|
|
212
|
+
...(displayPhoneNumber ? { displayPhoneNumber } : {}),
|
|
213
|
+
...(phoneNumberId ? { phoneNumberId } : {}),
|
|
214
|
+
...(contact?.name ? { contactName: contact.name } : {}),
|
|
215
|
+
...(contact?.waId ? { waId: contact.waId } : {}),
|
|
216
|
+
};
|
|
188
217
|
messages.push({
|
|
189
218
|
id: m.id,
|
|
190
219
|
from: m.from,
|
|
220
|
+
channelId: m.from,
|
|
191
221
|
timestamp: isoTimestamp,
|
|
192
222
|
type,
|
|
193
223
|
...(text !== undefined ? { text } : {}),
|
|
194
224
|
...(mediaId !== undefined ? { mediaId } : {}),
|
|
225
|
+
...(Object.keys(metadata).length > 0 ? { metadata } : {}),
|
|
195
226
|
});
|
|
196
227
|
}
|
|
197
228
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elizaos/agent",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.438",
|
|
4
4
|
"description": "Standalone elizaOS-based agent and backend server package.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -467,7 +467,7 @@
|
|
|
467
467
|
"@elizaos/app-steward": "^0.0.0",
|
|
468
468
|
"@elizaos/app-task-coordinator": "^0.0.0",
|
|
469
469
|
"@elizaos/app-training": "^0.0.1",
|
|
470
|
-
"@elizaos/core": "^2.0.0-alpha.
|
|
470
|
+
"@elizaos/core": "^2.0.0-alpha.438",
|
|
471
471
|
"@elizaos/plugin-agent-orchestrator": "^0.6.2-alpha.0",
|
|
472
472
|
"@elizaos/plugin-browser-bridge": "^0.1.0",
|
|
473
473
|
"@elizaos/plugin-local-embedding": "^2.0.0-alpha.12",
|
|
@@ -476,8 +476,8 @@
|
|
|
476
476
|
"@elizaos/plugin-solana": "^2.0.0-alpha.6",
|
|
477
477
|
"@elizaos/plugin-sql": "^2.0.0-alpha.19",
|
|
478
478
|
"@elizaos/plugin-wechat": "^0.1.0",
|
|
479
|
-
"@elizaos/shared": "^2.0.0-alpha.
|
|
480
|
-
"@elizaos/skills": "^2.0.0-alpha.
|
|
479
|
+
"@elizaos/shared": "^2.0.0-alpha.438",
|
|
480
|
+
"@elizaos/skills": "^2.0.0-alpha.438",
|
|
481
481
|
"@hapi/boom": "^10.0.1",
|
|
482
482
|
"@noble/curves": "^2.0.1",
|
|
483
483
|
"@solana/web3.js": "^1.98.4",
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* SHELL_COMMAND action — runs a shell command on the server.
|
|
3
3
|
*
|
|
4
4
|
* When triggered the action:
|
|
5
|
-
* 1. Extracts the command from
|
|
5
|
+
* 1. Extracts the command from parameters or MCP-style JSON
|
|
6
6
|
* 2. POSTs to the local API server to execute it
|
|
7
7
|
* 3. The API broadcasts output via WebSocket for real-time display
|
|
8
|
-
* 4.
|
|
9
|
-
* 5.
|
|
8
|
+
* 4. Captures the output for the post-action LLM response
|
|
9
|
+
* 5. Stores the full output as a document attachment for follow-up actions
|
|
10
10
|
*
|
|
11
11
|
* @module actions/terminal
|
|
12
12
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../../../../src/actions/terminal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,MAAM,
|
|
1
|
+
{"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../../../../src/actions/terminal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,MAAM,EAOP,MAAM,eAAe,CAAC;AAyOvB,eAAO,MAAM,cAAc,EAAE,MA2H5B,CAAC"}
|
|
@@ -2,47 +2,33 @@
|
|
|
2
2
|
* SHELL_COMMAND action — runs a shell command on the server.
|
|
3
3
|
*
|
|
4
4
|
* When triggered the action:
|
|
5
|
-
* 1. Extracts the command from
|
|
5
|
+
* 1. Extracts the command from parameters or MCP-style JSON
|
|
6
6
|
* 2. POSTs to the local API server to execute it
|
|
7
7
|
* 3. The API broadcasts output via WebSocket for real-time display
|
|
8
|
-
* 4.
|
|
9
|
-
* 5.
|
|
8
|
+
* 4. Captures the output for the post-action LLM response
|
|
9
|
+
* 5. Stores the full output as a document attachment for follow-up actions
|
|
10
10
|
*
|
|
11
11
|
* @module actions/terminal
|
|
12
12
|
*/
|
|
13
|
-
|
|
14
|
-
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
15
|
-
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
16
|
-
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
return path;
|
|
20
|
-
};
|
|
21
|
-
import { logger } from "@elizaos/core";
|
|
13
|
+
import { ContentType, logger, stringToUuid } from "@elizaos/core";
|
|
22
14
|
import { hasOwnerAccess } from "../security/access.js";
|
|
23
15
|
/** API port for posting terminal requests. */
|
|
24
16
|
const API_PORT = process.env.API_PORT || process.env.SERVER_PORT || "2138";
|
|
17
|
+
const TERMINAL_ACTION_NAME = "SHELL_COMMAND";
|
|
25
18
|
const FAIL = { success: false, text: "" };
|
|
26
|
-
let cachedClipboardStoreFn;
|
|
27
|
-
function parseBooleanFlag(value) {
|
|
28
|
-
if (value === true) {
|
|
29
|
-
return true;
|
|
30
|
-
}
|
|
31
|
-
if (typeof value === "string") {
|
|
32
|
-
return /^(true|1|yes|y|on)$/i.test(value.trim());
|
|
33
|
-
}
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
19
|
function readStringValue(value) {
|
|
37
20
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
38
21
|
}
|
|
22
|
+
function isJsonRecord(value) {
|
|
23
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
24
|
+
}
|
|
39
25
|
function parseJsonArguments(value) {
|
|
40
26
|
if (typeof value !== "string" || !value.trim()) {
|
|
41
27
|
return undefined;
|
|
42
28
|
}
|
|
43
29
|
try {
|
|
44
30
|
const parsed = JSON.parse(value);
|
|
45
|
-
if (
|
|
31
|
+
if (isJsonRecord(parsed)) {
|
|
46
32
|
return parsed;
|
|
47
33
|
}
|
|
48
34
|
}
|
|
@@ -51,27 +37,6 @@ function parseJsonArguments(value) {
|
|
|
51
37
|
}
|
|
52
38
|
return undefined;
|
|
53
39
|
}
|
|
54
|
-
function resolveClipboardRequested(params, argumentParams, message) {
|
|
55
|
-
return [
|
|
56
|
-
params.addToClipboard,
|
|
57
|
-
params.persistToClipboard,
|
|
58
|
-
params.saveToClipboard,
|
|
59
|
-
argumentParams?.addToClipboard,
|
|
60
|
-
argumentParams?.persistToClipboard,
|
|
61
|
-
argumentParams?.saveToClipboard,
|
|
62
|
-
message?.content?.addToClipboard,
|
|
63
|
-
message?.content?.persistToClipboard,
|
|
64
|
-
message?.content?.saveToClipboard,
|
|
65
|
-
].some((value) => parseBooleanFlag(value));
|
|
66
|
-
}
|
|
67
|
-
function resolveClipboardTitle(params, argumentParams, message) {
|
|
68
|
-
return (readStringValue(params.clipboardTitle) ??
|
|
69
|
-
readStringValue(params.title) ??
|
|
70
|
-
readStringValue(argumentParams?.clipboardTitle) ??
|
|
71
|
-
readStringValue(argumentParams?.title) ??
|
|
72
|
-
readStringValue(message?.content?.clipboardTitle) ??
|
|
73
|
-
readStringValue(message?.content?.title));
|
|
74
|
-
}
|
|
75
40
|
/**
|
|
76
41
|
* Extract a command from handler options and message text.
|
|
77
42
|
*
|
|
@@ -79,9 +44,8 @@ function resolveClipboardTitle(params, argumentParams, message) {
|
|
|
79
44
|
* 1. `parameters.command` — explicit parameter
|
|
80
45
|
* 2. `parameters.shellCommand` — explicit alias
|
|
81
46
|
* 3. `parameters.arguments` — MCP-style JSON string like `{"command":"ls"}`
|
|
82
|
-
* 4. Natural language extraction from message text
|
|
83
47
|
*/
|
|
84
|
-
function getCommand(options
|
|
48
|
+
function getCommand(options) {
|
|
85
49
|
const params = (options?.parameters ?? {});
|
|
86
50
|
const argumentParams = parseJsonArguments(params.arguments);
|
|
87
51
|
// The planner must extract the command as an explicit `command` param.
|
|
@@ -95,19 +59,13 @@ function getCommand(options, _message) {
|
|
|
95
59
|
readStringValue(argumentParams?.command) ??
|
|
96
60
|
readStringValue(argumentParams?.shellCommand));
|
|
97
61
|
}
|
|
98
|
-
function resolveTerminalInput(options
|
|
99
|
-
const params = (options?.parameters ?? {});
|
|
100
|
-
const argumentParams = parseJsonArguments(params.arguments);
|
|
62
|
+
function resolveTerminalInput(options) {
|
|
101
63
|
return {
|
|
102
|
-
command: getCommand(options
|
|
103
|
-
addToClipboard: resolveClipboardRequested(params, argumentParams, message),
|
|
104
|
-
clipboardTitle: resolveClipboardTitle(params, argumentParams, message),
|
|
64
|
+
command: getCommand(options),
|
|
105
65
|
};
|
|
106
66
|
}
|
|
107
67
|
function normalizeCapturedRun(command, value) {
|
|
108
|
-
const data = value
|
|
109
|
-
? value
|
|
110
|
-
: {};
|
|
68
|
+
const data = isJsonRecord(value) ? value : {};
|
|
111
69
|
const exitCode = typeof data.exitCode === "number" && Number.isFinite(data.exitCode)
|
|
112
70
|
? data.exitCode
|
|
113
71
|
: Number(data.exitCode ?? 0) || 0;
|
|
@@ -125,24 +83,6 @@ function normalizeCapturedRun(command, value) {
|
|
|
125
83
|
: undefined,
|
|
126
84
|
};
|
|
127
85
|
}
|
|
128
|
-
async function getClipboardStoreFn() {
|
|
129
|
-
if (cachedClipboardStoreFn !== undefined) {
|
|
130
|
-
return cachedClipboardStoreFn;
|
|
131
|
-
}
|
|
132
|
-
try {
|
|
133
|
-
const clipboardModule = "@elizaos/core/advanced-capabilities/clipboard/index";
|
|
134
|
-
const mod = (await import(__rewriteRelativeImportExtension(/* @vite-ignore */ clipboardModule)));
|
|
135
|
-
cachedClipboardStoreFn =
|
|
136
|
-
typeof mod.maybeStoreTaskClipboardItem === "function"
|
|
137
|
-
? mod.maybeStoreTaskClipboardItem
|
|
138
|
-
: null;
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
cachedClipboardStoreFn = null;
|
|
142
|
-
logger.warn(`[terminal] Clipboard plugin unavailable; shell output will not be persisted (${error instanceof Error ? error.message : String(error)})`);
|
|
143
|
-
}
|
|
144
|
-
return cachedClipboardStoreFn;
|
|
145
|
-
}
|
|
146
86
|
function formatOutputBlock(content) {
|
|
147
87
|
return content.trimEnd() || "(empty)";
|
|
148
88
|
}
|
|
@@ -164,81 +104,79 @@ function buildCommandArtifactContent(result) {
|
|
|
164
104
|
.filter(Boolean)
|
|
165
105
|
.join("\n");
|
|
166
106
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
stored: false,
|
|
172
|
-
};
|
|
107
|
+
function buildOutputPreview(content, maxLength = 3_000) {
|
|
108
|
+
const trimmed = content.trimEnd();
|
|
109
|
+
if (trimmed.length <= maxLength) {
|
|
110
|
+
return formatOutputBlock(trimmed);
|
|
173
111
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
};
|
|
112
|
+
return `${trimmed.slice(0, maxLength).trimEnd()}\n\n[... ${trimmed.length - maxLength} chars omitted; use the attachment for full output ...]`;
|
|
113
|
+
}
|
|
114
|
+
async function createCommandOutputAttachment(runtime, message, result) {
|
|
115
|
+
if (!runtime?.createMemory) {
|
|
116
|
+
return undefined;
|
|
180
117
|
}
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
118
|
+
const attachmentId = stringToUuid(`terminal-output:${message.id ?? message.roomId}:${result.runId ?? result.command}:${Date.now()}`);
|
|
119
|
+
const title = `Shell output: ${result.command}`;
|
|
120
|
+
const attachment = {
|
|
121
|
+
id: attachmentId,
|
|
122
|
+
url: `memory://terminal-output/${attachmentId}`,
|
|
123
|
+
title,
|
|
124
|
+
source: TERMINAL_ACTION_NAME,
|
|
125
|
+
description: `Full stdout/stderr for \`${result.command}\` (exit ${result.exitCode}).`,
|
|
126
|
+
text: buildCommandArtifactContent(result),
|
|
127
|
+
contentType: ContentType.DOCUMENT,
|
|
188
128
|
};
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
129
|
+
try {
|
|
130
|
+
const memoryId = await runtime.createMemory({
|
|
131
|
+
id: stringToUuid(`terminal-output-memory:${attachmentId}`),
|
|
132
|
+
entityId: runtime.agentId,
|
|
133
|
+
agentId: runtime.agentId,
|
|
134
|
+
roomId: message.roomId,
|
|
135
|
+
createdAt: Date.now(),
|
|
136
|
+
content: {
|
|
137
|
+
text: `Stored terminal output attachment ${attachment.id}: ${attachment.title}`,
|
|
138
|
+
source: TERMINAL_ACTION_NAME,
|
|
139
|
+
attachments: [attachment],
|
|
140
|
+
},
|
|
141
|
+
}, "messages");
|
|
142
|
+
return { attachment, memoryId };
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
logger.warn(`[terminal] Failed to store shell output attachment (${error instanceof Error ? error.message : String(error)})`);
|
|
146
|
+
return { attachment };
|
|
196
147
|
}
|
|
197
|
-
return storeClipboardItem(runtime, clipboardMessage, {
|
|
198
|
-
fallbackTitle: input.clipboardTitle ?? result.command,
|
|
199
|
-
content: buildCommandArtifactContent(result),
|
|
200
|
-
sourceType: "command",
|
|
201
|
-
sourceId: result.command,
|
|
202
|
-
sourceLabel: result.command,
|
|
203
|
-
});
|
|
204
148
|
}
|
|
205
|
-
function buildCapturedResponseText(result,
|
|
206
|
-
const
|
|
207
|
-
? clipboardResult.item
|
|
208
|
-
: undefined;
|
|
209
|
-
const clipboardSnapshot = clipboardResult.stored
|
|
210
|
-
? clipboardResult.snapshot
|
|
211
|
-
: undefined;
|
|
149
|
+
function buildCapturedResponseText(result, outputAttachment) {
|
|
150
|
+
const outputContent = buildCommandArtifactContent(result);
|
|
212
151
|
return [
|
|
213
|
-
`
|
|
152
|
+
`Shell command completed: \`${result.command}\``,
|
|
214
153
|
`Exit code: ${result.exitCode}`,
|
|
215
154
|
result.timedOut
|
|
216
155
|
? `Timed out${typeof result.maxDurationMs === "number" ? ` after ${result.maxDurationMs} ms` : ""}.`
|
|
217
156
|
: "",
|
|
218
157
|
result.truncated ? "Captured output truncated to 128 KB." : "",
|
|
219
|
-
|
|
220
|
-
?
|
|
221
|
-
? `${clipboardResult.replaced ? "Updated" : "Added"} clipboard item ${clipboardItem?.id ?? "unknown"}: ${clipboardItem?.title ?? result.command}`
|
|
222
|
-
: `Clipboard add skipped: ${clipboardResult.reason}`
|
|
223
|
-
: "",
|
|
224
|
-
clipboardSnapshot
|
|
225
|
-
? `Clipboard usage: ${clipboardSnapshot.items.length}/${clipboardSnapshot.maxItems}.`
|
|
226
|
-
: "",
|
|
227
|
-
clipboardSnapshot
|
|
228
|
-
? "Clear unused clipboard state when it is no longer needed."
|
|
158
|
+
outputAttachment
|
|
159
|
+
? `Full output attachment: ${outputAttachment.attachment.id} (${outputAttachment.attachment.title})`
|
|
229
160
|
: "",
|
|
161
|
+
outputAttachment?.memoryId
|
|
162
|
+
? `Attachment memory: ${outputAttachment.memoryId}`
|
|
163
|
+
: outputAttachment
|
|
164
|
+
? "Attachment memory could not be persisted; full output is still present in this action result."
|
|
165
|
+
: "No attachment was stored for this output.",
|
|
230
166
|
"",
|
|
231
|
-
"
|
|
232
|
-
|
|
167
|
+
"Output preview:",
|
|
168
|
+
buildOutputPreview(outputContent),
|
|
233
169
|
"",
|
|
234
|
-
"
|
|
235
|
-
|
|
170
|
+
"Next-step contract for the planner:",
|
|
171
|
+
"- Decide whether to reply to the user, stay silent, or continue with another action.",
|
|
172
|
+
"- If the output should be kept for this task, call SAVE_ATTACHMENT_TO_CLIPBOARD with the attachmentId above.",
|
|
173
|
+
"- If replying, answer naturally from the output instead of echoing this report.",
|
|
236
174
|
]
|
|
237
175
|
.filter(Boolean)
|
|
238
176
|
.join("\n");
|
|
239
177
|
}
|
|
240
178
|
export const terminalAction = {
|
|
241
|
-
name:
|
|
179
|
+
name: TERMINAL_ACTION_NAME,
|
|
242
180
|
similes: [
|
|
243
181
|
"RUN_IN_TERMINAL",
|
|
244
182
|
"RUN_COMMAND",
|
|
@@ -252,7 +190,7 @@ export const terminalAction = {
|
|
|
252
190
|
description: "Run a single explicit shell command that the user provided directly. " +
|
|
253
191
|
"Only use when the user gives a specific command like 'run ls -la' or 'execute npm install'. " +
|
|
254
192
|
"Do NOT use for building projects, creating websites, or multi-step work — use CREATE_TASK instead. " +
|
|
255
|
-
"
|
|
193
|
+
"The command output is captured as a document attachment for post-action planning. After the run, decide whether to reply, stay silent, continue with another action, or save the attachment via the clipboard plugin.",
|
|
256
194
|
validate: async (runtime, message) => {
|
|
257
195
|
// Permission is the only gate here. Whether the action is relevant to the
|
|
258
196
|
// current request is the planner's job — not a regex / keyword scan in
|
|
@@ -267,7 +205,7 @@ export const terminalAction = {
|
|
|
267
205
|
text: "Permission denied: only the owner may run terminal commands.",
|
|
268
206
|
};
|
|
269
207
|
}
|
|
270
|
-
const input = resolveTerminalInput(options
|
|
208
|
+
const input = resolveTerminalInput(options);
|
|
271
209
|
const command = input.command;
|
|
272
210
|
if (!command) {
|
|
273
211
|
return FAIL;
|
|
@@ -279,27 +217,24 @@ export const terminalAction = {
|
|
|
279
217
|
body: JSON.stringify({
|
|
280
218
|
command,
|
|
281
219
|
clientId: "runtime-terminal-action",
|
|
282
|
-
|
|
220
|
+
captureOutput: true,
|
|
283
221
|
}),
|
|
284
222
|
});
|
|
285
223
|
if (!response.ok) {
|
|
286
224
|
return FAIL;
|
|
287
225
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
success: true,
|
|
292
|
-
data: { command },
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
const capturedRun = normalizeCapturedRun(command, await response.json());
|
|
296
|
-
const clipboardResult = await maybeStoreCommandOutput(runtime, message, input, capturedRun);
|
|
226
|
+
const responseBody = (await response.json());
|
|
227
|
+
const capturedRun = normalizeCapturedRun(command, responseBody);
|
|
228
|
+
const outputAttachment = await createCommandOutputAttachment(runtime, message, capturedRun);
|
|
297
229
|
return {
|
|
298
|
-
text: buildCapturedResponseText(capturedRun,
|
|
230
|
+
text: buildCapturedResponseText(capturedRun, outputAttachment),
|
|
299
231
|
success: true,
|
|
300
232
|
data: {
|
|
233
|
+
actionName: TERMINAL_ACTION_NAME,
|
|
301
234
|
...capturedRun,
|
|
302
|
-
|
|
235
|
+
outputAttachment: outputAttachment?.attachment,
|
|
236
|
+
outputAttachmentMemoryId: outputAttachment?.memoryId,
|
|
237
|
+
suppressVisibleCallback: true,
|
|
303
238
|
},
|
|
304
239
|
};
|
|
305
240
|
}
|
|
@@ -314,18 +249,6 @@ export const terminalAction = {
|
|
|
314
249
|
required: true,
|
|
315
250
|
schema: { type: "string" },
|
|
316
251
|
},
|
|
317
|
-
{
|
|
318
|
-
name: "addToClipboard",
|
|
319
|
-
description: "When true, wait for the command to finish, capture stdout/stderr, and store the result in bounded clipboard state.",
|
|
320
|
-
required: false,
|
|
321
|
-
schema: { type: "boolean" },
|
|
322
|
-
},
|
|
323
|
-
{
|
|
324
|
-
name: "clipboardTitle",
|
|
325
|
-
description: "Optional clipboard title to use when addToClipboard=true.",
|
|
326
|
-
required: false,
|
|
327
|
-
schema: { type: "string" },
|
|
328
|
-
},
|
|
329
252
|
],
|
|
330
253
|
examples: [
|
|
331
254
|
[
|
|
@@ -338,7 +261,7 @@ export const terminalAction = {
|
|
|
338
261
|
{
|
|
339
262
|
name: "{{agentName}}",
|
|
340
263
|
content: {
|
|
341
|
-
text: "
|
|
264
|
+
text: "The directory listing completed. It shows the current files and folders in your home directory.",
|
|
342
265
|
},
|
|
343
266
|
},
|
|
344
267
|
],
|
|
@@ -352,7 +275,7 @@ export const terminalAction = {
|
|
|
352
275
|
{
|
|
353
276
|
name: "{{agentName}}",
|
|
354
277
|
content: {
|
|
355
|
-
text: "
|
|
278
|
+
text: "The `git status` output was captured. I saved the full output as an attachment and can keep it in the clipboard if it is useful for the next step.",
|
|
356
279
|
},
|
|
357
280
|
},
|
|
358
281
|
],
|