@sema-agent/core 2.8.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/send-message-tool.js +37 -29
- package/dist/agents/subagent.js +91 -4
- package/dist/brain/circuit-breaker.js +18 -8
- package/dist/brain/retry.d.ts +1 -0
- package/dist/brain/retry.js +29 -7
- package/dist/brain/stream-engine.d.ts +1 -0
- package/dist/brain/stream-engine.js +74 -12
- package/dist/config/defaults.d.ts +1 -0
- package/dist/config/defaults.js +1 -0
- package/dist/core/auto-compaction.js +9 -1
- package/dist/core/background-agent-store.d.ts +2 -0
- package/dist/core/background-agent-store.js +20 -0
- package/dist/core/mcp.js +8 -5
- package/dist/core/runner/assemble-result.d.ts +1 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/prepare-task.d.ts +2 -1
- package/dist/core/runner/prepare-task.js +61 -20
- package/dist/core/runner/runtask.js +38 -12
- package/dist/core/runner/tool-disclosure.d.ts +8 -3
- package/dist/core/runner/tool-disclosure.js +39 -10
- package/dist/core/skills-directory.d.ts +1 -1
- package/dist/core/skills-directory.js +257 -28
- package/dist/core/task-registry-agent.d.ts +2 -1
- package/dist/core/task-registry-agent.js +50 -54
- package/dist/core/task-registry.d.ts +1 -0
- package/dist/core/task-registry.js +1 -1
- package/dist/core/types.d.ts +9 -1
- package/dist/engine/compaction/compaction.js +71 -20
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/internal/harness.d.ts +1 -1
- package/dist/internal/harness.js +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +1 -0
- package/dist/orchestration/run-workflow-tool.js +4 -1
- package/dist/orchestration/workflow.d.ts +1 -1
- package/dist/orchestration/workflow.js +20 -12
- package/dist/tools/fs/bash-readonly-classifier.js +83 -17
- package/dist/tools/fs/fs-bash.js +17 -11
- package/dist/tools/fs/fs-shared.d.ts +1 -0
- package/dist/tools/fs/fs-shared.js +44 -2
- package/dist/tools/web.d.ts +15 -0
- package/dist/tools/web.js +42 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
3
|
+
import { canonicalToolName } from "./tool-name-aliases.js";
|
|
3
4
|
const SKILL_FILE = "SKILL.md";
|
|
4
5
|
const RESOURCE_DIRS = ["assets", "references", "scripts"];
|
|
5
6
|
const NAME_MAX_CHARS = 64;
|
|
@@ -22,26 +23,139 @@ function parseSkillFile(text) {
|
|
|
22
23
|
}
|
|
23
24
|
if (end === -1)
|
|
24
25
|
return { fields, body: normalized, hadFrontmatter: false };
|
|
25
|
-
|
|
26
|
+
let i = 1;
|
|
27
|
+
while (i < end) {
|
|
26
28
|
const line = lines[i];
|
|
27
29
|
const trimmed = line.trim();
|
|
28
|
-
if (trimmed === "" || trimmed.startsWith("#"))
|
|
30
|
+
if (trimmed === "" || trimmed.startsWith("#")) {
|
|
31
|
+
i++;
|
|
29
32
|
continue;
|
|
30
|
-
|
|
33
|
+
}
|
|
34
|
+
if (/^\s/.test(line)) {
|
|
35
|
+
i++;
|
|
31
36
|
continue;
|
|
37
|
+
}
|
|
32
38
|
const kv = /^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/.exec(trimmed);
|
|
33
|
-
if (!kv)
|
|
39
|
+
if (!kv) {
|
|
40
|
+
i++;
|
|
34
41
|
continue;
|
|
42
|
+
}
|
|
35
43
|
const [, key, raw] = kv;
|
|
44
|
+
const rawValue = raw.trim();
|
|
45
|
+
const block = parseBlockScalarHeader(rawValue);
|
|
46
|
+
if (block !== undefined) {
|
|
47
|
+
const read = readBlockScalar(lines, i + 1, end, block);
|
|
48
|
+
i = read.next;
|
|
49
|
+
if (!fields.has(key))
|
|
50
|
+
fields.set(key, read.value);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (rawValue.startsWith("[") && !isFlowSequenceClosed(rawValue)) {
|
|
54
|
+
const joined = readFlowSequenceContinuation(lines, i + 1, end, rawValue);
|
|
55
|
+
i = joined.next;
|
|
56
|
+
if (!fields.has(key))
|
|
57
|
+
fields.set(key, joined.value);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
i++;
|
|
36
61
|
if (fields.has(key))
|
|
37
62
|
continue;
|
|
38
|
-
fields.set(key, unquote(
|
|
63
|
+
fields.set(key, unquote(rawValue));
|
|
39
64
|
}
|
|
40
65
|
let body = lines.slice(end + 1).join("\n");
|
|
41
66
|
if (body.startsWith("\n"))
|
|
42
67
|
body = body.slice(1);
|
|
43
68
|
return { fields, body, hadFrontmatter: true };
|
|
44
69
|
}
|
|
70
|
+
function parseBlockScalarHeader(value) {
|
|
71
|
+
const m = /^([|>])([0-9+-]*)$/.exec(value);
|
|
72
|
+
if (!m)
|
|
73
|
+
return undefined;
|
|
74
|
+
const rest = m[2];
|
|
75
|
+
if (!/^(?:[1-9]?[+-]?|[+-][1-9]?)$/.test(rest))
|
|
76
|
+
return undefined;
|
|
77
|
+
const digit = /[1-9]/.exec(rest)?.[0];
|
|
78
|
+
return { style: m[1] === "|" ? "literal" : "folded", ...(digit !== undefined ? { indent: Number(digit) } : {}) };
|
|
79
|
+
}
|
|
80
|
+
function readBlockScalar(lines, start, end, header) {
|
|
81
|
+
const indentOf = (s) => /^[ \t]*/.exec(s)[0].length;
|
|
82
|
+
let indent = header.indent;
|
|
83
|
+
if (indent === undefined) {
|
|
84
|
+
for (let j = start; j < end; j++) {
|
|
85
|
+
if (lines[j].trim() === "")
|
|
86
|
+
continue;
|
|
87
|
+
indent = indentOf(lines[j]);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (indent === undefined || indent === 0)
|
|
92
|
+
return { value: "", next: start };
|
|
93
|
+
const taken = [];
|
|
94
|
+
let j = start;
|
|
95
|
+
for (; j < end; j++) {
|
|
96
|
+
const line = lines[j];
|
|
97
|
+
if (line.trim() === "") {
|
|
98
|
+
taken.push("");
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (indentOf(line) < indent)
|
|
102
|
+
break;
|
|
103
|
+
taken.push(line.slice(indent));
|
|
104
|
+
}
|
|
105
|
+
while (taken.length > 0 && taken[taken.length - 1] === "")
|
|
106
|
+
taken.pop();
|
|
107
|
+
if (header.style === "literal")
|
|
108
|
+
return { value: taken.join("\n"), next: j };
|
|
109
|
+
let folded = "";
|
|
110
|
+
let blanks = 0;
|
|
111
|
+
let started = false;
|
|
112
|
+
let prevMoreIndented = false;
|
|
113
|
+
for (const cur of taken) {
|
|
114
|
+
if (cur === "") {
|
|
115
|
+
blanks++;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const moreIndented = indentOf(cur) > 0;
|
|
119
|
+
if (!started) {
|
|
120
|
+
folded = cur;
|
|
121
|
+
started = true;
|
|
122
|
+
}
|
|
123
|
+
else if (blanks > 0) {
|
|
124
|
+
folded += "\n".repeat(blanks) + cur;
|
|
125
|
+
}
|
|
126
|
+
else if (moreIndented || prevMoreIndented) {
|
|
127
|
+
folded += `\n${cur}`;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
folded += ` ${cur}`;
|
|
131
|
+
}
|
|
132
|
+
blanks = 0;
|
|
133
|
+
prevMoreIndented = moreIndented;
|
|
134
|
+
}
|
|
135
|
+
return { value: folded, next: j };
|
|
136
|
+
}
|
|
137
|
+
function isFlowSequenceClosed(text) {
|
|
138
|
+
let depth = 0;
|
|
139
|
+
for (const ch of text) {
|
|
140
|
+
if (ch === "[")
|
|
141
|
+
depth++;
|
|
142
|
+
else if (ch === "]")
|
|
143
|
+
depth--;
|
|
144
|
+
}
|
|
145
|
+
return depth <= 0;
|
|
146
|
+
}
|
|
147
|
+
function readFlowSequenceContinuation(lines, start, end, head) {
|
|
148
|
+
let acc = head;
|
|
149
|
+
let j = start;
|
|
150
|
+
for (; j < end; j++) {
|
|
151
|
+
acc += ` ${lines[j].trim()}`;
|
|
152
|
+
if (isFlowSequenceClosed(acc)) {
|
|
153
|
+
j++;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { value: acc, next: j };
|
|
158
|
+
}
|
|
45
159
|
function unquote(value) {
|
|
46
160
|
if (value.length < 2)
|
|
47
161
|
return value;
|
|
@@ -51,7 +165,13 @@ function unquote(value) {
|
|
|
51
165
|
const inner = value.slice(1, -1);
|
|
52
166
|
return q === '"' ? inner.replace(/\\t/g, "\t").replace(/\\n/g, "\n") : inner;
|
|
53
167
|
}
|
|
54
|
-
function
|
|
168
|
+
function isInsideRoot(root, p) {
|
|
169
|
+
if (p === root)
|
|
170
|
+
return true;
|
|
171
|
+
const rel = relative(root, p);
|
|
172
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
173
|
+
}
|
|
174
|
+
function listRelativeFiles(base, dir, prefix, out, walk) {
|
|
55
175
|
let entries;
|
|
56
176
|
try {
|
|
57
177
|
entries = readdirSync(join(base, dir), { withFileTypes: true });
|
|
@@ -61,24 +181,68 @@ function listRelativeFiles(base, dir, prefix, out) {
|
|
|
61
181
|
}
|
|
62
182
|
for (const e of entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))) {
|
|
63
183
|
const rel = `${prefix}${e.name}`;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
184
|
+
const abs = join(base, dir, e.name);
|
|
185
|
+
const resolved = resolveInsideSkill(abs, rel, walk);
|
|
186
|
+
if (resolved === undefined)
|
|
187
|
+
continue;
|
|
188
|
+
if (resolved.stats.isDirectory()) {
|
|
189
|
+
if (walk.visited.has(resolved.real))
|
|
190
|
+
continue;
|
|
191
|
+
walk.visited.add(resolved.real);
|
|
192
|
+
listRelativeFiles(base, join(dir, e.name), `${rel}/`, out, walk);
|
|
193
|
+
}
|
|
194
|
+
else if (resolved.stats.isFile()) {
|
|
67
195
|
out.push(rel);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function resolveInsideSkill(abs, rel, walk) {
|
|
200
|
+
let real;
|
|
201
|
+
let stats;
|
|
202
|
+
try {
|
|
203
|
+
real = realpathSync(abs);
|
|
204
|
+
stats = statSync(abs);
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
walk.warn({ code: "attachment_skipped", skill: walk.skill, detail: `${rel}: could not be resolved (${errText(err)})` });
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
if (!isInsideRoot(walk.realRoot, real)) {
|
|
211
|
+
walk.warn({
|
|
212
|
+
code: "attachment_skipped",
|
|
213
|
+
skill: walk.skill,
|
|
214
|
+
detail: `${rel}: resolves outside the skill directory — skipped (attachments are collected from the declared skill directory only)`,
|
|
215
|
+
});
|
|
216
|
+
return undefined;
|
|
68
217
|
}
|
|
218
|
+
return { real, stats };
|
|
69
219
|
}
|
|
70
220
|
function readAttachments(skillDir, skillName, budgetBytes, warn) {
|
|
221
|
+
let realRoot;
|
|
222
|
+
try {
|
|
223
|
+
realRoot = realpathSync(skillDir);
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
warn({ code: "read_failed", skill: skillName, detail: `the skill directory could not be resolved (${errText(err)}) — attachments skipped` });
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
const walk = { realRoot, skill: skillName, warn, visited: new Set([realRoot]) };
|
|
71
230
|
const relPaths = [];
|
|
72
231
|
for (const d of RESOURCE_DIRS) {
|
|
73
|
-
let
|
|
232
|
+
let exists = true;
|
|
74
233
|
try {
|
|
75
|
-
|
|
234
|
+
statSync(join(skillDir, d));
|
|
76
235
|
}
|
|
77
236
|
catch {
|
|
78
|
-
|
|
237
|
+
exists = false;
|
|
79
238
|
}
|
|
80
|
-
if (
|
|
81
|
-
|
|
239
|
+
if (!exists)
|
|
240
|
+
continue;
|
|
241
|
+
const resolved = resolveInsideSkill(join(skillDir, d), d, walk);
|
|
242
|
+
if (resolved === undefined || !resolved.stats.isDirectory())
|
|
243
|
+
continue;
|
|
244
|
+
walk.visited.add(resolved.real);
|
|
245
|
+
listRelativeFiles(skillDir, d, `${d}/`, relPaths, walk);
|
|
82
246
|
}
|
|
83
247
|
relPaths.sort();
|
|
84
248
|
const files = [];
|
|
@@ -114,20 +278,60 @@ function isDecodableText(bytes) {
|
|
|
114
278
|
function errText(err) {
|
|
115
279
|
return err instanceof Error ? err.message : String(err);
|
|
116
280
|
}
|
|
117
|
-
|
|
281
|
+
const ALL_TOOLS_WILDCARD = "*";
|
|
282
|
+
function splitToolNames(declared) {
|
|
283
|
+
let text = declared.trim();
|
|
284
|
+
if (text.startsWith("[") && text.endsWith("]"))
|
|
285
|
+
text = text.slice(1, -1);
|
|
118
286
|
const names = [];
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
287
|
+
let current = "";
|
|
288
|
+
let depth = 0;
|
|
289
|
+
const flush = () => {
|
|
290
|
+
const name = unquote(current.trim());
|
|
291
|
+
if (name !== "")
|
|
292
|
+
names.push(name);
|
|
293
|
+
current = "";
|
|
294
|
+
};
|
|
295
|
+
for (const ch of text) {
|
|
296
|
+
if (ch === "(") {
|
|
297
|
+
depth++;
|
|
298
|
+
current += ch;
|
|
299
|
+
}
|
|
300
|
+
else if (ch === ")") {
|
|
301
|
+
if (depth > 0)
|
|
302
|
+
depth--;
|
|
303
|
+
current += ch;
|
|
304
|
+
}
|
|
305
|
+
else if (depth === 0 && (ch === "," || /\s/.test(ch))) {
|
|
306
|
+
flush();
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
current += ch;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
flush();
|
|
313
|
+
return names;
|
|
314
|
+
}
|
|
315
|
+
function manifestFromAllowedTools(declared, disallowed, skillName, deployedTools, warn) {
|
|
316
|
+
const names = [];
|
|
317
|
+
const seen = new Set();
|
|
318
|
+
for (const n of splitToolNames(declared)) {
|
|
319
|
+
const key = canonicalToolName(n);
|
|
320
|
+
if (seen.has(key))
|
|
321
|
+
continue;
|
|
322
|
+
seen.add(key);
|
|
323
|
+
names.push(n);
|
|
122
324
|
}
|
|
123
325
|
if (names.length === 0)
|
|
124
326
|
return undefined;
|
|
327
|
+
if (names.some((n) => n === ALL_TOOLS_WILDCARD))
|
|
328
|
+
return undefined;
|
|
125
329
|
let allowTools = names;
|
|
126
330
|
if (deployedTools !== undefined) {
|
|
127
|
-
const mounted = new Set(deployedTools);
|
|
128
|
-
allowTools = names.filter((n) => mounted.has(n));
|
|
331
|
+
const mounted = new Set(deployedTools.map(canonicalToolName));
|
|
332
|
+
allowTools = names.filter((n) => mounted.has(canonicalToolName(n)));
|
|
129
333
|
for (const n of names) {
|
|
130
|
-
if (!mounted.has(n)) {
|
|
334
|
+
if (!mounted.has(canonicalToolName(n))) {
|
|
131
335
|
warn({
|
|
132
336
|
code: "allowed_tool_not_mounted",
|
|
133
337
|
skill: skillName,
|
|
@@ -136,6 +340,10 @@ function manifestFromAllowedTools(declared, skillName, deployedTools, warn) {
|
|
|
136
340
|
}
|
|
137
341
|
}
|
|
138
342
|
}
|
|
343
|
+
if (disallowed.length > 0) {
|
|
344
|
+
const denied = new Set(disallowed.map(canonicalToolName));
|
|
345
|
+
allowTools = allowTools.filter((n) => !denied.has(canonicalToolName(n)));
|
|
346
|
+
}
|
|
139
347
|
return { allowTools, lineageId: `skill:${skillName}` };
|
|
140
348
|
}
|
|
141
349
|
export function createSkillsFromDirectory(dir, options = {}) {
|
|
@@ -153,10 +361,23 @@ export function createSkillsFromDirectory(dir, options = {}) {
|
|
|
153
361
|
catch (err) {
|
|
154
362
|
throw new Error(`skills directory could not be read: ${dir} (${errText(err)})`);
|
|
155
363
|
}
|
|
156
|
-
const dirNames =
|
|
157
|
-
|
|
158
|
-
.
|
|
159
|
-
|
|
364
|
+
const dirNames = [];
|
|
365
|
+
for (const e of entries) {
|
|
366
|
+
if (e.isDirectory()) {
|
|
367
|
+
dirNames.push(e.name);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (!e.isSymbolicLink())
|
|
371
|
+
continue;
|
|
372
|
+
try {
|
|
373
|
+
if (statSync(join(dir, e.name)).isDirectory())
|
|
374
|
+
dirNames.push(e.name);
|
|
375
|
+
}
|
|
376
|
+
catch (err) {
|
|
377
|
+
warn({ code: "read_failed", skill: e.name, detail: `entry is a symbolic link that could not be resolved (${errText(err)}) — skipped` });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
dirNames.sort();
|
|
160
381
|
const skills = [];
|
|
161
382
|
for (const name of dirNames) {
|
|
162
383
|
const skillDir = join(dir, name);
|
|
@@ -200,7 +421,15 @@ export function createSkillsFromDirectory(dir, options = {}) {
|
|
|
200
421
|
continue;
|
|
201
422
|
}
|
|
202
423
|
const allowed = parsed.fields.get("allowed-tools");
|
|
203
|
-
const
|
|
424
|
+
const disallowed = splitToolNames(parsed.fields.get("disallowed-tools") ?? "");
|
|
425
|
+
const manifest = allowed === undefined ? undefined : manifestFromAllowedTools(allowed, disallowed, name, options.deployedTools, warn);
|
|
426
|
+
if (disallowed.length > 0 && manifest === undefined) {
|
|
427
|
+
warn({
|
|
428
|
+
code: "disallowed_tools_unenforced",
|
|
429
|
+
skill: name,
|
|
430
|
+
detail: `disallowed-tools names ${disallowed.map((n) => `"${n}"`).join(", ")}, but this skill produced no allowlist to subtract from (allowed-tools is absent or claims every tool) — the deny declaration is NOT enforced (express it in the task's own tool policy)`,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
204
433
|
const files = readAttachments(skillDir, name, budget, warn);
|
|
205
434
|
skills.push({
|
|
206
435
|
name: declaredName,
|
|
@@ -2,7 +2,7 @@ import { type BackgroundAgentRecord, type BackgroundAgentStore } from "./backgro
|
|
|
2
2
|
import { type StopSource, type TaskAccess, type UnifiedTaskResult, type BackgroundAgentTaskHandle, type DurableAgentCore, type ParkedClaimTicket, type RegisterBackgroundAgentInput } from "./task-registry-shared.js";
|
|
3
3
|
import { type ToolResultStore } from "./tool-result-store.js";
|
|
4
4
|
export declare function ensureDurableHeartbeatLane(core: DurableAgentCore): void;
|
|
5
|
-
export declare function durableAgentWriteLane(handle: BackgroundAgentTaskHandle, patch: Partial<BackgroundAgentRecord>, clear?: (keyof BackgroundAgentRecord)[]): void;
|
|
5
|
+
export declare function durableAgentWriteLane(handle: BackgroundAgentTaskHandle, patch: Partial<BackgroundAgentRecord>, clear?: readonly (keyof BackgroundAgentRecord)[]): void;
|
|
6
6
|
export declare function durableAgentArmedLane(core: DurableAgentCore, id: string): boolean;
|
|
7
7
|
export declare function durableAgentRowProbeLane(core: DurableAgentCore, id: string): (() => Promise<boolean>) | undefined;
|
|
8
8
|
export declare function beginDurableClaimLane(core: DurableAgentCore, id: string): boolean;
|
|
@@ -71,6 +71,7 @@ export declare function settleBackgroundAgentLane(core: DurableAgentCore, id: st
|
|
|
71
71
|
errorKind?: string;
|
|
72
72
|
stoppedBy?: StopSource;
|
|
73
73
|
seq?: number;
|
|
74
|
+
cycle?: number;
|
|
74
75
|
}): "completed" | "failed" | "killed" | undefined;
|
|
75
76
|
export declare function abortBackgroundAgentsForOwnerLane(core: DurableAgentCore, access: TaskAccess, opts?: {
|
|
76
77
|
skipSessionScoped?: boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { uuidv7 } from "../internal/harness.js";
|
|
3
|
-
import { canAccessAgentRecord, BackgroundAgentStoreError, } from "./background-agent-store.js";
|
|
3
|
+
import { canAccessAgentRecord, BackgroundAgentStoreError, REVIVED_ROW_CLEARED_FIELDS, } from "./background-agent-store.js";
|
|
4
4
|
import { shutdownDebug } from "./shutdown-debug.js";
|
|
5
5
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
6
6
|
import { boundedRedactedSummary } from "./untrusted-egress.js";
|
|
@@ -652,6 +652,11 @@ export function settleBackgroundAgentLane(core, id, outcome) {
|
|
|
652
652
|
const handle = core.handles.get(id);
|
|
653
653
|
if (!handle || handle.type !== "background_agent")
|
|
654
654
|
return undefined;
|
|
655
|
+
if (outcome.cycle === undefined && (handle.reviveCycle ?? 0) !== 0) {
|
|
656
|
+
throw new Error(`settleBackgroundAgent("${id}"): the row has been revived (cycle ${handle.reviveCycle}) — pass the cycle this settle speaks for, or use settleRevivedAgent`);
|
|
657
|
+
}
|
|
658
|
+
if ((outcome.cycle ?? 0) !== (handle.reviveCycle ?? 0))
|
|
659
|
+
return undefined;
|
|
655
660
|
if (handle.status !== "running") {
|
|
656
661
|
if (handle.status === "killed") {
|
|
657
662
|
mintCompletionId(handle);
|
|
@@ -859,31 +864,11 @@ export function reviveBackgroundAgentLane(core, id, access, abort) {
|
|
|
859
864
|
handle.reviveCycle = (handle.reviveCycle ?? 0) + 1;
|
|
860
865
|
handle.cycleSeq = (handle.cycleSeq ?? 1) + 1;
|
|
861
866
|
handle.updatedAt = Date.now();
|
|
862
|
-
durableAgentWriteLane(handle, { status: "running" },
|
|
863
|
-
"settledAt",
|
|
864
|
-
"stoppedBy",
|
|
865
|
-
"finalOutput",
|
|
866
|
-
"finalOutputFull",
|
|
867
|
-
"error",
|
|
868
|
-
"errorCode",
|
|
869
|
-
"errorRetryable",
|
|
870
|
-
"errorKind",
|
|
871
|
-
"resultIsPartial",
|
|
872
|
-
"completionId",
|
|
873
|
-
"summary",
|
|
874
|
-
"recentSteps",
|
|
875
|
-
"editedFiles",
|
|
876
|
-
"usage",
|
|
877
|
-
]);
|
|
867
|
+
durableAgentWriteLane(handle, { status: "running" }, REVIVED_ROW_CLEARED_FIELDS);
|
|
878
868
|
return { ok: true, cycle: handle.reviveCycle };
|
|
879
869
|
}
|
|
880
870
|
export function settleRevivedAgentLane(core, id, cycle, outcome) {
|
|
881
|
-
|
|
882
|
-
if (!handle || handle.type !== "background_agent")
|
|
883
|
-
return undefined;
|
|
884
|
-
if ((handle.reviveCycle ?? 0) !== cycle)
|
|
885
|
-
return undefined;
|
|
886
|
-
return settleBackgroundAgentLane(core, id, outcome);
|
|
871
|
+
return settleBackgroundAgentLane(core, id, { ...outcome, cycle });
|
|
887
872
|
}
|
|
888
873
|
export function unmarkRetainedContinuationLane(core, id) {
|
|
889
874
|
const handle = core.handles.get(id);
|
|
@@ -1023,18 +1008,33 @@ export function notFoundRunningAgentsTail(footer) {
|
|
|
1023
1008
|
return ((footer.named.length > 0 ? `. Running named agents: ${footer.named.join(", ")}` : "") +
|
|
1024
1009
|
(footer.background.length > 0 ? `. Running background agents: ${footer.background.join(", ")}` : ""));
|
|
1025
1010
|
}
|
|
1011
|
+
function buildAgentPollDetails(input) {
|
|
1012
|
+
const failed = input.status === "failed";
|
|
1013
|
+
return {
|
|
1014
|
+
task_id: input.taskId,
|
|
1015
|
+
type: "background_agent",
|
|
1016
|
+
status: input.status,
|
|
1017
|
+
retrieval_status: input.retrievalStatus,
|
|
1018
|
+
...(input.seq !== undefined ? { seq: input.seq } : {}),
|
|
1019
|
+
...(input.status === "killed" && input.stoppedBy !== undefined ? { stoppedBy: input.stoppedBy } : {}),
|
|
1020
|
+
...(failed && input.error !== undefined ? { error: delimitUntrusted("agent error", boundedRedactedSummary(input.error, 300)) } : {}),
|
|
1021
|
+
...(failed && input.errorCode !== undefined ? { errorCode: input.errorCode } : {}),
|
|
1022
|
+
...(failed && input.errorRetryable !== undefined ? { retryable: input.errorRetryable } : {}),
|
|
1023
|
+
...(input.resultIsPartial === true ? { partial_result: true } : {}),
|
|
1024
|
+
...(input.completionId !== undefined ? { completionId: input.completionId } : {}),
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1026
1027
|
export function serveDurableAgentRowLane(row) {
|
|
1027
1028
|
if (row.status === "parked") {
|
|
1028
1029
|
return {
|
|
1029
1030
|
content: delimitUntrusted(`TaskOutput ${row.handle}`, `status: parked
|
|
1030
1031
|
The agent is durably suspended, waiting for an approval decision. It resumes when the pending approval is decided (durable approval inbox), or lands failed if the approval expires.`),
|
|
1031
|
-
details: {
|
|
1032
|
-
|
|
1033
|
-
type: "background_agent",
|
|
1032
|
+
details: buildAgentPollDetails({
|
|
1033
|
+
taskId: row.handle,
|
|
1034
1034
|
status: "parked",
|
|
1035
|
-
|
|
1035
|
+
retrievalStatus: "success",
|
|
1036
1036
|
...(row.seq !== undefined ? { seq: row.seq } : {}),
|
|
1037
|
-
},
|
|
1037
|
+
}),
|
|
1038
1038
|
};
|
|
1039
1039
|
}
|
|
1040
1040
|
const kindClause = row.status === "failed" && row.errorKind !== undefined && row.errorRetryable !== undefined
|
|
@@ -1046,18 +1046,18 @@ ${row.error ? `error: ${row.error}${kindClause}
|
|
|
1046
1046
|
${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}`;
|
|
1047
1047
|
return {
|
|
1048
1048
|
content: delimitUntrusted(`TaskOutput ${row.handle}`, body),
|
|
1049
|
-
details: {
|
|
1050
|
-
|
|
1051
|
-
type: "background_agent",
|
|
1049
|
+
details: buildAgentPollDetails({
|
|
1050
|
+
taskId: row.handle,
|
|
1052
1051
|
status: row.status,
|
|
1053
|
-
|
|
1054
|
-
...(row.status === "killed" && row.stoppedBy !== undefined ? { stoppedBy: row.stoppedBy } : {}),
|
|
1052
|
+
retrievalStatus: "success",
|
|
1055
1053
|
...(row.seq !== undefined ? { seq: row.seq } : {}),
|
|
1056
|
-
...(row.
|
|
1054
|
+
...(row.stoppedBy !== undefined ? { stoppedBy: row.stoppedBy } : {}),
|
|
1055
|
+
...(row.error !== undefined ? { error: row.error } : {}),
|
|
1056
|
+
...(row.errorCode !== undefined ? { errorCode: row.errorCode } : {}),
|
|
1057
|
+
...(row.errorRetryable !== undefined ? { errorRetryable: row.errorRetryable } : {}),
|
|
1058
|
+
...(row.resultIsPartial === true ? { resultIsPartial: true } : {}),
|
|
1057
1059
|
...(row.completionId !== undefined ? { completionId: row.completionId } : {}),
|
|
1058
|
-
|
|
1059
|
-
...(row.status === "failed" && row.errorRetryable !== undefined ? { retryable: row.errorRetryable } : {}),
|
|
1060
|
-
},
|
|
1060
|
+
}),
|
|
1061
1061
|
...(row.status === "failed" ? { isError: true } : {}),
|
|
1062
1062
|
};
|
|
1063
1063
|
}
|
|
@@ -1083,13 +1083,12 @@ export async function pollBackgroundAgentLane(handle, deadline, signal, oneShot,
|
|
|
1083
1083
|
return {
|
|
1084
1084
|
content: delimitUntrusted(`TaskOutput ${handle.id}`, `status: parked
|
|
1085
1085
|
The agent is durably suspended, waiting for an approval decision. It resumes when the pending approval is decided (durable approval inbox), or lands failed if the approval expires.`),
|
|
1086
|
-
details: {
|
|
1087
|
-
|
|
1088
|
-
type: "background_agent",
|
|
1086
|
+
details: buildAgentPollDetails({
|
|
1087
|
+
taskId: handle.id,
|
|
1089
1088
|
status: "parked",
|
|
1090
|
-
|
|
1089
|
+
retrievalStatus: "success",
|
|
1091
1090
|
...(handle.cycleSeq !== undefined ? { seq: handle.cycleSeq } : {}),
|
|
1092
|
-
},
|
|
1091
|
+
}),
|
|
1093
1092
|
};
|
|
1094
1093
|
}
|
|
1095
1094
|
const fullResult = handle.result ? (handle.resultFull ?? handle.result) : undefined;
|
|
@@ -1109,21 +1108,18 @@ ${handle.error ? `error: ${handle.error}${kindClause}
|
|
|
1109
1108
|
${resultText}` : "(no result text)"}`;
|
|
1110
1109
|
return {
|
|
1111
1110
|
content: delimitUntrusted(`TaskOutput ${handle.id}`, body),
|
|
1112
|
-
details: {
|
|
1113
|
-
|
|
1114
|
-
type: "background_agent",
|
|
1111
|
+
details: buildAgentPollDetails({
|
|
1112
|
+
taskId: handle.id,
|
|
1115
1113
|
status: handle.status,
|
|
1116
|
-
|
|
1114
|
+
retrievalStatus: retrieval,
|
|
1117
1115
|
...(handle.cycleSeq !== undefined ? { seq: handle.cycleSeq } : {}),
|
|
1118
|
-
...(handle.
|
|
1119
|
-
...(handle.
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
...(handle.
|
|
1123
|
-
...(handle.status === "failed" && handle.errorRetryable !== undefined ? { retryable: handle.errorRetryable } : {}),
|
|
1124
|
-
...(handle.resultIsPartial ? { partial_result: true } : {}),
|
|
1116
|
+
...(handle.stoppedBy !== undefined ? { stoppedBy: handle.stoppedBy } : {}),
|
|
1117
|
+
...(handle.error !== undefined ? { error: handle.error } : {}),
|
|
1118
|
+
...(handle.errorCode !== undefined ? { errorCode: handle.errorCode } : {}),
|
|
1119
|
+
...(handle.errorRetryable !== undefined ? { errorRetryable: handle.errorRetryable } : {}),
|
|
1120
|
+
...(handle.resultIsPartial === true ? { resultIsPartial: true } : {}),
|
|
1125
1121
|
...(handle.completionId !== undefined ? { completionId: handle.completionId } : {}),
|
|
1126
|
-
},
|
|
1122
|
+
}),
|
|
1127
1123
|
...(handle.status === "failed" ? { isError: true } : {}),
|
|
1128
1124
|
};
|
|
1129
1125
|
}
|
|
@@ -138,6 +138,7 @@ export declare class TaskRegistry {
|
|
|
138
138
|
errorKind?: string;
|
|
139
139
|
stoppedBy?: StopSource;
|
|
140
140
|
seq?: number;
|
|
141
|
+
cycle?: number;
|
|
141
142
|
}): "completed" | "failed" | "killed" | undefined;
|
|
142
143
|
abortBackgroundAgentsForOwner(access: TaskAccess, opts?: {
|
|
143
144
|
skipSessionScoped?: boolean;
|
|
@@ -518,7 +518,7 @@ export class TaskRegistry {
|
|
|
518
518
|
this.markStopSource(handle.id, "system");
|
|
519
519
|
const reapTerminalNote = handle.onReapTerminal;
|
|
520
520
|
handle.abort.abort();
|
|
521
|
-
this.settleBackgroundAgent(handle.id, { status: "killed", error: "session released" });
|
|
521
|
+
this.settleBackgroundAgent(handle.id, { status: "killed", error: "session released", cycle: handle.reviveCycle ?? 0 });
|
|
522
522
|
if (reapTerminalNote !== undefined && handle.terminalNotified !== true) {
|
|
523
523
|
handle.terminalNotified = true;
|
|
524
524
|
try {
|
package/dist/core/types.d.ts
CHANGED
|
@@ -97,6 +97,7 @@ export interface ToolExecuteContext {
|
|
|
97
97
|
clientContext?: TaskSpec["clientContext"];
|
|
98
98
|
excludeTools?: readonly string[];
|
|
99
99
|
deferTools?: readonly string[];
|
|
100
|
+
alwaysLoadTools?: readonly string[];
|
|
100
101
|
promptProfile?: "simple" | "classic";
|
|
101
102
|
additionalDirectories?: readonly string[];
|
|
102
103
|
envFacts?: TaskSpec["envFacts"];
|
|
@@ -389,6 +390,10 @@ export interface TaskResult {
|
|
|
389
390
|
atTurn: number;
|
|
390
391
|
};
|
|
391
392
|
structuredOutput?: unknown;
|
|
393
|
+
rewindNotes?: Array<{
|
|
394
|
+
code: "conversation_only" | "files_env_unsupported" | "snapshot_store_unconfigured";
|
|
395
|
+
message: string;
|
|
396
|
+
}>;
|
|
392
397
|
stats: {
|
|
393
398
|
turns: number;
|
|
394
399
|
tokens: number;
|
|
@@ -465,11 +470,14 @@ export interface TaskEventIdentity {
|
|
|
465
470
|
sourceTaskId?: string;
|
|
466
471
|
bgAgentId?: string;
|
|
467
472
|
}
|
|
468
|
-
export type BrainStatusPhase = "rate_limited" | "retrying" | "reconnecting" | "circuit_open";
|
|
473
|
+
export type BrainStatusPhase = "rate_limited" | "retrying" | "reconnecting" | "circuit_open" | "recovered" | "gave_up";
|
|
469
474
|
export interface BrainStatus {
|
|
470
475
|
phase: BrainStatusPhase;
|
|
471
476
|
detail?: string;
|
|
472
477
|
retryInSec?: number;
|
|
478
|
+
retryInMs?: number;
|
|
479
|
+
attempt?: number;
|
|
480
|
+
maxRetries?: number;
|
|
473
481
|
}
|
|
474
482
|
export interface ToolActivity {
|
|
475
483
|
phase: "start" | "end";
|