nolo-cli 0.33.0-alpha.8 → 0.33.0-alpha.9
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/{_executeModel-RI6DRQTP.js → _executeModel-2WR4RTZZ.js} +22 -22
- package/{action-SMLG6OTB.js → action-OQWPNKAH.js} +4 -4
- package/{addContentAction-SFO244QN.js → addContentAction-YVINZQTF.js} +3 -3
- package/{addDialogAgentAction-W44ZCEHB.js → addDialogAgentAction-6IOCIEPI.js} +2 -2
- package/{agentDeleteCommand-F2P4PF6R.js → agentDeleteCommand-H22WRYC6.js} +9 -9
- package/{agentEmailCommands-BW6ZDQ3C.js → agentEmailCommands-DGJVCMCG.js} +24 -24
- package/{agentListCommands-IC3GH45S.js → agentListCommands-6NWMLIMQ.js} +9 -9
- package/{agentMachineCommands-LGYGQX75.js → agentMachineCommands-5MUO6RDI.js} +10 -10
- package/{agentRecordCommands-FCQ7ZF6C.js → agentRecordCommands-YMGKT2WF.js} +24 -24
- package/{agentRunCommand-F5L6GPSK.js → agentRunCommand-LYEMVQJH.js} +24 -24
- package/{agentRuntimeLocal-B7SHOEHJ.js → agentRuntimeLocal-PRAR3NCW.js} +9 -9
- package/{agentSlice-GMKJKCPY.js → agentSlice-AMDSTXP5.js} +3 -3
- package/{appDeleteCommands-63VCZ5CT.js → appDeleteCommands-L5PKC42V.js} +11 -11
- package/{appDeployCommands-YFTG2P3K.js → appDeployCommands-3J7Z4XWV.js} +11 -11
- package/{appDeployStatusCommands-R5ENU5CQ.js → appDeployStatusCommands-YTRPXE72.js} +11 -11
- package/{appGetCommands-3CH3VMUB.js → appGetCommands-JVXV5WXD.js} +11 -11
- package/{appListCommands-Q2HVYG7J.js → appListCommands-7KDEOJSO.js} +11 -11
- package/{chatQueueReduxAdapter-ZRFKEKD3.js → chatQueueReduxAdapter-ZKSS6XRV.js} +9 -9
- package/{chunk-TFPUAELY.js → chunk-3RUW3ONU.js} +1 -1
- package/{chunk-VH2F7ZB6.js → chunk-3UG2IIZ3.js} +1 -1
- package/{chunk-JTSGWTYZ.js → chunk-4F5M2MW5.js} +1 -1
- package/{chunk-LZC5YX6P.js → chunk-5X4GQ53C.js} +4 -4
- package/{chunk-JZ5AMMXU.js → chunk-6YXDOOSH.js} +2 -2
- package/{chunk-37V5LNEQ.js → chunk-75U775RZ.js} +1 -1
- package/{chunk-JCGIUBE3.js → chunk-7INB7NXF.js} +8 -8
- package/{chunk-W7J5HZI7.js → chunk-7L5CQQIT.js} +3 -3
- package/{chunk-NKFMFT4W.js → chunk-AN7DGYMW.js} +440 -365
- package/{chunk-XCCGU6QB.js → chunk-ASDXPOJH.js} +1 -1
- package/{chunk-MUCM2MZB.js → chunk-BQ3OHLR5.js} +20 -20
- package/{chunk-XL7BR2GE.js → chunk-BXPBZVRE.js} +18 -18
- package/{chunk-OOK2ZT5F.js → chunk-D5JI6VDM.js} +4 -4
- package/{chunk-AOXVOC6D.js → chunk-DKMOY3QI.js} +2 -2
- package/{chunk-RTJWH72P.js → chunk-DNQK7M7K.js} +3 -3
- package/{chunk-6FNS3SQF.js → chunk-DSBGWT2C.js} +3 -3
- package/{chunk-PVVAWIUN.js → chunk-EMW55KKX.js} +1 -1
- package/{chunk-4C7K5NXX.js → chunk-GJXRIOLS.js} +1 -1
- package/{chunk-XFV2VK7O.js → chunk-GOIDVXK5.js} +1 -1
- package/{chunk-IL545V3E.js → chunk-IKWTTKWG.js} +1 -1
- package/{chunk-UK5JGQVU.js → chunk-IR6W62Q6.js} +8 -0
- package/{chunk-2GKZGZBN.js → chunk-MQWVAOIU.js} +10 -10
- package/{chunk-CHYAYZJH.js → chunk-RJKIB55Y.js} +1 -1
- package/{chunk-PREQU5QV.js → chunk-RPRRIFXV.js} +1 -1
- package/{chunk-O6W5PZXA.js → chunk-UEAUAXYS.js} +9 -9
- package/{chunk-57QJXIB6.js → chunk-UM4H3URE.js} +3 -3
- package/{chunk-CLP2K7U4.js → chunk-UNOTBWZZ.js} +2 -2
- package/{chunk-KBZCACG7.js → chunk-VWY2CZD2.js} +1 -1
- package/{chunk-YBPAQV37.js → chunk-WNHST4IE.js} +11 -11
- package/{chunk-VOBUVWP5.js → chunk-XLLTJXAD.js} +8 -8
- package/{chunk-5SBK2MTI.js → chunk-ZWS6DUHX.js} +18 -66
- package/{createAgentAutomationAction-6SE7CQ7F.js → createAgentAutomationAction-GVSJL2CI.js} +10 -10
- package/{createDialogAction-AROAXAPM.js → createDialogAction-VVR46Q2A.js} +11 -11
- package/{createPageAction-GCO3R6GR.js → createPageAction-M2VDKBYS.js} +10 -10
- package/{dbSlice-3SD5L5H2.js → dbSlice-UI2VCBLG.js} +2 -2
- package/{dialogCommands-N26NNRRD.js → dialogCommands-26DBYNBT.js} +9 -9
- package/{dialogSlice-JVPBOMFB.js → dialogSlice-FFGLRCBL.js} +9 -9
- package/{docCreateCommands-PON6FF4V.js → docCreateCommands-F2ASWG6B.js} +10 -10
- package/{docDeleteCommands-WWRFONDS.js → docDeleteCommands-A4URDZPI.js} +10 -10
- package/{docReadCommands-S4Z275LB.js → docReadCommands-DO6VHDZD.js} +10 -10
- package/{docStore-WGDO3I76.js → docStore-EDAE7RNT.js} +11 -11
- package/{docUpdateCommands-TJQB4IJ7.js → docUpdateCommands-ZTXZ3RTP.js} +10 -10
- package/{handleSendMessageAction-T7AS7DDY.js → handleSendMessageAction-CIRLGHOC.js} +9 -9
- package/index.js +41 -41
- package/{machineCommands-C6CPXZRR.js → machineCommands-NIQNCYJV.js} +24 -24
- package/{machineWsRunDispatch-27K2P4OP.js → machineWsRunDispatch-EMTSQGAR.js} +24 -24
- package/package.json +1 -1
- package/{read-6G73BMEY.js → read-2VHEHCAN.js} +1 -1
- package/{readDocTool-DK4EI2GA.js → readDocTool-CSWD6NPC.js} +1 -1
- package/{readlineWorkspace-LBWKTPJE.js → readlineWorkspace-A2JBJWLI.js} +24 -24
- package/{removeDialogAgentAction-E7H557UO.js → removeDialogAgentAction-2T7BPM3T.js} +4 -4
- package/{runAgentClientLoop-D2T2P36H.js → runAgentClientLoop-BV4736ED.js} +19 -19
- package/{setDialogExtraReferencesAction-RMMGBR5F.js → setDialogExtraReferencesAction-GPT37HTW.js} +3 -3
- package/{setPrimaryDialogAgentAction-FWBKKLVA.js → setPrimaryDialogAgentAction-3746P7IW.js} +4 -4
- package/{settingSlice-73PV4KG4.js → settingSlice-FIFOQ6OR.js} +3 -3
- package/{spaceCommands-B6LO4U5R.js → spaceCommands-AYZQHDB3.js} +9 -9
- package/{spaceSlice-VUAEZVDS.js → spaceSlice-43BNF22B.js} +10 -10
- package/{streamAgentChatTurn-BBPSFMTZ.js → streamAgentChatTurn-OLLRSITV.js} +22 -22
- package/{switchDialogAgentAction-US6QNL3N.js → switchDialogAgentAction-MQTUTQHL.js} +2 -2
- package/{tools-NWA3J7KE.js → tools-2SI3FXLL.js} +16 -16
- package/{tuiSummaryLlmCaller-KC7JCJBB.js → tuiSummaryLlmCaller-EAWQ3IZX.js} +5 -5
- package/{updateDialogTitleAction-TDDQ4J4L.js → updateDialogTitleAction-YJEQFYOA.js} +10 -10
|
@@ -7,14 +7,14 @@ import {
|
|
|
7
7
|
providerHttpFailure,
|
|
8
8
|
readSseFrames,
|
|
9
9
|
throwIfChatCompletionStreamFailed
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-RJKIB55Y.js";
|
|
11
11
|
import {
|
|
12
12
|
sanitizeForOutbound
|
|
13
13
|
} from "./chunk-T27IEBNQ.js";
|
|
14
14
|
import {
|
|
15
15
|
parseToolArgumentsJson,
|
|
16
16
|
resolveHistoricalToolContentCap
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-D5JI6VDM.js";
|
|
18
18
|
import {
|
|
19
19
|
createThinkParserState,
|
|
20
20
|
createToolCallAccumulator,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
import {
|
|
26
26
|
requiresBareImageUrl,
|
|
27
27
|
toBareImageUrlMessages
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-ZWS6DUHX.js";
|
|
29
29
|
import {
|
|
30
30
|
convertMessagesToResponsesInput,
|
|
31
31
|
extractReasoningFromResponseOutput,
|
|
@@ -1246,222 +1246,6 @@ function evaluateShellCommandPolicy(args) {
|
|
|
1246
1246
|
};
|
|
1247
1247
|
}
|
|
1248
1248
|
|
|
1249
|
-
// packages/agent-runtime/localToolPolicy.ts
|
|
1250
|
-
var NEVER_LOCAL_TOOLS = /* @__PURE__ */ new Set([
|
|
1251
|
-
"deleteSpaces",
|
|
1252
|
-
"updateAgent",
|
|
1253
|
-
"updateSelf",
|
|
1254
|
-
"createAgent"
|
|
1255
|
-
]);
|
|
1256
|
-
var REMOVED_LOCAL_TOOLS = /* @__PURE__ */ new Set([
|
|
1257
|
-
"gitStatus",
|
|
1258
|
-
"gitDiff",
|
|
1259
|
-
"gitCreateBranch",
|
|
1260
|
-
"gitAdd",
|
|
1261
|
-
"gitCommit",
|
|
1262
|
-
"commitWorkspace"
|
|
1263
|
-
]);
|
|
1264
|
-
var DEFAULT_LOCAL_TOOLS = /* @__PURE__ */ new Set([
|
|
1265
|
-
"listFiles",
|
|
1266
|
-
"readFile",
|
|
1267
|
-
"writeFile",
|
|
1268
|
-
"editFile",
|
|
1269
|
-
"globFiles",
|
|
1270
|
-
"searchFiles",
|
|
1271
|
-
"execShell",
|
|
1272
|
-
"launchProcess",
|
|
1273
|
-
"listProcesses",
|
|
1274
|
-
// Scheduling tools: forking/observing sub-agents is a declared capability
|
|
1275
|
-
// of capable agents, and the blast radius is strictly narrower than
|
|
1276
|
-
// execShell (already default-enabled). Requiring an env allowlist for these
|
|
1277
|
-
// while execShell is open by default inverted the risk model and forced every
|
|
1278
|
-
// new local user to hand-edit .env files before sub-agent tasks worked.
|
|
1279
|
-
"startAgentRun",
|
|
1280
|
-
"controlAgentRun"
|
|
1281
|
-
]);
|
|
1282
|
-
function parseToolAllowlist(value) {
|
|
1283
|
-
return (value ?? "").split(",").flatMap((item) => {
|
|
1284
|
-
const trimmed = item.trim();
|
|
1285
|
-
return trimmed ? [trimmed] : [];
|
|
1286
|
-
});
|
|
1287
|
-
}
|
|
1288
|
-
function normalizeLocalToolName(toolName) {
|
|
1289
|
-
return canonicalizeToolName(
|
|
1290
|
-
String(toolName ?? "").replace(/^functions\./, "").trim()
|
|
1291
|
-
);
|
|
1292
|
-
}
|
|
1293
|
-
function parseShellCommandPayload(rawArguments) {
|
|
1294
|
-
return parseToolArgumentsJson(rawArguments);
|
|
1295
|
-
}
|
|
1296
|
-
function toDisplayCommand(command) {
|
|
1297
|
-
if (typeof command === "string") {
|
|
1298
|
-
const trimmed = command.trim();
|
|
1299
|
-
return trimmed ? trimmed : void 0;
|
|
1300
|
-
}
|
|
1301
|
-
if (Array.isArray(command)) {
|
|
1302
|
-
const joined = command.map((item) => typeof item === "string" ? item : String(item)).filter((item) => item.length > 0).join(" ").trim();
|
|
1303
|
-
return joined ? joined : void 0;
|
|
1304
|
-
}
|
|
1305
|
-
return void 0;
|
|
1306
|
-
}
|
|
1307
|
-
function isRestrictedLocalToolMode(env) {
|
|
1308
|
-
return env.NOLO_LOCAL_TOOL_MODE === "restricted";
|
|
1309
|
-
}
|
|
1310
|
-
function resolveLocalToolPolicy(args) {
|
|
1311
|
-
const toolName = normalizeLocalToolName(args.toolName);
|
|
1312
|
-
const restrictedMode = isRestrictedLocalToolMode(args.env);
|
|
1313
|
-
if (!toolName) {
|
|
1314
|
-
return {
|
|
1315
|
-
allowed: false,
|
|
1316
|
-
toolName,
|
|
1317
|
-
reason: "Tool name is required."
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
|
-
if (NEVER_LOCAL_TOOLS.has(toolName)) {
|
|
1321
|
-
return {
|
|
1322
|
-
allowed: false,
|
|
1323
|
-
toolName,
|
|
1324
|
-
reason: `${toolName} is blocked by the local runtime safety policy.`
|
|
1325
|
-
};
|
|
1326
|
-
}
|
|
1327
|
-
if (REMOVED_LOCAL_TOOLS.has(toolName)) {
|
|
1328
|
-
return {
|
|
1329
|
-
allowed: false,
|
|
1330
|
-
toolName,
|
|
1331
|
-
reason: `${toolName} has been removed from the local runtime tool surface. Use execShell in shell-enabled workspace runs when shell is needed.`
|
|
1332
|
-
};
|
|
1333
|
-
}
|
|
1334
|
-
const allowedByEnv = new Set(parseToolAllowlist(args.env.NOLO_LOCAL_ALLOWED_TOOLS));
|
|
1335
|
-
const agentTools = new Set(
|
|
1336
|
-
(args.agentToolNames ?? []).flatMap((item) => {
|
|
1337
|
-
const normalized = normalizeLocalToolName(item);
|
|
1338
|
-
return normalized ? [normalized] : [];
|
|
1339
|
-
})
|
|
1340
|
-
);
|
|
1341
|
-
if (!restrictedMode && agentTools.has(toolName)) {
|
|
1342
|
-
return { allowed: true, toolName };
|
|
1343
|
-
}
|
|
1344
|
-
if (DEFAULT_LOCAL_TOOLS.has(toolName) && agentTools.has(toolName)) {
|
|
1345
|
-
return { allowed: true, toolName };
|
|
1346
|
-
}
|
|
1347
|
-
if (allowedByEnv.has(toolName) && agentTools.has(toolName)) {
|
|
1348
|
-
return { allowed: true, toolName };
|
|
1349
|
-
}
|
|
1350
|
-
return {
|
|
1351
|
-
allowed: false,
|
|
1352
|
-
toolName,
|
|
1353
|
-
reason: `${toolName} is not enabled for local runtime runs. The agent must declare the tool in its manifest; in restricted mode (NOLO_LOCAL_TOOL_MODE=restricted) non-default tools also require adding to NOLO_LOCAL_ALLOWED_TOOLS (e.g. NOLO_LOCAL_ALLOWED_TOOLS=someCustomTool) and restarting the session so the env var loads.`
|
|
1354
|
-
};
|
|
1355
|
-
}
|
|
1356
|
-
var ABORT_AWARE_LOCAL_TOOLS = /* @__PURE__ */ new Set(["execShell", "controlAgentRun"]);
|
|
1357
|
-
async function executeLocalToolWithPolicy(args) {
|
|
1358
|
-
const decision = resolveLocalToolPolicy({
|
|
1359
|
-
env: args.env,
|
|
1360
|
-
agentToolNames: args.agentToolNames,
|
|
1361
|
-
toolName: args.call.name
|
|
1362
|
-
});
|
|
1363
|
-
if (!decision.allowed) throw new Error(decision.reason);
|
|
1364
|
-
const executor = args.executors?.[decision.toolName];
|
|
1365
|
-
if (!executor) {
|
|
1366
|
-
throw new Error(`${decision.toolName} is allowed by policy but no local executor is registered.`);
|
|
1367
|
-
}
|
|
1368
|
-
const effectiveExecutor = args.abortSignal && ABORT_AWARE_LOCAL_TOOLS.has(decision.toolName) && args.executors ? (call) => executor(call, {
|
|
1369
|
-
abortSignal: args.abortSignal,
|
|
1370
|
-
detachMs: args.detachMs
|
|
1371
|
-
}) : executor;
|
|
1372
|
-
const guardEnabled = args.enableDestructiveShellGuard !== false;
|
|
1373
|
-
if (decision.toolName === "execShell" && !args.confirmed && guardEnabled) {
|
|
1374
|
-
const parsed = parseShellCommandPayload(args.call.arguments);
|
|
1375
|
-
const rawCommand = resolveShellCommandArg(parsed);
|
|
1376
|
-
if (isDestructiveShellCommand({ command: rawCommand, input: parsed.input })) {
|
|
1377
|
-
const command = toDisplayCommand(rawCommand);
|
|
1378
|
-
const request = {
|
|
1379
|
-
id: "permission-shell-destructive-action",
|
|
1380
|
-
tool: "execShell",
|
|
1381
|
-
action: "destructive_shell_command",
|
|
1382
|
-
title: "\u786E\u8BA4\u6267\u884C\u7834\u574F\u6027 shell \u547D\u4EE4",
|
|
1383
|
-
body: "\u8BE5\u547D\u4EE4\u53EF\u80FD\u5220\u9664\u6216\u91CD\u7F6E\u7528\u6237\u5185\u5BB9\uFF0C\u9700\u8981\u7528\u6237\u660E\u786E\u786E\u8BA4\u540E\u624D\u80FD\u6267\u884C\u3002",
|
|
1384
|
-
...command ? { command } : {},
|
|
1385
|
-
suggestedRule: {
|
|
1386
|
-
scope: "once",
|
|
1387
|
-
pattern: { capability: "destructive_action", target: "shell_command" }
|
|
1388
|
-
}
|
|
1389
|
-
};
|
|
1390
|
-
const buildBlockedError = (reason) => {
|
|
1391
|
-
const error = new Error(reason);
|
|
1392
|
-
error.code = "destructive_action_requires_confirmation";
|
|
1393
|
-
error.policy = {
|
|
1394
|
-
capability: "destructive_action",
|
|
1395
|
-
target: "shell_command",
|
|
1396
|
-
detail: "execShell destructive command"
|
|
1397
|
-
};
|
|
1398
|
-
error.permissionRequest = request;
|
|
1399
|
-
return error;
|
|
1400
|
-
};
|
|
1401
|
-
if (!args.confirmDestructiveAction) {
|
|
1402
|
-
if (args.blockDestructiveWithoutConfirmation) {
|
|
1403
|
-
throw buildBlockedError(
|
|
1404
|
-
"destructive shell command blocked: no confirmation channel available"
|
|
1405
|
-
);
|
|
1406
|
-
}
|
|
1407
|
-
return effectiveExecutor({
|
|
1408
|
-
...args.call,
|
|
1409
|
-
name: decision.toolName
|
|
1410
|
-
});
|
|
1411
|
-
}
|
|
1412
|
-
const confirmed = await args.confirmDestructiveAction(request);
|
|
1413
|
-
if (!confirmed) {
|
|
1414
|
-
throw buildBlockedError(
|
|
1415
|
-
"destructive shell command blocked: user declined confirmation"
|
|
1416
|
-
);
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
return effectiveExecutor({
|
|
1421
|
-
...args.call,
|
|
1422
|
-
name: decision.toolName
|
|
1423
|
-
});
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
// packages/agent-runtime/localWorkspaceTools.ts
|
|
1427
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
1428
|
-
import { createHash } from "node:crypto";
|
|
1429
|
-
import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
1430
|
-
import { dirname, relative, resolve, sep } from "node:path";
|
|
1431
|
-
import { spawnSync, spawn as spawnChildProcess } from "node:child_process";
|
|
1432
|
-
|
|
1433
|
-
// packages/core/readRangeLedger.ts
|
|
1434
|
-
function filterRetainedLedgerRecords(records, retainedCap) {
|
|
1435
|
-
return records.filter((record) => record.chars <= retainedCap);
|
|
1436
|
-
}
|
|
1437
|
-
function isValidRange(range) {
|
|
1438
|
-
return Number.isInteger(range.startLine) && Number.isInteger(range.endLine) && range.endLine >= range.startLine;
|
|
1439
|
-
}
|
|
1440
|
-
function mergeLineRanges(ranges) {
|
|
1441
|
-
const sorted = ranges.filter(isValidRange).slice().sort((a, b) => a.startLine - b.startLine);
|
|
1442
|
-
const merged = [];
|
|
1443
|
-
for (const range of sorted) {
|
|
1444
|
-
const last = merged[merged.length - 1];
|
|
1445
|
-
if (last && range.startLine <= last.endLine + 1) {
|
|
1446
|
-
last.endLine = Math.max(last.endLine, range.endLine);
|
|
1447
|
-
} else {
|
|
1448
|
-
merged.push({ startLine: range.startLine, endLine: range.endLine });
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
return merged;
|
|
1452
|
-
}
|
|
1453
|
-
function isLineRangeCovered(request, covered) {
|
|
1454
|
-
if (!isValidRange(request)) return false;
|
|
1455
|
-
let open = request.startLine;
|
|
1456
|
-
for (const range of mergeLineRanges(covered)) {
|
|
1457
|
-
if (range.endLine < open) continue;
|
|
1458
|
-
if (range.startLine > open) return false;
|
|
1459
|
-
open = range.endLine + 1;
|
|
1460
|
-
if (open > request.endLine) return true;
|
|
1461
|
-
}
|
|
1462
|
-
return open > request.endLine;
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
1249
|
// packages/agent-runtime/runtimeCompat.ts
|
|
1466
1250
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
1467
1251
|
import { join } from "node:path";
|
|
@@ -1636,70 +1420,6 @@ function spawnToWebStreams(options) {
|
|
|
1636
1420
|
};
|
|
1637
1421
|
}
|
|
1638
1422
|
|
|
1639
|
-
// packages/agent-runtime/processRegistry.ts
|
|
1640
|
-
var ProcessRegistry = class {
|
|
1641
|
-
processes = /* @__PURE__ */ new Map();
|
|
1642
|
-
add(proc) {
|
|
1643
|
-
this.processes.set(proc.pid, {
|
|
1644
|
-
pid: proc.pid,
|
|
1645
|
-
pgid: proc.pgid,
|
|
1646
|
-
command: proc.command,
|
|
1647
|
-
label: proc.label,
|
|
1648
|
-
startedAt: Date.now(),
|
|
1649
|
-
status: "running",
|
|
1650
|
-
persist: proc.persist ?? false
|
|
1651
|
-
});
|
|
1652
|
-
}
|
|
1653
|
-
list() {
|
|
1654
|
-
return Array.from(this.processes.values()).map((proc) => ({ ...proc }));
|
|
1655
|
-
}
|
|
1656
|
-
get(pid) {
|
|
1657
|
-
const item = this.processes.get(pid);
|
|
1658
|
-
return item ? { ...item } : void 0;
|
|
1659
|
-
}
|
|
1660
|
-
kill(pid, signal = "SIGTERM") {
|
|
1661
|
-
const item = this.processes.get(pid);
|
|
1662
|
-
if (!item) return false;
|
|
1663
|
-
if (item.status === "running") {
|
|
1664
|
-
try {
|
|
1665
|
-
process.kill(-item.pgid, signal);
|
|
1666
|
-
} catch {
|
|
1667
|
-
}
|
|
1668
|
-
item.status = "stopped";
|
|
1669
|
-
return true;
|
|
1670
|
-
}
|
|
1671
|
-
return false;
|
|
1672
|
-
}
|
|
1673
|
-
stopAll(signal = "SIGTERM", opts) {
|
|
1674
|
-
for (const item of this.processes.values()) {
|
|
1675
|
-
if (item.status === "running" && (opts?.includePersist || !item.persist)) {
|
|
1676
|
-
try {
|
|
1677
|
-
process.kill(-item.pgid, signal);
|
|
1678
|
-
} catch {
|
|
1679
|
-
}
|
|
1680
|
-
item.status = "stopped";
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
markExited(pid, exitCode) {
|
|
1685
|
-
const item = this.processes.get(pid);
|
|
1686
|
-
if (item && item.status === "running") {
|
|
1687
|
-
item.exitCode = exitCode;
|
|
1688
|
-
item.status = exitCode === 0 ? "exited" : "failed";
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
clear() {
|
|
1692
|
-
this.processes.clear();
|
|
1693
|
-
}
|
|
1694
|
-
};
|
|
1695
|
-
var registry = null;
|
|
1696
|
-
function getProcessRegistry() {
|
|
1697
|
-
if (!registry) {
|
|
1698
|
-
registry = new ProcessRegistry();
|
|
1699
|
-
}
|
|
1700
|
-
return registry;
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
1423
|
// packages/agent-runtime/localWorkspaceToolDefs.ts
|
|
1704
1424
|
var WORKSPACE_TOOL_NAMES = [
|
|
1705
1425
|
"listFiles",
|
|
@@ -2105,26 +1825,7 @@ function buildCaptureVisualStateTool() {
|
|
|
2105
1825
|
};
|
|
2106
1826
|
}
|
|
2107
1827
|
function buildExecShellTool(toolName) {
|
|
2108
|
-
return
|
|
2109
|
-
type: "function",
|
|
2110
|
-
function: {
|
|
2111
|
-
name: toolName,
|
|
2112
|
-
description: `Execute a shell command from the workspace root. Prefer one compound command (e.g. 'git status && git diff --stat') to perform complete verification in one step instead of multiple small roundtrips. Do not cd into guessed paths; commands already run from the workspace root. Commands block until exit. Long-running commands (sleep over ${IMMEDIATE_DETACH_SLEEP_THRESHOLD_SECONDS}s, dev servers, watchers) automatically detach to background returning {detached: true, pid, label}; for persistent services, prefer launchProcess.`,
|
|
2113
|
-
parameters: {
|
|
2114
|
-
type: "object",
|
|
2115
|
-
properties: {
|
|
2116
|
-
command: {
|
|
2117
|
-
type: "string",
|
|
2118
|
-
description: "Shell command to run (non-empty)."
|
|
2119
|
-
},
|
|
2120
|
-
cmd: {
|
|
2121
|
-
type: "string",
|
|
2122
|
-
description: "Compatibility alias for command."
|
|
2123
|
-
}
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
};
|
|
1828
|
+
return buildExecShellToolDefinition(toolName);
|
|
2128
1829
|
}
|
|
2129
1830
|
function buildLaunchProcessTool() {
|
|
2130
1831
|
return {
|
|
@@ -2276,6 +1977,109 @@ function filterDeclaredWorkspaceToolNames(args) {
|
|
|
2276
1977
|
);
|
|
2277
1978
|
}
|
|
2278
1979
|
|
|
1980
|
+
// packages/agent-runtime/localWorkspaceTools.ts
|
|
1981
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
1982
|
+
import { createHash } from "node:crypto";
|
|
1983
|
+
import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
1984
|
+
import { dirname, relative, resolve, sep } from "node:path";
|
|
1985
|
+
import { spawnSync, spawn as spawnChildProcess } from "node:child_process";
|
|
1986
|
+
|
|
1987
|
+
// packages/core/readRangeLedger.ts
|
|
1988
|
+
function filterRetainedLedgerRecords(records, retainedCap) {
|
|
1989
|
+
return records.filter((record) => record.chars <= retainedCap);
|
|
1990
|
+
}
|
|
1991
|
+
function isValidRange(range) {
|
|
1992
|
+
return Number.isInteger(range.startLine) && Number.isInteger(range.endLine) && range.endLine >= range.startLine;
|
|
1993
|
+
}
|
|
1994
|
+
function mergeLineRanges(ranges) {
|
|
1995
|
+
const sorted = ranges.filter(isValidRange).slice().sort((a, b) => a.startLine - b.startLine);
|
|
1996
|
+
const merged = [];
|
|
1997
|
+
for (const range of sorted) {
|
|
1998
|
+
const last = merged[merged.length - 1];
|
|
1999
|
+
if (last && range.startLine <= last.endLine + 1) {
|
|
2000
|
+
last.endLine = Math.max(last.endLine, range.endLine);
|
|
2001
|
+
} else {
|
|
2002
|
+
merged.push({ startLine: range.startLine, endLine: range.endLine });
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
return merged;
|
|
2006
|
+
}
|
|
2007
|
+
function isLineRangeCovered(request, covered) {
|
|
2008
|
+
if (!isValidRange(request)) return false;
|
|
2009
|
+
let open = request.startLine;
|
|
2010
|
+
for (const range of mergeLineRanges(covered)) {
|
|
2011
|
+
if (range.endLine < open) continue;
|
|
2012
|
+
if (range.startLine > open) return false;
|
|
2013
|
+
open = range.endLine + 1;
|
|
2014
|
+
if (open > request.endLine) return true;
|
|
2015
|
+
}
|
|
2016
|
+
return open > request.endLine;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
// packages/agent-runtime/processRegistry.ts
|
|
2020
|
+
var ProcessRegistry = class {
|
|
2021
|
+
processes = /* @__PURE__ */ new Map();
|
|
2022
|
+
add(proc) {
|
|
2023
|
+
this.processes.set(proc.pid, {
|
|
2024
|
+
pid: proc.pid,
|
|
2025
|
+
pgid: proc.pgid,
|
|
2026
|
+
command: proc.command,
|
|
2027
|
+
label: proc.label,
|
|
2028
|
+
startedAt: Date.now(),
|
|
2029
|
+
status: "running",
|
|
2030
|
+
persist: proc.persist ?? false
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
list() {
|
|
2034
|
+
return Array.from(this.processes.values()).map((proc) => ({ ...proc }));
|
|
2035
|
+
}
|
|
2036
|
+
get(pid) {
|
|
2037
|
+
const item = this.processes.get(pid);
|
|
2038
|
+
return item ? { ...item } : void 0;
|
|
2039
|
+
}
|
|
2040
|
+
kill(pid, signal = "SIGTERM") {
|
|
2041
|
+
const item = this.processes.get(pid);
|
|
2042
|
+
if (!item) return false;
|
|
2043
|
+
if (item.status === "running") {
|
|
2044
|
+
try {
|
|
2045
|
+
process.kill(-item.pgid, signal);
|
|
2046
|
+
} catch {
|
|
2047
|
+
}
|
|
2048
|
+
item.status = "stopped";
|
|
2049
|
+
return true;
|
|
2050
|
+
}
|
|
2051
|
+
return false;
|
|
2052
|
+
}
|
|
2053
|
+
stopAll(signal = "SIGTERM", opts) {
|
|
2054
|
+
for (const item of this.processes.values()) {
|
|
2055
|
+
if (item.status === "running" && (opts?.includePersist || !item.persist)) {
|
|
2056
|
+
try {
|
|
2057
|
+
process.kill(-item.pgid, signal);
|
|
2058
|
+
} catch {
|
|
2059
|
+
}
|
|
2060
|
+
item.status = "stopped";
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
markExited(pid, exitCode) {
|
|
2065
|
+
const item = this.processes.get(pid);
|
|
2066
|
+
if (item && item.status === "running") {
|
|
2067
|
+
item.exitCode = exitCode;
|
|
2068
|
+
item.status = exitCode === 0 ? "exited" : "failed";
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
clear() {
|
|
2072
|
+
this.processes.clear();
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
var registry = null;
|
|
2076
|
+
function getProcessRegistry() {
|
|
2077
|
+
if (!registry) {
|
|
2078
|
+
registry = new ProcessRegistry();
|
|
2079
|
+
}
|
|
2080
|
+
return registry;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2279
2083
|
// packages/agent-runtime/localWorkspaceTools.ts
|
|
2280
2084
|
var EXEC_SHELL_TIMEOUT_ENV = "NOLO_EXEC_SHELL_TIMEOUT_MS";
|
|
2281
2085
|
var EXEC_SHELL_DETACH_ENV = "NOLO_EXEC_SHELL_DETACH_MS";
|
|
@@ -4032,69 +3836,33 @@ async function captureVisualStateTool(args) {
|
|
|
4032
3836
|
...result.metadata,
|
|
4033
3837
|
script: "probe:visual-review",
|
|
4034
3838
|
args: extraArgs,
|
|
4035
|
-
baseUrl,
|
|
4036
|
-
path: captureArgs.path,
|
|
4037
|
-
waitSelector: captureArgs.waitSelector,
|
|
4038
|
-
scrollSelector: captureArgs.scrollSelector,
|
|
4039
|
-
focusSelector: captureArgs.focusSelector,
|
|
4040
|
-
expectText: captureArgs.expectText,
|
|
4041
|
-
screenshotPath: captureArgs.screenshotPath,
|
|
4042
|
-
metricsPath: captureArgs.metricsPath,
|
|
4043
|
-
...metrics ?? {}
|
|
4044
|
-
}
|
|
4045
|
-
};
|
|
4046
|
-
}
|
|
4047
|
-
async function execShellTool(args) {
|
|
4048
|
-
const parsed = parseWorkspaceToolArguments(args.call.arguments);
|
|
4049
|
-
const command = requireShellCommand(parsed, args.call.name);
|
|
4050
|
-
if (args.restrictToWorkspace) {
|
|
4051
|
-
const escapeToken = findWorkspaceShellEscapeToken(command);
|
|
4052
|
-
if (escapeToken) {
|
|
4053
|
-
return buildWorkspaceShellEscapeBlockedResult({ command, token: escapeToken });
|
|
4054
|
-
}
|
|
4055
|
-
}
|
|
4056
|
-
const interactiveAuthCommand = extractInteractiveGhAuthCommand(command);
|
|
4057
|
-
if (interactiveAuthCommand) {
|
|
4058
|
-
const activity2 = extractActivity(parsed);
|
|
4059
|
-
const blocked = buildInteractiveCommandBlockedResult(interactiveAuthCommand);
|
|
4060
|
-
return {
|
|
4061
|
-
...blocked,
|
|
4062
|
-
metadata: {
|
|
4063
|
-
...blocked.metadata,
|
|
4064
|
-
...activity2 ? { activity: activity2 } : {}
|
|
4065
|
-
}
|
|
4066
|
-
};
|
|
4067
|
-
}
|
|
4068
|
-
const result = await runWorkspaceCommand({
|
|
4069
|
-
workspaceRoot: args.workspaceRoot,
|
|
4070
|
-
command: buildWorkspaceShellCommand({
|
|
4071
|
-
toolName: args.call.name,
|
|
4072
|
-
command,
|
|
4073
|
-
shell: parsed.shell
|
|
4074
|
-
}),
|
|
4075
|
-
timeoutMs: resolveExecShellTimeoutMs(args.commandTimeoutMs),
|
|
4076
|
-
outputLimit: args.commandOutputLimit,
|
|
4077
|
-
commandPrefix: args.commandPrefix,
|
|
4078
|
-
abortSignal: args.abortSignal,
|
|
4079
|
-
// Commands that clearly never exit (long sleeps, tail -f, watch, dev
|
|
4080
|
-
// servers, infinite loops) are promoted to background immediately so the
|
|
4081
|
-
// turn — and the user's conversation — keeps going. Everything else keeps
|
|
4082
|
-
// the caller/env detach grace window.
|
|
4083
|
-
detachMs: isImmediateDetachShellCommand({ command }) ? 0 : args.detachMs
|
|
4084
|
-
});
|
|
4085
|
-
const activity = extractActivity(parsed);
|
|
4086
|
-
return {
|
|
4087
|
-
content: result.content,
|
|
4088
|
-
metadata: {
|
|
4089
|
-
command,
|
|
4090
|
-
exitCode: result.exitCode,
|
|
4091
|
-
timedOut: result.timedOut,
|
|
4092
|
-
...result.aborted ? { aborted: true } : {},
|
|
4093
|
-
...result.detached ? { detached: true, pid: result.pid, label: result.label, status: "running" } : {},
|
|
4094
|
-
...activity ? { activity } : {}
|
|
3839
|
+
baseUrl,
|
|
3840
|
+
path: captureArgs.path,
|
|
3841
|
+
waitSelector: captureArgs.waitSelector,
|
|
3842
|
+
scrollSelector: captureArgs.scrollSelector,
|
|
3843
|
+
focusSelector: captureArgs.focusSelector,
|
|
3844
|
+
expectText: captureArgs.expectText,
|
|
3845
|
+
screenshotPath: captureArgs.screenshotPath,
|
|
3846
|
+
metricsPath: captureArgs.metricsPath,
|
|
3847
|
+
...metrics ?? {}
|
|
4095
3848
|
}
|
|
4096
3849
|
};
|
|
4097
3850
|
}
|
|
3851
|
+
async function execShellTool(args) {
|
|
3852
|
+
const normalized = execShellCapability.normalizeInput(args.call.arguments);
|
|
3853
|
+
return execShellCapability.invoke(
|
|
3854
|
+
{
|
|
3855
|
+
workspaceRoot: args.workspaceRoot,
|
|
3856
|
+
commandTimeoutMs: args.commandTimeoutMs,
|
|
3857
|
+
commandOutputLimit: args.commandOutputLimit,
|
|
3858
|
+
commandPrefix: args.commandPrefix,
|
|
3859
|
+
restrictToWorkspace: args.restrictToWorkspace,
|
|
3860
|
+
abortSignal: args.abortSignal,
|
|
3861
|
+
detachMs: args.detachMs
|
|
3862
|
+
},
|
|
3863
|
+
normalized
|
|
3864
|
+
);
|
|
3865
|
+
}
|
|
4098
3866
|
function deriveLabel(command) {
|
|
4099
3867
|
const trimmed = command.trim();
|
|
4100
3868
|
if (!trimmed) return "process";
|
|
@@ -4255,6 +4023,313 @@ function createLocalWorkspaceToolExecutors(args) {
|
|
|
4255
4023
|
};
|
|
4256
4024
|
}
|
|
4257
4025
|
|
|
4026
|
+
// packages/agent-runtime/capabilities/execShellCapability.ts
|
|
4027
|
+
function buildExecShellToolDefinition(toolName = "execShell") {
|
|
4028
|
+
return {
|
|
4029
|
+
type: "function",
|
|
4030
|
+
function: {
|
|
4031
|
+
name: toolName,
|
|
4032
|
+
description: `Execute a shell command from the workspace root. Prefer one compound command (e.g. 'git status && git diff --stat') to perform complete verification in one step instead of multiple small roundtrips. Do not cd into guessed paths; commands already run from the workspace root. Commands block until exit. Long-running commands (sleep over ${IMMEDIATE_DETACH_SLEEP_THRESHOLD_SECONDS}s, dev servers, watchers) automatically detach to background returning {detached: true, pid, label}; for persistent services, prefer launchProcess.`,
|
|
4033
|
+
parameters: {
|
|
4034
|
+
type: "object",
|
|
4035
|
+
properties: {
|
|
4036
|
+
command: {
|
|
4037
|
+
type: "string",
|
|
4038
|
+
description: "Shell command to run (non-empty)."
|
|
4039
|
+
},
|
|
4040
|
+
cmd: {
|
|
4041
|
+
type: "string",
|
|
4042
|
+
description: "Compatibility alias for command."
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
};
|
|
4048
|
+
}
|
|
4049
|
+
function normalizeExecShellInput(input) {
|
|
4050
|
+
if (input === null || input === void 0) {
|
|
4051
|
+
throw new Error("execShell requires a non-empty command.");
|
|
4052
|
+
}
|
|
4053
|
+
if (typeof input === "string") {
|
|
4054
|
+
const trimmed = input.trim();
|
|
4055
|
+
if (!trimmed) {
|
|
4056
|
+
throw new Error("execShell requires a non-empty command.");
|
|
4057
|
+
}
|
|
4058
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
4059
|
+
try {
|
|
4060
|
+
const parsed = JSON.parse(trimmed);
|
|
4061
|
+
if (parsed && typeof parsed === "object") {
|
|
4062
|
+
return normalizeExecShellInput(parsed);
|
|
4063
|
+
}
|
|
4064
|
+
} catch {
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
return { command: trimmed };
|
|
4068
|
+
}
|
|
4069
|
+
if (typeof input === "object") {
|
|
4070
|
+
const record = input;
|
|
4071
|
+
let rawCommand = resolveShellCommandArg(record);
|
|
4072
|
+
if (typeof rawCommand !== "string" || !rawCommand.trim()) {
|
|
4073
|
+
const fallback = record.command ?? record.cmd;
|
|
4074
|
+
if (typeof fallback === "string" && fallback.trim()) {
|
|
4075
|
+
rawCommand = fallback;
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
const command = typeof rawCommand === "string" ? rawCommand.trim() : "";
|
|
4079
|
+
if (!command) {
|
|
4080
|
+
throw new Error("execShell requires a non-empty command.");
|
|
4081
|
+
}
|
|
4082
|
+
const activity = extractActivity(record);
|
|
4083
|
+
return {
|
|
4084
|
+
...record,
|
|
4085
|
+
command,
|
|
4086
|
+
...record.shell !== void 0 ? { shell: record.shell } : {},
|
|
4087
|
+
...activity ? { activity } : {}
|
|
4088
|
+
};
|
|
4089
|
+
}
|
|
4090
|
+
throw new Error("execShell requires a non-empty command.");
|
|
4091
|
+
}
|
|
4092
|
+
var execShellCapability = {
|
|
4093
|
+
name: "execShell",
|
|
4094
|
+
getToolDefinition(toolName = "execShell") {
|
|
4095
|
+
return buildExecShellToolDefinition(toolName);
|
|
4096
|
+
},
|
|
4097
|
+
normalizeInput(input) {
|
|
4098
|
+
return normalizeExecShellInput(input);
|
|
4099
|
+
},
|
|
4100
|
+
async invoke(ctx, input) {
|
|
4101
|
+
const normalized = input && typeof input === "object" && typeof input.command === "string" && input.command.trim() ? input : normalizeExecShellInput(input);
|
|
4102
|
+
const command = normalized.command;
|
|
4103
|
+
const workspaceRoot = ctx.workspaceRoot || process.cwd();
|
|
4104
|
+
if (ctx.restrictToWorkspace) {
|
|
4105
|
+
const escapeToken = findWorkspaceShellEscapeToken(command);
|
|
4106
|
+
if (escapeToken) {
|
|
4107
|
+
return buildWorkspaceShellEscapeBlockedResult({ command, token: escapeToken });
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
const interactiveAuthCommand = extractInteractiveGhAuthCommand(command);
|
|
4111
|
+
if (interactiveAuthCommand) {
|
|
4112
|
+
const blocked = buildInteractiveCommandBlockedResult(interactiveAuthCommand);
|
|
4113
|
+
return {
|
|
4114
|
+
...blocked,
|
|
4115
|
+
metadata: {
|
|
4116
|
+
...blocked.metadata,
|
|
4117
|
+
...normalized.activity ? { activity: normalized.activity } : {}
|
|
4118
|
+
}
|
|
4119
|
+
};
|
|
4120
|
+
}
|
|
4121
|
+
const result = await runWorkspaceCommand({
|
|
4122
|
+
workspaceRoot,
|
|
4123
|
+
command: buildWorkspaceShellCommand({
|
|
4124
|
+
toolName: "execShell",
|
|
4125
|
+
command,
|
|
4126
|
+
shell: normalized.shell
|
|
4127
|
+
}),
|
|
4128
|
+
timeoutMs: resolveExecShellTimeoutMs(ctx.commandTimeoutMs),
|
|
4129
|
+
outputLimit: ctx.commandOutputLimit,
|
|
4130
|
+
commandPrefix: ctx.commandPrefix,
|
|
4131
|
+
abortSignal: ctx.abortSignal,
|
|
4132
|
+
detachMs: isImmediateDetachShellCommand({ command }) ? 0 : ctx.detachMs
|
|
4133
|
+
});
|
|
4134
|
+
return {
|
|
4135
|
+
content: result.content,
|
|
4136
|
+
metadata: {
|
|
4137
|
+
command,
|
|
4138
|
+
exitCode: result.exitCode,
|
|
4139
|
+
timedOut: result.timedOut,
|
|
4140
|
+
...result.aborted ? { aborted: true } : {},
|
|
4141
|
+
...result.detached ? { detached: true, pid: result.pid, label: result.label, status: "running" } : {},
|
|
4142
|
+
...normalized.activity ? { activity: normalized.activity } : {}
|
|
4143
|
+
}
|
|
4144
|
+
};
|
|
4145
|
+
}
|
|
4146
|
+
};
|
|
4147
|
+
|
|
4148
|
+
// packages/agent-runtime/localToolPolicy.ts
|
|
4149
|
+
var NEVER_LOCAL_TOOLS = /* @__PURE__ */ new Set([
|
|
4150
|
+
"deleteSpaces",
|
|
4151
|
+
"updateAgent",
|
|
4152
|
+
"updateSelf",
|
|
4153
|
+
"createAgent"
|
|
4154
|
+
]);
|
|
4155
|
+
var REMOVED_LOCAL_TOOLS = /* @__PURE__ */ new Set([
|
|
4156
|
+
"gitStatus",
|
|
4157
|
+
"gitDiff",
|
|
4158
|
+
"gitCreateBranch",
|
|
4159
|
+
"gitAdd",
|
|
4160
|
+
"gitCommit",
|
|
4161
|
+
"commitWorkspace"
|
|
4162
|
+
]);
|
|
4163
|
+
var DEFAULT_LOCAL_TOOLS = /* @__PURE__ */ new Set([
|
|
4164
|
+
"listFiles",
|
|
4165
|
+
"readFile",
|
|
4166
|
+
"writeFile",
|
|
4167
|
+
"editFile",
|
|
4168
|
+
"globFiles",
|
|
4169
|
+
"searchFiles",
|
|
4170
|
+
"execShell",
|
|
4171
|
+
"launchProcess",
|
|
4172
|
+
"listProcesses",
|
|
4173
|
+
// Scheduling tools: forking/observing sub-agents is a declared capability
|
|
4174
|
+
// of capable agents, and the blast radius is strictly narrower than
|
|
4175
|
+
// execShell (already default-enabled). Requiring an env allowlist for these
|
|
4176
|
+
// while execShell is open by default inverted the risk model and forced every
|
|
4177
|
+
// new local user to hand-edit .env files before sub-agent tasks worked.
|
|
4178
|
+
"startAgentRun",
|
|
4179
|
+
"controlAgentRun"
|
|
4180
|
+
]);
|
|
4181
|
+
function parseToolAllowlist(value) {
|
|
4182
|
+
return (value ?? "").split(",").flatMap((item) => {
|
|
4183
|
+
const trimmed = item.trim();
|
|
4184
|
+
return trimmed ? [trimmed] : [];
|
|
4185
|
+
});
|
|
4186
|
+
}
|
|
4187
|
+
function normalizeLocalToolName(toolName) {
|
|
4188
|
+
return canonicalizeToolName(
|
|
4189
|
+
String(toolName ?? "").replace(/^functions\./, "").trim()
|
|
4190
|
+
);
|
|
4191
|
+
}
|
|
4192
|
+
function parseShellCommandPayload(rawArguments) {
|
|
4193
|
+
try {
|
|
4194
|
+
const normalized = normalizeExecShellInput(rawArguments);
|
|
4195
|
+
return {
|
|
4196
|
+
command: normalized.command,
|
|
4197
|
+
input: normalized.input
|
|
4198
|
+
};
|
|
4199
|
+
} catch {
|
|
4200
|
+
return parseToolArgumentsJson(typeof rawArguments === "string" ? rawArguments : "");
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
function toDisplayCommand(command) {
|
|
4204
|
+
if (typeof command === "string") {
|
|
4205
|
+
const trimmed = command.trim();
|
|
4206
|
+
return trimmed ? trimmed : void 0;
|
|
4207
|
+
}
|
|
4208
|
+
if (Array.isArray(command)) {
|
|
4209
|
+
const joined = command.map((item) => typeof item === "string" ? item : String(item)).filter((item) => item.length > 0).join(" ").trim();
|
|
4210
|
+
return joined ? joined : void 0;
|
|
4211
|
+
}
|
|
4212
|
+
return void 0;
|
|
4213
|
+
}
|
|
4214
|
+
function isRestrictedLocalToolMode(env) {
|
|
4215
|
+
return env.NOLO_LOCAL_TOOL_MODE === "restricted";
|
|
4216
|
+
}
|
|
4217
|
+
function resolveLocalToolPolicy(args) {
|
|
4218
|
+
const toolName = normalizeLocalToolName(args.toolName);
|
|
4219
|
+
const restrictedMode = isRestrictedLocalToolMode(args.env);
|
|
4220
|
+
if (!toolName) {
|
|
4221
|
+
return {
|
|
4222
|
+
allowed: false,
|
|
4223
|
+
toolName,
|
|
4224
|
+
reason: "Tool name is required."
|
|
4225
|
+
};
|
|
4226
|
+
}
|
|
4227
|
+
if (NEVER_LOCAL_TOOLS.has(toolName)) {
|
|
4228
|
+
return {
|
|
4229
|
+
allowed: false,
|
|
4230
|
+
toolName,
|
|
4231
|
+
reason: `${toolName} is blocked by the local runtime safety policy.`
|
|
4232
|
+
};
|
|
4233
|
+
}
|
|
4234
|
+
if (REMOVED_LOCAL_TOOLS.has(toolName)) {
|
|
4235
|
+
return {
|
|
4236
|
+
allowed: false,
|
|
4237
|
+
toolName,
|
|
4238
|
+
reason: `${toolName} has been removed from the local runtime tool surface. Use execShell in shell-enabled workspace runs when shell is needed.`
|
|
4239
|
+
};
|
|
4240
|
+
}
|
|
4241
|
+
const allowedByEnv = new Set(parseToolAllowlist(args.env.NOLO_LOCAL_ALLOWED_TOOLS));
|
|
4242
|
+
const agentTools = new Set(
|
|
4243
|
+
(args.agentToolNames ?? []).flatMap((item) => {
|
|
4244
|
+
const normalized = normalizeLocalToolName(item);
|
|
4245
|
+
return normalized ? [normalized] : [];
|
|
4246
|
+
})
|
|
4247
|
+
);
|
|
4248
|
+
if (!restrictedMode && agentTools.has(toolName)) {
|
|
4249
|
+
return { allowed: true, toolName };
|
|
4250
|
+
}
|
|
4251
|
+
if (DEFAULT_LOCAL_TOOLS.has(toolName) && agentTools.has(toolName)) {
|
|
4252
|
+
return { allowed: true, toolName };
|
|
4253
|
+
}
|
|
4254
|
+
if (allowedByEnv.has(toolName) && agentTools.has(toolName)) {
|
|
4255
|
+
return { allowed: true, toolName };
|
|
4256
|
+
}
|
|
4257
|
+
return {
|
|
4258
|
+
allowed: false,
|
|
4259
|
+
toolName,
|
|
4260
|
+
reason: `${toolName} is not enabled for local runtime runs. The agent must declare the tool in its manifest; in restricted mode (NOLO_LOCAL_TOOL_MODE=restricted) non-default tools also require adding to NOLO_LOCAL_ALLOWED_TOOLS (e.g. NOLO_LOCAL_ALLOWED_TOOLS=someCustomTool) and restarting the session so the env var loads.`
|
|
4261
|
+
};
|
|
4262
|
+
}
|
|
4263
|
+
var ABORT_AWARE_LOCAL_TOOLS = /* @__PURE__ */ new Set(["execShell", "controlAgentRun"]);
|
|
4264
|
+
async function executeLocalToolWithPolicy(args) {
|
|
4265
|
+
const decision = resolveLocalToolPolicy({
|
|
4266
|
+
env: args.env,
|
|
4267
|
+
agentToolNames: args.agentToolNames,
|
|
4268
|
+
toolName: args.call.name
|
|
4269
|
+
});
|
|
4270
|
+
if (!decision.allowed) throw new Error(decision.reason);
|
|
4271
|
+
const executor = args.executors?.[decision.toolName];
|
|
4272
|
+
if (!executor) {
|
|
4273
|
+
throw new Error(`${decision.toolName} is allowed by policy but no local executor is registered.`);
|
|
4274
|
+
}
|
|
4275
|
+
const effectiveExecutor = args.abortSignal && ABORT_AWARE_LOCAL_TOOLS.has(decision.toolName) && args.executors ? (call) => executor(call, {
|
|
4276
|
+
abortSignal: args.abortSignal,
|
|
4277
|
+
detachMs: args.detachMs
|
|
4278
|
+
}) : executor;
|
|
4279
|
+
const guardEnabled = args.enableDestructiveShellGuard !== false;
|
|
4280
|
+
if (decision.toolName === "execShell" && !args.confirmed && guardEnabled) {
|
|
4281
|
+
const parsed = parseShellCommandPayload(args.call.arguments);
|
|
4282
|
+
const rawCommand = resolveShellCommandArg(parsed);
|
|
4283
|
+
if (isDestructiveShellCommand({ command: rawCommand, input: parsed.input })) {
|
|
4284
|
+
const command = toDisplayCommand(rawCommand);
|
|
4285
|
+
const request = {
|
|
4286
|
+
id: "permission-shell-destructive-action",
|
|
4287
|
+
tool: "execShell",
|
|
4288
|
+
action: "destructive_shell_command",
|
|
4289
|
+
title: "\u786E\u8BA4\u6267\u884C\u7834\u574F\u6027 shell \u547D\u4EE4",
|
|
4290
|
+
body: "\u8BE5\u547D\u4EE4\u53EF\u80FD\u5220\u9664\u6216\u91CD\u7F6E\u7528\u6237\u5185\u5BB9\uFF0C\u9700\u8981\u7528\u6237\u660E\u786E\u786E\u8BA4\u540E\u624D\u80FD\u6267\u884C\u3002",
|
|
4291
|
+
...command ? { command } : {},
|
|
4292
|
+
suggestedRule: {
|
|
4293
|
+
scope: "once",
|
|
4294
|
+
pattern: { capability: "destructive_action", target: "shell_command" }
|
|
4295
|
+
}
|
|
4296
|
+
};
|
|
4297
|
+
const buildBlockedError = (reason) => {
|
|
4298
|
+
const error = new Error(reason);
|
|
4299
|
+
error.code = "destructive_action_requires_confirmation";
|
|
4300
|
+
error.policy = {
|
|
4301
|
+
capability: "destructive_action",
|
|
4302
|
+
target: "shell_command",
|
|
4303
|
+
detail: "execShell destructive command"
|
|
4304
|
+
};
|
|
4305
|
+
error.permissionRequest = request;
|
|
4306
|
+
return error;
|
|
4307
|
+
};
|
|
4308
|
+
if (!args.confirmDestructiveAction) {
|
|
4309
|
+
if (args.blockDestructiveWithoutConfirmation) {
|
|
4310
|
+
throw buildBlockedError(
|
|
4311
|
+
"destructive shell command blocked: no confirmation channel available"
|
|
4312
|
+
);
|
|
4313
|
+
}
|
|
4314
|
+
return effectiveExecutor({
|
|
4315
|
+
...args.call,
|
|
4316
|
+
name: decision.toolName
|
|
4317
|
+
});
|
|
4318
|
+
}
|
|
4319
|
+
const confirmed = await args.confirmDestructiveAction(request);
|
|
4320
|
+
if (!confirmed) {
|
|
4321
|
+
throw buildBlockedError(
|
|
4322
|
+
"destructive shell command blocked: user declined confirmation"
|
|
4323
|
+
);
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
return effectiveExecutor({
|
|
4328
|
+
...args.call,
|
|
4329
|
+
name: decision.toolName
|
|
4330
|
+
});
|
|
4331
|
+
}
|
|
4332
|
+
|
|
4258
4333
|
// packages/cli/client/localDialogRecords.ts
|
|
4259
4334
|
var localDialogMessageRecordToRuntimeMessage = dialogMessageRecordToAgentRuntimeMessage;
|
|
4260
4335
|
function buildLocalDialogWritePlan(args) {
|
|
@@ -4458,8 +4533,6 @@ export {
|
|
|
4458
4533
|
createHybridRecordStore,
|
|
4459
4534
|
isDestructiveShellCommand,
|
|
4460
4535
|
evaluateShellCommandPolicy,
|
|
4461
|
-
resolveLocalToolPolicy,
|
|
4462
|
-
executeLocalToolWithPolicy,
|
|
4463
4536
|
filterRetainedLedgerRecords,
|
|
4464
4537
|
isLineRangeCovered,
|
|
4465
4538
|
getProcessRegistry,
|
|
@@ -4467,6 +4540,8 @@ export {
|
|
|
4467
4540
|
buildLocalWorkspacePolicyToolNames,
|
|
4468
4541
|
buildLocalWorkspaceOpenAiTools,
|
|
4469
4542
|
createLocalWorkspaceToolExecutors,
|
|
4543
|
+
resolveLocalToolPolicy,
|
|
4544
|
+
executeLocalToolWithPolicy,
|
|
4470
4545
|
localDialogMessageRecordToRuntimeMessage,
|
|
4471
4546
|
buildLocalDialogWritePlan,
|
|
4472
4547
|
buildNoloWorkspaceCliToolExecutors,
|