talon-agent 5.30.0 → 5.31.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/package.json
CHANGED
|
@@ -50,7 +50,15 @@ never a substitute for `report_result`.
|
|
|
50
50
|
|
|
51
51
|
{% if canSpawn %}You may spawn your own sub-agents with `spawn_agent` (current depth {{depth}}, cap {{maxDepth}}) when the work genuinely splits into independent pieces. You are then responsible for them: `wait_for_agent`, `send_to_agent`, `kill_agent`, and folding their reports into yours.{% else %}You are at the maximum sub-agent depth ({{maxDepth}}) — `spawn_agent` will be refused. Do this work yourself.{% endif %}
|
|
52
52
|
|
|
53
|
-
##
|
|
53
|
+
{% if scratchDir %}## Scratch space
|
|
54
|
+
|
|
55
|
+
Your private temp directory is `{{scratchDir}}` — `TMPDIR` points there for
|
|
56
|
+
your shells and tools. Put scratch files, clones and build output there
|
|
57
|
+
rather than in shared `/tmp`, where other agents are working. It is deleted
|
|
58
|
+
when you finish successfully and kept if your run fails, so leave anything
|
|
59
|
+
worth keeping somewhere permanent.
|
|
60
|
+
|
|
61
|
+
{% endif %}## Boundaries
|
|
54
62
|
|
|
55
63
|
- Do **not** message the user's chat directly unless the brief explicitly
|
|
56
64
|
tells you to. Your report goes to the agent or chat that spawned you, and
|
|
@@ -91,6 +91,9 @@ export async function runOneShotAgent(
|
|
|
91
91
|
systemPrompt,
|
|
92
92
|
...thinkingConfig,
|
|
93
93
|
cwd: workspace,
|
|
94
|
+
// A sub-agent's private TMPDIR (and any other per-run vars), layered
|
|
95
|
+
// over the daemon's environment — the SDK replaces, not merges, `env`.
|
|
96
|
+
...(params.env ? { env: { ...process.env, ...params.env } } : {}),
|
|
94
97
|
permissionMode: "bypassPermissions" as const,
|
|
95
98
|
allowDangerouslySkipPermissions: true,
|
|
96
99
|
abortController,
|
|
@@ -40,8 +40,11 @@ export function buildAgentSystemPrompt(args: {
|
|
|
40
40
|
parent: AgentParent;
|
|
41
41
|
depth: number;
|
|
42
42
|
maxDepth: number;
|
|
43
|
+
/** The agent's private TMPDIR, when one was created. */
|
|
44
|
+
scratchDir?: string;
|
|
43
45
|
}): string {
|
|
44
46
|
return loadSystemTemplate("agent-brief", {
|
|
47
|
+
scratchDir: args.scratchDir ?? "",
|
|
45
48
|
agentId: args.agentId,
|
|
46
49
|
label: args.label,
|
|
47
50
|
parent: describeParent(args.parent),
|
|
@@ -86,6 +86,7 @@ import { startWatchdog, type WatchdogHandle } from "./watchdog.js";
|
|
|
86
86
|
import * as agentsRepo from "../../storage/agents/repo.js";
|
|
87
87
|
import type { PersistedAgent } from "../../storage/agents/repo.js";
|
|
88
88
|
import { agentRegistry } from "./registry.js";
|
|
89
|
+
import { closeScratch, openScratch, scratchEnv } from "./scratch.js";
|
|
89
90
|
import type {
|
|
90
91
|
AgentCaps,
|
|
91
92
|
AgentParent,
|
|
@@ -428,6 +429,7 @@ async function buildRunParams(
|
|
|
428
429
|
abortController: AbortController,
|
|
429
430
|
capture: { last: string },
|
|
430
431
|
trail: RunTrail,
|
|
432
|
+
scratchDir: string | undefined,
|
|
431
433
|
resume?: ResumePlan,
|
|
432
434
|
): Promise<OneShotAgentParams> {
|
|
433
435
|
const writeLog = await openRunLog(
|
|
@@ -458,7 +460,9 @@ async function buildRunParams(
|
|
|
458
460
|
parent: record.parent,
|
|
459
461
|
depth: record.depth,
|
|
460
462
|
maxDepth: capsHolder.caps.maxDepth,
|
|
463
|
+
...(scratchDir ? { scratchDir } : {}),
|
|
461
464
|
}),
|
|
465
|
+
...(scratchDir ? { env: scratchEnv(scratchDir) } : {}),
|
|
462
466
|
workspace: dirs.workspace,
|
|
463
467
|
model,
|
|
464
468
|
contextLabel: agentContextLabel(record.id),
|
|
@@ -623,6 +627,7 @@ async function runAgent(
|
|
|
623
627
|
|
|
624
628
|
let settled: AgentRecord | null = null;
|
|
625
629
|
try {
|
|
630
|
+
const scratchDir = await openScratch(id);
|
|
626
631
|
const params = await buildRunParams(
|
|
627
632
|
record,
|
|
628
633
|
spec,
|
|
@@ -630,6 +635,7 @@ async function runAgent(
|
|
|
630
635
|
abortController,
|
|
631
636
|
capture,
|
|
632
637
|
trail,
|
|
638
|
+
scratchDir,
|
|
633
639
|
resume,
|
|
634
640
|
);
|
|
635
641
|
if (agentRegistry.isInterrupted(id)) {
|
|
@@ -708,6 +714,8 @@ async function runAgent(
|
|
|
708
714
|
return;
|
|
709
715
|
}
|
|
710
716
|
if (!settled) return;
|
|
717
|
+
// Removed on success; kept on failure for whoever picks the work up.
|
|
718
|
+
await closeScratch(id, settled.state);
|
|
711
719
|
log(
|
|
712
720
|
"agents",
|
|
713
721
|
`${id} "${settled.label}" → ${settled.state} ` +
|
|
@@ -802,6 +810,7 @@ async function settleRestored(
|
|
|
802
810
|
): Promise<void> {
|
|
803
811
|
const settled = agentRegistry.settle(record.id, patch);
|
|
804
812
|
if (!settled) return;
|
|
813
|
+
await closeScratch(record.id, settled.state);
|
|
805
814
|
log(
|
|
806
815
|
"agents",
|
|
807
816
|
`${record.id} "${record.label}" → ${settled.state} (after restart)`,
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scratch — a private temp directory per sub-agent.
|
|
3
|
+
*
|
|
4
|
+
* Every agent gets `/tmp/talon-agents/<id>/` (under `os.tmpdir()`), created
|
|
5
|
+
* when its run starts and exported as `TMPDIR` / `TMP` / `TEMP` to the
|
|
6
|
+
* backend run (`OneShotAgentParams.env`), so its shells and tools write
|
|
7
|
+
* their temporary files somewhere no other agent is writing. The directory
|
|
8
|
+
* is named in the agent's system prompt too, for backends that cannot take
|
|
9
|
+
* a per-run environment.
|
|
10
|
+
*
|
|
11
|
+
* Lifecycle: kept across a daemon restart (a resumed agent finds its files
|
|
12
|
+
* where it left them), removed when the agent settles `done`, and **kept**
|
|
13
|
+
* on any other terminal state so whoever picks up a failed, killed or
|
|
14
|
+
* timed-out run can inspect what it left behind.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
18
|
+
import { tmpdir } from "node:os";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { logWarn } from "../../util/log.js";
|
|
21
|
+
import type { AgentState } from "./types.js";
|
|
22
|
+
|
|
23
|
+
/** Root of every agent's scratch dir. Overridable for tests. */
|
|
24
|
+
const root: { dir: string } = { dir: join(tmpdir(), "talon-agents") };
|
|
25
|
+
|
|
26
|
+
export function setScratchRootForTest(dir: string): void {
|
|
27
|
+
root.dir = dir;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Absolute path of an agent's scratch dir (whether or not it exists). */
|
|
31
|
+
export function agentScratchDir(agentId: string): string {
|
|
32
|
+
return join(root.dir, agentId);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The env a run with this scratch dir is given. */
|
|
36
|
+
export function scratchEnv(dir: string): Record<string, string> {
|
|
37
|
+
return { TMPDIR: dir, TMP: dir, TEMP: dir };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create (or reuse, on resume) the agent's scratch dir. Returns its path,
|
|
42
|
+
* or `undefined` when it could not be created — a run is never refused for
|
|
43
|
+
* want of a temp dir; it just shares the system one.
|
|
44
|
+
*/
|
|
45
|
+
export async function openScratch(
|
|
46
|
+
agentId: string,
|
|
47
|
+
): Promise<string | undefined> {
|
|
48
|
+
const dir = agentScratchDir(agentId);
|
|
49
|
+
try {
|
|
50
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
51
|
+
return dir;
|
|
52
|
+
} catch (err) {
|
|
53
|
+
logWarn(
|
|
54
|
+
"agents",
|
|
55
|
+
`could not create scratch dir ${dir}: ${err instanceof Error ? err.message : String(err)}`,
|
|
56
|
+
);
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Clean up after a settled agent: removed on `done`, kept otherwise.
|
|
63
|
+
* Returns whether the directory was removed. Never throws.
|
|
64
|
+
*/
|
|
65
|
+
export async function closeScratch(
|
|
66
|
+
agentId: string,
|
|
67
|
+
state: AgentState,
|
|
68
|
+
): Promise<boolean> {
|
|
69
|
+
if (state !== "done") return false;
|
|
70
|
+
try {
|
|
71
|
+
await rm(agentScratchDir(agentId), { recursive: true, force: true });
|
|
72
|
+
return true;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
logWarn(
|
|
75
|
+
"agents",
|
|
76
|
+
`could not remove scratch dir for ${agentId}: ${err instanceof Error ? err.message : String(err)}`,
|
|
77
|
+
);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
package/src/core/types.ts
CHANGED
|
@@ -187,6 +187,13 @@ export type OneShotAgentParams = {
|
|
|
187
187
|
* `onAssistantText`: a throwing callback must never fail the run.
|
|
188
188
|
*/
|
|
189
189
|
onSessionId?: (sessionId: string) => void;
|
|
190
|
+
/**
|
|
191
|
+
* Extra environment for the run's own subprocesses (shells, tools),
|
|
192
|
+
* layered over the daemon's. A sub-agent passes its private scratch dir
|
|
193
|
+
* as `TMPDIR` here. Honoured by the Claude SDK backend; backends whose
|
|
194
|
+
* subprocess environment is fixed per process ignore it.
|
|
195
|
+
*/
|
|
196
|
+
env?: Readonly<Record<string, string>>;
|
|
190
197
|
};
|
|
191
198
|
|
|
192
199
|
/** How much cache telemetry a backend can surface in /status. */
|