@sema-agent/core 7.10.0 → 7.11.1
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/CHANGELOG.md +90 -0
- package/dist/agents/child-model-seat.d.ts +45 -18
- package/dist/agents/child-model-seat.js +12 -8
- package/dist/agents/subagent.js +6 -5
- package/dist/agents/teacher.js +2 -2
- package/dist/core/auto-mode-defaults.d.ts +19 -3
- package/dist/core/auto-mode-defaults.js +1 -0
- package/dist/core/auto-mode.d.ts +24 -20
- package/dist/core/auto-mode.js +12 -12
- package/dist/core/gate-fold.js +1 -0
- package/dist/core/gate-lanes.d.ts +6 -1
- package/dist/core/gate-lanes.js +45 -18
- package/dist/core/governance-codes.d.ts +1 -0
- package/dist/core/governance-codes.js +1 -0
- package/dist/core/hooks.d.ts +13 -0
- package/dist/core/permission-rule-model.d.ts +5 -3
- package/dist/core/permission-rule-model.js +7 -3
- package/dist/core/persisted-rule-arms.js +4 -3
- package/dist/core/read-only-shell-table.d.ts +87 -0
- package/dist/core/read-only-shell-table.js +485 -0
- package/dist/core/read-only-shell.d.ts +42 -0
- package/dist/core/read-only-shell.js +316 -0
- package/dist/core/roles.d.ts +3 -2
- package/dist/core/runner/contracts.d.ts +70 -0
- package/dist/core/runner/gate-exit.d.ts +5 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +38 -12
- package/dist/core/runner/prepare-gate-stations.js +9 -0
- package/dist/core/runner/prepare-task.js +1 -1
- package/dist/core/runner/prepare-turn-wiring.js +1 -1
- package/dist/core/runner/runtask.js +34 -510
- package/dist/core/runner/stream-halt-verbs.d.ts +38 -0
- package/dist/core/runner/stream-halt-verbs.js +82 -0
- package/dist/core/runner/stream-lifecycle-verbs.d.ts +34 -0
- package/dist/core/runner/stream-lifecycle-verbs.js +126 -0
- package/dist/core/runner/stream-reap.d.ts +30 -0
- package/dist/core/runner/stream-reap.js +40 -0
- package/dist/core/runner/stream-settle-backstop.d.ts +38 -0
- package/dist/core/runner/stream-settle-backstop.js +113 -0
- package/dist/core/runner/stream-steer-verb.d.ts +30 -0
- package/dist/core/runner/stream-steer-verb.js +185 -0
- package/dist/core/shell-lexer.d.ts +18 -0
- package/dist/core/shell-lexer.js +17 -10
- package/dist/core/shell-wrapper-table.js +8 -5
- package/dist/core/tool-policy.d.ts +4 -1
- package/dist/core/tool-policy.js +1 -1
- package/dist/core/tools.d.ts +28 -7
- package/dist/core/tools.js +44 -4
- package/dist/core/trace.d.ts +15 -0
- package/dist/engine/harness/agent-harness.d.ts +3 -1
- package/dist/engine/harness/agent-harness.js +1 -1
- package/dist/engine/harness/types.d.ts +4 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/orchestration/run-workflow-tool.d.ts +5 -2
- package/dist/orchestration/run-workflow-tool.js +2 -1
- package/dist/orchestration/workflow-governance.d.ts +3 -2
- package/dist/orchestration/workflow-primitives.d.ts +4 -1
- package/dist/orchestration/workflow-primitives.js +1 -6
- package/dist/orchestration/workflow.d.ts +12 -4
- package/dist/orchestration/workflow.js +24 -7
- package/dist/prompt-assembly/turn-snapshot.d.ts +4 -2
- package/dist/tools/fs/bash-readonly-classifier.d.ts +5 -0
- package/dist/tools/fs/bash-readonly-classifier.js +1 -0
- package/dist/tools/fs/fs-bash.js +3 -2
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +35 -1
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { readShellCommand } from "./shell-lexer.js";
|
|
2
|
+
import { FIND_ACTION_PRIMARIES, FIND_NEWER_PRIMARY, FIND_VALUE_PRIMARIES, FLAG_VALUE_ACCEPTS, READ_ONLY_BARE_ONLY, READ_ONLY_BARE_PROGRAMS, READ_ONLY_COMMAND_TABLE, READ_ONLY_ENV_NAMES, READ_ONLY_EXACT_FORMS, READ_ONLY_GLOB_PROGRAMS, XARGS_READ_ONLY_TARGETS, dockerRetargetDanger, } from "./read-only-shell-table.js";
|
|
3
|
+
const NOT = (reason) => ({ readOnly: false, reason });
|
|
4
|
+
const READ_ONLY = { readOnly: true };
|
|
5
|
+
const BARE_PROGRAMS = new Set(READ_ONLY_BARE_PROGRAMS.filter((p) => !p.includes(" ")));
|
|
6
|
+
const BARE_MULTIWORD = READ_ONLY_BARE_PROGRAMS.filter((p) => p.includes(" ")).map((p) => p.split(" "));
|
|
7
|
+
const GLOB_PROGRAMS = new Set(READ_ONLY_GLOB_PROGRAMS);
|
|
8
|
+
const BARE_ONLY = new Set(READ_ONLY_BARE_ONLY);
|
|
9
|
+
const ENV_NAMES = new Set(READ_ONLY_ENV_NAMES);
|
|
10
|
+
const FIND_ACTIONS = new Set(FIND_ACTION_PRIMARIES);
|
|
11
|
+
const FIND_VALUES = new Set(FIND_VALUE_PRIMARIES);
|
|
12
|
+
const XARGS_TARGETS = XARGS_READ_ONLY_TARGETS;
|
|
13
|
+
const TABLE_ROWS = Object.entries(READ_ONLY_COMMAND_TABLE)
|
|
14
|
+
.map(([key, row]) => ({ words: key.split(" "), row }))
|
|
15
|
+
.sort((a, b) => b.words.length - a.words.length);
|
|
16
|
+
const READING_REDIRECTS = new Set(["<", "<<", "<<-", "<&", "<<<"]);
|
|
17
|
+
const LITERAL_ASSIGNMENT = /^([A-Za-z_][A-Za-z0-9_]*)\+?=(?:"[^"$`\\]*"|'[^']*'|[A-Za-z0-9_./:+-]*)$/;
|
|
18
|
+
const ASSIGNMENT_NAME = /^([A-Za-z_][A-Za-z0-9_]*)(?:\[[^\]]*\])?\+?=/;
|
|
19
|
+
const FLAG_WORD = /^-[a-zA-Z0-9_-]/;
|
|
20
|
+
const NUMBER_LITERAL = /^[-+]?(0[xX][0-9a-fA-F]+|[0-9]+#[0-9a-zA-Z]+|[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)$/;
|
|
21
|
+
const PRINTF_LENGTH = "[lLhqjzZt]*";
|
|
22
|
+
const PRINTF_ESCAPE = new RegExp(`%[^%a-zA-Z]*${PRINTF_LENGTH}\\\\[0-7xX]`);
|
|
23
|
+
const PRINTF_NUMERIC = new RegExp(`%[-+ 0#']*[0-9.*]*${PRINTF_LENGTH}[diouxXeEfFgGaAn]`);
|
|
24
|
+
const JQ_DANGER_FLAG = /^['"]?-[a-zA-Z]*[fL]/;
|
|
25
|
+
const JQ_DANGER_TEXT = /--from-file|--rawfile|--slurpfile|--run-tests|--library-path|\benv\b|\$ENV\b|\binclude\b|\bimport\b/;
|
|
26
|
+
export function readOnlyShellVerdict(command, shape = readShellCommand(command)) {
|
|
27
|
+
if (shape.grouped)
|
|
28
|
+
return NOT("a subshell, group or control structure — not a flat list of simple commands");
|
|
29
|
+
if (shape.segments.length === 0)
|
|
30
|
+
return NOT("no command");
|
|
31
|
+
if (shape.backgrounded)
|
|
32
|
+
return NOT("a background `&` defers execution past the approval-time reading");
|
|
33
|
+
if (shape.strayRedirection)
|
|
34
|
+
return NOT("a redirection on a piece that runs no program — the shell still opens its target");
|
|
35
|
+
for (const seg of shape.segments) {
|
|
36
|
+
if (seg.unreadable !== undefined)
|
|
37
|
+
return NOT(seg.unreadable);
|
|
38
|
+
if (seg.peelUnreadable !== undefined)
|
|
39
|
+
return NOT(seg.peelUnreadable);
|
|
40
|
+
for (const w of [...seg.argv, ...seg.redirections.map((r) => r.target)]) {
|
|
41
|
+
if (expansionKind(w) === "variable")
|
|
42
|
+
return NOT("a variable, arithmetic or command substitution — its value is not known at approval time");
|
|
43
|
+
if (w.expands !== false && /[()]/.test(w.raw))
|
|
44
|
+
return NOT("a pattern with a parenthesised part (an extended pattern or a shell-specific glob qualifier)");
|
|
45
|
+
if (/^=[A-Za-z_]/.test(w.raw) || w.raw.includes("~["))
|
|
46
|
+
return NOT("a shell-specific expansion spelling (`=cmd`, `~[…]`)");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const programs = shape.segments.map((s) => peelBuiltinPrefix(s.argv.slice(leadingAssignmentCount(s.argv)).map((w) => w.text))[0] ?? "");
|
|
50
|
+
if (shape.segments.length > 1 && programs.includes("cd") && programs.includes("git"))
|
|
51
|
+
return NOT("a compound that changes directory and runs git");
|
|
52
|
+
for (const seg of shape.segments) {
|
|
53
|
+
const verdict = segmentVerdict(seg);
|
|
54
|
+
if (!verdict.readOnly)
|
|
55
|
+
return verdict;
|
|
56
|
+
}
|
|
57
|
+
return READ_ONLY;
|
|
58
|
+
}
|
|
59
|
+
function expansionKind(w) {
|
|
60
|
+
if (w.expands === false)
|
|
61
|
+
return "none";
|
|
62
|
+
if (/[$`]|[<>]\(/.test(w.raw))
|
|
63
|
+
return "variable";
|
|
64
|
+
if (w.expands === "many")
|
|
65
|
+
return "glob";
|
|
66
|
+
return "none";
|
|
67
|
+
}
|
|
68
|
+
function leadingAssignmentCount(argv) {
|
|
69
|
+
let k = 0;
|
|
70
|
+
while (k < argv.length && ASSIGNMENT_NAME.test(argv[k].raw))
|
|
71
|
+
k++;
|
|
72
|
+
return k;
|
|
73
|
+
}
|
|
74
|
+
function peelBuiltinPrefix(words) {
|
|
75
|
+
let t = words;
|
|
76
|
+
for (;;) {
|
|
77
|
+
if (t[0] === "command") {
|
|
78
|
+
let r = 1;
|
|
79
|
+
while (t[r] !== undefined && /^-p+$/.test(t[r]))
|
|
80
|
+
r++;
|
|
81
|
+
if (t[r] === "--")
|
|
82
|
+
r++;
|
|
83
|
+
if (r >= t.length || t[r].startsWith("-"))
|
|
84
|
+
return t;
|
|
85
|
+
t = t.slice(r);
|
|
86
|
+
}
|
|
87
|
+
else if (t[0] === "builtin") {
|
|
88
|
+
const r = t[1] === "--" ? 2 : 1;
|
|
89
|
+
if (r >= t.length)
|
|
90
|
+
return t;
|
|
91
|
+
t = t.slice(r);
|
|
92
|
+
}
|
|
93
|
+
else if (t[0] === "noglob") {
|
|
94
|
+
if (t.length <= 1)
|
|
95
|
+
return t;
|
|
96
|
+
t = t.slice(1);
|
|
97
|
+
}
|
|
98
|
+
else
|
|
99
|
+
return t;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function segmentVerdict(seg) {
|
|
103
|
+
for (const r of seg.redirections) {
|
|
104
|
+
const target = r.target.text;
|
|
105
|
+
if (!READING_REDIRECTS.has(r.op) && target !== "/dev/null" && !(r.op === ">&" && /^\d+$/.test(target)))
|
|
106
|
+
return NOT(`the redirection \`${r.op} ${target}\` writes`);
|
|
107
|
+
if (/^\/dev\/(tcp|udp)\//.test(target))
|
|
108
|
+
return NOT("a redirection to a network pseudo-device");
|
|
109
|
+
}
|
|
110
|
+
const k = leadingAssignmentCount(seg.argv);
|
|
111
|
+
for (const w of seg.argv.slice(0, k)) {
|
|
112
|
+
const m = LITERAL_ASSIGNMENT.exec(w.raw);
|
|
113
|
+
if (m === null)
|
|
114
|
+
return NOT(`the assignment \`${w.raw}\` is not a literal value`);
|
|
115
|
+
if (!ENV_NAMES.has(m[1]))
|
|
116
|
+
return NOT(`the environment variable \`${m[1]}\` can alter what a program does`);
|
|
117
|
+
}
|
|
118
|
+
const run = seg.argv.slice(k);
|
|
119
|
+
if (run.length === 0)
|
|
120
|
+
return NOT("a bare assignment runs no program");
|
|
121
|
+
const words = peelBuiltinPrefix(run.map((w) => w.text));
|
|
122
|
+
const program = words[0];
|
|
123
|
+
if (run.some((w) => expansionKind(w) === "glob"))
|
|
124
|
+
return GLOB_PROGRAMS.has(program) ? READ_ONLY : NOT(`\`${program}\` with a glob argument — the glob may name anything`);
|
|
125
|
+
const structural = structuralReading(words);
|
|
126
|
+
if (structural !== null)
|
|
127
|
+
return structural ? READ_ONLY : NOT(`\`${program}\` with these arguments is not a read-only form`);
|
|
128
|
+
return tableReading(words, run);
|
|
129
|
+
}
|
|
130
|
+
function structuralReading(words) {
|
|
131
|
+
const program = words[0];
|
|
132
|
+
if (BARE_ONLY.has(program))
|
|
133
|
+
return words.length === 1;
|
|
134
|
+
for (const form of READ_ONLY_EXACT_FORMS)
|
|
135
|
+
if (words.length === form.length && words.every((w, i) => w === form[i]))
|
|
136
|
+
return true;
|
|
137
|
+
if (BARE_PROGRAMS.has(program))
|
|
138
|
+
return true;
|
|
139
|
+
for (const form of BARE_MULTIWORD)
|
|
140
|
+
if (words.length >= form.length && form.every((w, i) => words[i] === w))
|
|
141
|
+
return form[0] !== "docker" || !dockerRetargetDanger(words);
|
|
142
|
+
if (program === "echo")
|
|
143
|
+
return true;
|
|
144
|
+
if (program === "printf")
|
|
145
|
+
return printfReading(words);
|
|
146
|
+
if (program === "ls")
|
|
147
|
+
return true;
|
|
148
|
+
if (program === "cd")
|
|
149
|
+
return words.length <= 2;
|
|
150
|
+
if (program === "find") {
|
|
151
|
+
for (let i = 1; i < words.length; i++) {
|
|
152
|
+
const w = words[i];
|
|
153
|
+
if (FIND_ACTIONS.has(w))
|
|
154
|
+
return false;
|
|
155
|
+
if (FIND_VALUES.has(w) || FIND_NEWER_PRIMARY.test(w)) {
|
|
156
|
+
i++;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
if (program === "history")
|
|
163
|
+
return words.length === 1 || (words.length === 2 && /^\d+$/.test(words[1]));
|
|
164
|
+
if (program === "arch")
|
|
165
|
+
return words.length === 1 || (words.length === 2 && (words[1] === "-h" || words[1] === "--help"));
|
|
166
|
+
if (program === "ifconfig")
|
|
167
|
+
return words.length === 1 || (words.length === 2 && /^[a-zA-Z]/.test(words[1]));
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
function printfReading(words) {
|
|
171
|
+
if (words[1]?.startsWith("-") === true && words[1] !== "--")
|
|
172
|
+
return false;
|
|
173
|
+
const at = words[1] === "--" ? 2 : 1;
|
|
174
|
+
const format = words[at] ?? "";
|
|
175
|
+
if (format.includes("$"))
|
|
176
|
+
return false;
|
|
177
|
+
const stripped = format.replace(/%%/g, "");
|
|
178
|
+
if (PRINTF_ESCAPE.test(stripped) || /\\[uU]/.test(stripped))
|
|
179
|
+
return false;
|
|
180
|
+
if (PRINTF_NUMERIC.test(stripped) || /%[^%a-zA-Z]*\*/.test(stripped)) {
|
|
181
|
+
for (let i = at + 1; i < words.length; i++) {
|
|
182
|
+
const operand = words[i];
|
|
183
|
+
if (operand.includes("[") || operand.includes("`") || operand.includes("$(") || !NUMBER_LITERAL.test(operand))
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
function tableReading(words, run) {
|
|
190
|
+
const program = words[0];
|
|
191
|
+
const hit = TABLE_ROWS.find((r) => words.length >= r.words.length && r.words.every((w, i) => words[i] === w));
|
|
192
|
+
if (hit !== undefined) {
|
|
193
|
+
const args = words.slice(hit.words.length);
|
|
194
|
+
for (const a of args) {
|
|
195
|
+
if (a.includes("$"))
|
|
196
|
+
return NOT(`\`${program}\`: an argument spells \`$\``);
|
|
197
|
+
if (a.includes("{") && (a.includes(",") || a.includes("..")))
|
|
198
|
+
return NOT(`\`${program}\`: a brace pattern argument`);
|
|
199
|
+
}
|
|
200
|
+
if (!flagWalk(args, hit.row, program))
|
|
201
|
+
return NOT(`\`${program}\`: an option outside its read-only set (or a malformed option value)`);
|
|
202
|
+
if (hit.row.wordsShape !== undefined ? !hit.row.wordsShape(args) : run.some((w) => w.raw.includes("`")))
|
|
203
|
+
return NOT(`\`${program}\`: arguments outside its read-only form`);
|
|
204
|
+
if (hit.row.wordsShape === undefined && (program === "rg" || program === "grep" || program === "egrep" || program === "fgrep") && run.some((w) => /[\n\r]/.test(w.raw)))
|
|
205
|
+
return NOT(`\`${program}\`: a line break inside an argument`);
|
|
206
|
+
if (hit.row.dangerous?.(args) === true)
|
|
207
|
+
return NOT(`\`${program}\`: an argument form upstream marks as writing, executing or resolving`);
|
|
208
|
+
return READ_ONLY;
|
|
209
|
+
}
|
|
210
|
+
if (program === "uniq" && words.slice(1).every((w, i, all) => /^-[a-zA-Z]+$/.test(w) || /^--[a-zA-Z-]+(=\S+)?$/.test(w) || (i > 0 && /^-[fsw]$/.test(all[i - 1]) && /^\d+$/.test(w))))
|
|
211
|
+
return gitOptionGuard(words);
|
|
212
|
+
if (program === "jq" && jqReading(run.slice(run.length - words.length + 1)))
|
|
213
|
+
return gitOptionGuard(words);
|
|
214
|
+
return NOT(`\`${program}\` is not a read-only program (or not in a read-only form)`);
|
|
215
|
+
}
|
|
216
|
+
function gitOptionGuard(words) {
|
|
217
|
+
if (words.includes("git") && words.some((w) => /^-c(=|$)|^--exec-path(=|$)|^--config-env(=|$)/.test(w)))
|
|
218
|
+
return NOT("a git configuration override on the line");
|
|
219
|
+
return READ_ONLY;
|
|
220
|
+
}
|
|
221
|
+
function jqReading(args) {
|
|
222
|
+
if (args.some((w) => JQ_DANGER_FLAG.test(w.raw) || JQ_DANGER_TEXT.test(w.raw)))
|
|
223
|
+
return false;
|
|
224
|
+
let i = 0;
|
|
225
|
+
while (i < args.length && (/^-[a-zA-Z]+$/.test(args[i].raw) || /^--[a-zA-Z-]+(=\S+)?$/.test(args[i].raw)))
|
|
226
|
+
i++;
|
|
227
|
+
if (i >= args.length)
|
|
228
|
+
return false;
|
|
229
|
+
for (; i < args.length; i++) {
|
|
230
|
+
const w = args[i];
|
|
231
|
+
const quoted = /^'[^'`]*'$|^"[^"`]*"$/.test(w.raw);
|
|
232
|
+
if (!quoted && (/^[-'"\s]/.test(w.raw) || /['"]/.test(w.raw)))
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
function flagWalk(args, row, program) {
|
|
238
|
+
let i = 0;
|
|
239
|
+
while (i < args.length) {
|
|
240
|
+
let word = args[i];
|
|
241
|
+
if (word === "") {
|
|
242
|
+
i++;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (program === "xargs" && (!word.startsWith("-") || word === "--")) {
|
|
246
|
+
if (word === "--" && i + 1 < args.length) {
|
|
247
|
+
i++;
|
|
248
|
+
word = args[i];
|
|
249
|
+
}
|
|
250
|
+
return XARGS_TARGETS.includes(word);
|
|
251
|
+
}
|
|
252
|
+
if (word === "--") {
|
|
253
|
+
if (row.respectsDoubleDash !== false)
|
|
254
|
+
break;
|
|
255
|
+
i++;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (FLAG_WORD.test(word)) {
|
|
259
|
+
const eq = word.indexOf("=");
|
|
260
|
+
const flag = eq === -1 ? word : word.slice(0, eq);
|
|
261
|
+
const attached = eq === -1 ? undefined : word.slice(eq + 1);
|
|
262
|
+
const arity = row.safeFlags[flag];
|
|
263
|
+
if (arity === undefined) {
|
|
264
|
+
if (program === "git" && /^-\d+$/.test(flag)) {
|
|
265
|
+
i++;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if ((program === "grep" || program === "egrep" || program === "fgrep" || program === "rg") && !flag.startsWith("--") && flag.length > 2) {
|
|
269
|
+
const base = flag.slice(0, 2);
|
|
270
|
+
const rest = flag.slice(2);
|
|
271
|
+
const baseArity = row.safeFlags[base];
|
|
272
|
+
if (baseArity !== undefined && /^\d+$/.test(rest) && (baseArity === "number" || baseArity === "string")) {
|
|
273
|
+
if (FLAG_VALUE_ACCEPTS[baseArity](rest)) {
|
|
274
|
+
i++;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (!flag.startsWith("--") && flag.length > 2) {
|
|
281
|
+
for (let c = 1; c < flag.length; c++)
|
|
282
|
+
if (row.safeFlags[`-${flag[c]}`] !== "none")
|
|
283
|
+
return false;
|
|
284
|
+
i++;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
if (arity === "none") {
|
|
290
|
+
if (attached !== undefined)
|
|
291
|
+
return false;
|
|
292
|
+
i++;
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
let value;
|
|
296
|
+
if (attached !== undefined) {
|
|
297
|
+
value = attached;
|
|
298
|
+
i++;
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
const next = args[i + 1];
|
|
302
|
+
if (next === undefined || FLAG_WORD.test(next))
|
|
303
|
+
return false;
|
|
304
|
+
value = next;
|
|
305
|
+
i += 2;
|
|
306
|
+
}
|
|
307
|
+
if (arity === "string" && value.startsWith("-") && !(flag === "--sort" && program === "git" && /^-[a-zA-Z]/.test(value)))
|
|
308
|
+
return false;
|
|
309
|
+
if (!FLAG_VALUE_ACCEPTS[arity](value))
|
|
310
|
+
return false;
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
i++;
|
|
314
|
+
}
|
|
315
|
+
return true;
|
|
316
|
+
}
|
package/dist/core/roles.d.ts
CHANGED
|
@@ -104,8 +104,9 @@ export interface ResolvedRole {
|
|
|
104
104
|
* `modelRole` and NO run model: it runs on the deployment's roles alone — the boot default at the end of
|
|
105
105
|
* its chain — and never follows the model a run was switched to. Only a run's CHILDREN follow the parent:
|
|
106
106
|
* the delegation seat (`childModelSeat`) passes the parent's `Model` as `spec.model` with the role kept
|
|
107
|
-
* on `modelRole
|
|
108
|
-
*
|
|
107
|
+
* on `modelRole` — and keeps that role on an EXPLICIT-model seat too, so the explicit branch below reads
|
|
108
|
+
* the child's presets off the subagent role, not the default one. A side channel that must follow a run
|
|
109
|
+
* passes that run's model explicitly; nothing here infers it.
|
|
109
110
|
*/
|
|
110
111
|
export declare function resolveTaskModel(spec: {
|
|
111
112
|
model?: ModelRef;
|
|
@@ -2424,3 +2424,73 @@ export interface RunnerSelfSeat {
|
|
|
2424
2424
|
/** Streaming form of {@link RunnerSelfSeat.resume}: the pre-CAS guards and the CAS run first, then the live stream is returned. */
|
|
2425
2425
|
resumeStream(token: CheckpointToken, outcome: ResumeOutcome, taskConfig: ResumeTaskConfig, internals?: RunInternals): Promise<TaskStream>;
|
|
2426
2426
|
}
|
|
2427
|
+
/** The live-task handle `runLocked` publishes once the harness exists (design/47): the harness + abort
|
|
2428
|
+
* controller, the loop-liveness latch (`ended` flips when the single `harness.prompt` settles; `userInterrupted`
|
|
2429
|
+
* / `userHalted` are the interrupt and halt verbs' attribution seats), the run's reminder mark, its session and
|
|
2430
|
+
* engine-minted run id, and the hook bound + identity envelope the steer entrance screen runs under. */
|
|
2431
|
+
export interface LiveHandle {
|
|
2432
|
+
harness: AgentHarness;
|
|
2433
|
+
abortController: AbortController;
|
|
2434
|
+
loop: {
|
|
2435
|
+
ended: boolean;
|
|
2436
|
+
userInterrupted: boolean;
|
|
2437
|
+
userHalted: boolean;
|
|
2438
|
+
};
|
|
2439
|
+
reminderMark: string;
|
|
2440
|
+
sessionId: string;
|
|
2441
|
+
/** #499 — the run body's engine-minted run id, so the stream-layer verbs can name the run their disclosures are about. */
|
|
2442
|
+
runId: string;
|
|
2443
|
+
hookTimeoutMs: number;
|
|
2444
|
+
hookIdentity: HookInvocationIdentity;
|
|
2445
|
+
}
|
|
2446
|
+
/** The run body's backstop CARRIER (F-05/W8/件①/#327/#499/#281 r2-D2): the effective ids and the post-prepare
|
|
2447
|
+
* observations the run body publishes as they are minted, so the stream layer's failure backstop names the
|
|
2448
|
+
* same run, session and observations the frames the run body already emitted did. */
|
|
2449
|
+
export interface TaskIdRef {
|
|
2450
|
+
current?: string;
|
|
2451
|
+
sessionId?: string;
|
|
2452
|
+
runId?: string;
|
|
2453
|
+
effectiveMemoryScopes?: TaskResult["effectiveMemoryScopes"];
|
|
2454
|
+
effectiveReasoning?: TaskResult["effectiveReasoning"];
|
|
2455
|
+
delegationTerminalOwed?: HookInvocationIdentity;
|
|
2456
|
+
editedFiles?: () => TaskResult["editedFiles"];
|
|
2457
|
+
}
|
|
2458
|
+
/** design/99 MF-18 — the manual `/compact` request seat: the `requested` flag, the parked waiters (each with its
|
|
2459
|
+
* caller's cancel signal and per-call instructions), the run body's mooted-frame channel and the registration
|
|
2460
|
+
* gate the run-end backstop closes before its final drain. */
|
|
2461
|
+
export interface ManualCompactRef {
|
|
2462
|
+
requested: boolean;
|
|
2463
|
+
waiters: Array<{
|
|
2464
|
+
resolve: (outcome: CompactOutcome) => void;
|
|
2465
|
+
signal?: AbortSignal;
|
|
2466
|
+
instructions?: string;
|
|
2467
|
+
}>;
|
|
2468
|
+
emitMooted?: (reason: string) => void;
|
|
2469
|
+
closed?: boolean;
|
|
2470
|
+
}
|
|
2471
|
+
/** design/144 §2 — the `notify()` bridge: `runLocked` binds `inject` the moment the task-notification lane exists. */
|
|
2472
|
+
export interface NotifyRef {
|
|
2473
|
+
inject?: (n: TaskNotificationPayload, opts?: {
|
|
2474
|
+
priority?: SystemInjectionPriority;
|
|
2475
|
+
}) => void;
|
|
2476
|
+
}
|
|
2477
|
+
/** design/383 §2.1 — the capture opt-out flip verb's binding; bound only when the run mounted a memory session. */
|
|
2478
|
+
export interface CaptureOptOutRef {
|
|
2479
|
+
flip?: (reason?: string) => Promise<{
|
|
2480
|
+
outcome: "created" | "existed";
|
|
2481
|
+
}>;
|
|
2482
|
+
}
|
|
2483
|
+
/**
|
|
2484
|
+
* The stream's LIVE state as the verb lanes read it — a view over the driver's own locals (getters, and one
|
|
2485
|
+
* setter), never a copy: `resultValue` is written by the run body's `setResult` callback and by the failure
|
|
2486
|
+
* backstop, `handle` by the run body's `onReady`, `reapHandle` by its `onSuspend`; every lane reads each of them
|
|
2487
|
+
* at the instant of its own read, exactly as the one-function façade read the closure variables.
|
|
2488
|
+
*/
|
|
2489
|
+
export interface TaskStreamLiveSeat {
|
|
2490
|
+
/** The assembled result once the run settled (the run body's `setResult`, or the failure backstop's mint). */
|
|
2491
|
+
resultValue: TaskResult | undefined;
|
|
2492
|
+
/** The live-task handle once `runLocked` published it; `undefined` before, and forever when prepare threw. */
|
|
2493
|
+
readonly handle: LiveHandle | undefined;
|
|
2494
|
+
/** design/51 — what `destroy()` reaps when the run SUSPENDED; unset for every other terminal state. */
|
|
2495
|
+
readonly reapHandle: SuspendReap | undefined;
|
|
2496
|
+
}
|
|
@@ -218,6 +218,11 @@ export interface GatePass {
|
|
|
218
218
|
* touch. Recorded by the gate rather than read off `decisionReason` because that member is a policy's to
|
|
219
219
|
* compose (a policy stamping `"safety"` on its own ask must read as `policy`). Writer: the fold. */
|
|
220
220
|
tightenedBy: AskOriginFacts["tightened"];
|
|
221
|
+
/** borrowed-mutable — the reversibility probe was consulted on this pass and DID NOT ANSWER (it threw or
|
|
222
|
+
* timed out). The out-of-root mandate has no other source than the probe's answer, so an unanswered probe
|
|
223
|
+
* leaves the call's boundary UNKNOWN: the allow layer (a stored allow rule, the read-only reader) must
|
|
224
|
+
* not clear the surviving ask — a person confirms it. Writer: the fold. Reader: the lanes' mandate. */
|
|
225
|
+
probeUnanswered?: true;
|
|
221
226
|
/** borrowed-mutable — the rewrite remembered BEFORE a hook-ask promotion / tighten, re-applied on the
|
|
222
227
|
* approved path (a redaction is not cancelled because a person confirmed). Writer: the fold. Readers: the
|
|
223
228
|
* fold's probe and write-protection judges, the lanes, the allow exit. */
|
|
@@ -7,7 +7,7 @@ import { isSelfOrchestrationActive } from "../../orchestration/workflow-script-r
|
|
|
7
7
|
import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
|
|
8
8
|
import { thinkingOffExpressible } from "../../brain/reasoning.js";
|
|
9
9
|
import { autoModeArmingRecipeOf } from "../auto-mode-arming.js";
|
|
10
|
-
import { AUTO_MODE_CLASSIFIER_MAX_TOKENS } from "../auto-mode-defaults.js";
|
|
10
|
+
import { AUTO_MODE_CLASSIFIER_LOW_REASONING_BUDGET_TOKENS, AUTO_MODE_CLASSIFIER_MAX_TOKENS } from "../auto-mode-defaults.js";
|
|
11
11
|
import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from "../auto-mode-prompt.js";
|
|
12
12
|
import { createAutoModeDecider, createAutoModeDenialTracker } from "../auto-mode.js";
|
|
13
13
|
import { resolveCheckpointStore } from "../checkpoint-store.js";
|
|
@@ -17,9 +17,21 @@ import { resolveTaskModel } from "../roles.js";
|
|
|
17
17
|
import { brainToRuntime } from "../runtime.js";
|
|
18
18
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
19
19
|
import { emitTrace } from "../trace.js";
|
|
20
|
-
import { defineTool, isDefineToolProduct } from "../tools.js";
|
|
20
|
+
import { defineTool, isDefineToolProduct, rebindDefineToolCtx } from "../tools.js";
|
|
21
|
+
const MOUNT_REMEDY = "author it as a plain-object ToolSpec (own `name` and `execute`), hand the defineTool product itself, or wrap a product through the supported wrapper form (a fresh object re-stamped with `stampDefineToolBrand` whose rebind re-wraps the rebound product)";
|
|
21
22
|
import { derivedRouteFallsBack } from "./derived-route-fallback.js";
|
|
22
23
|
import { REPORT_FINDINGS_TOOL_NAME, createReportBlockedTool, createReportFindingsTool } from "./synthetic-tools.js";
|
|
24
|
+
function classifierReplyText(reply) {
|
|
25
|
+
return reply.content
|
|
26
|
+
.filter((c) => c.type === "text")
|
|
27
|
+
.map((c) => c.text)
|
|
28
|
+
.join("");
|
|
29
|
+
}
|
|
30
|
+
function classifierReplyEmptyAtCap(reply) {
|
|
31
|
+
if (classifierReplyText(reply) !== "")
|
|
32
|
+
return false;
|
|
33
|
+
return reply.stopReason === "length" || (reply.stopReason === "error" && reply.errorKind === "length_empty");
|
|
34
|
+
}
|
|
23
35
|
import { RosterBuilder, callerMountSource } from "../tool-roster.js";
|
|
24
36
|
function assembleParentCaptureState(o, i, ctl, ancestors) {
|
|
25
37
|
const build = (optedOut, indeterminate) => ({
|
|
@@ -71,9 +83,21 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
71
83
|
const mounted = mountCallerTool(t);
|
|
72
84
|
roster.mount(mounted, { source: callerMountSource(mounted), mountedBy: "scenario" });
|
|
73
85
|
}
|
|
86
|
+
function specArmCanConstruct(t) {
|
|
87
|
+
if (typeof t !== "object" || t === null)
|
|
88
|
+
return false;
|
|
89
|
+
const name = Object.getOwnPropertyDescriptor(t, "name");
|
|
90
|
+
const execute = Object.getOwnPropertyDescriptor(t, "execute");
|
|
91
|
+
return name !== undefined && "value" in name && typeof name.value === "string" && name.enumerable === true && execute !== undefined && "value" in execute && typeof execute.value === "function" && execute.enumerable === true;
|
|
92
|
+
}
|
|
74
93
|
function mountCallerTool(t) {
|
|
75
|
-
if (isDefineToolProduct(t))
|
|
76
|
-
return maybeOffload(t, t);
|
|
94
|
+
if (isDefineToolProduct(t))
|
|
95
|
+
return maybeOffload(rebindDefineToolCtx(t, enrichSpecToolCtx), t);
|
|
96
|
+
if (!specArmCanConstruct(t)) {
|
|
97
|
+
const shown = Object.getOwnPropertyDescriptor(t, "name")?.value;
|
|
98
|
+
const e = new Error(`spec.tools entry ${typeof shown === "string" ? JSON.stringify(shown) : "(no own name)"}: the ToolSpec arm rebuilds a caller tool from its OWN enumerable members, and this entry does not carry a string \`name\` and a function \`execute\` as own properties (a class instance, a prototype child of a spec or of a defineTool product, a function, or a Proxy over one) — ${MOUNT_REMEDY}`);
|
|
99
|
+
e.code = "config.tool_mount_denied";
|
|
100
|
+
throw e;
|
|
77
101
|
}
|
|
78
102
|
return maybeOffload(defineTool({
|
|
79
103
|
...t,
|
|
@@ -165,7 +189,9 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
165
189
|
const classifierLaneRule = peerLaneActive && peerSendMessageBuiltIn;
|
|
166
190
|
const classifierSystemPrompt = buildAutoModePrompt(classifierLaneRule ? { ...am, crossSessionMessagesRule: CROSS_SESSION_CLASSIFIER_RULE } : am);
|
|
167
191
|
const classifierRuntime = brainToRuntime(deps.brain);
|
|
168
|
-
const
|
|
192
|
+
const classifierOffSeat = { reasoning: "off", maxTokens: AUTO_MODE_CLASSIFIER_MAX_TOKENS };
|
|
193
|
+
const classifierLowSeat = { reasoning: "low", maxTokens: AUTO_MODE_CLASSIFIER_MAX_TOKENS + AUTO_MODE_CLASSIFIER_LOW_REASONING_BUDGET_TOKENS };
|
|
194
|
+
const classifierSeat = thinkingOffExpressible(classifierModel) ? classifierOffSeat : classifierLowSeat;
|
|
169
195
|
autoModeDenialTracking = createAutoModeDenialTracker(am.denialLimit);
|
|
170
196
|
autoModeDecider = createAutoModeDecider({
|
|
171
197
|
...(am.timeoutMs !== undefined ? { timeoutMs: am.timeoutMs } : {}),
|
|
@@ -191,17 +217,17 @@ export async function prepareCapsAndWorkflow(input) {
|
|
|
191
217
|
(m.role === "user" || m.role === "assistant" || m.role === "toolResult"));
|
|
192
218
|
const userPrompt = renderAutoModeWindow(known, am.window) + renderAutoModeAction(input);
|
|
193
219
|
const classifierAuth = await spec.getApiKeyAndHeaders?.(classifierModel);
|
|
194
|
-
const
|
|
220
|
+
const request = (seat) => classifierRuntime.completeSimple(classifierModel, { systemPrompt: classifierSystemPrompt, messages: [{ role: "user", content: userPrompt, timestamp: Date.now() }] }, {
|
|
195
221
|
signal,
|
|
196
|
-
|
|
197
|
-
...classifierCap,
|
|
222
|
+
...seat,
|
|
198
223
|
...(classifierAuth?.apiKey !== undefined ? { apiKey: classifierAuth.apiKey } : {}),
|
|
199
224
|
...(classifierAuth?.headers !== undefined ? { headers: classifierAuth.headers } : {}),
|
|
200
225
|
});
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
226
|
+
let response = await request(classifierSeat);
|
|
227
|
+
if (classifierSeat === classifierOffSeat && classifierReplyEmptyAtCap(response)) {
|
|
228
|
+
response = await request(classifierLowSeat);
|
|
229
|
+
}
|
|
230
|
+
return classifierReplyText(response);
|
|
205
231
|
},
|
|
206
232
|
});
|
|
207
233
|
if (am.persistArming === true)
|
|
@@ -139,6 +139,15 @@ export function prepareGateStations(input) {
|
|
|
139
139
|
},
|
|
140
140
|
}
|
|
141
141
|
: {}),
|
|
142
|
+
onReadOnlyAllowed: (info) => emitTrace(deps.tracer, () => ({
|
|
143
|
+
kind: "permission.read_only_allowed",
|
|
144
|
+
version: 1,
|
|
145
|
+
taskId: hostTaskId,
|
|
146
|
+
toolName: info.toolName,
|
|
147
|
+
toolCallId: info.toolCallId,
|
|
148
|
+
command: info.command,
|
|
149
|
+
ts: Date.now(),
|
|
150
|
+
})),
|
|
142
151
|
...(permissionRuleOrgLane
|
|
143
152
|
? {
|
|
144
153
|
orgRules: {
|
|
@@ -634,7 +634,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
634
634
|
session,
|
|
635
635
|
tools: [...harnessTools],
|
|
636
636
|
model,
|
|
637
|
-
thinkingLevel: thinking
|
|
637
|
+
thinkingLevel: thinking,
|
|
638
638
|
systemPrompt: systemPromptSeat.current,
|
|
639
639
|
...(systemBlocks ? { systemBlocks } : {}),
|
|
640
640
|
getApiKeyAndHeaders: spec.getApiKeyAndHeaders,
|
|
@@ -26,7 +26,7 @@ export function prepareTurnWiring(input) {
|
|
|
26
26
|
layoutGroups: (systemBlocks ?? [{ groupId: "body", cacheControlBoundary: true }]).map((b) => ({ groupId: b.groupId, cacheControlBoundary: b.cacheControlBoundary })),
|
|
27
27
|
stableSystemText: systemPromptSeat.current,
|
|
28
28
|
toolWire: toolsToFingerprintInputs(harnessTools),
|
|
29
|
-
thinkingLevel:
|
|
29
|
+
thinkingLevel: thinking ?? null,
|
|
30
30
|
maxTokens: model.maxTokens,
|
|
31
31
|
hasOutputSchema: spec.outputSchema !== undefined,
|
|
32
32
|
sessionId,
|