@sjawhar/pi-legion-envoy 5.17.1 → 5.17.3

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 (2) hide show
  1. package/dist/legion.js +371 -107
  2. package/package.json +1 -1
package/dist/legion.js CHANGED
@@ -33473,7 +33473,7 @@ import { logger } from "@oh-my-pi/pi-utils";
33473
33473
  // package.json
33474
33474
  var package_default = {
33475
33475
  name: "@sjawhar/pi-legion-envoy",
33476
- version: "5.17.1",
33476
+ version: "5.17.3",
33477
33477
  type: "module",
33478
33478
  omp: {
33479
33479
  extensions: [
@@ -41507,6 +41507,51 @@ var FILE_COMMANDS = new Set([
41507
41507
  "chgrp"
41508
41508
  ]);
41509
41509
  var SIGNAL_COMMANDS = new Set(["kill", "pkill", "killall", "killall5"]);
41510
+ var FIND_VALUED_PREDICATES = new Set([
41511
+ "-amin",
41512
+ "-anewer",
41513
+ "-atime",
41514
+ "-cmin",
41515
+ "-cnewer",
41516
+ "-ctime",
41517
+ "-fls",
41518
+ "-fprint",
41519
+ "-fprint0",
41520
+ "-fprintf",
41521
+ "-fstype",
41522
+ "-gid",
41523
+ "-group",
41524
+ "-ilname",
41525
+ "-iname",
41526
+ "-inum",
41527
+ "-ipath",
41528
+ "-iregex",
41529
+ "-iwholename",
41530
+ "-links",
41531
+ "-lname",
41532
+ "-maxdepth",
41533
+ "-mindepth",
41534
+ "-mmin",
41535
+ "-mtime",
41536
+ "-name",
41537
+ "-newer",
41538
+ "-newerat",
41539
+ "-newerct",
41540
+ "-newermt",
41541
+ "-path",
41542
+ "-perm",
41543
+ "-printf",
41544
+ "-regex",
41545
+ "-regextype",
41546
+ "-samefile",
41547
+ "-size",
41548
+ "-type",
41549
+ "-uid",
41550
+ "-used",
41551
+ "-user",
41552
+ "-wholename",
41553
+ "-xtype"
41554
+ ]);
41510
41555
  var FUNCTION_OUTPUT_SILENT = {
41511
41556
  ":": true,
41512
41557
  break: true,
@@ -42166,6 +42211,43 @@ function literalText(exp) {
42166
42211
  return;
42167
42212
  return exp.map((piece) => piece.text).join("");
42168
42213
  }
42214
+ function readableWord(arg) {
42215
+ const whole = literalText(arg.exp);
42216
+ if (whole !== undefined)
42217
+ return { text: whole, whole: true };
42218
+ let text = "";
42219
+ for (const piece of arg.exp) {
42220
+ if (piece.kind !== "literal")
42221
+ break;
42222
+ text += piece.text;
42223
+ }
42224
+ return { text, whole: false };
42225
+ }
42226
+ function mayBeOption(arg, matches) {
42227
+ const { text, whole } = readableWord(arg);
42228
+ if (whole)
42229
+ return matches(text);
42230
+ return text === "" || /^--?[a-zA-Z-]*$/.test(text);
42231
+ }
42232
+ function valueInWord(arg, count) {
42233
+ const pieces = [];
42234
+ let dropped = 0;
42235
+ for (const piece of arg.exp) {
42236
+ if (dropped >= count) {
42237
+ pieces.push(piece);
42238
+ continue;
42239
+ }
42240
+ if (piece.kind !== "literal")
42241
+ return;
42242
+ if (piece.text.length + dropped <= count) {
42243
+ dropped += piece.text.length;
42244
+ continue;
42245
+ }
42246
+ pieces.push({ ...piece, text: piece.text.slice(count - dropped) });
42247
+ dropped = count;
42248
+ }
42249
+ return dropped < count || pieces.length === 0 ? undefined : pieces;
42250
+ }
42169
42251
  function visitParts(parts, st, ctx) {
42170
42252
  for (const part of parts ?? []) {
42171
42253
  switch (part.type) {
@@ -42174,9 +42256,11 @@ function visitParts(parts, st, ctx) {
42174
42256
  visitParts(part.parts, st, ctx);
42175
42257
  break;
42176
42258
  case "CommandExpansion":
42177
- case "ProcessSubstitution":
42178
42259
  walkSubstitution(part.script, part.inner, part.text, st, ctx);
42179
42260
  break;
42261
+ case "ProcessSubstitution":
42262
+ uncertainly(ctx, () => walkSubstitution(part.script, part.inner, part.text, st, ctx));
42263
+ break;
42180
42264
  case "ArithmeticExpansion":
42181
42265
  visitArithmetic(part.expression, st, ctx);
42182
42266
  break;
@@ -42396,10 +42480,47 @@ function merge2(target, branches) {
42396
42480
  if (target.traps.includes(trap))
42397
42481
  continue;
42398
42482
  merged ??= new Map(target.vars);
42399
- lifted.push({ ...trap, vars: handlerVars(trap, branch.vars), merged });
42483
+ lifted.push({ ...trap, vars: handlerVars(trap, branch.vars), merged, uncertainSet: true });
42400
42484
  }
42401
42485
  }
42402
- target.traps = [...target.traps, ...lifted];
42486
+ target.traps = [
42487
+ ...target.traps.map((trap) => branches.every((branch) => branch.traps.includes(trap)) ? trap : { ...trap, uncertainSet: true }),
42488
+ ...lifted
42489
+ ];
42490
+ }
42491
+ function uncertainly(ctx, walk) {
42492
+ ctx.uncertain.depth += 1;
42493
+ try {
42494
+ walk();
42495
+ } finally {
42496
+ ctx.uncertain.depth -= 1;
42497
+ }
42498
+ }
42499
+ function modelWrite(st, ctx, file2, content) {
42500
+ st.files.set(file2, ctx.uncertain.depth > 0 ? UNCERTAIN_WRITE : content);
42501
+ }
42502
+ var UNCERTAIN_WRITE = {
42503
+ why: "the write is inside a branch, a loop body, a handler, or a command this shell does not wait " + "for, so the guard cannot know the shell performed it"
42504
+ };
42505
+ var UNRENDERED_WRITE = {
42506
+ why: "the command writes output the guard cannot render"
42507
+ };
42508
+ var OVERLONG_WRITE = {
42509
+ why: `the command writes more than the ${MAX_SCRIPT_BYTES} bytes the guard reads of a script`
42510
+ };
42511
+ var UNREAD_FILE = {
42512
+ why: "the command appends to a file it has not written, whose contents on disk the guard never reads"
42513
+ };
42514
+ function modelPidFile(st, ctx, file2, pid) {
42515
+ const model = st.pidFiles;
42516
+ const before = model.get(file2);
42517
+ model.delete(file2);
42518
+ if (ctx.uncertain.depth === 0) {
42519
+ if (pid !== undefined)
42520
+ model.set(file2, pid);
42521
+ } else if (pid !== undefined && before !== undefined) {
42522
+ model.set(file2, [unknown2("a pid a branch or loop body wrote to this file", true)]);
42523
+ }
42403
42524
  }
42404
42525
  function handlerVars(trap, vars) {
42405
42526
  const { vars: branch, merged } = trap;
@@ -42444,11 +42565,17 @@ function subshell(st, patch) {
42444
42565
  function runSetTraps(st, ctx) {
42445
42566
  for (const trap of st.traps) {
42446
42567
  const vars = new Map([...handlerVars(trap, st.vars), ...trap.pids ?? []]);
42447
- try {
42448
- runText(trap.text, "the EXIT trap", subshell(st, { source: trap.file, vars }), ctx, trap.site);
42449
- } catch (error48) {
42450
- locateRefusal(error48, trap.file);
42451
- }
42568
+ const run = () => {
42569
+ try {
42570
+ runText(trap.text, "the EXIT trap", subshell(st, { source: trap.file, vars }), ctx, trap.site);
42571
+ } catch (error48) {
42572
+ locateRefusal(error48, trap.file);
42573
+ }
42574
+ };
42575
+ if (trap.signals.includes("EXIT") && trap.uncertainSet !== true)
42576
+ run();
42577
+ else
42578
+ uncertainly(ctx, run);
42452
42579
  }
42453
42580
  }
42454
42581
  function walkList(statements, st, ctx) {
@@ -42459,8 +42586,10 @@ var MAX_DECIDED_PASSES = 64;
42459
42586
  function walkLoopAnyPasses(node, st, ctx) {
42460
42587
  const before = st.output;
42461
42588
  const body = clone2(st);
42462
- walkList(node.clause.commands, body, ctx);
42463
- walkList(node.body.commands, body, ctx);
42589
+ uncertainly(ctx, () => {
42590
+ walkList(node.clause.commands, body, ctx);
42591
+ walkList(node.body.commands, body, ctx);
42592
+ });
42464
42593
  merge2(st, [clone2(st), body]);
42465
42594
  if (outputChanged(before, body.output))
42466
42595
  st.output = undefined;
@@ -42564,8 +42693,10 @@ function walkNode(node, st, ctx, pipeIn) {
42564
42693
  if (node.background) {
42565
42694
  const before = st.output;
42566
42695
  const child = subshell(st);
42567
- walkNode(node.command, child, ctx, pipeIn);
42568
- runSetTraps(child, ctx);
42696
+ uncertainly(ctx, () => {
42697
+ walkNode(node.command, child, ctx, pipeIn);
42698
+ runSetTraps(child, ctx);
42699
+ });
42569
42700
  if (outputChanged(before, child.output))
42570
42701
  st.output = undefined;
42571
42702
  st.backgroundStarted = true;
@@ -42574,7 +42705,7 @@ function walkNode(node, st, ctx, pipeIn) {
42574
42705
  if (node.command.type === "BraceGroup") {
42575
42706
  const written = groupOutputFile(node, st, ctx);
42576
42707
  if (written !== undefined) {
42577
- st.files.set(written, walkRenderingGroup(node.command, st, ctx));
42708
+ modelWrite(st, ctx, written, walkRenderingGroup(node.command, st, ctx) ?? UNRENDERED_WRITE);
42578
42709
  return;
42579
42710
  }
42580
42711
  }
@@ -42592,20 +42723,38 @@ function walkNode(node, st, ctx, pipeIn) {
42592
42723
  }
42593
42724
  const before = st.output;
42594
42725
  const parts = [];
42595
- for (const [index, command] of node.commands.entries()) {
42596
- const part = subshell(st);
42597
- walkNode(command, part, ctx, index > 0);
42598
- runSetTraps(part, ctx);
42599
- parts.push(part);
42600
- }
42726
+ uncertainly(ctx, () => {
42727
+ for (const [index, command] of node.commands.entries()) {
42728
+ const part = subshell(st);
42729
+ walkNode(command, part, ctx, index > 0);
42730
+ runSetTraps(part, ctx);
42731
+ parts.push(part);
42732
+ }
42733
+ });
42601
42734
  if (parts.some((part) => outputChanged(before, part.output)))
42602
42735
  st.output = undefined;
42603
42736
  return;
42604
42737
  }
42605
- case "AndOr":
42606
- for (const command of node.commands)
42607
- walkNode(command, st, ctx, pipeIn);
42738
+ case "AndOr": {
42739
+ const [first, ...rest] = node.commands;
42740
+ if (first !== undefined)
42741
+ walkNode(first, st, ctx, pipeIn);
42742
+ if (rest.length === 0)
42743
+ return;
42744
+ const before = st.output;
42745
+ const branches = [clone2(st)];
42746
+ const running = clone2(st);
42747
+ uncertainly(ctx, () => {
42748
+ for (const command of rest) {
42749
+ walkNode(command, running, ctx, pipeIn);
42750
+ branches.push(clone2(running));
42751
+ }
42752
+ });
42753
+ merge2(st, branches);
42754
+ if (branches.some((branch) => outputChanged(before, branch.output)))
42755
+ st.output = undefined;
42608
42756
  return;
42757
+ }
42609
42758
  case "CompoundList":
42610
42759
  walkList(node.commands, st, ctx);
42611
42760
  return;
@@ -42626,10 +42775,12 @@ function walkNode(node, st, ctx, pipeIn) {
42626
42775
  walkList(node.clause.commands, st, ctx);
42627
42776
  const clauseOutput = st.output;
42628
42777
  const then = clone2(st);
42629
- walkList(node.then.commands, then, ctx);
42630
42778
  const otherwise = clone2(st);
42631
- if (node.else !== undefined)
42632
- walkNode(node.else, otherwise, ctx, pipeIn);
42779
+ uncertainly(ctx, () => {
42780
+ walkList(node.then.commands, then, ctx);
42781
+ if (node.else !== undefined)
42782
+ walkNode(node.else, otherwise, ctx, pipeIn);
42783
+ });
42633
42784
  merge2(st, [then, otherwise]);
42634
42785
  if (outputChanged(before, clauseOutput) || outputChanged(clauseOutput, then.output) || outputChanged(clauseOutput, otherwise.output)) {
42635
42786
  st.output = undefined;
@@ -42649,7 +42800,7 @@ function walkNode(node, st, ctx, pipeIn) {
42649
42800
  for (const value of known) {
42650
42801
  const body = clone2(st);
42651
42802
  assignScalar(body, name, [literal2(value)]);
42652
- walkList(node.body.commands, body, ctx);
42803
+ uncertainly(ctx, () => walkList(node.body.commands, body, ctx));
42653
42804
  branches.push(body);
42654
42805
  }
42655
42806
  } else {
@@ -42661,7 +42812,7 @@ function walkNode(node, st, ctx, pipeIn) {
42661
42812
  assignScalar(body, name, signalSafe ? [unknown2(`\`$${name}\`, a loop variable`, true)] : [unknown2(`\`$${name}\`, a loop variable`)]);
42662
42813
  if (node.type === "Select")
42663
42814
  assignScalar(body, "REPLY", [unknown2("`$REPLY`, read from input")]);
42664
- walkList(node.body.commands, body, ctx);
42815
+ uncertainly(ctx, () => walkList(node.body.commands, body, ctx));
42665
42816
  branches.push(body);
42666
42817
  }
42667
42818
  merge2(st, branches);
@@ -42675,7 +42826,7 @@ function walkNode(node, st, ctx, pipeIn) {
42675
42826
  visitArithmetic(node.test, st, ctx);
42676
42827
  visitArithmetic(node.update, st, ctx);
42677
42828
  const body = clone2(st);
42678
- walkList(node.body.commands, body, ctx);
42829
+ uncertainly(ctx, () => walkList(node.body.commands, body, ctx));
42679
42830
  merge2(st, [clone2(st), body]);
42680
42831
  if (outputChanged(before, body.output))
42681
42832
  st.output = undefined;
@@ -42702,7 +42853,7 @@ function walkNode(node, st, ctx, pipeIn) {
42702
42853
  for (const word of item.pattern)
42703
42854
  visitWord(word, st, ctx);
42704
42855
  const body = clone2(st);
42705
- walkList(item.body.commands, body, ctx);
42856
+ uncertainly(ctx, () => walkList(item.body.commands, body, ctx));
42706
42857
  branches.push(body);
42707
42858
  }
42708
42859
  merge2(st, branches);
@@ -42723,8 +42874,10 @@ function walkNode(node, st, ctx, pipeIn) {
42723
42874
  case "Coproc": {
42724
42875
  const before = st.output;
42725
42876
  const child = subshell(st);
42726
- walkNode(node.body, child, ctx, false);
42727
- runSetTraps(child, ctx);
42877
+ uncertainly(ctx, () => {
42878
+ walkNode(node.body, child, ctx, false);
42879
+ runSetTraps(child, ctx);
42880
+ });
42728
42881
  if (outputChanged(before, child.output))
42729
42882
  st.output = undefined;
42730
42883
  checkRedirects(node.redirects, undefined, siteOf(node, st), st, ctx);
@@ -42773,12 +42926,10 @@ function checkRedirects(redirects, command, site, st, ctx) {
42773
42926
  } else if (command?.name === "printf") {
42774
42927
  content = printfText(command.args, MAX_SCRIPT_BYTES, true) ?? null;
42775
42928
  }
42776
- st.pidFiles.delete(file2);
42777
- const before = appends ? st.files.get(file2) ?? null : "";
42778
- st.files.set(file2, content === null || before === null ? null : readable(`${before}${content}`));
42779
- if (writes && !appends && command?.name === "echo" && command.args.length === 1 && command.args[0]?.exp.length > 0 && command.args[0]?.exp.every((piece) => piece.descendantPid)) {
42780
- st.pidFiles.set(file2, command.args[0].exp);
42781
- }
42929
+ const before = appends ? st.files.get(file2) ?? UNREAD_FILE : "";
42930
+ modelWrite(st, ctx, file2, content === null ? UNRENDERED_WRITE : typeof before !== "string" ? before : readable(`${before}${content}`) ?? OVERLONG_WRITE);
42931
+ const pidWritten = writes && !appends && command?.name === "echo" && command.args.length === 1 && command.args[0]?.exp.length > 0 && command.args[0].exp.every((piece) => piece.descendantPid);
42932
+ modelPidFile(st, ctx, file2, pidWritten ? command?.args[0]?.exp : undefined);
42782
42933
  }
42783
42934
  }
42784
42935
  }
@@ -43018,6 +43169,7 @@ function operands(list, valued, longValued = []) {
43018
43169
  return { options, operands: found };
43019
43170
  }
43020
43171
  function unwrap2(argv, st, ctx, site) {
43172
+ const alts = [];
43021
43173
  let list = argv;
43022
43174
  let state = st;
43023
43175
  for (let guard = 0;guard < 16; guard += 1) {
@@ -43026,13 +43178,19 @@ function unwrap2(argv, st, ctx, site) {
43026
43178
  break;
43027
43179
  const base = path4.basename(name);
43028
43180
  const rest = list.slice(1);
43029
- const skip = (valued, longValued = []) => {
43181
+ const skip = (valued, longValued = [], positional = false) => {
43030
43182
  let i = 0;
43031
43183
  for (;i < rest.length; i += 1) {
43032
- const text = literalText(rest[i]?.exp);
43184
+ const arg = rest[i];
43185
+ const text = literalText(arg.exp);
43033
43186
  if (text === "--")
43034
43187
  return rest.slice(i + 1);
43035
- if (text === undefined || !text.startsWith("-") || text === "-")
43188
+ if (text === undefined) {
43189
+ if (positional || !mayBeOption(arg, (option) => option.startsWith("-")))
43190
+ break;
43191
+ continue;
43192
+ }
43193
+ if (!text.startsWith("-") || text === "-")
43036
43194
  break;
43037
43195
  if (text.startsWith("--")) {
43038
43196
  if (!text.includes("=") && longValued.includes(text))
@@ -43046,8 +43204,10 @@ function unwrap2(argv, st, ctx, site) {
43046
43204
  }
43047
43205
  return rest.slice(i);
43048
43206
  };
43049
- if ((base === "busybox" || base === "toybox") && rest[0] !== undefined)
43207
+ if ((base === "busybox" || base === "toybox") && rest[0] !== undefined && literalText(rest[0].exp) !== undefined) {
43050
43208
  list = rest;
43209
+ continue;
43210
+ }
43051
43211
  if (base === "sudo" || base === "doas")
43052
43212
  list = skip("ughpCDrtUT", ["--user", "--group"]);
43053
43213
  else if (base === "nice")
@@ -43059,15 +43219,23 @@ function unwrap2(argv, st, ctx, site) {
43059
43219
  else if (base === "command") {
43060
43220
  const flags = rest.map((arg) => literalText(arg.exp));
43061
43221
  if (flags[0] === "-v" || flags[0] === "-V")
43062
- return { argv: [], st: state };
43222
+ return { argv: [], st: state, alts };
43063
43223
  list = skip("");
43064
43224
  } else if (base === "stdbuf")
43065
43225
  list = skip("ioe", ["--input", "--output", "--error"]);
43066
43226
  else if (base === "ionice")
43067
43227
  list = skip("cnt", ["--class", "--classdata"]);
43068
43228
  else if (base === "timeout") {
43069
- const after = skip("sk", ["--signal", "--kill-after"]);
43070
- list = after.slice(1);
43229
+ const stopped = skip("sk", ["--signal", "--kill-after"], true);
43230
+ list = stopped.slice(1);
43231
+ for (let at = rest.length - stopped.length;at < rest.length; at += 1) {
43232
+ if (literalText(rest[at]?.exp) !== undefined)
43233
+ break;
43234
+ alts.push(rest.slice(at + 1));
43235
+ }
43236
+ const read = skip("sk", ["--signal", "--kill-after"]);
43237
+ if (read.length !== stopped.length)
43238
+ alts.push(read.slice(1));
43071
43239
  } else if (base === "env") {
43072
43240
  let i = 0;
43073
43241
  const overlay = new Map;
@@ -43107,7 +43275,7 @@ function unwrap2(argv, st, ctx, site) {
43107
43275
  overlay.set(arg.exp[0].text.slice(0, arg.exp[0].text.indexOf("=")), [
43108
43276
  unknown2("an `env` assignment")
43109
43277
  ]);
43110
- } else
43278
+ } else if (text === undefined && mayBeOption(arg, (option) => option.startsWith("-"))) {} else
43111
43279
  break;
43112
43280
  }
43113
43281
  if (i !== -1)
@@ -43124,7 +43292,7 @@ function unwrap2(argv, st, ctx, site) {
43124
43292
  } else
43125
43293
  break;
43126
43294
  }
43127
- return { argv: list, st: state };
43295
+ return { argv: list, st: state, alts };
43128
43296
  }
43129
43297
  function runFunction(name, definition, args2, outer, ctx) {
43130
43298
  if (outer.runningFunctions.has(name))
@@ -43209,6 +43377,9 @@ function dispatch(invocation, outer, ctx) {
43209
43377
  const unwrapped = unwrap2(invocation.args, outer, ctx, invocation.site);
43210
43378
  const argv = unwrapped.argv;
43211
43379
  const st = unwrapped.st;
43380
+ for (const other of unwrapped.alts) {
43381
+ dispatch({ ...invocation, args: other }, clone2(outer), ctx);
43382
+ }
43212
43383
  const name = literalText(argv[0]?.exp);
43213
43384
  if (name === undefined)
43214
43385
  return;
@@ -43222,15 +43393,18 @@ function dispatch(invocation, outer, ctx) {
43222
43393
  runFunction(base, only, rest, outer, ctx);
43223
43394
  return;
43224
43395
  }
43225
- const runs = callee.map((definition) => {
43226
- const run = clone2(outer);
43227
- if (definition !== undefined)
43228
- runFunction(base, definition, rest, run, ctx);
43229
- else {
43230
- run.functions.delete(base);
43231
- dispatch(invocation, run, ctx);
43396
+ const runs = [];
43397
+ uncertainly(ctx, () => {
43398
+ for (const definition of callee) {
43399
+ const run = clone2(outer);
43400
+ if (definition !== undefined)
43401
+ runFunction(base, definition, rest, run, ctx);
43402
+ else {
43403
+ run.functions.delete(base);
43404
+ dispatch(invocation, run, ctx);
43405
+ }
43406
+ runs.push(run);
43232
43407
  }
43233
- return run;
43234
43408
  });
43235
43409
  merge2(outer, runs);
43236
43410
  return;
@@ -43353,15 +43527,21 @@ function dispatch(invocation, outer, ctx) {
43353
43527
  const leading = head[0];
43354
43528
  const spaced = literalText(head) === "-v";
43355
43529
  const joined = !spaced && leading?.kind === "literal" && leading.text.startsWith("-v") && (leading.text.length > 2 || head.length > 1);
43356
- if (spaced || joined) {
43357
- const variable = spaced ? literalText(rest[1]?.exp) : literalText([
43530
+ const first = rest[0];
43531
+ const mayBeV = !spaced && !joined && first !== undefined && rest[1] !== undefined && mayBeOption(first, (text) => text.startsWith("-v"));
43532
+ if (spaced || joined || mayBeV) {
43533
+ const variable = spaced || mayBeV ? literalText(rest[1]?.exp) : literalText([
43358
43534
  { ...leading, text: leading?.text.slice(2) ?? "" },
43359
43535
  ...head.slice(1)
43360
43536
  ]);
43361
- const printed = rest.slice(spaced ? 2 : 1);
43537
+ const printed = rest.slice(spaced || mayBeV ? 2 : 1);
43362
43538
  const [format, value, ...others] = printed;
43363
43539
  let assigned;
43364
- if (literalText(format?.exp) === "%s" && value !== undefined && others.length === 0) {
43540
+ if (mayBeV) {
43541
+ assigned = [
43542
+ unknown2(`\`$${variable ?? "?"}\` (printf with an option word the guard cannot read, which may be \`-v\`)`)
43543
+ ];
43544
+ } else if (literalText(format?.exp) === "%s" && value !== undefined && others.length === 0) {
43365
43545
  assigned = value.exp;
43366
43546
  } else {
43367
43547
  const text = printfText(printed, MAX_VALUE_LENGTH);
@@ -43410,11 +43590,24 @@ function dispatch(invocation, outer, ctx) {
43410
43590
  pids.set(name2, [piece]);
43411
43591
  return `\${${name2}}`;
43412
43592
  }).join("");
43413
- outer.traps = [...outer.traps, { text: handlerText, site, signals: handled, file: file2, pids }];
43593
+ outer.traps = [
43594
+ ...outer.traps,
43595
+ {
43596
+ text: handlerText,
43597
+ site,
43598
+ signals: handled,
43599
+ file: file2,
43600
+ pids,
43601
+ uncertainSet: ctx.uncertain.depth > 0
43602
+ }
43603
+ ];
43414
43604
  } else if (text === undefined) {
43415
43605
  throw new Refusal(site.snippet, site.line, `the guard cannot read the handler this \`trap\` sets, \`${handler.text}\` (${handler.exp.find((piece) => piece.kind !== "literal")?.why ?? "a value the guard cannot know"}), so it cannot check what runs when this shell exits; write the handler out, or put it in a function`);
43416
43606
  } else if (!text.startsWith("-")) {
43417
- outer.traps = [...outer.traps, { text, site, signals: handled, file: file2 }];
43607
+ outer.traps = [
43608
+ ...outer.traps,
43609
+ { text, site, signals: handled, file: file2, uncertainSet: ctx.uncertain.depth > 0 }
43610
+ ];
43418
43611
  }
43419
43612
  return;
43420
43613
  }
@@ -43479,22 +43672,29 @@ function dispatch(invocation, outer, ctx) {
43479
43672
  case "tar":
43480
43673
  case "unzip": {
43481
43674
  const extracting = base === "unzip" || rest.some((arg, index) => {
43482
- const text = literalText(arg.exp);
43483
- return text === "--extract" || text === "--get" || text?.startsWith("-") === true && !text.startsWith("--") && text.includes("x") || index === 0 && /^[A-Za-z]*x[A-Za-z]*$/.test(text ?? "");
43675
+ const { text, whole } = readableWord(arg);
43676
+ if (!whole && index === 0 && /^[A-Za-z]*$/.test(text))
43677
+ return true;
43678
+ return mayBeOption(arg, (mode) => mode === "--extract" || mode === "--get" || mode.startsWith("-") && !mode.startsWith("--") && mode.includes("x") || index === 0 && /^[A-Za-z]*x[A-Za-z]*$/.test(mode));
43484
43679
  });
43485
43680
  if (!extracting)
43486
43681
  return;
43487
43682
  const targetDirectories = [];
43488
43683
  const short = base === "tar" ? "-C" : "-d";
43489
43684
  const long = base === "tar" ? "--directory" : "--destination";
43685
+ const letter = short.slice(1);
43490
43686
  for (const [index, arg] of rest.entries()) {
43491
- const text = literalText(arg.exp);
43492
- if (text === short || text === long) {
43687
+ const { text, whole } = readableWord(arg);
43688
+ const clusterAt = text.startsWith("-") && !text.startsWith("--") ? text.indexOf(letter, 1) : -1;
43689
+ const carried = text.startsWith(`${long}=`) ? valueInWord(arg, long.length + 1) : clusterAt !== -1 && (!whole || text.length > clusterAt + 1) ? valueInWord(arg, clusterAt + 1) : undefined;
43690
+ if (carried !== undefined) {
43691
+ targetDirectories.push({ text: arg.text, exp: carried });
43692
+ continue;
43693
+ }
43694
+ if (clusterAt !== -1 || mayBeOption(arg, (option) => option === short || option === long)) {
43493
43695
  const target = rest[index + 1];
43494
43696
  if (target !== undefined)
43495
43697
  targetDirectories.push(target);
43496
- } else if (text?.startsWith(`${long}=`)) {
43497
- targetDirectories.push({ text: arg.text, exp: [literal2(text.slice(long.length + 1))] });
43498
43698
  }
43499
43699
  }
43500
43700
  checkTargets(base, "extract and overwrite in", targetDirectories, true, st, ctx, site);
@@ -43530,7 +43730,7 @@ function dispatch(invocation, outer, ctx) {
43530
43730
  const file2 = path4.resolve(st.cwd, target);
43531
43731
  st.pidFiles.delete(file2);
43532
43732
  const text = heredoc === undefined ? null : heredocText(heredoc);
43533
- st.files.set(file2, append || text === null ? null : readable(text));
43733
+ modelWrite(st, ctx, file2, append ? UNREAD_FILE : text === null ? UNRENDERED_WRITE : readable(text) ?? OVERLONG_WRITE);
43534
43734
  }
43535
43735
  }
43536
43736
  return;
@@ -43542,41 +43742,73 @@ function dispatch(invocation, outer, ctx) {
43542
43742
  case "killall":
43543
43743
  case "killall5":
43544
43744
  throw new Refusal(site.snippet, site.line, `${base} selects processes by name or pattern, which reaches other agents' processes on a ` + "shared machine; a pane signals only processes it started. Stop a process you started " + 'with the hub tool (`op: "stop"`), or `kill` the pid of a child you started from this pane');
43545
- case "fuser":
43546
- if (rest.some((arg) => /^-[a-zA-Z]*k/.test(literalText(arg.exp) ?? ""))) {
43745
+ case "fuser": {
43746
+ const kills = rest.some((arg) => mayBeOption(arg, (text) => /^-[a-zA-Z]*k/.test(text) || text === "--kill"));
43747
+ const readable2 = rest.every((arg) => literalText(arg.exp) !== undefined);
43748
+ const mayBeSeveral = rest.some((arg) => arg.fields === "unknown");
43749
+ if (kills && (readable2 || mayBeSeveral || rest.length > 1)) {
43547
43750
  throw new Refusal(site.snippet, site.line, "`fuser -k` kills every process using a file, other agents' included; a pane signals only " + "processes it started");
43548
43751
  }
43549
43752
  return;
43753
+ }
43550
43754
  case "tmux": {
43551
43755
  let socket;
43552
43756
  let socketIsPath = false;
43553
- for (let i = 0;i < rest.length; i += 1) {
43554
- const text = literalText(rest[i]?.exp);
43555
- if (text === "-L" || text === "-S" || text === "--socket") {
43556
- socket = rest[i + 1];
43557
- socketIsPath = text !== "-L";
43558
- i += 1;
43559
- } else if (text?.startsWith("--socket=")) {
43560
- socket = { text, exp: [literal2(text.slice(9))] };
43561
- socketIsPath = true;
43757
+ let current = { words: [], speculative: false };
43758
+ const segments = [current];
43759
+ for (const arg of rest) {
43760
+ const word = literalText(arg.exp);
43761
+ const trailing = word !== ";" && word !== undefined && word.endsWith(";");
43762
+ if (trailing)
43763
+ current.words.push({ text: arg.text, exp: [literal2(word.slice(0, -1))] });
43764
+ else if (word !== ";")
43765
+ current.words.push(arg);
43766
+ if (word === ";" || trailing) {
43767
+ current = { words: [], speculative: false };
43768
+ segments.push(current);
43769
+ } else if (word === undefined || arg.fields === "unknown") {
43770
+ current = { words: [], speculative: true };
43771
+ segments.push(current);
43562
43772
  }
43563
43773
  }
43564
43774
  const killCommands = ["kill-server", "kill-session", "kill-window", "kill-pane"];
43565
43775
  const respawnCommands = ["respawn-pane", "respawn-window"];
43566
- const respawning = rest.some((arg) => {
43567
- const text = literalText(arg.exp);
43568
- if (text === undefined)
43569
- return false;
43570
- const compact = text.replace("-", "");
43571
- return respawnCommands.filter((command) => command.replace("-", "").startsWith(compact)).length === 1;
43572
- }) && rest.some((arg) => literalText(arg.exp) === "-k");
43573
- const killing = rest.find((arg) => {
43574
- const text = literalText(arg.exp);
43575
- if (text === undefined)
43776
+ const names = (word, commands) => {
43777
+ if (word === undefined)
43576
43778
  return false;
43779
+ const { text, whole } = readableWord(word);
43577
43780
  const compact = text.replace("-", "");
43578
- return killCommands.filter((command) => command.replace("-", "").startsWith(compact)).length === 1 || respawning;
43579
- });
43781
+ const reachable = commands.filter((command) => command.replace("-", "").startsWith(compact));
43782
+ return whole ? reachable.length === 1 : reachable.length > 0;
43783
+ };
43784
+ let killing;
43785
+ for (const segment of segments) {
43786
+ const words = segment.words;
43787
+ let subcommand;
43788
+ for (let i = 0;i < words.length; i += 1) {
43789
+ const arg = words[i];
43790
+ const { text, whole } = readableWord(arg);
43791
+ const attached = (option) => text.startsWith(option) && (!whole || text.length > option.length) ? valueInWord(arg, option.length) : undefined;
43792
+ const carried = attached("-L") ?? attached("-S") ?? attached("--socket=");
43793
+ if (carried !== undefined) {
43794
+ socket = { text: arg.text, exp: carried };
43795
+ socketIsPath = !text.startsWith("-L");
43796
+ } else if (text === "-L" || text === "-S" || text === "--socket") {
43797
+ socket = words[i + 1];
43798
+ socketIsPath = text !== "-L";
43799
+ i += 1;
43800
+ } else if (text === "-f" || text === "-T") {
43801
+ i += 1;
43802
+ } else if (subcommand === undefined && !text.startsWith("-")) {
43803
+ subcommand = arg;
43804
+ }
43805
+ }
43806
+ if (segment.speculative && (subcommand === undefined || readableWord(subcommand).text === ""))
43807
+ continue;
43808
+ const respawning = names(subcommand, respawnCommands) && words.some((arg) => mayBeOption(arg, (text) => text === "-k"));
43809
+ if (names(subcommand, killCommands) || respawning)
43810
+ killing ??= subcommand;
43811
+ }
43580
43812
  if (killing === undefined)
43581
43813
  return;
43582
43814
  let resolvedSocket;
@@ -43600,13 +43832,19 @@ function dispatch(invocation, outer, ctx) {
43600
43832
  if (resolvedSocket !== undefined && judgePath(resolvedSocket, st, ctx, { follow: true, overwrite: false }).ok) {
43601
43833
  return;
43602
43834
  }
43603
- throw new Refusal(site.snippet, site.line, `\`tmux ${literalText(killing.exp)}\` needs a socket path inside this pane's roots; the ` + "resolved socket can end panes and processes this pane did not start");
43835
+ throw new Refusal(site.snippet, site.line, `\`tmux ${killing.text}\` needs a socket path inside this pane's roots; the ` + "resolved socket can end panes and processes this pane did not start");
43836
+ }
43837
+ case "busybox":
43838
+ case "toybox": {
43839
+ if (rest.length === 0)
43840
+ return;
43841
+ throw new Refusal(site.snippet, site.line, `the guard cannot read the applet \`${rest[0].text}\` ${base} would run, and the ` + "applets it carries include `rm`, `sh` and `killall`, which reach paths and processes " + `this pane did not start; name the applet (\`${base} rm \u2026\`) so it can be checked`);
43604
43842
  }
43605
43843
  default:
43606
43844
  break;
43607
43845
  }
43608
- if (SHELLS.has(base) || base === "busybox" && SHELLS.has(literalText(rest[0]?.exp) ?? "")) {
43609
- runShell(base === "busybox" ? rest.slice(1) : rest, invocation, st, ctx);
43846
+ if (SHELLS.has(base)) {
43847
+ runShell(rest, invocation, st, ctx);
43610
43848
  return;
43611
43849
  }
43612
43850
  const interpreter = interpreterLanguage(base);
@@ -43636,8 +43874,9 @@ function changeDirectory(target, st, ctx) {
43636
43874
  function declaration(builtin, list, st, ctx, site) {
43637
43875
  const options = list.map((arg) => literalText(arg.exp) ?? "").filter((text) => /^[-+][a-zA-Z]+$/.test(text));
43638
43876
  const option = (letter) => options.some((text) => text.startsWith("-") && text.includes(letter));
43639
- if (builtin !== "export" && option("n")) {
43640
- throw new Refusal(site.snippet, site.line, "the guard does not follow a nameref (`declare -n`), whose writes land in the variable it names");
43877
+ const mayBeNameref = list.some((arg) => mayBeOption(arg, (text) => /^-[a-zA-Z]*n/.test(text)));
43878
+ if (builtin !== "export" && mayBeNameref) {
43879
+ throw new Refusal(site.snippet, site.line, option("n") ? "the guard does not follow a nameref (`declare -n`), whose writes land in the variable it names" : `an option word \`${builtin}\` carries that the guard cannot read may be \`-n\`, a nameref ` + "whose writes land in the variable it names and which the guard does not follow; write " + "the option as a literal");
43641
43880
  }
43642
43881
  if (option("f") || option("F"))
43643
43882
  return;
@@ -43822,11 +44061,20 @@ function checkRecursiveMode(program, list, st, ctx, site) {
43822
44061
  }
43823
44062
  found.push(arg);
43824
44063
  }
43825
- if (!recursive)
43826
- return;
43827
- const targets = reference ? found : found.slice(1);
43828
44064
  const verb = program === "chmod" ? "recursively change the mode of" : "recursively change the owner of";
43829
- checkTargets(`${program} -R`, verb, targets, true, st, ctx, site);
44065
+ if (recursive) {
44066
+ checkTargets(`${program} -R`, verb, reference ? found : found.slice(1), true, st, ctx, site);
44067
+ return;
44068
+ }
44069
+ for (const [index, arg] of found.entries()) {
44070
+ if (!mayBeOption(arg, (text) => /^-[A-Za-z]*R/.test(text)))
44071
+ continue;
44072
+ const others = arg.fields === "unknown" ? found : found.filter((_, at) => at !== index);
44073
+ const targets = reference || arg.fields === "unknown" ? others : others.slice(1);
44074
+ if (targets.length === 0)
44075
+ continue;
44076
+ checkTargets(`${program} -R`, verb, targets, true, st, ctx, site);
44077
+ }
43830
44078
  }
43831
44079
  function checkFind(list, st, ctx, site) {
43832
44080
  let i = 0;
@@ -43841,17 +44089,27 @@ function checkFind(list, st, ctx, site) {
43841
44089
  break;
43842
44090
  }
43843
44091
  const starts = [];
44092
+ let rootsBeforePredicate;
43844
44093
  for (;i < list.length; i += 1) {
43845
- const text = literalText(list[i]?.exp);
44094
+ const arg = list[i];
44095
+ const text = literalText(arg.exp);
43846
44096
  if (text !== undefined && (text.startsWith("-") || text === "(" || text === "!" || text === ")"))
43847
44097
  break;
43848
- starts.push(list[i]);
44098
+ if (rootsBeforePredicate === undefined && mayBeOption(arg, (option) => option.startsWith("-"))) {
44099
+ rootsBeforePredicate = [...starts];
44100
+ }
44101
+ starts.push(arg);
43849
44102
  }
43850
44103
  const targets = starts.length > 0 ? starts : [{ text: ".", exp: [literal2(".")] }];
43851
44104
  const expression = list.slice(i).map((arg) => literalText(arg.exp));
43852
44105
  let action;
43853
44106
  let shell;
44107
+ let consumedByPredicate = false;
43854
44108
  for (const [index, word] of expression.entries()) {
44109
+ const isValue = consumedByPredicate;
44110
+ consumedByPredicate = word !== undefined && FIND_VALUED_PREDICATES.has(word);
44111
+ if (word === undefined && !isValue)
44112
+ action ??= "find with a predicate the guard cannot read";
43855
44113
  if (word === "-delete")
43856
44114
  action = "find -delete";
43857
44115
  if (word !== "-exec" && word !== "-execdir" && word !== "-ok" && word !== "-okdir")
@@ -43868,8 +44126,13 @@ function checkFind(list, st, ctx, site) {
43868
44126
  shell = inner.argv;
43869
44127
  }
43870
44128
  }
43871
- if (action === undefined)
44129
+ if (action === undefined) {
44130
+ if (rootsBeforePredicate === undefined)
44131
+ return;
44132
+ const under = rootsBeforePredicate.length > 0 ? rootsBeforePredicate : [{ text: ".", exp: [literal2(".")] }];
44133
+ checkTargets("find with a root the guard cannot read, which may be a predicate", "delete or change what it finds under", under, false, st, ctx, site);
43872
44134
  return;
44135
+ }
43873
44136
  checkTargets(action, "delete or change what it finds under", targets, false, st, ctx, site);
43874
44137
  if (shell === undefined)
43875
44138
  return;
@@ -44146,7 +44409,7 @@ function runInterpreter(base, language, list, invocation, st, ctx) {
44146
44409
  for (;i < words.length; i += 1) {
44147
44410
  const word = words[i];
44148
44411
  if (word === undefined)
44149
- return;
44412
+ continue;
44150
44413
  if (codeFlags.includes(word)) {
44151
44414
  runCode(language, list[i + 1], invocation, st, ctx, site);
44152
44415
  return;
@@ -44195,8 +44458,8 @@ function runFile(file2, positional, st, ctx, site, kind, source = false) {
44195
44458
  let content;
44196
44459
  if (st.files.has(abs)) {
44197
44460
  const written = st.files.get(abs);
44198
- if (written === null || written === undefined) {
44199
- throw new Refusal(site.snippet, site.line, `this command writes ${abs} in a way the guard cannot read before running it (output it cannot render, or more than the ${MAX_SCRIPT_BYTES} bytes it reads of a script); write the script with the write tool first`);
44461
+ if (written === undefined || typeof written !== "string") {
44462
+ throw new Refusal(site.snippet, site.line, `this command may write ${abs} in a way the guard cannot read before running it (${written?.why ?? "the guard cannot say what it holds"}); write the script with the write tool first`);
44200
44463
  }
44201
44464
  content = written;
44202
44465
  } else {
@@ -44389,7 +44652,8 @@ function createPaneGuard(options) {
44389
44652
  env,
44390
44653
  ompPid: options.ompPid,
44391
44654
  parentOf: options.parentOf ?? procParent,
44392
- steps: { count: 0 }
44655
+ steps: { count: 0 },
44656
+ uncertain: { depth: 0 }
44393
44657
  });
44394
44658
  const initial = (cwd, source) => ({
44395
44659
  vars: new Map,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-legion-envoy",
3
- "version": "5.17.1",
3
+ "version": "5.17.3",
4
4
  "type": "module",
5
5
  "omp": {
6
6
  "extensions": [