@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.
Files changed (36) hide show
  1. package/apps/app-lifeops/src/lifeops/discord-browser-scraper.d.ts +1 -0
  2. package/apps/app-lifeops/src/lifeops/discord-browser-scraper.d.ts.map +1 -1
  3. package/apps/app-lifeops/src/lifeops/discord-browser-scraper.js +57 -21
  4. package/apps/app-lifeops/src/lifeops/imessage-bridge.d.ts +1 -0
  5. package/apps/app-lifeops/src/lifeops/imessage-bridge.d.ts.map +1 -1
  6. package/apps/app-lifeops/src/lifeops/imessage-bridge.js +41 -36
  7. package/apps/app-lifeops/src/lifeops/service-mixin-signal.js +7 -0
  8. package/apps/app-lifeops/src/lifeops/service-mixin-telegram.js +8 -1
  9. package/apps/app-lifeops/src/lifeops/signal-local-client.d.ts +4 -4
  10. package/apps/app-lifeops/src/lifeops/signal-local-client.d.ts.map +1 -1
  11. package/apps/app-lifeops/src/lifeops/signal-local-client.js +40 -15
  12. package/apps/app-lifeops/src/lifeops/telegram-auth.d.ts.map +1 -1
  13. package/apps/app-lifeops/src/lifeops/telegram-auth.js +21 -1
  14. package/apps/app-lifeops/src/lifeops/telegram-local-client.d.ts +3 -0
  15. package/apps/app-lifeops/src/lifeops/telegram-local-client.d.ts.map +1 -1
  16. package/apps/app-lifeops/src/lifeops/telegram-local-client.js +61 -18
  17. package/apps/app-lifeops/src/lifeops/whatsapp-client.d.ts +7 -0
  18. package/apps/app-lifeops/src/lifeops/whatsapp-client.d.ts.map +1 -1
  19. package/apps/app-lifeops/src/lifeops/whatsapp-client.js +47 -16
  20. package/package.json +4 -4
  21. package/packages/agent/src/actions/terminal.d.ts +3 -3
  22. package/packages/agent/src/actions/terminal.d.ts.map +1 -1
  23. package/packages/agent/src/actions/terminal.js +80 -157
  24. package/packages/shared/src/contracts/lifeops.d.ts +14 -0
  25. package/packages/shared/src/contracts/lifeops.d.ts.map +1 -1
  26. package/packages/typescript/src/features/advanced-capabilities/clipboard/actions/save-attachment-to-clipboard.d.ts +4 -0
  27. package/packages/typescript/src/features/advanced-capabilities/clipboard/actions/save-attachment-to-clipboard.d.ts.map +1 -0
  28. package/packages/typescript/src/features/advanced-capabilities/clipboard/actions/save-attachment-to-clipboard.js +127 -0
  29. package/packages/typescript/src/features/advanced-capabilities/clipboard/index.d.ts +2 -0
  30. package/packages/typescript/src/features/advanced-capabilities/clipboard/index.d.ts.map +1 -1
  31. package/packages/typescript/src/features/advanced-capabilities/clipboard/index.js +4 -0
  32. package/packages/typescript/src/runtime.d.ts.map +1 -1
  33. package/packages/typescript/src/runtime.js +10 -2
  34. package/packages/typescript/src/services/message.d.ts +7 -0
  35. package/packages/typescript/src/services/message.d.ts.map +1 -1
  36. 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: sent?.id !== undefined ? serializeTelegramId(sent.id) : null,
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
- id: message.id !== undefined
254
- ? serializeTelegramId(message.id) || null
255
- : null,
256
- dialogId: dialog?.id !== undefined
257
- ? serializeTelegramId(dialog.id) || null
258
- : null,
259
- dialogTitle: dialog ? normalizeDialogTitle(dialog) : null,
260
- username: typeof dialog?.entity?.username === "string" &&
261
- dialog.entity.username.trim().length > 0
262
- ? dialog.entity.username.trim()
263
- : null,
264
- content: normalizeMessageContent(message),
265
- timestamp: toIsoDate(message.date),
266
- outgoing: message.out === true,
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
- messageId = sent?.id !== undefined ? serializeTelegramId(sent.id) : null;
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;CAClB;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,CAqD1C;AA0CD;;;;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,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,EAAE,CA0EhF"}
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 ?? `WhatsApp request failed with HTTP ${response.status}`;
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" || raw === "image" || raw === "audio" || raw === "document") {
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
- // WhatsApp timestamps are unix seconds as string; convert to ISO.
164
- let isoTimestamp;
165
- if (typeof m.timestamp === "string") {
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.437",
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.437",
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.437",
480
- "@elizaos/skills": "^2.0.0-alpha.437",
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 the parameters, NL text, or MCP-style JSON
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. Optionally captures the output and stores it in bounded clipboard state
9
- * 5. Returns a descriptive text response
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,EAKP,MAAM,eAAe,CAAC;AAqVvB,eAAO,MAAM,cAAc,EAAE,MAgJ5B,CAAC"}
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 the parameters, NL text, or MCP-style JSON
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. Optionally captures the output and stores it in bounded clipboard state
9
- * 5. Returns a descriptive text response
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
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
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 (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
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, _message) {
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, message) {
99
- const params = (options?.parameters ?? {});
100
- const argumentParams = parseJsonArguments(params.arguments);
62
+ function resolveTerminalInput(options) {
101
63
  return {
102
- command: getCommand(options, message),
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 && typeof value === "object" && !Array.isArray(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
- async function maybeStoreCommandOutput(runtime, message, input, result) {
168
- if (!input.addToClipboard) {
169
- return {
170
- requested: false,
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
- if (!runtime) {
175
- return {
176
- requested: true,
177
- stored: false,
178
- reason: "Runtime unavailable; command output could not be added to the clipboard.",
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 clipboardMessage = {
182
- ...message,
183
- content: {
184
- ...message.content,
185
- addToClipboard: true,
186
- ...(input.clipboardTitle ? { clipboardTitle: input.clipboardTitle } : {}),
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
- const storeClipboardItem = await getClipboardStoreFn();
190
- if (!storeClipboardItem) {
191
- return {
192
- requested: true,
193
- stored: false,
194
- reason: "Clipboard plugin unavailable; command output could not be added to the clipboard.",
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, clipboardResult) {
206
- const clipboardItem = clipboardResult.stored
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
- `Executed shell command: \`${result.command}\``,
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
- clipboardResult.requested
220
- ? clipboardResult.stored
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
- "STDOUT:",
232
- formatOutputBlock(result.stdout),
167
+ "Output preview:",
168
+ buildOutputPreview(outputContent),
233
169
  "",
234
- "STDERR:",
235
- formatOutputBlock(result.stderr),
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: "SHELL_COMMAND",
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
- "Set addToClipboard=true to capture the command output, return it inline, and store it in bounded clipboard state.",
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, message);
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
- ...(input.addToClipboard ? { captureOutput: true } : {}),
220
+ captureOutput: true,
283
221
  }),
284
222
  });
285
223
  if (!response.ok) {
286
224
  return FAIL;
287
225
  }
288
- if (!input.addToClipboard) {
289
- return {
290
- text: `Running in terminal: \`${command}\``,
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, clipboardResult),
230
+ text: buildCapturedResponseText(capturedRun, outputAttachment),
299
231
  success: true,
300
232
  data: {
233
+ actionName: TERMINAL_ACTION_NAME,
301
234
  ...capturedRun,
302
- clipboard: clipboardResult,
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: "Running in terminal: `ls -la`",
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: "Executed shell command: `git status`\nExit code: 0",
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
  ],