@namzu/sdk 52.4.0 → 52.6.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 (152) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/authorization/gate.d.ts +11 -0
  3. package/dist/authorization/gate.d.ts.map +1 -1
  4. package/dist/authorization/gate.js +35 -0
  5. package/dist/authorization/gate.js.map +1 -1
  6. package/dist/authorization/removal.js +11 -6
  7. package/dist/authorization/removal.js.map +1 -1
  8. package/dist/authorization/rules.d.ts.map +1 -1
  9. package/dist/authorization/rules.js +30 -2
  10. package/dist/authorization/rules.js.map +1 -1
  11. package/dist/authorization/shell-paths.d.ts +20 -0
  12. package/dist/authorization/shell-paths.d.ts.map +1 -1
  13. package/dist/authorization/shell-paths.js +254 -52
  14. package/dist/authorization/shell-paths.js.map +1 -1
  15. package/dist/bridge/acp/server.d.ts +8 -0
  16. package/dist/bridge/acp/server.d.ts.map +1 -1
  17. package/dist/bridge/acp/server.js +7 -0
  18. package/dist/bridge/acp/server.js.map +1 -1
  19. package/dist/manager/resident/outbox.d.ts +4 -4
  20. package/dist/public-runtime.d.ts +2 -1
  21. package/dist/public-runtime.d.ts.map +1 -1
  22. package/dist/public-runtime.js +4 -1
  23. package/dist/public-runtime.js.map +1 -1
  24. package/dist/public-types.d.ts +1 -0
  25. package/dist/public-types.d.ts.map +1 -1
  26. package/dist/runtime/query/auto-review/classifier.d.ts +130 -0
  27. package/dist/runtime/query/auto-review/classifier.d.ts.map +1 -0
  28. package/dist/runtime/query/auto-review/classifier.js +244 -0
  29. package/dist/runtime/query/auto-review/classifier.js.map +1 -0
  30. package/dist/runtime/query/auto-review/index.d.ts +7 -0
  31. package/dist/runtime/query/auto-review/index.d.ts.map +1 -0
  32. package/dist/runtime/query/auto-review/index.js +4 -0
  33. package/dist/runtime/query/auto-review/index.js.map +1 -0
  34. package/dist/runtime/query/auto-review/prompt.d.ts +20 -0
  35. package/dist/runtime/query/auto-review/prompt.d.ts.map +1 -0
  36. package/dist/runtime/query/auto-review/prompt.js +89 -0
  37. package/dist/runtime/query/auto-review/prompt.js.map +1 -0
  38. package/dist/runtime/query/auto-review/rules.d.ts +49 -0
  39. package/dist/runtime/query/auto-review/rules.d.ts.map +1 -0
  40. package/dist/runtime/query/auto-review/rules.js +81 -0
  41. package/dist/runtime/query/auto-review/rules.js.map +1 -0
  42. package/dist/runtime/query/auto-review/transcript.d.ts +59 -0
  43. package/dist/runtime/query/auto-review/transcript.d.ts.map +1 -0
  44. package/dist/runtime/query/auto-review/transcript.js +116 -0
  45. package/dist/runtime/query/auto-review/transcript.js.map +1 -0
  46. package/dist/runtime/query/auto-review/verdict.d.ts +21 -0
  47. package/dist/runtime/query/auto-review/verdict.d.ts.map +1 -0
  48. package/dist/runtime/query/auto-review/verdict.js +45 -0
  49. package/dist/runtime/query/auto-review/verdict.js.map +1 -0
  50. package/dist/runtime/query/executor.d.ts +7 -0
  51. package/dist/runtime/query/executor.d.ts.map +1 -1
  52. package/dist/runtime/query/executor.js +26 -2
  53. package/dist/runtime/query/executor.js.map +1 -1
  54. package/dist/runtime/query/index.js +2 -2
  55. package/dist/runtime/query/index.js.map +1 -1
  56. package/dist/runtime/query/iteration/phases/context.d.ts +0 -9
  57. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  58. package/dist/runtime/query/iteration/phases/context.js +11 -2
  59. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  60. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  61. package/dist/runtime/query/iteration/phases/tool-review.js +39 -0
  62. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  63. package/dist/runtime/query/path-escalation.d.ts.map +1 -1
  64. package/dist/runtime/query/path-escalation.js +43 -10
  65. package/dist/runtime/query/path-escalation.js.map +1 -1
  66. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  67. package/dist/runtime/query/resume-pending.js +14 -0
  68. package/dist/runtime/query/resume-pending.js.map +1 -1
  69. package/dist/runtime/query/review-policy.d.ts +88 -1
  70. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  71. package/dist/runtime/query/review-policy.js +504 -11
  72. package/dist/runtime/query/review-policy.js.map +1 -1
  73. package/dist/tools/builtins/bash.d.ts.map +1 -1
  74. package/dist/tools/builtins/bash.js +2 -0
  75. package/dist/tools/builtins/bash.js.map +1 -1
  76. package/dist/tools/builtins/classifier-input.d.ts +9 -0
  77. package/dist/tools/builtins/classifier-input.d.ts.map +1 -0
  78. package/dist/tools/builtins/classifier-input.js +20 -0
  79. package/dist/tools/builtins/classifier-input.js.map +1 -0
  80. package/dist/tools/builtins/code-analysis.d.ts +2 -0
  81. package/dist/tools/builtins/code-analysis.d.ts.map +1 -1
  82. package/dist/tools/builtins/code-analysis.js +196 -119
  83. package/dist/tools/builtins/code-analysis.js.map +1 -1
  84. package/dist/tools/builtins/computer-use.d.ts +10 -10
  85. package/dist/tools/builtins/edit-apply.d.ts.map +1 -1
  86. package/dist/tools/builtins/edit-apply.js +30 -12
  87. package/dist/tools/builtins/edit-apply.js.map +1 -1
  88. package/dist/tools/builtins/edit-match.d.ts +47 -0
  89. package/dist/tools/builtins/edit-match.d.ts.map +1 -0
  90. package/dist/tools/builtins/edit-match.js +262 -0
  91. package/dist/tools/builtins/edit-match.js.map +1 -0
  92. package/dist/tools/builtins/edit.d.ts.map +1 -1
  93. package/dist/tools/builtins/edit.js +12 -0
  94. package/dist/tools/builtins/edit.js.map +1 -1
  95. package/dist/tools/builtins/monitor.d.ts.map +1 -1
  96. package/dist/tools/builtins/monitor.js +3 -0
  97. package/dist/tools/builtins/monitor.js.map +1 -1
  98. package/dist/tools/builtins/web.d.ts.map +1 -1
  99. package/dist/tools/builtins/web.js +3 -0
  100. package/dist/tools/builtins/web.js.map +1 -1
  101. package/dist/tools/builtins/write-file.d.ts.map +1 -1
  102. package/dist/tools/builtins/write-file.js +6 -0
  103. package/dist/tools/builtins/write-file.js.map +1 -1
  104. package/dist/tools/defineTool.d.ts +2 -0
  105. package/dist/tools/defineTool.d.ts.map +1 -1
  106. package/dist/tools/defineTool.js +1 -0
  107. package/dist/tools/defineTool.js.map +1 -1
  108. package/dist/types/acp/index.d.ts +7 -0
  109. package/dist/types/acp/index.d.ts.map +1 -1
  110. package/dist/types/code-analysis/index.d.ts +54 -3
  111. package/dist/types/code-analysis/index.d.ts.map +1 -1
  112. package/dist/types/hitl/index.d.ts +45 -1
  113. package/dist/types/hitl/index.d.ts.map +1 -1
  114. package/dist/types/hitl/index.js.map +1 -1
  115. package/dist/types/tool/index.d.ts +16 -0
  116. package/dist/types/tool/index.d.ts.map +1 -1
  117. package/dist/types/tool/index.js.map +1 -1
  118. package/package.json +1 -1
  119. package/src/authorization/gate.ts +35 -0
  120. package/src/authorization/removal.ts +12 -6
  121. package/src/authorization/rules.ts +27 -2
  122. package/src/authorization/shell-paths.ts +264 -51
  123. package/src/bridge/acp/server.ts +20 -0
  124. package/src/public-runtime.ts +18 -0
  125. package/src/public-types.ts +11 -0
  126. package/src/runtime/query/auto-review/classifier.ts +404 -0
  127. package/src/runtime/query/auto-review/index.ts +23 -0
  128. package/src/runtime/query/auto-review/prompt.ts +97 -0
  129. package/src/runtime/query/auto-review/rules.ts +106 -0
  130. package/src/runtime/query/auto-review/transcript.ts +159 -0
  131. package/src/runtime/query/auto-review/verdict.ts +50 -0
  132. package/src/runtime/query/executor.ts +29 -2
  133. package/src/runtime/query/index.ts +2 -2
  134. package/src/runtime/query/iteration/phases/context.ts +17 -3
  135. package/src/runtime/query/iteration/phases/tool-review.ts +41 -0
  136. package/src/runtime/query/path-escalation.ts +44 -9
  137. package/src/runtime/query/resume-pending.ts +14 -0
  138. package/src/runtime/query/review-policy.ts +591 -14
  139. package/src/tools/builtins/bash.ts +3 -0
  140. package/src/tools/builtins/classifier-input.ts +19 -0
  141. package/src/tools/builtins/code-analysis.ts +211 -119
  142. package/src/tools/builtins/edit-apply.ts +31 -12
  143. package/src/tools/builtins/edit-match.ts +274 -0
  144. package/src/tools/builtins/edit.ts +13 -0
  145. package/src/tools/builtins/monitor.ts +4 -0
  146. package/src/tools/builtins/web.ts +3 -0
  147. package/src/tools/builtins/write-file.ts +6 -0
  148. package/src/tools/defineTool.ts +3 -0
  149. package/src/types/acp/index.ts +7 -0
  150. package/src/types/code-analysis/index.ts +51 -3
  151. package/src/types/hitl/index.ts +49 -1
  152. package/src/types/tool/index.ts +17 -0
@@ -38,6 +38,8 @@ export interface ShellInvocation {
38
38
  readonly program: string
39
39
  /** Its arguments, as the shell passes them. */
40
40
  readonly args: readonly ShellWord[]
41
+ /** The redirections written on this command itself, when the lexer attached them. */
42
+ readonly redirections?: readonly ShellRedirection[]
41
43
  }
42
44
 
43
45
  /** What the lexer found, in the shape this module and the removal check share. */
@@ -50,13 +52,20 @@ export interface ShellReading {
50
52
 
51
53
  const EXEC_FLAGS = new Set(['-exec', '-execdir', '-ok', '-okdir'])
52
54
 
53
- function invocationOf(words: readonly ShellWord[]): ShellInvocation | undefined {
55
+ function invocationOf(
56
+ words: readonly ShellWord[],
57
+ redirections?: readonly ShellRedirection[],
58
+ ): ShellInvocation | undefined {
54
59
  if (words.length === 0) return undefined
55
60
  const step = unwrapReexecChain(words)
56
61
  if (!('rest' in step)) return undefined
57
62
  const [head, ...args] = step.rest
58
63
  if (head === undefined || head.expands) return undefined
59
- return { program: basename(head.value), args }
64
+ return {
65
+ program: basename(head.value),
66
+ args,
67
+ ...(redirections !== undefined ? { redirections } : {}),
68
+ }
60
69
  }
61
70
 
62
71
  /** Every program a line runs, and every redirection it carries. */
@@ -64,7 +73,7 @@ export function readShellLine(line: string, dialect: ShellDialect): ShellReading
64
73
  const lexed = lexShellCommandLine(line, { dialect })
65
74
  const invocations: ShellInvocation[] = []
66
75
  for (const command of lexed.commands) {
67
- const own = invocationOf(command.words.slice(command.assignments))
76
+ const own = invocationOf(command.words.slice(command.assignments), command.redirections)
68
77
  if (own === undefined) continue
69
78
  invocations.push(own)
70
79
  if (own.program !== 'find') continue
@@ -102,8 +111,20 @@ export interface ShellPathUse {
102
111
  readonly via: string
103
112
  /** True when the word is decided at runtime and `path` is not what it will be. */
104
113
  readonly unresolved: boolean
114
+ /**
115
+ * Where a relative `path` starts from, when an earlier `cd` in the line moved
116
+ * the shell. Absent: the directory the line began in. Only a `cd` to one
117
+ * literal relative directory is followed; anything else is `unknown`.
118
+ */
119
+ readonly directory?: ShellDirectory
105
120
  }
106
121
 
122
+ /** Where a `cd` earlier in the line left the shell, relative to where the line began. */
123
+ export type ShellDirectory =
124
+ | { readonly relative: string }
125
+ /** A `cd` to a runtime-decided, absolute, `~` or `-` target, or a `pushd`/`popd`/`chdir`. */
126
+ | { readonly unknown: true }
127
+
107
128
  export interface ShellPathAccess {
108
129
  readonly uses: readonly ShellPathUse[]
109
130
  /** A `cd`, `pushd`, `popd` or `chdir` runs somewhere in the line. */
@@ -150,6 +171,105 @@ function operands(args: Args): ShellWord[] {
150
171
  return out
151
172
  }
152
173
 
174
+ /** `value` as a word that keeps whether `from` will still be expanded by the shell. */
175
+ function derived(from: ShellWord, value: string): ShellWord {
176
+ return { ...from, text: value, value }
177
+ }
178
+
179
+ /**
180
+ * Operands of `cp` and `mv`. GNU lets a flag follow an operand, and the
181
+ * destination can be a flag's value: `--target-directory=D`, `--target-directory D`,
182
+ * `-tD`, `-t D`, also inside a cluster (`-at D`, `-rtD`). Reading the operands
183
+ * alone would miss the attached and the trailing forms.
184
+ */
185
+ function copyOperands(args: Args): ShellWord[] {
186
+ const out: ShellWord[] = []
187
+ let afterDashes = false
188
+ for (let i = 0; i < args.length; i += 1) {
189
+ const arg = args[i] as ShellWord
190
+ const text = arg.value
191
+ if (afterDashes || text === '-' || !text.startsWith('-')) {
192
+ out.push(arg)
193
+ continue
194
+ }
195
+ if (text === '--') {
196
+ afterDashes = true
197
+ continue
198
+ }
199
+ if (text.startsWith('--')) {
200
+ const eq = text.indexOf('=')
201
+ const name = eq >= 0 ? text.slice(0, eq) : text
202
+ if (name === '--target-directory') {
203
+ if (eq >= 0) out.push(derived(arg, text.slice(eq + 1)))
204
+ else if (args[i + 1] !== undefined) {
205
+ out.push(args[i + 1] as ShellWord)
206
+ i += 1
207
+ }
208
+ } else if (name === '--suffix' && eq < 0) i += 1
209
+ continue
210
+ }
211
+ for (let j = 1; j < text.length; j += 1) {
212
+ const letter = text[j] as string
213
+ if (letter !== 't' && letter !== 'S') continue
214
+ const rest = text.slice(j + 1)
215
+ if (letter === 't') {
216
+ if (rest !== '') out.push(derived(arg, rest))
217
+ else if (args[i + 1] !== undefined) {
218
+ out.push(args[i + 1] as ShellWord)
219
+ i += 1
220
+ }
221
+ } else if (rest === '') i += 1
222
+ break
223
+ }
224
+ }
225
+ return out
226
+ }
227
+
228
+ /**
229
+ * The files `touch` reads: `--reference=F`, `--reference F`, `-rF`, `-r F`
230
+ * (also in a cluster). They are not written, but a path outside the roots is
231
+ * the same question whichever way it is opened.
232
+ */
233
+ function touchReferences(args: Args): ShellWord[] {
234
+ const out: ShellWord[] = []
235
+ let afterDashes = false
236
+ for (let i = 0; i < args.length; i += 1) {
237
+ const arg = args[i] as ShellWord
238
+ const text = arg.value
239
+ if (afterDashes || text === '-' || !text.startsWith('-')) continue
240
+ if (text === '--') {
241
+ afterDashes = true
242
+ continue
243
+ }
244
+ if (text.startsWith('--')) {
245
+ const eq = text.indexOf('=')
246
+ const name = eq >= 0 ? text.slice(0, eq) : text
247
+ if (name === '--reference') {
248
+ if (eq >= 0) out.push(derived(arg, text.slice(eq + 1)))
249
+ else if (args[i + 1] !== undefined) {
250
+ out.push(args[i + 1] as ShellWord)
251
+ i += 1
252
+ }
253
+ }
254
+ continue
255
+ }
256
+ for (let j = 1; j < text.length; j += 1) {
257
+ const letter = text[j] as string
258
+ if (letter !== 'r' && letter !== 'd' && letter !== 't') continue
259
+ const rest = text.slice(j + 1)
260
+ if (letter === 'r') {
261
+ if (rest !== '') out.push(derived(arg, rest))
262
+ else if (args[i + 1] !== undefined) {
263
+ out.push(args[i + 1] as ShellWord)
264
+ i += 1
265
+ }
266
+ } else if (rest === '') i += 1
267
+ break
268
+ }
269
+ }
270
+ return out
271
+ }
272
+
153
273
  interface FlagTable {
154
274
  readonly booleanShort: string
155
275
  readonly valuedShort: string
@@ -681,11 +801,11 @@ const EXTRACTORS: Readonly<Record<string, (args: Args) => ShellWord[]>> = {
681
801
  ls: lsOperands,
682
802
  find: findOperands,
683
803
  mkdir: operands,
684
- touch: operands,
804
+ touch: touchOperands,
685
805
  rm: operands,
686
806
  rmdir: operands,
687
- mv: operands,
688
- cp: operands,
807
+ mv: copyOperands,
808
+ cp: copyOperands,
689
809
  cat: operands,
690
810
  head: operands,
691
811
  tail: operands,
@@ -845,6 +965,38 @@ const EXTRACTORS: Readonly<Record<string, (args: Args) => ShellWord[]>> = {
845
965
  git: gitOperands,
846
966
  }
847
967
 
968
+ /**
969
+ * Operands of `touch`: the files it changes, with the values of `-d`, `-t` and
970
+ * `-r`/`--reference` taken out (they are a date, a stamp and a file that is read).
971
+ */
972
+ function touchOperands(args: Args): ShellWord[] {
973
+ const out: ShellWord[] = []
974
+ let afterDashes = false
975
+ for (let i = 0; i < args.length; i += 1) {
976
+ const arg = args[i] as ShellWord
977
+ const text = arg.value
978
+ if (afterDashes || text === '-' || !text.startsWith('-')) {
979
+ out.push(arg)
980
+ continue
981
+ }
982
+ if (text === '--') {
983
+ afterDashes = true
984
+ continue
985
+ }
986
+ if (text.startsWith('--')) {
987
+ if (/^--(?:reference|date|time)$/.test(text)) i += 1
988
+ continue
989
+ }
990
+ for (let j = 1; j < text.length; j += 1) {
991
+ if ('rdt'.includes(text[j] as string)) {
992
+ if (j === text.length - 1) i += 1
993
+ break
994
+ }
995
+ }
996
+ }
997
+ return out
998
+ }
999
+
848
1000
  /** Commands that create or change what their operands name. `sed` is added per call when it edits in place. */
849
1001
  const WRITERS = new Set(['mkdir', 'touch', 'rm', 'rmdir', 'mv', 'cp', 'tee'])
850
1002
 
@@ -860,68 +1012,129 @@ function editsInPlace(args: Args): boolean {
860
1012
  })
861
1013
  }
862
1014
 
1015
+ /** The directory a line is in after `invocation`, given the one before it. */
1016
+ function directoryAfter(
1017
+ before: ShellDirectory | undefined,
1018
+ invocation: ShellInvocation,
1019
+ ): ShellDirectory {
1020
+ if (before !== undefined && 'unknown' in before) return before
1021
+ if (invocation.program !== 'cd') return { unknown: true }
1022
+ const operands = invocation.args.filter((a) => a.expands || !/^-[LPe@]+$/.test(a.value))
1023
+ const [target, ...extra] = operands
1024
+ if (target === undefined || extra.length > 0 || target.expands || target.value === '') {
1025
+ return { unknown: true }
1026
+ }
1027
+ const value = target.value
1028
+ if (value === '-' || value === '--' || value.startsWith('/') || value.startsWith('~')) {
1029
+ return { unknown: true }
1030
+ }
1031
+ const base = before?.relative
1032
+ return { relative: base === undefined ? value : `${base}/${value}` }
1033
+ }
1034
+
863
1035
  /**
864
1036
  * The paths `reading` names: positional arguments of the commands in the
865
1037
  * table, and the targets of its redirections.
1038
+ *
1039
+ * Each use carries the directory an earlier literal `cd` left the shell in
1040
+ * (see {@link ShellPathUse.directory}), so a caller can tell
1041
+ * `cd packages/sdk && echo x > out.txt` from `cd "$X" && echo x > out.txt`.
866
1042
  */
867
1043
  export function shellPathAccess(reading: ShellReading): ShellPathAccess {
868
1044
  const uses: ShellPathUse[] = []
869
1045
  let changesDirectory = false
870
- for (const { program, args } of reading.invocations) {
1046
+ let directory: ShellDirectory | undefined
1047
+ const covered = new Set<ShellRedirection>()
1048
+ for (const invocation of reading.invocations) {
1049
+ const { program, args } = invocation
871
1050
  if (CD_PROGRAMS.has(program)) changesDirectory = true
872
1051
  const extract = EXTRACTORS[program]
873
- if (extract === undefined) continue
874
- const access: PathAccess =
875
- WRITERS.has(program) || (program === 'sed' && editsInPlace(args)) ? 'write' : 'read'
876
- for (const arg of extract(args)) {
877
- if (arg.value === '' || isHarmlessTarget(arg.value)) continue
878
- uses.push({
879
- path: arg.value,
880
- access,
881
- via: program,
882
- unresolved: expandsAtRuntime(arg),
883
- })
1052
+ if (extract !== undefined) {
1053
+ const access: PathAccess =
1054
+ WRITERS.has(program) || (program === 'sed' && editsInPlace(args)) ? 'write' : 'read'
1055
+ for (const arg of extract(args)) {
1056
+ if (arg.value === '' || isHarmlessTarget(arg.value)) continue
1057
+ uses.push({
1058
+ path: arg.value,
1059
+ access,
1060
+ via: program,
1061
+ unresolved: expandsAtRuntime(arg),
1062
+ ...(directory !== undefined ? { directory } : {}),
1063
+ })
1064
+ }
1065
+ }
1066
+ if (program === 'touch') {
1067
+ for (const arg of touchReferences(args)) {
1068
+ if (arg.value === '' || isHarmlessTarget(arg.value)) continue
1069
+ uses.push({
1070
+ path: arg.value,
1071
+ access: 'read',
1072
+ via: program,
1073
+ unresolved: expandsAtRuntime(arg),
1074
+ ...(directory !== undefined ? { directory } : {}),
1075
+ })
1076
+ }
1077
+ }
1078
+ for (const redirection of invocation.redirections ?? []) {
1079
+ covered.add(redirection)
1080
+ pushRedirection(uses, redirection, directory)
884
1081
  }
1082
+ if (CD_PROGRAMS.has(program)) directory = directoryAfter(directory, invocation)
885
1083
  }
886
1084
  for (const redirection of reading.redirections) {
887
- const target = redirection.target
888
- if (target.value === '' || isHarmlessTarget(target.value)) continue
889
- switch (redirection.operator) {
890
- case '>':
891
- case '>>':
892
- case '>|':
893
- case '&>':
894
- case '&>>':
895
- case '<>':
1085
+ if (covered.has(redirection)) continue
1086
+ // Not tied to a command the reader saw (a compound command's own), so
1087
+ // where it runs cannot be told once a `cd` is in the line.
1088
+ pushRedirection(uses, redirection, changesDirectory ? { unknown: true } : undefined)
1089
+ }
1090
+ return { uses, changesDirectory }
1091
+ }
1092
+
1093
+ function pushRedirection(
1094
+ uses: ShellPathUse[],
1095
+ redirection: ShellRedirection,
1096
+ directory: ShellDirectory | undefined,
1097
+ ): void {
1098
+ const target = redirection.target
1099
+ if (target.value === '' || isHarmlessTarget(target.value)) return
1100
+ const where = directory !== undefined ? { directory } : {}
1101
+ switch (redirection.operator) {
1102
+ case '>':
1103
+ case '>>':
1104
+ case '>|':
1105
+ case '&>':
1106
+ case '&>>':
1107
+ case '<>':
1108
+ uses.push({
1109
+ path: target.value,
1110
+ access: 'write',
1111
+ via: 'redirect',
1112
+ unresolved: expandsAtRuntime(target),
1113
+ ...where,
1114
+ })
1115
+ break
1116
+ case '>&':
1117
+ // `>&2` and `>&-` duplicate or close a descriptor; any other word is a file.
1118
+ if (!/^(?:\d+-?|-)$/.test(target.value)) {
896
1119
  uses.push({
897
1120
  path: target.value,
898
1121
  access: 'write',
899
1122
  via: 'redirect',
900
1123
  unresolved: expandsAtRuntime(target),
1124
+ ...where,
901
1125
  })
902
- break
903
- case '>&':
904
- // `>&2` and `>&-` duplicate or close a descriptor; any other word is a file.
905
- if (!/^(?:\d+-?|-)$/.test(target.value)) {
906
- uses.push({
907
- path: target.value,
908
- access: 'write',
909
- via: 'redirect',
910
- unresolved: expandsAtRuntime(target),
911
- })
912
- }
913
- break
914
- case '<':
915
- uses.push({
916
- path: target.value,
917
- access: 'read',
918
- via: 'redirect',
919
- unresolved: expandsAtRuntime(target),
920
- })
921
- break
922
- default:
923
- break
924
- }
1126
+ }
1127
+ break
1128
+ case '<':
1129
+ uses.push({
1130
+ path: target.value,
1131
+ access: 'read',
1132
+ via: 'redirect',
1133
+ unresolved: expandsAtRuntime(target),
1134
+ ...where,
1135
+ })
1136
+ break
1137
+ default:
1138
+ break
925
1139
  }
926
- return { uses, changesDirectory }
927
1140
  }
@@ -142,6 +142,14 @@ export interface AcpServerOptions {
142
142
  readonly supportsPromptAttachments?: boolean
143
143
  /** The host applies optional effort and permission settings to the exact turn. */
144
144
  readonly supportsPromptOptions?: boolean
145
+ /**
146
+ * Which of the newer review modes, `bypass` and `classify`, the gateway
147
+ * applies when a prompt asks for one in its options. Omitted, a prompt that
148
+ * asks for either is refused with an error that says so, before any turn
149
+ * starts, rather than reaching a gateway that cannot honour it. Advertised
150
+ * as `promptPermissionModes` at initialization.
151
+ */
152
+ readonly promptPermissionModes?: readonly ('bypass' | 'classify')[]
145
153
  /** Emit planning notifications only to clients declaring namzu/tasks. */
146
154
  readonly supportsTaskNotifications?: boolean
147
155
  readonly transport: MCPTransport
@@ -465,6 +473,9 @@ export class ACPServer {
465
473
  protocolVersion: ACP_PROTOCOL_VERSION,
466
474
  ...(this.options.supportsPromptAttachments ? { promptAttachments: true } : {}),
467
475
  ...(this.options.supportsPromptOptions ? { promptOptions: true } : {}),
476
+ ...(this.options.supportsPromptOptions && this.options.promptPermissionModes?.length
477
+ ? { promptPermissionModes: [...this.options.promptPermissionModes] }
478
+ : {}),
468
479
  agentInfo: this.options.agentInfo,
469
480
  // From the registry, per call. A hard-coded list here would be a
470
481
  // second definition of the command surface.
@@ -850,6 +861,15 @@ export class ACPServer {
850
861
  (permissionMode !== undefined && !isReviewMode(permissionMode))
851
862
  )
852
863
  throw new AcpError(ACP_ERROR_CODES.INVALID_PARAMS, 'Invalid prompt settings.')
864
+ if (
865
+ (permissionMode === 'bypass' || permissionMode === 'classify') &&
866
+ this.options.promptPermissionModes?.includes(permissionMode) !== true
867
+ ) {
868
+ throw new AcpError(
869
+ ACP_ERROR_CODES.INVALID_PARAMS,
870
+ `This agent does not accept the "${permissionMode}" permission mode in prompt settings.`,
871
+ )
872
+ }
853
873
  return {
854
874
  ...(effort
855
875
  ? { effort: effort as NonNullable<AcpSessionPromptParams['options']>['effort'] }
@@ -1431,7 +1431,11 @@ export { AUTO_APPROVE_POLICY_NAME } from './runtime/query/approval-policy.js'
1431
1431
  // accept-edits, plan, strict — as an `ApprovalPolicy`. See
1432
1432
  // `runtime/query/review-policy.ts`.
1433
1433
  export {
1434
+ ACCEPT_EDITS_SHELL_COMMANDS,
1434
1435
  ACCEPT_EDITS_TOOLS,
1436
+ ASK_RULE_UNATTENDED_REFUSAL,
1437
+ CLASSIFIER_SAFE_TOOLS,
1438
+ CLASSIFIER_TRANSCRIPT_TOO_LONG_FEEDBACK,
1435
1439
  OUTSIDE_ROOTS_UNATTENDED_REFUSAL,
1436
1440
  PLAN_MODE_REFUSAL,
1437
1441
  REVIEW_EXEMPT_WRITES,
@@ -1440,12 +1444,26 @@ export {
1440
1444
  SCREEN_CONSENT_DECLINED_FEEDBACK,
1441
1445
  SCREEN_CONSENT_UNATTENDED_REFUSAL,
1442
1446
  STRICT_MODE_REFUSAL,
1447
+ UNFINISHED_CHECK_UNATTENDED_REFUSAL,
1443
1448
  batchNeedsReview,
1444
1449
  createReviewHandler,
1445
1450
  createReviewPolicy,
1446
1451
  isReviewExempt,
1447
1452
  isReviewMode,
1453
+ modeReviewsAllowedCalls,
1448
1454
  } from './runtime/query/review-policy.js'
1455
+ // The model that decides, in the `classify` review mode, what a person would
1456
+ // otherwise be asked. See `runtime/query/auto-review/`.
1457
+ export {
1458
+ AUTO_REVIEW_DEFAULTS_MARKER,
1459
+ DEFAULT_ALLOW as DEFAULT_AUTO_REVIEW_ALLOW,
1460
+ DEFAULT_AUTO_REVIEW_LIMITS,
1461
+ DEFAULT_HARD_DENY as DEFAULT_AUTO_REVIEW_HARD_DENY,
1462
+ DEFAULT_SOFT_DENY as DEFAULT_AUTO_REVIEW_SOFT_DENY,
1463
+ autoReviewBlockedFeedback,
1464
+ createAutoReviewClassifier,
1465
+ resolveAutoReviewRules,
1466
+ } from './runtime/query/auto-review/index.js'
1449
1467
  // What the model is told when a person declines a call without words of their own.
1450
1468
  export { DECLINED_TOOL_CALL_FEEDBACK } from './runtime/query/declined.js'
1451
1469
 
@@ -436,6 +436,17 @@ export type {
436
436
  CompactionSkipReason,
437
437
  } from './compaction/plan.js'
438
438
 
439
+ export type {
440
+ AutoReviewClassifier,
441
+ AutoReviewClassifierOptions,
442
+ AutoReviewDecision,
443
+ AutoReviewLimits,
444
+ AutoReviewRequest,
445
+ AutoReviewRules,
446
+ AutoReviewStage,
447
+ ResolvedAutoReviewRules,
448
+ } from './runtime/query/auto-review/index.js'
449
+
439
450
  export type {
440
451
  ReviewExemption,
441
452
  ReviewMode,