@shanepadgett/tau-agent 0.33.1 → 0.34.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/docs/context.md +29 -20
- package/docs/extending-tau-agent.md +63 -24
- package/extensions/appshot/index.ts +112 -111
- package/extensions/aside/README.md +7 -0
- package/extensions/aside/index.ts +168 -0
- package/extensions/aside/panel.ts +83 -0
- package/extensions/branch/index.ts +38 -21
- package/extensions/cache-diagnostics/index.ts +70 -58
- package/extensions/checkpoint/README.md +9 -0
- package/extensions/checkpoint/checkpoint-budget.ts +79 -0
- package/extensions/checkpoint/checkpoint.ts +277 -0
- package/extensions/checkpoint/index.ts +102 -0
- package/extensions/checkpoint/messages.ts +169 -0
- package/extensions/checkpoint/prompt.ts +24 -0
- package/extensions/checkpoint/settings.ts +27 -0
- package/extensions/commit/commit-effort.ts +3 -3
- package/extensions/context/README.md +12 -8
- package/extensions/context/definitions.ts +178 -55
- package/extensions/context/index.ts +192 -160
- package/extensions/context/panel.ts +57 -37
- package/extensions/context/sync.ts +139 -89
- package/extensions/context/validation.ts +37 -25
- package/extensions/effort/README.md +1 -1
- package/extensions/effort/index.ts +3 -3
- package/extensions/effort/state.ts +3 -18
- package/extensions/explore/guidance.ts +6 -47
- package/extensions/explore/index.ts +38 -53
- package/extensions/explore/{ast/read → read}/hook.ts +12 -7
- package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
- package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
- package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
- package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
- package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
- package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
- package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
- package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
- package/extensions/explore/tools/target-params.ts +9 -0
- package/extensions/footer/index.ts +75 -56
- package/extensions/handoff/index.ts +8 -30
- package/extensions/image-gen/index.ts +46 -40
- package/extensions/patch/executor.ts +190 -135
- package/extensions/patch/matcher.ts +82 -48
- package/extensions/patch/parser.ts +149 -105
- package/extensions/patch/render.ts +32 -39
- package/extensions/patch/summary.ts +30 -26
- package/extensions/ready/README.md +7 -0
- package/extensions/ready/index.ts +58 -0
- package/extensions/ready/model.ts +128 -0
- package/extensions/ready/packs.ts +272 -0
- package/extensions/ready/render.ts +445 -0
- package/extensions/ready/scan.ts +760 -0
- package/extensions/reference/panel.ts +28 -16
- package/extensions/review/index.ts +63 -51
- package/extensions/review/panel.ts +5 -34
- package/extensions/runtime-context/context.ts +30 -26
- package/extensions/script-runner/README.md +2 -2
- package/extensions/script-runner/index.ts +122 -82
- package/extensions/silent-command-runner/index.ts +38 -23
- package/extensions/subagent/agents/context-sync.md +185 -33
- package/extensions/subagent/agents/scout.md +2 -2
- package/extensions/subagent/agents.ts +137 -84
- package/extensions/subagent/cmux-dashboard.ts +39 -30
- package/extensions/subagent/index.ts +79 -59
- package/extensions/subagent/resume.ts +1 -1
- package/extensions/subagent/run.ts +304 -193
- package/extensions/subagent/runtime.ts +781 -343
- package/extensions/tau/index.ts +47 -38
- package/extensions/tau-help/help.md +14 -6
- package/extensions/tool-loader/README.md +4 -2
- package/extensions/tool-loader/index.ts +77 -52
- package/extensions/web/index.ts +6 -3
- package/extensions/web/webfetch.ts +114 -86
- package/package.json +3 -3
- package/schemas/tau.schema.json +20 -33
- package/shared/bounded-text-result.ts +1 -0
- package/shared/checkpoint-visibility.ts +9 -0
- package/shared/events.ts +28 -10
- package/shared/isolated-session.ts +71 -29
- package/shared/model-effort.ts +9 -19
- package/shared/model-fallback/index.ts +66 -41
- package/shared/settings/load.ts +45 -38
- package/shared/temporary-output-store.ts +16 -10
- package/{extensions/explore → src}/ast/adapter.ts +11 -2
- package/{extensions/explore → src}/ast/engine.ts +54 -38
- package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
- package/{extensions/explore → src}/ast/format/context.ts +3 -6
- package/src/ast/format/deps.ts +94 -0
- package/{extensions/explore → src}/ast/format/discover.ts +2 -2
- package/{extensions/explore → src}/ast/format/impact.ts +9 -11
- package/{extensions/explore → src}/ast/format/outline.ts +2 -2
- package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
- package/{extensions/explore → src}/ast/format/show.ts +1 -1
- package/src/ast/format/targeted-result.ts +20 -0
- package/src/ast/grammars/c_sharp.wasm +0 -0
- package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
- package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
- package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
- package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
- package/{extensions/explore → src}/ast/ir.ts +6 -0
- package/src/ast/languages/csharp-file-deps.ts +90 -0
- package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
- package/src/ast/languages/dotted-index.ts +131 -0
- package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
- package/src/ast/languages/fixtures/sample.cs +53 -0
- package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
- package/src/ast/languages/fixtures/sample.go +37 -0
- package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
- package/src/ast/languages/fixtures/sample.java +40 -0
- package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
- package/src/ast/languages/fixtures/sample.kt +31 -0
- package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
- package/src/ast/languages/fixtures/sample.md +23 -0
- package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
- package/src/ast/languages/fixtures/sample.rs +54 -0
- package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
- package/src/ast/languages/fixtures/sample.swift +45 -0
- package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
- package/src/ast/languages/fixtures/sample.ts +45 -0
- package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
- package/src/ast/languages/fixtures/sample.tsx +36 -0
- package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
- package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
- package/{extensions/explore → src}/ast/languages/go.ts +21 -3
- package/{extensions/explore → src}/ast/languages/java.ts +5 -38
- package/src/ast/languages/jvm-file-deps.ts +135 -0
- package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
- package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
- package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
- package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
- package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
- package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
- package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
- package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
- package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
- package/{extensions/explore → src}/ast/markdown.ts +72 -47
- package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
- package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
- package/{extensions/explore → src}/ast/queries/context.ts +116 -80
- package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
- package/src/ast/queries/impact.ts +291 -0
- package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
- package/{extensions/explore → src}/ast/queries/show.ts +46 -42
- package/{extensions/explore → src}/ast/query.ts +1 -1
- package/{extensions/explore → src}/ast/registry.ts +1 -2
- package/{extensions/explore → src}/ast/scan.ts +1 -1
- package/src/ast/session.ts +50 -0
- package/{extensions/explore → src}/ast/slice.ts +8 -0
- package/{extensions/explore → src/ast}/traverse.ts +32 -20
- package/src/file-injection/index.ts +547 -0
- package/src/image-generation/client.ts +50 -31
- package/src/image-generation/index.ts +177 -134
- package/src/index.ts +2 -0
- package/src/tool-loading/index.ts +58 -0
- package/extensions/context/evidence.ts +0 -517
- package/extensions/context/projection.ts +0 -141
- package/extensions/context/state.ts +0 -30
- package/extensions/context/write-scope.ts +0 -109
- package/extensions/explore/ast/format/deps.ts +0 -86
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
- package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
- package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
- package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
- package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
- package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
- package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
- package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
- package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
- package/extensions/explore/ast/languages/odin.ts +0 -407
- package/extensions/explore/ast/queries/impact.ts +0 -250
- package/extensions/explore/outline-injection.ts +0 -151
- package/extensions/explore/read/autoread.ts +0 -118
- package/extensions/working-memory/README.md +0 -17
- package/extensions/working-memory/checkpoint.ts +0 -242
- package/extensions/working-memory/index.ts +0 -341
- package/extensions/working-memory/memory.ts +0 -235
- package/extensions/working-memory/render.ts +0 -185
- package/extensions/working-memory/settings.ts +0 -38
- package/extensions/working-memory/state.ts +0 -158
- package/shared/autoread.ts +0 -234
- package/shared/context-messages.ts +0 -19
- package/shared/outline-injection.ts +0 -56
- /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
- /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
- /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
- /package/{extensions/explore → src}/ast/identity.ts +0 -0
- /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
- /package/{extensions/explore/ast/read/policy.ts → src/ast/read-policy.ts} +0 -0
|
@@ -14,13 +14,13 @@ import {
|
|
|
14
14
|
} from "@earendil-works/pi-coding-agent";
|
|
15
15
|
import { createCompleteFileMeta } from "../../shared/full-file-knowledge.ts";
|
|
16
16
|
import { createIsolatedSessionResource, type IsolatedSessionResource } from "../../shared/isolated-session.ts";
|
|
17
|
+
import { FILE_INJECTION_TYPE } from "@shanepadgett/tau-agent";
|
|
17
18
|
import type { AgentDefinition } from "./agents.ts";
|
|
18
19
|
import { emptySubagentResumeState, type RetainedTurnOutcome, type SubagentResumeState } from "./resume.ts";
|
|
19
20
|
import { resolveSubagentSessionInputs, type SubagentSessionInputs } from "./session-resource.ts";
|
|
20
21
|
|
|
21
22
|
const PREVIEW_LIMIT = 600;
|
|
22
23
|
const VALUE_LIMIT = 180;
|
|
23
|
-
const AUTOREAD_MESSAGE_TYPE = "tau.autoread";
|
|
24
24
|
|
|
25
25
|
export interface SubagentAction {
|
|
26
26
|
tool: string;
|
|
@@ -163,7 +163,7 @@ function capped(value: unknown, limit = VALUE_LIMIT): string {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
/** Rolling tail so live previews keep changing after the limit. */
|
|
166
|
-
|
|
166
|
+
function cappedTail(value: unknown, limit = PREVIEW_LIMIT): string {
|
|
167
167
|
let text: string;
|
|
168
168
|
try {
|
|
169
169
|
text = typeof value === "string" ? value : (JSON.stringify(value) ?? String(value));
|
|
@@ -248,6 +248,285 @@ export async function disposeSubagentThread(thread: SubagentThread): Promise<voi
|
|
|
248
248
|
await thread.resource.dispose();
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
function emptyTurnUsage(): { usage: SubagentUsage; toolUsage: Usage } {
|
|
252
|
+
return {
|
|
253
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 },
|
|
254
|
+
toolUsage: {
|
|
255
|
+
input: 0,
|
|
256
|
+
output: 0,
|
|
257
|
+
cacheRead: 0,
|
|
258
|
+
cacheWrite: 0,
|
|
259
|
+
totalTokens: 0,
|
|
260
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function accumulateAssistantUsage(usage: SubagentUsage, toolUsage: Usage, message: AssistantMessage): void {
|
|
266
|
+
usage.input += message.usage.input;
|
|
267
|
+
usage.output += message.usage.output;
|
|
268
|
+
usage.cacheRead += message.usage.cacheRead;
|
|
269
|
+
usage.cacheWrite += message.usage.cacheWrite;
|
|
270
|
+
usage.cost += message.usage.cost.total;
|
|
271
|
+
usage.turns += 1;
|
|
272
|
+
toolUsage.input += message.usage.input;
|
|
273
|
+
toolUsage.output += message.usage.output;
|
|
274
|
+
toolUsage.cacheRead += message.usage.cacheRead;
|
|
275
|
+
toolUsage.cacheWrite += message.usage.cacheWrite;
|
|
276
|
+
toolUsage.totalTokens += message.usage.totalTokens;
|
|
277
|
+
toolUsage.cost.input += message.usage.cost.input;
|
|
278
|
+
toolUsage.cost.output += message.usage.cost.output;
|
|
279
|
+
toolUsage.cost.cacheRead += message.usage.cost.cacheRead;
|
|
280
|
+
toolUsage.cost.cacheWrite += message.usage.cost.cacheWrite;
|
|
281
|
+
toolUsage.cost.total += message.usage.cost.total;
|
|
282
|
+
if (message.usage.cacheWrite1h !== undefined)
|
|
283
|
+
toolUsage.cacheWrite1h = (toolUsage.cacheWrite1h ?? 0) + message.usage.cacheWrite1h;
|
|
284
|
+
if (message.usage.reasoning !== undefined)
|
|
285
|
+
toolUsage.reasoning = (toolUsage.reasoning ?? 0) + message.usage.reasoning;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function pushBoundedAction(details: SubagentDetails, action: SubagentAction): void {
|
|
289
|
+
details.actions.push(action);
|
|
290
|
+
if (details.actions.length <= 20) return;
|
|
291
|
+
const removable = details.actions.findIndex((item) => !item.error);
|
|
292
|
+
const [removed] = details.actions.splice(removable >= 0 ? removable : 0, 1);
|
|
293
|
+
details.omittedActions += 1;
|
|
294
|
+
if (removed?.error) details.omittedErrors += 1;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function subscribeTurnEvents(options: {
|
|
298
|
+
session: SubagentThread["resource"]["session"];
|
|
299
|
+
details: SubagentDetails;
|
|
300
|
+
usage: SubagentUsage;
|
|
301
|
+
toolUsage: Usage;
|
|
302
|
+
turnMessages: AssistantMessage[];
|
|
303
|
+
assistantMessageEndAt: number[];
|
|
304
|
+
publish: (force?: boolean) => void;
|
|
305
|
+
}): () => void {
|
|
306
|
+
const { session, details, usage, toolUsage, turnMessages, assistantMessageEndAt, publish } = options;
|
|
307
|
+
const actionById = new Map<string, string>();
|
|
308
|
+
return session.subscribe((event: AgentSessionEvent) => {
|
|
309
|
+
if (event.type === "message_update" && event.message.role === "assistant") {
|
|
310
|
+
details.response = cappedTail(textOf(event.message), PREVIEW_LIMIT);
|
|
311
|
+
publish();
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
315
|
+
turnMessages.push(event.message);
|
|
316
|
+
assistantMessageEndAt.push(Date.now());
|
|
317
|
+
accumulateAssistantUsage(usage, toolUsage, event.message);
|
|
318
|
+
details.response = cappedTail(textOf(event.message), PREVIEW_LIMIT);
|
|
319
|
+
details.currentActivity = undefined;
|
|
320
|
+
publish(true);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (event.type === "tool_execution_start") {
|
|
324
|
+
details.toolCalls += 1;
|
|
325
|
+
const summary = `${event.toolName} ${capped(event.args)}`.trim();
|
|
326
|
+
actionById.set(event.toolCallId, summary);
|
|
327
|
+
details.currentActivity = summary;
|
|
328
|
+
publish(true);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (event.type === "tool_execution_end") {
|
|
332
|
+
pushBoundedAction(details, {
|
|
333
|
+
tool: event.toolName,
|
|
334
|
+
summary: actionById.get(event.toolCallId) ?? event.toolName,
|
|
335
|
+
error: event.isError,
|
|
336
|
+
});
|
|
337
|
+
details.currentActivity = undefined;
|
|
338
|
+
publish(true);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function buildAutoreadMessages(options: { files: readonly string[]; cwd: string; invocationId: string }): Promise<
|
|
344
|
+
Array<{
|
|
345
|
+
customType: typeof FILE_INJECTION_TYPE;
|
|
346
|
+
content: string;
|
|
347
|
+
display: false;
|
|
348
|
+
details: Record<string, unknown>;
|
|
349
|
+
}>
|
|
350
|
+
> {
|
|
351
|
+
const { files, cwd, invocationId } = options;
|
|
352
|
+
return Promise.all(
|
|
353
|
+
files.map(async (file, index) => {
|
|
354
|
+
const pathKey = resolve(cwd, file);
|
|
355
|
+
const common = {
|
|
356
|
+
v: 1 as const,
|
|
357
|
+
rowId: `${invocationId}:${index}`,
|
|
358
|
+
path: file,
|
|
359
|
+
cwd,
|
|
360
|
+
source: "subagent",
|
|
361
|
+
batchId: invocationId,
|
|
362
|
+
};
|
|
363
|
+
try {
|
|
364
|
+
const bytes = await readFile(pathKey);
|
|
365
|
+
const content = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
366
|
+
const lines = content.split("\n");
|
|
367
|
+
const numbered = lines.map((line, lineIndex) => `${lineIndex + 1}: ${line}`).join("\n");
|
|
368
|
+
return {
|
|
369
|
+
customType: FILE_INJECTION_TYPE,
|
|
370
|
+
content: `${file}\n${numbered}`,
|
|
371
|
+
display: false as const,
|
|
372
|
+
details: {
|
|
373
|
+
...common,
|
|
374
|
+
kind: "full",
|
|
375
|
+
status: "injected",
|
|
376
|
+
readCache: createCompleteFileMeta({
|
|
377
|
+
pathKey,
|
|
378
|
+
presentation: "line-numbered",
|
|
379
|
+
servedHash: createHash("sha256").update(bytes).digest("hex"),
|
|
380
|
+
mode: "baseline",
|
|
381
|
+
sourceText: content,
|
|
382
|
+
returnedText: `${file}\n${numbered}`,
|
|
383
|
+
totalLines: lines.length,
|
|
384
|
+
summary: `${lines.length} lines`,
|
|
385
|
+
}),
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
} catch (error) {
|
|
389
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
390
|
+
return {
|
|
391
|
+
customType: FILE_INJECTION_TYPE,
|
|
392
|
+
content: `${file}\nInjection failed: ${message}`,
|
|
393
|
+
display: false as const,
|
|
394
|
+
details: { ...common, kind: "full", status: "failed", error: message },
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
}),
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function buildTurnPrompt(options: {
|
|
402
|
+
definition: AgentDefinition;
|
|
403
|
+
task: string;
|
|
404
|
+
files: readonly string[];
|
|
405
|
+
initial: boolean;
|
|
406
|
+
resumePrompt: string | undefined;
|
|
407
|
+
}): string {
|
|
408
|
+
const { definition, task, files, initial, resumePrompt } = options;
|
|
409
|
+
if (resumePrompt !== undefined) return resumePrompt;
|
|
410
|
+
const fileNote = files.length
|
|
411
|
+
? " Parent-supplied autoread files are included as line-numbered context for this turn."
|
|
412
|
+
: "";
|
|
413
|
+
if (initial) {
|
|
414
|
+
return `You are an isolated delegated child agent. Stay within the delegated task and return only the requested result.${fileNote}\n\n## Agent instructions\n${definition.prompt}\n\n## Delegated task\n${task}`;
|
|
415
|
+
}
|
|
416
|
+
return `Continue the existing delegated work using the context already in this thread. Return only the requested result.${fileNote}\n\n## Parent follow-up\n${task}`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async function applyTerminalOutcome(
|
|
420
|
+
details: SubagentDetails,
|
|
421
|
+
terminal: AssistantMessage,
|
|
422
|
+
definitionName: string,
|
|
423
|
+
): Promise<void> {
|
|
424
|
+
const response = textOf(terminal);
|
|
425
|
+
if (terminal.stopReason === "aborted") {
|
|
426
|
+
details.status = "aborted";
|
|
427
|
+
details.completionState = "aborted";
|
|
428
|
+
details.error = capped(terminal.errorMessage ?? "Child aborted");
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (terminal.stopReason === "error") {
|
|
432
|
+
details.status = "failed";
|
|
433
|
+
details.completionState = "error";
|
|
434
|
+
details.error = capped(terminal.errorMessage ?? "Child terminal error");
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if ((terminal.stopReason === "stop" || terminal.stopReason === "length") && response.trim()) {
|
|
438
|
+
details.status = "completed";
|
|
439
|
+
details.completionState = terminal.stopReason;
|
|
440
|
+
details.phase = "output";
|
|
441
|
+
const truncation = truncateHead(response, { maxBytes: DEFAULT_MAX_BYTES, maxLines: DEFAULT_MAX_LINES });
|
|
442
|
+
let returned = truncation.content;
|
|
443
|
+
let path: string | undefined;
|
|
444
|
+
if (truncation.truncated) {
|
|
445
|
+
const directory = await mkdtemp(join(tmpdir(), "tau-subagent-"));
|
|
446
|
+
path = join(directory, "output.md");
|
|
447
|
+
await writeFile(path, response, { encoding: "utf8", mode: 0o600 });
|
|
448
|
+
returned += `\n\n[Output truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}). Full output saved to: ${path}]`;
|
|
449
|
+
}
|
|
450
|
+
details.response = returned;
|
|
451
|
+
details.truncation = {
|
|
452
|
+
truncated: truncation.truncated,
|
|
453
|
+
path,
|
|
454
|
+
outputLines: truncation.outputLines,
|
|
455
|
+
totalLines: truncation.totalLines,
|
|
456
|
+
outputBytes: truncation.outputBytes,
|
|
457
|
+
totalBytes: truncation.totalBytes,
|
|
458
|
+
};
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
details.status = "failed";
|
|
462
|
+
details.completionState = terminal.stopReason;
|
|
463
|
+
details.error = `Agent ${definitionName} run failed: empty or non-terminal response`;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async function deliverTurnPrompt(options: {
|
|
467
|
+
session: SubagentThread["resource"]["session"];
|
|
468
|
+
thread: SubagentThread;
|
|
469
|
+
task: string;
|
|
470
|
+
files: readonly string[];
|
|
471
|
+
invocationId: string;
|
|
472
|
+
initial: boolean;
|
|
473
|
+
resumePrompt: string | undefined;
|
|
474
|
+
signal: AbortSignal;
|
|
475
|
+
}): Promise<void> {
|
|
476
|
+
const { session, thread, task, files, invocationId, initial, resumePrompt, signal } = options;
|
|
477
|
+
const abort = () => {
|
|
478
|
+
void session.abort().catch(() => undefined);
|
|
479
|
+
};
|
|
480
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
481
|
+
try {
|
|
482
|
+
const autoreadMessages = await buildAutoreadMessages({
|
|
483
|
+
files,
|
|
484
|
+
cwd: thread.cwd,
|
|
485
|
+
invocationId,
|
|
486
|
+
});
|
|
487
|
+
if (signal.aborted) throw new Error("Subagent call aborted before prompt");
|
|
488
|
+
for (const message of autoreadMessages) {
|
|
489
|
+
if (signal.aborted) throw new Error("Subagent call aborted during autoread delivery");
|
|
490
|
+
await session.sendCustomMessage<unknown>(message, { deliverAs: "nextTurn" });
|
|
491
|
+
}
|
|
492
|
+
if (signal.aborted) throw new Error("Subagent call aborted before prompt");
|
|
493
|
+
await session.prompt(buildTurnPrompt({ definition: thread.definition, task, files, initial, resumePrompt }), {
|
|
494
|
+
expandPromptTemplates: false,
|
|
495
|
+
});
|
|
496
|
+
} finally {
|
|
497
|
+
signal.removeEventListener("abort", abort);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function turnResult(
|
|
502
|
+
details: SubagentDetails,
|
|
503
|
+
retainable: boolean,
|
|
504
|
+
assistantMessageEndAt: readonly number[],
|
|
505
|
+
usage: SubagentUsage,
|
|
506
|
+
toolUsage: Usage,
|
|
507
|
+
definitionName: string,
|
|
508
|
+
): {
|
|
509
|
+
content: string;
|
|
510
|
+
details: SubagentDetails;
|
|
511
|
+
retainable: boolean;
|
|
512
|
+
terminalOutcome: RetainedTurnOutcome;
|
|
513
|
+
assistantMessageEndAt: readonly number[];
|
|
514
|
+
usage?: Usage;
|
|
515
|
+
} {
|
|
516
|
+
return {
|
|
517
|
+
content:
|
|
518
|
+
details.status === "completed"
|
|
519
|
+
? (details.response ?? "")
|
|
520
|
+
: (details.error ?? `Agent ${definitionName} failed`),
|
|
521
|
+
details,
|
|
522
|
+
retainable,
|
|
523
|
+
terminalOutcome:
|
|
524
|
+
details.status === "completed" ? "completed" : details.status === "aborted" ? "aborted" : "failed",
|
|
525
|
+
assistantMessageEndAt,
|
|
526
|
+
...(usage.turns === 0 ? {} : { usage: toolUsage }),
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
251
530
|
export async function runSubagentTurn(options: {
|
|
252
531
|
thread: SubagentThread;
|
|
253
532
|
task: string;
|
|
@@ -269,15 +548,7 @@ export async function runSubagentTurn(options: {
|
|
|
269
548
|
const { definition } = thread;
|
|
270
549
|
const { session } = thread.resource;
|
|
271
550
|
const started = Date.now();
|
|
272
|
-
const
|
|
273
|
-
const toolUsage: Usage = {
|
|
274
|
-
input: 0,
|
|
275
|
-
output: 0,
|
|
276
|
-
cacheRead: 0,
|
|
277
|
-
cacheWrite: 0,
|
|
278
|
-
totalTokens: 0,
|
|
279
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
280
|
-
};
|
|
551
|
+
const { usage, toolUsage } = emptyTurnUsage();
|
|
281
552
|
const details: SubagentDetails = {
|
|
282
553
|
agent: definition.name,
|
|
283
554
|
displayName: thread.displayName,
|
|
@@ -320,188 +591,35 @@ export async function runSubagentTurn(options: {
|
|
|
320
591
|
details.error = "Subagent call aborted before prompt";
|
|
321
592
|
details.durationMs = Date.now() - started;
|
|
322
593
|
publish(true);
|
|
323
|
-
return
|
|
324
|
-
content: details.error,
|
|
325
|
-
details,
|
|
326
|
-
retainable: false,
|
|
327
|
-
terminalOutcome: "aborted",
|
|
328
|
-
assistantMessageEndAt,
|
|
329
|
-
};
|
|
594
|
+
return turnResult(details, false, assistantMessageEndAt, usage, toolUsage, definition.name);
|
|
330
595
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
toolUsage.totalTokens += event.message.usage.totalTokens;
|
|
350
|
-
toolUsage.cost.input += event.message.usage.cost.input;
|
|
351
|
-
toolUsage.cost.output += event.message.usage.cost.output;
|
|
352
|
-
toolUsage.cost.cacheRead += event.message.usage.cost.cacheRead;
|
|
353
|
-
toolUsage.cost.cacheWrite += event.message.usage.cost.cacheWrite;
|
|
354
|
-
toolUsage.cost.total += event.message.usage.cost.total;
|
|
355
|
-
if (event.message.usage.cacheWrite1h !== undefined)
|
|
356
|
-
toolUsage.cacheWrite1h = (toolUsage.cacheWrite1h ?? 0) + event.message.usage.cacheWrite1h;
|
|
357
|
-
if (event.message.usage.reasoning !== undefined)
|
|
358
|
-
toolUsage.reasoning = (toolUsage.reasoning ?? 0) + event.message.usage.reasoning;
|
|
359
|
-
details.response = cappedTail(textOf(event.message), PREVIEW_LIMIT);
|
|
360
|
-
details.currentActivity = undefined;
|
|
361
|
-
publish(true);
|
|
362
|
-
} else if (event.type === "tool_execution_start") {
|
|
363
|
-
details.toolCalls += 1;
|
|
364
|
-
const summary = `${event.toolName} ${capped(event.args)}`.trim();
|
|
365
|
-
actionById.set(event.toolCallId, summary);
|
|
366
|
-
details.currentActivity = summary;
|
|
367
|
-
publish(true);
|
|
368
|
-
} else if (event.type === "tool_execution_end") {
|
|
369
|
-
const action = {
|
|
370
|
-
tool: event.toolName,
|
|
371
|
-
summary: actionById.get(event.toolCallId) ?? event.toolName,
|
|
372
|
-
error: event.isError,
|
|
373
|
-
};
|
|
374
|
-
details.actions.push(action);
|
|
375
|
-
if (details.actions.length > 20) {
|
|
376
|
-
const removable = details.actions.findIndex((item) => !item.error);
|
|
377
|
-
const [removed] = details.actions.splice(removable >= 0 ? removable : 0, 1);
|
|
378
|
-
details.omittedActions += 1;
|
|
379
|
-
if (removed?.error) details.omittedErrors += 1;
|
|
380
|
-
}
|
|
381
|
-
details.currentActivity = undefined;
|
|
382
|
-
publish(true);
|
|
383
|
-
}
|
|
596
|
+
unsubscribe = subscribeTurnEvents({
|
|
597
|
+
session,
|
|
598
|
+
details,
|
|
599
|
+
usage,
|
|
600
|
+
toolUsage,
|
|
601
|
+
turnMessages,
|
|
602
|
+
assistantMessageEndAt,
|
|
603
|
+
publish,
|
|
604
|
+
});
|
|
605
|
+
await deliverTurnPrompt({
|
|
606
|
+
session,
|
|
607
|
+
thread,
|
|
608
|
+
task,
|
|
609
|
+
files,
|
|
610
|
+
invocationId,
|
|
611
|
+
initial,
|
|
612
|
+
resumePrompt,
|
|
613
|
+
signal,
|
|
384
614
|
});
|
|
385
|
-
const abort = () => {
|
|
386
|
-
void session.abort().catch(() => undefined);
|
|
387
|
-
};
|
|
388
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
389
|
-
try {
|
|
390
|
-
const autoreadMessages = await Promise.all(
|
|
391
|
-
files.map(async (file, index) => {
|
|
392
|
-
const pathKey = resolve(thread.cwd, file);
|
|
393
|
-
const common = {
|
|
394
|
-
rowId: `${invocationId}:${index}`,
|
|
395
|
-
path: file,
|
|
396
|
-
cwd: thread.cwd,
|
|
397
|
-
source: "subagent",
|
|
398
|
-
batchId: invocationId,
|
|
399
|
-
};
|
|
400
|
-
try {
|
|
401
|
-
const bytes = await readFile(pathKey);
|
|
402
|
-
const content = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
403
|
-
const lines = content.split("\n");
|
|
404
|
-
const numbered = lines.map((line, lineIndex) => `${lineIndex + 1}: ${line}`).join("\n");
|
|
405
|
-
return {
|
|
406
|
-
customType: AUTOREAD_MESSAGE_TYPE,
|
|
407
|
-
content: `${file}\n${numbered}`,
|
|
408
|
-
display: false,
|
|
409
|
-
details: {
|
|
410
|
-
...common,
|
|
411
|
-
status: "read",
|
|
412
|
-
readCache: createCompleteFileMeta({
|
|
413
|
-
pathKey,
|
|
414
|
-
presentation: "line-numbered",
|
|
415
|
-
servedHash: createHash("sha256").update(bytes).digest("hex"),
|
|
416
|
-
mode: "baseline",
|
|
417
|
-
sourceText: content,
|
|
418
|
-
returnedText: `${file}\n${numbered}`,
|
|
419
|
-
totalLines: lines.length,
|
|
420
|
-
summary: `${lines.length} lines`,
|
|
421
|
-
}),
|
|
422
|
-
},
|
|
423
|
-
};
|
|
424
|
-
} catch (error) {
|
|
425
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
426
|
-
return {
|
|
427
|
-
customType: AUTOREAD_MESSAGE_TYPE,
|
|
428
|
-
content: `${file}\nAutoread failed: ${message}`,
|
|
429
|
-
display: false,
|
|
430
|
-
details: { ...common, status: "failed", error: message },
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
}),
|
|
434
|
-
);
|
|
435
|
-
if (signal.aborted) throw new Error("Subagent call aborted before prompt");
|
|
436
|
-
for (const message of autoreadMessages) {
|
|
437
|
-
if (signal.aborted) throw new Error("Subagent call aborted during autoread delivery");
|
|
438
|
-
await session.sendCustomMessage<unknown>(message, { deliverAs: "nextTurn" });
|
|
439
|
-
}
|
|
440
|
-
if (signal.aborted) throw new Error("Subagent call aborted before prompt");
|
|
441
|
-
await session.prompt(
|
|
442
|
-
resumePrompt ??
|
|
443
|
-
(initial
|
|
444
|
-
? `You are an isolated delegated child agent. Stay within the delegated task and return only the requested result.${files.length ? " Parent-supplied autoread files are included as line-numbered context for this turn." : ""}\n\n## Agent instructions\n${definition.prompt}\n\n## Delegated task\n${task}`
|
|
445
|
-
: `Continue the existing delegated work using the context already in this thread. Return only the requested result.${files.length ? " Parent-supplied autoread files are included as line-numbered context for this turn." : ""}\n\n## Parent follow-up\n${task}`),
|
|
446
|
-
{ expandPromptTemplates: false },
|
|
447
|
-
);
|
|
448
|
-
} finally {
|
|
449
|
-
signal.removeEventListener("abort", abort);
|
|
450
|
-
}
|
|
451
615
|
// Prompt returned without throw — session remains usable for retention decisions.
|
|
452
616
|
retainable = true;
|
|
453
617
|
const terminal = turnMessages.at(-1);
|
|
454
618
|
if (!terminal) throw new Error(`Agent ${definition.name} run failed: no terminal assistant response`);
|
|
455
|
-
|
|
456
|
-
if (terminal.stopReason === "aborted") {
|
|
457
|
-
details.status = "aborted";
|
|
458
|
-
details.completionState = "aborted";
|
|
459
|
-
details.error = capped(terminal.errorMessage ?? "Child aborted");
|
|
460
|
-
} else if (terminal.stopReason === "error") {
|
|
461
|
-
details.status = "failed";
|
|
462
|
-
details.completionState = "error";
|
|
463
|
-
details.error = capped(terminal.errorMessage ?? "Child terminal error");
|
|
464
|
-
} else if ((terminal.stopReason === "stop" || terminal.stopReason === "length") && response.trim()) {
|
|
465
|
-
details.status = "completed";
|
|
466
|
-
details.completionState = terminal.stopReason;
|
|
467
|
-
details.phase = "output";
|
|
468
|
-
const truncation = truncateHead(response, { maxBytes: DEFAULT_MAX_BYTES, maxLines: DEFAULT_MAX_LINES });
|
|
469
|
-
let returned = truncation.content;
|
|
470
|
-
let path: string | undefined;
|
|
471
|
-
if (truncation.truncated) {
|
|
472
|
-
const directory = await mkdtemp(join(tmpdir(), "tau-subagent-"));
|
|
473
|
-
path = join(directory, "output.md");
|
|
474
|
-
await writeFile(path, response, { encoding: "utf8", mode: 0o600 });
|
|
475
|
-
returned += `\n\n[Output truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}). Full output saved to: ${path}]`;
|
|
476
|
-
}
|
|
477
|
-
details.response = returned;
|
|
478
|
-
details.truncation = {
|
|
479
|
-
truncated: truncation.truncated,
|
|
480
|
-
path,
|
|
481
|
-
outputLines: truncation.outputLines,
|
|
482
|
-
totalLines: truncation.totalLines,
|
|
483
|
-
outputBytes: truncation.outputBytes,
|
|
484
|
-
totalBytes: truncation.totalBytes,
|
|
485
|
-
};
|
|
486
|
-
} else {
|
|
487
|
-
details.status = "failed";
|
|
488
|
-
details.completionState = terminal.stopReason;
|
|
489
|
-
details.error = `Agent ${definition.name} run failed: empty or non-terminal response`;
|
|
490
|
-
}
|
|
619
|
+
await applyTerminalOutcome(details, terminal, definition.name);
|
|
491
620
|
details.durationMs = Date.now() - started;
|
|
492
621
|
publish(true);
|
|
493
|
-
return
|
|
494
|
-
content:
|
|
495
|
-
details.status === "completed"
|
|
496
|
-
? (details.response ?? "")
|
|
497
|
-
: (details.error ?? `Agent ${definition.name} failed`),
|
|
498
|
-
details,
|
|
499
|
-
retainable,
|
|
500
|
-
terminalOutcome:
|
|
501
|
-
details.status === "completed" ? "completed" : details.status === "aborted" ? "aborted" : "failed",
|
|
502
|
-
assistantMessageEndAt,
|
|
503
|
-
...(usage.turns === 0 ? {} : { usage: toolUsage }),
|
|
504
|
-
};
|
|
622
|
+
return turnResult(details, retainable, assistantMessageEndAt, usage, toolUsage, definition.name);
|
|
505
623
|
} catch (error) {
|
|
506
624
|
details.status = signal.aborted ? "aborted" : "failed";
|
|
507
625
|
details.error = capped(error instanceof Error ? error.message : "Subagent failed");
|
|
@@ -509,14 +627,7 @@ export async function runSubagentTurn(options: {
|
|
|
509
627
|
// Prompt/session failures leave the child unsafe for reuse.
|
|
510
628
|
retainable = false;
|
|
511
629
|
publish(true);
|
|
512
|
-
return
|
|
513
|
-
content: details.error,
|
|
514
|
-
details,
|
|
515
|
-
retainable,
|
|
516
|
-
terminalOutcome: details.status === "aborted" ? "aborted" : "failed",
|
|
517
|
-
assistantMessageEndAt,
|
|
518
|
-
...(usage.turns === 0 ? {} : { usage: toolUsage }),
|
|
519
|
-
};
|
|
630
|
+
return turnResult(details, retainable, assistantMessageEndAt, usage, toolUsage, definition.name);
|
|
520
631
|
} finally {
|
|
521
632
|
unsubscribe?.();
|
|
522
633
|
thread.turns += 1;
|