@shanepadgett/tau-agent 0.22.0 → 0.23.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/extensions/context-pruning/README.md +1 -1
- package/extensions/context-pruning/index.ts +5 -1
- package/extensions/context-pruning/prune.ts +14 -9
- package/extensions/explore/read-cache.ts +8 -32
- package/extensions/subagent/cmux-dashboard.ts +62 -34
- package/extensions/subagent/run.ts +1 -0
- package/extensions/subagent/runtime.ts +2 -0
- package/package.json +2 -2
- package/shared/context-pruning-state.ts +1 -9
|
@@ -11,7 +11,7 @@ Before calling the tool, the agent writes visible prose containing the durable c
|
|
|
11
11
|
The tool accepts three required lists:
|
|
12
12
|
|
|
13
13
|
- `keepToolCalls` retains exact earlier tool exchanges by tool-call ID. Parallel calls remain independently selectable: retaining one call does not retain its siblings.
|
|
14
|
-
- `keepFiles` reads each selected file from disk and carries its complete current contents forward as a fresh autoread snapshot. It does not require an earlier complete read.
|
|
14
|
+
- `keepFiles` reads each selected file from disk and carries its complete current contents forward as a fresh autoread snapshot. Each successful snapshot appears as its own autoread marker below the compact checkpoint result. It does not require an earlier complete read.
|
|
15
15
|
- `deferFiles` carries forward a short advisory note explaining why a file is irrelevant now and when to reconsider it.
|
|
16
16
|
|
|
17
17
|
Duplicate selections are collapsed. A file selected in both `keepFiles` and `deferFiles` is kept. Missing, unreadable, or otherwise unsnapshotable files produce warnings in the successful tool result; they do not block the checkpoint or other file snapshots.
|
|
@@ -117,7 +117,7 @@ export default function contextPruningExtension(pi: ExtensionAPI): void {
|
|
|
117
117
|
parameters: contextPruneParameters,
|
|
118
118
|
executionMode: "sequential",
|
|
119
119
|
async execute(toolCallId, params, signal, _onUpdate, executionContext) {
|
|
120
|
-
|
|
120
|
+
const execution = await executeContextPrune({
|
|
121
121
|
toolCallId,
|
|
122
122
|
params,
|
|
123
123
|
signal,
|
|
@@ -125,6 +125,10 @@ export default function contextPruningExtension(pi: ExtensionAPI): void {
|
|
|
125
125
|
generation: lifecycleGeneration,
|
|
126
126
|
currentGeneration: () => lifecycleGeneration,
|
|
127
127
|
});
|
|
128
|
+
for (const autoread of execution.autoreads) {
|
|
129
|
+
pi.sendMessage(autoread, { deliverAs: "steer" });
|
|
130
|
+
}
|
|
131
|
+
return execution.result;
|
|
128
132
|
},
|
|
129
133
|
renderCall(args, theme, context) {
|
|
130
134
|
return renderContextPruneCall(args, theme, {
|
|
@@ -34,11 +34,16 @@ interface ContextPruneExecutionOptions {
|
|
|
34
34
|
currentGeneration: () => number;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
interface
|
|
37
|
+
interface ContextPruneToolResult {
|
|
38
38
|
content: Array<{ type: "text"; text: string }>;
|
|
39
39
|
details: ContextPruneDetailsV2;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
interface ContextPruneExecutionResult {
|
|
43
|
+
result: ContextPruneToolResult;
|
|
44
|
+
autoreads: PreparedAutoreadMessage[];
|
|
45
|
+
}
|
|
46
|
+
|
|
42
47
|
export async function executeContextPrune(options: ContextPruneExecutionOptions): Promise<ContextPruneExecutionResult> {
|
|
43
48
|
assertCurrent(options);
|
|
44
49
|
const retainedToolCallIds = [...new Set(options.params.keepToolCalls.map((selection) => selection.toolCallId))];
|
|
@@ -91,8 +96,6 @@ export async function executeContextPrune(options: ContextPruneExecutionOptions)
|
|
|
91
96
|
const refreshedFiles = preparedSnapshots.map((snapshot) => ({
|
|
92
97
|
path: snapshot.details.path,
|
|
93
98
|
rowId: snapshot.details.rowId,
|
|
94
|
-
servedHash: snapshot.details.readCache.servedHash,
|
|
95
|
-
autoreadDetails: { ...snapshot.details },
|
|
96
99
|
}));
|
|
97
100
|
const details: ContextPruneDetailsV2 = {
|
|
98
101
|
v: 2,
|
|
@@ -110,12 +113,14 @@ export async function executeContextPrune(options: ContextPruneExecutionOptions)
|
|
|
110
113
|
? "Context checkpoint applied. Continue with the next action stated before this call."
|
|
111
114
|
: `Context checkpoint applied with ${warnings.length} warning${warnings.length === 1 ? "" : "s"}:\n${warnings.map((warning) => `- ${warning}`).join("\n")}\nContinue with the next action stated before this call.`;
|
|
112
115
|
return {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
result: {
|
|
117
|
+
content: [
|
|
118
|
+
{ type: "text", text: status },
|
|
119
|
+
...(deferredFiles.length === 0 ? [] : [{ type: "text" as const, text: deferredFileText(deferredFiles) }]),
|
|
120
|
+
],
|
|
121
|
+
details,
|
|
122
|
+
},
|
|
123
|
+
autoreads: preparedSnapshots,
|
|
119
124
|
};
|
|
120
125
|
}
|
|
121
126
|
|
|
@@ -128,30 +128,25 @@ export function replayReadCache(
|
|
|
128
128
|
if (entry.type === "message" && "message" in entry) message = entry.message;
|
|
129
129
|
else if (entry.type === "custom_message") message = entry;
|
|
130
130
|
else continue;
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (!parsedMeta) continue;
|
|
136
|
-
acceptedCarryForward = true;
|
|
137
|
-
const candidateRowId = readRowId(candidate);
|
|
138
|
-
if (candidateRowId && ignoredRowIds.has(candidateRowId)) continue;
|
|
131
|
+
const parsedMeta = readMetaFromMessage(message);
|
|
132
|
+
if (parsedMeta) {
|
|
133
|
+
const rowId = readRowId(message);
|
|
134
|
+
if (!rowId || ignoredRowIds.has(rowId)) continue;
|
|
139
135
|
const pathKey = resolve(cwd, parsedMeta.pathKey);
|
|
140
136
|
const meta = pathKey === parsedMeta.pathKey ? parsedMeta : { ...parsedMeta, pathKey };
|
|
141
|
-
if (
|
|
137
|
+
if (applyReadMeta(trust, failedPatchRecoveryPaths, message, meta, rowId)) {
|
|
142
138
|
const acceptedTrust = trust.get(meta.pathKey)?.get(meta.scopeKey);
|
|
143
|
-
if (acceptedTrust)
|
|
139
|
+
if (acceptedTrust)
|
|
144
140
|
acceptedRows.push({
|
|
145
|
-
rowId
|
|
141
|
+
rowId,
|
|
146
142
|
pathKey: meta.pathKey,
|
|
147
143
|
scopeKey: meta.scopeKey,
|
|
148
144
|
meta,
|
|
149
145
|
dependencyRowIds: [...acceptedTrust.rowIds],
|
|
150
146
|
});
|
|
151
|
-
}
|
|
152
147
|
}
|
|
148
|
+
continue;
|
|
153
149
|
}
|
|
154
|
-
if (acceptedCarryForward) continue;
|
|
155
150
|
for (const path of failedPatchPaths(message, cwd)) failedPatchRecoveryPaths.add(path);
|
|
156
151
|
}
|
|
157
152
|
const completeFileChains = new Map<string, CompleteFileDependencyChain>();
|
|
@@ -168,25 +163,6 @@ export function replayReadCache(
|
|
|
168
163
|
return { scopeTrust: trust, acceptedRows, completeFileChains, failedPatchRecoveryPaths };
|
|
169
164
|
}
|
|
170
165
|
|
|
171
|
-
function carriedFileMessages(message: unknown): unknown[] | undefined {
|
|
172
|
-
if (!isRecord(message) || message.role !== "toolResult" || message.toolName !== "context_prune") return undefined;
|
|
173
|
-
if (!isRecord(message.details) || message.details.v !== 2 || !Array.isArray(message.details.refreshedFiles)) {
|
|
174
|
-
return undefined;
|
|
175
|
-
}
|
|
176
|
-
if (!Array.isArray(message.content)) {
|
|
177
|
-
return undefined;
|
|
178
|
-
}
|
|
179
|
-
const messages: unknown[] = [];
|
|
180
|
-
for (let index = 0; index < message.details.refreshedFiles.length; index += 1) {
|
|
181
|
-
const file = message.details.refreshedFiles[index];
|
|
182
|
-
const part = message.content[index + 1];
|
|
183
|
-
if (!isRecord(file) || !isRecord(file.autoreadDetails)) continue;
|
|
184
|
-
if (!isRecord(part) || part.type !== "text" || typeof part.text !== "string") continue;
|
|
185
|
-
messages.push({ customType: "tau.autoread", content: part.text, details: file.autoreadDetails });
|
|
186
|
-
}
|
|
187
|
-
return messages;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
166
|
function applyReadMeta(
|
|
191
167
|
trust: Map<string, Map<string, ReadCacheScopeTrust>>,
|
|
192
168
|
failedPatchRecoveryPaths: Set<string>,
|
|
@@ -71,54 +71,82 @@ function cap(text: string | undefined, limit: number): string {
|
|
|
71
71
|
return `${normalized.slice(0, limit - 1)}…`;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
function tableCell(text: string, limit: number): string {
|
|
75
|
+
const compact = cap(text.replace(/\s+/g, " ").trim(), limit);
|
|
76
|
+
return compact.replaceAll("|", "|").replaceAll("<", "<").replaceAll(">", ">");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function quote(text: string): string {
|
|
80
|
+
return cap(text, 1200)
|
|
81
|
+
.replaceAll("<", "<")
|
|
82
|
+
.replaceAll(">", ">")
|
|
83
|
+
.split("\n")
|
|
84
|
+
.map((line) => `> ${line}`)
|
|
85
|
+
.join("\n");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function elapsed(durationMs: number): string {
|
|
89
|
+
if (durationMs < 1000) return "<1s";
|
|
90
|
+
if (durationMs < 60_000) return `${(durationMs / 1000).toFixed(1)}s`;
|
|
91
|
+
const minutes = Math.floor(durationMs / 60_000);
|
|
92
|
+
const seconds = Math.floor((durationMs % 60_000) / 1000);
|
|
93
|
+
return `${minutes}m ${seconds}s`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function dashboardState(status: SubagentInvocationSnapshot["status"]): string {
|
|
97
|
+
switch (status) {
|
|
98
|
+
case "waiting":
|
|
99
|
+
return "queued";
|
|
100
|
+
case "completed":
|
|
101
|
+
return "done";
|
|
102
|
+
case "aborted":
|
|
103
|
+
return "stopped";
|
|
104
|
+
default:
|
|
105
|
+
return status;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
74
109
|
export function formatDashboardMarkdown(snapshots: readonly SubagentInvocationSnapshot[]): string {
|
|
75
110
|
const ordered = [...snapshots].sort(
|
|
76
111
|
(a, b) => a.startedAt - b.startedAt || a.invocationId.localeCompare(b.invocationId),
|
|
77
112
|
);
|
|
78
|
-
const activeCount = ordered.filter(
|
|
79
|
-
|
|
113
|
+
const activeCount = ordered.filter((snapshot) =>
|
|
114
|
+
["waiting", "starting", "running"].includes(snapshot.status),
|
|
80
115
|
).length;
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
116
|
+
const doneCount = ordered.filter((snapshot) => snapshot.status === "completed").length;
|
|
117
|
+
const failedCount = ordered.filter((snapshot) => snapshot.status === "failed").length;
|
|
118
|
+
const stoppedCount = ordered.filter((snapshot) => snapshot.status === "aborted").length;
|
|
119
|
+
const counts = [`${activeCount} active`, `${doneCount} done`];
|
|
120
|
+
if (failedCount > 0) counts.push(`${failedCount} failed`);
|
|
121
|
+
if (stoppedCount > 0) counts.push(`${stoppedCount} stopped`);
|
|
122
|
+
const lines = ["# Subagents", "", counts.join(" · "), ""];
|
|
87
123
|
if (ordered.length === 0) {
|
|
88
|
-
lines.push("_No
|
|
124
|
+
lines.push("_No subagents._", "");
|
|
89
125
|
return lines.join("\n");
|
|
90
126
|
}
|
|
127
|
+
lines.push("| Agent | State | Request | Last tool | Calls | Time |", "| --- | --- | --- | --- | ---: | ---: |");
|
|
91
128
|
for (const details of ordered) {
|
|
92
|
-
const
|
|
129
|
+
const latest = details.actions.at(-1);
|
|
130
|
+
const currentTool = details.currentActivity?.match(/^\S+/)?.[0];
|
|
131
|
+
const lastTool = currentTool ?? latest?.tool ?? "";
|
|
93
132
|
lines.push(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
133
|
+
`| ${tableCell(`${details.displayName} (${details.agent})`, 48)} | ${dashboardState(details.status)} | ${tableCell(details.task, 90)} | ${tableCell(lastTool, 32) || "—"} | ${details.toolCalls} | ${elapsed(details.durationMs)} |`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
lines.push("", "## Inputs", "");
|
|
137
|
+
for (const details of ordered) {
|
|
138
|
+
lines.push(
|
|
139
|
+
`### ${tableCell(details.displayName, 80)}`,
|
|
98
140
|
"",
|
|
99
|
-
|
|
141
|
+
`${tableCell(details.agent, 80)} · ${tableCell(details.invocationId, 80)}`,
|
|
100
142
|
"",
|
|
101
|
-
|
|
143
|
+
quote(details.task) || "> _(empty)_",
|
|
102
144
|
);
|
|
103
|
-
if (details.
|
|
104
|
-
lines.push("", "
|
|
105
|
-
|
|
106
|
-
if (details.actions.length > 0) {
|
|
107
|
-
lines.push("", "### Actions", "");
|
|
108
|
-
for (const action of details.actions.slice(-12)) {
|
|
109
|
-
const mark = action.error ? "!" : "·";
|
|
110
|
-
lines.push(`- ${mark} ${cap(action.summary, 200)}`);
|
|
111
|
-
}
|
|
112
|
-
if (details.omittedActions > 0) {
|
|
113
|
-
lines.push(`- _${details.omittedActions} earlier actions omitted_`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
if (details.response) {
|
|
117
|
-
lines.push("", "### Response", "", cap(details.response, 3000));
|
|
118
|
-
}
|
|
119
|
-
if (details.error) {
|
|
120
|
-
lines.push("", "### Error", "", cap(details.error, 800));
|
|
145
|
+
if (details.files.length > 0) {
|
|
146
|
+
lines.push("", "**Files**", "");
|
|
147
|
+
for (const file of details.files) lines.push(`- ${tableCell(file, 300)}`);
|
|
121
148
|
}
|
|
149
|
+
if (details.error) lines.push("", "**Error**", "", quote(details.error));
|
|
122
150
|
lines.push("");
|
|
123
151
|
}
|
|
124
152
|
return lines.join("\n");
|
|
@@ -243,6 +243,7 @@ export class SubagentRuntime {
|
|
|
243
243
|
}),
|
|
244
244
|
invocationId,
|
|
245
245
|
startedAt,
|
|
246
|
+
files: [...(options.files ?? [])],
|
|
246
247
|
};
|
|
247
248
|
const active: ActiveInvocation = {
|
|
248
249
|
snapshot: initialSnapshot,
|
|
@@ -330,6 +331,7 @@ export class SubagentRuntime {
|
|
|
330
331
|
...details,
|
|
331
332
|
invocationId,
|
|
332
333
|
startedAt,
|
|
334
|
+
files: current.snapshot.files,
|
|
333
335
|
threadId: details.threadId ?? threadId,
|
|
334
336
|
agent: details.agent || agent,
|
|
335
337
|
displayName: details.displayName || displayName,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shanepadgett/tau-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Tau is a custom agentic harness built with pi extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"README.md"
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@shanepadgett/tau-tui": "0.
|
|
37
|
+
"@shanepadgett/tau-tui": "0.23.0",
|
|
38
38
|
"@toon-format/toon": "2.3.0",
|
|
39
39
|
"image-size": "2.0.2",
|
|
40
40
|
"smol-toml": "1.7.0"
|
|
@@ -5,8 +5,6 @@ const CONTEXT_PRUNE_TOOL = "context_prune";
|
|
|
5
5
|
export interface ContextPruneRefreshedFileV2 {
|
|
6
6
|
path: string;
|
|
7
7
|
rowId: string;
|
|
8
|
-
servedHash: string;
|
|
9
|
-
autoreadDetails: Record<string, unknown>;
|
|
10
8
|
}
|
|
11
9
|
|
|
12
10
|
export interface ContextPruneDeferredFileV2 {
|
|
@@ -116,13 +114,7 @@ function parseRefreshedFiles(value: unknown): ContextPruneRefreshedFileV2[] | un
|
|
|
116
114
|
const files: ContextPruneRefreshedFileV2[] = [];
|
|
117
115
|
for (const item of value) {
|
|
118
116
|
if (!isRecord(item) || !isNonEmptyString(item.path) || !isNonEmptyString(item.rowId)) return undefined;
|
|
119
|
-
|
|
120
|
-
files.push({
|
|
121
|
-
path: item.path,
|
|
122
|
-
rowId: item.rowId,
|
|
123
|
-
servedHash: item.servedHash,
|
|
124
|
-
autoreadDetails: { ...item.autoreadDetails },
|
|
125
|
-
});
|
|
117
|
+
files.push({ path: item.path, rowId: item.rowId });
|
|
126
118
|
}
|
|
127
119
|
return files;
|
|
128
120
|
}
|