@sema-agent/core 1.426.0 → 1.428.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.js +9 -0
- package/dist/core/runner/prepare-task.js +5 -0
- package/dist/core/tool-policy.d.ts +2 -0
- package/dist/core/tool-policy.js +21 -10
- package/dist/core/types.d.ts +1 -0
- package/dist/engine/execution-env/node-execution-env.d.ts +2 -0
- package/dist/engine/execution-env/node-execution-env.js +26 -7
- package/dist/engine/harness/types.d.ts +2 -0
- package/dist/orchestration/run-workflow-tool.js +3 -1
- package/dist/tools/fs/index.d.ts +3 -0
- package/dist/tools/fs/index.js +60 -4
- package/package.json +1 -1
package/dist/agents/subagent.js
CHANGED
|
@@ -528,6 +528,7 @@ function makeSubagentResume(deps) {
|
|
|
528
528
|
...(deps.rowAgentType !== undefined ? { agentType: deps.rowAgentType } : {}),
|
|
529
529
|
...(deps.rowName !== undefined ? { name: deps.rowName } : {}),
|
|
530
530
|
sessionId: entry.childSessionId,
|
|
531
|
+
transcriptId: entry.childSessionId,
|
|
531
532
|
...(deps.rowParentTaskId !== undefined ? { parentTaskId: deps.rowParentTaskId } : {}),
|
|
532
533
|
...(deps.rowParentSessionId !== undefined ? { parentSessionId: deps.rowParentSessionId } : {}),
|
|
533
534
|
...(deps.rowRootSessionId !== undefined ? { rootSessionId: deps.rowRootSessionId } : {}),
|
|
@@ -553,6 +554,7 @@ function makeSubagentResume(deps) {
|
|
|
553
554
|
taskId: deps.taskId,
|
|
554
555
|
sessionScoped: deps.sessionScoped === true,
|
|
555
556
|
...(deps.rowAgentType !== undefined ? { agentType: deps.rowAgentType } : {}),
|
|
557
|
+
transcriptId: entry.childSessionId,
|
|
556
558
|
progressTaskId: e.taskId,
|
|
557
559
|
...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
|
|
558
560
|
...(e.name !== undefined ? { name: e.name } : {}),
|
|
@@ -685,6 +687,7 @@ function makeSubagentResume(deps) {
|
|
|
685
687
|
taskId: deps.taskId,
|
|
686
688
|
sessionScoped: deps.sessionScoped === true,
|
|
687
689
|
sessionId: entry.childSessionId,
|
|
690
|
+
transcriptId: entry.childSessionId,
|
|
688
691
|
status: abort.signal.aborted ? "killed" : "failed",
|
|
689
692
|
seq: entry.cycleSeq,
|
|
690
693
|
...(stoppedByReject !== undefined ? { stoppedBy: stoppedByReject } : {}),
|
|
@@ -1657,6 +1660,7 @@ function makeSubagentTool(opts, depth, excluded, extraToolsBudget) {
|
|
|
1657
1660
|
agentType: spawnAgentType,
|
|
1658
1661
|
...(agentName !== undefined ? { name: agentName } : {}),
|
|
1659
1662
|
sessionId: forkedId,
|
|
1663
|
+
transcriptId: forkedId,
|
|
1660
1664
|
...(ctx.taskId !== undefined && ctx.taskId !== ctx.sessionId ? { parentTaskId: ctx.taskId } : {}),
|
|
1661
1665
|
...(ctx.sessionId !== undefined ? { parentSessionId: ctx.sessionId } : {}),
|
|
1662
1666
|
...((ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}),
|
|
@@ -1686,6 +1690,7 @@ function makeSubagentTool(opts, depth, excluded, extraToolsBudget) {
|
|
|
1686
1690
|
kind: "tick",
|
|
1687
1691
|
taskId,
|
|
1688
1692
|
sessionScoped: sessionScopedBg === true,
|
|
1693
|
+
transcriptId: forkedId,
|
|
1689
1694
|
progressTaskId: e.taskId,
|
|
1690
1695
|
...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
|
|
1691
1696
|
agentType: spawnAgentType,
|
|
@@ -1790,6 +1795,7 @@ function makeSubagentTool(opts, depth, excluded, extraToolsBudget) {
|
|
|
1790
1795
|
taskId,
|
|
1791
1796
|
sessionScoped: sessionScopedBg === true,
|
|
1792
1797
|
sessionId: forkedId,
|
|
1798
|
+
transcriptId: forkedId,
|
|
1793
1799
|
status: settledBg,
|
|
1794
1800
|
...(stoppedByBg !== undefined ? { stoppedBy: stoppedByBg } : {}),
|
|
1795
1801
|
summary: summaryBg,
|
|
@@ -2006,6 +2012,7 @@ task_id: ${taskId}
|
|
|
2006
2012
|
agentType: spawnAgentType,
|
|
2007
2013
|
...(agentName !== undefined ? { name: agentName } : {}),
|
|
2008
2014
|
sessionId: bgChildSessionId,
|
|
2015
|
+
transcriptId: bgChildSessionId,
|
|
2009
2016
|
...(reviveRow !== undefined
|
|
2010
2017
|
? {
|
|
2011
2018
|
...(reviveRow.parentTaskId !== undefined ? { parentTaskId: reviveRow.parentTaskId } : {}),
|
|
@@ -2075,6 +2082,7 @@ task_id: ${taskId}
|
|
|
2075
2082
|
kind: "tick",
|
|
2076
2083
|
taskId,
|
|
2077
2084
|
sessionScoped: sessionScopedBg === true,
|
|
2085
|
+
transcriptId: bgChildSessionId,
|
|
2078
2086
|
progressTaskId: e.taskId,
|
|
2079
2087
|
...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
|
|
2080
2088
|
agentType: spawnAgentType,
|
|
@@ -2388,6 +2396,7 @@ task_id: ${taskId}
|
|
|
2388
2396
|
taskId,
|
|
2389
2397
|
sessionScoped: sessionScopedBg === true,
|
|
2390
2398
|
sessionId: bgChildSessionId,
|
|
2399
|
+
transcriptId: bgChildSessionId,
|
|
2391
2400
|
status: settled,
|
|
2392
2401
|
...(seqAtSettle !== undefined ? { seq: seqAtSettle } : {}),
|
|
2393
2402
|
...(stoppedBy !== undefined ? { stoppedBy } : {}),
|
|
@@ -1164,6 +1164,7 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
|
|
|
1164
1164
|
...(deps.hands?.bashReadonlyAllow !== undefined ? { bashReadonlyAllow: deps.hands.bashReadonlyAllow } : {}),
|
|
1165
1165
|
...(deps.hands?.commitCoAuthor !== undefined ? { commitCoAuthor: deps.hands.commitCoAuthor } : {}),
|
|
1166
1166
|
...(deps.hands?.readImageDownsampler !== undefined ? { readImageDownsampler: deps.hands.readImageDownsampler } : {}),
|
|
1167
|
+
...(deps.hands?.autoBackgroundOnTimeout !== undefined ? { autoBackgroundOnTimeout: deps.hands.autoBackgroundOnTimeout } : {}),
|
|
1167
1168
|
beforeWrite: async (w) => {
|
|
1168
1169
|
const deploymentGate = deps.hands?.beforeWrite;
|
|
1169
1170
|
if (deploymentGate) {
|
|
@@ -2320,6 +2321,7 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
|
|
|
2320
2321
|
args: editArgs,
|
|
2321
2322
|
message: re.message ?? re.reason ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
2322
2323
|
...askSourceIdentity(),
|
|
2324
|
+
...(re.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
2323
2325
|
}, onAskOf, csignal ?? abortController.signal);
|
|
2324
2326
|
if (rr.action !== "allow")
|
|
2325
2327
|
return rr;
|
|
@@ -2365,6 +2367,7 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
|
|
|
2365
2367
|
args: presentedArgs,
|
|
2366
2368
|
message: first.message ?? first.reason ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
2367
2369
|
...askSourceIdentity(),
|
|
2370
|
+
...(first.action === "ask" && first.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
2368
2371
|
}, pc.onAsk, csignal ?? abortController.signal);
|
|
2369
2372
|
const askWaitMs = Math.max(0, now() - askT0);
|
|
2370
2373
|
if (resolved.action === "deny" && resolved.approverUnavailable === true) {
|
|
@@ -2420,6 +2423,7 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
|
|
|
2420
2423
|
args: presentedArgs,
|
|
2421
2424
|
message: decision.message ?? decision.reason ?? `approval required for "${creq.toolName}" (inherited parent policy)`,
|
|
2422
2425
|
...askSourceIdentity(),
|
|
2426
|
+
...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
2423
2427
|
}, pc.onAsk, csignal ?? abortController.signal);
|
|
2424
2428
|
const askWaitMs = Math.max(0, now() - askT0);
|
|
2425
2429
|
if (resolved.action === "deny" && resolved.approverUnavailable === true) {
|
|
@@ -2654,6 +2658,7 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
|
|
|
2654
2658
|
})(),
|
|
2655
2659
|
message: decision.message ?? decision.reason ?? `approval required for "${req.toolName}"`,
|
|
2656
2660
|
...askSourceIdentity(),
|
|
2661
|
+
...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
2657
2662
|
}, onAsk, abortController.signal);
|
|
2658
2663
|
const waitMs = Math.max(0, now() - t0);
|
|
2659
2664
|
if (resolved.approverUnavailable !== true) {
|
|
@@ -21,6 +21,7 @@ export type PermissionResult = {
|
|
|
21
21
|
message?: string;
|
|
22
22
|
reason?: string;
|
|
23
23
|
decisionReason?: DecisionReason;
|
|
24
|
+
requiresRealApproval?: boolean;
|
|
24
25
|
} | {
|
|
25
26
|
action: "deny";
|
|
26
27
|
updatedInput?: unknown;
|
|
@@ -82,6 +83,7 @@ export interface AskRequest {
|
|
|
82
83
|
readonly sourceTaskId?: string;
|
|
83
84
|
readonly fromSubagent?: true;
|
|
84
85
|
readonly sourceAgentName?: string;
|
|
86
|
+
readonly requiresRealApproval?: boolean;
|
|
85
87
|
}
|
|
86
88
|
export type OnAsk = "deny" | "allow" | ((req: AskRequest, signal?: AbortSignal) => AskOutcome | Promise<AskOutcome>);
|
|
87
89
|
export type AskOutcome = boolean | "unavailable" | {
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -380,9 +380,10 @@ export function createUnverifiableDeletePolicy(opts) {
|
|
|
380
380
|
if (finding === undefined)
|
|
381
381
|
return ALLOW;
|
|
382
382
|
return {
|
|
383
|
-
action: "
|
|
383
|
+
action: "ask",
|
|
384
384
|
decisionReason: "safety",
|
|
385
|
-
|
|
385
|
+
requiresRealApproval: true,
|
|
386
|
+
reason: `Unverifiable recursive delete (fail-closed unless cleared): ${finding}. ` +
|
|
386
387
|
`Re-run the delete with the resolved literal path written into the command itself ` +
|
|
387
388
|
`(or assign the variable in the same command, e.g. \`DIR=/exact/path; rm -rf "$DIR"\`) so the target can be verified.`,
|
|
388
389
|
};
|
|
@@ -410,13 +411,14 @@ export function createTranscriptIntegrityPolicy(opts) {
|
|
|
410
411
|
return false;
|
|
411
412
|
return dirs.some((d) => abs === d || abs.startsWith(d + sep));
|
|
412
413
|
};
|
|
413
|
-
const
|
|
414
|
-
action: "
|
|
414
|
+
const askReason = (what) => ({
|
|
415
|
+
action: "ask",
|
|
415
416
|
decisionReason: "safety",
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
`
|
|
419
|
-
`
|
|
417
|
+
requiresRealApproval: true,
|
|
418
|
+
reason: `Session-transcript write (fail-closed unless cleared): ${what}. Session transcripts (the .jsonl files ` +
|
|
419
|
+
`under the agent data dir's sessions/ directory) are harness-written session state, not agent working ` +
|
|
420
|
+
`files — modifying or deleting them tampers with the run's own audit trail. Reading them (ls/cat/grep ` +
|
|
421
|
+
`as a single simple command) is fine.`,
|
|
420
422
|
});
|
|
421
423
|
return {
|
|
422
424
|
check(req) {
|
|
@@ -431,7 +433,7 @@ export function createTranscriptIntegrityPolicy(opts) {
|
|
|
431
433
|
const parsed = parseLeadingCommandName(command);
|
|
432
434
|
if ("name" in parsed && readAllow.has(parsed.name))
|
|
433
435
|
return ALLOW;
|
|
434
|
-
return
|
|
436
|
+
return askReason("name" in parsed
|
|
435
437
|
? `\`${parsed.name}\` is not a read-only command`
|
|
436
438
|
: "the command references the transcript directory and is not a single read-only command (fail-closed)");
|
|
437
439
|
}
|
|
@@ -439,7 +441,7 @@ export function createTranscriptIntegrityPolicy(opts) {
|
|
|
439
441
|
const a = req.args;
|
|
440
442
|
const p = a?.file_path ?? a?.notebook_path;
|
|
441
443
|
if (typeof p === "string" && inProtectedDir(p)) {
|
|
442
|
-
return
|
|
444
|
+
return askReason(`"${p}" is inside the session-transcript directory`);
|
|
443
445
|
}
|
|
444
446
|
}
|
|
445
447
|
return ALLOW;
|
|
@@ -486,6 +488,15 @@ function containsSharedMemory(v, seen = new Set()) {
|
|
|
486
488
|
}
|
|
487
489
|
export async function resolveAsk(req, onAsk, signal) {
|
|
488
490
|
if (onAsk === "allow") {
|
|
491
|
+
if (req.requiresRealApproval === true) {
|
|
492
|
+
return {
|
|
493
|
+
action: "deny",
|
|
494
|
+
message: `approval required for "${req.toolName}" but the wired approver is a blanket "allow" (no ` +
|
|
495
|
+
`judgment was applied) — this decision requires an actual auto-mode classifier verdict or a ` +
|
|
496
|
+
`real approval callback, neither of which a blanket bypass can provide: ${req.message}`,
|
|
497
|
+
decisionReason: "mode",
|
|
498
|
+
};
|
|
499
|
+
}
|
|
489
500
|
return { action: "allow", decisionReason: "mode" };
|
|
490
501
|
}
|
|
491
502
|
if (onAsk === undefined || onAsk === "deny") {
|
package/dist/core/types.d.ts
CHANGED
|
@@ -146,6 +146,7 @@ export interface HandsBandOptions {
|
|
|
146
146
|
commitCoAuthor?: string | false;
|
|
147
147
|
readImageDownsampler?: ((input: Buffer, mimeType: string) => Promise<import("./mcp.js").DownsampledImage | undefined>) | false;
|
|
148
148
|
beforeWrite?: BeforeWriteHook;
|
|
149
|
+
autoBackgroundOnTimeout?: boolean;
|
|
149
150
|
}
|
|
150
151
|
export interface AgentDefinition {
|
|
151
152
|
name: string;
|
|
@@ -54,12 +54,14 @@ export declare class NodeExecutionEnv implements ExecutionEnv, BackgroundShellCa
|
|
|
54
54
|
kill: () => void;
|
|
55
55
|
}) => void;
|
|
56
56
|
onDetachAdopted?: () => void;
|
|
57
|
+
autoBackgroundOnTimeout?: boolean;
|
|
57
58
|
}): Promise<Result<{
|
|
58
59
|
stdout: string;
|
|
59
60
|
stderr: string;
|
|
60
61
|
exitCode: number;
|
|
61
62
|
detached?: {
|
|
62
63
|
shellId: string;
|
|
64
|
+
cause?: "timeout";
|
|
63
65
|
};
|
|
64
66
|
}, ExecutionError>>;
|
|
65
67
|
readTextFile(path: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
|
|
@@ -298,12 +298,14 @@ export class NodeExecutionEnv {
|
|
|
298
298
|
const stderrTail = new RollingTailBuffer();
|
|
299
299
|
let settled = false;
|
|
300
300
|
let timedOut = false;
|
|
301
|
+
let abortKillInFlight = false;
|
|
301
302
|
let callbackError;
|
|
302
303
|
let child;
|
|
303
304
|
const timeoutRef = {};
|
|
304
305
|
const forceSettleRef = {};
|
|
305
306
|
const spoolPumpRef = {};
|
|
306
307
|
const execSpoolRef = {};
|
|
308
|
+
const adoptOnTimeoutRef = {};
|
|
307
309
|
const pumpSpoolRef = {};
|
|
308
310
|
const drainSpoolRef = {};
|
|
309
311
|
const armForceSettle = (result) => {
|
|
@@ -327,6 +329,7 @@ export class NodeExecutionEnv {
|
|
|
327
329
|
return { stdout: markTruncated(so.text, so.droppedBytes), stderr: markTruncated(se.text, se.droppedBytes) };
|
|
328
330
|
};
|
|
329
331
|
const onAbort = () => {
|
|
332
|
+
abortKillInFlight = true;
|
|
330
333
|
if (child?.pid) {
|
|
331
334
|
killProcessTree(child.pid, { force: true });
|
|
332
335
|
}
|
|
@@ -415,9 +418,12 @@ export class NodeExecutionEnv {
|
|
|
415
418
|
try {
|
|
416
419
|
options.onSpawn({
|
|
417
420
|
pid: spawnedPid,
|
|
418
|
-
kill: () =>
|
|
419
|
-
|
|
420
|
-
|
|
421
|
+
kill: () => {
|
|
422
|
+
abortKillInFlight = true;
|
|
423
|
+
killProcessTree(spawnedPid, {
|
|
424
|
+
stillRunning: () => spawnedChild.exitCode === null && spawnedChild.signalCode === null,
|
|
425
|
+
});
|
|
426
|
+
},
|
|
421
427
|
});
|
|
422
428
|
}
|
|
423
429
|
catch {
|
|
@@ -428,6 +434,10 @@ export class NodeExecutionEnv {
|
|
|
428
434
|
timeoutMs === undefined
|
|
429
435
|
? undefined
|
|
430
436
|
: setTimeout(() => {
|
|
437
|
+
if (options?.autoBackgroundOnTimeout && !abortKillInFlight && adoptOnTimeoutRef.current) {
|
|
438
|
+
adoptOnTimeoutRef.current();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
431
441
|
timedOut = true;
|
|
432
442
|
if (child?.pid) {
|
|
433
443
|
killProcessTree(child.pid, { force: true });
|
|
@@ -543,13 +553,13 @@ export class NodeExecutionEnv {
|
|
|
543
553
|
spoolPumpRef.current = setInterval(pumpSpool, EXEC_SPOOL_PUMP_MS);
|
|
544
554
|
spoolPumpRef.current.unref?.();
|
|
545
555
|
}
|
|
546
|
-
const onDetach = () => {
|
|
556
|
+
const onDetach = (cause) => {
|
|
547
557
|
if (settled || !child)
|
|
548
|
-
return;
|
|
558
|
+
return false;
|
|
549
559
|
pumpSpoolRef.current?.();
|
|
550
560
|
const shellId = this.adoptRunningChild(child, stdoutTail.result(), stderrTail.result(), execSpoolRef.current !== undefined ? { spool: execSpoolRef.current, cursors: { out: spoolCursor.out, err: spoolCursor.err } } : undefined);
|
|
551
561
|
if (shellId === undefined)
|
|
552
|
-
return;
|
|
562
|
+
return false;
|
|
553
563
|
if (spoolPumpRef.current)
|
|
554
564
|
clearInterval(spoolPumpRef.current);
|
|
555
565
|
try {
|
|
@@ -561,7 +571,16 @@ export class NodeExecutionEnv {
|
|
|
561
571
|
clearTimeout(timeoutRef.current);
|
|
562
572
|
const dso = stdoutTail.result();
|
|
563
573
|
const dse = stderrTail.result();
|
|
564
|
-
settle(ok({ stdout: markTruncated(dso.text, dso.droppedBytes), stderr: markTruncated(dse.text, dse.droppedBytes), exitCode: 0, detached: { shellId } }));
|
|
574
|
+
settle(ok({ stdout: markTruncated(dso.text, dso.droppedBytes), stderr: markTruncated(dse.text, dse.droppedBytes), exitCode: 0, detached: { shellId, ...(cause !== undefined ? { cause } : {}) } }));
|
|
575
|
+
return true;
|
|
576
|
+
};
|
|
577
|
+
adoptOnTimeoutRef.current = () => {
|
|
578
|
+
if (onDetach("timeout"))
|
|
579
|
+
return;
|
|
580
|
+
timedOut = true;
|
|
581
|
+
if (child?.pid)
|
|
582
|
+
killProcessTree(child.pid, { force: true });
|
|
583
|
+
armForceSettle(err(new ExecutionError("timeout", `timeout:${options?.timeout}`, undefined, partialOutput())));
|
|
565
584
|
};
|
|
566
585
|
if (options?.detachSignal) {
|
|
567
586
|
if (options.detachSignal.aborted)
|
|
@@ -92,6 +92,7 @@ export interface ExecutionEnvExecOptions {
|
|
|
92
92
|
kill: () => void;
|
|
93
93
|
}) => void;
|
|
94
94
|
onDetachAdopted?: () => void;
|
|
95
|
+
autoBackgroundOnTimeout?: boolean;
|
|
95
96
|
}
|
|
96
97
|
export interface FileSystem {
|
|
97
98
|
cwd: string;
|
|
@@ -135,6 +136,7 @@ export interface Shell {
|
|
|
135
136
|
exitCode: number;
|
|
136
137
|
detached?: {
|
|
137
138
|
shellId: string;
|
|
139
|
+
cause?: "timeout";
|
|
138
140
|
};
|
|
139
141
|
}, ExecutionError>>;
|
|
140
142
|
cleanup(): Promise<void>;
|
|
@@ -360,6 +360,9 @@ export async function createRunWorkflowTool(d) {
|
|
|
360
360
|
const primitives = buildWorkflowPrimitives(wfCtx, governance, d.onAgentSpawn, d.parentThinking, principal);
|
|
361
361
|
return d.scriptRunner.run({ scriptSource: script, primitives, scriptArgs: effectiveArgs, signal: wfCtx.signal }).then((r) => r.result);
|
|
362
362
|
};
|
|
363
|
+
if (ctx.signal?.aborted) {
|
|
364
|
+
return structuredError("the launching tool call was aborted before the workflow could start — not started");
|
|
365
|
+
}
|
|
363
366
|
let handle;
|
|
364
367
|
const workflowTaskId = d.taskRegistry?.mintTaskId("workflow");
|
|
365
368
|
try {
|
|
@@ -379,7 +382,6 @@ export async function createRunWorkflowTool(d) {
|
|
|
379
382
|
...(resumeFromRunId !== undefined ? { resumeFromRunId } : {}),
|
|
380
383
|
...(d.onBackgroundChildEvent !== undefined ? { onBackgroundChildEvent: d.onBackgroundChildEvent } : {}),
|
|
381
384
|
scope: taskScope ?? d.scope,
|
|
382
|
-
signal: ctx.signal,
|
|
383
385
|
budget: lim.budget,
|
|
384
386
|
maxAgents: lim.maxAgents ?? DEFAULTS.maxAgents,
|
|
385
387
|
totalTimeoutMs: lim.totalTimeoutMs ?? DEFAULTS.totalTimeoutMs,
|
package/dist/tools/fs/index.d.ts
CHANGED
|
@@ -47,6 +47,7 @@ export interface ExecClampOption {
|
|
|
47
47
|
}) => () => void;
|
|
48
48
|
}
|
|
49
49
|
export declare function bashExitOneInterpretation(command: string): string | undefined;
|
|
50
|
+
export declare function canAutoBackground(command: string): boolean;
|
|
50
51
|
export declare function makeBashTool(env: ExecutionEnv, rootCanonical: string, coAuthor?: string | false, cwdRef?: CwdRef, taskOpts?: {
|
|
51
52
|
taskRegistry?: TaskRegistry;
|
|
52
53
|
taskOwner?: string;
|
|
@@ -57,6 +58,7 @@ export declare function makeBashTool(env: ExecutionEnv, rootCanonical: string, c
|
|
|
57
58
|
}) => void;
|
|
58
59
|
detachHub?: import("../../core/tool-detach.js").ToolDetachHub;
|
|
59
60
|
execClamp?: ExecClampOption;
|
|
61
|
+
autoBackgroundOnTimeout?: boolean;
|
|
60
62
|
}): AgentTool;
|
|
61
63
|
export declare function makeBashReadonlyTool(env: ExecutionEnv, rootCanonical: string, allow: ReadonlySet<string>, execClamp?: ExecClampOption): AgentTool;
|
|
62
64
|
export declare function makeBashOutputTool(env: ExecutionEnv): AgentTool;
|
|
@@ -78,6 +80,7 @@ export interface HandsToolkitOptions {
|
|
|
78
80
|
}) => void;
|
|
79
81
|
detachHub?: import("../../core/tool-detach.js").ToolDetachHub;
|
|
80
82
|
execClamp?: ExecClampOption;
|
|
83
|
+
autoBackgroundOnTimeout?: boolean;
|
|
81
84
|
readImageDownsampler?: ReadImageDownsamplerOption;
|
|
82
85
|
pdfModelCapabilities?: PdfModelCapabilities;
|
|
83
86
|
beforeWrite?: BeforeWriteHook;
|
package/dist/tools/fs/index.js
CHANGED
|
@@ -1359,6 +1359,57 @@ async function diagnoseDeadCwd(env, toolName, effectiveCwd, root, cwdRef) {
|
|
|
1359
1359
|
`It was likely deleted by a previous command (e.g. rm -rf on the current directory). There is no directory to reset to; ` +
|
|
1360
1360
|
`this task cannot recover its working tree.`);
|
|
1361
1361
|
}
|
|
1362
|
+
const AUTO_BACKGROUND_BLOCKED_FIRST_WORDS = new Set(["sleep"]);
|
|
1363
|
+
function stripAutoBackgroundWrapperPrefixes(segment) {
|
|
1364
|
+
let s = segment.trim();
|
|
1365
|
+
const wrappers = [
|
|
1366
|
+
/^timeout\s+(?:-[A-Za-z]\S*\s+)*\d+(?:\.\d+)?[smhd]?\s+/,
|
|
1367
|
+
/^time\s+/,
|
|
1368
|
+
/^nice(?:\s+-n?\s*-?\d+)?\s+/,
|
|
1369
|
+
/^nohup\s+/,
|
|
1370
|
+
/^command\s+/,
|
|
1371
|
+
/^builtin\s+/,
|
|
1372
|
+
/^stdbuf(?:\s+-[ioe]\S+)+\s+/,
|
|
1373
|
+
/^noglob\s+/,
|
|
1374
|
+
/^[A-Za-z_][A-Za-z0-9_]*=\S+\s+/,
|
|
1375
|
+
];
|
|
1376
|
+
let changed = true;
|
|
1377
|
+
while (changed) {
|
|
1378
|
+
changed = false;
|
|
1379
|
+
for (const re of wrappers) {
|
|
1380
|
+
const stripped = s.replace(re, "");
|
|
1381
|
+
if (stripped !== s) {
|
|
1382
|
+
s = stripped.trim();
|
|
1383
|
+
changed = true;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
return s;
|
|
1388
|
+
}
|
|
1389
|
+
function commandBasename(token) {
|
|
1390
|
+
const idx = token.lastIndexOf("/");
|
|
1391
|
+
return idx === -1 ? token : token.slice(idx + 1);
|
|
1392
|
+
}
|
|
1393
|
+
export function canAutoBackground(command) {
|
|
1394
|
+
const segments = command
|
|
1395
|
+
.split(/&&|\|\||;|\||\r?\n/)
|
|
1396
|
+
.map((s) => s.trim())
|
|
1397
|
+
.filter((s) => s.length > 0);
|
|
1398
|
+
if (segments.length === 0)
|
|
1399
|
+
return true;
|
|
1400
|
+
for (const seg of segments) {
|
|
1401
|
+
const tokens = stripAutoBackgroundWrapperPrefixes(seg).split(/\s+/);
|
|
1402
|
+
const word = commandBasename(tokens[0] ?? "");
|
|
1403
|
+
if (word === "git")
|
|
1404
|
+
return false;
|
|
1405
|
+
if (word === "xargs" && tokens.slice(1).some((t) => commandBasename(t) === "git"))
|
|
1406
|
+
return false;
|
|
1407
|
+
}
|
|
1408
|
+
const firstWord = commandBasename(stripAutoBackgroundWrapperPrefixes(segments[0]).split(/\s+/)[0] ?? "");
|
|
1409
|
+
if (AUTO_BACKGROUND_BLOCKED_FIRST_WORDS.has(firstWord))
|
|
1410
|
+
return false;
|
|
1411
|
+
return true;
|
|
1412
|
+
}
|
|
1362
1413
|
async function runShell(env, cwd, toolName, command, timeoutSec, signal, cwdRef, detach, execClamp, toolCallId) {
|
|
1363
1414
|
let timeout = Math.min(BASH_MAX_TIMEOUT_SEC, Math.max(1, Math.floor(timeoutSec ?? BASH_DEFAULT_TIMEOUT_SEC)));
|
|
1364
1415
|
const requestedSec = timeout;
|
|
@@ -1398,6 +1449,7 @@ async function runShell(env, cwd, toolName, command, timeoutSec, signal, cwdRef,
|
|
|
1398
1449
|
deregisterKill?.();
|
|
1399
1450
|
deregisterKill = undefined;
|
|
1400
1451
|
},
|
|
1452
|
+
autoBackgroundOnTimeout: detach.autoBackgroundEligible === true && !clampedByDeadline,
|
|
1401
1453
|
}
|
|
1402
1454
|
: {}),
|
|
1403
1455
|
...(registerKillHandle !== undefined && toolCallId !== undefined
|
|
@@ -1460,7 +1512,7 @@ async function runShell(env, cwd, toolName, command, timeoutSec, signal, cwdRef,
|
|
|
1460
1512
|
return `Error (${toolName}): command execution failed (${res.error.code}): ${res.error.message}. Whether the command started or completed could not be determined — verify its effects before retrying.`;
|
|
1461
1513
|
}
|
|
1462
1514
|
if (res.value.detached && detach) {
|
|
1463
|
-
return await detach.onDetached(res.value.detached.shellId, res.value.stdout, res.value.stderr);
|
|
1515
|
+
return await detach.onDetached(res.value.detached.shellId, res.value.stdout, res.value.stderr, res.value.detached.cause);
|
|
1464
1516
|
}
|
|
1465
1517
|
let { stderr } = res.value;
|
|
1466
1518
|
const { stdout, exitCode } = res.value;
|
|
@@ -1656,7 +1708,7 @@ export function makeBashTool(env, rootCanonical, coAuthor = false, cwdRef = { cu
|
|
|
1656
1708
|
const detachChain = taskOpts.detachHub && taskOpts.taskRegistry && hasBackgroundShell(env) && env.backgroundCapabilities.supportsDetach === true
|
|
1657
1709
|
? {
|
|
1658
1710
|
signal: taskOpts.detachHub.signalFor(ctx.toolCallId),
|
|
1659
|
-
onDetached: async (shellId, stdoutSoFar, stderrSoFar) => {
|
|
1711
|
+
onDetached: async (shellId, stdoutSoFar, stderrSoFar, cause) => {
|
|
1660
1712
|
const sessionScoped = taskOpts.sessionId !== undefined;
|
|
1661
1713
|
const owner = sessionScoped ? taskOpts.sessionId : (ctx.taskId ?? taskOpts.taskOwner);
|
|
1662
1714
|
const scope = ctx.principal ?? taskOpts.taskScope;
|
|
@@ -1720,7 +1772,9 @@ export function makeBashTool(env, rootCanonical, coAuthor = false, cwdRef = { cu
|
|
|
1720
1772
|
? ` Time budget: auto-terminates if still running after ${Math.min(adoptCaps.defaultBgTimeoutSec, adoptCaps.maxBgTimeoutSec)}s (hard cap ${adoptCaps.maxBgTimeoutSec}s).`
|
|
1721
1773
|
: "";
|
|
1722
1774
|
return {
|
|
1723
|
-
content:
|
|
1775
|
+
content: (cause === "timeout"
|
|
1776
|
+
? `Command exceeded its timeout but is still running — moved to background instead of being killed (task_id=${taskId}). `
|
|
1777
|
+
: `Command moved to background; task_id=${taskId}. `) +
|
|
1724
1778
|
(outputFile !== undefined
|
|
1725
1779
|
? `Output file: ${outputFile} (full output is appended there — Read it any time). `
|
|
1726
1780
|
: `Use TaskOutput("${taskId}") to read its output. `) +
|
|
@@ -1730,9 +1784,10 @@ export function makeBashTool(env, rootCanonical, coAuthor = false, cwdRef = { cu
|
|
|
1730
1784
|
: `Poll TaskOutput until its status is no longer "running".`) +
|
|
1731
1785
|
adoptBudgetNote +
|
|
1732
1786
|
(tail ? `\n--- output so far (tail) ---\n${tail}` : ""),
|
|
1733
|
-
details: { type: "bash", detached: true, task_id: taskId, ...(description !== undefined ? { description } : {}), ...(outputFile !== undefined ? { output_file: outputFile } : {}) },
|
|
1787
|
+
details: { type: "bash", detached: true, task_id: taskId, ...(description !== undefined ? { description } : {}), ...(outputFile !== undefined ? { output_file: outputFile } : {}), ...(cause === "timeout" ? { autoBackgrounded: true } : {}) },
|
|
1734
1788
|
};
|
|
1735
1789
|
},
|
|
1790
|
+
autoBackgroundEligible: taskOpts.autoBackgroundOnTimeout === true && canAutoBackground(command),
|
|
1736
1791
|
}
|
|
1737
1792
|
: undefined;
|
|
1738
1793
|
let res;
|
|
@@ -1882,6 +1937,7 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
|
|
|
1882
1937
|
onTaskNotification: opts.taskNotification,
|
|
1883
1938
|
detachHub: opts.detachHub,
|
|
1884
1939
|
execClamp: opts.execClamp,
|
|
1940
|
+
...(opts.autoBackgroundOnTimeout !== undefined ? { autoBackgroundOnTimeout: opts.autoBackgroundOnTimeout } : {}),
|
|
1885
1941
|
}));
|
|
1886
1942
|
if (!readOnly && mountBackgroundTaskTools && hasBackgroundShell(env)) {
|
|
1887
1943
|
tools.push(opts.taskRegistry
|