@elizaos/plugin-shell 2.0.0-alpha.4 → 2.0.0-alpha.537
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/LICENSE +21 -0
- package/README.md +366 -0
- package/dist/actions/clearHistory.d.ts.map +1 -1
- package/dist/actions/index.d.ts +0 -2
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/approvals/allowlist.d.ts.map +1 -1
- package/dist/approvals/analysis.d.ts.map +1 -1
- package/dist/approvals/service.d.ts.map +1 -1
- package/dist/generated/specs/spec-helpers.d.ts +1 -1
- package/dist/generated/specs/spec-helpers.d.ts.map +1 -1
- package/dist/generated/specs/specs.d.ts +3 -10
- package/dist/generated/specs/specs.d.ts.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +291 -576
- package/dist/index.js.map +15 -17
- package/dist/providers/shellHistoryProvider.d.ts.map +1 -1
- package/dist/providers/terminalUsage.d.ts +3 -0
- package/dist/providers/terminalUsage.d.ts.map +1 -0
- package/dist/services/processRegistry.d.ts.map +1 -1
- package/dist/services/shellService.d.ts.map +1 -1
- package/dist/utils/ptyKeys.d.ts.map +1 -1
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/package.json +10 -10
- package/dist/actions/executeCommand.d.ts +0 -6
- package/dist/actions/executeCommand.d.ts.map +0 -1
- package/dist/actions/processAction.d.ts +0 -7
- package/dist/actions/processAction.d.ts.map +0 -1
- package/dist/build.d.ts +0 -2
- package/dist/build.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -4,21 +4,35 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
7
12
|
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
8
20
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
21
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
22
|
for (let key of __getOwnPropNames(mod))
|
|
11
23
|
if (!__hasOwnProp.call(to, key))
|
|
12
24
|
__defProp(to, key, {
|
|
13
|
-
get: (
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
14
26
|
enumerable: true
|
|
15
27
|
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
16
30
|
return to;
|
|
17
31
|
};
|
|
18
32
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
19
33
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
34
|
|
|
21
|
-
// ../../../node_modules
|
|
35
|
+
// ../../../node_modules/@lydell/node-pty/eventEmitter2.js
|
|
22
36
|
var require_eventEmitter2 = __commonJS((exports) => {
|
|
23
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
38
|
var EventEmitter2 = function() {
|
|
@@ -63,7 +77,7 @@ var require_eventEmitter2 = __commonJS((exports) => {
|
|
|
63
77
|
exports.EventEmitter2 = EventEmitter2;
|
|
64
78
|
});
|
|
65
79
|
|
|
66
|
-
// ../../../node_modules
|
|
80
|
+
// ../../../node_modules/@lydell/node-pty/terminal.js
|
|
67
81
|
var require_terminal = __commonJS((exports) => {
|
|
68
82
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
69
83
|
var events_1 = __require("events");
|
|
@@ -251,7 +265,7 @@ var require_terminal = __commonJS((exports) => {
|
|
|
251
265
|
exports.Terminal = Terminal;
|
|
252
266
|
});
|
|
253
267
|
|
|
254
|
-
// ../../../node_modules
|
|
268
|
+
// ../../../node_modules/@lydell/node-pty/shared/conout.js
|
|
255
269
|
var require_conout = __commonJS((exports) => {
|
|
256
270
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
257
271
|
function getWorkerPipeName(conoutPipeName) {
|
|
@@ -260,9 +274,9 @@ var require_conout = __commonJS((exports) => {
|
|
|
260
274
|
exports.getWorkerPipeName = getWorkerPipeName;
|
|
261
275
|
});
|
|
262
276
|
|
|
263
|
-
// ../../../node_modules
|
|
277
|
+
// ../../../node_modules/@lydell/node-pty/windowsConoutConnection.js
|
|
264
278
|
var require_windowsConoutConnection = __commonJS((exports) => {
|
|
265
|
-
var __dirname = "/Users/shawwalters/eliza-
|
|
279
|
+
var __dirname = "/Users/shawwalters/eliza-workspace/milady/node_modules/@lydell/node-pty";
|
|
266
280
|
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
|
|
267
281
|
function adopt(value) {
|
|
268
282
|
return value instanceof P ? value : new P(function(resolve) {
|
|
@@ -435,7 +449,7 @@ var require_windowsConoutConnection = __commonJS((exports) => {
|
|
|
435
449
|
exports.ConoutConnection = ConoutConnection;
|
|
436
450
|
});
|
|
437
451
|
|
|
438
|
-
// ../../../node_modules
|
|
452
|
+
// ../../../node_modules/@lydell/node-pty/package.json
|
|
439
453
|
var require_package = __commonJS((exports, module) => {
|
|
440
454
|
module.exports = {
|
|
441
455
|
name: "@lydell/node-pty",
|
|
@@ -470,7 +484,7 @@ var require_package = __commonJS((exports, module) => {
|
|
|
470
484
|
};
|
|
471
485
|
});
|
|
472
486
|
|
|
473
|
-
// ../../../node_modules
|
|
487
|
+
// ../../../node_modules/@lydell/node-pty/requireBinary.js
|
|
474
488
|
var require_requireBinary = __commonJS((exports) => {
|
|
475
489
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
476
490
|
var PACKAGE_NAME = "@lydell/node-pty-" + process.platform + "-" + process.arch;
|
|
@@ -511,9 +525,9 @@ Your platform (` + process.platform + "-" + process.arch + ") might not be suppo
|
|
|
511
525
|
}
|
|
512
526
|
});
|
|
513
527
|
|
|
514
|
-
// ../../../node_modules
|
|
528
|
+
// ../../../node_modules/@lydell/node-pty/windowsPtyAgent.js
|
|
515
529
|
var require_windowsPtyAgent = __commonJS((exports) => {
|
|
516
|
-
var __dirname = "/Users/shawwalters/eliza-
|
|
530
|
+
var __dirname = "/Users/shawwalters/eliza-workspace/milady/node_modules/@lydell/node-pty";
|
|
517
531
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
518
532
|
var fs5 = __require("fs");
|
|
519
533
|
var path6 = __require("path");
|
|
@@ -729,7 +743,7 @@ var require_windowsPtyAgent = __commonJS((exports) => {
|
|
|
729
743
|
}
|
|
730
744
|
});
|
|
731
745
|
|
|
732
|
-
// ../../../node_modules
|
|
746
|
+
// ../../../node_modules/@lydell/node-pty/utils.js
|
|
733
747
|
var require_utils = __commonJS((exports) => {
|
|
734
748
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
735
749
|
function assign(target) {
|
|
@@ -747,7 +761,7 @@ var require_utils = __commonJS((exports) => {
|
|
|
747
761
|
exports.assign = assign;
|
|
748
762
|
});
|
|
749
763
|
|
|
750
|
-
// ../../../node_modules
|
|
764
|
+
// ../../../node_modules/@lydell/node-pty/windowsTerminal.js
|
|
751
765
|
var require_windowsTerminal = __commonJS((exports) => {
|
|
752
766
|
var __extends = exports && exports.__extends || function() {
|
|
753
767
|
var extendStatics = function(d, b) {
|
|
@@ -925,7 +939,7 @@ var require_windowsTerminal = __commonJS((exports) => {
|
|
|
925
939
|
exports.WindowsTerminal = WindowsTerminal;
|
|
926
940
|
});
|
|
927
941
|
|
|
928
|
-
// ../../../node_modules
|
|
942
|
+
// ../../../node_modules/@lydell/node-pty/unixTerminal.js
|
|
929
943
|
var require_unixTerminal = __commonJS((exports) => {
|
|
930
944
|
var __extends = exports && exports.__extends || function() {
|
|
931
945
|
var extendStatics = function(d, b) {
|
|
@@ -1171,7 +1185,7 @@ var require_unixTerminal = __commonJS((exports) => {
|
|
|
1171
1185
|
exports.UnixTerminal = UnixTerminal;
|
|
1172
1186
|
});
|
|
1173
1187
|
|
|
1174
|
-
// ../../../node_modules
|
|
1188
|
+
// ../../../node_modules/@lydell/node-pty/index.js
|
|
1175
1189
|
var require_node_pty = __commonJS((exports) => {
|
|
1176
1190
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1177
1191
|
var terminalCtor;
|
|
@@ -1212,26 +1226,6 @@ var coreActionsSpec = {
|
|
|
1212
1226
|
description: "Clears the recorded history of shell commands for the current conversation",
|
|
1213
1227
|
similes: ["RESET_SHELL", "CLEAR_TERMINAL", "CLEAR_HISTORY", "RESET_HISTORY"],
|
|
1214
1228
|
parameters: []
|
|
1215
|
-
},
|
|
1216
|
-
{
|
|
1217
|
-
name: "EXECUTE_COMMAND",
|
|
1218
|
-
description: "Execute shell commands including brew install, npm install, apt-get, system commands, file operations, directory navigation, and scripts.",
|
|
1219
|
-
similes: [
|
|
1220
|
-
"RUN_COMMAND",
|
|
1221
|
-
"SHELL_COMMAND",
|
|
1222
|
-
"TERMINAL_COMMAND",
|
|
1223
|
-
"EXEC",
|
|
1224
|
-
"RUN",
|
|
1225
|
-
"EXECUTE",
|
|
1226
|
-
"CREATE_FILE",
|
|
1227
|
-
"WRITE_FILE",
|
|
1228
|
-
"MAKE_FILE",
|
|
1229
|
-
"INSTALL",
|
|
1230
|
-
"BREW_INSTALL",
|
|
1231
|
-
"NPM_INSTALL",
|
|
1232
|
-
"APT_INSTALL"
|
|
1233
|
-
],
|
|
1234
|
-
parameters: []
|
|
1235
1229
|
}
|
|
1236
1230
|
]
|
|
1237
1231
|
};
|
|
@@ -1243,26 +1237,6 @@ var allActionsSpec = {
|
|
|
1243
1237
|
description: "Clears the recorded history of shell commands for the current conversation",
|
|
1244
1238
|
similes: ["RESET_SHELL", "CLEAR_TERMINAL", "CLEAR_HISTORY", "RESET_HISTORY"],
|
|
1245
1239
|
parameters: []
|
|
1246
|
-
},
|
|
1247
|
-
{
|
|
1248
|
-
name: "EXECUTE_COMMAND",
|
|
1249
|
-
description: "Execute shell commands including brew install, npm install, apt-get, system commands, file operations, directory navigation, and scripts.",
|
|
1250
|
-
similes: [
|
|
1251
|
-
"RUN_COMMAND",
|
|
1252
|
-
"SHELL_COMMAND",
|
|
1253
|
-
"TERMINAL_COMMAND",
|
|
1254
|
-
"EXEC",
|
|
1255
|
-
"RUN",
|
|
1256
|
-
"EXECUTE",
|
|
1257
|
-
"CREATE_FILE",
|
|
1258
|
-
"WRITE_FILE",
|
|
1259
|
-
"MAKE_FILE",
|
|
1260
|
-
"INSTALL",
|
|
1261
|
-
"BREW_INSTALL",
|
|
1262
|
-
"NPM_INSTALL",
|
|
1263
|
-
"APT_INSTALL"
|
|
1264
|
-
],
|
|
1265
|
-
parameters: []
|
|
1266
1240
|
}
|
|
1267
1241
|
]
|
|
1268
1242
|
};
|
|
@@ -1335,7 +1309,8 @@ var clearHistory = {
|
|
|
1335
1309
|
name: spec.name,
|
|
1336
1310
|
similes: spec.similes ? [...spec.similes] : [],
|
|
1337
1311
|
description: spec.description,
|
|
1338
|
-
|
|
1312
|
+
descriptionCompressed: spec.descriptionCompressed,
|
|
1313
|
+
validate: async (runtime, message, _state, _options) => {
|
|
1339
1314
|
const shellService = runtime.getService("shell");
|
|
1340
1315
|
if (!shellService) {
|
|
1341
1316
|
return false;
|
|
@@ -1343,9 +1318,7 @@ var clearHistory = {
|
|
|
1343
1318
|
const text = message.content.text?.toLowerCase() || "";
|
|
1344
1319
|
const clearKeywords = ["clear", "reset", "delete", "remove", "clean"];
|
|
1345
1320
|
const historyKeywords = ["history", "terminal", "shell", "command"];
|
|
1346
|
-
|
|
1347
|
-
const hasHistoryKeyword = historyKeywords.some((keyword) => text.includes(keyword));
|
|
1348
|
-
return hasClearKeyword && hasHistoryKeyword;
|
|
1321
|
+
return clearKeywords.some((keyword) => text.includes(keyword)) && historyKeywords.some((keyword) => text.includes(keyword));
|
|
1349
1322
|
},
|
|
1350
1323
|
handler: async (runtime, message, _state, _options, callback) => {
|
|
1351
1324
|
const shellService = runtime.getService("shell");
|
|
@@ -1382,389 +1355,12 @@ var clearHistory = {
|
|
|
1382
1355
|
},
|
|
1383
1356
|
examples: spec.examples ?? []
|
|
1384
1357
|
};
|
|
1385
|
-
// actions/executeCommand.ts
|
|
1386
|
-
import {
|
|
1387
|
-
composePromptFromState,
|
|
1388
|
-
logger as logger2,
|
|
1389
|
-
ModelType,
|
|
1390
|
-
parseJSONObjectFromText
|
|
1391
|
-
} from "@elizaos/core";
|
|
1392
|
-
|
|
1393
|
-
// generated/prompts/typescript/prompts.ts
|
|
1394
|
-
var commandExtractionTemplate = `# Extracting shell command from request
|
|
1395
|
-
{{recentMessages}}
|
|
1396
|
-
|
|
1397
|
-
# Instructions: {{senderName}} wants to execute a shell command. Extract the COMPLETE shell command they want to run.
|
|
1398
|
-
|
|
1399
|
-
IMPORTANT:
|
|
1400
|
-
1. Always return the FULL executable shell command, not just the content or partial command.
|
|
1401
|
-
2. If the user mentions installing something, create the appropriate brew/npm/apt command.
|
|
1402
|
-
3. If the user directly provides a command (like "brew install X"), use it exactly as provided.
|
|
1403
|
-
4. ALWAYS extract a command if the user is asking for ANY kind of system operation.
|
|
1404
|
-
|
|
1405
|
-
Common patterns:
|
|
1406
|
-
- "run ls -la" -> command: "ls -la"
|
|
1407
|
-
- "execute npm test" -> command: "npm test"
|
|
1408
|
-
- "show me the files" or "list files" -> command: "ls -la"
|
|
1409
|
-
- "what's in this directory" -> command: "ls -la"
|
|
1410
|
-
- "check git status" -> command: "git status"
|
|
1411
|
-
- "navigate to src folder" -> command: "cd src"
|
|
1412
|
-
- "create a file called test.txt" -> command: "touch test.txt"
|
|
1413
|
-
- "write hello world to a file" -> command: "echo 'hello world' > file.txt"
|
|
1414
|
-
- "create hello.js with javascript code" -> command: "echo 'console.log(\\"Hello, World!\\");' > hello.js"
|
|
1415
|
-
- "create hello_world.py and write a python hello world script inside" -> command: "echo 'print(\\"Hello, World!\\")' > hello_world.py"
|
|
1416
|
-
- "make a new directory" -> command: "mkdir newdir"
|
|
1417
|
-
- "list files inside your filesystem" -> command: "ls -la"
|
|
1418
|
-
- "install orbstack" or "brew install orbstack" -> command: "brew install orbstack"
|
|
1419
|
-
- "install mullvad vpn" -> command: "brew install --cask mullvad-vpn"
|
|
1420
|
-
- "get system info" -> command: "system_profiler SPHardwareDataType"
|
|
1421
|
-
- "check memory usage" -> command: "vm_stat"
|
|
1422
|
-
- "install package" -> command: "brew install <package>"
|
|
1423
|
-
|
|
1424
|
-
Special cases:
|
|
1425
|
-
- "Run it in your shell" or "execute it" -> Extract the command from previous context
|
|
1426
|
-
- "Install these" -> Look for package names in previous messages
|
|
1427
|
-
- Direct commands should be used exactly as provided
|
|
1428
|
-
|
|
1429
|
-
Key rules:
|
|
1430
|
-
1. For file creation with content, use: echo 'content' > filename
|
|
1431
|
-
2. For listing files, use: ls -la (not just ls)
|
|
1432
|
-
3. Always include the echo command when writing to files
|
|
1433
|
-
4. Include all flags and arguments
|
|
1434
|
-
5. When user says "run it", "execute it", or similar, they want you to run the command
|
|
1435
|
-
|
|
1436
|
-
Your response must be formatted as a JSON block:
|
|
1437
|
-
\`\`\`json
|
|
1438
|
-
{
|
|
1439
|
-
"command": "<complete shell command to execute>"
|
|
1440
|
-
}
|
|
1441
|
-
\`\`\``;
|
|
1442
|
-
|
|
1443
|
-
// actions/executeCommand.ts
|
|
1444
|
-
var extractCommand = async (runtime, _message, state) => {
|
|
1445
|
-
const prompt = composePromptFromState({
|
|
1446
|
-
state,
|
|
1447
|
-
template: commandExtractionTemplate
|
|
1448
|
-
});
|
|
1449
|
-
for (let i = 0;i < 3; i++) {
|
|
1450
|
-
const response = await runtime.useModel(ModelType.TEXT_SMALL, {
|
|
1451
|
-
prompt
|
|
1452
|
-
});
|
|
1453
|
-
const parsedResponse = parseJSONObjectFromText(response);
|
|
1454
|
-
if (parsedResponse?.command) {
|
|
1455
|
-
return { command: parsedResponse.command };
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
return null;
|
|
1459
|
-
};
|
|
1460
|
-
var spec2 = requireActionSpec("EXECUTE_COMMAND");
|
|
1461
|
-
var executeCommand = {
|
|
1462
|
-
name: spec2.name,
|
|
1463
|
-
similes: spec2.similes ? [...spec2.similes] : [],
|
|
1464
|
-
description: spec2.description,
|
|
1465
|
-
validate: async (runtime, message, _state) => {
|
|
1466
|
-
const shellService = runtime.getService("shell");
|
|
1467
|
-
if (!shellService) {
|
|
1468
|
-
return false;
|
|
1469
|
-
}
|
|
1470
|
-
const text = message.content.text?.toLowerCase() || "";
|
|
1471
|
-
const commandKeywords = [
|
|
1472
|
-
"run",
|
|
1473
|
-
"execute",
|
|
1474
|
-
"command",
|
|
1475
|
-
"shell",
|
|
1476
|
-
"install",
|
|
1477
|
-
"brew",
|
|
1478
|
-
"npm",
|
|
1479
|
-
"create",
|
|
1480
|
-
"file",
|
|
1481
|
-
"directory",
|
|
1482
|
-
"folder",
|
|
1483
|
-
"list",
|
|
1484
|
-
"show",
|
|
1485
|
-
"system",
|
|
1486
|
-
"info",
|
|
1487
|
-
"check",
|
|
1488
|
-
"status",
|
|
1489
|
-
"cd",
|
|
1490
|
-
"ls",
|
|
1491
|
-
"mkdir",
|
|
1492
|
-
"echo",
|
|
1493
|
-
"cat",
|
|
1494
|
-
"touch",
|
|
1495
|
-
"git",
|
|
1496
|
-
"build",
|
|
1497
|
-
"test"
|
|
1498
|
-
];
|
|
1499
|
-
const hasCommandKeyword = commandKeywords.some((keyword) => text.includes(keyword));
|
|
1500
|
-
const hasDirectCommand = /^(brew|npm|apt|git|ls|cd|echo|cat|touch|mkdir|rm|mv|cp)\s/i.test(message.content.text || "");
|
|
1501
|
-
return hasCommandKeyword || hasDirectCommand;
|
|
1502
|
-
},
|
|
1503
|
-
handler: async (runtime, message, state, _options, callback) => {
|
|
1504
|
-
const shellService = runtime.getService("shell");
|
|
1505
|
-
if (!shellService) {
|
|
1506
|
-
if (callback) {
|
|
1507
|
-
await callback({
|
|
1508
|
-
text: "Shell service is not available.",
|
|
1509
|
-
source: message.content.source
|
|
1510
|
-
});
|
|
1511
|
-
}
|
|
1512
|
-
return { success: false, error: "Shell service is not available." };
|
|
1513
|
-
}
|
|
1514
|
-
const commandInfo = await extractCommand(runtime, message, state);
|
|
1515
|
-
if (!commandInfo?.command) {
|
|
1516
|
-
logger2.error("Failed to extract command from message:", message.content.text);
|
|
1517
|
-
if (callback) {
|
|
1518
|
-
await callback({
|
|
1519
|
-
text: "Could not determine which command to execute. Please specify a shell command.",
|
|
1520
|
-
source: message.content.source
|
|
1521
|
-
});
|
|
1522
|
-
}
|
|
1523
|
-
return { success: false, error: "Could not extract command." };
|
|
1524
|
-
}
|
|
1525
|
-
logger2.info(`Extracted command: "${commandInfo.command}"`);
|
|
1526
|
-
try {
|
|
1527
|
-
const conversationId = message.roomId || message.agentId;
|
|
1528
|
-
const result = await shellService.executeCommand(commandInfo.command, conversationId);
|
|
1529
|
-
let responseText = "";
|
|
1530
|
-
if (result.success) {
|
|
1531
|
-
responseText = `Command executed successfully in ${result.executedIn}
|
|
1532
|
-
|
|
1533
|
-
`;
|
|
1534
|
-
if (result.stdout) {
|
|
1535
|
-
responseText += `Output:
|
|
1536
|
-
\`\`\`
|
|
1537
|
-
${result.stdout}
|
|
1538
|
-
\`\`\``;
|
|
1539
|
-
} else {
|
|
1540
|
-
responseText += "Command completed with no output.";
|
|
1541
|
-
}
|
|
1542
|
-
} else {
|
|
1543
|
-
responseText = `Command failed with exit code ${result.exitCode} in ${result.executedIn}
|
|
1544
|
-
|
|
1545
|
-
`;
|
|
1546
|
-
if (result.error) {
|
|
1547
|
-
responseText += `Error: ${result.error}
|
|
1548
|
-
`;
|
|
1549
|
-
}
|
|
1550
|
-
if (result.stderr) {
|
|
1551
|
-
responseText += `
|
|
1552
|
-
Error output:
|
|
1553
|
-
\`\`\`
|
|
1554
|
-
${result.stderr}
|
|
1555
|
-
\`\`\``;
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
const response = {
|
|
1559
|
-
text: responseText,
|
|
1560
|
-
source: message.content.source
|
|
1561
|
-
};
|
|
1562
|
-
if (callback) {
|
|
1563
|
-
await callback(response);
|
|
1564
|
-
}
|
|
1565
|
-
return { success: result.success, text: responseText };
|
|
1566
|
-
} catch (error) {
|
|
1567
|
-
logger2.error("Error executing command:", error);
|
|
1568
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1569
|
-
if (callback) {
|
|
1570
|
-
await callback({
|
|
1571
|
-
text: `Failed to execute command: ${errorMessage}`,
|
|
1572
|
-
source: message.content.source
|
|
1573
|
-
});
|
|
1574
|
-
}
|
|
1575
|
-
return { success: false, error: errorMessage };
|
|
1576
|
-
}
|
|
1577
|
-
},
|
|
1578
|
-
examples: spec2.examples ?? []
|
|
1579
|
-
};
|
|
1580
|
-
// actions/processAction.ts
|
|
1581
|
-
import { composePromptFromState as composePromptFromState2, logger as logger3, ModelType as ModelType2 } from "@elizaos/core";
|
|
1582
|
-
var processActionTemplate = `You are helping extract process management parameters from user messages.
|
|
1583
|
-
|
|
1584
|
-
Recent conversation:
|
|
1585
|
-
{{recentMessages}}
|
|
1586
|
-
|
|
1587
|
-
Based on the conversation, extract the process action parameters:
|
|
1588
|
-
- action: The action to perform (list, poll, log, write, send-keys, submit, paste, kill, clear, remove)
|
|
1589
|
-
- sessionId: The session ID (required for all actions except "list")
|
|
1590
|
-
- data: Data to write (for "write" action)
|
|
1591
|
-
- keys: Array of key tokens (for "send-keys" action)
|
|
1592
|
-
- literal: Literal string to send (for "send-keys" action)
|
|
1593
|
-
- text: Text to paste (for "paste" action)
|
|
1594
|
-
- eof: Whether to close stdin after write (for "write" action)
|
|
1595
|
-
- offset: Log offset (for "log" action)
|
|
1596
|
-
- limit: Log limit (for "log" action)
|
|
1597
|
-
|
|
1598
|
-
Respond with a JSON object containing the extracted parameters:
|
|
1599
|
-
\`\`\`json
|
|
1600
|
-
{
|
|
1601
|
-
"action": "list|poll|log|write|send-keys|submit|paste|kill|clear|remove",
|
|
1602
|
-
"sessionId": "optional-session-id",
|
|
1603
|
-
"data": "optional-data",
|
|
1604
|
-
"keys": ["optional", "key", "tokens"],
|
|
1605
|
-
"literal": "optional-literal",
|
|
1606
|
-
"text": "optional-paste-text",
|
|
1607
|
-
"eof": false,
|
|
1608
|
-
"offset": 0,
|
|
1609
|
-
"limit": 100
|
|
1610
|
-
}
|
|
1611
|
-
\`\`\``;
|
|
1612
|
-
function extractJsonFromResponse(response) {
|
|
1613
|
-
const jsonMatch = response.match(/```json\s*([\s\S]*?)\s*```/);
|
|
1614
|
-
if (jsonMatch?.[1]) {
|
|
1615
|
-
try {
|
|
1616
|
-
return JSON.parse(jsonMatch[1]);
|
|
1617
|
-
} catch {}
|
|
1618
|
-
}
|
|
1619
|
-
try {
|
|
1620
|
-
const trimmed = response.trim();
|
|
1621
|
-
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
1622
|
-
return JSON.parse(trimmed);
|
|
1623
|
-
}
|
|
1624
|
-
} catch {}
|
|
1625
|
-
return null;
|
|
1626
|
-
}
|
|
1627
|
-
var processAction = {
|
|
1628
|
-
name: "MANAGE_PROCESS",
|
|
1629
|
-
similes: [
|
|
1630
|
-
"PROCESS_LIST",
|
|
1631
|
-
"PROCESS_POLL",
|
|
1632
|
-
"PROCESS_LOG",
|
|
1633
|
-
"PROCESS_WRITE",
|
|
1634
|
-
"PROCESS_KILL",
|
|
1635
|
-
"LIST_SESSIONS",
|
|
1636
|
-
"POLL_SESSION",
|
|
1637
|
-
"KILL_SESSION",
|
|
1638
|
-
"CHECK_PROCESS",
|
|
1639
|
-
"SEND_KEYS"
|
|
1640
|
-
],
|
|
1641
|
-
description: "Manage running shell/exec sessions: list, poll, log, write, send-keys, submit, paste, kill, clear, remove",
|
|
1642
|
-
validate: async (_runtime, message) => {
|
|
1643
|
-
const text = message.content.text?.toLowerCase() || "";
|
|
1644
|
-
const processKeywords = [
|
|
1645
|
-
"process",
|
|
1646
|
-
"session",
|
|
1647
|
-
"sessions",
|
|
1648
|
-
"list",
|
|
1649
|
-
"poll",
|
|
1650
|
-
"log",
|
|
1651
|
-
"write",
|
|
1652
|
-
"send-keys",
|
|
1653
|
-
"send keys",
|
|
1654
|
-
"submit",
|
|
1655
|
-
"paste",
|
|
1656
|
-
"kill",
|
|
1657
|
-
"clear",
|
|
1658
|
-
"remove",
|
|
1659
|
-
"running",
|
|
1660
|
-
"background"
|
|
1661
|
-
];
|
|
1662
|
-
const actionKeywords = [
|
|
1663
|
-
"check",
|
|
1664
|
-
"show",
|
|
1665
|
-
"get",
|
|
1666
|
-
"view",
|
|
1667
|
-
"manage",
|
|
1668
|
-
"stop",
|
|
1669
|
-
"terminate",
|
|
1670
|
-
"status"
|
|
1671
|
-
];
|
|
1672
|
-
const hasProcessKeyword = processKeywords.some((kw) => text.includes(kw));
|
|
1673
|
-
const hasActionKeyword = actionKeywords.some((kw) => text.includes(kw));
|
|
1674
|
-
return hasProcessKeyword || hasActionKeyword && text.includes("session");
|
|
1675
|
-
},
|
|
1676
|
-
handler: async (runtime, message, state) => {
|
|
1677
|
-
const shellService = runtime.getService("shell");
|
|
1678
|
-
if (!shellService) {
|
|
1679
|
-
return {
|
|
1680
|
-
success: false,
|
|
1681
|
-
text: "Shell service is not available.",
|
|
1682
|
-
error: "Shell service not found"
|
|
1683
|
-
};
|
|
1684
|
-
}
|
|
1685
|
-
const composedState = state ?? await runtime.composeState(message);
|
|
1686
|
-
const prompt = composePromptFromState2({
|
|
1687
|
-
state: composedState,
|
|
1688
|
-
template: processActionTemplate
|
|
1689
|
-
});
|
|
1690
|
-
let params = null;
|
|
1691
|
-
const text = message.content.text?.toLowerCase() || "";
|
|
1692
|
-
if (text.includes("list") && (text.includes("session") || text.includes("process"))) {
|
|
1693
|
-
params = { action: "list" };
|
|
1694
|
-
} else {
|
|
1695
|
-
try {
|
|
1696
|
-
const response = await runtime.useModel(ModelType2.TEXT_SMALL, {
|
|
1697
|
-
prompt
|
|
1698
|
-
});
|
|
1699
|
-
params = extractJsonFromResponse(String(response));
|
|
1700
|
-
} catch (error) {
|
|
1701
|
-
logger3.error("Failed to extract process parameters:", error);
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
if (!params) {
|
|
1705
|
-
params = { action: "list" };
|
|
1706
|
-
}
|
|
1707
|
-
const result = await shellService.processAction(params);
|
|
1708
|
-
return {
|
|
1709
|
-
success: result.success,
|
|
1710
|
-
text: result.message
|
|
1711
|
-
};
|
|
1712
|
-
},
|
|
1713
|
-
examples: [
|
|
1714
|
-
[
|
|
1715
|
-
{
|
|
1716
|
-
name: "{{user1}}",
|
|
1717
|
-
content: { text: "List all running processes" }
|
|
1718
|
-
},
|
|
1719
|
-
{
|
|
1720
|
-
name: "{{agentName}}",
|
|
1721
|
-
content: {
|
|
1722
|
-
text: `Here are the running sessions:
|
|
1723
|
-
calm-harbor running 5m30s :: npm install
|
|
1724
|
-
brisk-reef completed 2m15s :: git status`,
|
|
1725
|
-
action: "MANAGE_PROCESS"
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
],
|
|
1729
|
-
[
|
|
1730
|
-
{
|
|
1731
|
-
name: "{{user1}}",
|
|
1732
|
-
content: { text: "Check the status of session calm-harbor" }
|
|
1733
|
-
},
|
|
1734
|
-
{
|
|
1735
|
-
name: "{{agentName}}",
|
|
1736
|
-
content: {
|
|
1737
|
-
text: `Session calm-harbor is still running.
|
|
1738
|
-
|
|
1739
|
-
Output:
|
|
1740
|
-
npm WARN deprecated...
|
|
1741
|
-
|
|
1742
|
-
Process still running.`,
|
|
1743
|
-
action: "MANAGE_PROCESS"
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
],
|
|
1747
|
-
[
|
|
1748
|
-
{
|
|
1749
|
-
name: "{{user1}}",
|
|
1750
|
-
content: { text: "Kill the session brisk-reef" }
|
|
1751
|
-
},
|
|
1752
|
-
{
|
|
1753
|
-
name: "{{agentName}}",
|
|
1754
|
-
content: {
|
|
1755
|
-
text: "Killed session brisk-reef.",
|
|
1756
|
-
action: "MANAGE_PROCESS"
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
]
|
|
1760
|
-
]
|
|
1761
|
-
};
|
|
1762
1358
|
// approvals/allowlist.ts
|
|
1763
1359
|
import crypto2 from "node:crypto";
|
|
1764
1360
|
import fs from "node:fs";
|
|
1765
1361
|
import os from "node:os";
|
|
1766
1362
|
import path from "node:path";
|
|
1767
|
-
import { logger as
|
|
1363
|
+
import { logger as logger2 } from "@elizaos/core";
|
|
1768
1364
|
|
|
1769
1365
|
// approvals/types.ts
|
|
1770
1366
|
var DEFAULT_SAFE_BINS = [
|
|
@@ -1902,12 +1498,12 @@ function readApprovalsSnapshot() {
|
|
|
1902
1498
|
try {
|
|
1903
1499
|
parsed = JSON.parse(raw);
|
|
1904
1500
|
} catch (parseError) {
|
|
1905
|
-
|
|
1501
|
+
logger2.warn({ src: "exec-approval", parseError, filePath }, "Failed to parse approval config snapshot - file may be corrupted");
|
|
1906
1502
|
parsed = null;
|
|
1907
1503
|
}
|
|
1908
1504
|
const file = parsed?.version === 1 ? normalizeApprovals(parsed) : normalizeApprovals({ version: 1, agents: {} });
|
|
1909
1505
|
if (parsed && parsed.version !== 1) {
|
|
1910
|
-
|
|
1506
|
+
logger2.warn({ src: "exec-approval", version: parsed.version, filePath }, "Approval config snapshot has unexpected version");
|
|
1911
1507
|
}
|
|
1912
1508
|
return {
|
|
1913
1509
|
path: filePath,
|
|
@@ -1921,7 +1517,7 @@ function loadApprovals() {
|
|
|
1921
1517
|
const filePath = getApprovalFilePath();
|
|
1922
1518
|
try {
|
|
1923
1519
|
if (!fs.existsSync(filePath)) {
|
|
1924
|
-
|
|
1520
|
+
logger2.debug({ src: "exec-approval", filePath }, "Approval config file does not exist, using defaults");
|
|
1925
1521
|
return normalizeApprovals({ version: 1, agents: {} });
|
|
1926
1522
|
}
|
|
1927
1523
|
const raw = fs.readFileSync(filePath, "utf8");
|
|
@@ -1929,16 +1525,16 @@ function loadApprovals() {
|
|
|
1929
1525
|
try {
|
|
1930
1526
|
parsed = JSON.parse(raw);
|
|
1931
1527
|
} catch (parseError) {
|
|
1932
|
-
|
|
1528
|
+
logger2.error({ src: "exec-approval", parseError, filePath }, "Failed to parse approval config JSON - file may be corrupted. Using defaults.");
|
|
1933
1529
|
return normalizeApprovals({ version: 1, agents: {} });
|
|
1934
1530
|
}
|
|
1935
1531
|
if (parsed?.version !== 1) {
|
|
1936
|
-
|
|
1532
|
+
logger2.warn({ src: "exec-approval", version: parsed?.version, filePath }, "Approval config has unexpected version, using defaults");
|
|
1937
1533
|
return normalizeApprovals({ version: 1, agents: {} });
|
|
1938
1534
|
}
|
|
1939
1535
|
return normalizeApprovals(parsed);
|
|
1940
1536
|
} catch (error) {
|
|
1941
|
-
|
|
1537
|
+
logger2.error({ src: "exec-approval", error, filePath }, "Failed to load approval config - using defaults. This may indicate a permissions issue.");
|
|
1942
1538
|
return normalizeApprovals({ version: 1, agents: {} });
|
|
1943
1539
|
}
|
|
1944
1540
|
}
|
|
@@ -1954,7 +1550,7 @@ function saveApprovals(file) {
|
|
|
1954
1550
|
fs.chmodSync(filePath, 384);
|
|
1955
1551
|
} catch {}
|
|
1956
1552
|
} catch (error) {
|
|
1957
|
-
|
|
1553
|
+
logger2.error({ src: "exec-approval", error, filePath }, "Failed to save approval configuration");
|
|
1958
1554
|
throw new Error(`Failed to save approval configuration to ${filePath}: ${error}`);
|
|
1959
1555
|
}
|
|
1960
1556
|
}
|
|
@@ -1973,7 +1569,7 @@ function ensureApprovals() {
|
|
|
1973
1569
|
try {
|
|
1974
1570
|
saveApprovals(updated);
|
|
1975
1571
|
} catch (error) {
|
|
1976
|
-
|
|
1572
|
+
logger2.warn({ src: "exec-approval", error }, "Failed to save approval config during ensureApprovals - " + "returning in-memory config. Changes will not persist.");
|
|
1977
1573
|
throw error;
|
|
1978
1574
|
}
|
|
1979
1575
|
return updated;
|
|
@@ -1995,7 +1591,7 @@ function resolveApprovals(agentId, overrides) {
|
|
|
1995
1591
|
try {
|
|
1996
1592
|
file = ensureApprovals();
|
|
1997
1593
|
} catch (error) {
|
|
1998
|
-
|
|
1594
|
+
logger2.warn({ src: "exec-approval", error }, "Could not ensure approval config exists - using read-only config");
|
|
1999
1595
|
file = loadApprovals();
|
|
2000
1596
|
}
|
|
2001
1597
|
return resolveApprovalsFromFile({
|
|
@@ -2137,7 +1733,7 @@ function recordAllowlistUse(approvals, agentId, entry, command, resolvedPath) {
|
|
|
2137
1733
|
saveApprovals(approvals);
|
|
2138
1734
|
return true;
|
|
2139
1735
|
} catch (error) {
|
|
2140
|
-
|
|
1736
|
+
logger2.warn({ src: "exec-approval", error, pattern: entry.pattern }, "Failed to record allowlist usage - continuing without update");
|
|
2141
1737
|
return false;
|
|
2142
1738
|
}
|
|
2143
1739
|
}
|
|
@@ -2148,11 +1744,11 @@ function addAllowlistEntry(approvals, agentId, pattern) {
|
|
|
2148
1744
|
const allowlist = Array.isArray(existing.allowlist) ? existing.allowlist : [];
|
|
2149
1745
|
const trimmed = pattern.trim();
|
|
2150
1746
|
if (!trimmed) {
|
|
2151
|
-
|
|
1747
|
+
logger2.warn({ src: "exec-approval" }, "Attempted to add empty pattern to allowlist");
|
|
2152
1748
|
return false;
|
|
2153
1749
|
}
|
|
2154
1750
|
if (allowlist.some((entry) => entry.pattern === trimmed)) {
|
|
2155
|
-
|
|
1751
|
+
logger2.debug({ src: "exec-approval", pattern: trimmed }, "Pattern already in allowlist");
|
|
2156
1752
|
return false;
|
|
2157
1753
|
}
|
|
2158
1754
|
allowlist.push({
|
|
@@ -2164,15 +1760,19 @@ function addAllowlistEntry(approvals, agentId, pattern) {
|
|
|
2164
1760
|
approvals.agents = agents;
|
|
2165
1761
|
try {
|
|
2166
1762
|
saveApprovals(approvals);
|
|
2167
|
-
|
|
1763
|
+
logger2.info({ src: "exec-approval", pattern: trimmed, agentId: target }, "Added pattern to allowlist");
|
|
2168
1764
|
return true;
|
|
2169
1765
|
} catch (error) {
|
|
2170
|
-
|
|
1766
|
+
logger2.error({ src: "exec-approval", error, pattern: trimmed }, "Failed to save allowlist after adding entry");
|
|
2171
1767
|
return false;
|
|
2172
1768
|
}
|
|
2173
1769
|
}
|
|
2174
1770
|
function minSecurity(a, b) {
|
|
2175
|
-
const order = {
|
|
1771
|
+
const order = {
|
|
1772
|
+
deny: 0,
|
|
1773
|
+
allowlist: 1,
|
|
1774
|
+
full: 2
|
|
1775
|
+
};
|
|
2176
1776
|
return order[a] <= order[b] ? a : b;
|
|
2177
1777
|
}
|
|
2178
1778
|
function maxAsk(a, b) {
|
|
@@ -2494,7 +2094,11 @@ function analyzeShellCommand(params) {
|
|
|
2494
2094
|
}
|
|
2495
2095
|
const segments2 = parseSegmentsFromParts(pipelineSplit.segments, params.cwd, params.env);
|
|
2496
2096
|
if (!segments2) {
|
|
2497
|
-
return {
|
|
2097
|
+
return {
|
|
2098
|
+
ok: false,
|
|
2099
|
+
reason: "unable to parse shell segment",
|
|
2100
|
+
segments: []
|
|
2101
|
+
};
|
|
2498
2102
|
}
|
|
2499
2103
|
chains.push(segments2);
|
|
2500
2104
|
allSegments.push(...segments2);
|
|
@@ -2525,7 +2129,11 @@ function analyzeWindowsCommand(params) {
|
|
|
2525
2129
|
}
|
|
2526
2130
|
const argv = tokenizeWindowsSegment(params.command);
|
|
2527
2131
|
if (!argv || argv.length === 0) {
|
|
2528
|
-
return {
|
|
2132
|
+
return {
|
|
2133
|
+
ok: false,
|
|
2134
|
+
reason: "unable to parse windows command",
|
|
2135
|
+
segments: []
|
|
2136
|
+
};
|
|
2529
2137
|
}
|
|
2530
2138
|
return {
|
|
2531
2139
|
ok: true,
|
|
@@ -2780,7 +2388,10 @@ function evaluateExecAllowlist(params) {
|
|
|
2780
2388
|
skillBins: params.skillBins,
|
|
2781
2389
|
autoAllowSkills: params.autoAllowSkills
|
|
2782
2390
|
});
|
|
2783
|
-
return {
|
|
2391
|
+
return {
|
|
2392
|
+
allowlistSatisfied: result.satisfied,
|
|
2393
|
+
allowlistMatches: result.matches
|
|
2394
|
+
};
|
|
2784
2395
|
}
|
|
2785
2396
|
function evaluateShellAllowlist(params) {
|
|
2786
2397
|
const chainParts = isWindowsPlatform(params.platform) ? null : splitCommandChain(params.command);
|
|
@@ -2861,7 +2472,7 @@ function requiresExecApproval(params) {
|
|
|
2861
2472
|
return params.ask === "always" || params.ask === "on-miss" && params.security === "allowlist" && (!params.analysisOk || !params.allowlistSatisfied);
|
|
2862
2473
|
}
|
|
2863
2474
|
// approvals/service.ts
|
|
2864
|
-
import { logger as
|
|
2475
|
+
import { logger as logger3, Service } from "@elizaos/core";
|
|
2865
2476
|
var EXEC_APPROVAL_OPTIONS = [
|
|
2866
2477
|
{ name: "allow-once", description: "Allow this one time" },
|
|
2867
2478
|
{ name: "allow-always", description: "Always allow this" },
|
|
@@ -2884,7 +2495,7 @@ class ExecApprovalService extends Service {
|
|
|
2884
2495
|
try {
|
|
2885
2496
|
service.approvalConfig = resolveApprovals(runtime.agentId);
|
|
2886
2497
|
} catch (error) {
|
|
2887
|
-
|
|
2498
|
+
logger3.error({ src: "service:exec_approval", error, agentId: runtime.agentId }, "Failed to load approval config during startup - using in-memory defaults. " + "Approvals may not persist. Check file permissions for ~/.eliza/exec-approvals.json");
|
|
2888
2499
|
service.approvalConfig = {
|
|
2889
2500
|
path: "",
|
|
2890
2501
|
socketPath: "",
|
|
@@ -2905,11 +2516,11 @@ class ExecApprovalService extends Service {
|
|
|
2905
2516
|
file: { version: 1, agents: {} }
|
|
2906
2517
|
};
|
|
2907
2518
|
}
|
|
2908
|
-
|
|
2519
|
+
logger3.info({ src: "service:exec_approval", agentId: runtime.agentId }, "ExecApprovalService started");
|
|
2909
2520
|
return service;
|
|
2910
2521
|
}
|
|
2911
2522
|
async stop() {
|
|
2912
|
-
|
|
2523
|
+
logger3.debug({ src: "service:exec_approval" }, "ExecApprovalService stopped");
|
|
2913
2524
|
}
|
|
2914
2525
|
loadConfig(agentId) {
|
|
2915
2526
|
this.approvalConfig = resolveApprovals(agentId ?? this.runtime?.agentId);
|
|
@@ -2981,7 +2592,7 @@ class ExecApprovalService extends Service {
|
|
|
2981
2592
|
}
|
|
2982
2593
|
}
|
|
2983
2594
|
if (recordingFailed) {
|
|
2984
|
-
|
|
2595
|
+
logger3.debug({ src: "service:exec_approval", command: params.command }, "Some allowlist usage records failed to save - command will still proceed");
|
|
2985
2596
|
}
|
|
2986
2597
|
return {
|
|
2987
2598
|
allowed: true,
|
|
@@ -3037,7 +2648,7 @@ class ExecApprovalService extends Service {
|
|
|
3037
2648
|
async requestApproval(request) {
|
|
3038
2649
|
const approvalService = this.runtime?.getService("approval");
|
|
3039
2650
|
if (!approvalService) {
|
|
3040
|
-
|
|
2651
|
+
logger3.warn({ src: "service:exec_approval" }, "ApprovalService not available, denying by default");
|
|
3041
2652
|
return {
|
|
3042
2653
|
decision: "deny",
|
|
3043
2654
|
timedOut: false
|
|
@@ -3085,7 +2696,7 @@ class ExecApprovalService extends Service {
|
|
|
3085
2696
|
async requestApprovalAsync(request, callbacks) {
|
|
3086
2697
|
const approvalService = this.runtime?.getService("approval");
|
|
3087
2698
|
if (!approvalService) {
|
|
3088
|
-
|
|
2699
|
+
logger3.warn({ src: "service:exec_approval" }, "ApprovalService not available");
|
|
3089
2700
|
if (callbacks?.onDenied) {
|
|
3090
2701
|
await callbacks.onDenied();
|
|
3091
2702
|
}
|
|
@@ -3156,13 +2767,14 @@ class ExecApprovalService extends Service {
|
|
|
3156
2767
|
}
|
|
3157
2768
|
async getPendingApprovals(roomId) {
|
|
3158
2769
|
if (!this.runtime) {
|
|
3159
|
-
|
|
2770
|
+
logger3.warn({ src: "service:exec_approval" }, "Cannot get pending approvals - runtime not available");
|
|
3160
2771
|
return [];
|
|
3161
2772
|
}
|
|
3162
2773
|
try {
|
|
3163
2774
|
const tasks = await this.runtime.getTasks({
|
|
3164
2775
|
roomId,
|
|
3165
|
-
tags: ["AWAITING_CHOICE", "EXEC"]
|
|
2776
|
+
tags: ["AWAITING_CHOICE", "EXEC"],
|
|
2777
|
+
agentIds: [this.runtime.agentId]
|
|
3166
2778
|
});
|
|
3167
2779
|
if (!tasks)
|
|
3168
2780
|
return [];
|
|
@@ -3180,7 +2792,7 @@ class ExecApprovalService extends Service {
|
|
|
3180
2792
|
};
|
|
3181
2793
|
});
|
|
3182
2794
|
} catch (error) {
|
|
3183
|
-
|
|
2795
|
+
logger3.error({ src: "service:exec_approval", error, roomId }, "Failed to get pending approvals");
|
|
3184
2796
|
return [];
|
|
3185
2797
|
}
|
|
3186
2798
|
}
|
|
@@ -3198,19 +2810,21 @@ function mapOptionToDecision(option) {
|
|
|
3198
2810
|
// providers/shellHistoryProvider.ts
|
|
3199
2811
|
import {
|
|
3200
2812
|
addHeader,
|
|
3201
|
-
logger as
|
|
2813
|
+
logger as logger4
|
|
3202
2814
|
} from "@elizaos/core";
|
|
3203
2815
|
var MAX_OUTPUT_LENGTH = 8000;
|
|
3204
2816
|
var TRUNCATE_SEGMENT_LENGTH = 4000;
|
|
3205
|
-
var
|
|
2817
|
+
var spec2 = requireProviderSpec("SHELL_HISTORY");
|
|
3206
2818
|
var shellHistoryProvider = {
|
|
3207
|
-
name:
|
|
2819
|
+
name: spec2.name,
|
|
3208
2820
|
description: "Provides recent shell command history, current working directory, and file operations within the restricted environment",
|
|
2821
|
+
descriptionCompressed: "Recent shell history, cwd, and file ops in restricted env.",
|
|
3209
2822
|
position: 99,
|
|
2823
|
+
dynamic: true,
|
|
3210
2824
|
get: async (runtime, message, _state) => {
|
|
3211
2825
|
const shellService = runtime.getService("shell");
|
|
3212
2826
|
if (!shellService) {
|
|
3213
|
-
|
|
2827
|
+
logger4.warn("[shellHistoryProvider] Shell service not found");
|
|
3214
2828
|
return {
|
|
3215
2829
|
values: {
|
|
3216
2830
|
shellHistory: "Shell service is not available",
|
|
@@ -3310,15 +2924,81 @@ ${addHeader("# Shell History (Last 10)", historyText)}${fileOpsText}`;
|
|
|
3310
2924
|
};
|
|
3311
2925
|
}
|
|
3312
2926
|
};
|
|
2927
|
+
// providers/terminalUsage.ts
|
|
2928
|
+
import {
|
|
2929
|
+
validateActionKeywords,
|
|
2930
|
+
validateActionRegex
|
|
2931
|
+
} from "@elizaos/core";
|
|
2932
|
+
var terminalUsageProvider = {
|
|
2933
|
+
name: "terminalUsage",
|
|
2934
|
+
description: "Terminal usage instructions",
|
|
2935
|
+
descriptionCompressed: "Terminal usage instructions.",
|
|
2936
|
+
dynamic: true,
|
|
2937
|
+
relevanceKeywords: [
|
|
2938
|
+
"terminalusage",
|
|
2939
|
+
"terminalusageprovider",
|
|
2940
|
+
"plugin",
|
|
2941
|
+
"shell",
|
|
2942
|
+
"status",
|
|
2943
|
+
"state",
|
|
2944
|
+
"context",
|
|
2945
|
+
"info",
|
|
2946
|
+
"details",
|
|
2947
|
+
"chat",
|
|
2948
|
+
"conversation",
|
|
2949
|
+
"agent",
|
|
2950
|
+
"room",
|
|
2951
|
+
"channel"
|
|
2952
|
+
],
|
|
2953
|
+
get: async (_runtime, _message, _state) => {
|
|
2954
|
+
const __providerKeywords = [
|
|
2955
|
+
"terminalusage",
|
|
2956
|
+
"terminalusageprovider",
|
|
2957
|
+
"plugin",
|
|
2958
|
+
"shell",
|
|
2959
|
+
"status",
|
|
2960
|
+
"state",
|
|
2961
|
+
"context",
|
|
2962
|
+
"info",
|
|
2963
|
+
"details",
|
|
2964
|
+
"chat",
|
|
2965
|
+
"conversation",
|
|
2966
|
+
"agent",
|
|
2967
|
+
"room",
|
|
2968
|
+
"channel"
|
|
2969
|
+
];
|
|
2970
|
+
const __providerRegex = new RegExp(`\\b(${__providerKeywords.join("|")})\\b`, "i");
|
|
2971
|
+
const __recentMessages = _state?.recentMessagesData || [];
|
|
2972
|
+
const __isRelevant = validateActionKeywords(_message, __recentMessages, __providerKeywords) || validateActionRegex(_message, __recentMessages, __providerRegex);
|
|
2973
|
+
if (!__isRelevant) {
|
|
2974
|
+
return { text: "" };
|
|
2975
|
+
}
|
|
2976
|
+
const settings = _runtime.character?.settings;
|
|
2977
|
+
if (settings?.DISABLE_TERMINAL) {
|
|
2978
|
+
return { text: "" };
|
|
2979
|
+
}
|
|
2980
|
+
return {
|
|
2981
|
+
text: [
|
|
2982
|
+
"## Terminal",
|
|
2983
|
+
"",
|
|
2984
|
+
"You can run shell commands in the user's embedded terminal using the SHELL_COMMAND action.",
|
|
2985
|
+
"Use this when the user asks you to run a command, execute a script, install packages, etc.",
|
|
2986
|
+
"The terminal auto-opens and shows the command output in real time."
|
|
2987
|
+
].join(`
|
|
2988
|
+
`)
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
};
|
|
2992
|
+
|
|
3313
2993
|
// services/shellService.ts
|
|
3314
2994
|
import path6 from "node:path";
|
|
3315
|
-
import { logger as
|
|
2995
|
+
import { logger as logger7, Service as Service2 } from "@elizaos/core";
|
|
3316
2996
|
import spawn2 from "cross-spawn";
|
|
3317
2997
|
|
|
3318
2998
|
// utils/config.ts
|
|
3319
2999
|
import fs3 from "node:fs";
|
|
3320
3000
|
import path3 from "node:path";
|
|
3321
|
-
import { logger as
|
|
3001
|
+
import { logger as logger5 } from "@elizaos/core";
|
|
3322
3002
|
import { z } from "zod";
|
|
3323
3003
|
var configSchema = z.object({
|
|
3324
3004
|
enabled: z.boolean(),
|
|
@@ -3387,7 +3067,7 @@ function loadShellConfig() {
|
|
|
3387
3067
|
throw new Error(`SHELL_ALLOWED_DIRECTORY is not a directory: ${allowedDirectory}`);
|
|
3388
3068
|
}
|
|
3389
3069
|
config.allowedDirectory = path3.resolve(allowedDirectory);
|
|
3390
|
-
|
|
3070
|
+
logger5.info(`Shell plugin enabled with allowed directory: ${config.allowedDirectory}, ` + `background: ${allowBackground}, timeout: ${timeout}ms`);
|
|
3391
3071
|
} catch (error) {
|
|
3392
3072
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
3393
3073
|
throw new Error(`SHELL_ALLOWED_DIRECTORY does not exist: ${allowedDirectory}`);
|
|
@@ -3398,13 +3078,13 @@ function loadShellConfig() {
|
|
|
3398
3078
|
}
|
|
3399
3079
|
// utils/pathUtils.ts
|
|
3400
3080
|
import path4 from "node:path";
|
|
3401
|
-
import { logger as
|
|
3081
|
+
import { logger as logger6 } from "@elizaos/core";
|
|
3402
3082
|
function validatePath(commandPath, allowedDir, currentDir) {
|
|
3403
3083
|
const resolvedPath = path4.resolve(currentDir, commandPath);
|
|
3404
3084
|
const normalizedPath = path4.normalize(resolvedPath);
|
|
3405
3085
|
const normalizedAllowed = path4.normalize(allowedDir);
|
|
3406
3086
|
if (!normalizedPath.startsWith(normalizedAllowed)) {
|
|
3407
|
-
|
|
3087
|
+
logger6.warn(`Path validation failed: ${normalizedPath} is outside allowed directory ${normalizedAllowed}`);
|
|
3408
3088
|
return null;
|
|
3409
3089
|
}
|
|
3410
3090
|
return normalizedPath;
|
|
@@ -3414,19 +3094,19 @@ function isSafeCommand(command) {
|
|
|
3414
3094
|
const dangerousPatterns = [/\$\(/g, /`[^']*`/g, /\|\s*sudo/g, /;\s*sudo/g, /&\s*&/g, /\|\s*\|/g];
|
|
3415
3095
|
for (const pattern of pathTraversalPatterns) {
|
|
3416
3096
|
if (pattern.test(command)) {
|
|
3417
|
-
|
|
3097
|
+
logger6.warn(`Path traversal detected in command: ${command}`);
|
|
3418
3098
|
return false;
|
|
3419
3099
|
}
|
|
3420
3100
|
}
|
|
3421
3101
|
for (const pattern of dangerousPatterns) {
|
|
3422
3102
|
if (pattern.test(command)) {
|
|
3423
|
-
|
|
3103
|
+
logger6.warn(`Dangerous pattern detected in command: ${command}`);
|
|
3424
3104
|
return false;
|
|
3425
3105
|
}
|
|
3426
3106
|
}
|
|
3427
3107
|
const pipeCount = (command.match(/\|/g) || []).length;
|
|
3428
3108
|
if (pipeCount > 1) {
|
|
3429
|
-
|
|
3109
|
+
logger6.warn(`Multiple pipes detected in command: ${command}`);
|
|
3430
3110
|
return false;
|
|
3431
3111
|
}
|
|
3432
3112
|
return true;
|
|
@@ -3456,8 +3136,8 @@ var ESC = "\x1B";
|
|
|
3456
3136
|
var CR = "\r";
|
|
3457
3137
|
var TAB = "\t";
|
|
3458
3138
|
var BACKSPACE = "";
|
|
3459
|
-
var
|
|
3460
|
-
var
|
|
3139
|
+
var BRACKETED_PASTE_START2 = `${ESC}[200~`;
|
|
3140
|
+
var BRACKETED_PASTE_END2 = `${ESC}[201~`;
|
|
3461
3141
|
function escapeRegExp(value) {
|
|
3462
3142
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3463
3143
|
}
|
|
@@ -3536,7 +3216,7 @@ var modifiableNamedKeys = new Set([
|
|
|
3536
3216
|
"del",
|
|
3537
3217
|
"dc"
|
|
3538
3218
|
]);
|
|
3539
|
-
function
|
|
3219
|
+
function encodeKeySequence2(request) {
|
|
3540
3220
|
const warnings = [];
|
|
3541
3221
|
let data = "";
|
|
3542
3222
|
if (request.literal) {
|
|
@@ -3559,11 +3239,11 @@ function encodeKeySequence(request) {
|
|
|
3559
3239
|
}
|
|
3560
3240
|
return { data, warnings };
|
|
3561
3241
|
}
|
|
3562
|
-
function
|
|
3242
|
+
function encodePaste2(text, bracketed = true) {
|
|
3563
3243
|
if (!bracketed) {
|
|
3564
3244
|
return text;
|
|
3565
3245
|
}
|
|
3566
|
-
return `${
|
|
3246
|
+
return `${BRACKETED_PASTE_START2}${text}${BRACKETED_PASTE_END2}`;
|
|
3567
3247
|
}
|
|
3568
3248
|
function encodeKeyToken(raw, warnings) {
|
|
3569
3249
|
const token = raw.trim();
|
|
@@ -3700,7 +3380,7 @@ function parseHexByte(raw) {
|
|
|
3700
3380
|
return value;
|
|
3701
3381
|
}
|
|
3702
3382
|
var DSR_PATTERN = new RegExp(`${ESC}\\[\\??6n`, "g");
|
|
3703
|
-
function
|
|
3383
|
+
function stripDsrRequests2(input) {
|
|
3704
3384
|
let requests = 0;
|
|
3705
3385
|
const cleaned = input.replace(DSR_PATTERN, () => {
|
|
3706
3386
|
requests += 1;
|
|
@@ -3708,7 +3388,7 @@ function stripDsrRequests(input) {
|
|
|
3708
3388
|
});
|
|
3709
3389
|
return { cleaned, requests };
|
|
3710
3390
|
}
|
|
3711
|
-
function
|
|
3391
|
+
function buildCursorPositionResponse2(row = 1, col = 1) {
|
|
3712
3392
|
return `\x1B[${row};${col}R`;
|
|
3713
3393
|
}
|
|
3714
3394
|
|
|
@@ -3743,7 +3423,7 @@ function resolveShellFromPath(name) {
|
|
|
3743
3423
|
}
|
|
3744
3424
|
return;
|
|
3745
3425
|
}
|
|
3746
|
-
function
|
|
3426
|
+
function getShellConfig2() {
|
|
3747
3427
|
if (process.platform === "win32") {
|
|
3748
3428
|
return {
|
|
3749
3429
|
shell: resolvePowerShellPath(),
|
|
@@ -3765,7 +3445,7 @@ function getShellConfig() {
|
|
|
3765
3445
|
const shell = envShell && envShell.length > 0 ? envShell : "sh";
|
|
3766
3446
|
return { shell, args: ["-c"] };
|
|
3767
3447
|
}
|
|
3768
|
-
function
|
|
3448
|
+
function sanitizeBinaryOutput2(text) {
|
|
3769
3449
|
const scrubbed = text.replace(/[\p{Format}\p{Surrogate}]/gu, "");
|
|
3770
3450
|
if (!scrubbed) {
|
|
3771
3451
|
return scrubbed;
|
|
@@ -3787,7 +3467,7 @@ function sanitizeBinaryOutput(text) {
|
|
|
3787
3467
|
}
|
|
3788
3468
|
return chunks.join("");
|
|
3789
3469
|
}
|
|
3790
|
-
function
|
|
3470
|
+
function killProcessTree2(pid) {
|
|
3791
3471
|
if (process.platform === "win32") {
|
|
3792
3472
|
try {
|
|
3793
3473
|
spawn("taskkill", ["/F", "/T", "/PID", String(pid)], {
|
|
@@ -3805,13 +3485,13 @@ function killProcessTree(pid) {
|
|
|
3805
3485
|
} catch {}
|
|
3806
3486
|
}
|
|
3807
3487
|
}
|
|
3808
|
-
function
|
|
3488
|
+
function killSession2(session) {
|
|
3809
3489
|
const pid = session.pid ?? session.child?.pid;
|
|
3810
3490
|
if (pid) {
|
|
3811
|
-
|
|
3491
|
+
killProcessTree2(pid);
|
|
3812
3492
|
}
|
|
3813
3493
|
}
|
|
3814
|
-
function
|
|
3494
|
+
function coerceEnv2(env) {
|
|
3815
3495
|
const record = {};
|
|
3816
3496
|
if (!env) {
|
|
3817
3497
|
return record;
|
|
@@ -3823,7 +3503,7 @@ function coerceEnv(env) {
|
|
|
3823
3503
|
}
|
|
3824
3504
|
return record;
|
|
3825
3505
|
}
|
|
3826
|
-
function
|
|
3506
|
+
function resolveWorkdir2(workdir, warnings) {
|
|
3827
3507
|
const current = safeCwd();
|
|
3828
3508
|
const fallback = current ?? homedir();
|
|
3829
3509
|
try {
|
|
@@ -3843,13 +3523,13 @@ function safeCwd() {
|
|
|
3843
3523
|
return null;
|
|
3844
3524
|
}
|
|
3845
3525
|
}
|
|
3846
|
-
function
|
|
3526
|
+
function clampNumber2(value, defaultValue, min, max) {
|
|
3847
3527
|
if (value === undefined || Number.isNaN(value)) {
|
|
3848
3528
|
return defaultValue;
|
|
3849
3529
|
}
|
|
3850
3530
|
return Math.min(Math.max(value, min), max);
|
|
3851
3531
|
}
|
|
3852
|
-
function
|
|
3532
|
+
function readEnvInt2(key) {
|
|
3853
3533
|
const raw = process.env[key];
|
|
3854
3534
|
if (!raw) {
|
|
3855
3535
|
return;
|
|
@@ -3857,14 +3537,14 @@ function readEnvInt(key) {
|
|
|
3857
3537
|
const parsed = Number.parseInt(raw, 10);
|
|
3858
3538
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
3859
3539
|
}
|
|
3860
|
-
function
|
|
3540
|
+
function chunkString2(input, limit = CHUNK_LIMIT) {
|
|
3861
3541
|
const chunks = [];
|
|
3862
3542
|
for (let i = 0;i < input.length; i += limit) {
|
|
3863
3543
|
chunks.push(input.slice(i, i + limit));
|
|
3864
3544
|
}
|
|
3865
3545
|
return chunks;
|
|
3866
3546
|
}
|
|
3867
|
-
function
|
|
3547
|
+
function sliceUtf16Safe2(str, start, end) {
|
|
3868
3548
|
const effectiveEnd = end ?? str.length;
|
|
3869
3549
|
if (start < 0) {
|
|
3870
3550
|
const adjustedStart = Math.max(0, str.length + start);
|
|
@@ -3872,14 +3552,14 @@ function sliceUtf16Safe(str, start, end) {
|
|
|
3872
3552
|
}
|
|
3873
3553
|
return str.slice(start, effectiveEnd);
|
|
3874
3554
|
}
|
|
3875
|
-
function
|
|
3555
|
+
function truncateMiddle2(str, max) {
|
|
3876
3556
|
if (str.length <= max) {
|
|
3877
3557
|
return str;
|
|
3878
3558
|
}
|
|
3879
3559
|
const half = Math.floor((max - 3) / 2);
|
|
3880
|
-
return `${
|
|
3560
|
+
return `${sliceUtf16Safe2(str, 0, half)}...${sliceUtf16Safe2(str, -half)}`;
|
|
3881
3561
|
}
|
|
3882
|
-
function
|
|
3562
|
+
function sliceLogLines2(text, offset, limit) {
|
|
3883
3563
|
if (!text) {
|
|
3884
3564
|
return { slice: "", totalLines: 0, totalChars: 0 };
|
|
3885
3565
|
}
|
|
@@ -3901,7 +3581,7 @@ function sliceLogLines(text, offset, limit) {
|
|
|
3901
3581
|
return { slice: lines.slice(start, end).join(`
|
|
3902
3582
|
`), totalLines, totalChars };
|
|
3903
3583
|
}
|
|
3904
|
-
function
|
|
3584
|
+
function deriveSessionName2(command) {
|
|
3905
3585
|
const tokens = tokenizeCommand(command);
|
|
3906
3586
|
if (tokens.length === 0) {
|
|
3907
3587
|
return;
|
|
@@ -3914,7 +3594,7 @@ function deriveSessionName(command) {
|
|
|
3914
3594
|
if (!target) {
|
|
3915
3595
|
return verb;
|
|
3916
3596
|
}
|
|
3917
|
-
const cleaned =
|
|
3597
|
+
const cleaned = truncateMiddle2(stripQuotes(target), 48);
|
|
3918
3598
|
return `${stripQuotes(verb)} ${cleaned}`;
|
|
3919
3599
|
}
|
|
3920
3600
|
function tokenizeCommand(command) {
|
|
@@ -3928,7 +3608,7 @@ function stripQuotes(value) {
|
|
|
3928
3608
|
}
|
|
3929
3609
|
return trimmed;
|
|
3930
3610
|
}
|
|
3931
|
-
function
|
|
3611
|
+
function formatDuration2(ms) {
|
|
3932
3612
|
if (ms < 1000) {
|
|
3933
3613
|
return `${ms}ms`;
|
|
3934
3614
|
}
|
|
@@ -3940,7 +3620,7 @@ function formatDuration(ms) {
|
|
|
3940
3620
|
const rem = seconds % 60;
|
|
3941
3621
|
return `${minutes}m${rem.toString().padStart(2, "0")}s`;
|
|
3942
3622
|
}
|
|
3943
|
-
function
|
|
3623
|
+
function pad2(str, width) {
|
|
3944
3624
|
if (str.length >= width) {
|
|
3945
3625
|
return str;
|
|
3946
3626
|
}
|
|
@@ -4359,9 +4039,9 @@ function stopSweeper() {
|
|
|
4359
4039
|
}
|
|
4360
4040
|
|
|
4361
4041
|
// services/shellService.ts
|
|
4362
|
-
var DEFAULT_MAX_OUTPUT =
|
|
4363
|
-
var DEFAULT_PENDING_MAX_OUTPUT =
|
|
4364
|
-
var DEFAULT_BACKGROUND_MS =
|
|
4042
|
+
var DEFAULT_MAX_OUTPUT = clampNumber2(readEnvInt2("SHELL_MAX_OUTPUT_CHARS"), 200000, 1000, 200000);
|
|
4043
|
+
var DEFAULT_PENDING_MAX_OUTPUT = clampNumber2(readEnvInt2("SHELL_PENDING_MAX_OUTPUT_CHARS"), 200000, 1000, 200000);
|
|
4044
|
+
var DEFAULT_BACKGROUND_MS = clampNumber2(readEnvInt2("SHELL_BACKGROUND_MS"), 1e4, 10, 120000);
|
|
4365
4045
|
var DEFAULT_TIMEOUT_SEC = 1800;
|
|
4366
4046
|
|
|
4367
4047
|
class ShellService extends Service2 {
|
|
@@ -4379,21 +4059,21 @@ class ShellService extends Service2 {
|
|
|
4379
4059
|
}
|
|
4380
4060
|
static async start(runtime) {
|
|
4381
4061
|
const instance = new ShellService(runtime);
|
|
4382
|
-
|
|
4062
|
+
logger7.info("Shell service initialized with PTY, background execution, and history tracking");
|
|
4383
4063
|
return instance;
|
|
4384
4064
|
}
|
|
4385
4065
|
async stop() {
|
|
4386
4066
|
const runningSessions2 = listRunningSessions();
|
|
4387
4067
|
for (const session of runningSessions2) {
|
|
4388
4068
|
try {
|
|
4389
|
-
|
|
4390
|
-
|
|
4069
|
+
killSession2(session);
|
|
4070
|
+
logger7.debug(`Killed shell session: ${session.id}`);
|
|
4391
4071
|
} catch (err) {
|
|
4392
|
-
|
|
4072
|
+
logger7.warn(`Failed to kill shell session ${session.id}: ${err}`);
|
|
4393
4073
|
}
|
|
4394
4074
|
}
|
|
4395
4075
|
this.commandHistory.clear();
|
|
4396
|
-
|
|
4076
|
+
logger7.info(`Shell service stopped, cleaned up ${runningSessions2.length} running sessions`);
|
|
4397
4077
|
}
|
|
4398
4078
|
get capabilityDescription() {
|
|
4399
4079
|
return "Execute shell commands with PTY support, background execution, and session management";
|
|
@@ -4412,6 +4092,42 @@ class ShellService extends Service2 {
|
|
|
4412
4092
|
executedIn: this.currentDirectory
|
|
4413
4093
|
};
|
|
4414
4094
|
}
|
|
4095
|
+
if (this.runtime && this.runtime.sandboxMode) {
|
|
4096
|
+
const hostApiUrl = this.runtime.getSetting("SANDBOX_HOST_API_URL") ?? "http://localhost:2138";
|
|
4097
|
+
const runtimeFetch = this.runtime.fetch ?? globalThis.fetch;
|
|
4098
|
+
logger7.info(`[shell:sandbox] routing exec to ${hostApiUrl}: ${command.substring(0, 100)}`);
|
|
4099
|
+
try {
|
|
4100
|
+
const response = await runtimeFetch(`${hostApiUrl}/api/sandbox/exec`, {
|
|
4101
|
+
method: "POST",
|
|
4102
|
+
headers: { "Content-Type": "application/json" },
|
|
4103
|
+
body: JSON.stringify({
|
|
4104
|
+
command,
|
|
4105
|
+
workdir: this.currentDirectory,
|
|
4106
|
+
timeoutMs: 30000
|
|
4107
|
+
})
|
|
4108
|
+
});
|
|
4109
|
+
const result2 = await response.json();
|
|
4110
|
+
logger7.info(`[shell:sandbox] exec completed: exit=${result2.exitCode} duration=${result2.durationMs}ms`);
|
|
4111
|
+
return {
|
|
4112
|
+
success: result2.exitCode === 0,
|
|
4113
|
+
stdout: result2.stdout,
|
|
4114
|
+
stderr: result2.stderr,
|
|
4115
|
+
exitCode: result2.exitCode,
|
|
4116
|
+
executedIn: this.currentDirectory
|
|
4117
|
+
};
|
|
4118
|
+
} catch (err) {
|
|
4119
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4120
|
+
logger7.error(`[shell:sandbox] exec failed: ${errMsg}`);
|
|
4121
|
+
return {
|
|
4122
|
+
success: false,
|
|
4123
|
+
stdout: "",
|
|
4124
|
+
stderr: `Sandbox exec failed: ${errMsg}`,
|
|
4125
|
+
exitCode: 1,
|
|
4126
|
+
error: "Sandbox remote execution failed",
|
|
4127
|
+
executedIn: this.currentDirectory
|
|
4128
|
+
};
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4415
4131
|
const trimmedCommand = command.trim();
|
|
4416
4132
|
if (!isSafeCommand(trimmedCommand)) {
|
|
4417
4133
|
return {
|
|
@@ -4490,10 +4206,10 @@ class ShellService extends Service2 {
|
|
|
4490
4206
|
if (!allowBackground && (backgroundRequested || yieldRequested)) {
|
|
4491
4207
|
warnings.push("Warning: background execution is disabled; running synchronously.");
|
|
4492
4208
|
}
|
|
4493
|
-
const yieldWindow = allowBackground ? backgroundRequested ? 0 :
|
|
4209
|
+
const yieldWindow = allowBackground ? backgroundRequested ? 0 : clampNumber2(options.yieldMs ?? defaultBackgroundMs, defaultBackgroundMs, 10, 120000) : null;
|
|
4494
4210
|
const rawWorkdir = options.workdir?.trim() || this.currentDirectory || process.cwd();
|
|
4495
|
-
const workdir =
|
|
4496
|
-
const baseEnv =
|
|
4211
|
+
const workdir = resolveWorkdir2(rawWorkdir, warnings);
|
|
4212
|
+
const baseEnv = coerceEnv2(process.env);
|
|
4497
4213
|
const mergedEnv = options.env ? { ...baseEnv, ...options.env } : baseEnv;
|
|
4498
4214
|
const timeoutSec = typeof options.timeout === "number" && options.timeout > 0 ? options.timeout : DEFAULT_TIMEOUT_SEC;
|
|
4499
4215
|
const usePty = options.pty === true;
|
|
@@ -4603,7 +4319,7 @@ class ShellService extends Service2 {
|
|
|
4603
4319
|
runtimeMs: Date.now() - s.startedAt,
|
|
4604
4320
|
cwd: s.cwd,
|
|
4605
4321
|
command: s.command,
|
|
4606
|
-
name:
|
|
4322
|
+
name: deriveSessionName2(s.command),
|
|
4607
4323
|
tail: s.tail,
|
|
4608
4324
|
truncated: s.truncated
|
|
4609
4325
|
}));
|
|
@@ -4615,7 +4331,7 @@ class ShellService extends Service2 {
|
|
|
4615
4331
|
runtimeMs: s.endedAt - s.startedAt,
|
|
4616
4332
|
cwd: s.cwd,
|
|
4617
4333
|
command: s.command,
|
|
4618
|
-
name:
|
|
4334
|
+
name: deriveSessionName2(s.command),
|
|
4619
4335
|
tail: s.tail,
|
|
4620
4336
|
truncated: s.truncated,
|
|
4621
4337
|
exitCode: s.exitCode ?? undefined,
|
|
@@ -4623,8 +4339,8 @@ class ShellService extends Service2 {
|
|
|
4623
4339
|
}));
|
|
4624
4340
|
const sessions = [...running, ...finished2].slice().sort((a, b) => b.startedAt - a.startedAt);
|
|
4625
4341
|
const lines = sessions.map((s) => {
|
|
4626
|
-
const label = s.name ?
|
|
4627
|
-
return `${s.sessionId} ${
|
|
4342
|
+
const label = s.name ? truncateMiddle2(s.name, 80) : truncateMiddle2(s.command, 120);
|
|
4343
|
+
return `${s.sessionId} ${pad2(s.status, 9)} ${formatDuration2(s.runtimeMs)} :: ${label}`;
|
|
4628
4344
|
});
|
|
4629
4345
|
return {
|
|
4630
4346
|
success: true,
|
|
@@ -4657,7 +4373,7 @@ Process exited with ${scopedFinished.exitSignal ? `signal ${scopedFinished.exitS
|
|
|
4657
4373
|
sessionId: params.sessionId,
|
|
4658
4374
|
exitCode: scopedFinished.exitCode ?? undefined,
|
|
4659
4375
|
aggregated: scopedFinished.aggregated,
|
|
4660
|
-
name:
|
|
4376
|
+
name: deriveSessionName2(scopedFinished.command)
|
|
4661
4377
|
}
|
|
4662
4378
|
};
|
|
4663
4379
|
}
|
|
@@ -4695,7 +4411,7 @@ Process still running.`),
|
|
|
4695
4411
|
sessionId: params.sessionId,
|
|
4696
4412
|
exitCode: exited ? exitCode : undefined,
|
|
4697
4413
|
aggregated: scopedSession.aggregated,
|
|
4698
|
-
name:
|
|
4414
|
+
name: deriveSessionName2(scopedSession.command)
|
|
4699
4415
|
}
|
|
4700
4416
|
};
|
|
4701
4417
|
}
|
|
@@ -4707,7 +4423,7 @@ Process still running.`),
|
|
|
4707
4423
|
message: `Session ${params.sessionId} is not backgrounded.`
|
|
4708
4424
|
};
|
|
4709
4425
|
}
|
|
4710
|
-
const { slice, totalLines, totalChars } =
|
|
4426
|
+
const { slice, totalLines, totalChars } = sliceLogLines2(scopedSession.aggregated, params.offset, params.limit);
|
|
4711
4427
|
return {
|
|
4712
4428
|
success: true,
|
|
4713
4429
|
message: slice || "(no output yet)",
|
|
@@ -4717,12 +4433,12 @@ Process still running.`),
|
|
|
4717
4433
|
totalLines,
|
|
4718
4434
|
totalChars,
|
|
4719
4435
|
truncated: scopedSession.truncated,
|
|
4720
|
-
name:
|
|
4436
|
+
name: deriveSessionName2(scopedSession.command)
|
|
4721
4437
|
}
|
|
4722
4438
|
};
|
|
4723
4439
|
}
|
|
4724
4440
|
if (scopedFinished) {
|
|
4725
|
-
const { slice, totalLines, totalChars } =
|
|
4441
|
+
const { slice, totalLines, totalChars } = sliceLogLines2(scopedFinished.aggregated, params.offset, params.limit);
|
|
4726
4442
|
const status = scopedFinished.status === "completed" ? "completed" : "failed";
|
|
4727
4443
|
return {
|
|
4728
4444
|
success: true,
|
|
@@ -4735,7 +4451,7 @@ Process still running.`),
|
|
|
4735
4451
|
truncated: scopedFinished.truncated,
|
|
4736
4452
|
exitCode: scopedFinished.exitCode ?? undefined,
|
|
4737
4453
|
exitSignal: scopedFinished.exitSignal ?? undefined,
|
|
4738
|
-
name:
|
|
4454
|
+
name: deriveSessionName2(scopedFinished.command)
|
|
4739
4455
|
}
|
|
4740
4456
|
};
|
|
4741
4457
|
}
|
|
@@ -4780,7 +4496,7 @@ Process still running.`),
|
|
|
4780
4496
|
message: `Wrote ${(params.data ?? "").length} bytes to session ${params.sessionId}${params.eof ? " (stdin closed)" : ""}.`,
|
|
4781
4497
|
data: {
|
|
4782
4498
|
sessionId: params.sessionId,
|
|
4783
|
-
name:
|
|
4499
|
+
name: deriveSessionName2(scopedSession.command)
|
|
4784
4500
|
}
|
|
4785
4501
|
};
|
|
4786
4502
|
}
|
|
@@ -4804,7 +4520,7 @@ Process still running.`),
|
|
|
4804
4520
|
message: `Session ${params.sessionId} stdin is not writable.`
|
|
4805
4521
|
};
|
|
4806
4522
|
}
|
|
4807
|
-
const { data, warnings } =
|
|
4523
|
+
const { data, warnings } = encodeKeySequence2({
|
|
4808
4524
|
keys: params.keys,
|
|
4809
4525
|
hex: params.hex,
|
|
4810
4526
|
literal: params.literal
|
|
@@ -4828,7 +4544,7 @@ Warnings:
|
|
|
4828
4544
|
- `)}` : ""),
|
|
4829
4545
|
data: {
|
|
4830
4546
|
sessionId: params.sessionId,
|
|
4831
|
-
name:
|
|
4547
|
+
name: deriveSessionName2(scopedSession.command)
|
|
4832
4548
|
}
|
|
4833
4549
|
};
|
|
4834
4550
|
}
|
|
@@ -4865,7 +4581,7 @@ Warnings:
|
|
|
4865
4581
|
message: `Submitted session ${params.sessionId} (sent CR).`,
|
|
4866
4582
|
data: {
|
|
4867
4583
|
sessionId: params.sessionId,
|
|
4868
|
-
name:
|
|
4584
|
+
name: deriveSessionName2(scopedSession.command)
|
|
4869
4585
|
}
|
|
4870
4586
|
};
|
|
4871
4587
|
}
|
|
@@ -4889,7 +4605,7 @@ Warnings:
|
|
|
4889
4605
|
message: `Session ${params.sessionId} stdin is not writable.`
|
|
4890
4606
|
};
|
|
4891
4607
|
}
|
|
4892
|
-
const payload =
|
|
4608
|
+
const payload = encodePaste2(params.text ?? "", params.bracketed !== false);
|
|
4893
4609
|
if (!payload) {
|
|
4894
4610
|
return { success: false, message: "No paste text provided." };
|
|
4895
4611
|
}
|
|
@@ -4906,7 +4622,7 @@ Warnings:
|
|
|
4906
4622
|
message: `Pasted ${params.text?.length ?? 0} chars to session ${params.sessionId}.`,
|
|
4907
4623
|
data: {
|
|
4908
4624
|
sessionId: params.sessionId,
|
|
4909
|
-
name:
|
|
4625
|
+
name: deriveSessionName2(scopedSession.command)
|
|
4910
4626
|
}
|
|
4911
4627
|
};
|
|
4912
4628
|
}
|
|
@@ -4923,12 +4639,12 @@ Warnings:
|
|
|
4923
4639
|
message: `Session ${params.sessionId} is not backgrounded.`
|
|
4924
4640
|
};
|
|
4925
4641
|
}
|
|
4926
|
-
|
|
4642
|
+
killSession2(scopedSession);
|
|
4927
4643
|
markExited(scopedSession, null, "SIGKILL", "failed");
|
|
4928
4644
|
return {
|
|
4929
4645
|
success: true,
|
|
4930
4646
|
message: `Killed session ${params.sessionId}.`,
|
|
4931
|
-
data: { name:
|
|
4647
|
+
data: { name: deriveSessionName2(scopedSession.command) }
|
|
4932
4648
|
};
|
|
4933
4649
|
}
|
|
4934
4650
|
case "clear": {
|
|
@@ -4946,12 +4662,12 @@ Warnings:
|
|
|
4946
4662
|
}
|
|
4947
4663
|
case "remove": {
|
|
4948
4664
|
if (scopedSession) {
|
|
4949
|
-
|
|
4665
|
+
killSession2(scopedSession);
|
|
4950
4666
|
markExited(scopedSession, null, "SIGKILL", "failed");
|
|
4951
4667
|
return {
|
|
4952
4668
|
success: true,
|
|
4953
4669
|
message: `Removed session ${params.sessionId}.`,
|
|
4954
|
-
data: { name:
|
|
4670
|
+
data: { name: deriveSessionName2(scopedSession.command) }
|
|
4955
4671
|
};
|
|
4956
4672
|
}
|
|
4957
4673
|
if (scopedFinished) {
|
|
@@ -5000,7 +4716,7 @@ Warnings:
|
|
|
5000
4716
|
const session = this.getSession(id);
|
|
5001
4717
|
if (!session)
|
|
5002
4718
|
return false;
|
|
5003
|
-
|
|
4719
|
+
killSession2(session);
|
|
5004
4720
|
markExited(session, null, "SIGKILL", "killed");
|
|
5005
4721
|
return true;
|
|
5006
4722
|
}
|
|
@@ -5013,7 +4729,7 @@ Warnings:
|
|
|
5013
4729
|
}
|
|
5014
4730
|
clearCommandHistory(conversationId) {
|
|
5015
4731
|
this.commandHistory.delete(conversationId);
|
|
5016
|
-
|
|
4732
|
+
logger7.info(`Cleared command history for conversation: ${conversationId}`);
|
|
5017
4733
|
}
|
|
5018
4734
|
getCurrentDirectory(_conversationId) {
|
|
5019
4735
|
return this.currentDirectory;
|
|
@@ -5073,12 +4789,12 @@ Warnings:
|
|
|
5073
4789
|
if (useShell) {
|
|
5074
4790
|
cmd = "sh";
|
|
5075
4791
|
args = ["-c", command];
|
|
5076
|
-
|
|
4792
|
+
logger7.info(`Executing shell command: sh -c "${command}" in ${this.currentDirectory}`);
|
|
5077
4793
|
} else {
|
|
5078
4794
|
const parts = command.split(/\s+/);
|
|
5079
4795
|
cmd = parts[0];
|
|
5080
4796
|
args = parts.slice(1);
|
|
5081
|
-
|
|
4797
|
+
logger7.info(`Executing command: ${cmd} ${args.join(" ")} in ${this.currentDirectory}`);
|
|
5082
4798
|
}
|
|
5083
4799
|
let stdout = "";
|
|
5084
4800
|
let stderr = "";
|
|
@@ -5149,7 +4865,7 @@ Command timed out`,
|
|
|
5149
4865
|
let pty = null;
|
|
5150
4866
|
let stdin;
|
|
5151
4867
|
if (opts.usePty) {
|
|
5152
|
-
const { shell, args: shellArgs } =
|
|
4868
|
+
const { shell, args: shellArgs } = getShellConfig2();
|
|
5153
4869
|
try {
|
|
5154
4870
|
const ptyModule = await Promise.resolve().then(() => __toESM(require_node_pty(), 1));
|
|
5155
4871
|
const spawnPty = ptyModule.spawn ?? ptyModule.default?.spawn;
|
|
@@ -5183,12 +4899,12 @@ Command timed out`,
|
|
|
5183
4899
|
} catch (err) {
|
|
5184
4900
|
const errText = String(err);
|
|
5185
4901
|
const warning = `Warning: PTY spawn failed (${errText}); retrying without PTY.`;
|
|
5186
|
-
|
|
4902
|
+
logger7.warn(`exec: PTY spawn failed (${errText}); retrying without PTY.`);
|
|
5187
4903
|
opts.warnings.push(warning);
|
|
5188
4904
|
}
|
|
5189
4905
|
}
|
|
5190
4906
|
if (!pty) {
|
|
5191
|
-
const { shell, args: shellArgs } =
|
|
4907
|
+
const { shell, args: shellArgs } = getShellConfig2();
|
|
5192
4908
|
const proc = spawn2(shell, [...shellArgs, opts.command], {
|
|
5193
4909
|
cwd: opts.workdir,
|
|
5194
4910
|
env: opts.env,
|
|
@@ -5261,7 +4977,7 @@ ${reason}` : reason
|
|
|
5261
4977
|
};
|
|
5262
4978
|
const onTimeout = () => {
|
|
5263
4979
|
timedOut = true;
|
|
5264
|
-
|
|
4980
|
+
killSession2(session);
|
|
5265
4981
|
if (!timeoutFinalizeTimer) {
|
|
5266
4982
|
timeoutFinalizeTimer = setTimeout(() => {
|
|
5267
4983
|
finalizeTimeout();
|
|
@@ -5279,24 +4995,24 @@ ${reason}` : reason
|
|
|
5279
4995
|
}
|
|
5280
4996
|
};
|
|
5281
4997
|
const handleStdout = (data) => {
|
|
5282
|
-
const str =
|
|
5283
|
-
for (const chunk of
|
|
4998
|
+
const str = sanitizeBinaryOutput2(data.toString());
|
|
4999
|
+
for (const chunk of chunkString2(str)) {
|
|
5284
5000
|
appendOutput(session, "stdout", chunk);
|
|
5285
5001
|
emitUpdate();
|
|
5286
5002
|
}
|
|
5287
5003
|
};
|
|
5288
5004
|
const handleStderr = (data) => {
|
|
5289
|
-
const str =
|
|
5290
|
-
for (const chunk of
|
|
5005
|
+
const str = sanitizeBinaryOutput2(data.toString());
|
|
5006
|
+
for (const chunk of chunkString2(str)) {
|
|
5291
5007
|
appendOutput(session, "stderr", chunk);
|
|
5292
5008
|
emitUpdate();
|
|
5293
5009
|
}
|
|
5294
5010
|
};
|
|
5295
5011
|
if (pty) {
|
|
5296
|
-
const cursorResponse =
|
|
5012
|
+
const cursorResponse = buildCursorPositionResponse2();
|
|
5297
5013
|
pty.onData((data) => {
|
|
5298
5014
|
const raw = data.toString();
|
|
5299
|
-
const { cleaned, requests } =
|
|
5015
|
+
const { cleaned, requests } = stripDsrRequests2(raw);
|
|
5300
5016
|
if (requests > 0) {
|
|
5301
5017
|
for (let i = 0;i < requests; i += 1) {
|
|
5302
5018
|
pty.write(cursorResponse);
|
|
@@ -5392,7 +5108,7 @@ ${String(err)}` : String(err);
|
|
|
5392
5108
|
session,
|
|
5393
5109
|
startedAt,
|
|
5394
5110
|
promise,
|
|
5395
|
-
kill: () =>
|
|
5111
|
+
kill: () => killSession2(session)
|
|
5396
5112
|
};
|
|
5397
5113
|
}
|
|
5398
5114
|
addToHistory(conversationId, command, result, fileOperations) {
|
|
@@ -5472,27 +5188,28 @@ ${String(err)}` : String(err);
|
|
|
5472
5188
|
// index.ts
|
|
5473
5189
|
var shellPlugin = {
|
|
5474
5190
|
name: "shell",
|
|
5475
|
-
description: "
|
|
5191
|
+
description: "Shell observability and history management providers",
|
|
5476
5192
|
services: [ShellService, ExecApprovalService],
|
|
5477
|
-
actions: [
|
|
5478
|
-
providers: [shellHistoryProvider]
|
|
5193
|
+
actions: [clearHistory],
|
|
5194
|
+
providers: [shellHistoryProvider, terminalUsageProvider]
|
|
5479
5195
|
};
|
|
5480
|
-
var
|
|
5196
|
+
var plugin_shell_default = shellPlugin;
|
|
5481
5197
|
export {
|
|
5482
5198
|
validatePath,
|
|
5483
|
-
truncateMiddle,
|
|
5199
|
+
truncateMiddle2 as truncateMiddle,
|
|
5484
5200
|
trimWithCap,
|
|
5201
|
+
terminalUsageProvider,
|
|
5485
5202
|
tail,
|
|
5486
|
-
stripDsrRequests,
|
|
5203
|
+
stripDsrRequests2 as stripDsrRequests,
|
|
5487
5204
|
spawnWithFallback,
|
|
5488
|
-
sliceUtf16Safe,
|
|
5489
|
-
sliceLogLines,
|
|
5205
|
+
sliceUtf16Safe2 as sliceUtf16Safe,
|
|
5206
|
+
sliceLogLines2 as sliceLogLines,
|
|
5490
5207
|
shellPlugin,
|
|
5491
5208
|
shellHistoryProvider,
|
|
5492
5209
|
setJobTtlMs,
|
|
5493
5210
|
saveApprovals,
|
|
5494
|
-
sanitizeBinaryOutput,
|
|
5495
|
-
resolveWorkdir,
|
|
5211
|
+
sanitizeBinaryOutput2 as sanitizeBinaryOutput,
|
|
5212
|
+
resolveWorkdir2 as resolveWorkdir,
|
|
5496
5213
|
resolveSafeBins,
|
|
5497
5214
|
resolveCommandResolution,
|
|
5498
5215
|
resolveCommandFromArgv,
|
|
@@ -5501,10 +5218,9 @@ export {
|
|
|
5501
5218
|
resetProcessRegistryForTests,
|
|
5502
5219
|
requiresExecApproval,
|
|
5503
5220
|
recordAllowlistUse,
|
|
5504
|
-
readEnvInt,
|
|
5221
|
+
readEnvInt2 as readEnvInt,
|
|
5505
5222
|
readApprovalsSnapshot,
|
|
5506
|
-
|
|
5507
|
-
pad,
|
|
5223
|
+
pad2 as pad,
|
|
5508
5224
|
normalizeSafeBins,
|
|
5509
5225
|
normalizeApprovals,
|
|
5510
5226
|
minSecurity,
|
|
@@ -5516,36 +5232,35 @@ export {
|
|
|
5516
5232
|
loadApprovals,
|
|
5517
5233
|
listRunningSessions,
|
|
5518
5234
|
listFinishedSessions,
|
|
5519
|
-
killSession,
|
|
5520
|
-
killProcessTree,
|
|
5235
|
+
killSession2 as killSession,
|
|
5236
|
+
killProcessTree2 as killProcessTree,
|
|
5521
5237
|
isSafeCommand,
|
|
5522
5238
|
isSafeBinUsage,
|
|
5523
5239
|
isForbiddenCommand,
|
|
5524
|
-
getShellConfig,
|
|
5240
|
+
getShellConfig2 as getShellConfig,
|
|
5525
5241
|
getSession,
|
|
5526
5242
|
getFinishedSession,
|
|
5527
5243
|
getApprovalSocketPath,
|
|
5528
5244
|
getApprovalFilePath,
|
|
5529
5245
|
formatSpawnError,
|
|
5530
|
-
formatDuration,
|
|
5246
|
+
formatDuration2 as formatDuration,
|
|
5531
5247
|
extractBaseCommand,
|
|
5532
|
-
executeCommand,
|
|
5533
5248
|
evaluateShellAllowlist,
|
|
5534
5249
|
evaluateExecAllowlist,
|
|
5535
5250
|
ensureApprovals,
|
|
5536
|
-
encodePaste,
|
|
5537
|
-
encodeKeySequence,
|
|
5251
|
+
encodePaste2 as encodePaste,
|
|
5252
|
+
encodeKeySequence2 as encodeKeySequence,
|
|
5538
5253
|
drainSession,
|
|
5539
|
-
deriveSessionName,
|
|
5254
|
+
deriveSessionName2 as deriveSessionName,
|
|
5540
5255
|
deleteSession,
|
|
5541
|
-
|
|
5256
|
+
plugin_shell_default as default,
|
|
5542
5257
|
createSessionSlug,
|
|
5543
|
-
coerceEnv,
|
|
5258
|
+
coerceEnv2 as coerceEnv,
|
|
5544
5259
|
clearHistory,
|
|
5545
5260
|
clearFinished,
|
|
5546
|
-
clampNumber,
|
|
5547
|
-
chunkString,
|
|
5548
|
-
buildCursorPositionResponse,
|
|
5261
|
+
clampNumber2 as clampNumber,
|
|
5262
|
+
chunkString2 as chunkString,
|
|
5263
|
+
buildCursorPositionResponse2 as buildCursorPositionResponse,
|
|
5549
5264
|
appendOutput,
|
|
5550
5265
|
analyzeShellCommand,
|
|
5551
5266
|
addSession,
|
|
@@ -5555,8 +5270,8 @@ export {
|
|
|
5555
5270
|
EXEC_APPROVAL_DEFAULTS,
|
|
5556
5271
|
DEFAULT_SAFE_BINS,
|
|
5557
5272
|
DEFAULT_FORBIDDEN_COMMANDS,
|
|
5558
|
-
BRACKETED_PASTE_START,
|
|
5559
|
-
BRACKETED_PASTE_END
|
|
5273
|
+
BRACKETED_PASTE_START2 as BRACKETED_PASTE_START,
|
|
5274
|
+
BRACKETED_PASTE_END2 as BRACKETED_PASTE_END
|
|
5560
5275
|
};
|
|
5561
5276
|
|
|
5562
|
-
//# debugId=
|
|
5277
|
+
//# debugId=D974DA05B88C09D864756E2164756E21
|