@sema-agent/core 7.29.0 → 7.30.0

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 (149) hide show
  1. package/CHANGELOG.md +361 -0
  2. package/dist/agents/child-model-seat.d.ts +59 -7
  3. package/dist/agents/child-model-seat.js +33 -7
  4. package/dist/agents/cross-session-judge.d.ts +27 -5
  5. package/dist/agents/cross-session-judge.js +23 -12
  6. package/dist/agents/list-agents-tool.d.ts +31 -6
  7. package/dist/agents/list-agents-tool.js +66 -14
  8. package/dist/agents/peer-admission.d.ts +20 -8
  9. package/dist/agents/peer-admission.js +21 -12
  10. package/dist/agents/peer-directory.d.ts +29 -0
  11. package/dist/agents/peer-directory.js +20 -0
  12. package/dist/agents/peer-held-queue.d.ts +31 -4
  13. package/dist/agents/peer-held-queue.js +32 -1
  14. package/dist/agents/peer-session-drain.d.ts +91 -13
  15. package/dist/agents/peer-session-drain.js +409 -102
  16. package/dist/agents/read-inbox-tool.d.ts +22 -0
  17. package/dist/agents/read-inbox-tool.js +39 -0
  18. package/dist/agents/roster-store.d.ts +2 -4
  19. package/dist/agents/roster-store.js +1 -1
  20. package/dist/agents/send-message-tool.d.ts +4 -4
  21. package/dist/agents/send-message-tool.js +22 -2
  22. package/dist/agents/subagent.js +62 -44
  23. package/dist/agents/team-role.d.ts +34 -0
  24. package/dist/agents/team-role.js +17 -0
  25. package/dist/agents/verify.js +2 -2
  26. package/dist/core/auto-compaction.d.ts +42 -0
  27. package/dist/core/auto-compaction.js +35 -2
  28. package/dist/core/child-clamp-seats.d.ts +11 -0
  29. package/dist/core/child-clamp-seats.js +3 -0
  30. package/dist/core/context-edit.d.ts +3 -2
  31. package/dist/core/context-edit.js +6 -5
  32. package/dist/core/context-guard.d.ts +2 -1
  33. package/dist/core/context-guard.js +3 -2
  34. package/dist/core/context-settings.d.ts +103 -0
  35. package/dist/core/context-settings.js +60 -0
  36. package/dist/core/engine-notice.d.ts +11 -1
  37. package/dist/core/frame-settlement.d.ts +97 -0
  38. package/dist/core/frame-settlement.js +36 -0
  39. package/dist/core/governance-codes.d.ts +1 -1
  40. package/dist/core/governance-codes.js +2 -0
  41. package/dist/core/hooks.d.ts +10 -10
  42. package/dist/core/mailbox-store.d.ts +41 -7
  43. package/dist/core/mailbox-store.js +9 -3
  44. package/dist/core/parent-spec-seat.d.ts +14 -1
  45. package/dist/core/parent-spec-seat.js +6 -0
  46. package/dist/core/permission-rule-model.js +1 -1
  47. package/dist/core/roles.d.ts +20 -0
  48. package/dist/core/roles.js +35 -16
  49. package/dist/core/runner/assemble-result.d.ts +2 -0
  50. package/dist/core/runner/assemble-result.js +1 -1
  51. package/dist/core/runner/compaction-call-options.d.ts +26 -2
  52. package/dist/core/runner/compaction-call-options.js +9 -0
  53. package/dist/core/runner/compaction-seams.d.ts +4 -3
  54. package/dist/core/runner/compaction-seams.js +13 -6
  55. package/dist/core/runner/contracts.d.ts +56 -7
  56. package/dist/core/runner/decide-continuation.d.ts +2 -1
  57. package/dist/core/runner/decide-continuation.js +11 -10
  58. package/dist/core/runner/initial-run-state.js +1 -1
  59. package/dist/core/runner/prepare-caps-and-workflow.js +9 -1
  60. package/dist/core/runner/prepare-config-doors.js +4 -1
  61. package/dist/core/runner/prepare-context-lane.d.ts +9 -2
  62. package/dist/core/runner/prepare-context-lane.js +15 -10
  63. package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
  64. package/dist/core/runner/prepare-delegation-surface.js +9 -3
  65. package/dist/core/runner/prepare-file-history.js +10 -8
  66. package/dist/core/runner/prepare-gate-stations.d.ts +4 -0
  67. package/dist/core/runner/prepare-gate-stations.js +3 -1
  68. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  69. package/dist/core/runner/prepare-prompt-inputs.d.ts +6 -0
  70. package/dist/core/runner/prepare-prompt-inputs.js +2 -1
  71. package/dist/core/runner/prepare-task.js +22 -9
  72. package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -1
  73. package/dist/core/runner/prepare-wiring-manifest.js +6 -1
  74. package/dist/core/runner/retired-runner-deps.d.ts +3 -1
  75. package/dist/core/runner/retired-runner-deps.js +1 -0
  76. package/dist/core/runner/run-compaction-machinery.d.ts +4 -2
  77. package/dist/core/runner/run-compaction-machinery.js +5 -3
  78. package/dist/core/runner/run-harness-handlers.js +1 -1
  79. package/dist/core/runner/run-identity-wiring.js +19 -1
  80. package/dist/core/runner/run-leg.js +1 -1
  81. package/dist/core/runner/run-notification-lane.js +8 -2
  82. package/dist/core/runner/run-recovery-lanes.js +9 -5
  83. package/dist/core/runner/run-settle-and-teardown.d.ts +4 -22
  84. package/dist/core/runner/run-settle-and-teardown.js +6 -10
  85. package/dist/core/runner/run-stop-and-final-verify.js +1 -0
  86. package/dist/core/runner/run-terminal-adoption.js +5 -5
  87. package/dist/core/runner/run-turn-boundary.js +32 -5
  88. package/dist/core/runner/runtask.d.ts +8 -1
  89. package/dist/core/runner/runtask.js +32 -1
  90. package/dist/core/runner-deps.d.ts +44 -13
  91. package/dist/core/swappable-deps.d.ts +20 -4
  92. package/dist/core/swappable-deps.js +6 -1
  93. package/dist/core/task-event.d.ts +5 -0
  94. package/dist/core/task-notification.d.ts +144 -2
  95. package/dist/core/task-notification.js +154 -5
  96. package/dist/core/task-result.d.ts +14 -0
  97. package/dist/core/task-spec.d.ts +7 -0
  98. package/dist/core/tool-catalog-entries.js +2 -1
  99. package/dist/core/tool-conformance.js +1 -0
  100. package/dist/core/tool-policy.d.ts +9 -4
  101. package/dist/core/tool-spec.d.ts +14 -0
  102. package/dist/core/wiring-manifest.d.ts +12 -0
  103. package/dist/core/wiring-manifest.js +5 -1
  104. package/dist/engine/harness/agent-harness.d.ts +14 -1
  105. package/dist/engine/harness/agent-harness.js +28 -3
  106. package/dist/engine/harness/types.d.ts +3 -1
  107. package/dist/engine/llm/types.d.ts +8 -7
  108. package/dist/engine/llm/types.js +1 -1
  109. package/dist/engine/session/import-validate.js +4 -0
  110. package/dist/index.d.ts +10 -7
  111. package/dist/index.js +7 -5
  112. package/dist/orchestration/run-workflow-tool.d.ts +4 -4
  113. package/dist/orchestration/workflow.d.ts +1 -1
  114. package/dist/orchestration/workflow.js +8 -5
  115. package/dist/shell/lex.d.ts +30 -51
  116. package/dist/shell/lex.js +237 -284
  117. package/dist/stores/cc/mailbox-store.js +3 -2
  118. package/dist/stores/file/corrupt-read-sink.d.ts +28 -2
  119. package/dist/stores/file/file-history-store.d.ts +2 -4
  120. package/dist/stores/file/file-history-store.js +1 -1
  121. package/dist/stores/file/fs-atomic.d.ts +16 -11
  122. package/dist/stores/file/fs-atomic.js +17 -6
  123. package/dist/stores/file/index.d.ts +5 -6
  124. package/dist/stores/file/index.js +1 -1
  125. package/dist/stores/file/mailbox-store.d.ts +4 -9
  126. package/dist/stores/file/mailbox-store.js +29 -30
  127. package/dist/stores/file/session-policy-store.d.ts +2 -0
  128. package/dist/stores/file/session-policy-store.js +1 -1
  129. package/dist/stores/file/session-store.d.ts +11 -7
  130. package/dist/stores/file/session-store.js +23 -16
  131. package/dist/stores/file/shared-ledger.js +3 -14
  132. package/dist/stores/file/tool-result-store.d.ts +2 -0
  133. package/dist/stores/file/tool-result-store.js +5 -2
  134. package/dist/stores/file/workflow-journal-store.js +14 -9
  135. package/dist/tools/fs/bash-program-position.d.ts +15 -24
  136. package/dist/tools/fs/bash-program-position.js +11 -29
  137. package/dist/tools/fs/bash-readonly-classifier.d.ts +125 -117
  138. package/dist/tools/fs/bash-readonly-classifier.js +400 -272
  139. package/dist/tools/fs/shell-read-check.js +3 -3
  140. package/dist/tools/fs/shell-read-establish.js +1 -1
  141. package/dist/tools/fs/shell-read-plan.d.ts +11 -6
  142. package/dist/tools/fs/shell-read-plan.js +167 -183
  143. package/dist/tools/fs/shell-read-word.d.ts +50 -0
  144. package/dist/tools/fs/shell-read-word.js +59 -0
  145. package/dist/tools/fs/shell-reentry-text.d.ts +10 -0
  146. package/dist/tools/fs/shell-reentry-text.js +73 -0
  147. package/package.json +1 -1
  148. package/test/export-surface.snapshot.json +58 -8
  149. package/wire-changes/core@7.30.0.json +1821 -0
package/dist/shell/lex.js CHANGED
@@ -2,13 +2,6 @@ export const SHELL_LEX_DIALECT = "bash";
2
2
  export const QUOTE_KINDS = ["'", '"', "$'", '$"', "\\"];
3
3
  export const EXPANSION_KINDS = ["parameter", "command", "process", "arithmetic", "tilde", "brace", "glob"];
4
4
  export const UNRESOLVED_REASONS = ["ansi_c_escape", "locale_string", "parameter_text", "subscript_text", "compound_array_value"];
5
- export const UNRESOLVED_VIEW = {
6
- ansi_c_escape: "refuse",
7
- locale_string: "fold",
8
- parameter_text: "refuse",
9
- subscript_text: "refuse",
10
- compound_array_value: "refuse",
11
- };
12
5
  export const UNRESOLVED_SENTENCE = {
13
6
  ansi_c_escape: "an ANSI-C quoted word carries an escape this lexer does not fold",
14
7
  locale_string: "a locale-translated string carries text this lexer does not read",
@@ -30,7 +23,6 @@ export const PARSE_FAILURE_REASONS = [
30
23
  "redirect_without_target",
31
24
  "heredoc_without_delimiter",
32
25
  "heredoc_delimiter_expands",
33
- "background_operator",
34
26
  ];
35
27
  export const PARSE_FAILURE_SENTENCE = {
36
28
  unterminated_single_quote: "a single-quoted word never closes",
@@ -46,7 +38,6 @@ export const PARSE_FAILURE_SENTENCE = {
46
38
  redirect_without_target: "a redirection names no target",
47
39
  heredoc_without_delimiter: "a here-document operator names no delimiter",
48
40
  heredoc_delimiter_expands: "a here-document delimiter is not a plain word",
49
- background_operator: "`&` backgrounding is not allowed — a backgrounded process outlives the command",
50
41
  };
51
42
  export const BASH_ONLY_CONSTRUCTS = [
52
43
  "ansi_c_quoting",
@@ -57,6 +48,7 @@ export const BASH_ONLY_CONSTRUCTS = [
57
48
  "here_string",
58
49
  "process_substitution",
59
50
  "redirect_both",
51
+ "pipe_both",
60
52
  "brace_expansion",
61
53
  ];
62
54
  export const ANSI_C_ESCAPES = {
@@ -78,11 +70,6 @@ export const SUBSTITUTION_PLACEHOLDER = " subst";
78
70
  export const SHELL_CONTROL_PREFIXES = new Set(["!", "if", "then", "elif", "else", "while", "until", "do", "time"]);
79
71
  const COMPOUND_OPERAND_COMMANDS = new Set(["alias", "declare", "eval", "export", "let", "local", "readonly", "typeset"]);
80
72
  const GROUP_WORDS = new Set(["{", "}"]);
81
- function quoteStep(open, ch) {
82
- if (open === undefined)
83
- return ch === '"' || ch === "'" ? ch : undefined;
84
- return open === ch ? undefined : open;
85
- }
86
73
  export function dollarOpensQuote(text, at) {
87
74
  const next = text[at + 1];
88
75
  if (next !== "'" && next !== '"')
@@ -103,32 +90,6 @@ export function ansiCQuoteClose(source, open) {
103
90
  }
104
91
  return undefined;
105
92
  }
106
- export function quoteMask(s) {
107
- const openedBy = new Array(s.length).fill(undefined);
108
- const quoted = new Array(s.length).fill(false);
109
- let open;
110
- for (let i = 0; i < s.length; i++) {
111
- if (open === undefined && s[i] === "$" && s[i + 1] === "'") {
112
- const close = ansiCQuoteClose(s, i + 1);
113
- for (let k = i; k <= (close ?? s.length - 1); k++) {
114
- openedBy[k] = "'";
115
- quoted[k] = true;
116
- }
117
- if (close === undefined) {
118
- open = "'";
119
- break;
120
- }
121
- i = close;
122
- continue;
123
- }
124
- const before = open;
125
- open = quoteStep(open, s[i]);
126
- const holds = before ?? open;
127
- openedBy[i] = holds;
128
- quoted[i] = holds !== undefined;
129
- }
130
- return { openedBy, quoted, balanced: open === undefined };
131
- }
132
93
  export function literalTildeSpelling(word) {
133
94
  return word.startsWith("~") ? `./${word}` : word;
134
95
  }
@@ -153,6 +114,9 @@ function decodeAnsiC(body) {
153
114
  }
154
115
  return { text, unresolved };
155
116
  }
117
+ export function argvOf(segment) {
118
+ return segment.tokens.filter((t) => t.kind === "word");
119
+ }
156
120
  export function wordsOf(segment) {
157
121
  const folded = [];
158
122
  const raw = [];
@@ -355,7 +319,6 @@ export function substitutionsInHeredocBody(body) {
355
319
  }
356
320
  return out;
357
321
  }
358
- const WHITESPACE = /\s/;
359
322
  const BLANK = /[ \t]/;
360
323
  const fail = (reason, at) => ({ parseFailure: { reason, at } });
361
324
  const publicPiece = (p) => ({
@@ -681,8 +644,7 @@ class WordBuilder {
681
644
  this.carveBraceAndGlob();
682
645
  if (assignment !== undefined && this.subscript?.text)
683
646
  this.mark("subscript_text", this.subscript.rawStart);
684
- const marks = this.marks.filter((m) => m.reason !== "subscript_text" || assignment?.subscript !== undefined)
685
- .sort((a, b) => (UNRESOLVED_VIEW[a.reason] === UNRESOLVED_VIEW[b.reason] ? a.at - b.at : UNRESOLVED_VIEW[a.reason] === "refuse" ? -1 : 1));
647
+ const marks = this.marks.filter((m) => m.reason !== "subscript_text" || assignment?.subscript !== undefined).sort((a, b) => a.at - b.at);
686
648
  const unresolved = marks[0]?.reason ?? (assignment?.value.unresolved);
687
649
  const spans = [];
688
650
  for (let i = 0; i < this.rawQuoted.length; i++) {
@@ -709,9 +671,8 @@ class WordBuilder {
709
671
  };
710
672
  }
711
673
  }
712
- function scan(source, opts) {
713
- const lenient = opts.mode === "lenient";
714
- const split = opts.connectors !== "text";
674
+ function scan(source, connectorsAreText) {
675
+ const split = !connectorsAreText;
715
676
  const segments = [{ start: 0, end: source.length, connector: "start", tokens: [], substitutions: [] }];
716
677
  const current = () => segments[segments.length - 1];
717
678
  const pendingHeredocs = [];
@@ -723,11 +684,14 @@ function scan(source, opts) {
723
684
  let regionAt = 0;
724
685
  let pendingLocale = false;
725
686
  let acceptable = true;
687
+ let timeGrammar;
688
+ let definitionName = false;
726
689
  let afterLeadingRedirect = false;
727
- let wordsSoFar = 0;
690
+ let assignmentSeen = false;
691
+ let subscriptsClosed = false;
728
692
  let commandWord;
729
693
  let w = new WordBuilder();
730
- const next = (j) => (lenient ? past(source, j + 1) : j + 1);
694
+ const next = (j) => past(source, j + 1);
731
695
  const quotedBy = () => (inSingle || inDouble ? regionBy : undefined);
732
696
  const take = (ch, src) => w.emit(ch, ch, quotedBy(), src, src + 1);
733
697
  const substitution = (from, to, body, kind) => {
@@ -735,6 +699,7 @@ function scan(source, opts) {
735
699
  w.opaque(source.slice(from, to), SUBSTITUTION_PLACEHOLDER, by, from, to, { kind, yields: kind === "command" && by === undefined ? "many" : "one", body });
736
700
  current().substitutions.push(body);
737
701
  };
702
+ const resetPosition = () => { acceptable = true; commandWord = undefined; afterLeadingRedirect = false; assignmentSeen = false; subscriptsClosed = false; timeGrammar = undefined; definitionName = false; };
738
703
  const endWord = () => {
739
704
  if (!w.started)
740
705
  return undefined;
@@ -751,48 +716,72 @@ function scan(source, opts) {
751
716
  const token = { kind: "heredoc", delimiter: word.folded, quoted: /["'\\]/.test(word.raw), body: "" };
752
717
  current().tokens.push(token);
753
718
  pendingHeredocs.push({ delimiter: word.folded, quoted: token.quoted, dash: header.dash, token });
754
- if (wordsSoFar > 0)
755
- acceptable = false;
756
- else
757
- afterLeadingRedirect = true;
719
+ if (acceptable) {
720
+ if (assignmentSeen)
721
+ subscriptsClosed = true;
722
+ else
723
+ afterLeadingRedirect = true;
724
+ }
758
725
  return undefined;
759
726
  }
760
727
  if (pendingRedirect !== undefined) {
761
- const op = pendingRedirect;
728
+ const { op, fd } = pendingRedirect;
762
729
  pendingRedirect = undefined;
763
730
  const target = { ...word, atCommandPosition: false };
731
+ const written = fd !== undefined ? { fd } : {};
764
732
  if ((op === ">&" || op === "<&") && word.raw === word.folded && /^(?:\d+-?|-)$/.test(word.folded))
765
- current().tokens.push({ kind: "redirect", op });
766
- else
767
- current().tokens.push({ kind: "redirect", op: op === ">&" ? "&>" : op === "<&" ? "<" : op, target });
768
- if (wordsSoFar > 0)
769
- acceptable = false;
733
+ current().tokens.push({ kind: "redirect", op, ...written, dup: word.folded });
770
734
  else
771
- afterLeadingRedirect = true;
735
+ current().tokens.push({ kind: "redirect", op: op === ">&" ? "&>" : op === "<&" ? "<" : op, ...written, target });
736
+ if (acceptable) {
737
+ if (assignmentSeen)
738
+ subscriptsClosed = true;
739
+ else
740
+ afterLeadingRedirect = true;
741
+ }
772
742
  return undefined;
773
743
  }
774
744
  current().tokens.push(word);
775
- if (acceptable && word.raw === word.folded && (SHELL_CONTROL_PREFIXES.has(word.folded) || GROUP_WORDS.has(word.folded)))
745
+ const bare = word.raw === word.folded;
746
+ if (definitionName) {
747
+ resetPosition();
748
+ return undefined;
749
+ }
750
+ if (acceptable && bare && word.folded === "function") {
751
+ acceptable = false;
752
+ definitionName = true;
776
753
  return undefined;
777
- wordsSoFar++;
778
- if (word.assignment !== undefined && word.assignment.bare && acceptable)
754
+ }
755
+ if (acceptable && bare && (SHELL_CONTROL_PREFIXES.has(word.folded) || GROUP_WORDS.has(word.folded))) {
756
+ timeGrammar = word.folded === "time" ? "opt" : undefined;
757
+ return undefined;
758
+ }
759
+ if (acceptable && bare && timeGrammar === "opt" && word.folded === "-p") {
760
+ timeGrammar = "ign";
761
+ return undefined;
762
+ }
763
+ if (acceptable && bare && timeGrammar !== undefined && word.folded === "--") {
764
+ timeGrammar = undefined;
779
765
  return undefined;
766
+ }
767
+ timeGrammar = undefined;
768
+ if (word.assignment !== undefined && word.assignment.bare && acceptable) {
769
+ assignmentSeen = true;
770
+ return undefined;
771
+ }
780
772
  if (acceptable)
781
- commandWord = word.raw === word.folded ? word.folded : undefined;
773
+ commandWord = bare ? word.folded : undefined;
782
774
  acceptable = false;
783
775
  return undefined;
784
776
  };
785
777
  const operandOwed = (at) => pendingRedirect !== undefined ? fail("redirect_without_target", at) : pendingHeredoc !== undefined ? fail("heredoc_without_delimiter", at) : undefined;
786
- const closeSegment = (connector, at, from) => {
778
+ const closeSegment = (connector, at, from, pipesStderr = false) => {
787
779
  const refusal = endWord() ?? operandOwed(at);
788
780
  if (refusal !== undefined)
789
781
  return refusal;
790
782
  current().end = at;
791
- segments.push({ start: from, end: source.length, connector, tokens: [], substitutions: [] });
792
- acceptable = true;
793
- wordsSoFar = 0;
794
- commandWord = undefined;
795
- afterLeadingRedirect = false;
783
+ segments.push({ start: from, end: source.length, connector, ...(pipesStderr ? { pipesStderr: true } : {}), tokens: [], substitutions: [] });
784
+ resetPosition();
796
785
  return undefined;
797
786
  };
798
787
  const readHeredocBodies = (from) => {
@@ -826,7 +815,7 @@ function scan(source, opts) {
826
815
  return Math.min(p, source.length);
827
816
  };
828
817
  for (let i = 0; i < source.length; i++) {
829
- if (lenient && !inSingle) {
818
+ if (!inSingle) {
830
819
  const j = past(source, i);
831
820
  if (j >= source.length)
832
821
  break;
@@ -897,7 +886,7 @@ function scan(source, opts) {
897
886
  take(ch, i);
898
887
  continue;
899
888
  }
900
- if (!inDouble && ch === "[" && w.started && w.subscript === undefined && !w.compound && acceptable && IDENTIFIER.test(w.raw)) {
889
+ if (!inDouble && ch === "[" && w.started && w.subscript === undefined && !w.compound && acceptable && !subscriptsClosed && IDENTIFIER.test(w.raw)) {
901
890
  w.subscript = { rawStart: w.raw.length, depth: 1, text: false };
902
891
  take(ch, i);
903
892
  continue;
@@ -926,250 +915,211 @@ function scan(source, opts) {
926
915
  take(ch, i);
927
916
  continue;
928
917
  }
929
- if (!lenient) {
930
- take(ch, i);
931
- continue;
932
- }
933
918
  if (ch === "\\" && source[i + 1] === "]")
934
919
  sub.text = true;
935
920
  }
936
921
  if (w.inSubscript() && inDouble && ch === "]")
937
922
  w.subscript.text = true;
938
- if (lenient) {
939
- const n1 = next(i);
940
- if (ch === "\\") {
941
- const escaped = source[i + 1];
942
- if (escaped === undefined) {
943
- take(ch, i);
944
- continue;
945
- }
946
- if (inDouble && !"$`\"\\".includes(escaped))
947
- w.emit(ch + escaped, ch + escaped, quotedBy(), i, i + 2);
948
- else
949
- w.emit(ch + escaped, escaped, inDouble ? quotedBy() : "\\", i, i + 2);
950
- i++;
951
- continue;
952
- }
953
- if (ch === "`") {
954
- const tick = backtickBody(source, i);
955
- if (tick === undefined)
956
- return fail("unterminated_command_substitution", i);
957
- substitution(i, tick.close + 1, tick.body, "command");
958
- i = tick.close;
959
- continue;
960
- }
961
- if (ch === "$" && source[n1] === "(") {
962
- const arithmetic = source[next(n1)] === "(";
963
- const end = parenBodyEnd(source, n1);
964
- if (end === undefined)
965
- return fail("unterminated_command_substitution", i);
966
- if (end === "unreadable")
967
- return fail("undelimited_substitution", i);
968
- substitution(i, end, source.slice(n1 + 1, end - 1), arithmetic ? "arithmetic" : "command");
969
- i = end - 1;
970
- continue;
971
- }
972
- if (ch === "$" && source[n1] === "{") {
973
- const end = braceExpansionEnd(source, n1);
974
- if (end === undefined)
975
- return fail("unterminated_parameter_expansion", i);
976
- const body = source.slice(n1 + 1, end - 1);
977
- const text = source.slice(i, end);
978
- const name = EXPANSION_NAME.exec(body)?.[1];
979
- if (UNREADABLE_EXPANSION_TEXT.test(body))
980
- w.mark("parameter_text", w.raw.length);
981
- w.opaque(text, text, quotedBy(), i, end, { kind: "parameter", yields: inDouble ? "one" : "many", body, ...(name !== undefined ? { name } : {}) });
982
- i = end - 1;
983
- continue;
984
- }
985
- if (ch === "$") {
986
- const head = BARE_PARAMETER.exec(source.slice(n1, n1 + 64))?.[0];
987
- if (head !== undefined) {
988
- const text = source.slice(i, n1 + head.length);
989
- w.opaque(text, text, quotedBy(), i, n1 + head.length, { kind: "parameter", yields: inDouble ? "one" : "many", name: head });
990
- i = n1 + head.length - 1;
991
- continue;
992
- }
993
- }
994
- if (inDouble) {
923
+ const n1 = next(i);
924
+ if (ch === "\\") {
925
+ const escaped = source[i + 1];
926
+ if (escaped === undefined) {
995
927
  take(ch, i);
996
928
  continue;
997
929
  }
998
- if ((ch === "<" || ch === ">") && source[n1] === "(") {
999
- const end = parenBodyEnd(source, n1);
1000
- if (end === undefined)
1001
- return fail("unterminated_process_substitution", i);
1002
- if (end === "unreadable")
1003
- return fail("undelimited_substitution", i);
1004
- substitution(i, end, source.slice(n1 + 1, end - 1), "process");
1005
- i = end - 1;
1006
- continue;
1007
- }
1008
- if (ch === "(" && w.started && w.isAssignmentHead() && (acceptable || (commandWord !== undefined && COMPOUND_OPERAND_COMMANDS.has(commandWord)))) {
1009
- const end = matchedParen(source, i);
1010
- if (end === undefined)
1011
- return fail("unterminated_compound_value", i);
1012
- const inner = scan(source.slice(i + 1, end - 1), { mode: "lenient", connectors: "text" });
1013
- if ("parseFailure" in inner)
1014
- return fail(inner.parseFailure.reason, i + 1 + inner.parseFailure.at);
1015
- const text = source.slice(i, end);
1016
- w.mark("compound_array_value", w.raw.length);
1017
- w.opaque(text, text, undefined, i, end, undefined);
1018
- w.compound = true;
1019
- current().substitutions.push(...inner.command.substitutions);
1020
- i = end - 1;
930
+ if (inDouble && !"$`\"\\".includes(escaped))
931
+ w.emit(ch + escaped, ch + escaped, quotedBy(), i, i + 2);
932
+ else
933
+ w.emit(ch + escaped, escaped, inDouble ? quotedBy() : "\\", i, i + 2);
934
+ i++;
935
+ continue;
936
+ }
937
+ if (ch === "`") {
938
+ const tick = backtickBody(source, i);
939
+ if (tick === undefined)
940
+ return fail("unterminated_command_substitution", i);
941
+ substitution(i, tick.close + 1, tick.body, "command");
942
+ i = tick.close;
943
+ continue;
944
+ }
945
+ if (ch === "$" && source[n1] === "(") {
946
+ const arithmetic = source[next(n1)] === "(";
947
+ const end = parenBodyEnd(source, n1);
948
+ if (end === undefined)
949
+ return fail("unterminated_command_substitution", i);
950
+ if (end === "unreadable")
951
+ return fail("undelimited_substitution", i);
952
+ substitution(i, end, source.slice(n1 + 1, end - 1), arithmetic ? "arithmetic" : "command");
953
+ i = end - 1;
954
+ continue;
955
+ }
956
+ if (ch === "$" && source[n1] === "{") {
957
+ const end = braceExpansionEnd(source, n1);
958
+ if (end === undefined)
959
+ return fail("unterminated_parameter_expansion", i);
960
+ const body = source.slice(n1 + 1, end - 1);
961
+ const text = source.slice(i, end);
962
+ const name = EXPANSION_NAME.exec(body)?.[1];
963
+ if (UNREADABLE_EXPANSION_TEXT.test(body))
964
+ w.mark("parameter_text", w.raw.length);
965
+ w.opaque(text, text, quotedBy(), i, end, { kind: "parameter", yields: inDouble ? "one" : "many", body, ...(name !== undefined ? { name } : {}) });
966
+ i = end - 1;
967
+ continue;
968
+ }
969
+ if (ch === "$") {
970
+ const head = BARE_PARAMETER.exec(source.slice(n1, n1 + 64))?.[0];
971
+ if (head !== undefined) {
972
+ const text = source.slice(i, n1 + head.length);
973
+ w.opaque(text, text, quotedBy(), i, n1 + head.length, { kind: "parameter", yields: inDouble ? "one" : "many", name: head });
974
+ i = n1 + head.length - 1;
1021
975
  continue;
1022
976
  }
1023
- if (w.inSubscript()) {
1024
- take(ch, i);
977
+ }
978
+ if (inDouble) {
979
+ take(ch, i);
980
+ continue;
981
+ }
982
+ if ((ch === "<" || ch === ">") && source[n1] === "(") {
983
+ const end = parenBodyEnd(source, n1);
984
+ if (end === undefined)
985
+ return fail("unterminated_process_substitution", i);
986
+ if (end === "unreadable")
987
+ return fail("undelimited_substitution", i);
988
+ substitution(i, end, source.slice(n1 + 1, end - 1), "process");
989
+ i = end - 1;
990
+ continue;
991
+ }
992
+ if (ch === "(" && w.started && w.isAssignmentHead() && (acceptable || (commandWord !== undefined && COMPOUND_OPERAND_COMMANDS.has(commandWord)))) {
993
+ const end = matchedParen(source, i);
994
+ if (end === undefined)
995
+ return fail("unterminated_compound_value", i);
996
+ const inner = scan(source.slice(i + 1, end - 1), true);
997
+ if ("parseFailure" in inner)
998
+ return fail(inner.parseFailure.reason, i + 1 + inner.parseFailure.at);
999
+ const text = source.slice(i, end);
1000
+ w.mark("compound_array_value", w.raw.length);
1001
+ w.opaque(text, text, undefined, i, end, undefined);
1002
+ w.compound = true;
1003
+ current().substitutions.push(...inner.command.substitutions);
1004
+ i = end - 1;
1005
+ continue;
1006
+ }
1007
+ if (w.inSubscript()) {
1008
+ take(ch, i);
1009
+ continue;
1010
+ }
1011
+ if (ch === "#" && !w.started) {
1012
+ const nl = source.indexOf("\n", i);
1013
+ i = nl < 0 ? source.length : nl - 1;
1014
+ continue;
1015
+ }
1016
+ if (ch === "\n") {
1017
+ if (!split) {
1018
+ const r = endWord();
1019
+ if (r !== undefined)
1020
+ return r;
1025
1021
  continue;
1026
1022
  }
1027
- if (ch === "#" && !w.started) {
1028
- const nl = source.indexOf("\n", i);
1029
- i = nl < 0 ? source.length : nl - 1;
1030
- continue;
1023
+ const refusal = closeSegment("\n", i, i + 1);
1024
+ if (refusal !== undefined)
1025
+ return refusal;
1026
+ if (pendingHeredocs.length > 0) {
1027
+ const resume = readHeredocBodies(i + 1);
1028
+ if (typeof resume !== "number")
1029
+ return resume;
1030
+ current().start = resume;
1031
+ i = resume - 1;
1031
1032
  }
1032
- if (ch === "\n") {
1033
- if (!split) {
1034
- const r = endWord();
1035
- if (r !== undefined)
1036
- return r;
1037
- continue;
1038
- }
1039
- const refusal = closeSegment("\n", i, i + 1);
1040
- if (refusal !== undefined)
1041
- return refusal;
1042
- if (pendingHeredocs.length > 0) {
1043
- const resume = readHeredocBodies(i + 1);
1044
- if (typeof resume !== "number")
1045
- return resume;
1046
- current().start = resume;
1047
- i = resume - 1;
1048
- }
1049
- continue;
1033
+ continue;
1034
+ }
1035
+ if (ch === "(" || ch === ")") {
1036
+ const r = endWord() ?? operandOwed(i);
1037
+ if (r !== undefined)
1038
+ return r;
1039
+ current().tokens.push({ kind: "group", op: ch });
1040
+ resetPosition();
1041
+ continue;
1042
+ }
1043
+ if (ch === "<" || ch === ">" || (ch === "&" && source[n1] === ">")) {
1044
+ let fd;
1045
+ if (ch !== "&" && w.started && w.raw === w.folded && /^\d+$/.test(w.raw)) {
1046
+ fd = Number(w.raw);
1047
+ w = new WordBuilder();
1050
1048
  }
1051
- if (ch === "(" || ch === ")") {
1049
+ else {
1052
1050
  const r = endWord() ?? operandOwed(i);
1053
1051
  if (r !== undefined)
1054
1052
  return r;
1055
- current().tokens.push({ kind: "group", op: ch });
1056
- acceptable = true;
1057
- wordsSoFar = 0;
1058
- commandWord = undefined;
1059
- afterLeadingRedirect = false;
1060
- continue;
1061
1053
  }
1062
- if (ch === "<" || ch === ">" || (ch === "&" && source[n1] === ">")) {
1063
- if (ch !== "&" && w.started && w.raw === w.folded && /^\d+$/.test(w.raw))
1064
- w = new WordBuilder();
1065
- else {
1066
- const r = endWord() ?? operandOwed(i);
1067
- if (r !== undefined)
1068
- return r;
1069
- }
1070
- const n2 = next(n1);
1071
- if (ch === "&") {
1072
- if (source[n2] === ">") {
1073
- pendingRedirect = "&>>";
1074
- i = n2;
1075
- }
1076
- else {
1077
- pendingRedirect = "&>";
1078
- i = n1;
1079
- }
1080
- continue;
1081
- }
1082
- if (ch === "<" && source[n1] === "<") {
1083
- if (source[n2] === "<") {
1084
- pendingRedirect = "<<<";
1085
- i = n2;
1086
- continue;
1087
- }
1088
- const dash = source[n2] === "-";
1089
- pendingHeredoc = { dash };
1090
- i = dash ? n2 : n1;
1091
- continue;
1092
- }
1093
- if (source[n1] === "&") {
1094
- pendingRedirect = ch === ">" ? ">&" : "<&";
1095
- i = n1;
1096
- continue;
1054
+ const written = fd !== undefined ? { fd } : {};
1055
+ const n2 = next(n1);
1056
+ if (ch === "&") {
1057
+ if (source[n2] === ">") {
1058
+ pendingRedirect = { op: "&>>" };
1059
+ i = n2;
1097
1060
  }
1098
- if (ch === ">" && (source[n1] === ">" || source[n1] === "|")) {
1099
- pendingRedirect = source[n1] === ">" ? ">>" : ">";
1061
+ else {
1062
+ pendingRedirect = { op: "&>" };
1100
1063
  i = n1;
1101
- continue;
1102
1064
  }
1103
- pendingRedirect = ch;
1104
1065
  continue;
1105
1066
  }
1106
- if (split) {
1107
- let refusal;
1108
- if (ch === ";")
1109
- refusal = closeSegment(";", i, i + 1);
1110
- else if (ch === "&") {
1111
- if (source[n1] === "&") {
1112
- refusal = closeSegment("&&", i, n1 + 1);
1113
- i = n1;
1114
- }
1115
- else
1116
- refusal = closeSegment("&", i, i + 1);
1117
- }
1118
- else if (ch === "|") {
1119
- if (source[n1] === "|" || source[n1] === "&") {
1120
- refusal = closeSegment(source[n1] === "|" ? "||" : "|", i, n1 + 1);
1121
- i = n1;
1122
- }
1123
- else
1124
- refusal = closeSegment("|", i, i + 1);
1125
- }
1126
- else if (BLANK.test(ch)) {
1127
- refusal = endWord();
1128
- }
1129
- else {
1130
- take(ch, i);
1067
+ if (ch === "<" && source[n1] === "<") {
1068
+ if (source[n2] === "<") {
1069
+ pendingRedirect = { op: "<<<", ...written };
1070
+ i = n2;
1131
1071
  continue;
1132
1072
  }
1133
- if (refusal !== undefined)
1134
- return refusal;
1073
+ const dash = source[n2] === "-";
1074
+ pendingHeredoc = { dash };
1075
+ i = dash ? n2 : n1;
1135
1076
  continue;
1136
1077
  }
1137
- if (BLANK.test(ch)) {
1138
- const r = endWord();
1139
- if (r !== undefined)
1140
- return r;
1078
+ if (source[n1] === "&") {
1079
+ pendingRedirect = { op: ch === ">" ? ">&" : "<&", ...written };
1080
+ i = n1;
1141
1081
  continue;
1142
1082
  }
1143
- take(ch, i);
1083
+ if (ch === ">" && (source[n1] === ">" || source[n1] === "|")) {
1084
+ pendingRedirect = { op: source[n1] === ">" ? ">>" : ">", ...written };
1085
+ i = n1;
1086
+ continue;
1087
+ }
1088
+ pendingRedirect = { op: ch, ...written };
1144
1089
  continue;
1145
1090
  }
1146
- if (!inDouble && split) {
1147
- if (ch === "&") {
1148
- if (source[i + 1] !== "&")
1149
- return fail("background_operator", i);
1150
- const r = closeSegment("&&", i, i + 2);
1151
- if (r !== undefined)
1152
- return r;
1153
- i++;
1154
- continue;
1091
+ if (split) {
1092
+ let refusal;
1093
+ if (ch === ";")
1094
+ refusal = closeSegment(";", i, i + 1);
1095
+ else if (ch === "&") {
1096
+ if (source[n1] === "&") {
1097
+ refusal = closeSegment("&&", i, n1 + 1);
1098
+ i = n1;
1099
+ }
1100
+ else
1101
+ refusal = closeSegment("&", i, i + 1);
1155
1102
  }
1156
- if (ch === "|") {
1157
- const double = source[i + 1] === "|";
1158
- const r = closeSegment(double ? "||" : "|", i, double ? i + 2 : i + 1);
1159
- if (r !== undefined)
1160
- return r;
1161
- if (double)
1162
- i++;
1163
- continue;
1103
+ else if (ch === "|") {
1104
+ if (source[n1] === "|" || source[n1] === "&") {
1105
+ refusal = closeSegment(source[n1] === "|" ? "||" : "|", i, n1 + 1, source[n1] === "&");
1106
+ i = n1;
1107
+ }
1108
+ else
1109
+ refusal = closeSegment("|", i, i + 1);
1164
1110
  }
1165
- if (ch === ";") {
1166
- const r = closeSegment(";", i, i + 1);
1167
- if (r !== undefined)
1168
- return r;
1111
+ else if (BLANK.test(ch)) {
1112
+ refusal = endWord();
1113
+ }
1114
+ else {
1115
+ take(ch, i);
1169
1116
  continue;
1170
1117
  }
1118
+ if (refusal !== undefined)
1119
+ return refusal;
1120
+ continue;
1171
1121
  }
1172
- if (!inDouble && WHITESPACE.test(ch)) {
1122
+ if (BLANK.test(ch)) {
1173
1123
  const r = endWord();
1174
1124
  if (r !== undefined)
1175
1125
  return r;
@@ -1189,23 +1139,24 @@ function scan(source, opts) {
1189
1139
  const out = segments.map((s) => ({
1190
1140
  text: source.slice(s.start, s.end),
1191
1141
  connector: s.connector,
1142
+ ...(s.pipesStderr ? { pipesStderr: true } : {}),
1192
1143
  tokens: s.tokens,
1193
1144
  substitutions: s.substitutions,
1194
1145
  }));
1195
1146
  return { command: { segments: out, substitutions: out.flatMap((s) => s.substitutions) } };
1196
1147
  }
1197
- export function lexShell(source, opts) {
1198
- const pass = scan(source, opts);
1148
+ export function lexShell(source) {
1149
+ const pass = scan(source, false);
1199
1150
  return "parseFailure" in pass ? pass : pass.command;
1200
1151
  }
1201
- export function lexShellText(source, opts) {
1202
- const lexed = lexShell(source, opts);
1152
+ export function lexShellText(source) {
1153
+ const lexed = lexShell(source);
1203
1154
  if ("parseFailure" in lexed)
1204
1155
  return { reject: PARSE_FAILURE_SENTENCE[lexed.parseFailure.reason] };
1205
1156
  for (const segment of lexed.segments) {
1206
1157
  for (const token of segment.tokens) {
1207
1158
  const word = token.kind === "word" ? token : token.kind === "redirect" ? token.target : undefined;
1208
- if (word?.unresolved !== undefined && UNRESOLVED_VIEW[word.unresolved] === "refuse")
1159
+ if (word?.unresolved !== undefined)
1209
1160
  return { reject: UNRESOLVED_SENTENCE[word.unresolved] };
1210
1161
  }
1211
1162
  }
@@ -1236,6 +1187,8 @@ export function bashOnlyConstructsOf(command) {
1236
1187
  }
1237
1188
  };
1238
1189
  for (const segment of command.segments) {
1190
+ if (segment.pipesStderr)
1191
+ found.add("pipe_both");
1239
1192
  for (const t of segment.tokens) {
1240
1193
  if (t.kind === "word")
1241
1194
  word(t);