@shanepadgett/tau-agent 0.25.1 → 0.27.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/subagents.md +1 -1
- package/extensions/appshot/README.md +1 -1
- package/extensions/appshot/index.ts +5 -11
- package/extensions/context-pruning/README.md +2 -2
- package/extensions/context-pruning/index.ts +58 -57
- package/extensions/context-pruning/render.ts +51 -39
- package/extensions/context-pruning/settings.ts +4 -5
- package/extensions/explore/README.md +18 -10
- package/extensions/explore/ast-guidance.ts +67 -0
- package/extensions/explore/ast-languages.ts +61 -0
- package/extensions/explore/ast-tools.ts +1597 -94
- package/extensions/explore/ast-worker.ts +768 -60
- package/extensions/explore/index.ts +62 -6
- package/extensions/explore/orientation-state.ts +280 -0
- package/extensions/explore/read-stats-panel.ts +23 -1
- package/extensions/explore/read-stats.ts +21 -3
- package/extensions/explore/read.ts +58 -21
- package/extensions/explore/settings.ts +46 -0
- package/extensions/explore/traverse.ts +15 -4
- package/extensions/patch/executor.ts +95 -1
- package/extensions/patch/index.ts +1 -0
- package/extensions/soul/prompt.ts +1 -3
- package/extensions/subagent/agents/review.md +76 -25
- package/extensions/subagent/agents/scout.md +118 -22
- package/extensions/subagent/session-resource.ts +1 -1
- package/extensions/tau-help/help.md +3 -1
- package/native-bin/darwin-arm64/tau-ast +0 -0
- package/package.json +2 -3
- package/schemas/tau.schema.json +44 -4
- package/shared/bounded-text-result.ts +304 -0
- package/shared/events.ts +2 -1
- package/shared/temporary-output-store.ts +275 -0
package/docs/subagents.md
CHANGED
|
@@ -83,7 +83,7 @@ Tau assigns one display name to each fresh child and keeps it for follow-up turn
|
|
|
83
83
|
## Built-ins
|
|
84
84
|
|
|
85
85
|
- `review` — adversarial, read-only review for correctness, runtime risks, duplication, and over- or under-engineering
|
|
86
|
-
- `scout` —
|
|
86
|
+
- `scout` — tiered, AST-first local discovery of files, symbols, data flow, constraints, and unknowns without changing anything
|
|
87
87
|
- `web-research` — `websearch`, `codesearch`, `webfetch`
|
|
88
88
|
- `context-sync` — maps meaningful uncommitted work into `.pi/contexts`. Offered to the coding agent when `extensions.context.sync.enabled` and `sync.automation` are true. Manual `/context-sync` remains when sync is enabled with `automation` false. Validation can auto-run it when `validation.enabled` and `sync.enabled`
|
|
89
89
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Appshot
|
|
2
2
|
|
|
3
|
-
Appshot gives Tau direct discovery and capture of macOS application windows. Tau can list visible windows as compact
|
|
3
|
+
Appshot gives Tau direct discovery and capture of macOS application windows. Tau can list visible windows as compact JSON with their application identity, title, process ID, bounds, and stable window ID; capture an exact window as a PNG for visual inspection; and bring an application forward when needed. Captures preserve aspect ratio and fit within 1568×1568 pixels to keep image payloads bounded.
|
|
4
4
|
|
|
5
5
|
The extension provides three tools:
|
|
6
6
|
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
type ExtensionAPI,
|
|
7
7
|
} from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { Text } from "@earendil-works/pi-tui";
|
|
9
|
-
import { encode } from "@toon-format/toon";
|
|
10
9
|
import { randomUUID } from "node:crypto";
|
|
11
10
|
import { mkdir, readFile, rename, rm, stat } from "node:fs/promises";
|
|
12
11
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
@@ -86,20 +85,15 @@ function encodeWindowList(windows: WindowInfo[]): string {
|
|
|
86
85
|
height: window.bounds.height,
|
|
87
86
|
}));
|
|
88
87
|
const render = (count: number, omitted: number) =>
|
|
89
|
-
|
|
90
|
-
indent: 1,
|
|
91
|
-
});
|
|
88
|
+
JSON.stringify(omitted === 0 ? { windows: rows.slice(0, count) } : { windows: rows.slice(0, count), omitted });
|
|
92
89
|
const fits = (value: string) =>
|
|
93
90
|
Buffer.byteLength(value, "utf8") <= DEFAULT_MAX_BYTES && value.split("\n").length <= DEFAULT_MAX_LINES;
|
|
94
|
-
const maximumRows = Math.max(0, DEFAULT_MAX_LINES - 2);
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (fits(full)) return full;
|
|
99
|
-
}
|
|
92
|
+
const full = render(rows.length, 0);
|
|
93
|
+
if (fits(full)) return full;
|
|
100
94
|
|
|
101
95
|
let low = 0;
|
|
102
|
-
let high =
|
|
96
|
+
let high = rows.length;
|
|
103
97
|
while (low < high) {
|
|
104
98
|
const middle = Math.ceil((low + high) / 2);
|
|
105
99
|
if (fits(render(middle, rows.length - middle))) low = middle;
|
|
@@ -116,7 +110,7 @@ function registerAppshotTools(pi: ExtensionAPI, runHelper: RunHelper): void {
|
|
|
116
110
|
name: "list_windows",
|
|
117
111
|
label: "List Windows",
|
|
118
112
|
description:
|
|
119
|
-
"List visible normal macOS windows as compact
|
|
113
|
+
"List visible normal macOS windows as compact JSON with window IDs, titles, application identity, process IDs, and bounds. Use list_windows to discover exact window IDs and application PIDs before screenshot_window or activate_app. Requires macOS 14 or newer and Screen & System Audio Recording permission.",
|
|
120
114
|
parameters: listWindowsSchema,
|
|
121
115
|
async execute(_toolCallId, _params, signal) {
|
|
122
116
|
if (process.platform !== "darwin") throw new Error("list_windows is only available on macOS");
|
|
@@ -20,7 +20,7 @@ Context Pruning does not impose a minimum token saving and does not reject a che
|
|
|
20
20
|
|
|
21
21
|
## Automatic and manual requests
|
|
22
22
|
|
|
23
|
-
Tau checks context
|
|
23
|
+
Tau checks active context size after tool-using turns and can send progressively stronger private instructions from `nudgeInstructions`. Hints occur at fixed token intervals rather than percentages of the model's context window. After a checkpoint, Tau suppresses boundaries already crossed by the resulting context and resumes at the next boundary. Branch navigation and compaction reconstruct that state from the active branch.
|
|
24
24
|
|
|
25
25
|
Run `/prune` with no arguments to ask the agent to create a checkpoint and continue its task immediately.
|
|
26
26
|
|
|
@@ -35,5 +35,5 @@ Normal Pi compaction remains independent. When a compaction no longer includes a
|
|
|
35
35
|
Settings live under `extensions.contextPruning` in Tau settings.
|
|
36
36
|
|
|
37
37
|
- `enabled`: enables the tool, `/prune`, projection, markers, and branch replay. Defaults to `true`.
|
|
38
|
-
- `
|
|
38
|
+
- `nudgeEveryTokens`: active-context interval between automatic hints. Defaults to `30000`, producing the default instruction ladder at 30k, 60k, and 90k tokens.
|
|
39
39
|
- `nudgeInstructions`: ordered list of one through five nonempty instructions. Later reminders repeat the final instruction. Defaults to three escalating instructions.
|
|
@@ -15,11 +15,11 @@ import { createToolRowStateStore } from "../../shared/tool-row-state.ts";
|
|
|
15
15
|
import { contextPruneParameters, executeContextPrune } from "./prune.ts";
|
|
16
16
|
import { projectContext } from "./projection.ts";
|
|
17
17
|
import {
|
|
18
|
-
|
|
18
|
+
parseContextPruningNudgeDetailsV3,
|
|
19
19
|
renderContextPruneCall,
|
|
20
20
|
renderContextPruneResult,
|
|
21
21
|
renderContextPruningNudge,
|
|
22
|
-
type
|
|
22
|
+
type ContextPruningNudgeDetailsV3,
|
|
23
23
|
} from "./render.ts";
|
|
24
24
|
import contextPruningSettings from "./settings.ts";
|
|
25
25
|
|
|
@@ -30,8 +30,8 @@ const NUDGE_BASELINE_ENTRY_TYPE = "tau.context-pruning.nudge-baseline";
|
|
|
30
30
|
|
|
31
31
|
interface NudgeState {
|
|
32
32
|
anchorToolCallId: string | undefined;
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
suppressedThroughTokens: number | undefined;
|
|
34
|
+
highestBoundaryTokens: number;
|
|
35
35
|
highestTier: number;
|
|
36
36
|
terminalTierReached: boolean;
|
|
37
37
|
}
|
|
@@ -39,20 +39,20 @@ interface NudgeState {
|
|
|
39
39
|
export default function contextPruningExtension(pi: ExtensionAPI): void {
|
|
40
40
|
let enabled = false;
|
|
41
41
|
let lifecycleGeneration = 0;
|
|
42
|
-
let
|
|
42
|
+
let nudgeEveryTokens = contextPruningSettings.defaults.nudgeEveryTokens;
|
|
43
43
|
let nudgeInstructions = contextPruningSettings.defaults.nudgeInstructions;
|
|
44
44
|
let toolRegistered = false;
|
|
45
45
|
let commandRegistered = false;
|
|
46
46
|
let visualRows = new Set<string>();
|
|
47
47
|
let nudgeState: NudgeState = {
|
|
48
48
|
anchorToolCallId: undefined,
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
suppressedThroughTokens: 0,
|
|
50
|
+
highestBoundaryTokens: 0,
|
|
51
51
|
highestTier: 0,
|
|
52
52
|
terminalTierReached: false,
|
|
53
53
|
};
|
|
54
54
|
const rowState = createToolRowStateStore(pi, "context-pruning.tool-row-state");
|
|
55
|
-
pi.registerMessageRenderer<
|
|
55
|
+
pi.registerMessageRenderer<ContextPruningNudgeDetailsV3>(NUDGE_MESSAGE_TYPE, (message, _options, theme) =>
|
|
56
56
|
renderContextPruningNudge(message.details, theme),
|
|
57
57
|
);
|
|
58
58
|
|
|
@@ -98,7 +98,7 @@ export default function contextPruningExtension(pi: ExtensionAPI): void {
|
|
|
98
98
|
const settings = await loadTauExtensionSettings(ctx, contextPruningSettings);
|
|
99
99
|
if (generation !== lifecycleGeneration) return;
|
|
100
100
|
enabled = settings.enabled;
|
|
101
|
-
|
|
101
|
+
nudgeEveryTokens = settings.nudgeEveryTokens;
|
|
102
102
|
nudgeInstructions = settings.nudgeInstructions;
|
|
103
103
|
setContextPruningEnabled(enabled);
|
|
104
104
|
if (enabled && !toolRegistered) {
|
|
@@ -162,22 +162,22 @@ export default function contextPruningExtension(pi: ExtensionAPI): void {
|
|
|
162
162
|
commandContext.sessionManager.getBranch(),
|
|
163
163
|
true,
|
|
164
164
|
).latestAnchorToolCallId;
|
|
165
|
-
pi.sendMessage<
|
|
165
|
+
pi.sendMessage<ContextPruningNudgeDetailsV3>(
|
|
166
166
|
{
|
|
167
167
|
customType: NUDGE_MESSAGE_TYPE,
|
|
168
168
|
content: manualPruneSteeringMessage(),
|
|
169
169
|
display: true,
|
|
170
170
|
details: {
|
|
171
|
-
v:
|
|
171
|
+
v: 3,
|
|
172
172
|
kind: "manual",
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
tokens: null,
|
|
174
|
+
boundaryTokens: null,
|
|
175
175
|
reminder: null,
|
|
176
176
|
tier: null,
|
|
177
177
|
tierCount: null,
|
|
178
178
|
tierFloor: null,
|
|
179
179
|
anchorToolCallId: anchorToolCallId ?? null,
|
|
180
|
-
|
|
180
|
+
suppressedThroughTokens: null,
|
|
181
181
|
},
|
|
182
182
|
},
|
|
183
183
|
{ deliverAs: "steer", triggerTurn: true },
|
|
@@ -204,8 +204,8 @@ export default function contextPruningExtension(pi: ExtensionAPI): void {
|
|
|
204
204
|
visualRows.clear();
|
|
205
205
|
nudgeState = {
|
|
206
206
|
anchorToolCallId: undefined,
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
suppressedThroughTokens: 0,
|
|
208
|
+
highestBoundaryTokens: 0,
|
|
209
209
|
highestTier: 0,
|
|
210
210
|
terminalTierReached: false,
|
|
211
211
|
};
|
|
@@ -216,45 +216,44 @@ export default function contextPruningExtension(pi: ExtensionAPI): void {
|
|
|
216
216
|
pi.on("turn_end", (event, ctx) => {
|
|
217
217
|
if (!enabled || event.toolResults.length === 0) return undefined;
|
|
218
218
|
const usage = ctx.getContextUsage();
|
|
219
|
-
if (!usage || usage.
|
|
220
|
-
const
|
|
221
|
-
const percent = Math.floor(rawPercent);
|
|
219
|
+
if (!usage || usage.tokens === null || !Number.isFinite(usage.tokens)) return undefined;
|
|
220
|
+
const tokens = Math.max(0, Math.floor(usage.tokens));
|
|
222
221
|
const activeAnchor = replayContextPruningState(ctx.sessionManager.getBranch(), true).latestAnchorToolCallId;
|
|
223
222
|
if (activeAnchor !== nudgeState.anchorToolCallId) {
|
|
224
223
|
nudgeState = reconstructNudgeState(ctx.sessionManager.getBranch(), activeAnchor);
|
|
225
224
|
}
|
|
226
|
-
if (activeAnchor !== undefined && nudgeState.
|
|
227
|
-
const
|
|
225
|
+
if (activeAnchor !== undefined && nudgeState.suppressedThroughTokens === undefined) {
|
|
226
|
+
const suppressedThroughTokens = Math.floor(tokens / nudgeEveryTokens) * nudgeEveryTokens;
|
|
228
227
|
pi.appendEntry(NUDGE_BASELINE_ENTRY_TYPE, {
|
|
229
|
-
v:
|
|
228
|
+
v: 2,
|
|
230
229
|
anchorToolCallId: activeAnchor,
|
|
231
|
-
|
|
230
|
+
suppressedThroughTokens,
|
|
232
231
|
});
|
|
233
|
-
nudgeState.
|
|
232
|
+
nudgeState.suppressedThroughTokens = suppressedThroughTokens;
|
|
233
|
+
nudgeState.highestBoundaryTokens = suppressedThroughTokens;
|
|
234
234
|
return undefined;
|
|
235
235
|
}
|
|
236
|
-
const
|
|
237
|
-
const reminder = Math.floor((percent - baseline) / nudgeEveryPercent);
|
|
236
|
+
const reminder = Math.floor(tokens / nudgeEveryTokens);
|
|
238
237
|
if (reminder < 1) return undefined;
|
|
239
|
-
const
|
|
240
|
-
if (
|
|
238
|
+
const boundaryTokens = reminder * nudgeEveryTokens;
|
|
239
|
+
if (boundaryTokens <= nudgeState.highestBoundaryTokens) return undefined;
|
|
241
240
|
const tierCount = nudgeInstructions.length;
|
|
242
241
|
const tierFloor = nudgeState.terminalTierReached ? tierCount : Math.min(nudgeState.highestTier, tierCount);
|
|
243
242
|
const tier = Math.max(Math.min(reminder, tierCount), tierFloor);
|
|
244
243
|
const instruction = nudgeInstructions[tier - 1] ?? nudgeInstructions[0];
|
|
245
|
-
const details:
|
|
246
|
-
v:
|
|
244
|
+
const details: ContextPruningNudgeDetailsV3 = {
|
|
245
|
+
v: 3,
|
|
247
246
|
kind: "automatic",
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
tokens,
|
|
248
|
+
boundaryTokens,
|
|
250
249
|
reminder,
|
|
251
250
|
tier,
|
|
252
251
|
tierCount,
|
|
253
252
|
tierFloor,
|
|
254
253
|
anchorToolCallId: activeAnchor ?? null,
|
|
255
|
-
|
|
254
|
+
suppressedThroughTokens: nudgeState.suppressedThroughTokens ?? 0,
|
|
256
255
|
};
|
|
257
|
-
pi.sendMessage<
|
|
256
|
+
pi.sendMessage<ContextPruningNudgeDetailsV3>(
|
|
258
257
|
{
|
|
259
258
|
customType: NUDGE_MESSAGE_TYPE,
|
|
260
259
|
content: automaticPruneSteeringMessage(instruction, tier === tierCount),
|
|
@@ -263,7 +262,7 @@ export default function contextPruningExtension(pi: ExtensionAPI): void {
|
|
|
263
262
|
},
|
|
264
263
|
{ deliverAs: "steer" },
|
|
265
264
|
);
|
|
266
|
-
nudgeState.
|
|
265
|
+
nudgeState.highestBoundaryTokens = boundaryTokens;
|
|
267
266
|
nudgeState.highestTier = Math.max(nudgeState.highestTier, tier);
|
|
268
267
|
nudgeState.terminalTierReached ||= tier === tierCount;
|
|
269
268
|
return undefined;
|
|
@@ -289,8 +288,8 @@ function setsEqual(left: ReadonlySet<string>, right: ReadonlySet<string>): boole
|
|
|
289
288
|
}
|
|
290
289
|
|
|
291
290
|
function reconstructNudgeState(branch: readonly SessionEntry[], anchorToolCallId: string | undefined): NudgeState {
|
|
292
|
-
let
|
|
293
|
-
let
|
|
291
|
+
let suppressedThroughTokens = anchorToolCallId === undefined ? 0 : undefined;
|
|
292
|
+
let highestBoundaryTokens = 0;
|
|
294
293
|
let highestTier = 0;
|
|
295
294
|
let terminalTierReached = false;
|
|
296
295
|
let anchorResultIndex = -1;
|
|
@@ -310,65 +309,67 @@ function reconstructNudgeState(branch: readonly SessionEntry[], anchorToolCallId
|
|
|
310
309
|
const baseline = parseNudgeBaseline(entry.data);
|
|
311
310
|
if (
|
|
312
311
|
baseline &&
|
|
313
|
-
|
|
312
|
+
suppressedThroughTokens === undefined &&
|
|
314
313
|
index > anchorResultIndex &&
|
|
315
314
|
baseline.anchorToolCallId === anchorToolCallId
|
|
316
315
|
) {
|
|
317
|
-
|
|
316
|
+
suppressedThroughTokens = baseline.suppressedThroughTokens;
|
|
317
|
+
highestBoundaryTokens = baseline.suppressedThroughTokens;
|
|
318
318
|
}
|
|
319
319
|
continue;
|
|
320
320
|
}
|
|
321
321
|
if (entry.type !== "custom_message" || entry.customType !== NUDGE_MESSAGE_TYPE) continue;
|
|
322
|
-
const details =
|
|
322
|
+
const details = parseContextPruningNudgeDetailsV3(entry.details);
|
|
323
323
|
if (
|
|
324
324
|
!details ||
|
|
325
325
|
details.kind !== "automatic" ||
|
|
326
326
|
index <= anchorResultIndex ||
|
|
327
327
|
details.anchorToolCallId !== (anchorToolCallId ?? null) ||
|
|
328
|
-
details.
|
|
329
|
-
details.
|
|
330
|
-
(
|
|
328
|
+
details.boundaryTokens === null ||
|
|
329
|
+
details.suppressedThroughTokens === null ||
|
|
330
|
+
(suppressedThroughTokens !== undefined && details.suppressedThroughTokens !== suppressedThroughTokens)
|
|
331
331
|
)
|
|
332
332
|
continue;
|
|
333
333
|
const expectedTierFloor: number = terminalTierReached
|
|
334
334
|
? details.tierCount
|
|
335
335
|
: Math.min(highestTier, details.tierCount);
|
|
336
|
-
if (details.
|
|
337
|
-
|
|
336
|
+
if (details.boundaryTokens <= highestBoundaryTokens || details.tierFloor !== expectedTierFloor) continue;
|
|
337
|
+
highestBoundaryTokens = Math.max(highestBoundaryTokens, details.boundaryTokens);
|
|
338
338
|
highestTier = Math.max(highestTier, details.tier);
|
|
339
339
|
terminalTierReached ||= details.tier === details.tierCount;
|
|
340
|
-
|
|
340
|
+
suppressedThroughTokens = details.suppressedThroughTokens;
|
|
341
341
|
}
|
|
342
|
-
return { anchorToolCallId,
|
|
342
|
+
return { anchorToolCallId, suppressedThroughTokens, highestBoundaryTokens, highestTier, terminalTierReached };
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
-
function parseNudgeBaseline(
|
|
345
|
+
function parseNudgeBaseline(
|
|
346
|
+
value: unknown,
|
|
347
|
+
): { v: 2; anchorToolCallId: string; suppressedThroughTokens: number } | undefined {
|
|
346
348
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
|
|
347
349
|
const record = value as Record<string, unknown>;
|
|
348
350
|
if (
|
|
349
351
|
Object.keys(record).length !== 3 ||
|
|
350
352
|
!Object.hasOwn(record, "v") ||
|
|
351
353
|
!Object.hasOwn(record, "anchorToolCallId") ||
|
|
352
|
-
!Object.hasOwn(record, "
|
|
353
|
-
record.v !==
|
|
354
|
+
!Object.hasOwn(record, "suppressedThroughTokens") ||
|
|
355
|
+
record.v !== 2 ||
|
|
354
356
|
typeof record.anchorToolCallId !== "string" ||
|
|
355
357
|
record.anchorToolCallId.length === 0 ||
|
|
356
|
-
typeof record.
|
|
357
|
-
!Number.
|
|
358
|
-
record.
|
|
359
|
-
record.baselinePercent > 100
|
|
358
|
+
typeof record.suppressedThroughTokens !== "number" ||
|
|
359
|
+
!Number.isSafeInteger(record.suppressedThroughTokens) ||
|
|
360
|
+
record.suppressedThroughTokens < 0
|
|
360
361
|
)
|
|
361
362
|
return undefined;
|
|
362
363
|
return {
|
|
363
|
-
v:
|
|
364
|
+
v: 2,
|
|
364
365
|
anchorToolCallId: record.anchorToolCallId,
|
|
365
|
-
|
|
366
|
+
suppressedThroughTokens: record.suppressedThroughTokens,
|
|
366
367
|
};
|
|
367
368
|
}
|
|
368
369
|
|
|
369
370
|
function automaticPruneSteeringMessage(instruction: string, finalTier: boolean): string {
|
|
370
371
|
const silent =
|
|
371
|
-
"Internal context-management instruction. Follow it silently. Do not mention or acknowledge context
|
|
372
|
+
"Internal context-management instruction. Follow it silently. Do not mention or acknowledge context-token counts, prune messages, or internal context management.";
|
|
372
373
|
const protocol =
|
|
373
374
|
"When pruning, first preserve durable conclusions, user constraints, conditional relevance, and the next action in visible prose, then call context_prune.";
|
|
374
375
|
return finalTier
|
|
@@ -10,48 +10,48 @@ const MAX_EXPANDED_LINE_CHARACTERS = 240;
|
|
|
10
10
|
const MAX_EXPANDED_TEXT_CHARACTERS = 3_000;
|
|
11
11
|
const MAX_WARNING_CHARACTERS = 1_000;
|
|
12
12
|
|
|
13
|
-
export type
|
|
13
|
+
export type ContextPruningNudgeDetailsV3 =
|
|
14
14
|
| {
|
|
15
|
-
v:
|
|
15
|
+
v: 3;
|
|
16
16
|
kind: "automatic";
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
tokens: number;
|
|
18
|
+
boundaryTokens: number;
|
|
19
19
|
reminder: number;
|
|
20
20
|
tier: number;
|
|
21
21
|
tierCount: number;
|
|
22
22
|
tierFloor: number;
|
|
23
23
|
anchorToolCallId: string | null;
|
|
24
|
-
|
|
24
|
+
suppressedThroughTokens: number;
|
|
25
25
|
}
|
|
26
26
|
| {
|
|
27
|
-
v:
|
|
27
|
+
v: 3;
|
|
28
28
|
kind: "manual";
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
tokens: null;
|
|
30
|
+
boundaryTokens: null;
|
|
31
31
|
reminder: null;
|
|
32
32
|
tier: null;
|
|
33
33
|
tierCount: null;
|
|
34
34
|
tierFloor: null;
|
|
35
35
|
anchorToolCallId: string | null;
|
|
36
|
-
|
|
36
|
+
suppressedThroughTokens: null;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
export function
|
|
39
|
+
export function parseContextPruningNudgeDetailsV3(value: unknown): ContextPruningNudgeDetailsV3 | undefined {
|
|
40
40
|
if (!isRecord(value)) return undefined;
|
|
41
41
|
const keys = [
|
|
42
42
|
"v",
|
|
43
43
|
"kind",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
44
|
+
"tokens",
|
|
45
|
+
"boundaryTokens",
|
|
46
46
|
"reminder",
|
|
47
47
|
"tier",
|
|
48
48
|
"tierCount",
|
|
49
49
|
"tierFloor",
|
|
50
50
|
"anchorToolCallId",
|
|
51
|
-
"
|
|
51
|
+
"suppressedThroughTokens",
|
|
52
52
|
];
|
|
53
53
|
if (Object.keys(value).length !== keys.length || !keys.every((key) => Object.hasOwn(value, key))) return undefined;
|
|
54
|
-
if (value.v !==
|
|
54
|
+
if (value.v !== 3 || (value.kind !== "automatic" && value.kind !== "manual")) return undefined;
|
|
55
55
|
if (
|
|
56
56
|
value.anchorToolCallId !== null &&
|
|
57
57
|
(typeof value.anchorToolCallId !== "string" || value.anchorToolCallId.length === 0)
|
|
@@ -60,64 +60,69 @@ export function parseContextPruningNudgeDetailsV2(value: unknown): ContextPrunin
|
|
|
60
60
|
}
|
|
61
61
|
if (value.kind === "manual") {
|
|
62
62
|
if (
|
|
63
|
-
value.
|
|
64
|
-
value.
|
|
63
|
+
value.tokens !== null ||
|
|
64
|
+
value.boundaryTokens !== null ||
|
|
65
65
|
value.reminder !== null ||
|
|
66
66
|
value.tier !== null ||
|
|
67
67
|
value.tierCount !== null ||
|
|
68
68
|
value.tierFloor !== null ||
|
|
69
|
-
value.
|
|
69
|
+
value.suppressedThroughTokens !== null
|
|
70
70
|
)
|
|
71
71
|
return undefined;
|
|
72
72
|
return {
|
|
73
|
-
v:
|
|
73
|
+
v: 3,
|
|
74
74
|
kind: "manual",
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
tokens: null,
|
|
76
|
+
boundaryTokens: null,
|
|
77
77
|
reminder: null,
|
|
78
78
|
tier: null,
|
|
79
79
|
tierCount: null,
|
|
80
80
|
tierFloor: null,
|
|
81
81
|
anchorToolCallId: value.anchorToolCallId,
|
|
82
|
-
|
|
82
|
+
suppressedThroughTokens: null,
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
if (
|
|
86
|
-
!
|
|
87
|
-
!isBoundary(value.
|
|
86
|
+
!isTokenCount(value.tokens) ||
|
|
87
|
+
!isBoundary(value.boundaryTokens) ||
|
|
88
88
|
!isReminder(value.reminder) ||
|
|
89
89
|
!isTier(value.tier) ||
|
|
90
90
|
!isTier(value.tierCount) ||
|
|
91
91
|
!isTierFloor(value.tierFloor) ||
|
|
92
|
-
!
|
|
93
|
-
value.
|
|
94
|
-
value.
|
|
92
|
+
!isTokenCount(value.suppressedThroughTokens) ||
|
|
93
|
+
value.boundaryTokens > value.tokens ||
|
|
94
|
+
value.boundaryTokens <= value.suppressedThroughTokens ||
|
|
95
95
|
value.tier > value.tierCount ||
|
|
96
96
|
value.tierFloor > value.tierCount ||
|
|
97
97
|
value.tier !== Math.max(Math.min(value.reminder, value.tierCount), value.tierFloor) ||
|
|
98
|
-
(value.anchorToolCallId === null && value.
|
|
98
|
+
(value.anchorToolCallId === null && value.suppressedThroughTokens !== 0)
|
|
99
99
|
) {
|
|
100
100
|
return undefined;
|
|
101
101
|
}
|
|
102
|
-
const interval =
|
|
103
|
-
if (
|
|
102
|
+
const interval = value.boundaryTokens / value.reminder;
|
|
103
|
+
if (
|
|
104
|
+
!Number.isSafeInteger(interval) ||
|
|
105
|
+
interval < 1 ||
|
|
106
|
+
value.tokens - value.boundaryTokens >= interval ||
|
|
107
|
+
value.suppressedThroughTokens % interval !== 0
|
|
108
|
+
)
|
|
104
109
|
return undefined;
|
|
105
110
|
return {
|
|
106
|
-
v:
|
|
111
|
+
v: 3,
|
|
107
112
|
kind: "automatic",
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
tokens: value.tokens,
|
|
114
|
+
boundaryTokens: value.boundaryTokens,
|
|
110
115
|
reminder: value.reminder,
|
|
111
116
|
tier: value.tier,
|
|
112
117
|
tierCount: value.tierCount,
|
|
113
118
|
tierFloor: value.tierFloor,
|
|
114
119
|
anchorToolCallId: value.anchorToolCallId,
|
|
115
|
-
|
|
120
|
+
suppressedThroughTokens: value.suppressedThroughTokens,
|
|
116
121
|
};
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
export function renderContextPruningNudge(details: unknown, theme: Theme): Marker | undefined {
|
|
120
|
-
const parsed =
|
|
125
|
+
const parsed = parseContextPruningNudgeDetailsV3(details);
|
|
121
126
|
if (!parsed) return undefined;
|
|
122
127
|
return new Marker({
|
|
123
128
|
theme,
|
|
@@ -127,7 +132,7 @@ export function renderContextPruningNudge(details: unknown, theme: Theme): Marke
|
|
|
127
132
|
parsed.kind === "manual"
|
|
128
133
|
? ["Prune requested."]
|
|
129
134
|
: [
|
|
130
|
-
|
|
135
|
+
formatTokens(parsed.tokens),
|
|
131
136
|
...(parsed.tier === parsed.tierCount ? ["Prune now."] : parsed.tier > 1 ? ["Prune soon."] : []),
|
|
132
137
|
],
|
|
133
138
|
});
|
|
@@ -214,16 +219,23 @@ function firstResultText(result: AgentToolResult<unknown>): string {
|
|
|
214
219
|
return "";
|
|
215
220
|
}
|
|
216
221
|
|
|
217
|
-
function
|
|
218
|
-
|
|
222
|
+
function formatTokens(count: number): string {
|
|
223
|
+
if (count < 1_000) return `${count}`;
|
|
224
|
+
if (count < 10_000) return `${(count / 1_000).toFixed(1)}k`;
|
|
225
|
+
if (count < 1_000_000) return `${Math.round(count / 1_000)}k`;
|
|
226
|
+
return `${(count / 1_000_000).toFixed(1)}M`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function isTokenCount(value: unknown): value is number {
|
|
230
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
219
231
|
}
|
|
220
232
|
|
|
221
233
|
function isBoundary(value: unknown): value is number {
|
|
222
|
-
return
|
|
234
|
+
return isTokenCount(value) && value > 0;
|
|
223
235
|
}
|
|
224
236
|
|
|
225
237
|
function isReminder(value: unknown): value is number {
|
|
226
|
-
return typeof value === "number" && Number.
|
|
238
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
|
|
227
239
|
}
|
|
228
240
|
|
|
229
241
|
function isTier(value: unknown): value is number {
|
|
@@ -11,18 +11,17 @@ export default defineTauExtensionSettings({
|
|
|
11
11
|
key: "contextPruning",
|
|
12
12
|
defaults: {
|
|
13
13
|
enabled: true as boolean,
|
|
14
|
-
|
|
14
|
+
nudgeEveryTokens: 30_000 as number,
|
|
15
15
|
nudgeInstructions: DEFAULT_NUDGE_INSTRUCTIONS,
|
|
16
16
|
},
|
|
17
17
|
schema: Type.Object(
|
|
18
18
|
{
|
|
19
19
|
enabled: Type.Optional(Type.Boolean({ default: true, description: "Enable context pruning." })),
|
|
20
|
-
|
|
20
|
+
nudgeEveryTokens: Type.Optional(
|
|
21
21
|
Type.Integer({
|
|
22
|
-
default:
|
|
22
|
+
default: 30_000,
|
|
23
23
|
minimum: 1,
|
|
24
|
-
|
|
25
|
-
description: "Context growth interval between automatic pruning hints.",
|
|
24
|
+
description: "Active-context token interval between automatic pruning hints.",
|
|
26
25
|
}),
|
|
27
26
|
),
|
|
28
27
|
nudgeInstructions: Type.Optional(
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
# Explore
|
|
2
2
|
|
|
3
|
+
Locator edit tools (`replace_declaration`, `replace_body`, `insert_declaration`, and `rename_declaration`) consume numeric declaration locators and reject stale or parser-recovered targets. They validate candidate syntax before writing, invalidate old locators, and return fresh locators after a clean reparse. For Markdown headings, `replace_declaration` replaces the complete section and `replace_body` preserves the heading while replacing its contents. Nested replacement headings must stay below the selected heading depth. Markdown insertion and rename remain unavailable. Code rename requires an explicit file or repository scope. Inferred references change only with approval; ambiguous references stay unchanged and are reported.
|
|
4
|
+
|
|
3
5
|
Explore is Tau's first-party filesystem exploration extension.
|
|
4
6
|
|
|
5
|
-
It exists so agents can inspect paths, discover files, search text, inspect declarations in supported source languages, and read exact source with compact model payloads and readable tool rows. Autoread establishes complete-file knowledge while that source remains in active context. Later reads can return an unchanged marker or a smaller diff instead of resending the file.
|
|
7
|
+
It exists so agents can inspect paths, discover files, search text, inspect declarations in supported source languages, and read exact source with compact model payloads and readable tool rows. For configured supported source, the official `read` tool requires a current structural attempt for that exact file fingerprint. Direct file outlines and searches qualify, along with files returned by `symbol`, `api_discover`, structural search, and relationship tools. Markdown is ungated by default. Unsupported files and unavailable workers keep the ordinary read path. Autoread establishes complete-file knowledge while that source remains in active context. Later reads can return an unchanged marker or a smaller diff instead of resending the file. A successful Tau patch can return a trusted cached complete-file diff without another structural attempt when the current branch still contains the prior complete-file baseline and the resulting fingerprint matches.
|
|
6
8
|
|
|
7
9
|
When the source baseline is no longer available, such as after compaction or a cold subagent resume, `read` safely returns the current full source.
|
|
8
10
|
|
|
9
|
-
Agents invoke it with `ls`, `find`, `grep`, `outline`, `symbol`, and `read`. `outline` returns public declaration signatures and parenthesized numeric locators for TypeScript, TSX, Odin, Go, Rust, C#, Java, Kotlin, and
|
|
11
|
+
Agents invoke it with `ls`, `find`, `grep`, `api_discover`, `ast_search`, `outline`, `symbol`, `references`, `callers`, `callees`, `implementations`, `tests`, and `read`. The five relationship tools expand a declaration locator across a repository or subtree. Every result reports exact source location, syntactic relationship, exact/inferred/ambiguous certainty, production/test/generated/re-export classification, declaration candidates, and a numeric locator for the nearest complete editable scope. Ambiguous results are marked non-actionable. Scans stay bounded, deterministic, ignore-aware, and cancellable. `ast_search` finds code shapes with ast-grep patterns across one supported source file, repository, package, or subtree. Directory searches require an explicit language; supported files can infer it. Results include exact previews, metavariable bindings, parser uncertainty, numeric locators for matches and enclosing scopes, complete scan counts, and explicit result or traversal limits. Output stays bounded and complete overflow is saved to the active session temporary store. `api_discover` searches declarations across a repository, package, or subtree by exact, prefix, substring, bounded fuzzy, declaration-kind, or documentation query. It filters public, private, source-exported, or package-surface declarations and reports defining files separately from caller access for TypeScript, TSX, Odin, Go, Rust, C#, Java, Kotlin, and Swift. Caller access includes the canonical module path, exact import statement, usable access expression, and resolution uncertainty; TypeScript results also include re-export chains. Candidates contain signatures without implementation bodies and numeric locators accepted by `symbol`. `outline` returns public declaration signatures and parenthesized numeric locators for TypeScript, TSX, Odin, Go, Rust, C#, Java, Kotlin, Swift, and Markdown files. Markdown headings locate their complete sections. It accepts a package directory for one-level inspection, or `recursive: true` for ignore-aware mixed-language orientation of a repository or subtree. Recursive output stays bounded; when the complete outline is larger, Tau saves it to a temporary path for targeted `grep` and ranged `read` during the active session. Exact-name filters narrow the result, `includePrivate` exposes internal declarations, and `includeDocs` adds attached documentation comments when they are needed. Annotations and attributes remain in normal outlines. `symbol` accepts those numbers in four views: `signature` omits documentation and bodies, `signatureWithDocs` adds attached documentation without the body, `declaration` returns exact declaration source, and `declarationWithImports` adds required imports. It retrieves several locators in one call, can add bounded surrounding lines to exact declarations, and rejects the whole batch when any locator is stale. Users run `/read-stats` to see estimated token and cost savings for the session.
|
|
12
|
+
|
|
13
|
+
Explore scans a bounded, ignore-aware slice of the working root before each agent run. When the repository contains supported source and the selected native worker is usable, Explore adds language-specific AST-first guidance to the agent prompt. Unsupported repositories and hosts get the ordinary filesystem workflow. `api_discover`, `outline`, and `symbol` stay registered either way.
|
|
14
|
+
|
|
15
|
+
Installed packages support `api_discover`, `ast_search`, `outline`, `symbol`, and the relationship tools on Apple Silicon Macs. They include the worker, so users do not need Rust or Cargo. On other platforms, the rest of Explore remains available and AST tools report the platform limit when invoked.
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
For supported source, use the cheapest useful step:
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
1. Identify the current job: locate, reuse, edit, explain, or debug. Avoid loading implementation, callers, tests, or documentation needed only later.
|
|
20
|
+
2. Use a recursive `outline` to orient an unfamiliar repository or subtree. Outline a known package or file directly. Outline large Markdown files before retrieving a relevant heading section.
|
|
21
|
+
3. Use `api_discover` when reuse intent is known but the declaration path or exact name is not. Prefer package surfaces and public exports before private declarations.
|
|
22
|
+
4. Narrow paths, exact names, query work, and result limits once the likely target is known. Enable `includePrivate` only for targeted implementation work.
|
|
23
|
+
5. Stop at a clear outline or API candidate signature. Use `symbol(signature)` for closer contract inspection, `signatureWithDocs` only when attached documentation matters, and declaration views only for exact source.
|
|
24
|
+
6. Use `references`, `callers`, `callees`, `implementations`, or `tests` after selecting a change target and when the result can affect the plan. Inspect tests earlier only when the task begins with test behavior or a failure.
|
|
25
|
+
7. Use `ast_search` for code shapes and `grep` for literal text. Use targeted `read` for exact formatting, comments, parser gaps, unsupported files, or source outside declaration boundaries.
|
|
26
|
+
8. Prefer locator edits when a complete declaration or body is the natural edit boundary. Use textual patching when the change crosses those boundaries or depends on surrounding text.
|
|
14
27
|
|
|
15
|
-
|
|
16
|
-
2. Add exact names when likely declarations are known.
|
|
17
|
-
3. Set `includePrivate` when implementation work needs internals.
|
|
18
|
-
4. Send several locators to `symbol` for complete declarations.
|
|
19
|
-
5. Add context lines when the edit needs nearby source.
|
|
20
|
-
6. Use ranged or whole-file `read` for cross-cutting logic, exact formatting, or parser gaps.
|
|
28
|
+
Stop when the current question is answered. Expand exploration only for a specific unresolved question.
|