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.
Files changed (80) hide show
  1. package/{_executeModel-RI6DRQTP.js → _executeModel-2WR4RTZZ.js} +22 -22
  2. package/{action-SMLG6OTB.js → action-OQWPNKAH.js} +4 -4
  3. package/{addContentAction-SFO244QN.js → addContentAction-YVINZQTF.js} +3 -3
  4. package/{addDialogAgentAction-W44ZCEHB.js → addDialogAgentAction-6IOCIEPI.js} +2 -2
  5. package/{agentDeleteCommand-F2P4PF6R.js → agentDeleteCommand-H22WRYC6.js} +9 -9
  6. package/{agentEmailCommands-BW6ZDQ3C.js → agentEmailCommands-DGJVCMCG.js} +24 -24
  7. package/{agentListCommands-IC3GH45S.js → agentListCommands-6NWMLIMQ.js} +9 -9
  8. package/{agentMachineCommands-LGYGQX75.js → agentMachineCommands-5MUO6RDI.js} +10 -10
  9. package/{agentRecordCommands-FCQ7ZF6C.js → agentRecordCommands-YMGKT2WF.js} +24 -24
  10. package/{agentRunCommand-F5L6GPSK.js → agentRunCommand-LYEMVQJH.js} +24 -24
  11. package/{agentRuntimeLocal-B7SHOEHJ.js → agentRuntimeLocal-PRAR3NCW.js} +9 -9
  12. package/{agentSlice-GMKJKCPY.js → agentSlice-AMDSTXP5.js} +3 -3
  13. package/{appDeleteCommands-63VCZ5CT.js → appDeleteCommands-L5PKC42V.js} +11 -11
  14. package/{appDeployCommands-YFTG2P3K.js → appDeployCommands-3J7Z4XWV.js} +11 -11
  15. package/{appDeployStatusCommands-R5ENU5CQ.js → appDeployStatusCommands-YTRPXE72.js} +11 -11
  16. package/{appGetCommands-3CH3VMUB.js → appGetCommands-JVXV5WXD.js} +11 -11
  17. package/{appListCommands-Q2HVYG7J.js → appListCommands-7KDEOJSO.js} +11 -11
  18. package/{chatQueueReduxAdapter-ZRFKEKD3.js → chatQueueReduxAdapter-ZKSS6XRV.js} +9 -9
  19. package/{chunk-TFPUAELY.js → chunk-3RUW3ONU.js} +1 -1
  20. package/{chunk-VH2F7ZB6.js → chunk-3UG2IIZ3.js} +1 -1
  21. package/{chunk-JTSGWTYZ.js → chunk-4F5M2MW5.js} +1 -1
  22. package/{chunk-LZC5YX6P.js → chunk-5X4GQ53C.js} +4 -4
  23. package/{chunk-JZ5AMMXU.js → chunk-6YXDOOSH.js} +2 -2
  24. package/{chunk-37V5LNEQ.js → chunk-75U775RZ.js} +1 -1
  25. package/{chunk-JCGIUBE3.js → chunk-7INB7NXF.js} +8 -8
  26. package/{chunk-W7J5HZI7.js → chunk-7L5CQQIT.js} +3 -3
  27. package/{chunk-NKFMFT4W.js → chunk-AN7DGYMW.js} +440 -365
  28. package/{chunk-XCCGU6QB.js → chunk-ASDXPOJH.js} +1 -1
  29. package/{chunk-MUCM2MZB.js → chunk-BQ3OHLR5.js} +20 -20
  30. package/{chunk-XL7BR2GE.js → chunk-BXPBZVRE.js} +18 -18
  31. package/{chunk-OOK2ZT5F.js → chunk-D5JI6VDM.js} +4 -4
  32. package/{chunk-AOXVOC6D.js → chunk-DKMOY3QI.js} +2 -2
  33. package/{chunk-RTJWH72P.js → chunk-DNQK7M7K.js} +3 -3
  34. package/{chunk-6FNS3SQF.js → chunk-DSBGWT2C.js} +3 -3
  35. package/{chunk-PVVAWIUN.js → chunk-EMW55KKX.js} +1 -1
  36. package/{chunk-4C7K5NXX.js → chunk-GJXRIOLS.js} +1 -1
  37. package/{chunk-XFV2VK7O.js → chunk-GOIDVXK5.js} +1 -1
  38. package/{chunk-IL545V3E.js → chunk-IKWTTKWG.js} +1 -1
  39. package/{chunk-UK5JGQVU.js → chunk-IR6W62Q6.js} +8 -0
  40. package/{chunk-2GKZGZBN.js → chunk-MQWVAOIU.js} +10 -10
  41. package/{chunk-CHYAYZJH.js → chunk-RJKIB55Y.js} +1 -1
  42. package/{chunk-PREQU5QV.js → chunk-RPRRIFXV.js} +1 -1
  43. package/{chunk-O6W5PZXA.js → chunk-UEAUAXYS.js} +9 -9
  44. package/{chunk-57QJXIB6.js → chunk-UM4H3URE.js} +3 -3
  45. package/{chunk-CLP2K7U4.js → chunk-UNOTBWZZ.js} +2 -2
  46. package/{chunk-KBZCACG7.js → chunk-VWY2CZD2.js} +1 -1
  47. package/{chunk-YBPAQV37.js → chunk-WNHST4IE.js} +11 -11
  48. package/{chunk-VOBUVWP5.js → chunk-XLLTJXAD.js} +8 -8
  49. package/{chunk-5SBK2MTI.js → chunk-ZWS6DUHX.js} +18 -66
  50. package/{createAgentAutomationAction-6SE7CQ7F.js → createAgentAutomationAction-GVSJL2CI.js} +10 -10
  51. package/{createDialogAction-AROAXAPM.js → createDialogAction-VVR46Q2A.js} +11 -11
  52. package/{createPageAction-GCO3R6GR.js → createPageAction-M2VDKBYS.js} +10 -10
  53. package/{dbSlice-3SD5L5H2.js → dbSlice-UI2VCBLG.js} +2 -2
  54. package/{dialogCommands-N26NNRRD.js → dialogCommands-26DBYNBT.js} +9 -9
  55. package/{dialogSlice-JVPBOMFB.js → dialogSlice-FFGLRCBL.js} +9 -9
  56. package/{docCreateCommands-PON6FF4V.js → docCreateCommands-F2ASWG6B.js} +10 -10
  57. package/{docDeleteCommands-WWRFONDS.js → docDeleteCommands-A4URDZPI.js} +10 -10
  58. package/{docReadCommands-S4Z275LB.js → docReadCommands-DO6VHDZD.js} +10 -10
  59. package/{docStore-WGDO3I76.js → docStore-EDAE7RNT.js} +11 -11
  60. package/{docUpdateCommands-TJQB4IJ7.js → docUpdateCommands-ZTXZ3RTP.js} +10 -10
  61. package/{handleSendMessageAction-T7AS7DDY.js → handleSendMessageAction-CIRLGHOC.js} +9 -9
  62. package/index.js +41 -41
  63. package/{machineCommands-C6CPXZRR.js → machineCommands-NIQNCYJV.js} +24 -24
  64. package/{machineWsRunDispatch-27K2P4OP.js → machineWsRunDispatch-EMTSQGAR.js} +24 -24
  65. package/package.json +1 -1
  66. package/{read-6G73BMEY.js → read-2VHEHCAN.js} +1 -1
  67. package/{readDocTool-DK4EI2GA.js → readDocTool-CSWD6NPC.js} +1 -1
  68. package/{readlineWorkspace-LBWKTPJE.js → readlineWorkspace-A2JBJWLI.js} +24 -24
  69. package/{removeDialogAgentAction-E7H557UO.js → removeDialogAgentAction-2T7BPM3T.js} +4 -4
  70. package/{runAgentClientLoop-D2T2P36H.js → runAgentClientLoop-BV4736ED.js} +19 -19
  71. package/{setDialogExtraReferencesAction-RMMGBR5F.js → setDialogExtraReferencesAction-GPT37HTW.js} +3 -3
  72. package/{setPrimaryDialogAgentAction-FWBKKLVA.js → setPrimaryDialogAgentAction-3746P7IW.js} +4 -4
  73. package/{settingSlice-73PV4KG4.js → settingSlice-FIFOQ6OR.js} +3 -3
  74. package/{spaceCommands-B6LO4U5R.js → spaceCommands-AYZQHDB3.js} +9 -9
  75. package/{spaceSlice-VUAEZVDS.js → spaceSlice-43BNF22B.js} +10 -10
  76. package/{streamAgentChatTurn-BBPSFMTZ.js → streamAgentChatTurn-OLLRSITV.js} +22 -22
  77. package/{switchDialogAgentAction-US6QNL3N.js → switchDialogAgentAction-MQTUTQHL.js} +2 -2
  78. package/{tools-NWA3J7KE.js → tools-2SI3FXLL.js} +16 -16
  79. package/{tuiSummaryLlmCaller-KC7JCJBB.js → tuiSummaryLlmCaller-EAWQ3IZX.js} +5 -5
  80. 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-CHYAYZJH.js";
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-OOK2ZT5F.js";
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-5SBK2MTI.js";
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,