@ordewell/core 0.4.8 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ModeResolver-Dx5gzNPC.d.ts +44 -0
- package/dist/ModeResolver-F5wnlSz1.d.mts +44 -0
- package/dist/{chunk-SRGQBGI5.mjs → chunk-IO6PEEN6.mjs} +3 -1
- package/dist/{chunk-SRGQBGI5.mjs.map → chunk-IO6PEEN6.mjs.map} +1 -1
- package/dist/{chunk-YXAIQKVE.mjs → chunk-TUGJXD4G.mjs} +2 -2
- package/dist/chunk-YI4ZDZEE.mjs +862 -0
- package/dist/chunk-YI4ZDZEE.mjs.map +1 -0
- package/dist/index.d.mts +202 -17
- package/dist/index.d.ts +202 -17
- package/dist/index.js +2568 -433
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1966 -182
- package/dist/index.mjs.map +1 -1
- package/dist/{parsing-RjN9JbyG.d.ts → parsing-BqbJySeo.d.mts} +5 -43
- package/dist/{parsing-xpozY5ac.d.mts → parsing-X0Gvuz-q.d.ts} +5 -43
- package/dist/parsing.d.mts +2 -1
- package/dist/parsing.d.ts +2 -1
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +2 -2
- package/dist/{plan-utils-nfHKSbd3.d.mts → plan-utils-B5rVXNVI.d.mts} +27 -5
- package/dist/{plan-utils-Cgc0cCH1.d.ts → plan-utils-DtsfHnn_.d.ts} +27 -5
- package/dist/plan-utils.d.mts +2 -1
- package/dist/plan-utils.d.ts +2 -1
- package/dist/plan-utils.js +1 -9
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +2 -2
- package/package.json +1 -1
- package/dist/chunk-4KIWPW4K.mjs +0 -510
- package/dist/chunk-4KIWPW4K.mjs.map +0 -1
- /package/dist/{chunk-YXAIQKVE.mjs.map → chunk-TUGJXD4G.mjs.map} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -13,23 +13,30 @@ import {
|
|
|
13
13
|
resolveTaskMode,
|
|
14
14
|
runnerModesFrom,
|
|
15
15
|
validatePlanModification
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-TUGJXD4G.mjs";
|
|
17
17
|
import {
|
|
18
|
+
UPDATABLE_FIELDS,
|
|
18
19
|
applyTaskOps,
|
|
19
20
|
canMergeTasks,
|
|
20
21
|
canSetDependencies,
|
|
21
22
|
canSplitTask,
|
|
23
|
+
clampThinkingEffort,
|
|
22
24
|
classifyOutcome,
|
|
25
|
+
coerceAssignments,
|
|
23
26
|
dependencyCandidates,
|
|
24
27
|
dependentsOf,
|
|
28
|
+
effectiveAllowlist,
|
|
29
|
+
filterModelsForPrompt,
|
|
25
30
|
parseTaskOpsJson,
|
|
26
31
|
summarizeToolCall,
|
|
27
|
-
textHasTaskOps
|
|
28
|
-
|
|
32
|
+
textHasTaskOps,
|
|
33
|
+
validateTaskEdit
|
|
34
|
+
} from "./chunk-YI4ZDZEE.mjs";
|
|
29
35
|
import {
|
|
30
36
|
PLAN_ENVELOPE_KEY,
|
|
31
37
|
PlanParseError,
|
|
32
38
|
TASK_OPS_ENVELOPE_KEY,
|
|
39
|
+
TASK_QUERY_ENVELOPE_KEY,
|
|
33
40
|
addTaskToPlan,
|
|
34
41
|
createEmptyPlan,
|
|
35
42
|
createTask,
|
|
@@ -49,7 +56,7 @@ import {
|
|
|
49
56
|
updateTaskInPlan,
|
|
50
57
|
validateModifiedPlan,
|
|
51
58
|
warningsText
|
|
52
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-IO6PEEN6.mjs";
|
|
53
60
|
|
|
54
61
|
// src/interfaces/IFileSystem.ts
|
|
55
62
|
var GREP_DEFAULT_HEAD_LIMIT = 100;
|
|
@@ -118,13 +125,1233 @@ var AUTO_COMMANDS = [
|
|
|
118
125
|
"jq",
|
|
119
126
|
"yq"
|
|
120
127
|
];
|
|
128
|
+
var UNIVERSAL_FLAGS = ["--help", "--version"];
|
|
129
|
+
var COUNT_FLAG = /^-\d+$/;
|
|
130
|
+
var FLAG_POLICY = {
|
|
131
|
+
ls: {
|
|
132
|
+
cluster: true,
|
|
133
|
+
booleans: [
|
|
134
|
+
"-1",
|
|
135
|
+
"-a",
|
|
136
|
+
"-A",
|
|
137
|
+
"-b",
|
|
138
|
+
"-c",
|
|
139
|
+
"-C",
|
|
140
|
+
"-d",
|
|
141
|
+
"-D",
|
|
142
|
+
"-f",
|
|
143
|
+
"-F",
|
|
144
|
+
"-g",
|
|
145
|
+
"-G",
|
|
146
|
+
"-h",
|
|
147
|
+
"-H",
|
|
148
|
+
"-i",
|
|
149
|
+
"-k",
|
|
150
|
+
"-l",
|
|
151
|
+
"-L",
|
|
152
|
+
"-m",
|
|
153
|
+
"-n",
|
|
154
|
+
"-N",
|
|
155
|
+
"-o",
|
|
156
|
+
"-p",
|
|
157
|
+
"-q",
|
|
158
|
+
"-Q",
|
|
159
|
+
"-r",
|
|
160
|
+
"-R",
|
|
161
|
+
"-s",
|
|
162
|
+
"-S",
|
|
163
|
+
"-t",
|
|
164
|
+
"-u",
|
|
165
|
+
"-U",
|
|
166
|
+
"-v",
|
|
167
|
+
"-x",
|
|
168
|
+
"-X",
|
|
169
|
+
"-Z",
|
|
170
|
+
"--all",
|
|
171
|
+
"--almost-all",
|
|
172
|
+
"--author",
|
|
173
|
+
"--escape",
|
|
174
|
+
"--classify",
|
|
175
|
+
"--file-type",
|
|
176
|
+
"--full-time",
|
|
177
|
+
"--no-group",
|
|
178
|
+
"--human-readable",
|
|
179
|
+
"--si",
|
|
180
|
+
"--dereference-command-line",
|
|
181
|
+
"--inode",
|
|
182
|
+
"--kibibytes",
|
|
183
|
+
"--dereference",
|
|
184
|
+
"--numeric-uid-gid",
|
|
185
|
+
"--literal",
|
|
186
|
+
"--hide-control-chars",
|
|
187
|
+
"--show-control-chars",
|
|
188
|
+
"--quote-name",
|
|
189
|
+
"--reverse",
|
|
190
|
+
"--recursive",
|
|
191
|
+
"--size",
|
|
192
|
+
"--directory",
|
|
193
|
+
"--group-directories-first",
|
|
194
|
+
"--context",
|
|
195
|
+
"--zero"
|
|
196
|
+
],
|
|
197
|
+
values: [
|
|
198
|
+
"-w",
|
|
199
|
+
"-I",
|
|
200
|
+
"-T",
|
|
201
|
+
"--width",
|
|
202
|
+
"--ignore",
|
|
203
|
+
"--hide",
|
|
204
|
+
"--block-size",
|
|
205
|
+
"--color",
|
|
206
|
+
"--format",
|
|
207
|
+
"--sort",
|
|
208
|
+
"--time",
|
|
209
|
+
"--time-style",
|
|
210
|
+
"--indicator-style",
|
|
211
|
+
"--quoting-style",
|
|
212
|
+
"--tabsize"
|
|
213
|
+
]
|
|
214
|
+
},
|
|
215
|
+
tree: {
|
|
216
|
+
cluster: true,
|
|
217
|
+
// `-o` is absent on purpose: it is tree's write-to-a-file flag, which the
|
|
218
|
+
// redirect check never sees because there is no redirect.
|
|
219
|
+
booleans: [
|
|
220
|
+
"-a",
|
|
221
|
+
"-d",
|
|
222
|
+
"-f",
|
|
223
|
+
"-i",
|
|
224
|
+
"-l",
|
|
225
|
+
"-x",
|
|
226
|
+
"-C",
|
|
227
|
+
"-n",
|
|
228
|
+
"-p",
|
|
229
|
+
"-s",
|
|
230
|
+
"-h",
|
|
231
|
+
"-u",
|
|
232
|
+
"-g",
|
|
233
|
+
"-D",
|
|
234
|
+
"-t",
|
|
235
|
+
"-c",
|
|
236
|
+
"-r",
|
|
237
|
+
"-F",
|
|
238
|
+
"-q",
|
|
239
|
+
"-N",
|
|
240
|
+
"-Q",
|
|
241
|
+
"-v",
|
|
242
|
+
"-J",
|
|
243
|
+
"-X",
|
|
244
|
+
"-Z",
|
|
245
|
+
"--noreport",
|
|
246
|
+
"--dirsfirst",
|
|
247
|
+
"--du",
|
|
248
|
+
"--prune",
|
|
249
|
+
"--matchdirs",
|
|
250
|
+
"--inodes",
|
|
251
|
+
"--device",
|
|
252
|
+
"--si",
|
|
253
|
+
"--gitignore",
|
|
254
|
+
"--ignore-case",
|
|
255
|
+
"--filesfirst",
|
|
256
|
+
"--nolinks",
|
|
257
|
+
"--hintro"
|
|
258
|
+
],
|
|
259
|
+
values: [
|
|
260
|
+
"-L",
|
|
261
|
+
"-P",
|
|
262
|
+
"-I",
|
|
263
|
+
"-H",
|
|
264
|
+
"--filelimit",
|
|
265
|
+
"--timefmt",
|
|
266
|
+
"--charset",
|
|
267
|
+
"--sort",
|
|
268
|
+
"--gitfile",
|
|
269
|
+
"--info",
|
|
270
|
+
"--prefix"
|
|
271
|
+
]
|
|
272
|
+
},
|
|
273
|
+
git: {
|
|
274
|
+
cluster: true,
|
|
275
|
+
// Absent on purpose, and each for a reason that was demonstrated rather than
|
|
276
|
+
// guessed at:
|
|
277
|
+
// -c, --config-env set configuration inline, and several configuration
|
|
278
|
+
// keys name a program to run (`core.pager`, `core.editor`,
|
|
279
|
+
// `diff.external`), so this is an execution flag with one
|
|
280
|
+
// indirection in front of it.
|
|
281
|
+
// --exec-path redirects where git finds its helper programs; a
|
|
282
|
+
// fabricated helper ran during an ordinary remote listing.
|
|
283
|
+
// -O, --open-files-in-pager runs an arbitrary named program over search hits.
|
|
284
|
+
// --upload-pack, --receive-pack name the program run on the other end.
|
|
285
|
+
// --textconv, --ext-diff run filters the repository itself configures.
|
|
286
|
+
// --git-dir, --work-tree repoint git at a tree whose configuration is not
|
|
287
|
+
// the one the developer is looking at.
|
|
288
|
+
// -o, --output write a file. `git ls-files -o` is a legitimate spelling
|
|
289
|
+
// of `--others`, but `git archive -o` writes, and the
|
|
290
|
+
// long spelling stays available for the read.
|
|
291
|
+
booleans: [
|
|
292
|
+
"-a",
|
|
293
|
+
"-b",
|
|
294
|
+
"-E",
|
|
295
|
+
"-F",
|
|
296
|
+
"-h",
|
|
297
|
+
"-H",
|
|
298
|
+
"-i",
|
|
299
|
+
"-I",
|
|
300
|
+
"-l",
|
|
301
|
+
"-p",
|
|
302
|
+
"-P",
|
|
303
|
+
"-q",
|
|
304
|
+
"-r",
|
|
305
|
+
"-R",
|
|
306
|
+
"-s",
|
|
307
|
+
"-t",
|
|
308
|
+
"-u",
|
|
309
|
+
"-v",
|
|
310
|
+
"-w",
|
|
311
|
+
"-z",
|
|
312
|
+
"--oneline",
|
|
313
|
+
"--graph",
|
|
314
|
+
"--stat",
|
|
315
|
+
"--shortstat",
|
|
316
|
+
"--numstat",
|
|
317
|
+
"--dirstat",
|
|
318
|
+
"--summary",
|
|
319
|
+
"--name-only",
|
|
320
|
+
"--name-status",
|
|
321
|
+
"--raw",
|
|
322
|
+
"--patch",
|
|
323
|
+
"--no-patch",
|
|
324
|
+
"--patch-with-stat",
|
|
325
|
+
"--cached",
|
|
326
|
+
"--staged",
|
|
327
|
+
"--porcelain",
|
|
328
|
+
"--short",
|
|
329
|
+
"--long",
|
|
330
|
+
"--branch",
|
|
331
|
+
"--show-stash",
|
|
332
|
+
"--show-current",
|
|
333
|
+
"--all",
|
|
334
|
+
"--tags",
|
|
335
|
+
"--heads",
|
|
336
|
+
"--remotes",
|
|
337
|
+
"--list",
|
|
338
|
+
"--verbose",
|
|
339
|
+
"--quiet",
|
|
340
|
+
"--abbrev-ref",
|
|
341
|
+
"--count",
|
|
342
|
+
"--reverse",
|
|
343
|
+
"--first-parent",
|
|
344
|
+
"--no-merges",
|
|
345
|
+
"--merges",
|
|
346
|
+
"--follow",
|
|
347
|
+
"--full-history",
|
|
348
|
+
"--simplify-merges",
|
|
349
|
+
"--sparse",
|
|
350
|
+
"--dense",
|
|
351
|
+
"--ancestry-path",
|
|
352
|
+
"--left-right",
|
|
353
|
+
"--cherry",
|
|
354
|
+
"--cherry-pick",
|
|
355
|
+
"--cherry-mark",
|
|
356
|
+
"--boundary",
|
|
357
|
+
"--topo-order",
|
|
358
|
+
"--date-order",
|
|
359
|
+
"--author-date-order",
|
|
360
|
+
"--relative-date",
|
|
361
|
+
"--parents",
|
|
362
|
+
"--children",
|
|
363
|
+
"--timestamp",
|
|
364
|
+
"--no-walk",
|
|
365
|
+
"--do-walk",
|
|
366
|
+
"--decorate",
|
|
367
|
+
"--no-decorate",
|
|
368
|
+
"--abbrev-commit",
|
|
369
|
+
"--no-abbrev-commit",
|
|
370
|
+
"--abbrev",
|
|
371
|
+
"--no-abbrev",
|
|
372
|
+
"--color",
|
|
373
|
+
"--no-color",
|
|
374
|
+
"--pretty",
|
|
375
|
+
"--format",
|
|
376
|
+
"--expand-tabs",
|
|
377
|
+
"--no-expand-tabs",
|
|
378
|
+
"--numbered",
|
|
379
|
+
"--email",
|
|
380
|
+
"--no-notes",
|
|
381
|
+
"--notes",
|
|
382
|
+
"--show-signature",
|
|
383
|
+
"--find-renames",
|
|
384
|
+
"--find-copies",
|
|
385
|
+
"--find-copies-harder",
|
|
386
|
+
"--break-rewrites",
|
|
387
|
+
"--no-renames",
|
|
388
|
+
"--irreversible-delete",
|
|
389
|
+
"--full-index",
|
|
390
|
+
"--binary",
|
|
391
|
+
"--text",
|
|
392
|
+
"--check",
|
|
393
|
+
"--exit-code",
|
|
394
|
+
"--word-diff",
|
|
395
|
+
"--function-context",
|
|
396
|
+
"--ignore-all-space",
|
|
397
|
+
"--ignore-space-change",
|
|
398
|
+
"--ignore-space-at-eol",
|
|
399
|
+
"--ignore-blank-lines",
|
|
400
|
+
"--ignore-cr-at-eol",
|
|
401
|
+
"--no-prefix",
|
|
402
|
+
"--default-prefix",
|
|
403
|
+
"--no-textconv",
|
|
404
|
+
"--no-ext-diff",
|
|
405
|
+
"--submodule",
|
|
406
|
+
"--ignore-submodules",
|
|
407
|
+
"--recurse-submodules",
|
|
408
|
+
"--no-recurse-submodules",
|
|
409
|
+
"--others",
|
|
410
|
+
"--deleted",
|
|
411
|
+
"--modified",
|
|
412
|
+
"--unmerged",
|
|
413
|
+
"--stage",
|
|
414
|
+
"--killed",
|
|
415
|
+
"--ignored",
|
|
416
|
+
"--exclude-standard",
|
|
417
|
+
"--error-unmatch",
|
|
418
|
+
"--full-name",
|
|
419
|
+
"--directory",
|
|
420
|
+
"--no-empty-directory",
|
|
421
|
+
"--untracked-files",
|
|
422
|
+
"--no-optional-locks",
|
|
423
|
+
"--no-pager",
|
|
424
|
+
"--paginate",
|
|
425
|
+
"--bare",
|
|
426
|
+
"--literal-pathspecs",
|
|
427
|
+
"--glob-pathspecs",
|
|
428
|
+
"--noglob-pathspecs",
|
|
429
|
+
"--icase-pathspecs",
|
|
430
|
+
"--line-number",
|
|
431
|
+
"--column",
|
|
432
|
+
"--heading",
|
|
433
|
+
"--no-index",
|
|
434
|
+
"--untracked",
|
|
435
|
+
"--no-recursive",
|
|
436
|
+
"--name-rev",
|
|
437
|
+
"--root",
|
|
438
|
+
"--dirty",
|
|
439
|
+
"--broken",
|
|
440
|
+
"--always",
|
|
441
|
+
"--merged",
|
|
442
|
+
"--no-merged",
|
|
443
|
+
"--show-toplevel",
|
|
444
|
+
"--show-cdup",
|
|
445
|
+
"--git-common-dir",
|
|
446
|
+
"--absolute-git-dir",
|
|
447
|
+
"--is-inside-work-tree",
|
|
448
|
+
"--is-inside-git-dir",
|
|
449
|
+
"--is-bare-repository",
|
|
450
|
+
"--symbolic",
|
|
451
|
+
"--symbolic-full-name",
|
|
452
|
+
"--verify",
|
|
453
|
+
"--short-hash",
|
|
454
|
+
"--textual",
|
|
455
|
+
"--batch",
|
|
456
|
+
"--batch-check",
|
|
457
|
+
"--batch-all-objects",
|
|
458
|
+
"--buffer",
|
|
459
|
+
"--follow-symlinks",
|
|
460
|
+
"--use-mailmap"
|
|
461
|
+
],
|
|
462
|
+
values: [
|
|
463
|
+
"-n",
|
|
464
|
+
"-C",
|
|
465
|
+
"-B",
|
|
466
|
+
"-M",
|
|
467
|
+
"-L",
|
|
468
|
+
"-S",
|
|
469
|
+
"-G",
|
|
470
|
+
"-U",
|
|
471
|
+
"-e",
|
|
472
|
+
"-m",
|
|
473
|
+
"-f",
|
|
474
|
+
"-g",
|
|
475
|
+
"-x",
|
|
476
|
+
"-X",
|
|
477
|
+
"-W",
|
|
478
|
+
"--max-count",
|
|
479
|
+
"--skip",
|
|
480
|
+
"--since",
|
|
481
|
+
"--after",
|
|
482
|
+
"--until",
|
|
483
|
+
"--before",
|
|
484
|
+
"--author",
|
|
485
|
+
"--grep",
|
|
486
|
+
"--grep-reflog",
|
|
487
|
+
"--committer",
|
|
488
|
+
"--date",
|
|
489
|
+
"--diff-filter",
|
|
490
|
+
"--unified",
|
|
491
|
+
"--stat-width",
|
|
492
|
+
"--stat-name-width",
|
|
493
|
+
"--stat-count",
|
|
494
|
+
"--word-diff-regex",
|
|
495
|
+
"--line-prefix",
|
|
496
|
+
"--src-prefix",
|
|
497
|
+
"--dst-prefix",
|
|
498
|
+
"--pickaxe-regex",
|
|
499
|
+
"--anchored",
|
|
500
|
+
"--color-moved",
|
|
501
|
+
"--color-words",
|
|
502
|
+
"--inter-hunk-context",
|
|
503
|
+
"--max-parents",
|
|
504
|
+
"--min-parents",
|
|
505
|
+
"--simplify-by-decoration",
|
|
506
|
+
"--glob",
|
|
507
|
+
"--exclude",
|
|
508
|
+
"--sort",
|
|
509
|
+
"--contains",
|
|
510
|
+
"--points-at",
|
|
511
|
+
"--decorate-refs",
|
|
512
|
+
"--decorate-refs-exclude",
|
|
513
|
+
"--encoding",
|
|
514
|
+
"--context",
|
|
515
|
+
"--max-depth",
|
|
516
|
+
// `--filters` is absent from this list on purpose: it runs the clean and
|
|
517
|
+
// smudge filters the repository under research configures, which is the
|
|
518
|
+
// same execution vector as the configuration flag above with a different
|
|
519
|
+
// spelling.
|
|
520
|
+
"--and",
|
|
521
|
+
"--or",
|
|
522
|
+
"--not",
|
|
523
|
+
"--all-match",
|
|
524
|
+
"--threads",
|
|
525
|
+
"--path"
|
|
526
|
+
],
|
|
527
|
+
patterns: [COUNT_FLAG]
|
|
528
|
+
},
|
|
529
|
+
wc: {
|
|
530
|
+
cluster: true,
|
|
531
|
+
booleans: [
|
|
532
|
+
"-c",
|
|
533
|
+
"-l",
|
|
534
|
+
"-m",
|
|
535
|
+
"-w",
|
|
536
|
+
"-L",
|
|
537
|
+
"--bytes",
|
|
538
|
+
"--chars",
|
|
539
|
+
"--lines",
|
|
540
|
+
"--words",
|
|
541
|
+
"--max-line-length",
|
|
542
|
+
"--total"
|
|
543
|
+
]
|
|
544
|
+
},
|
|
545
|
+
du: {
|
|
546
|
+
cluster: true,
|
|
547
|
+
booleans: [
|
|
548
|
+
"-0",
|
|
549
|
+
"-a",
|
|
550
|
+
"-b",
|
|
551
|
+
"-c",
|
|
552
|
+
"-D",
|
|
553
|
+
"-h",
|
|
554
|
+
"-H",
|
|
555
|
+
"-k",
|
|
556
|
+
"-l",
|
|
557
|
+
"-L",
|
|
558
|
+
"-m",
|
|
559
|
+
"-P",
|
|
560
|
+
"-s",
|
|
561
|
+
"-S",
|
|
562
|
+
"-x",
|
|
563
|
+
"--all",
|
|
564
|
+
"--apparent-size",
|
|
565
|
+
"--bytes",
|
|
566
|
+
"--total",
|
|
567
|
+
"--dereference-args",
|
|
568
|
+
"--human-readable",
|
|
569
|
+
"--si",
|
|
570
|
+
"--inodes",
|
|
571
|
+
"--count-links",
|
|
572
|
+
"--dereference",
|
|
573
|
+
"--no-dereference",
|
|
574
|
+
"--separate-dirs",
|
|
575
|
+
"--summarize",
|
|
576
|
+
"--one-file-system",
|
|
577
|
+
"--null"
|
|
578
|
+
],
|
|
579
|
+
values: [
|
|
580
|
+
"-d",
|
|
581
|
+
"-t",
|
|
582
|
+
"-B",
|
|
583
|
+
"-X",
|
|
584
|
+
"--max-depth",
|
|
585
|
+
"--threshold",
|
|
586
|
+
"--block-size",
|
|
587
|
+
"--exclude",
|
|
588
|
+
"--exclude-from",
|
|
589
|
+
"--time",
|
|
590
|
+
"--time-style",
|
|
591
|
+
"--files0-from"
|
|
592
|
+
]
|
|
593
|
+
},
|
|
594
|
+
df: {
|
|
595
|
+
cluster: true,
|
|
596
|
+
booleans: [
|
|
597
|
+
"-a",
|
|
598
|
+
"-h",
|
|
599
|
+
"-H",
|
|
600
|
+
"-i",
|
|
601
|
+
"-k",
|
|
602
|
+
"-l",
|
|
603
|
+
"-m",
|
|
604
|
+
"-P",
|
|
605
|
+
"-T",
|
|
606
|
+
"-v",
|
|
607
|
+
"--all",
|
|
608
|
+
"--human-readable",
|
|
609
|
+
"--si",
|
|
610
|
+
"--inodes",
|
|
611
|
+
"--local",
|
|
612
|
+
"--portability",
|
|
613
|
+
"--print-type",
|
|
614
|
+
"--total",
|
|
615
|
+
"--sync",
|
|
616
|
+
"--no-sync"
|
|
617
|
+
],
|
|
618
|
+
values: ["-B", "-t", "-x", "--block-size", "--type", "--exclude-type"]
|
|
619
|
+
},
|
|
620
|
+
file: {
|
|
621
|
+
cluster: true,
|
|
622
|
+
// `-C`/`--compile` is absent: it writes a compiled magic file.
|
|
623
|
+
booleans: [
|
|
624
|
+
"-b",
|
|
625
|
+
"-c",
|
|
626
|
+
"-h",
|
|
627
|
+
"-i",
|
|
628
|
+
"-k",
|
|
629
|
+
"-L",
|
|
630
|
+
"-n",
|
|
631
|
+
"-N",
|
|
632
|
+
"-p",
|
|
633
|
+
"-r",
|
|
634
|
+
"-s",
|
|
635
|
+
"-S",
|
|
636
|
+
"-v",
|
|
637
|
+
"-z",
|
|
638
|
+
"-0",
|
|
639
|
+
"--brief",
|
|
640
|
+
"--mime",
|
|
641
|
+
"--mime-type",
|
|
642
|
+
"--mime-encoding",
|
|
643
|
+
"--dereference",
|
|
644
|
+
"--no-dereference",
|
|
645
|
+
"--keep-going",
|
|
646
|
+
"--no-buffer",
|
|
647
|
+
"--no-pad",
|
|
648
|
+
"--raw",
|
|
649
|
+
"--special-files",
|
|
650
|
+
"--uncompress",
|
|
651
|
+
"--print0",
|
|
652
|
+
"--checking-printout"
|
|
653
|
+
],
|
|
654
|
+
values: [
|
|
655
|
+
"-e",
|
|
656
|
+
"-m",
|
|
657
|
+
"-P",
|
|
658
|
+
"-F",
|
|
659
|
+
"--exclude",
|
|
660
|
+
"--magic-file",
|
|
661
|
+
"--separator",
|
|
662
|
+
"--parameter",
|
|
663
|
+
"--extension",
|
|
664
|
+
"--files-from"
|
|
665
|
+
]
|
|
666
|
+
},
|
|
667
|
+
head: {
|
|
668
|
+
cluster: true,
|
|
669
|
+
booleans: ["-q", "-v", "-z", "--quiet", "--silent", "--verbose", "--zero-terminated"],
|
|
670
|
+
values: ["-c", "-n", "--bytes", "--lines"],
|
|
671
|
+
patterns: [COUNT_FLAG]
|
|
672
|
+
},
|
|
673
|
+
tail: {
|
|
674
|
+
cluster: true,
|
|
675
|
+
booleans: [
|
|
676
|
+
"-f",
|
|
677
|
+
"-F",
|
|
678
|
+
"-q",
|
|
679
|
+
"-v",
|
|
680
|
+
"-z",
|
|
681
|
+
"--follow",
|
|
682
|
+
"--retry",
|
|
683
|
+
"--quiet",
|
|
684
|
+
"--silent",
|
|
685
|
+
"--verbose",
|
|
686
|
+
"--zero-terminated"
|
|
687
|
+
],
|
|
688
|
+
values: [
|
|
689
|
+
"-c",
|
|
690
|
+
"-n",
|
|
691
|
+
"-s",
|
|
692
|
+
"--bytes",
|
|
693
|
+
"--lines",
|
|
694
|
+
"--sleep-interval",
|
|
695
|
+
"--pid",
|
|
696
|
+
"--max-unchanged-stats"
|
|
697
|
+
],
|
|
698
|
+
patterns: [COUNT_FLAG]
|
|
699
|
+
},
|
|
700
|
+
cat: {
|
|
701
|
+
cluster: true,
|
|
702
|
+
booleans: [
|
|
703
|
+
"-A",
|
|
704
|
+
"-b",
|
|
705
|
+
"-e",
|
|
706
|
+
"-E",
|
|
707
|
+
"-n",
|
|
708
|
+
"-s",
|
|
709
|
+
"-t",
|
|
710
|
+
"-T",
|
|
711
|
+
"-u",
|
|
712
|
+
"-v",
|
|
713
|
+
"--show-all",
|
|
714
|
+
"--number-nonblank",
|
|
715
|
+
"--show-ends",
|
|
716
|
+
"--number",
|
|
717
|
+
"--squeeze-blank",
|
|
718
|
+
"--show-tabs",
|
|
719
|
+
"--show-nonprinting"
|
|
720
|
+
]
|
|
721
|
+
},
|
|
722
|
+
sort: {
|
|
723
|
+
cluster: true,
|
|
724
|
+
// Absent on purpose: `-o`/`--output` writes a file with no redirect for the
|
|
725
|
+
// redirect check to see, and `--compress-program` names a program that sort
|
|
726
|
+
// runs over its own temporary files — a permitted binary as an interpreter.
|
|
727
|
+
booleans: [
|
|
728
|
+
"-b",
|
|
729
|
+
"-c",
|
|
730
|
+
"-C",
|
|
731
|
+
"-d",
|
|
732
|
+
"-f",
|
|
733
|
+
"-g",
|
|
734
|
+
"-h",
|
|
735
|
+
"-i",
|
|
736
|
+
"-M",
|
|
737
|
+
"-n",
|
|
738
|
+
"-r",
|
|
739
|
+
"-R",
|
|
740
|
+
"-s",
|
|
741
|
+
"-u",
|
|
742
|
+
"-V",
|
|
743
|
+
"-z",
|
|
744
|
+
"--ignore-leading-blanks",
|
|
745
|
+
"--check",
|
|
746
|
+
"--dictionary-order",
|
|
747
|
+
"--ignore-case",
|
|
748
|
+
"--general-numeric-sort",
|
|
749
|
+
"--human-numeric-sort",
|
|
750
|
+
"--ignore-nonprinting",
|
|
751
|
+
"--month-sort",
|
|
752
|
+
"--numeric-sort",
|
|
753
|
+
"--random-sort",
|
|
754
|
+
"--reverse",
|
|
755
|
+
"--stable",
|
|
756
|
+
"--unique",
|
|
757
|
+
"--version-sort",
|
|
758
|
+
"--zero-terminated",
|
|
759
|
+
"--debug"
|
|
760
|
+
],
|
|
761
|
+
values: [
|
|
762
|
+
"-k",
|
|
763
|
+
"-t",
|
|
764
|
+
"-S",
|
|
765
|
+
"-T",
|
|
766
|
+
"--key",
|
|
767
|
+
"--field-separator",
|
|
768
|
+
"--buffer-size",
|
|
769
|
+
"--temporary-directory",
|
|
770
|
+
"--random-source",
|
|
771
|
+
"--batch-size",
|
|
772
|
+
"--parallel",
|
|
773
|
+
"--sort"
|
|
774
|
+
]
|
|
775
|
+
},
|
|
776
|
+
uniq: {
|
|
777
|
+
cluster: true,
|
|
778
|
+
booleans: [
|
|
779
|
+
"-c",
|
|
780
|
+
"-d",
|
|
781
|
+
"-D",
|
|
782
|
+
"-i",
|
|
783
|
+
"-u",
|
|
784
|
+
"-z",
|
|
785
|
+
"--count",
|
|
786
|
+
"--repeated",
|
|
787
|
+
"--all-repeated",
|
|
788
|
+
"--ignore-case",
|
|
789
|
+
"--unique",
|
|
790
|
+
"--zero-terminated"
|
|
791
|
+
],
|
|
792
|
+
values: ["-f", "-s", "-w", "--skip-fields", "--skip-chars", "--check-chars", "--group"]
|
|
793
|
+
},
|
|
794
|
+
// No flag on either runs a program or writes a file, and the planner's
|
|
795
|
+
// commonest use of `echo` is a section marker — `echo --- files changed ---`
|
|
796
|
+
// is three tokens that all begin with a dash and none of them is a flag.
|
|
797
|
+
echo: { freeform: true },
|
|
798
|
+
printf: { freeform: true },
|
|
799
|
+
date: {
|
|
800
|
+
cluster: true,
|
|
801
|
+
// `-s`/`--set` is absent: it sets the system clock.
|
|
802
|
+
booleans: [
|
|
803
|
+
"-u",
|
|
804
|
+
"-R",
|
|
805
|
+
"-I",
|
|
806
|
+
"--utc",
|
|
807
|
+
"--universal",
|
|
808
|
+
"--rfc-email",
|
|
809
|
+
"--rfc-3339",
|
|
810
|
+
"--iso-8601",
|
|
811
|
+
"--debug"
|
|
812
|
+
],
|
|
813
|
+
values: ["-d", "-f", "-r", "--date", "--file", "--reference"]
|
|
814
|
+
},
|
|
815
|
+
stat: {
|
|
816
|
+
cluster: true,
|
|
817
|
+
booleans: ["-f", "-L", "-t", "--file-system", "--terse", "--dereference"],
|
|
818
|
+
values: ["-c", "--format", "--printf", "--cached"]
|
|
819
|
+
},
|
|
820
|
+
basename: {
|
|
821
|
+
cluster: true,
|
|
822
|
+
booleans: ["-a", "-z", "--multiple", "--zero"],
|
|
823
|
+
values: ["-s", "--suffix"]
|
|
824
|
+
},
|
|
825
|
+
dirname: {
|
|
826
|
+
cluster: true,
|
|
827
|
+
booleans: ["-z", "--zero"]
|
|
828
|
+
},
|
|
829
|
+
realpath: {
|
|
830
|
+
cluster: true,
|
|
831
|
+
booleans: [
|
|
832
|
+
"-e",
|
|
833
|
+
"-m",
|
|
834
|
+
"-L",
|
|
835
|
+
"-P",
|
|
836
|
+
"-q",
|
|
837
|
+
"-s",
|
|
838
|
+
"-z",
|
|
839
|
+
"--canonicalize-existing",
|
|
840
|
+
"--canonicalize-missing",
|
|
841
|
+
"--logical",
|
|
842
|
+
"--physical",
|
|
843
|
+
"--quiet",
|
|
844
|
+
"--strip",
|
|
845
|
+
"--no-symlinks",
|
|
846
|
+
"--zero"
|
|
847
|
+
],
|
|
848
|
+
values: ["--relative-to", "--relative-base"]
|
|
849
|
+
},
|
|
850
|
+
pwd: { cluster: true, booleans: ["-L", "-P", "--logical", "--physical"] },
|
|
851
|
+
which: { cluster: true, booleans: ["-a", "-s", "--all"] },
|
|
852
|
+
type: { cluster: true, booleans: ["-a", "-f", "-p", "-P", "-t"] },
|
|
853
|
+
uname: {
|
|
854
|
+
cluster: true,
|
|
855
|
+
booleans: [
|
|
856
|
+
"-a",
|
|
857
|
+
"-i",
|
|
858
|
+
"-m",
|
|
859
|
+
"-n",
|
|
860
|
+
"-o",
|
|
861
|
+
"-p",
|
|
862
|
+
"-r",
|
|
863
|
+
"-s",
|
|
864
|
+
"-v",
|
|
865
|
+
"--all",
|
|
866
|
+
"--kernel-name",
|
|
867
|
+
"--nodename",
|
|
868
|
+
"--kernel-release",
|
|
869
|
+
"--kernel-version",
|
|
870
|
+
"--machine",
|
|
871
|
+
"--processor",
|
|
872
|
+
"--hardware-platform",
|
|
873
|
+
"--operating-system"
|
|
874
|
+
]
|
|
875
|
+
},
|
|
876
|
+
whoami: {},
|
|
877
|
+
cut: {
|
|
878
|
+
cluster: true,
|
|
879
|
+
// `--output-delimiter` names a separator string, not a file.
|
|
880
|
+
booleans: ["-n", "-s", "-z", "--only-delimited", "--complement", "--zero-terminated"],
|
|
881
|
+
values: [
|
|
882
|
+
"-b",
|
|
883
|
+
"-c",
|
|
884
|
+
"-d",
|
|
885
|
+
"-f",
|
|
886
|
+
"--bytes",
|
|
887
|
+
"--characters",
|
|
888
|
+
"--delimiter",
|
|
889
|
+
"--fields",
|
|
890
|
+
"--output-delimiter"
|
|
891
|
+
]
|
|
892
|
+
},
|
|
893
|
+
nl: {
|
|
894
|
+
cluster: true,
|
|
895
|
+
booleans: ["-p", "--no-renumber"],
|
|
896
|
+
values: [
|
|
897
|
+
"-b",
|
|
898
|
+
"-d",
|
|
899
|
+
"-f",
|
|
900
|
+
"-h",
|
|
901
|
+
"-i",
|
|
902
|
+
"-l",
|
|
903
|
+
"-n",
|
|
904
|
+
"-s",
|
|
905
|
+
"-v",
|
|
906
|
+
"-w",
|
|
907
|
+
"--body-numbering",
|
|
908
|
+
"--section-delimiter",
|
|
909
|
+
"--footer-numbering",
|
|
910
|
+
"--header-numbering",
|
|
911
|
+
"--join-blank-lines",
|
|
912
|
+
"--line-increment",
|
|
913
|
+
"--number-format",
|
|
914
|
+
"--number-separator",
|
|
915
|
+
"--starting-line-number",
|
|
916
|
+
"--number-width"
|
|
917
|
+
]
|
|
918
|
+
},
|
|
919
|
+
rg: {
|
|
920
|
+
cluster: true,
|
|
921
|
+
// `--pre` runs a named preprocessor over every file searched and
|
|
922
|
+
// `--hostname-bin` runs a named program to resolve the hostname; both are
|
|
923
|
+
// absent, and `--pre-glob` with them.
|
|
924
|
+
booleans: [
|
|
925
|
+
"-a",
|
|
926
|
+
"-c",
|
|
927
|
+
"-F",
|
|
928
|
+
"-h",
|
|
929
|
+
"-H",
|
|
930
|
+
"-i",
|
|
931
|
+
"-I",
|
|
932
|
+
"-l",
|
|
933
|
+
"-L",
|
|
934
|
+
"-n",
|
|
935
|
+
"-N",
|
|
936
|
+
"-o",
|
|
937
|
+
"-p",
|
|
938
|
+
"-P",
|
|
939
|
+
"-q",
|
|
940
|
+
"-s",
|
|
941
|
+
"-S",
|
|
942
|
+
"-u",
|
|
943
|
+
"-uu",
|
|
944
|
+
"-uuu",
|
|
945
|
+
"-U",
|
|
946
|
+
"-v",
|
|
947
|
+
"-V",
|
|
948
|
+
"-w",
|
|
949
|
+
"-x",
|
|
950
|
+
"-z",
|
|
951
|
+
"--text",
|
|
952
|
+
"--count",
|
|
953
|
+
"--count-matches",
|
|
954
|
+
"--fixed-strings",
|
|
955
|
+
"--files",
|
|
956
|
+
"--files-with-matches",
|
|
957
|
+
"--files-without-match",
|
|
958
|
+
"--follow",
|
|
959
|
+
"--hidden",
|
|
960
|
+
"--no-hidden",
|
|
961
|
+
"--ignore-case",
|
|
962
|
+
"--case-sensitive",
|
|
963
|
+
"--smart-case",
|
|
964
|
+
"--invert-match",
|
|
965
|
+
"--line-number",
|
|
966
|
+
"--no-line-number",
|
|
967
|
+
"--with-filename",
|
|
968
|
+
"--no-filename",
|
|
969
|
+
"--heading",
|
|
970
|
+
"--no-heading",
|
|
971
|
+
"--column",
|
|
972
|
+
"--no-column",
|
|
973
|
+
"--byte-offset",
|
|
974
|
+
"--vimgrep",
|
|
975
|
+
"--json",
|
|
976
|
+
"--stats",
|
|
977
|
+
"--trim",
|
|
978
|
+
"--pretty",
|
|
979
|
+
"--passthru",
|
|
980
|
+
"--word-regexp",
|
|
981
|
+
"--line-regexp",
|
|
982
|
+
"--multiline",
|
|
983
|
+
"--multiline-dotall",
|
|
984
|
+
"--no-multiline",
|
|
985
|
+
"--crlf",
|
|
986
|
+
"--null",
|
|
987
|
+
"--null-data",
|
|
988
|
+
"--binary",
|
|
989
|
+
"--search-zip",
|
|
990
|
+
"--no-ignore",
|
|
991
|
+
"--no-ignore-dot",
|
|
992
|
+
"--no-ignore-exclude",
|
|
993
|
+
"--no-ignore-files",
|
|
994
|
+
"--no-ignore-global",
|
|
995
|
+
"--no-ignore-parent",
|
|
996
|
+
"--no-ignore-vcs",
|
|
997
|
+
"--no-ignore-messages",
|
|
998
|
+
"--no-messages",
|
|
999
|
+
"--no-require-git",
|
|
1000
|
+
"--require-git",
|
|
1001
|
+
"--one-file-system",
|
|
1002
|
+
"--pcre2",
|
|
1003
|
+
"--no-pcre2",
|
|
1004
|
+
"--auto-hybrid-regex",
|
|
1005
|
+
"--line-buffered",
|
|
1006
|
+
"--block-buffered",
|
|
1007
|
+
"--unicode",
|
|
1008
|
+
"--no-unicode",
|
|
1009
|
+
"--unrestricted",
|
|
1010
|
+
"--type-list",
|
|
1011
|
+
"--debug",
|
|
1012
|
+
"--trace",
|
|
1013
|
+
"--include-zero",
|
|
1014
|
+
"--no-context-separator",
|
|
1015
|
+
"--stop-on-nonmatch",
|
|
1016
|
+
"--quiet",
|
|
1017
|
+
"--no-binary"
|
|
1018
|
+
],
|
|
1019
|
+
values: [
|
|
1020
|
+
"-A",
|
|
1021
|
+
"-B",
|
|
1022
|
+
"-C",
|
|
1023
|
+
"-d",
|
|
1024
|
+
"-e",
|
|
1025
|
+
"-E",
|
|
1026
|
+
"-f",
|
|
1027
|
+
"-g",
|
|
1028
|
+
"-j",
|
|
1029
|
+
"-m",
|
|
1030
|
+
"-M",
|
|
1031
|
+
"-r",
|
|
1032
|
+
"-t",
|
|
1033
|
+
"-T",
|
|
1034
|
+
"--after-context",
|
|
1035
|
+
"--before-context",
|
|
1036
|
+
"--context",
|
|
1037
|
+
"--context-separator",
|
|
1038
|
+
"--field-context-separator",
|
|
1039
|
+
"--field-match-separator",
|
|
1040
|
+
"--regexp",
|
|
1041
|
+
"--file",
|
|
1042
|
+
"--glob",
|
|
1043
|
+
"--iglob",
|
|
1044
|
+
"--glob-case-insensitive",
|
|
1045
|
+
"--type",
|
|
1046
|
+
"--type-not",
|
|
1047
|
+
"--type-add",
|
|
1048
|
+
"--type-clear",
|
|
1049
|
+
"--max-count",
|
|
1050
|
+
"--max-columns",
|
|
1051
|
+
"--max-depth",
|
|
1052
|
+
"--maxdepth",
|
|
1053
|
+
"--max-filesize",
|
|
1054
|
+
"--replace",
|
|
1055
|
+
"--threads",
|
|
1056
|
+
"--encoding",
|
|
1057
|
+
"--engine",
|
|
1058
|
+
"--sort",
|
|
1059
|
+
"--sortr",
|
|
1060
|
+
"--color",
|
|
1061
|
+
"--colors",
|
|
1062
|
+
"--path-separator",
|
|
1063
|
+
"--ignore-file",
|
|
1064
|
+
"--dfa-size-limit",
|
|
1065
|
+
"--regex-size-limit",
|
|
1066
|
+
"--binary-files",
|
|
1067
|
+
"--max-columns-preview"
|
|
1068
|
+
]
|
|
1069
|
+
},
|
|
1070
|
+
grep: {
|
|
1071
|
+
cluster: true,
|
|
1072
|
+
// `-o` here is `--only-matching`, which writes nothing. It is the same two
|
|
1073
|
+
// characters as `sort -o`, which writes a file — the reason these sets are
|
|
1074
|
+
// per-binary rather than one shared list.
|
|
1075
|
+
booleans: [
|
|
1076
|
+
"-a",
|
|
1077
|
+
"-b",
|
|
1078
|
+
"-c",
|
|
1079
|
+
"-E",
|
|
1080
|
+
"-F",
|
|
1081
|
+
"-G",
|
|
1082
|
+
"-H",
|
|
1083
|
+
"-h",
|
|
1084
|
+
"-i",
|
|
1085
|
+
"-I",
|
|
1086
|
+
"-l",
|
|
1087
|
+
"-L",
|
|
1088
|
+
"-n",
|
|
1089
|
+
"-o",
|
|
1090
|
+
"-P",
|
|
1091
|
+
"-q",
|
|
1092
|
+
"-r",
|
|
1093
|
+
"-R",
|
|
1094
|
+
"-s",
|
|
1095
|
+
"-T",
|
|
1096
|
+
"-U",
|
|
1097
|
+
"-v",
|
|
1098
|
+
"-w",
|
|
1099
|
+
"-x",
|
|
1100
|
+
"-y",
|
|
1101
|
+
"-z",
|
|
1102
|
+
"-Z",
|
|
1103
|
+
"--extended-regexp",
|
|
1104
|
+
"--fixed-strings",
|
|
1105
|
+
"--basic-regexp",
|
|
1106
|
+
"--perl-regexp",
|
|
1107
|
+
"--ignore-case",
|
|
1108
|
+
"--no-ignore-case",
|
|
1109
|
+
"--invert-match",
|
|
1110
|
+
"--word-regexp",
|
|
1111
|
+
"--line-regexp",
|
|
1112
|
+
"--count",
|
|
1113
|
+
"--files-with-matches",
|
|
1114
|
+
"--files-without-match",
|
|
1115
|
+
"--line-number",
|
|
1116
|
+
"--with-filename",
|
|
1117
|
+
"--no-filename",
|
|
1118
|
+
"--only-matching",
|
|
1119
|
+
"--quiet",
|
|
1120
|
+
"--silent",
|
|
1121
|
+
"--no-messages",
|
|
1122
|
+
"--text",
|
|
1123
|
+
"--binary",
|
|
1124
|
+
"--line-buffered",
|
|
1125
|
+
"--null",
|
|
1126
|
+
"--null-data",
|
|
1127
|
+
"--byte-offset",
|
|
1128
|
+
"--recursive",
|
|
1129
|
+
"--dereference-recursive",
|
|
1130
|
+
"--initial-tab",
|
|
1131
|
+
"--unix-byte-offsets",
|
|
1132
|
+
"--no-group-separator",
|
|
1133
|
+
"--color",
|
|
1134
|
+
"--colour"
|
|
1135
|
+
],
|
|
1136
|
+
values: [
|
|
1137
|
+
"-A",
|
|
1138
|
+
"-B",
|
|
1139
|
+
"-C",
|
|
1140
|
+
"-d",
|
|
1141
|
+
"-D",
|
|
1142
|
+
"-e",
|
|
1143
|
+
"-f",
|
|
1144
|
+
"-m",
|
|
1145
|
+
"--after-context",
|
|
1146
|
+
"--before-context",
|
|
1147
|
+
"--context",
|
|
1148
|
+
"--regexp",
|
|
1149
|
+
"--file",
|
|
1150
|
+
"--max-count",
|
|
1151
|
+
"--devices",
|
|
1152
|
+
"--directories",
|
|
1153
|
+
"--binary-files",
|
|
1154
|
+
"--include",
|
|
1155
|
+
"--exclude",
|
|
1156
|
+
"--exclude-dir",
|
|
1157
|
+
"--exclude-from",
|
|
1158
|
+
"--label",
|
|
1159
|
+
"--group-separator"
|
|
1160
|
+
]
|
|
1161
|
+
},
|
|
1162
|
+
find: {
|
|
1163
|
+
// No clustering: find spells its flags as single-dash words, so walking the
|
|
1164
|
+
// characters of `-name` would read it as five short flags. And its arguments
|
|
1165
|
+
// are an expression, so `--` does not end them.
|
|
1166
|
+
expressionArgs: true,
|
|
1167
|
+
// Absent on purpose: `-exec`, `-execdir`, `-ok` and `-okdir` run an inner
|
|
1168
|
+
// command (also caught by the named guard below, which says so more
|
|
1169
|
+
// precisely), `-delete` removes files, and `-fprint`, `-fprint0`, `-fprintf`
|
|
1170
|
+
// and `-fls` are find's write family — the same write as `> file` with no
|
|
1171
|
+
// redirect for the redirect check to see.
|
|
1172
|
+
booleans: [
|
|
1173
|
+
"-H",
|
|
1174
|
+
"-L",
|
|
1175
|
+
"-P",
|
|
1176
|
+
"-a",
|
|
1177
|
+
"-and",
|
|
1178
|
+
"-o",
|
|
1179
|
+
"-or",
|
|
1180
|
+
"-not",
|
|
1181
|
+
"-d",
|
|
1182
|
+
"-depth",
|
|
1183
|
+
"-daystart",
|
|
1184
|
+
"-empty",
|
|
1185
|
+
"-executable",
|
|
1186
|
+
"-false",
|
|
1187
|
+
"-follow",
|
|
1188
|
+
"-help",
|
|
1189
|
+
"--help",
|
|
1190
|
+
"-ignore_readdir_race",
|
|
1191
|
+
"-noignore_readdir_race",
|
|
1192
|
+
"-ls",
|
|
1193
|
+
"-mount",
|
|
1194
|
+
"-nogroup",
|
|
1195
|
+
"-noleaf",
|
|
1196
|
+
"-nouser",
|
|
1197
|
+
"-print",
|
|
1198
|
+
"-print0",
|
|
1199
|
+
"-prune",
|
|
1200
|
+
"-quit",
|
|
1201
|
+
"-readable",
|
|
1202
|
+
"-true",
|
|
1203
|
+
"-version",
|
|
1204
|
+
"-writable",
|
|
1205
|
+
"-xdev"
|
|
1206
|
+
],
|
|
1207
|
+
values: [
|
|
1208
|
+
"-maxdepth",
|
|
1209
|
+
"-mindepth",
|
|
1210
|
+
"-name",
|
|
1211
|
+
"-iname",
|
|
1212
|
+
"-path",
|
|
1213
|
+
"-ipath",
|
|
1214
|
+
"-wholename",
|
|
1215
|
+
"-iwholename",
|
|
1216
|
+
"-regex",
|
|
1217
|
+
"-iregex",
|
|
1218
|
+
"-lname",
|
|
1219
|
+
"-ilname",
|
|
1220
|
+
"-type",
|
|
1221
|
+
"-xtype",
|
|
1222
|
+
"-size",
|
|
1223
|
+
"-newer",
|
|
1224
|
+
"-anewer",
|
|
1225
|
+
"-cnewer",
|
|
1226
|
+
"-mtime",
|
|
1227
|
+
"-mmin",
|
|
1228
|
+
"-ctime",
|
|
1229
|
+
"-cmin",
|
|
1230
|
+
"-atime",
|
|
1231
|
+
"-amin",
|
|
1232
|
+
"-used",
|
|
1233
|
+
"-uid",
|
|
1234
|
+
"-gid",
|
|
1235
|
+
"-user",
|
|
1236
|
+
"-group",
|
|
1237
|
+
"-perm",
|
|
1238
|
+
"-links",
|
|
1239
|
+
"-inum",
|
|
1240
|
+
"-samefile",
|
|
1241
|
+
"-fstype",
|
|
1242
|
+
"-regextype",
|
|
1243
|
+
"-files0-from",
|
|
1244
|
+
"-printf",
|
|
1245
|
+
"-context",
|
|
1246
|
+
"-D"
|
|
1247
|
+
],
|
|
1248
|
+
// `find -O2` glues the optimisation level on, and `-newermt`/`-newerat` and
|
|
1249
|
+
// friends spell the two comparison fields into the flag name.
|
|
1250
|
+
patterns: [/^-O\d$/, /^-newer[acmBt][acmBt]$/]
|
|
1251
|
+
},
|
|
1252
|
+
jq: {
|
|
1253
|
+
cluster: true,
|
|
1254
|
+
booleans: [
|
|
1255
|
+
"-a",
|
|
1256
|
+
"-c",
|
|
1257
|
+
"-C",
|
|
1258
|
+
"-e",
|
|
1259
|
+
"-h",
|
|
1260
|
+
"-j",
|
|
1261
|
+
"-M",
|
|
1262
|
+
"-n",
|
|
1263
|
+
"-r",
|
|
1264
|
+
"-R",
|
|
1265
|
+
"-s",
|
|
1266
|
+
"-S",
|
|
1267
|
+
"--ascii-output",
|
|
1268
|
+
"--compact-output",
|
|
1269
|
+
"--color-output",
|
|
1270
|
+
"--monochrome-output",
|
|
1271
|
+
"--exit-status",
|
|
1272
|
+
"--join-output",
|
|
1273
|
+
"--null-input",
|
|
1274
|
+
"--raw-input",
|
|
1275
|
+
"--raw-output",
|
|
1276
|
+
"--raw-output0",
|
|
1277
|
+
"--slurp",
|
|
1278
|
+
"--sort-keys",
|
|
1279
|
+
"--seq",
|
|
1280
|
+
"--stream",
|
|
1281
|
+
"--stream-errors",
|
|
1282
|
+
"--tab",
|
|
1283
|
+
"--unbuffered",
|
|
1284
|
+
"--args",
|
|
1285
|
+
"--jsonargs"
|
|
1286
|
+
],
|
|
1287
|
+
values: [
|
|
1288
|
+
"-f",
|
|
1289
|
+
"-L",
|
|
1290
|
+
"--arg",
|
|
1291
|
+
"--argjson",
|
|
1292
|
+
"--slurpfile",
|
|
1293
|
+
"--rawfile",
|
|
1294
|
+
"--indent",
|
|
1295
|
+
"--from-file"
|
|
1296
|
+
]
|
|
1297
|
+
},
|
|
1298
|
+
yq: {
|
|
1299
|
+
cluster: true,
|
|
1300
|
+
// `-i`/`--inplace`/`--in-place` rewrite the file, and `-s`/`--split-exp`
|
|
1301
|
+
// writes one file per document. All absent — and all bare or near-bare
|
|
1302
|
+
// booleans, which is why exempting booleans was not an option.
|
|
1303
|
+
booleans: [
|
|
1304
|
+
"-C",
|
|
1305
|
+
"-e",
|
|
1306
|
+
"-h",
|
|
1307
|
+
"-j",
|
|
1308
|
+
"-M",
|
|
1309
|
+
"-n",
|
|
1310
|
+
"-N",
|
|
1311
|
+
"-P",
|
|
1312
|
+
"-r",
|
|
1313
|
+
"-S",
|
|
1314
|
+
"-v",
|
|
1315
|
+
"-y",
|
|
1316
|
+
"-Y",
|
|
1317
|
+
"--colors",
|
|
1318
|
+
"--no-colors",
|
|
1319
|
+
"--exit-status",
|
|
1320
|
+
"--no-doc",
|
|
1321
|
+
"--null-input",
|
|
1322
|
+
"--prettyPrint",
|
|
1323
|
+
"--raw-output",
|
|
1324
|
+
"--sort-keys",
|
|
1325
|
+
"--tojson",
|
|
1326
|
+
"--yaml-output",
|
|
1327
|
+
"--yaml-roundtrip",
|
|
1328
|
+
"--unwrapScalar",
|
|
1329
|
+
"--header-preprocess",
|
|
1330
|
+
"--no-exit-status",
|
|
1331
|
+
"--verbose",
|
|
1332
|
+
"--xml-strict-mode"
|
|
1333
|
+
],
|
|
1334
|
+
values: [
|
|
1335
|
+
"-I",
|
|
1336
|
+
"-o",
|
|
1337
|
+
"-p",
|
|
1338
|
+
"-L",
|
|
1339
|
+
"--indent",
|
|
1340
|
+
"--output-format",
|
|
1341
|
+
"--input-format",
|
|
1342
|
+
"--expression",
|
|
1343
|
+
"--width",
|
|
1344
|
+
"--xml-attribute-prefix",
|
|
1345
|
+
"--xml-content-name"
|
|
1346
|
+
]
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
121
1349
|
var GIT_READONLY_SUBCOMMANDS = [
|
|
122
1350
|
"log",
|
|
123
1351
|
"status",
|
|
124
1352
|
"diff",
|
|
125
1353
|
"show",
|
|
126
1354
|
"ls-files",
|
|
127
|
-
"ls-remote",
|
|
128
1355
|
"ls-tree",
|
|
129
1356
|
"branch",
|
|
130
1357
|
"tag",
|
|
@@ -239,6 +1466,26 @@ var REFUSED_SUBCOMMANDS = {
|
|
|
239
1466
|
az: ["login", "logout"],
|
|
240
1467
|
terraform: ["apply", "destroy"]
|
|
241
1468
|
};
|
|
1469
|
+
var SHELL_KEYWORDS = [
|
|
1470
|
+
"{",
|
|
1471
|
+
"}",
|
|
1472
|
+
"if",
|
|
1473
|
+
"then",
|
|
1474
|
+
"elif",
|
|
1475
|
+
"else",
|
|
1476
|
+
"fi",
|
|
1477
|
+
"for",
|
|
1478
|
+
"while",
|
|
1479
|
+
"until",
|
|
1480
|
+
"do",
|
|
1481
|
+
"done",
|
|
1482
|
+
"case",
|
|
1483
|
+
"esac",
|
|
1484
|
+
"select",
|
|
1485
|
+
"function",
|
|
1486
|
+
"time",
|
|
1487
|
+
"export"
|
|
1488
|
+
];
|
|
242
1489
|
var INTERPRETERS = [
|
|
243
1490
|
"sh",
|
|
244
1491
|
"bash",
|
|
@@ -682,16 +1929,79 @@ function pathLikeArgs(command, opts = {}) {
|
|
|
682
1929
|
return !a.startsWith("-") && looksLikePath(a) ? [a] : [];
|
|
683
1930
|
}));
|
|
684
1931
|
}
|
|
1932
|
+
function matchFlag(spec, booleans, values, token) {
|
|
1933
|
+
if (token.startsWith("--")) {
|
|
1934
|
+
const eq = token.indexOf("=");
|
|
1935
|
+
const name = eq > 0 ? token.slice(0, eq) : token;
|
|
1936
|
+
if (booleans.has(name)) return "boolean";
|
|
1937
|
+
if (values.has(name)) return eq > 0 ? "boolean" : "value";
|
|
1938
|
+
return void 0;
|
|
1939
|
+
}
|
|
1940
|
+
if (booleans.has(token)) return "boolean";
|
|
1941
|
+
if (values.has(token)) return "value";
|
|
1942
|
+
if (spec.patterns?.some((p) => p.test(token))) return "boolean";
|
|
1943
|
+
if (!spec.cluster) return void 0;
|
|
1944
|
+
for (let i = 1; i < token.length; i++) {
|
|
1945
|
+
const short = `-${token[i]}`;
|
|
1946
|
+
if (booleans.has(short)) continue;
|
|
1947
|
+
if (values.has(short)) return i === token.length - 1 ? "value" : "boolean";
|
|
1948
|
+
return void 0;
|
|
1949
|
+
}
|
|
1950
|
+
return "boolean";
|
|
1951
|
+
}
|
|
1952
|
+
function takesNextToken(next) {
|
|
1953
|
+
if (next === void 0) return false;
|
|
1954
|
+
return !/^-./.test(next) || /^-\d/.test(next);
|
|
1955
|
+
}
|
|
1956
|
+
function flagLabel(token) {
|
|
1957
|
+
const eq = token.startsWith("--") ? token.indexOf("=") : -1;
|
|
1958
|
+
return eq > 0 ? token.slice(0, eq) : token;
|
|
1959
|
+
}
|
|
1960
|
+
function scanFlags(seg) {
|
|
1961
|
+
const spec = FLAG_POLICY[seg.binary];
|
|
1962
|
+
if (!spec || spec.freeform) return void 0;
|
|
1963
|
+
const booleans = /* @__PURE__ */ new Set([...spec.booleans ?? [], ...UNIVERSAL_FLAGS]);
|
|
1964
|
+
const values = new Set(spec.values ?? []);
|
|
1965
|
+
const operands = [];
|
|
1966
|
+
let i = 0;
|
|
1967
|
+
while (i < seg.args.length) {
|
|
1968
|
+
const token = seg.args[i];
|
|
1969
|
+
if (token === "--" && !spec.expressionArgs) {
|
|
1970
|
+
operands.push(...seg.args.slice(i + 1));
|
|
1971
|
+
break;
|
|
1972
|
+
}
|
|
1973
|
+
if (!/^-./.test(token)) {
|
|
1974
|
+
operands.push(token);
|
|
1975
|
+
i++;
|
|
1976
|
+
continue;
|
|
1977
|
+
}
|
|
1978
|
+
const shape = matchFlag(spec, booleans, values, token);
|
|
1979
|
+
if (shape === void 0) return { unknown: flagLabel(token), operands };
|
|
1980
|
+
i += shape === "value" && takesNextToken(seg.args[i + 1]) ? 2 : 1;
|
|
1981
|
+
}
|
|
1982
|
+
return { operands };
|
|
1983
|
+
}
|
|
1984
|
+
function subcommandOf(seg) {
|
|
1985
|
+
const scan = scanFlags(seg);
|
|
1986
|
+
if (scan) return scan.operands[0];
|
|
1987
|
+
return seg.args.find((a) => !a.startsWith("-"));
|
|
1988
|
+
}
|
|
1989
|
+
var SCOPE_LEAD_ARGS = 2;
|
|
685
1990
|
function scopeFor(seg) {
|
|
686
1991
|
if (!MULTIPLEXERS.includes(seg.binary)) return seg.binary;
|
|
687
|
-
const
|
|
688
|
-
|
|
1992
|
+
const lead = [];
|
|
1993
|
+
for (const arg of seg.args) {
|
|
1994
|
+
if (arg.startsWith("-")) break;
|
|
1995
|
+
lead.push(arg);
|
|
1996
|
+
if (lead.length === SCOPE_LEAD_ARGS) break;
|
|
1997
|
+
}
|
|
1998
|
+
return [seg.binary, ...lead].join(" ");
|
|
689
1999
|
}
|
|
690
2000
|
function isAuto(seg) {
|
|
691
2001
|
if (!AUTO_COMMANDS.includes(seg.binary)) return false;
|
|
692
2002
|
if (seg.expandable) return false;
|
|
693
2003
|
if (seg.binary !== "git") return true;
|
|
694
|
-
const sub = seg
|
|
2004
|
+
const sub = subcommandOf(seg);
|
|
695
2005
|
return sub !== void 0 && GIT_READONLY_SUBCOMMANDS.includes(sub);
|
|
696
2006
|
}
|
|
697
2007
|
function refusalFor(seg) {
|
|
@@ -701,29 +2011,31 @@ function refusalFor(seg) {
|
|
|
701
2011
|
if (seg.binary === "eval" || seg.binary === "exec") {
|
|
702
2012
|
return `"${seg.binary}" runs a string as a command, which this classifier cannot inspect. Use the read-only research tools, or describe it as a task.`;
|
|
703
2013
|
}
|
|
2014
|
+
if (seg.binary === "source" || seg.binary === ".") {
|
|
2015
|
+
return `"${seg.binary}" runs a file's contents as commands, which this classifier cannot inspect. Use the read-only research tools, or describe it as a task.`;
|
|
2016
|
+
}
|
|
2017
|
+
if (SHELL_KEYWORDS.includes(seg.binary)) {
|
|
2018
|
+
return `"${seg.binary}" is a shell keyword or compound-command opener. This classifier only inspects the command that runs first, and a keyword hides the real one from it. Run the inner command directly, or describe it as a task.`;
|
|
2019
|
+
}
|
|
704
2020
|
const subs = REFUSED_SUBCOMMANDS[seg.binary];
|
|
705
2021
|
if (subs) {
|
|
706
|
-
const sub = seg
|
|
2022
|
+
const sub = subcommandOf(seg);
|
|
707
2023
|
if (sub && subs.includes(sub)) {
|
|
708
2024
|
return `"${seg.binary} ${sub}" changes state or reaches outward. You are a read-only planner \u2014 put it in the plan and let the runner do it.`;
|
|
709
2025
|
}
|
|
710
2026
|
}
|
|
711
2027
|
if (seg.binary === "git") {
|
|
712
|
-
const sub = seg
|
|
2028
|
+
const sub = subcommandOf(seg);
|
|
713
2029
|
if (sub === "branch" || sub === "tag") {
|
|
714
2030
|
const flag = seg.args.find((a) => ["-D", "-d", "-m", "-M", "--delete", "--move"].includes(a));
|
|
715
2031
|
if (flag) {
|
|
716
2032
|
return `"git ${sub} ${flag}" mutates refs. You are a read-only planner \u2014 describe the change as a task instead.`;
|
|
717
2033
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
if (sub === "grep") {
|
|
724
|
-
const pagerFlag = seg.args.find((a) => a === "-O" || a === "--open-files-in-pager" || a.startsWith("--open-files-in-pager="));
|
|
725
|
-
if (pagerFlag) {
|
|
726
|
-
return `"git grep ${pagerFlag}" opens the results in an arbitrary named program. Use the read-only research tools, or describe the change as a task.`;
|
|
2034
|
+
const listing = seg.args.includes("-l") || seg.args.includes("--list");
|
|
2035
|
+
const scan2 = scanFlags(seg);
|
|
2036
|
+
const target = scan2?.operands[1];
|
|
2037
|
+
if (!listing && target) {
|
|
2038
|
+
return `"git ${sub} ${target}" creates or moves a ref. You are a read-only planner \u2014 describe the change as a task instead.`;
|
|
727
2039
|
}
|
|
728
2040
|
}
|
|
729
2041
|
}
|
|
@@ -751,6 +2063,13 @@ function refusalFor(seg) {
|
|
|
751
2063
|
if (seg.assignments.length > 0) {
|
|
752
2064
|
return `The environment assignment "${seg.assignments[0]}" decides what "${seg.binary}" actually does, and this classifier cannot judge the value. Re-run the command without the assignment.`;
|
|
753
2065
|
}
|
|
2066
|
+
const scan = scanFlags(seg);
|
|
2067
|
+
if (scan?.unknown) {
|
|
2068
|
+
return `"${scan.unknown}" is not a flag this classifier knows to be read-only on "${seg.binary}", and flags on otherwise read-only binaries can run a helper program or write a file. Re-run with read-only flags only, or describe the work as a task.`;
|
|
2069
|
+
}
|
|
2070
|
+
if (seg.binary === "uniq" && scan && scan.operands.length > 1) {
|
|
2071
|
+
return `"uniq" writes to its second file argument ("${scan.operands[1]}"). You are a read-only planner \u2014 pipe the output instead of naming an output file, or describe the write as a task.`;
|
|
2072
|
+
}
|
|
754
2073
|
return void 0;
|
|
755
2074
|
}
|
|
756
2075
|
function withWrapperNote(reason, wrappers) {
|
|
@@ -1137,6 +2456,139 @@ It is outside the auto-allowed read-only set (${scope}). Continue with the read-
|
|
|
1137
2456
|
}
|
|
1138
2457
|
};
|
|
1139
2458
|
|
|
2459
|
+
// src/utils/redactSecrets.ts
|
|
2460
|
+
var REDACTION_MARKER = "REDACTED";
|
|
2461
|
+
function marker(kind, length) {
|
|
2462
|
+
return `[${REDACTION_MARKER} ${kind}: ${length} chars]`;
|
|
2463
|
+
}
|
|
2464
|
+
var LINE_PREFIX = String.raw`(?:[ \t]*\d+\||[^\s:]*:\d+:|)[ \t]*`;
|
|
2465
|
+
var LINE_PREFIX_ANCHORED = new RegExp("^" + LINE_PREFIX);
|
|
2466
|
+
var CREDENTIAL_WORD = String.raw`(?:api[_-]?keys?|secrets?|passwords?|passwd|passphrase|tokens?|access[_-]?keys?|private[_-]?keys?|client[_-]?secrets?|credentials?|auth)(?![a-z])`;
|
|
2467
|
+
var NAMED_CREDENTIAL = new RegExp(
|
|
2468
|
+
String.raw`((?:^|[^A-Za-z0-9_])["']?[A-Za-z0-9_.\-]*` + CREDENTIAL_WORD + String.raw`[A-Za-z0-9_.\-]*["']?[ \t]*(?:=>|[:=])[ \t]*)(["'\x60]?)([A-Za-z0-9_\-./+=:~!@#%^&*]{8,})\2`,
|
|
2469
|
+
"gi"
|
|
2470
|
+
);
|
|
2471
|
+
var DOTENV_CREDENTIAL = new RegExp(
|
|
2472
|
+
String.raw`^(` + LINE_PREFIX + String.raw`(?:export[ \t]+)?[A-Z0-9_]*` + CREDENTIAL_WORD.toUpperCase() + String.raw`[A-Z0-9_]*[ \t]*=[ \t]*)(["'\x60]?)([^\s"'\x60]{3,})\2[ \t]*$`,
|
|
2473
|
+
"gm"
|
|
2474
|
+
);
|
|
2475
|
+
var PROVIDER_KEY_PATTERNS = [
|
|
2476
|
+
// OpenAI / Anthropic / OpenRouter and the many `sk-` derivatives
|
|
2477
|
+
/(?<![A-Za-z0-9])sk-(?:ant-|proj-|or-v1-|live-|test-)?[A-Za-z0-9_-]{20,}/g,
|
|
2478
|
+
// GitHub personal-access, OAuth, app and refresh tokens
|
|
2479
|
+
/(?<![A-Za-z0-9])(?:gh[pousr]|github_pat)_[A-Za-z0-9_]{22,}/g,
|
|
2480
|
+
// Slack bot / user / app tokens
|
|
2481
|
+
/(?<![A-Za-z0-9])xox[abprse]-[A-Za-z0-9-]{10,}/g,
|
|
2482
|
+
// Stripe secret and restricted keys
|
|
2483
|
+
/(?<![A-Za-z0-9])[sr]k_(?:live|test)_[A-Za-z0-9]{16,}/g,
|
|
2484
|
+
// Google API keys and OAuth access tokens
|
|
2485
|
+
/(?<![A-Za-z0-9])AIza[A-Za-z0-9_-]{30,}/g,
|
|
2486
|
+
/(?<![A-Za-z0-9])ya29\.[A-Za-z0-9_-]{20,}/g,
|
|
2487
|
+
// npm, Hugging Face, Groq, GitLab, DigitalOcean, Shopify
|
|
2488
|
+
/(?<![A-Za-z0-9])(?:npm|hf|gsk|glpat|dop_v1|shpat|shpss)[_-][A-Za-z0-9]{20,}/g,
|
|
2489
|
+
/(?<![A-Za-z0-9])xai-[A-Za-z0-9]{20,}/g,
|
|
2490
|
+
// Three-segment JWTs, whose payload segment is base64 for `{"`
|
|
2491
|
+
/(?<![A-Za-z0-9])eyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g
|
|
2492
|
+
];
|
|
2493
|
+
var CLOUD_ACCESS_KEY_ID = /(?<![A-Za-z0-9])(?:A3T[A-Z0-9]{2}|AKIA|ASIA|ABIA|ACCA|AGPA|AIDA|AIPA|ANPA|ANVA|AROA)[A-Z0-9]{16}(?![A-Za-z0-9])/g;
|
|
2494
|
+
var PEM_BEGIN = /-----BEGIN ((?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?)-----/;
|
|
2495
|
+
var PEM_BODY_LINE = /^[A-Za-z0-9+/=:,.\- \t]*$/;
|
|
2496
|
+
function redactPrivateKeyBlocks(text) {
|
|
2497
|
+
if (!PEM_BEGIN.test(text)) return text;
|
|
2498
|
+
const out = [];
|
|
2499
|
+
let label = null;
|
|
2500
|
+
let bodyChars = 0;
|
|
2501
|
+
const closeBlock = () => {
|
|
2502
|
+
if (label === null) return;
|
|
2503
|
+
out.push(marker("private key", bodyChars));
|
|
2504
|
+
label = null;
|
|
2505
|
+
bodyChars = 0;
|
|
2506
|
+
};
|
|
2507
|
+
for (const line of text.split("\n")) {
|
|
2508
|
+
if (label === null) {
|
|
2509
|
+
out.push(line);
|
|
2510
|
+
const begun = PEM_BEGIN.exec(line);
|
|
2511
|
+
if (begun) label = begun[1];
|
|
2512
|
+
continue;
|
|
2513
|
+
}
|
|
2514
|
+
const content = line.replace(LINE_PREFIX_ANCHORED, "");
|
|
2515
|
+
if (content.includes(`-----END ${label}-----`)) {
|
|
2516
|
+
closeBlock();
|
|
2517
|
+
out.push(line);
|
|
2518
|
+
} else if (PEM_BODY_LINE.test(content)) {
|
|
2519
|
+
bodyChars += content.trim().length;
|
|
2520
|
+
} else {
|
|
2521
|
+
closeBlock();
|
|
2522
|
+
out.push(line);
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
closeBlock();
|
|
2526
|
+
return out.join("\n");
|
|
2527
|
+
}
|
|
2528
|
+
var NUMERIC = /^[-+]?\d+(?:\.\d+)?$/;
|
|
2529
|
+
var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
2530
|
+
var MEMBER_EXPRESSION = /^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)+$/;
|
|
2531
|
+
var SLUG = /^[a-z]+(?:[-_][a-z]+)+$/;
|
|
2532
|
+
var PATHISH = /^(?:\.{0,2}\/|~\/)/;
|
|
2533
|
+
var URLISH = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
2534
|
+
var CODE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
2535
|
+
"true",
|
|
2536
|
+
"false",
|
|
2537
|
+
"null",
|
|
2538
|
+
"none",
|
|
2539
|
+
"nil",
|
|
2540
|
+
"undefined",
|
|
2541
|
+
"void",
|
|
2542
|
+
"string",
|
|
2543
|
+
"number",
|
|
2544
|
+
"boolean",
|
|
2545
|
+
"object",
|
|
2546
|
+
"any",
|
|
2547
|
+
"unknown",
|
|
2548
|
+
"bool",
|
|
2549
|
+
"str",
|
|
2550
|
+
"int"
|
|
2551
|
+
]);
|
|
2552
|
+
var UNAMBIGUOUS_CREDENTIAL_NAME = /(?:password|passwd|passphrase|secret|credentials?|private[_-]?key)/i;
|
|
2553
|
+
function looksLikeKeyMaterial(value) {
|
|
2554
|
+
const hasDigit = /\d/.test(value);
|
|
2555
|
+
const hasAlpha = /[A-Za-z]/.test(value);
|
|
2556
|
+
const mixedCase = /[a-z]/.test(value) && /[A-Z]/.test(value);
|
|
2557
|
+
return hasDigit && hasAlpha || mixedCase || value.length >= 24;
|
|
2558
|
+
}
|
|
2559
|
+
function looksLikeCode(value) {
|
|
2560
|
+
return NUMERIC.test(value) || CODE_KEYWORDS.has(value.toLowerCase()) || MEMBER_EXPRESSION.test(value) || PATHISH.test(value) || URLISH.test(value);
|
|
2561
|
+
}
|
|
2562
|
+
function trimTrailingPunctuation(value) {
|
|
2563
|
+
const match = /[.:!]+$/.exec(value);
|
|
2564
|
+
if (!match || match.index === 0) return { value, tail: "" };
|
|
2565
|
+
return { value: value.slice(0, match.index), tail: match[0] };
|
|
2566
|
+
}
|
|
2567
|
+
function redactSecrets(text) {
|
|
2568
|
+
if (!text) return text;
|
|
2569
|
+
let out = redactPrivateKeyBlocks(text);
|
|
2570
|
+
out = out.replace(DOTENV_CREDENTIAL, (full, head, quote, value) => {
|
|
2571
|
+
if (looksLikeCode(value) || value.startsWith("$")) return full;
|
|
2572
|
+
return `${head}${quote}${marker("credential", value.length)}${quote}`;
|
|
2573
|
+
});
|
|
2574
|
+
out = out.replace(NAMED_CREDENTIAL, (full, head, quote, raw) => {
|
|
2575
|
+
const { value, tail } = trimTrailingPunctuation(raw);
|
|
2576
|
+
if (value.length < 8) return full;
|
|
2577
|
+
if (looksLikeCode(value) || value.startsWith("$")) return full;
|
|
2578
|
+
if (UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && /\d/.test(value)) {
|
|
2579
|
+
return `${head}${quote}${marker("credential", value.length)}${quote}${tail}`;
|
|
2580
|
+
}
|
|
2581
|
+
if (IDENTIFIER.test(value) || SLUG.test(value)) return full;
|
|
2582
|
+
if (!UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && !looksLikeKeyMaterial(value)) return full;
|
|
2583
|
+
return `${head}${quote}${marker("credential", value.length)}${quote}${tail}`;
|
|
2584
|
+
});
|
|
2585
|
+
for (const pattern of PROVIDER_KEY_PATTERNS) {
|
|
2586
|
+
out = out.replace(pattern, (hit) => marker("api key", hit.length));
|
|
2587
|
+
}
|
|
2588
|
+
out = out.replace(CLOUD_ACCESS_KEY_ID, (hit) => marker("access key id", hit.length));
|
|
2589
|
+
return out;
|
|
2590
|
+
}
|
|
2591
|
+
|
|
1140
2592
|
// src/services/executeTool.ts
|
|
1141
2593
|
var GREP_OUTPUT_MODES = ["content", "files", "count"];
|
|
1142
2594
|
function num(value) {
|
|
@@ -1163,6 +2615,10 @@ function grepOptions(args) {
|
|
|
1163
2615
|
}
|
|
1164
2616
|
var STOPPED_TOOL_RESULT = "Stopped by the user \u2014 this call was not executed, and no further tool calls will run this turn.";
|
|
1165
2617
|
async function executeTool(name, args, fs10, fetcher, signal) {
|
|
2618
|
+
const outcome = await runTool(name, args, fs10, fetcher, signal);
|
|
2619
|
+
return { ...outcome, output: redactSecrets(outcome.output) };
|
|
2620
|
+
}
|
|
2621
|
+
async function runTool(name, args, fs10, fetcher, signal) {
|
|
1166
2622
|
if (signal?.aborted) return { success: false, output: STOPPED_TOOL_RESULT, truncated: false };
|
|
1167
2623
|
switch (name) {
|
|
1168
2624
|
case "read_file": {
|
|
@@ -2317,6 +3773,177 @@ function modesFor(scope, modes) {
|
|
|
2317
3773
|
return scoped;
|
|
2318
3774
|
}
|
|
2319
3775
|
|
|
3776
|
+
// src/services/TaskQuery.ts
|
|
3777
|
+
var TASK_QUERY_FIELDS = [
|
|
3778
|
+
"description",
|
|
3779
|
+
"prompt",
|
|
3780
|
+
"userSteps",
|
|
3781
|
+
"verdict",
|
|
3782
|
+
"outputSummary",
|
|
3783
|
+
"userStoriesCovered"
|
|
3784
|
+
];
|
|
3785
|
+
function textHasTaskQuery(text) {
|
|
3786
|
+
return text.includes(`"${TASK_QUERY_ENVELOPE_KEY}"`);
|
|
3787
|
+
}
|
|
3788
|
+
function parseTaskQueryJson(text) {
|
|
3789
|
+
const { matches, fallback } = extractObjectsWithKey(text, TASK_QUERY_ENVELOPE_KEY);
|
|
3790
|
+
const candidates = matches.length > 0 ? [...matches].reverse() : [fallback.json];
|
|
3791
|
+
let lastError = null;
|
|
3792
|
+
for (const json of candidates) {
|
|
3793
|
+
try {
|
|
3794
|
+
return parseTaskQueryObject(json, text);
|
|
3795
|
+
} catch (err) {
|
|
3796
|
+
if (!(err instanceof PlanParseError)) throw err;
|
|
3797
|
+
if (!lastError) lastError = err;
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
throw lastError ?? new PlanParseError("No JSON object found in task-query reply", text);
|
|
3801
|
+
}
|
|
3802
|
+
function parseTaskQueryObject(json, text) {
|
|
3803
|
+
let parsed;
|
|
3804
|
+
try {
|
|
3805
|
+
parsed = JSON.parse(escapeControlCharsInStrings(stripTrailingCommas(json)));
|
|
3806
|
+
} catch (err) {
|
|
3807
|
+
throw new PlanParseError(`Task-query JSON is invalid: ${err instanceof Error ? err.message : String(err)}`, text);
|
|
3808
|
+
}
|
|
3809
|
+
const raw = parsed.taskQuery;
|
|
3810
|
+
const body = Array.isArray(raw) ? { tasks: raw } : raw;
|
|
3811
|
+
if (typeof body !== "object" || body === null) {
|
|
3812
|
+
throw new PlanParseError('"taskQuery" must be an object (or an array of task references)', text);
|
|
3813
|
+
}
|
|
3814
|
+
const { tasks: rawTasks, fields: rawFields, catalog: rawCatalog } = body;
|
|
3815
|
+
const tasks = [];
|
|
3816
|
+
if (rawTasks !== void 0) {
|
|
3817
|
+
if (!Array.isArray(rawTasks)) throw new PlanParseError('"tasks" in a taskQuery must be an array of task references', text);
|
|
3818
|
+
for (const ref of rawTasks) {
|
|
3819
|
+
if (typeof ref !== "string" && typeof ref !== "number") {
|
|
3820
|
+
throw new PlanParseError(`Invalid task reference "${String(ref)}" in taskQuery`, text);
|
|
3821
|
+
}
|
|
3822
|
+
tasks.push(String(ref).trim());
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
let fields;
|
|
3826
|
+
if (rawFields !== void 0) {
|
|
3827
|
+
if (!Array.isArray(rawFields)) throw new PlanParseError('"fields" in a taskQuery must be an array of field names', text);
|
|
3828
|
+
fields = [];
|
|
3829
|
+
for (const f of rawFields) {
|
|
3830
|
+
if (typeof f !== "string" || !TASK_QUERY_FIELDS.includes(f)) {
|
|
3831
|
+
throw new PlanParseError(`Unknown taskQuery field "${String(f)}" \u2014 choose from ${TASK_QUERY_FIELDS.join(", ")}`, text);
|
|
3832
|
+
}
|
|
3833
|
+
fields.push(f);
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
const catalog = rawCatalog === true;
|
|
3837
|
+
if (tasks.length === 0 && !catalog) {
|
|
3838
|
+
throw new PlanParseError('A taskQuery must name at least one task or set "catalog": true', text);
|
|
3839
|
+
}
|
|
3840
|
+
return { tasks, fields, catalog };
|
|
3841
|
+
}
|
|
3842
|
+
function taskQuerySignature(query) {
|
|
3843
|
+
return JSON.stringify([query.tasks, query.fields ?? null, query.catalog]);
|
|
3844
|
+
}
|
|
3845
|
+
var TASK_QUERY_ANSWER_OR_OPS = "You have now read everything you asked for. Do not send another taskQuery this turn: answer the user in prose, or emit the taskOps JSON for the change you came to make.";
|
|
3846
|
+
var TASK_QUERY_PROTOCOL = [
|
|
3847
|
+
"READING A TASK BEFORE YOU EDIT IT:",
|
|
3848
|
+
"The plan block you are shown each turn carries only short fields \u2014 it never contains a task's prompt, its user steps, or a completed task's verdict. Never rewrite a field you have not read. To read one, reply with ONLY this JSON object:",
|
|
3849
|
+
` {"${TASK_QUERY_ENVELOPE_KEY}":{"tasks":["<id or #order>", "..."],"fields":[${TASK_QUERY_FIELDS.map((f) => `"${f}"`).join(", ")}],"catalog":true}}`,
|
|
3850
|
+
'- "tasks": one or more task references. Ask for everything you need in ONE query \u2014 each query costs a round-trip.',
|
|
3851
|
+
'- "fields": optional. Omit it to get every field above.',
|
|
3852
|
+
'- "catalog": optional. Set it to true for the full runner and model catalog \u2014 every model id with its label and thinking-effort variants, and every task mode with its description. It works before any task exists, and may be used on its own.',
|
|
3853
|
+
"Ordewell answers immediately with the detail and changes nothing. Then reply again with your taskOps JSON, or with prose for the user.",
|
|
3854
|
+
"Three queries per user message; after that every answer also tells you to land the turn. Do not ask the same question twice."
|
|
3855
|
+
];
|
|
3856
|
+
var TASK_QUERY_REMINDER = `- To READ a task in full (prompt, user steps, verdict, output, user stories) or the whole model/mode catalog before editing, reply with ONLY {"${TASK_QUERY_ENVELOPE_KEY}":{"tasks":["<id or #order>"],"catalog":true}} \u2014 it changes nothing, and you then reply again with your ops.`;
|
|
3857
|
+
function findTask(tasks, ref) {
|
|
3858
|
+
const byId = tasks.find((t) => t.id === ref);
|
|
3859
|
+
if (byId) return byId;
|
|
3860
|
+
const orderStr = ref.startsWith("#") ? ref.slice(1) : ref;
|
|
3861
|
+
if (/^\d+$/.test(orderStr)) {
|
|
3862
|
+
const byOrder = tasks.filter((t) => t.order === Number(orderStr));
|
|
3863
|
+
if (byOrder.length === 1) return byOrder[0];
|
|
3864
|
+
}
|
|
3865
|
+
return tasks.find((t) => t.title === ref);
|
|
3866
|
+
}
|
|
3867
|
+
function renderField(task, field) {
|
|
3868
|
+
switch (field) {
|
|
3869
|
+
case "description":
|
|
3870
|
+
return [`description: ${task.description || "(none)"}`];
|
|
3871
|
+
case "prompt":
|
|
3872
|
+
return task.prompt ? ["prompt:", task.prompt] : ["prompt: (none)"];
|
|
3873
|
+
case "userSteps": {
|
|
3874
|
+
const steps = task.userSteps ?? [];
|
|
3875
|
+
if (steps.length === 0) return ["userSteps: (none)"];
|
|
3876
|
+
return ["userSteps:", ...steps.map((s) => ` ${s.order}. [${s.completed ? "x" : " "}] ${s.instruction}`)];
|
|
3877
|
+
}
|
|
3878
|
+
case "verdict": {
|
|
3879
|
+
const v = task.verdict;
|
|
3880
|
+
if (!v) return ["verdict: (none)"];
|
|
3881
|
+
const checks = v.checks.map((c) => `${c.name}=${c.skipped ? "skipped" : c.passed ? "pass" : "fail"}`).join(", ");
|
|
3882
|
+
return [`verdict: ${v.outcome.toUpperCase()} \u2014 ${v.reason}${checks ? ` [${checks}]` : ""}`];
|
|
3883
|
+
}
|
|
3884
|
+
case "outputSummary": {
|
|
3885
|
+
const o = task.outputSummary;
|
|
3886
|
+
if (!o) return ["outputSummary: (none)"];
|
|
3887
|
+
return [`outputSummary: ${o.reviewReason}`, ...o.logTail ? ["log tail:", o.logTail] : []];
|
|
3888
|
+
}
|
|
3889
|
+
case "userStoriesCovered": {
|
|
3890
|
+
const stories = task.userStoriesCovered ?? [];
|
|
3891
|
+
if (stories.length === 0) return ["userStoriesCovered: (none)"];
|
|
3892
|
+
return ["userStoriesCovered:", ...stories.map((s) => ` - ${s}`)];
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
function renderTask(task, fields) {
|
|
3897
|
+
return [
|
|
3898
|
+
`#${task.order} id=${task.id} "${task.title}" [${task.status}] type:${task.type === "user" ? "MAN" : "AI"}`,
|
|
3899
|
+
...fields.flatMap((f) => renderField(task, f)),
|
|
3900
|
+
""
|
|
3901
|
+
];
|
|
3902
|
+
}
|
|
3903
|
+
function renderCatalog(catalog) {
|
|
3904
|
+
const lines = [];
|
|
3905
|
+
for (const runner of catalog.runners) {
|
|
3906
|
+
const models = catalog.models[runner] ?? [];
|
|
3907
|
+
lines.push(`${runner} models:`);
|
|
3908
|
+
if (models.length === 0) lines.push(" (none discovered)");
|
|
3909
|
+
for (const m of models) {
|
|
3910
|
+
const variants = m.variants?.length ? ` variants: ${m.variants.map((v) => `${v.id}${v.label && v.label !== v.id ? ` (${v.label})` : ""}`).join(", ")}` : " variants: none";
|
|
3911
|
+
lines.push(` - ${m.modelId} \u2014 ${m.modelLabel}${variants}`);
|
|
3912
|
+
}
|
|
3913
|
+
const modes = catalog.modes[runner] ?? [];
|
|
3914
|
+
lines.push(`${runner} task modes:`);
|
|
3915
|
+
if (modes.length === 0) lines.push(" (none declared)");
|
|
3916
|
+
const defaultId = resolveDefaultMode(modes, catalog.autonomousDefault);
|
|
3917
|
+
for (const mode of modes) {
|
|
3918
|
+
lines.push(` - ${mode.id} \u2014 ${mode.label}: ${mode.description}${mode.id === defaultId ? " (default)" : ""}`);
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
return lines;
|
|
3922
|
+
}
|
|
3923
|
+
function renderTaskQueryAnswer(query, tasks, catalog) {
|
|
3924
|
+
const fields = query.fields ?? TASK_QUERY_FIELDS;
|
|
3925
|
+
const blocks = [];
|
|
3926
|
+
if (query.tasks.length > 0) {
|
|
3927
|
+
const body = [];
|
|
3928
|
+
for (const ref of query.tasks) {
|
|
3929
|
+
const task = findTask(tasks, ref);
|
|
3930
|
+
if (!task) {
|
|
3931
|
+
body.push(`${ref}: no task matches this reference in the current plan.`, "");
|
|
3932
|
+
continue;
|
|
3933
|
+
}
|
|
3934
|
+
body.push(...renderTask(task, fields));
|
|
3935
|
+
}
|
|
3936
|
+
blocks.push(["<task_detail>", ...body, "</task_detail>"].join("\n"));
|
|
3937
|
+
}
|
|
3938
|
+
if (query.catalog) {
|
|
3939
|
+
blocks.push(["<runner_catalog>", ...renderCatalog(catalog), "</runner_catalog>"].join("\n"));
|
|
3940
|
+
}
|
|
3941
|
+
blocks.push(
|
|
3942
|
+
"That is everything you asked for. Nothing has changed. Reply now with the taskOps JSON for the edit you intended, or answer the user in prose."
|
|
3943
|
+
);
|
|
3944
|
+
return blocks.join("\n\n");
|
|
3945
|
+
}
|
|
3946
|
+
|
|
2320
3947
|
// src/services/PlanPrompts.ts
|
|
2321
3948
|
function buildResearchToolsPrompt(subagentsEnabled = false) {
|
|
2322
3949
|
const lines = [
|
|
@@ -2406,7 +4033,8 @@ function researchPhaseBlock(harnessMode) {
|
|
|
2406
4033
|
];
|
|
2407
4034
|
const toolSpecific = harnessMode ? [
|
|
2408
4035
|
"- Explore the repository with your own tools until you understand its architecture",
|
|
2409
|
-
"- You are planning, not implementing: do NOT edit, create, or delete any file, and do not run commands that change the workspace"
|
|
4036
|
+
"- You are planning, not implementing: do NOT edit, create, or delete any file, and do not run commands that change the workspace",
|
|
4037
|
+
"- If you delegate exploration to your own agents, WAIT for their results inside this reply. Do NOT launch them in the background or async and end your turn saying you will report back later: your turn ending is what hands the conversation back to the user, and anything you say after it never reaches them."
|
|
2410
4038
|
] : [
|
|
2411
4039
|
"- Use list_dir with depth=2-3 for a tree overview",
|
|
2412
4040
|
"- Use glob and grep to find relevant source files",
|
|
@@ -2571,6 +4199,11 @@ function buildConversationBody(goal, context, modelsJson, runners, modeGuide, mo
|
|
|
2571
4199
|
"TASK MODE:",
|
|
2572
4200
|
modeGuide,
|
|
2573
4201
|
"",
|
|
4202
|
+
// Shared by both variants on purpose: the read channel is a text envelope
|
|
4203
|
+
// exactly so a harness planner, which Ordewell cannot hand tools to, speaks
|
|
4204
|
+
// the same protocol as an API-backed one (ADR-0009).
|
|
4205
|
+
...TASK_QUERY_PROTOCOL,
|
|
4206
|
+
"",
|
|
2574
4207
|
context ? `PROJECT CONTEXT:
|
|
2575
4208
|
${context}
|
|
2576
4209
|
` : "",
|
|
@@ -2878,7 +4511,8 @@ function buildMergePrompt(taskIds, tasks) {
|
|
|
2878
4511
|
"The merged task takes the union of their dependencies (excluding the merged tasks themselves) and preserves their user stories.",
|
|
2879
4512
|
'Set "assignedRunner" and "assignedModel" on the merged task \u2014 use one of the runners and models listed in <available_models> above. Prefer the strongest model if the merged work is complex.',
|
|
2880
4513
|
'Reply with ONLY a taskOps JSON object using a single "merge" op:',
|
|
2881
|
-
` {"taskOps":[{"op":"merge","taskIds":[${selected.map((t) => `"${t.id}"`).join(", ")}],"merged":{"title":"...","description":"...","prompt":"...","assignedRunner":"...","assignedModel":{"modelId":"...","modelLabel":"..."}}}]}
|
|
4514
|
+
` {"taskOps":[{"op":"merge","taskIds":[${selected.map((t) => `"${t.id}"`).join(", ")}],"merged":{"title":"...","description":"...","prompt":"...","assignedRunner":"...","assignedModel":{"modelId":"...","modelLabel":"..."}}}]}`,
|
|
4515
|
+
`If this merge needs a companion op in the same batch (e.g. an added task that should depend on the merge result), give the merge op a "handle" (any unused name) and reference it from the later op's taskId/dependencies.`
|
|
2882
4516
|
].join("\n");
|
|
2883
4517
|
}
|
|
2884
4518
|
function buildSplitPrompt(taskId, tasks) {
|
|
@@ -2890,7 +4524,8 @@ function buildSplitPrompt(taskId, tasks) {
|
|
|
2890
4524
|
"The first part inherits the original task's dependencies. Each later part depends on the previous part. Tasks that depended on the original now depend on the LAST part.",
|
|
2891
4525
|
'Set "assignedRunner" and "assignedModel" on each part \u2014 use the runners and models listed in <available_models> above. You may assign different models to different parts (e.g. a stronger model for a complex part, a faster one for a simple part).',
|
|
2892
4526
|
'Reply with ONLY a taskOps JSON object using a single "split" op:',
|
|
2893
|
-
` {"taskOps":[{"op":"split","taskId":"${task.id}","parts":[{"title":"...","description":"...","prompt":"...","assignedRunner":"...","assignedModel":{"modelId":"...","modelLabel":"..."}},...]}]}
|
|
4527
|
+
` {"taskOps":[{"op":"split","taskId":"${task.id}","parts":[{"title":"...","description":"...","prompt":"...","assignedRunner":"...","assignedModel":{"modelId":"...","modelLabel":"..."}},...]}]}`,
|
|
4528
|
+
`If this split needs a companion op in the same batch (e.g. another task that should depend on the last part), give the split op a "handle" (any unused name) \u2014 it names the last part \u2014 and reference it from the later op's taskId/dependencies.`
|
|
2894
4529
|
].join("\n");
|
|
2895
4530
|
}
|
|
2896
4531
|
|
|
@@ -2917,6 +4552,9 @@ function truncatedPlanReEmitPrompt(historyCompacted) {
|
|
|
2917
4552
|
function reEmitTaskOpsPrompt(detail) {
|
|
2918
4553
|
return `Your task edits could not be applied: ${detail}. Re-emit ONLY a corrected {"${TASK_OPS_ENVELOPE_KEY}":[...]} JSON object \u2014 no prose, no code fences \u2014 or reply in prose if you did not intend to edit tasks.`;
|
|
2919
4554
|
}
|
|
4555
|
+
function reEmitTaskQueryPrompt(detail) {
|
|
4556
|
+
return `Your task-detail request could not be read: ${detail}. Re-emit ONLY a corrected {"${TASK_QUERY_ENVELOPE_KEY}":{"tasks":["<id or #order>"],"fields"?:[...],"catalog"?:true}} JSON object \u2014 no prose, no code fences \u2014 or reply in prose if you did not mean to look anything up.`;
|
|
4557
|
+
}
|
|
2920
4558
|
function taskOpsRejectedPrompt(errors) {
|
|
2921
4559
|
return `Those task edits were rejected:
|
|
2922
4560
|
- ${errors.join("\n- ")}
|
|
@@ -2943,6 +4581,16 @@ function classifyPlannerReply(text, opts) {
|
|
|
2943
4581
|
}
|
|
2944
4582
|
}
|
|
2945
4583
|
}
|
|
4584
|
+
if (textHasTaskQuery(text)) {
|
|
4585
|
+
try {
|
|
4586
|
+
return { kind: "task_query", query: parseTaskQueryJson(text) };
|
|
4587
|
+
} catch (err) {
|
|
4588
|
+
if (!(err instanceof PlanParseError)) throw err;
|
|
4589
|
+
if (extractObjectsWithKey(text, TASK_QUERY_ENVELOPE_KEY).matches.length > 0) {
|
|
4590
|
+
return { kind: "broken_task_query", error: err };
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
2946
4594
|
if (text.includes(`"${PLAN_ENVELOPE_KEY}"`)) {
|
|
2947
4595
|
try {
|
|
2948
4596
|
return { kind: "plan", tasks: parsePlanJson(text, opts.runners, opts.runnerModes, opts.autonomousDefault) };
|
|
@@ -3672,6 +5320,10 @@ ${llmOutput}
|
|
|
3672
5320
|
switch (reply.kind) {
|
|
3673
5321
|
case "task_ops":
|
|
3674
5322
|
return { kind: "task_ops", ops: reply.ops, text: turn.text, researchLog };
|
|
5323
|
+
// A read is settled by the Session (it owns the plan and the catalog),
|
|
5324
|
+
// so it leaves this loop the same way a plan or an edit does.
|
|
5325
|
+
case "task_query":
|
|
5326
|
+
return { kind: "task_query", query: reply.query, text: turn.text, researchLog };
|
|
3675
5327
|
case "plan":
|
|
3676
5328
|
if (ctx.prdEnabled && !ctx.prdCaptured && !ctx.prdNudgeSent && !signal?.aborted) {
|
|
3677
5329
|
ctx.prdNudgeSent = true;
|
|
@@ -3694,6 +5346,13 @@ ${llmOutput}
|
|
|
3694
5346
|
continue;
|
|
3695
5347
|
}
|
|
3696
5348
|
break;
|
|
5349
|
+
case "broken_task_query":
|
|
5350
|
+
if (jsonRepairAttempts < MAX_JSON_REPAIRS2 && !signal?.aborted) {
|
|
5351
|
+
jsonRepairAttempts++;
|
|
5352
|
+
pending = reEmitTaskQueryPrompt(reply.error.message);
|
|
5353
|
+
continue;
|
|
5354
|
+
}
|
|
5355
|
+
break;
|
|
3697
5356
|
case "broken_plan":
|
|
3698
5357
|
if (jsonRepairAttempts < MAX_JSON_REPAIRS2 && !signal?.aborted) {
|
|
3699
5358
|
jsonRepairAttempts++;
|
|
@@ -4498,11 +6157,11 @@ function composeAugmentedPrompt(task, allTasks, opts) {
|
|
|
4498
6157
|
if (isHitlTask(task)) {
|
|
4499
6158
|
blocks.push(renderCheckpointInstruction());
|
|
4500
6159
|
}
|
|
4501
|
-
const
|
|
4502
|
-
if (blocks.length === 0) return basePrompt +
|
|
6160
|
+
const marker2 = renderCompletionMarker(task);
|
|
6161
|
+
if (blocks.length === 0) return basePrompt + marker2;
|
|
4503
6162
|
return `${blocks.join("\n\n")}
|
|
4504
6163
|
|
|
4505
|
-
${basePrompt}${
|
|
6164
|
+
${basePrompt}${marker2}`;
|
|
4506
6165
|
}
|
|
4507
6166
|
|
|
4508
6167
|
// src/services/VerdictEngine.ts
|
|
@@ -5675,111 +7334,6 @@ ${output.slice(-3e3)}`);
|
|
|
5675
7334
|
}
|
|
5676
7335
|
};
|
|
5677
7336
|
|
|
5678
|
-
// src/services/ModelAllowlistResolver.ts
|
|
5679
|
-
function effectiveAllowlist(allowlist, runner, modelsByRunner) {
|
|
5680
|
-
if (!allowlist || allowlist.length === 0) return void 0;
|
|
5681
|
-
if (!modelsByRunner) return allowlist;
|
|
5682
|
-
const own = new Set((modelsByRunner[runner] ?? []).map((m) => m.modelId));
|
|
5683
|
-
const elsewhere = /* @__PURE__ */ new Set();
|
|
5684
|
-
for (const [other, models] of Object.entries(modelsByRunner)) {
|
|
5685
|
-
if (other === runner) continue;
|
|
5686
|
-
for (const m of models ?? []) elsewhere.add(m.modelId);
|
|
5687
|
-
}
|
|
5688
|
-
const kept = allowlist.filter((id) => own.has(id) || !elsewhere.has(id));
|
|
5689
|
-
return kept.length > 0 ? kept : void 0;
|
|
5690
|
-
}
|
|
5691
|
-
function filterModelsForPrompt(modelsByRunner, perRunnerAllowlist) {
|
|
5692
|
-
const result = {};
|
|
5693
|
-
for (const [runner, models] of Object.entries(modelsByRunner)) {
|
|
5694
|
-
if (!models) continue;
|
|
5695
|
-
const allowed = effectiveAllowlist(perRunnerAllowlist[runner], runner, modelsByRunner);
|
|
5696
|
-
if (!allowed) {
|
|
5697
|
-
result[runner] = models;
|
|
5698
|
-
continue;
|
|
5699
|
-
}
|
|
5700
|
-
const allowedSet = new Set(allowed);
|
|
5701
|
-
const intersection = models.filter((m) => allowedSet.has(m.modelId));
|
|
5702
|
-
const covered = new Set(intersection.map((m) => m.modelId));
|
|
5703
|
-
const missing = allowed.filter((id) => !covered.has(id)).map((id) => ({ modelId: id, modelLabel: id.split("/").pop() || id, variants: [] }));
|
|
5704
|
-
result[runner] = [...intersection, ...missing];
|
|
5705
|
-
}
|
|
5706
|
-
return result;
|
|
5707
|
-
}
|
|
5708
|
-
var EFFORT_LADDER = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
|
|
5709
|
-
var EFFORT_ALIASES = { disabled: "none", adaptive: "medium" };
|
|
5710
|
-
function clampThinkingEffort(effort, variants) {
|
|
5711
|
-
if (!effort) return void 0;
|
|
5712
|
-
if (variants.length === 0) return void 0;
|
|
5713
|
-
const ids = new Set(variants.map((v) => v.id));
|
|
5714
|
-
if (ids.has(effort)) return effort;
|
|
5715
|
-
const canon = EFFORT_ALIASES[effort] ?? effort;
|
|
5716
|
-
if (ids.has(canon)) return canon;
|
|
5717
|
-
const idx = EFFORT_LADDER.indexOf(canon);
|
|
5718
|
-
if (idx < 0) return void 0;
|
|
5719
|
-
for (let d = 1; d < EFFORT_LADDER.length; d++) {
|
|
5720
|
-
const lower = EFFORT_LADDER[idx - d];
|
|
5721
|
-
if (lower && ids.has(lower)) return lower;
|
|
5722
|
-
const higher = EFFORT_LADDER[idx + d];
|
|
5723
|
-
if (higher && ids.has(higher)) return higher;
|
|
5724
|
-
}
|
|
5725
|
-
return void 0;
|
|
5726
|
-
}
|
|
5727
|
-
function coerceAssignments(tasks, perRunnerAllowlist, allowedRunners, modelsByRunner) {
|
|
5728
|
-
const clampVariant = (task) => {
|
|
5729
|
-
if (!task.assignedModel || !modelsByRunner) return task;
|
|
5730
|
-
const model = modelsByRunner[task.assignedRunner]?.find(
|
|
5731
|
-
(m) => m.modelId === task.assignedModel.modelId
|
|
5732
|
-
);
|
|
5733
|
-
if (!model) return task;
|
|
5734
|
-
const clamped = clampThinkingEffort(task.assignedModel.thinkingEffort, model.variants);
|
|
5735
|
-
return {
|
|
5736
|
-
...task,
|
|
5737
|
-
assignedModel: {
|
|
5738
|
-
...task.assignedModel,
|
|
5739
|
-
thinkingEffort: clamped,
|
|
5740
|
-
availableVariants: model.variants.map((v) => v.id)
|
|
5741
|
-
}
|
|
5742
|
-
};
|
|
5743
|
-
};
|
|
5744
|
-
const allowedFor = (runner) => effectiveAllowlist(perRunnerAllowlist[runner], runner, modelsByRunner);
|
|
5745
|
-
const assignmentFor = (runner, modelId) => {
|
|
5746
|
-
const known = modelsByRunner?.[runner]?.find((m) => m.modelId === modelId);
|
|
5747
|
-
return {
|
|
5748
|
-
modelId,
|
|
5749
|
-
modelLabel: known?.modelLabel ?? modelId,
|
|
5750
|
-
thinkingEffort: void 0,
|
|
5751
|
-
...known ? { availableVariants: known.variants.map((v) => v.id) } : {}
|
|
5752
|
-
};
|
|
5753
|
-
};
|
|
5754
|
-
return tasks.map((task) => {
|
|
5755
|
-
if (task.type === "user") return task;
|
|
5756
|
-
if (allowedRunners && !allowedRunners.includes(task.assignedRunner)) {
|
|
5757
|
-
const fallbackRunner = allowedRunners[0];
|
|
5758
|
-
const fallbackModels = allowedFor(fallbackRunner);
|
|
5759
|
-
const fallbackModel = fallbackModels?.length ? assignmentFor(fallbackRunner, fallbackModels[0]) : modelsByRunner?.[fallbackRunner]?.[0] ? assignmentFor(fallbackRunner, modelsByRunner[fallbackRunner][0].modelId) : task.assignedModel;
|
|
5760
|
-
return clampVariant({
|
|
5761
|
-
...task,
|
|
5762
|
-
assignedRunner: fallbackRunner,
|
|
5763
|
-
assignedModel: fallbackModel
|
|
5764
|
-
});
|
|
5765
|
-
}
|
|
5766
|
-
const allowlist = allowedFor(task.assignedRunner);
|
|
5767
|
-
if (!task.assignedModel) {
|
|
5768
|
-
if (allowlist && allowlist.length > 0) {
|
|
5769
|
-
return clampVariant({ ...task, assignedModel: assignmentFor(task.assignedRunner, allowlist[0]) });
|
|
5770
|
-
}
|
|
5771
|
-
const discovered = modelsByRunner?.[task.assignedRunner]?.[0];
|
|
5772
|
-
if (discovered) {
|
|
5773
|
-
return clampVariant({ ...task, assignedModel: assignmentFor(task.assignedRunner, discovered.modelId) });
|
|
5774
|
-
}
|
|
5775
|
-
return task;
|
|
5776
|
-
}
|
|
5777
|
-
if (!allowlist || allowlist.length === 0) return clampVariant(task);
|
|
5778
|
-
if (allowlist.includes(task.assignedModel.modelId)) return clampVariant(task);
|
|
5779
|
-
return { ...task, assignedModel: assignmentFor(task.assignedRunner, allowlist[0]) };
|
|
5780
|
-
});
|
|
5781
|
-
}
|
|
5782
|
-
|
|
5783
7337
|
// src/services/Planner.ts
|
|
5784
7338
|
function plannerModesWith(req) {
|
|
5785
7339
|
const modes = req.modes ?? DEFAULT_PLANNER_MODES;
|
|
@@ -7449,7 +9003,7 @@ function assertWorkspaceIsProject(workspace, deps = {}) {
|
|
|
7449
9003
|
throw new WorkspaceNotAProjectError(workspace);
|
|
7450
9004
|
}
|
|
7451
9005
|
const hasMarker = [...PROJECT_MARKER_DIRS, ...PROJECT_MARKER_FILES].some(
|
|
7452
|
-
(
|
|
9006
|
+
(marker2) => exists(path9.join(resolved, marker2))
|
|
7453
9007
|
);
|
|
7454
9008
|
if (!hasMarker) throw new WorkspaceNotAProjectError(workspace);
|
|
7455
9009
|
}
|
|
@@ -7491,12 +9045,19 @@ var StdioAgentAdapter = class {
|
|
|
7491
9045
|
/** Set for the duration of a turn. Outside one, events are buffered rather than dropped. */
|
|
7492
9046
|
turnEmit = null;
|
|
7493
9047
|
/**
|
|
7494
|
-
* Events the agent produced
|
|
7495
|
-
* that arrive during the handshake. Dropping them hid the one
|
|
7496
|
-
* that explains a planner which cannot read the workspace, so they
|
|
7497
|
-
* until a turn exists to show them in.
|
|
9048
|
+
* Events the agent produced before its first turn — in practice the startup
|
|
9049
|
+
* warnings that arrive during the handshake. Dropping them hid the one
|
|
9050
|
+
* diagnostic that explains a planner which cannot read the workspace, so they
|
|
9051
|
+
* are held until a turn exists to show them in.
|
|
9052
|
+
*
|
|
9053
|
+
* Only before the *first* turn. Once one has run, anything arriving outside a
|
|
9054
|
+
* turn is that closed turn's straggling work — a backgrounded subagent
|
|
9055
|
+
* finishing, a late read — and replaying it into the next turn presents it as
|
|
9056
|
+
* research done for the user's new message.
|
|
7498
9057
|
*/
|
|
7499
9058
|
betweenTurns = [];
|
|
9059
|
+
/** Set once a turn has ended: after that, out-of-turn events are stale, not startup. */
|
|
9060
|
+
hadTurn = false;
|
|
7500
9061
|
disposed = false;
|
|
7501
9062
|
/** Resolves when the process ends, so a handshake can lose the race instead of waiting out its timeout. */
|
|
7502
9063
|
processEnded;
|
|
@@ -7531,7 +9092,7 @@ var StdioAgentAdapter = class {
|
|
|
7531
9092
|
this.process.stdout?.on("data", (chunk) => {
|
|
7532
9093
|
this.stdout.push(chunk.toString(), (line) => this.handleLine(line, (event) => {
|
|
7533
9094
|
if (this.turnEmit) this.turnEmit(event);
|
|
7534
|
-
else if (!TURN_SCOPED_EVENTS.has(event.type) && this.betweenTurns.length < BETWEEN_TURN_EVENT_CAP) {
|
|
9095
|
+
else if (!this.hadTurn && !TURN_SCOPED_EVENTS.has(event.type) && this.betweenTurns.length < BETWEEN_TURN_EVENT_CAP) {
|
|
7535
9096
|
this.betweenTurns.push(event);
|
|
7536
9097
|
}
|
|
7537
9098
|
}));
|
|
@@ -7563,6 +9124,7 @@ ${err.message}`).slice(-STDERR_TAIL_CHARS);
|
|
|
7563
9124
|
if (settled) return;
|
|
7564
9125
|
settled = true;
|
|
7565
9126
|
this.turnEmit = null;
|
|
9127
|
+
this.hadTurn = true;
|
|
7566
9128
|
signal?.removeEventListener("abort", onAbort);
|
|
7567
9129
|
this.process?.removeListener("exit", onExit);
|
|
7568
9130
|
this.process?.removeListener("error", onExit);
|
|
@@ -7641,6 +9203,7 @@ ${tail}` : ""}`;
|
|
|
7641
9203
|
|
|
7642
9204
|
// src/services/harness/ClaudeCodeAdapter.ts
|
|
7643
9205
|
var DISALLOWED_TOOLS = ["Edit", "Write", "MultiEdit", "NotebookEdit", "KillShell"];
|
|
9206
|
+
var ASYNC_LAUNCH_MARKER = "Async agent launched successfully";
|
|
7644
9207
|
function flattenContent(content) {
|
|
7645
9208
|
if (typeof content === "string") return content;
|
|
7646
9209
|
if (Array.isArray(content)) {
|
|
@@ -7650,6 +9213,8 @@ function flattenContent(content) {
|
|
|
7650
9213
|
}
|
|
7651
9214
|
var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
7652
9215
|
agentId = "claude-code";
|
|
9216
|
+
/** Whether this turn has already emitted reply text — see {@link handleLine}. */
|
|
9217
|
+
turnHasText = false;
|
|
7653
9218
|
spawnSpec(opts) {
|
|
7654
9219
|
const args = [
|
|
7655
9220
|
"-p",
|
|
@@ -7674,6 +9239,7 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
7674
9239
|
return { command: "claude", args };
|
|
7675
9240
|
}
|
|
7676
9241
|
turnPayload(message) {
|
|
9242
|
+
this.turnHasText = false;
|
|
7677
9243
|
return `${JSON.stringify({
|
|
7678
9244
|
type: "user",
|
|
7679
9245
|
message: { role: "user", content: [{ type: "text", text: message }] }
|
|
@@ -7684,6 +9250,7 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
7684
9250
|
const msg = StdioAgentAdapter.parse(line);
|
|
7685
9251
|
if (!msg) return;
|
|
7686
9252
|
if (msg.session_id) this.sessionId = msg.session_id;
|
|
9253
|
+
if (msg.parent_tool_use_id) return;
|
|
7687
9254
|
switch (msg.type) {
|
|
7688
9255
|
// The control channel: Claude asks whether a tool may run when its mode
|
|
7689
9256
|
// cannot decide alone. A read-only planner answers "deny", every time —
|
|
@@ -7708,8 +9275,12 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
7708
9275
|
}
|
|
7709
9276
|
case "assistant":
|
|
7710
9277
|
for (const block of Array.isArray(msg.message?.content) ? msg.message.content : []) {
|
|
7711
|
-
if (block.type === "text" && block.text)
|
|
7712
|
-
|
|
9278
|
+
if (block.type === "text" && block.text) {
|
|
9279
|
+
emit({ type: "assistant_text", text: this.turnHasText ? `
|
|
9280
|
+
|
|
9281
|
+
${block.text}` : block.text });
|
|
9282
|
+
this.turnHasText = true;
|
|
9283
|
+
} else if (block.type === "thinking" && block.thinking) emit({ type: "thinking", text: block.thinking });
|
|
7713
9284
|
else if (block.type === "tool_use" && block.name) {
|
|
7714
9285
|
emit({ type: "tool_call", id: block.id ?? block.name, name: block.name, args: block.input ?? {} });
|
|
7715
9286
|
}
|
|
@@ -7718,11 +9289,15 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
7718
9289
|
case "user":
|
|
7719
9290
|
for (const block of Array.isArray(msg.message?.content) ? msg.message.content : []) {
|
|
7720
9291
|
if (block.type !== "tool_result") continue;
|
|
9292
|
+
const output = flattenContent(block.content);
|
|
9293
|
+
if (output.includes(ASYNC_LAUNCH_MARKER)) {
|
|
9294
|
+
emit({ type: "background_agent", id: block.tool_use_id ?? "" });
|
|
9295
|
+
}
|
|
7721
9296
|
emit({
|
|
7722
9297
|
type: "tool_result",
|
|
7723
9298
|
id: block.tool_use_id ?? "",
|
|
7724
9299
|
name: "",
|
|
7725
|
-
output
|
|
9300
|
+
output,
|
|
7726
9301
|
success: block.is_error !== true
|
|
7727
9302
|
});
|
|
7728
9303
|
}
|
|
@@ -8142,6 +9717,8 @@ var OpenCodeAdapter = class {
|
|
|
8142
9717
|
exited = false;
|
|
8143
9718
|
disposed = false;
|
|
8144
9719
|
opts = null;
|
|
9720
|
+
/** Whether this turn has already emitted reply text — see {@link emitPart}. */
|
|
9721
|
+
turnHasText = false;
|
|
8145
9722
|
async start(opts) {
|
|
8146
9723
|
this.opts = opts;
|
|
8147
9724
|
assertWorkspaceExists(opts.cwd, { isDirectory: this.deps.isDirectory });
|
|
@@ -8209,6 +9786,7 @@ ${this.stderrTail.trim()}` : ""}`);
|
|
|
8209
9786
|
return;
|
|
8210
9787
|
}
|
|
8211
9788
|
const seen = /* @__PURE__ */ new Set();
|
|
9789
|
+
this.turnHasText = false;
|
|
8212
9790
|
const streamAbort = new AbortController();
|
|
8213
9791
|
let connected = () => {
|
|
8214
9792
|
};
|
|
@@ -8277,7 +9855,10 @@ ${this.stderrTail.trim()}` : ""}`);
|
|
|
8277
9855
|
if (part.type === "text" && part.text) {
|
|
8278
9856
|
if (seen.has(`text:${id}`)) return;
|
|
8279
9857
|
seen.add(`text:${id}`);
|
|
8280
|
-
onEvent({ type: "assistant_text", text:
|
|
9858
|
+
onEvent({ type: "assistant_text", text: this.turnHasText ? `
|
|
9859
|
+
|
|
9860
|
+
${part.text}` : part.text });
|
|
9861
|
+
this.turnHasText = true;
|
|
8281
9862
|
return;
|
|
8282
9863
|
}
|
|
8283
9864
|
if (part.type === "reasoning" && part.text) {
|
|
@@ -8444,6 +10025,7 @@ function normalizeAgentArgs(tool, args) {
|
|
|
8444
10025
|
|
|
8445
10026
|
// src/services/harness/CliAgentAiService.ts
|
|
8446
10027
|
var MAX_JSON_REPAIRS = 2;
|
|
10028
|
+
var MAX_AGENT_WAITS = 2;
|
|
8447
10029
|
var LOG_MAX_CHARS = 1e4;
|
|
8448
10030
|
function defaultAdapter(runner, deps) {
|
|
8449
10031
|
switch (runner) {
|
|
@@ -8584,13 +10166,16 @@ var CliAgentAiService = class {
|
|
|
8584
10166
|
let pending = message;
|
|
8585
10167
|
let emptyNudgeSent = false;
|
|
8586
10168
|
let jsonRepairAttempts = 0;
|
|
10169
|
+
let agentWaits = 0;
|
|
10170
|
+
const carried = [];
|
|
10171
|
+
const replyText = (text) => [...carried, text].filter((part) => part.trim()).join("\n\n");
|
|
8587
10172
|
try {
|
|
8588
10173
|
for (; ; ) {
|
|
8589
10174
|
const turn = await this.runTurn(pending, onProgress, combined);
|
|
8590
10175
|
researchLog.push(...turn.researchLog);
|
|
8591
10176
|
if (turn.aborted || combined?.aborted) {
|
|
8592
10177
|
onProgress({ type: "interrupted" });
|
|
8593
|
-
return { kind: "message", text: turn.text, researchLog };
|
|
10178
|
+
return { kind: "message", text: replyText(turn.text), researchLog };
|
|
8594
10179
|
}
|
|
8595
10180
|
if (turn.error) {
|
|
8596
10181
|
return { kind: "message", text: turn.error, researchLog };
|
|
@@ -8608,6 +10193,18 @@ var CliAgentAiService = class {
|
|
|
8608
10193
|
researchLog
|
|
8609
10194
|
};
|
|
8610
10195
|
}
|
|
10196
|
+
if (turn.backgroundAgents > 0 && agentWaits < MAX_AGENT_WAITS && !combined?.aborted) {
|
|
10197
|
+
agentWaits++;
|
|
10198
|
+
carried.push(turn.text);
|
|
10199
|
+
pending = [
|
|
10200
|
+
`You ended your turn with ${turn.backgroundAgents} subagent(s) still running in the background.`,
|
|
10201
|
+
"Ordewell hands the conversation back to the user when your turn ends, so anything you say after it never reaches them \u2014",
|
|
10202
|
+
"the results you promised to report would be lost.",
|
|
10203
|
+
"Wait for those agents to finish NOW, in this reply, and do not end your turn until you have their results.",
|
|
10204
|
+
"Then give the user your synthesis. In future replies, await your agents inside the turn rather than backgrounding them."
|
|
10205
|
+
].join(" ");
|
|
10206
|
+
continue;
|
|
10207
|
+
}
|
|
8611
10208
|
if (extractPrdBlock(turn.text)) conversation.prdCaptured = true;
|
|
8612
10209
|
const reply = classifyPlannerReply(turn.text, {
|
|
8613
10210
|
runners: conversation.runners,
|
|
@@ -8616,7 +10213,11 @@ var CliAgentAiService = class {
|
|
|
8616
10213
|
});
|
|
8617
10214
|
switch (reply.kind) {
|
|
8618
10215
|
case "task_ops":
|
|
8619
|
-
return { kind: "task_ops", ops: reply.ops, text: turn.text, researchLog };
|
|
10216
|
+
return { kind: "task_ops", ops: reply.ops, text: replyText(turn.text), researchLog };
|
|
10217
|
+
// The read channel is a text envelope precisely so it reaches here
|
|
10218
|
+
// too: a harness planner has no Ordewell tool loop to call into.
|
|
10219
|
+
case "task_query":
|
|
10220
|
+
return { kind: "task_query", query: reply.query, text: replyText(turn.text), researchLog };
|
|
8620
10221
|
case "plan":
|
|
8621
10222
|
if (conversation.prdEnabled && !conversation.prdCaptured && !conversation.prdNudgeSent && !combined?.aborted) {
|
|
8622
10223
|
conversation.prdNudgeSent = true;
|
|
@@ -8629,7 +10230,7 @@ var CliAgentAiService = class {
|
|
|
8629
10230
|
continue;
|
|
8630
10231
|
}
|
|
8631
10232
|
this.conversation = null;
|
|
8632
|
-
return { kind: "plan", tasks: reply.tasks, text: turn.text, researchLog };
|
|
10233
|
+
return { kind: "plan", tasks: reply.tasks, text: replyText(turn.text), researchLog };
|
|
8633
10234
|
case "broken_task_ops":
|
|
8634
10235
|
if (jsonRepairAttempts < MAX_JSON_REPAIRS && !combined?.aborted) {
|
|
8635
10236
|
jsonRepairAttempts++;
|
|
@@ -8637,6 +10238,13 @@ var CliAgentAiService = class {
|
|
|
8637
10238
|
continue;
|
|
8638
10239
|
}
|
|
8639
10240
|
break;
|
|
10241
|
+
case "broken_task_query":
|
|
10242
|
+
if (jsonRepairAttempts < MAX_JSON_REPAIRS && !combined?.aborted) {
|
|
10243
|
+
jsonRepairAttempts++;
|
|
10244
|
+
pending = reEmitTaskQueryPrompt(reply.error.message);
|
|
10245
|
+
continue;
|
|
10246
|
+
}
|
|
10247
|
+
break;
|
|
8640
10248
|
case "broken_plan":
|
|
8641
10249
|
if (jsonRepairAttempts < MAX_JSON_REPAIRS && !combined?.aborted) {
|
|
8642
10250
|
jsonRepairAttempts++;
|
|
@@ -8647,7 +10255,7 @@ var CliAgentAiService = class {
|
|
|
8647
10255
|
case "prose":
|
|
8648
10256
|
break;
|
|
8649
10257
|
}
|
|
8650
|
-
return { kind: "message", text: turn.text, researchLog };
|
|
10258
|
+
return { kind: "message", text: replyText(turn.text), researchLog };
|
|
8651
10259
|
}
|
|
8652
10260
|
} finally {
|
|
8653
10261
|
this.activeAbort = null;
|
|
@@ -8667,9 +10275,11 @@ var CliAgentAiService = class {
|
|
|
8667
10275
|
let text = "";
|
|
8668
10276
|
let error;
|
|
8669
10277
|
let stepIndex = 0;
|
|
10278
|
+
let backgroundAgents = 0;
|
|
8670
10279
|
const truncate = (value) => value.length > LOG_MAX_CHARS ? `${value.slice(0, LOG_MAX_CHARS)}
|
|
8671
10280
|
[... truncated, total ${value.length} chars]` : value;
|
|
8672
|
-
const settle = (id,
|
|
10281
|
+
const settle = (id, rawOutput, success, outcome) => {
|
|
10282
|
+
const output = redactSecrets(rawOutput);
|
|
8673
10283
|
const call = pendingCalls.get(id);
|
|
8674
10284
|
pendingCalls.delete(id);
|
|
8675
10285
|
const step = {
|
|
@@ -8705,6 +10315,9 @@ var CliAgentAiService = class {
|
|
|
8705
10315
|
case "tool_result":
|
|
8706
10316
|
settle(event.id, event.output, event.success, event.success ? "success" : "failure");
|
|
8707
10317
|
return;
|
|
10318
|
+
case "background_agent":
|
|
10319
|
+
backgroundAgents++;
|
|
10320
|
+
return;
|
|
8708
10321
|
case "permission_request": {
|
|
8709
10322
|
const mapped = mapAgentTool(event.name);
|
|
8710
10323
|
const args = JSON.stringify({ detail: event.detail });
|
|
@@ -8735,7 +10348,7 @@ var CliAgentAiService = class {
|
|
|
8735
10348
|
adapter.dispose();
|
|
8736
10349
|
this.adapter = null;
|
|
8737
10350
|
}
|
|
8738
|
-
return { text, researchLog, error, aborted: signal?.aborted };
|
|
10351
|
+
return { text, researchLog, error, aborted: signal?.aborted, backgroundAgents };
|
|
8739
10352
|
}
|
|
8740
10353
|
// --- Process lifecycle ---
|
|
8741
10354
|
async startAdapter(opts) {
|
|
@@ -9200,6 +10813,16 @@ function ensureDir(dir) {
|
|
|
9200
10813
|
function getStateDir(baseDir) {
|
|
9201
10814
|
return path10.join(baseDir ?? process.cwd(), STATE_DIR);
|
|
9202
10815
|
}
|
|
10816
|
+
var IGNORE_FILE = ".gitignore";
|
|
10817
|
+
function ensureStateDirIgnored(baseDir) {
|
|
10818
|
+
const dir = getStateDir(baseDir);
|
|
10819
|
+
ensureDir(dir);
|
|
10820
|
+
try {
|
|
10821
|
+
fs4.writeFileSync(path10.join(dir, IGNORE_FILE), "*\n", { flag: "wx" });
|
|
10822
|
+
} catch (err) {
|
|
10823
|
+
if (err.code !== "EEXIST") throw err;
|
|
10824
|
+
}
|
|
10825
|
+
}
|
|
9203
10826
|
|
|
9204
10827
|
// src/utils/sessionId.ts
|
|
9205
10828
|
import { randomBytes } from "crypto";
|
|
@@ -9229,6 +10852,7 @@ function sessionFilename(session) {
|
|
|
9229
10852
|
function saveSession(plan, goal, baseDir, id) {
|
|
9230
10853
|
const sessionsDir = getSessionsDir(baseDir);
|
|
9231
10854
|
ensureDir(sessionsDir);
|
|
10855
|
+
ensureStateDirIgnored(baseDir);
|
|
9232
10856
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9233
10857
|
const meta = {
|
|
9234
10858
|
id: id || mintSessionId(),
|
|
@@ -9331,6 +10955,12 @@ function deleteSession(sessionId, baseDir, logger = defaultLogger) {
|
|
|
9331
10955
|
}
|
|
9332
10956
|
|
|
9333
10957
|
// src/services/createSession.ts
|
|
10958
|
+
var CATALOG_MODEL_CAP = 100;
|
|
10959
|
+
var MAX_TASK_QUERIES = 3;
|
|
10960
|
+
var MAX_TASK_QUERIES_HARD = 6;
|
|
10961
|
+
function freshReadBudget() {
|
|
10962
|
+
return { answered: 0, seen: /* @__PURE__ */ new Set() };
|
|
10963
|
+
}
|
|
9334
10964
|
var PlanEditError = class extends Error {
|
|
9335
10965
|
constructor(message) {
|
|
9336
10966
|
super(message);
|
|
@@ -9567,6 +11197,21 @@ var Session = class {
|
|
|
9567
11197
|
runnerModesFor(runners) {
|
|
9568
11198
|
return runnerModesFrom(this.registry, runners);
|
|
9569
11199
|
}
|
|
11200
|
+
/**
|
|
11201
|
+
* The catalog a model/task-mode edit is checked against — the same
|
|
11202
|
+
* discovered models and manifest modes the per-turn catalog block shows the
|
|
11203
|
+
* planner, so a refusal here can never name something invalid that the
|
|
11204
|
+
* planner was never told about. `modelsCache` is read live, not filtered by
|
|
11205
|
+
* the allowlist here — {@link checkModelAndModeValidity} narrows by
|
|
11206
|
+
* allowlist itself, the same way `coerceAssignments` does.
|
|
11207
|
+
*/
|
|
11208
|
+
editCatalog() {
|
|
11209
|
+
return {
|
|
11210
|
+
modelsByRunner: this.modelsCache,
|
|
11211
|
+
runnerModes: this.runnerModesFor(this.plan?.runners ?? []),
|
|
11212
|
+
perRunnerAllowlist: this.settingsFn().modelAllowlist ?? this.currentAllowlist
|
|
11213
|
+
};
|
|
11214
|
+
}
|
|
9570
11215
|
get planState() {
|
|
9571
11216
|
return this.plan;
|
|
9572
11217
|
}
|
|
@@ -9746,10 +11391,12 @@ var Session = class {
|
|
|
9746
11391
|
async continueConversation(userMessage, options) {
|
|
9747
11392
|
if (!this.plan) throw new Error("No planning conversation to continue");
|
|
9748
11393
|
const priorHistory = this.plan.conversationHistory ?? [];
|
|
11394
|
+
const priorResearchLog = this.plan.researchLog ?? [];
|
|
9749
11395
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9750
11396
|
this.appendTranscript("user", userMessage, now);
|
|
11397
|
+
const historyAfterOwnAppend = this.plan.conversationHistory;
|
|
9751
11398
|
this.plan.researchLog = [...this.plan.researchLog ?? [], { id: `up-${Date.now()}`, type: "user_prompt", content: userMessage, timestamp: now }];
|
|
9752
|
-
const contextBlock = this.planContextBlock();
|
|
11399
|
+
const contextBlock = [this.catalogBlock(), this.planContextBlock()].filter(Boolean).join("\n\n");
|
|
9753
11400
|
const outgoing = contextBlock ? `${contextBlock}
|
|
9754
11401
|
|
|
9755
11402
|
${userMessage}` : userMessage;
|
|
@@ -9762,6 +11409,8 @@ ${userMessage}` : userMessage;
|
|
|
9762
11409
|
(p) => this.translateProgress(p),
|
|
9763
11410
|
options?.signal
|
|
9764
11411
|
) : await this.resumeConversation(outgoing, priorHistory, options);
|
|
11412
|
+
const reads = freshReadBudget();
|
|
11413
|
+
turn = await this.drainTaskQueries(turn, reads, options);
|
|
9765
11414
|
if ((turn.kind === "task_ops" || turn.kind === "plan") && this.hasLiveWork) {
|
|
9766
11415
|
this.queueMessage(userMessage);
|
|
9767
11416
|
this.plan.queuedMessages = this.getQueuedMessages();
|
|
@@ -9771,11 +11420,74 @@ ${userMessage}` : userMessage;
|
|
|
9771
11420
|
researchLog: turn.researchLog
|
|
9772
11421
|
};
|
|
9773
11422
|
}
|
|
9774
|
-
return await this.settleTurn(turn, options);
|
|
11423
|
+
return await this.settleTurn(turn, options, reads);
|
|
11424
|
+
} catch (err) {
|
|
11425
|
+
if (this.plan && this.plan.conversationHistory === historyAfterOwnAppend) {
|
|
11426
|
+
this.plan.conversationHistory = priorHistory;
|
|
11427
|
+
this.plan.researchLog = priorResearchLog;
|
|
11428
|
+
}
|
|
11429
|
+
throw err;
|
|
9775
11430
|
} finally {
|
|
9776
11431
|
releaseAbort();
|
|
9777
11432
|
}
|
|
9778
11433
|
}
|
|
11434
|
+
/**
|
|
11435
|
+
* Answer every read the planner emits until it says something else.
|
|
11436
|
+
*
|
|
11437
|
+
* The channel is a text envelope rather than a registered tool because the
|
|
11438
|
+
* protocol has to be identical on both planner backends (ADR-0009): Ordewell
|
|
11439
|
+
* owns a tool loop only in the API case, and a harness planner running as a
|
|
11440
|
+
* coding-agent subprocess can only be reached this way.
|
|
11441
|
+
*
|
|
11442
|
+
* Draining here — outside {@link repairLoop} — is what keeps reads free of
|
|
11443
|
+
* the repair budget. A planner that looks a task up and *then* fumbles its
|
|
11444
|
+
* ops JSON still gets its two corrective retries; charging it for the read
|
|
11445
|
+
* would cost it the chance to fix the edit.
|
|
11446
|
+
*/
|
|
11447
|
+
async drainTaskQueries(turn, reads, options) {
|
|
11448
|
+
const carried = [];
|
|
11449
|
+
let current = turn;
|
|
11450
|
+
while (current.kind === "task_query") {
|
|
11451
|
+
carried.push(...current.researchLog);
|
|
11452
|
+
if (reads.answered >= MAX_TASK_QUERIES_HARD || !this.aiService.hasActiveConversation() || options?.signal?.aborted) {
|
|
11453
|
+
return {
|
|
11454
|
+
kind: "message",
|
|
11455
|
+
text: "The planner kept asking to read tasks instead of answering. Nothing was changed \u2014 ask again, or be more specific about the edit you want.",
|
|
11456
|
+
researchLog: carried
|
|
11457
|
+
};
|
|
11458
|
+
}
|
|
11459
|
+
const signature = taskQuerySignature(current.query);
|
|
11460
|
+
const insist = reads.seen.has(signature) || reads.answered >= MAX_TASK_QUERIES;
|
|
11461
|
+
reads.seen.add(signature);
|
|
11462
|
+
reads.answered++;
|
|
11463
|
+
const answer = this.taskQueryAnswer(current.query);
|
|
11464
|
+
current = await this.aiService.continueConversation(
|
|
11465
|
+
insist ? `${answer}
|
|
11466
|
+
|
|
11467
|
+
${TASK_QUERY_ANSWER_OR_OPS}` : answer,
|
|
11468
|
+
(p) => this.translateProgress(p),
|
|
11469
|
+
options?.signal
|
|
11470
|
+
);
|
|
11471
|
+
}
|
|
11472
|
+
return carried.length > 0 ? { ...current, researchLog: [...carried, ...current.researchLog] } : current;
|
|
11473
|
+
}
|
|
11474
|
+
/**
|
|
11475
|
+
* Render one read out of live state. Never persisted to the transcript: the
|
|
11476
|
+
* detail is context for the planner's next reply, and re-sending it on every
|
|
11477
|
+
* later turn is exactly the cost this channel exists to avoid.
|
|
11478
|
+
*/
|
|
11479
|
+
taskQueryAnswer(query) {
|
|
11480
|
+
const runners = this.plan?.runners ?? [];
|
|
11481
|
+
const allowlist = this.settingsFn().modelAllowlist ?? this.currentAllowlist;
|
|
11482
|
+
return renderTaskQueryAnswer(query, this.store.planTasks, {
|
|
11483
|
+
runners,
|
|
11484
|
+
// Allowlist-filtered like the always-on block: a read must not offer a
|
|
11485
|
+
// model the planner is forbidden to assign.
|
|
11486
|
+
models: filterModelsForPrompt(this.modelsCache, allowlist),
|
|
11487
|
+
modes: this.runnerModesFor(runners),
|
|
11488
|
+
autonomousDefault: this.config.autonomousMode
|
|
11489
|
+
});
|
|
11490
|
+
}
|
|
9779
11491
|
/**
|
|
9780
11492
|
* Drive a planner turn to a persisted, broadcast outcome. Task edits apply
|
|
9781
11493
|
* atomically; validation failures are fed back to the model for up to 2
|
|
@@ -9783,7 +11495,7 @@ ${userMessage}` : userMessage;
|
|
|
9783
11495
|
* first turn (startPlanning) and every later turn route through here — one
|
|
9784
11496
|
* path, not two.
|
|
9785
11497
|
*/
|
|
9786
|
-
async settleTurn(turn, options) {
|
|
11498
|
+
async settleTurn(turn, options, reads = freshReadBudget()) {
|
|
9787
11499
|
const invalidOps = (errors, researchLog) => ({
|
|
9788
11500
|
turn: {
|
|
9789
11501
|
kind: "message",
|
|
@@ -9795,11 +11507,15 @@ The plan is unchanged. Rephrase the request, or adjust the tasks manually.`,
|
|
|
9795
11507
|
}
|
|
9796
11508
|
});
|
|
9797
11509
|
const settled = await repairLoop({
|
|
9798
|
-
first: () => turn,
|
|
9799
|
-
resend: (corrective) => this.
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
11510
|
+
first: () => this.drainTaskQueries(turn, reads, options),
|
|
11511
|
+
resend: async (corrective) => this.drainTaskQueries(
|
|
11512
|
+
await this.aiService.continueConversation(
|
|
11513
|
+
corrective,
|
|
11514
|
+
(p) => this.translateProgress(p),
|
|
11515
|
+
options?.signal
|
|
11516
|
+
),
|
|
11517
|
+
reads,
|
|
11518
|
+
options
|
|
9803
11519
|
),
|
|
9804
11520
|
interpret: (t) => {
|
|
9805
11521
|
if (t.kind !== "task_ops") return { done: { turn: t } };
|
|
@@ -9819,6 +11535,52 @@ The plan is unchanged. Rephrase the request, or adjust the tasks manually.`,
|
|
|
9819
11535
|
}
|
|
9820
11536
|
return this.applyConversationTurn(settled.turn);
|
|
9821
11537
|
}
|
|
11538
|
+
/**
|
|
11539
|
+
* The catalog the planner may actually draw from — model ids and task-mode
|
|
11540
|
+
* ids, per selected runner — emitted on EVERY turn, before any plan exists
|
|
11541
|
+
* or after. The system prompt shows this once at conversation start; a long
|
|
11542
|
+
* grill-me interview or PRD negotiation outlives that single showing and the
|
|
11543
|
+
* planner starts misquoting it, so this re-states it per turn instead.
|
|
11544
|
+
*
|
|
11545
|
+
* Built from the allowlist-filtered view (`filterModelsForPrompt`), the same
|
|
11546
|
+
* one the system prompt used — a restricted allowlist stays a hard bound on
|
|
11547
|
+
* every turn, not just the first. `this.modelsCache` itself is never
|
|
11548
|
+
* filtered: `coerceAssignments` needs the full discovered catalog alongside
|
|
11549
|
+
* the allowlist to resolve labels and clamp effort to real variants.
|
|
11550
|
+
*
|
|
11551
|
+
* Reads `this.plan.runners` live, so a runner admitted mid-session by a
|
|
11552
|
+
* retarget (`admitRunner`) is filtered and shown like every other — there is
|
|
11553
|
+
* no separate "originally selected" list to fall stale.
|
|
11554
|
+
*/
|
|
11555
|
+
catalogBlock() {
|
|
11556
|
+
if (!this.plan) return null;
|
|
11557
|
+
const allowlist = this.settingsFn().modelAllowlist ?? this.currentAllowlist;
|
|
11558
|
+
const filteredModels = filterModelsForPrompt(this.modelsCache, allowlist);
|
|
11559
|
+
const runnerModes = this.runnerModesFor(this.plan.runners);
|
|
11560
|
+
const autonomousDefault = this.config.autonomousMode;
|
|
11561
|
+
const modelLines = this.plan.runners.map((runner) => {
|
|
11562
|
+
const models = filteredModels[runner] ?? [];
|
|
11563
|
+
const capped = models.slice(0, CATALOG_MODEL_CAP);
|
|
11564
|
+
const remainder = models.length - capped.length;
|
|
11565
|
+
const ids = capped.map((m) => m.modelId).join(", ") || "(no models discovered)";
|
|
11566
|
+
return `${runner}: ${ids}${remainder > 0 ? ` \u2026 +${remainder} more not shown` : ""}`;
|
|
11567
|
+
});
|
|
11568
|
+
const modeLines = this.plan.runners.map((runner) => {
|
|
11569
|
+
const modes = runnerModes[runner] ?? [];
|
|
11570
|
+
if (modes.length === 0) return `${runner}: (no modes declared)`;
|
|
11571
|
+
const defaultId = resolveDefaultMode(modes, autonomousDefault);
|
|
11572
|
+
const ids = modes.map((m) => `${m.id}${m.id === defaultId ? " (default)" : ""}`).join(", ");
|
|
11573
|
+
return `${runner}: ${ids}`;
|
|
11574
|
+
});
|
|
11575
|
+
return [
|
|
11576
|
+
"<available_models>",
|
|
11577
|
+
...modelLines,
|
|
11578
|
+
"</available_models>",
|
|
11579
|
+
"<available_task_modes>",
|
|
11580
|
+
...modeLines,
|
|
11581
|
+
"</available_task_modes>"
|
|
11582
|
+
].join("\n");
|
|
11583
|
+
}
|
|
9822
11584
|
/**
|
|
9823
11585
|
* The "you are here" block for post-plan chat: current tasks with stable
|
|
9824
11586
|
* references, plus the task-ops protocol. Injected per turn (never
|
|
@@ -9828,44 +11590,48 @@ The plan is unchanged. Rephrase the request, or adjust the tasks manually.`,
|
|
|
9828
11590
|
planContextBlock() {
|
|
9829
11591
|
if (!this.plan || this.store.planTasks.length === 0) return null;
|
|
9830
11592
|
const orderOf = new Map(this.store.planTasks.map((t) => [t.id, `#${t.order}`]));
|
|
9831
|
-
const lines = this.store.planTasks.map(
|
|
9832
|
-
|
|
9833
|
-
|
|
11593
|
+
const lines = this.store.planTasks.map((t) => {
|
|
11594
|
+
const isMan = t.type === "user";
|
|
11595
|
+
const runFields = isMan ? `steps:${t.userSteps?.length ?? 0}` : `${t.assignedModel ? `model:${t.assignedModel.modelId} ` : ""}mode:${t.taskMode ?? "build"} effort:${t.thinkingEffort ?? "-"}`;
|
|
11596
|
+
return `#${t.order} id=${t.id} "${t.title}" [${t.status}] type:${isMan ? "MAN" : "AI"} runner:${t.assignedRunner} ${runFields} autonomy:${t.autonomy ?? "-"} slice:${t.sliceType ?? "-"} deps:[${t.dependencies.map((d) => orderOf.get(d) ?? d).join(", ")}]`;
|
|
11597
|
+
});
|
|
9834
11598
|
const locked = this.store.planTasks.filter((t) => t.status === "in_progress");
|
|
9835
11599
|
const execNote = this.hasLiveWork ? `
|
|
9836
11600
|
Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.map((t) => `#${t.order}`).join(", ")}` : ""}. Any task edits you emit will be queued and applied between batches.` : "";
|
|
9837
|
-
const
|
|
9838
|
-
for (const runner of this.plan.runners) {
|
|
9839
|
-
const models = this.modelsCache[runner] ?? [];
|
|
9840
|
-
const ids = models.map((m) => m.modelId).join(", ");
|
|
9841
|
-
modelLines.push(`${runner}: ${ids || "(no models discovered)"}`);
|
|
9842
|
-
}
|
|
9843
|
-
const modelBlock = modelLines.length > 0 ? ["<available_models>", ...modelLines, "</available_models>"].join("\n") : "";
|
|
11601
|
+
const updateChanges = UPDATABLE_FIELDS.map((f) => f === "dependencies" ? '"dependencies"?:["<id or #order>"]' : `"${f}"?`).join(",");
|
|
9844
11602
|
return [
|
|
9845
11603
|
"<current_plan>",
|
|
9846
11604
|
...lines,
|
|
9847
11605
|
"</current_plan>",
|
|
9848
|
-
|
|
9849
|
-
"The block above is the CURRENT task plan. Choose how to respond:",
|
|
11606
|
+
"The block above is the CURRENT task plan \u2014 short fields only. Choose how to respond:",
|
|
9850
11607
|
"- To answer a question or discuss, reply in plain prose (no JSON).",
|
|
11608
|
+
// The reminder text is owned by TaskQuery beside the parser, so the
|
|
11609
|
+
// protocol the planner reads and the one Ordewell accepts stay one thing.
|
|
11610
|
+
TASK_QUERY_REMINDER,
|
|
9851
11611
|
"- To modify specific tasks, reply with ONLY this JSON object:",
|
|
9852
11612
|
' {"taskOps": [',
|
|
9853
|
-
|
|
9854
|
-
' {"op":"add","task":{"title","description","prompt","dependencies":["<id or #order>"],"assignedRunner"?,"assignedModel"?}},',
|
|
11613
|
+
` {"op":"update","taskId":"<id or #order>","changes":{${updateChanges}}},`,
|
|
11614
|
+
' {"op":"add","task":{"title","description","prompt","dependencies":["<id or #order>"],"assignedRunner"?,"assignedModel"?},"handle"?:"<name>"},',
|
|
9855
11615
|
' {"op":"remove","taskId":"<id or #order>"},',
|
|
9856
|
-
' {"op":"reorder","taskIds":["<id or #order>", "... every task exactly once"]},',
|
|
9857
|
-
' {"op":"merge","taskIds":["<id or #order>", "..."],"merged":{"title","description","prompt"?,"assignedRunner"?,"assignedModel"?,...}},',
|
|
9858
|
-
' {"op":"split","taskId":"<id or #order>","parts":[{"title","description","prompt"?,"assignedRunner"?,"assignedModel"?,...}, ...]}',
|
|
11616
|
+
' {"op":"reorder","taskIds":["<id or #order>", "... every task exactly once"]}, // only to re-prioritise INDEPENDENT tasks',
|
|
11617
|
+
' {"op":"merge","taskIds":["<id or #order>", "..."],"merged":{"title","description","prompt"?,"assignedRunner"?,"assignedModel"?,...},"handle"?:"<name>"},',
|
|
11618
|
+
' {"op":"split","taskId":"<id or #order>","parts":[{"title","description","prompt"?,"assignedRunner"?,"assignedModel"?,...}, ...],"handle"?:"<name>"},',
|
|
11619
|
+
` {"op":"rearm","taskId":"<id or #order>","changes"?:{${updateChanges}}}`,
|
|
9859
11620
|
" ]}",
|
|
9860
11621
|
'- For sweeping changes, you may instead emit a full {"tasks":[...]} plan JSON.',
|
|
11622
|
+
'Every "<id or #order>" ref in a batch resolves against the plan shown above, before any op in the batch runs \u2014 an earlier remove/merge/split never shifts what a later "#N" means.',
|
|
11623
|
+
'Give "add", "merge", or "split" a "handle" (any name you choose, unused elsewhere in this batch) to let a LATER op in the same batch reference the task it produces \u2014 for "split", the handle names its last part. A handle used before its defining op is rejected.',
|
|
9861
11624
|
'When creating or changing tasks, set "assignedRunner" and "assignedModel" ({"modelId","modelLabel","thinkingEffort"?}) using only the runners and models listed in <available_models>.',
|
|
9862
|
-
|
|
11625
|
+
'Keep dependencies consistent: no cycles, no references to removed tasks, and never touch running or completed tasks \u2014 "rearm" is the one exception, below.',
|
|
11626
|
+
'Just declare the dependencies you want \u2014 display order is repaired for you afterwards, so a rewire or a newly added prerequisite never needs a "reorder" op. Only a task that is running or completed cannot be shifted, so an edit that would need one to move is rejected.' + execNote,
|
|
11627
|
+
'Flipping "type" between "ai" and "user" is a content change, not just a label: an update to "user" needs "userSteps" in the SAME op, and an update to "ai" needs "prompt" in the SAME op \u2014 the model/mode/effort/autonomy fields (flipping to "user") or the userSteps (flipping to "ai") are cleared automatically.',
|
|
11628
|
+
'"rearm" puts a failed OR completed task back to pending \u2014 verdict and output summary are cleared, any dependents it had blocked are released, and it may carry field changes (e.g. a corrected "prompt") applied in the same op. A running task cannot be re-armed. Never rearm a task just to relabel it \u2014 only when it should actually run again.'
|
|
9863
11629
|
].filter(Boolean).join("\n");
|
|
9864
11630
|
}
|
|
9865
11631
|
/** Validate + commit a task_ops turn atomically. Returns the errors on rejection (plan untouched). */
|
|
9866
11632
|
applyTaskOpsTurn(turn) {
|
|
9867
11633
|
if (!this.plan) throw new Error("No active plan state");
|
|
9868
|
-
const result = applyTaskOps(this.store.planTasks, turn.ops, this.plan.runners);
|
|
11634
|
+
const result = applyTaskOps(this.store.planTasks, turn.ops, this.plan.runners, this.editCatalog());
|
|
9869
11635
|
if (!result.ok) return { errors: result.errors };
|
|
9870
11636
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9871
11637
|
const content = `Tasks updated:
|
|
@@ -10132,15 +11898,22 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
|
|
|
10132
11898
|
return plan;
|
|
10133
11899
|
}
|
|
10134
11900
|
/**
|
|
10135
|
-
* Patch one task's fields. A hand-set dependency list
|
|
10136
|
-
*
|
|
10137
|
-
*
|
|
10138
|
-
*
|
|
11901
|
+
* Patch one task's fields. A hand-set dependency list, or a type flip
|
|
11902
|
+
* between AI and MAN, are the patches that can leave a task incoherent
|
|
11903
|
+
* (unschedulable, or carrying fields that mean nothing for its new type),
|
|
11904
|
+
* so both go through the same {@link validateTaskEdit} guard the planner's
|
|
11905
|
+
* task-ops applier uses (as the 'direct' actor, which skips the lock rule)
|
|
11906
|
+
* rather than a second copy of the rules — and throws, so the surface can
|
|
11907
|
+
* say why. Gated on the task existing so an edit to an unknown id still
|
|
11908
|
+
* falls through to the no-op `store.update` below instead of throwing.
|
|
10139
11909
|
*/
|
|
10140
11910
|
async updateTask(taskId, changes) {
|
|
10141
|
-
if (changes.dependencies) {
|
|
10142
|
-
const check =
|
|
10143
|
-
if (!check.ok) throw new PlanEditError(check.error ?? "Those
|
|
11911
|
+
if ((changes.dependencies || changes.type || changes.assignedModel || changes.taskMode) && this.store.get(taskId)) {
|
|
11912
|
+
const check = validateTaskEdit("direct", this.store.planTasks, taskId, changes, this.editCatalog());
|
|
11913
|
+
if (!check.ok) throw new PlanEditError(check.error ?? "Those changes are not valid");
|
|
11914
|
+
if (check.clear?.length) {
|
|
11915
|
+
changes = { ...changes, ...Object.fromEntries(check.clear.map((f) => [f, void 0])) };
|
|
11916
|
+
}
|
|
10144
11917
|
}
|
|
10145
11918
|
return this.editPlan(
|
|
10146
11919
|
() => Boolean(this.store.update(taskId, changes)),
|
|
@@ -10781,7 +12554,7 @@ import { execFile as execFile2 } from "child_process";
|
|
|
10781
12554
|
import { promisify as promisify4 } from "util";
|
|
10782
12555
|
import { tmpdir } from "os";
|
|
10783
12556
|
import { join as join10 } from "path";
|
|
10784
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync as
|
|
12557
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
10785
12558
|
|
|
10786
12559
|
// src/utils/tmux.ts
|
|
10787
12560
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -10872,7 +12645,7 @@ var TmuxSession = class extends AbstractTerminalSession {
|
|
|
10872
12645
|
* lets the user keep poking at a finished task's terminal.
|
|
10873
12646
|
*/
|
|
10874
12647
|
async start(command, args, cwd, env) {
|
|
10875
|
-
|
|
12648
|
+
writeFileSync6(this.logPath, "");
|
|
10876
12649
|
const envAssignments = Object.entries(env ?? {}).map(([k, v]) => `${k}=${posixShellQuote(v)}`).join(" ");
|
|
10877
12650
|
const inner = [command, ...args].map(posixShellQuote).join(" ");
|
|
10878
12651
|
const prefix = envAssignments ? `env ${envAssignments} ` : "";
|
|
@@ -12175,6 +13948,11 @@ export {
|
|
|
12175
13948
|
SettingsService,
|
|
12176
13949
|
StdioAgentAdapter,
|
|
12177
13950
|
TASK_OPS_ENVELOPE_KEY,
|
|
13951
|
+
TASK_QUERY_ANSWER_OR_OPS,
|
|
13952
|
+
TASK_QUERY_ENVELOPE_KEY,
|
|
13953
|
+
TASK_QUERY_FIELDS,
|
|
13954
|
+
TASK_QUERY_PROTOCOL,
|
|
13955
|
+
TASK_QUERY_REMINDER,
|
|
12178
13956
|
TRUNCATED_PLAN_REPAIR_INSTRUCTION,
|
|
12179
13957
|
TaskOrchestrator,
|
|
12180
13958
|
TmuxRunner,
|
|
@@ -12245,6 +14023,7 @@ export {
|
|
|
12245
14023
|
emptyWarnings,
|
|
12246
14024
|
enabledRunners,
|
|
12247
14025
|
ensureDir,
|
|
14026
|
+
ensureStateDirIgnored,
|
|
12248
14027
|
escapeControlCharsInStrings,
|
|
12249
14028
|
executionLogBlock,
|
|
12250
14029
|
executionSummary,
|
|
@@ -12294,6 +14073,7 @@ export {
|
|
|
12294
14073
|
parsePartialPlan,
|
|
12295
14074
|
parsePlanJson,
|
|
12296
14075
|
parseTaskOpsJson,
|
|
14076
|
+
parseTaskQueryJson,
|
|
12297
14077
|
pendingEditRulesBlock,
|
|
12298
14078
|
planDirectLaunch,
|
|
12299
14079
|
planShellLaunch,
|
|
@@ -12302,11 +14082,13 @@ export {
|
|
|
12302
14082
|
providerForRunner,
|
|
12303
14083
|
reEmitPlanPrompt,
|
|
12304
14084
|
reEmitTaskOpsPrompt,
|
|
14085
|
+
reEmitTaskQueryPrompt,
|
|
12305
14086
|
readDaemonToken,
|
|
12306
14087
|
referencePattern,
|
|
12307
14088
|
removeTaskFromPlan,
|
|
12308
14089
|
renderPlanMap,
|
|
12309
14090
|
renderPriorOutputs,
|
|
14091
|
+
renderTaskQueryAnswer,
|
|
12310
14092
|
renumberTasks,
|
|
12311
14093
|
repairLoop,
|
|
12312
14094
|
researchShellWarning,
|
|
@@ -12340,7 +14122,9 @@ export {
|
|
|
12340
14122
|
summarizeOutput,
|
|
12341
14123
|
summarizeToolCall,
|
|
12342
14124
|
taskOpsRejectedPrompt,
|
|
14125
|
+
taskQuerySignature,
|
|
12343
14126
|
textHasTaskOps,
|
|
14127
|
+
textHasTaskQuery,
|
|
12344
14128
|
tmuxSessionName,
|
|
12345
14129
|
tmuxSocketName,
|
|
12346
14130
|
tmuxWindowName,
|