@sema-agent/core 1.428.0 → 1.430.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/subagent-steps.d.ts +6 -0
- package/dist/agents/subagent-steps.js +9 -0
- package/dist/agents/subagent.js +55 -0
- package/dist/core/runner/prepare-task.js +1 -0
- package/dist/core/types.d.ts +7 -0
- package/dist/tools/fs/index.d.ts +1 -0
- package/dist/tools/fs/index.js +38 -2
- package/package.json +1 -1
|
@@ -18,6 +18,8 @@ export declare class SubagentStepRecorder {
|
|
|
18
18
|
private readonly pending;
|
|
19
19
|
private readonly edits;
|
|
20
20
|
private action;
|
|
21
|
+
private actionTool;
|
|
22
|
+
private actionTarget;
|
|
21
23
|
constructor(parentToolCallId?: string);
|
|
22
24
|
lockTo(taskId: string): void;
|
|
23
25
|
private resetRecorded;
|
|
@@ -26,5 +28,9 @@ export declare class SubagentStepRecorder {
|
|
|
26
28
|
recentSteps(): SubagentStep[] | undefined;
|
|
27
29
|
editedFiles(): SubagentEditedFile[] | undefined;
|
|
28
30
|
currentAction(): string | undefined;
|
|
31
|
+
currentActionStructured(): {
|
|
32
|
+
toolName: string;
|
|
33
|
+
target?: string;
|
|
34
|
+
} | undefined;
|
|
29
35
|
}
|
|
30
36
|
export declare function stepsFromMessages(messages: readonly unknown[], lastN: number): SubagentStep[];
|
|
@@ -58,6 +58,8 @@ export class SubagentStepRecorder {
|
|
|
58
58
|
pending = new Map();
|
|
59
59
|
edits = new Map();
|
|
60
60
|
action;
|
|
61
|
+
actionTool;
|
|
62
|
+
actionTarget;
|
|
61
63
|
constructor(parentToolCallId) {
|
|
62
64
|
this.parentToolCallId = parentToolCallId;
|
|
63
65
|
}
|
|
@@ -72,6 +74,8 @@ export class SubagentStepRecorder {
|
|
|
72
74
|
this.pending.clear();
|
|
73
75
|
this.edits.clear();
|
|
74
76
|
this.action = undefined;
|
|
77
|
+
this.actionTool = undefined;
|
|
78
|
+
this.actionTarget = undefined;
|
|
75
79
|
}
|
|
76
80
|
inScope(e) {
|
|
77
81
|
const { sourceTaskId: src, parentToolCallId: ptc } = e;
|
|
@@ -94,6 +98,8 @@ export class SubagentStepRecorder {
|
|
|
94
98
|
const target = extractTarget(e.args);
|
|
95
99
|
this.pending.set(e.toolCallId, { tool, target, ...(editTargetPath(e.toolName, e.args) ? { edit: editTargetPath(e.toolName, e.args) } : {}) });
|
|
96
100
|
this.action = target ? `${tool} ${target}`.slice(0, FIELD_MAX + tool.length + 1) : tool;
|
|
101
|
+
this.actionTool = tool;
|
|
102
|
+
this.actionTarget = target || undefined;
|
|
97
103
|
}
|
|
98
104
|
else if (e.type === "tool_end") {
|
|
99
105
|
if (!this.inScope(e))
|
|
@@ -128,6 +134,9 @@ export class SubagentStepRecorder {
|
|
|
128
134
|
currentAction() {
|
|
129
135
|
return this.action;
|
|
130
136
|
}
|
|
137
|
+
currentActionStructured() {
|
|
138
|
+
return this.actionTool === undefined ? undefined : { toolName: this.actionTool, ...(this.actionTarget !== undefined ? { target: this.actionTarget } : {}) };
|
|
139
|
+
}
|
|
131
140
|
}
|
|
132
141
|
export function stepsFromMessages(messages, lastN) {
|
|
133
142
|
const results = new Map();
|
package/dist/agents/subagent.js
CHANGED
|
@@ -1569,6 +1569,36 @@ function makeSubagentTool(opts, depth, excluded, extraToolsBudget) {
|
|
|
1569
1569
|
timer.unref?.();
|
|
1570
1570
|
return true;
|
|
1571
1571
|
};
|
|
1572
|
+
if (ctx.autoModeReview) {
|
|
1573
|
+
const spawnToolNames = childTools.map((t) => t.name);
|
|
1574
|
+
const spawnToolsNote = spawnToolNames.length > 0
|
|
1575
|
+
? spawnToolNames.join(", ")
|
|
1576
|
+
: "(none explicitly listed — but if this deployment gave the child a real execution environment, it likely also has the standard file/shell tools: Read/Edit/Write/Bash/Grep/Glob)";
|
|
1577
|
+
const spawnVerdict = await ctx.autoModeReview.decider
|
|
1578
|
+
.decide({
|
|
1579
|
+
req: {
|
|
1580
|
+
toolName: wantsFork ? "Agent(fork)" : "Agent",
|
|
1581
|
+
args: { objective: prompt.slice(0, 2_000), tools: spawnToolNames, systemPrompt: childSystemPrompt?.slice(0, 2_000) },
|
|
1582
|
+
toolCallId: ctx.toolCallId,
|
|
1583
|
+
},
|
|
1584
|
+
askMessage: `Reviewing a sub-agent about to be spawned. Objective: ${prompt.slice(0, 2000)}${prompt.length > 2000 ? "…" : ""}\nTools available to it: ${spawnToolsNote}`,
|
|
1585
|
+
}, ctx.signal)
|
|
1586
|
+
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
1587
|
+
if (ctx.signal?.aborted) {
|
|
1588
|
+
await cancelObserver();
|
|
1589
|
+
const wt = await finishWorktree();
|
|
1590
|
+
return { isError: true, content: `Sub-agent not started: the delegating call was aborted.${wt ? `\n${wt}` : ""}`, details: { error: "aborted" } };
|
|
1591
|
+
}
|
|
1592
|
+
if (spawnVerdict.kind === "block") {
|
|
1593
|
+
await cancelObserver();
|
|
1594
|
+
const wt = await finishWorktree();
|
|
1595
|
+
return {
|
|
1596
|
+
isError: true,
|
|
1597
|
+
content: `Sub-agent not started: blocked by the auto-mode classifier${spawnVerdict.reason ? `: ${inlineUntrusted(spawnVerdict.reason)}` : ""}.${wt ? `\n${wt}` : ""}`,
|
|
1598
|
+
details: { error: "autoMode.spawn_blocked" },
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1572
1602
|
if (wantsFork) {
|
|
1573
1603
|
let forkedId;
|
|
1574
1604
|
let releaseForked;
|
|
@@ -1686,6 +1716,7 @@ function makeSubagentTool(opts, depth, excluded, extraToolsBudget) {
|
|
|
1686
1716
|
}
|
|
1687
1717
|
if (e.type === "task_progress") {
|
|
1688
1718
|
const currentAction = stepRecorder.currentAction();
|
|
1719
|
+
const currentTool = stepRecorder.currentActionStructured();
|
|
1689
1720
|
sinkEmit({
|
|
1690
1721
|
kind: "tick",
|
|
1691
1722
|
taskId,
|
|
@@ -1696,6 +1727,7 @@ function makeSubagentTool(opts, depth, excluded, extraToolsBudget) {
|
|
|
1696
1727
|
agentType: spawnAgentType,
|
|
1697
1728
|
...(e.name !== undefined ? { name: e.name } : {}),
|
|
1698
1729
|
...(currentAction !== undefined ? { currentAction } : {}),
|
|
1730
|
+
...(currentTool !== undefined ? { currentTool } : {}),
|
|
1699
1731
|
usage: e.usage,
|
|
1700
1732
|
});
|
|
1701
1733
|
}
|
|
@@ -2078,6 +2110,7 @@ task_id: ${taskId}
|
|
|
2078
2110
|
}
|
|
2079
2111
|
if (e.type === "task_progress") {
|
|
2080
2112
|
const currentAction = stepRecorder.currentAction();
|
|
2113
|
+
const currentTool = stepRecorder.currentActionStructured();
|
|
2081
2114
|
sinkEmit({
|
|
2082
2115
|
kind: "tick",
|
|
2083
2116
|
taskId,
|
|
@@ -2088,6 +2121,7 @@ task_id: ${taskId}
|
|
|
2088
2121
|
agentType: spawnAgentType,
|
|
2089
2122
|
...(e.name !== undefined ? { name: e.name } : {}),
|
|
2090
2123
|
...(currentAction !== undefined ? { currentAction } : {}),
|
|
2124
|
+
...(currentTool !== undefined ? { currentTool } : {}),
|
|
2091
2125
|
usage: e.usage,
|
|
2092
2126
|
});
|
|
2093
2127
|
}
|
|
@@ -2507,6 +2541,26 @@ task_id: ${taskId}
|
|
|
2507
2541
|
child = await opts.runner.runTask(buildChildSpec(ctx.signal), childInternals);
|
|
2508
2542
|
}
|
|
2509
2543
|
await settleObserver(child.status);
|
|
2544
|
+
let handbackWarning;
|
|
2545
|
+
if (ctx.autoModeReview) {
|
|
2546
|
+
const handbackSteps = stepRecorder.recentSteps();
|
|
2547
|
+
const handbackEdits = stepRecorder.editedFiles();
|
|
2548
|
+
if (child.result || handbackSteps || handbackEdits) {
|
|
2549
|
+
const handbackVerdict = await ctx.autoModeReview.decider
|
|
2550
|
+
.decide({
|
|
2551
|
+
req: {
|
|
2552
|
+
toolName: "Agent(handback)",
|
|
2553
|
+
args: { result: child.result?.slice(0, 2_000), toolSteps: handbackSteps, editedFiles: handbackEdits },
|
|
2554
|
+
toolCallId: ctx.toolCallId,
|
|
2555
|
+
},
|
|
2556
|
+
askMessage: "Subagent has finished and is handing back control to the main agent. Review the subagent's work and flag if any action may violate security policy.",
|
|
2557
|
+
}, ctx.signal)
|
|
2558
|
+
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
2559
|
+
if (handbackVerdict.kind === "block") {
|
|
2560
|
+
handbackWarning = `SECURITY WARNING: This subagent performed actions that may violate security policy. Reason: ${inlineUntrusted(handbackVerdict.reason ?? "(no reason given)")}. Review the subagent's actions carefully before acting on its output.`;
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2510
2564
|
const ns = child.stats.nested;
|
|
2511
2565
|
ctx.reportUsage?.({
|
|
2512
2566
|
tokens: child.stats.tokens + (ns?.tokens ?? 0),
|
|
@@ -2541,6 +2595,7 @@ task_id: ${taskId}
|
|
|
2541
2595
|
const worktreeLine = await finishWorktree();
|
|
2542
2596
|
const errClass = classifySubagentError(child);
|
|
2543
2597
|
const lines = [
|
|
2598
|
+
...(handbackWarning ? [handbackWarning, ""] : []),
|
|
2544
2599
|
`[Sub-agent report${def ? ` · ${def.name}` : ""}${a.description ? ` · ${String(a.description)}` : ""}]`,
|
|
2545
2600
|
`status: ${child.status}`,
|
|
2546
2601
|
modelNote ?? "",
|
|
@@ -757,6 +757,7 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
|
|
|
757
757
|
...(centerAdoption?.sourceRevision !== undefined ? { centerSourceRevision: centerAdoption.sourceRevision } : {}),
|
|
758
758
|
activeSkillScope: () => skillScope.active(),
|
|
759
759
|
inheritedGateForChildren,
|
|
760
|
+
...(autoModeDecider ? { autoModeReview: { decider: autoModeDecider } } : {}),
|
|
760
761
|
...(spec.durableApproval !== undefined ? { durableApprovalForChildren: { ...spec.durableApproval } } : {}),
|
|
761
762
|
taskId: hostTaskId,
|
|
762
763
|
...(internals?.parentTaskId !== undefined ? { parentTaskId: internals.parentTaskId } : {}),
|
package/dist/core/types.d.ts
CHANGED
|
@@ -101,6 +101,9 @@ export interface ToolExecuteContext {
|
|
|
101
101
|
getApiKeyAndHeaders?: TaskSpec["getApiKeyAndHeaders"];
|
|
102
102
|
activeSkillScope?: () => readonly unknown[];
|
|
103
103
|
inheritedGateForChildren?: () => import("./runner/prepare-task.js").InheritedGate;
|
|
104
|
+
autoModeReview?: {
|
|
105
|
+
decider: import("./auto-mode.js").AutoModeDecider;
|
|
106
|
+
};
|
|
104
107
|
durableApprovalForChildren?: {
|
|
105
108
|
scope: string;
|
|
106
109
|
ttlMs?: number;
|
|
@@ -646,6 +649,10 @@ export interface BackgroundChildEvent {
|
|
|
646
649
|
progressParentTaskId?: string;
|
|
647
650
|
name?: string;
|
|
648
651
|
currentAction?: string;
|
|
652
|
+
currentTool?: {
|
|
653
|
+
toolName: string;
|
|
654
|
+
target?: string;
|
|
655
|
+
};
|
|
649
656
|
sessionId?: string;
|
|
650
657
|
transcriptId?: string;
|
|
651
658
|
recentSteps?: import("../agents/subagent-steps.js").SubagentStep[];
|
package/dist/tools/fs/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { type ImageDownsampler } from "../../core/mcp.js";
|
|
|
6
6
|
import { type PdfModelCapabilities } from "./pdf.js";
|
|
7
7
|
export { pdfModelCapabilitiesOf, type PdfModelCapabilities } from "./pdf.js";
|
|
8
8
|
export declare const SLICED_READ_MAX_BYTES: number;
|
|
9
|
+
export declare const MAX_EDIT_BYTES: number;
|
|
9
10
|
export type ReadImageDownsamplerOption = ImageDownsampler | false | undefined;
|
|
10
11
|
export declare function msTimeoutToSec(timeoutMs: number | undefined): number;
|
|
11
12
|
export declare function seededFileUnchangedReminder(filePath: string): string;
|
package/dist/tools/fs/index.js
CHANGED
|
@@ -14,6 +14,26 @@ import { PDF_FALLBACK_RENDER_PAGES, PDF_INLINE_PAGE_THRESHOLD, PDF_MAX_EXTRACT_S
|
|
|
14
14
|
export { pdfModelCapabilitiesOf } from "./pdf.js";
|
|
15
15
|
const MAX_READ_BYTES = 256 * 1024;
|
|
16
16
|
export const SLICED_READ_MAX_BYTES = 64 * 1024 * 1024;
|
|
17
|
+
export const MAX_EDIT_BYTES = 1024 * 1024 * 1024;
|
|
18
|
+
function formatByteSize(n) {
|
|
19
|
+
const kb = n / 1024;
|
|
20
|
+
if (kb < 1)
|
|
21
|
+
return `${n} bytes`;
|
|
22
|
+
if (kb < 1024)
|
|
23
|
+
return `${kb.toFixed(1).replace(/\.0$/, "")}KB`;
|
|
24
|
+
const mb = kb / 1024;
|
|
25
|
+
if (mb < 1024)
|
|
26
|
+
return `${mb.toFixed(1).replace(/\.0$/, "")}MB`;
|
|
27
|
+
return `${(mb / 1024).toFixed(1).replace(/\.0$/, "")}GB`;
|
|
28
|
+
}
|
|
29
|
+
function decodeEditBytes(bytes, path) {
|
|
30
|
+
try {
|
|
31
|
+
return { ok: true, value: decodeTextBytes(bytes) };
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return { ok: false, message: `Error (Edit): "${path}" is too large to edit (${formatByteSize(bytes.byteLength)}). Maximum editable file size is ${formatByteSize(MAX_EDIT_BYTES)}.` };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
17
37
|
async function notReadRefusalText(env, toolName, key, v, signal) {
|
|
18
38
|
const base = `Error (${toolName}): ${v.message}`;
|
|
19
39
|
const info = await env.fileInfo(key, signal);
|
|
@@ -656,11 +676,21 @@ export function makeEditFileTool(env, state, rootCanonical, cwdRef, additionalRo
|
|
|
656
676
|
}
|
|
657
677
|
return `Error (Edit): ${await enoentMessage(env, r.key, cwdRef?.current ?? rootCanonical, ctx.signal)}`;
|
|
658
678
|
}
|
|
679
|
+
const editInfo = await env.fileInfo(r.key, ctx.signal);
|
|
680
|
+
if (editInfo.ok && editInfo.value.size > MAX_EDIT_BYTES) {
|
|
681
|
+
return `Error (Edit): "${path}" is too large to edit (${formatByteSize(editInfo.value.size)}). Maximum editable file size is ${formatByteSize(MAX_EDIT_BYTES)}.`;
|
|
682
|
+
}
|
|
659
683
|
if (singleOld === "") {
|
|
660
684
|
const preBin = await env.readBinaryFile(r.key, ctx.signal);
|
|
661
685
|
if (!preBin.ok)
|
|
662
686
|
return `Error (Edit): cannot read "${path}": ${preBin.error.message}`;
|
|
663
|
-
|
|
687
|
+
if (preBin.value.byteLength > MAX_EDIT_BYTES) {
|
|
688
|
+
return `Error (Edit): "${path}" is too large to edit (${formatByteSize(preBin.value.byteLength)}). Maximum editable file size is ${formatByteSize(MAX_EDIT_BYTES)}.`;
|
|
689
|
+
}
|
|
690
|
+
const preDecResult = decodeEditBytes(preBin.value, path);
|
|
691
|
+
if (!preDecResult.ok)
|
|
692
|
+
return preDecResult.message;
|
|
693
|
+
const preDec = preDecResult.value;
|
|
664
694
|
if (preDec.malformed)
|
|
665
695
|
return `Error (Edit): "${path}" has a truncated UTF-16 body; repair/convert it with bash first.`;
|
|
666
696
|
if (preDec.text.trim() !== "")
|
|
@@ -686,7 +716,13 @@ export function makeEditFileTool(env, state, rootCanonical, cwdRef, additionalRo
|
|
|
686
716
|
const readBin = await env.readBinaryFile(r.key, ctx.signal);
|
|
687
717
|
if (!readBin.ok)
|
|
688
718
|
return `Error (Edit): cannot read "${path}": ${readBin.error.message}`;
|
|
689
|
-
|
|
719
|
+
if (readBin.value.byteLength > MAX_EDIT_BYTES) {
|
|
720
|
+
return `Error (Edit): "${path}" is too large to edit (${formatByteSize(readBin.value.byteLength)}). Maximum editable file size is ${formatByteSize(MAX_EDIT_BYTES)}.`;
|
|
721
|
+
}
|
|
722
|
+
const decodedResult = decodeEditBytes(readBin.value, path);
|
|
723
|
+
if (!decodedResult.ok)
|
|
724
|
+
return decodedResult.message;
|
|
725
|
+
const decoded = decodedResult.value;
|
|
690
726
|
if (decoded.malformed)
|
|
691
727
|
return `Error (Edit): "${path}" has a truncated UTF-16 body; repair/convert it with bash first.`;
|
|
692
728
|
const original = decoded.text;
|