@shanepadgett/tau-agent 0.25.0 → 0.26.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/explore/README.md +5 -4
- package/extensions/explore/ast-tools.ts +362 -25
- package/extensions/explore/ast-worker.ts +48 -9
- package/extensions/subagent/session-resource.ts +1 -1
- package/extensions/tau-help/help.md +1 -1
- package/native-bin/darwin-arm64/tau-ast +0 -0
- package/package.json +2 -2
|
@@ -6,7 +6,7 @@ It exists so agents can inspect paths, discover files, search text, inspect decl
|
|
|
6
6
|
|
|
7
7
|
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
8
|
|
|
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
|
|
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, Swift, and Markdown files. Markdown headings locate their complete sections. It also accepts a package directory, which inspects supported files directly inside it. 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 to retrieve several exact declarations in one call, can add bounded surrounding lines, and rejects the whole batch when any locator is stale. Users run `/read-stats` to see estimated token and cost savings for the session.
|
|
10
10
|
|
|
11
11
|
Installed packages support `outline` and `symbol` 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.
|
|
12
12
|
|
|
@@ -15,6 +15,7 @@ Use the cheapest useful step:
|
|
|
15
15
|
1. Outline a package directory to discover its public API.
|
|
16
16
|
2. Add exact names when likely declarations are known.
|
|
17
17
|
3. Set `includePrivate` when implementation work needs internals.
|
|
18
|
-
4.
|
|
19
|
-
5.
|
|
20
|
-
6.
|
|
18
|
+
4. Set `includeDocs` when declaration documentation affects the task.
|
|
19
|
+
5. Send several locators to `symbol` for complete declarations.
|
|
20
|
+
6. Add context lines when the edit needs nearby source.
|
|
21
|
+
7. Use ranged or whole-file `read` for cross-cutting logic, exact formatting, or parser gaps.
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
truncateHead,
|
|
8
8
|
type Theme,
|
|
9
9
|
} from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
10
11
|
import { Text, truncateToWidth, visibleWidth, type Component } from "@earendil-works/pi-tui";
|
|
11
12
|
import { stat } from "node:fs/promises";
|
|
12
13
|
import { basename, extname, resolve } from "node:path";
|
|
@@ -18,17 +19,22 @@ import type {
|
|
|
18
19
|
AstLanguage,
|
|
19
20
|
OutlineEntry,
|
|
20
21
|
OutlineFileResult,
|
|
22
|
+
SourceRange,
|
|
21
23
|
OutlineTarget,
|
|
22
24
|
OutlineTargetResult,
|
|
23
25
|
SymbolBatchResult,
|
|
26
|
+
SymbolView,
|
|
24
27
|
} from "./ast-worker.ts";
|
|
25
28
|
|
|
26
29
|
const outlineParams = Type.Object(
|
|
27
30
|
{
|
|
28
31
|
path: Type.String({
|
|
29
|
-
description: "TypeScript, TSX, Odin, Go, Rust, C#, Java, Kotlin, or
|
|
32
|
+
description: "TypeScript, TSX, Odin, Go, Rust, C#, Java, Kotlin, Swift, or Markdown source file or directory",
|
|
30
33
|
}),
|
|
31
34
|
includePrivate: Type.Optional(Type.Boolean({ description: "Include private declarations and members" })),
|
|
35
|
+
includeDocs: Type.Optional(
|
|
36
|
+
Type.Boolean({ description: "Include attached documentation comments in declaration signatures" }),
|
|
37
|
+
),
|
|
32
38
|
names: Type.Optional(
|
|
33
39
|
Type.Array(Type.String(), {
|
|
34
40
|
minItems: 1,
|
|
@@ -44,6 +50,10 @@ const symbolParams = Type.Object(
|
|
|
44
50
|
minItems: 1,
|
|
45
51
|
description: "Numeric locators shown in parentheses by outline",
|
|
46
52
|
}),
|
|
53
|
+
view: StringEnum(["signature", "declaration", "declarationWithImports"] as const, {
|
|
54
|
+
description:
|
|
55
|
+
"Source view to retrieve; TypeScript, TSX, Odin, Go, Rust, C#, Java, Kotlin, Swift, and Markdown support selective views",
|
|
56
|
+
}),
|
|
47
57
|
contextLines: Type.Optional(
|
|
48
58
|
Type.Integer({ minimum: 0, description: "Lines of source context before and after each declaration" }),
|
|
49
59
|
),
|
|
@@ -67,9 +77,11 @@ interface LocatorRecord {
|
|
|
67
77
|
path: string;
|
|
68
78
|
name: string;
|
|
69
79
|
stale: boolean;
|
|
80
|
+
declarationRetrieved: boolean;
|
|
81
|
+
generation: number;
|
|
70
82
|
}
|
|
71
83
|
|
|
72
|
-
type OutlineArgs = { path: string; includePrivate?: boolean; names?: string[] };
|
|
84
|
+
type OutlineArgs = { path: string; includePrivate?: boolean; includeDocs?: boolean; names?: string[] };
|
|
73
85
|
|
|
74
86
|
export function createAstTools(client: AstClient, rowState: ToolRowStateStore) {
|
|
75
87
|
const locators = new Map<number, LocatorRecord>();
|
|
@@ -102,29 +114,204 @@ export function createAstTools(client: AstClient, rowState: ToolRowStateStore) {
|
|
|
102
114
|
}
|
|
103
115
|
|
|
104
116
|
function locator(entry: OutlineEntry, path: string): number {
|
|
117
|
+
if (!entry.locator) throw new Error(`Structural outline row ${entry.name} has no symbol locator`);
|
|
105
118
|
const id = nextLocator++;
|
|
106
|
-
const record = {
|
|
119
|
+
const record = {
|
|
120
|
+
id,
|
|
121
|
+
token: entry.locator,
|
|
122
|
+
path: resolve(path),
|
|
123
|
+
name: entry.name,
|
|
124
|
+
stale: false,
|
|
125
|
+
declarationRetrieved: false,
|
|
126
|
+
generation: client.getGeneration(),
|
|
127
|
+
};
|
|
107
128
|
locators.set(id, record);
|
|
108
129
|
return id;
|
|
109
130
|
}
|
|
110
131
|
|
|
111
|
-
function renderEntry(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
132
|
+
function renderEntry(
|
|
133
|
+
entry: OutlineEntry,
|
|
134
|
+
path: string,
|
|
135
|
+
language: AstLanguage,
|
|
136
|
+
indent: string,
|
|
137
|
+
signatureOverride?: string,
|
|
138
|
+
): string {
|
|
139
|
+
const lines = displayLineRange(entry.range);
|
|
140
|
+
let signature = (signatureOverride ?? entry.signature).trim();
|
|
141
|
+
if (
|
|
142
|
+
language === "typeScript" ||
|
|
143
|
+
language === "tsx" ||
|
|
144
|
+
language === "odin" ||
|
|
145
|
+
language === "go" ||
|
|
146
|
+
language === "rust" ||
|
|
147
|
+
language === "cSharp" ||
|
|
148
|
+
language === "java" ||
|
|
149
|
+
language === "kotlin" ||
|
|
150
|
+
language === "swift" ||
|
|
151
|
+
language === "markdown"
|
|
152
|
+
) {
|
|
153
|
+
const signatureLines = signature.split("\n");
|
|
154
|
+
const relativeNameLine = entry.nameRange.start.line - entry.range.start.line;
|
|
155
|
+
const keywordNameLine =
|
|
156
|
+
language === "go"
|
|
157
|
+
? signatureLines.findIndex(
|
|
158
|
+
(line) => /\b(?:func|type|const|var)\b/.test(line) && line.includes(entry.name),
|
|
159
|
+
)
|
|
160
|
+
: -1;
|
|
161
|
+
let inBlockComment = false;
|
|
162
|
+
let annotationDepth = 0;
|
|
163
|
+
let attributeDepth = 0;
|
|
164
|
+
const metadataLines = signatureLines.map((line) => {
|
|
165
|
+
const trimmed = line.trimStart();
|
|
166
|
+
if (inBlockComment) {
|
|
167
|
+
if (trimmed.includes("*/")) inBlockComment = false;
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
if (trimmed.startsWith("/*")) {
|
|
171
|
+
inBlockComment = !trimmed.includes("*/");
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
if (annotationDepth > 0) {
|
|
175
|
+
annotationDepth += (line.match(/\(/g) ?? []).length - (line.match(/\)/g) ?? []).length;
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
if (attributeDepth > 0) {
|
|
179
|
+
attributeDepth += (line.match(/\[/g) ?? []).length - (line.match(/\]/g) ?? []).length;
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
if (trimmed.startsWith("@") && !trimmed.startsWith(`@interface ${entry.name}`)) {
|
|
183
|
+
annotationDepth = (line.match(/\(/g) ?? []).length - (line.match(/\)/g) ?? []).length;
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
if (trimmed.startsWith("#[")) {
|
|
187
|
+
attributeDepth = (line.match(/\[/g) ?? []).length - (line.match(/\]/g) ?? []).length;
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
return trimmed.startsWith("//");
|
|
191
|
+
});
|
|
192
|
+
const declarationNameLine = signatureLines.findIndex(
|
|
193
|
+
(line, index) => !metadataLines[index] && line.includes(entry.name),
|
|
194
|
+
);
|
|
195
|
+
const nameLine =
|
|
196
|
+
relativeNameLine >= 0 && relativeNameLine === declarationNameLine
|
|
197
|
+
? relativeNameLine
|
|
198
|
+
: keywordNameLine >= 0
|
|
199
|
+
? keywordNameLine
|
|
200
|
+
: declarationNameLine;
|
|
201
|
+
const declarationLine = language === "markdown" ? Math.max(0, relativeNameLine) : nameLine >= 0 ? nameLine : 0;
|
|
202
|
+
const sourceLine = signatureLines[declarationLine] ?? entry.name;
|
|
203
|
+
const first =
|
|
204
|
+
language === "markdown" || sourceLine.includes(entry.name) ? sourceLine : `${entry.name} ${sourceLine}`;
|
|
205
|
+
const warning =
|
|
206
|
+
entry.certainty === "certain"
|
|
207
|
+
? ""
|
|
208
|
+
: ` [${entry.certainty}${entry.certaintyReason ? `: ${entry.certaintyReason}` : ""}]`;
|
|
209
|
+
return [
|
|
210
|
+
...signatureLines.slice(0, declarationLine).map((line) => `${indent}${line}`),
|
|
211
|
+
`${indent}${lines}(${locator(entry, path)}): ${first}${warning}`,
|
|
212
|
+
...signatureLines.slice(declarationLine + 1).map((line) => `${indent}${line}`),
|
|
213
|
+
].join("\n");
|
|
123
214
|
}
|
|
215
|
+
signature = signature.replace(/\s+/g, " ");
|
|
124
216
|
const label = signature && signature.includes(entry.name) ? signature : `${entry.name} ${signature}`.trim();
|
|
125
217
|
return `${indent}${lines}(${locator(entry, path)}): ${label}`;
|
|
126
218
|
}
|
|
127
219
|
|
|
220
|
+
function renderStructuralEntry(entry: OutlineEntry, indent: string): string {
|
|
221
|
+
const lines = displayLineRange(entry.range);
|
|
222
|
+
return `${indent}${lines}: ${entry.signature.trim().replace(/\s+/g, " ")}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function containerFrame(
|
|
226
|
+
item: OutlineFileResult["items"][number],
|
|
227
|
+
language: AstLanguage,
|
|
228
|
+
): { header: string; footer: string } | undefined {
|
|
229
|
+
const firstMember = item.members[0];
|
|
230
|
+
if (!firstMember) return undefined;
|
|
231
|
+
if (
|
|
232
|
+
(language === "odin" ||
|
|
233
|
+
language === "cSharp" ||
|
|
234
|
+
language === "java" ||
|
|
235
|
+
language === "kotlin" ||
|
|
236
|
+
language === "swift") &&
|
|
237
|
+
item.bodyRange
|
|
238
|
+
) {
|
|
239
|
+
const bodyOffset = item.bodyRange.startByte - item.range.startByte;
|
|
240
|
+
const signature = Buffer.from(item.signature);
|
|
241
|
+
if (bodyOffset >= 0 && bodyOffset < signature.byteLength) {
|
|
242
|
+
const header = signature
|
|
243
|
+
.subarray(0, bodyOffset + 1)
|
|
244
|
+
.toString("utf8")
|
|
245
|
+
.trimEnd();
|
|
246
|
+
if (header.endsWith("{")) return { header, footer: "}" };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (
|
|
250
|
+
item.symbolType === "class" ||
|
|
251
|
+
item.symbolType === "struct" ||
|
|
252
|
+
item.symbolType === "interface" ||
|
|
253
|
+
item.symbolType === "enum" ||
|
|
254
|
+
item.symbolType === "object" ||
|
|
255
|
+
item.symbolType === "namespace"
|
|
256
|
+
) {
|
|
257
|
+
const members = (
|
|
258
|
+
language === "typeScript" ||
|
|
259
|
+
language === "tsx" ||
|
|
260
|
+
language === "cSharp" ||
|
|
261
|
+
language === "java" ||
|
|
262
|
+
language === "kotlin" ||
|
|
263
|
+
language === "swift"
|
|
264
|
+
? item.members.filter(
|
|
265
|
+
(member) => member.qualifiedName.split(".").slice(0, -1).join(".") === item.qualifiedName,
|
|
266
|
+
)
|
|
267
|
+
: item.members
|
|
268
|
+
)
|
|
269
|
+
.map((member) =>
|
|
270
|
+
(language === "java" &&
|
|
271
|
+
(member.symbolType === "class" ||
|
|
272
|
+
member.symbolType === "struct" ||
|
|
273
|
+
member.symbolType === "interface" ||
|
|
274
|
+
member.symbolType === "enum") &&
|
|
275
|
+
member.signature.endsWith("{")
|
|
276
|
+
? `${member.signature} … }`
|
|
277
|
+
: member.signature
|
|
278
|
+
)
|
|
279
|
+
.split("\n")
|
|
280
|
+
.map((line) => ` ${line}`)
|
|
281
|
+
.join("\n"),
|
|
282
|
+
)
|
|
283
|
+
.join("\n");
|
|
284
|
+
const braceSuffix = `\n${members}\n}`;
|
|
285
|
+
if (item.signature.endsWith(braceSuffix))
|
|
286
|
+
return { header: item.signature.slice(0, -braceSuffix.length), footer: "}" };
|
|
287
|
+
const tupleMembers = item.members
|
|
288
|
+
.map((member) =>
|
|
289
|
+
`${member.signature},`
|
|
290
|
+
.split("\n")
|
|
291
|
+
.map((line) => ` ${line}`)
|
|
292
|
+
.join("\n"),
|
|
293
|
+
)
|
|
294
|
+
.join("\n");
|
|
295
|
+
const tupleSuffix = `\n${tupleMembers}\n);`;
|
|
296
|
+
if (item.signature.endsWith(tupleSuffix))
|
|
297
|
+
return { header: item.signature.slice(0, -tupleSuffix.length), footer: ");" };
|
|
298
|
+
const tupleMarker = `\n${tupleMembers}\n)`;
|
|
299
|
+
const tupleMarkerIndex = item.signature.lastIndexOf(tupleMarker);
|
|
300
|
+
if (tupleMarkerIndex >= 0)
|
|
301
|
+
return {
|
|
302
|
+
header: item.signature.slice(0, tupleMarkerIndex),
|
|
303
|
+
footer: item.signature.slice(tupleMarkerIndex + tupleMarker.length - 1),
|
|
304
|
+
};
|
|
305
|
+
if (item.symbolType === "class") return undefined;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const memberOffset = firstMember.range.startByte - item.range.startByte;
|
|
309
|
+
const signature = Buffer.from(item.signature);
|
|
310
|
+
if (memberOffset <= 0 || memberOffset > signature.byteLength) return undefined;
|
|
311
|
+
const header = signature.subarray(0, memberOffset).toString("utf8").trimEnd();
|
|
312
|
+
return header.endsWith("{") ? { header, footer: "}" } : undefined;
|
|
313
|
+
}
|
|
314
|
+
|
|
128
315
|
function renderOutlineFile(file: OutlineFileResult, cwd: string, includeHeader: boolean): string[] {
|
|
129
316
|
const lines = includeHeader
|
|
130
317
|
? [
|
|
@@ -136,7 +323,123 @@ export function createAstTools(client: AstClient, rowState: ToolRowStateStore) {
|
|
|
136
323
|
`warning: parser recovered with ${file.diagnostics.errorNodes} ERROR and ${file.diagnostics.missingNodes} MISSING nodes`,
|
|
137
324
|
);
|
|
138
325
|
}
|
|
139
|
-
const declarations = file.items.filter((item) =>
|
|
326
|
+
const declarations = file.items.filter((item) => item.rowKind === "declaration");
|
|
327
|
+
if (
|
|
328
|
+
file.language === "typeScript" ||
|
|
329
|
+
file.language === "tsx" ||
|
|
330
|
+
file.language === "odin" ||
|
|
331
|
+
file.language === "go" ||
|
|
332
|
+
file.language === "rust" ||
|
|
333
|
+
file.language === "cSharp" ||
|
|
334
|
+
file.language === "java" ||
|
|
335
|
+
file.language === "kotlin" ||
|
|
336
|
+
file.language === "swift" ||
|
|
337
|
+
file.language === "markdown"
|
|
338
|
+
) {
|
|
339
|
+
let previousSection = "";
|
|
340
|
+
for (const item of file.items) {
|
|
341
|
+
const section =
|
|
342
|
+
item.rowKind === "package"
|
|
343
|
+
? "package"
|
|
344
|
+
: item.rowKind === "import"
|
|
345
|
+
? "imports"
|
|
346
|
+
: item.rowKind === "export"
|
|
347
|
+
? "exports"
|
|
348
|
+
: item.rowKind === "sideEffect"
|
|
349
|
+
? "side effects"
|
|
350
|
+
: "declarations";
|
|
351
|
+
if (section !== previousSection) {
|
|
352
|
+
if (lines.length > 0) lines.push("");
|
|
353
|
+
lines.push(section);
|
|
354
|
+
previousSection = section;
|
|
355
|
+
}
|
|
356
|
+
const frame = item.rowKind === "declaration" ? containerFrame(item, file.language) : undefined;
|
|
357
|
+
lines.push(
|
|
358
|
+
item.rowKind !== "declaration"
|
|
359
|
+
? renderStructuralEntry(item, "")
|
|
360
|
+
: renderEntry(item, file.path, file.language, "", frame?.header),
|
|
361
|
+
);
|
|
362
|
+
const nestedContainers = new Map(
|
|
363
|
+
file.language === "typeScript" ||
|
|
364
|
+
file.language === "tsx" ||
|
|
365
|
+
file.language === "odin" ||
|
|
366
|
+
file.language === "cSharp" ||
|
|
367
|
+
file.language === "java" ||
|
|
368
|
+
file.language === "kotlin" ||
|
|
369
|
+
file.language === "swift"
|
|
370
|
+
? [item, ...item.members]
|
|
371
|
+
.filter(
|
|
372
|
+
(entry) =>
|
|
373
|
+
entry.symbolType === "class" ||
|
|
374
|
+
entry.symbolType === "struct" ||
|
|
375
|
+
entry.symbolType === "interface" ||
|
|
376
|
+
entry.symbolType === "enum",
|
|
377
|
+
)
|
|
378
|
+
.map((entry) => [entry.qualifiedName, entry] as const)
|
|
379
|
+
: [],
|
|
380
|
+
);
|
|
381
|
+
const emittedEnumSeparators = new Set<string>();
|
|
382
|
+
const openNestedDepths: number[] = [];
|
|
383
|
+
for (const [memberIndex, member] of item.members.entries()) {
|
|
384
|
+
const depth =
|
|
385
|
+
file.language === "typeScript" ||
|
|
386
|
+
file.language === "tsx" ||
|
|
387
|
+
file.language === "odin" ||
|
|
388
|
+
file.language === "cSharp" ||
|
|
389
|
+
file.language === "java" ||
|
|
390
|
+
file.language === "kotlin" ||
|
|
391
|
+
file.language === "swift"
|
|
392
|
+
? Math.max(1, member.qualifiedName.split(".").length - item.qualifiedName.split(".").length)
|
|
393
|
+
: 1;
|
|
394
|
+
const parent = member.qualifiedName.split(".").slice(0, -1).join(".");
|
|
395
|
+
const siblings =
|
|
396
|
+
file.language === "typeScript" ||
|
|
397
|
+
file.language === "tsx" ||
|
|
398
|
+
file.language === "odin" ||
|
|
399
|
+
file.language === "cSharp" ||
|
|
400
|
+
file.language === "java" ||
|
|
401
|
+
file.language === "kotlin" ||
|
|
402
|
+
file.language === "swift"
|
|
403
|
+
? item.members.filter(
|
|
404
|
+
(candidate) => candidate.qualifiedName.split(".").slice(0, -1).join(".") === parent,
|
|
405
|
+
)
|
|
406
|
+
: [];
|
|
407
|
+
const parentIsEnum = file.language === "java" && nestedContainers.get(parent)?.symbolType === "enum";
|
|
408
|
+
while (openNestedDepths.length > 0 && (openNestedDepths.at(-1) ?? 0) >= depth) {
|
|
409
|
+
const closingDepth = openNestedDepths.pop();
|
|
410
|
+
if (closingDepth !== undefined) lines.push(`${" ".repeat(closingDepth)}}`);
|
|
411
|
+
}
|
|
412
|
+
if (
|
|
413
|
+
parentIsEnum &&
|
|
414
|
+
!siblings.some((candidate) => candidate.symbolType === "enumMember") &&
|
|
415
|
+
!emittedEnumSeparators.has(parent)
|
|
416
|
+
) {
|
|
417
|
+
lines.push(`${" ".repeat(depth)};`);
|
|
418
|
+
emittedEnumSeparators.add(parent);
|
|
419
|
+
}
|
|
420
|
+
let memberSignature = member.signature;
|
|
421
|
+
const nextMember = item.members[memberIndex + 1];
|
|
422
|
+
const hasDescendants = nextMember?.qualifiedName.startsWith(`${member.qualifiedName}.`) ?? false;
|
|
423
|
+
if (hasDescendants && memberSignature.endsWith(" { … }")) {
|
|
424
|
+
memberSignature = `${memberSignature.slice(0, -" { … }".length)} {`;
|
|
425
|
+
openNestedDepths.push(depth);
|
|
426
|
+
}
|
|
427
|
+
if (parentIsEnum && member.symbolType === "enumMember") {
|
|
428
|
+
const constants = siblings.filter((candidate) => candidate.symbolType === "enumMember");
|
|
429
|
+
const constantIndex = constants.indexOf(member);
|
|
430
|
+
if (constantIndex + 1 < constants.length) memberSignature += ",";
|
|
431
|
+
else if (siblings.some((candidate) => candidate.symbolType !== "enumMember")) memberSignature += ";";
|
|
432
|
+
}
|
|
433
|
+
lines.push(renderEntry(member, file.path, file.language, " ".repeat(depth), memberSignature));
|
|
434
|
+
}
|
|
435
|
+
while (openNestedDepths.length > 0) {
|
|
436
|
+
const closingDepth = openNestedDepths.pop();
|
|
437
|
+
if (closingDepth !== undefined) lines.push(`${" ".repeat(closingDepth)}}`);
|
|
438
|
+
}
|
|
439
|
+
if (frame) lines.push(frame.footer);
|
|
440
|
+
}
|
|
441
|
+
return lines;
|
|
442
|
+
}
|
|
140
443
|
const groups = new Map<string, typeof declarations>();
|
|
141
444
|
for (const item of declarations) {
|
|
142
445
|
const visibility = item.isExported ? "public" : "private";
|
|
@@ -167,6 +470,7 @@ export function createAstTools(client: AstClient, rowState: ToolRowStateStore) {
|
|
|
167
470
|
promptGuidelines: [
|
|
168
471
|
"Use a public package outline first to discover reusable APIs; add exact names when likely symbols are known.",
|
|
169
472
|
"Set includePrivate when internal implementation discovery is needed.",
|
|
473
|
+
"Leave includeDocs off for routine exploration; enable it when documentation comments are needed.",
|
|
170
474
|
"Treat each parenthesized number after a line range as that declaration's symbol locator.",
|
|
171
475
|
"Use symbol with several locators when complete declaration source is needed.",
|
|
172
476
|
],
|
|
@@ -178,7 +482,13 @@ export function createAstTools(client: AstClient, rowState: ToolRowStateStore) {
|
|
|
178
482
|
? { kind: "directory", path }
|
|
179
483
|
: { kind: "file", path, language: languageForPath(path) };
|
|
180
484
|
const names = params.names ?? [];
|
|
181
|
-
const result = await client.outline(
|
|
485
|
+
const result = await client.outline(
|
|
486
|
+
target,
|
|
487
|
+
params.includePrivate ?? false,
|
|
488
|
+
params.includeDocs ?? false,
|
|
489
|
+
names,
|
|
490
|
+
signal,
|
|
491
|
+
);
|
|
182
492
|
const lines = result.files.flatMap((file, index) => [
|
|
183
493
|
...(index === 0 ? [] : [""]),
|
|
184
494
|
...renderOutlineFile(file, ctx.cwd, result.files.length > 1),
|
|
@@ -186,7 +496,10 @@ export function createAstTools(client: AstClient, rowState: ToolRowStateStore) {
|
|
|
186
496
|
const declarationCount = result.files.reduce(
|
|
187
497
|
(count, file) =>
|
|
188
498
|
count +
|
|
189
|
-
file.items.reduce(
|
|
499
|
+
file.items.reduce(
|
|
500
|
+
(fileCount, item) => fileCount + (item.rowKind === "declaration" ? 1 + item.members.length : 0),
|
|
501
|
+
0,
|
|
502
|
+
),
|
|
190
503
|
0,
|
|
191
504
|
);
|
|
192
505
|
if (declarationCount === 0) lines.push(names.length > 0 ? "No matching declarations" : "No declarations");
|
|
@@ -222,14 +535,21 @@ export function createAstTools(client: AstClient, rowState: ToolRowStateStore) {
|
|
|
222
535
|
promptSnippet: "Retrieve exact declaration source for several outline locators",
|
|
223
536
|
parameters: symbolParams,
|
|
224
537
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
538
|
+
if (params.contextLines !== undefined && params.view !== "declaration") {
|
|
539
|
+
throw new Error("contextLines is supported only with view=declaration");
|
|
540
|
+
}
|
|
225
541
|
const records = [...new Set(params.locators)].map((id) => {
|
|
226
542
|
const record = locators.get(id);
|
|
227
543
|
if (!record) throw new Error(`Unknown symbol locator: ${id}. Run outline again.`);
|
|
228
544
|
if (record.stale) throw new Error(`Symbol locator ${id} is stale. Run outline again.`);
|
|
545
|
+
if (record.generation !== client.getGeneration()) {
|
|
546
|
+
throw new Error(`Symbol locator ${id} is stale because the AST worker restarted. Run outline again.`);
|
|
547
|
+
}
|
|
229
548
|
return record;
|
|
230
549
|
});
|
|
231
550
|
const result = await client.symbol(
|
|
232
551
|
[...new Set(records.map((record) => record.token))],
|
|
552
|
+
params.view,
|
|
233
553
|
params.contextLines ?? 0,
|
|
234
554
|
signal,
|
|
235
555
|
);
|
|
@@ -248,10 +568,7 @@ export function createAstTools(client: AstClient, rowState: ToolRowStateStore) {
|
|
|
248
568
|
return declaration ? (requestedByToken.get(declaration.locator) ?? []) : [];
|
|
249
569
|
});
|
|
250
570
|
const range = block.returnedRange;
|
|
251
|
-
const lineRange =
|
|
252
|
-
range.start.line === range.end.line
|
|
253
|
-
? `${range.start.line + 1}`
|
|
254
|
-
: `${range.start.line + 1}-${range.end.line + 1}`;
|
|
571
|
+
const lineRange = displayLineRange(range);
|
|
255
572
|
lines.push(
|
|
256
573
|
...(includePaths ? [formatPathForDisplay(block.path, ctx.cwd)] : []),
|
|
257
574
|
`${lineRange}(${represented.map((record) => record.id).join(",")}): ${represented.map((record) => record.name).join(", ")}`,
|
|
@@ -260,6 +577,10 @@ export function createAstTools(client: AstClient, rowState: ToolRowStateStore) {
|
|
|
260
577
|
}
|
|
261
578
|
const sourceBytes = result.blocks.reduce((count, block) => count + Buffer.byteLength(block.source), 0);
|
|
262
579
|
const output = compact(lines.join("\n"), sourceBytes, result.declarations.length, "symbol", result);
|
|
580
|
+
if (output.details.truncated) {
|
|
581
|
+
throw new Error("Symbol result exceeded the output limit. Request fewer locators.");
|
|
582
|
+
}
|
|
583
|
+
if (params.view === "declaration") for (const record of records) record.declarationRetrieved = true;
|
|
263
584
|
return { content: [{ type: "text", text: output.text }], details: output.details };
|
|
264
585
|
},
|
|
265
586
|
renderCall(args, theme, context) {
|
|
@@ -272,10 +593,10 @@ export function createAstTools(client: AstClient, rowState: ToolRowStateStore) {
|
|
|
272
593
|
context.toolCallId,
|
|
273
594
|
"symbol",
|
|
274
595
|
targets,
|
|
275
|
-
[args.
|
|
596
|
+
[symbolOptions(args.view, args.contextLines)],
|
|
276
597
|
theme,
|
|
277
598
|
);
|
|
278
|
-
component.set(targets, [args.
|
|
599
|
+
component.set(targets, [symbolOptions(args.view, args.contextLines)], theme);
|
|
279
600
|
return component;
|
|
280
601
|
},
|
|
281
602
|
renderResult(result, options, theme, context) {
|
|
@@ -300,6 +621,18 @@ export function createAstTools(client: AstClient, rowState: ToolRowStateStore) {
|
|
|
300
621
|
};
|
|
301
622
|
}
|
|
302
623
|
|
|
624
|
+
function displayLineRange(range: SourceRange): string {
|
|
625
|
+
const endLine =
|
|
626
|
+
range.endByte > range.startByte && range.end.column === 0
|
|
627
|
+
? Math.max(range.start.line, range.end.line - 1)
|
|
628
|
+
: range.end.line;
|
|
629
|
+
return range.start.line === endLine ? `${range.start.line + 1}` : `${range.start.line + 1}-${endLine + 1}`;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function symbolOptions(view: SymbolView, contextLines: number | undefined): string {
|
|
633
|
+
return `[${view}${contextLines === undefined ? "" : ` context=${contextLines}`}]`;
|
|
634
|
+
}
|
|
635
|
+
|
|
303
636
|
function renderAstResult(
|
|
304
637
|
result: { content: Array<{ type: string; text?: string }>; details?: AstToolDetails },
|
|
305
638
|
expanded: boolean,
|
|
@@ -407,7 +740,7 @@ function truncateLeft(text: string, width: number): string {
|
|
|
407
740
|
}
|
|
408
741
|
|
|
409
742
|
function outlineOptionVariants(args: OutlineArgs): string[] {
|
|
410
|
-
const fixed = args.includePrivate ? ["private"] : [];
|
|
743
|
+
const fixed = [...(args.includePrivate ? ["private"] : []), ...(args.includeDocs ? ["docs"] : [])];
|
|
411
744
|
const names = args.names ?? [];
|
|
412
745
|
if (names.length === 0) return [fixed.length > 0 ? `[${fixed.join(" ")}]` : ""];
|
|
413
746
|
|
|
@@ -485,6 +818,10 @@ function languageForPath(path: string): AstLanguage {
|
|
|
485
818
|
return "kotlin";
|
|
486
819
|
case ".swift":
|
|
487
820
|
return "swift";
|
|
821
|
+
case ".md":
|
|
822
|
+
case ".markdown":
|
|
823
|
+
case ".mdown":
|
|
824
|
+
return "markdown";
|
|
488
825
|
default:
|
|
489
826
|
throw new Error(`Unsupported outline file type: ${extname(path) || "no extension"}`);
|
|
490
827
|
}
|
|
@@ -3,7 +3,17 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
|
-
export type AstLanguage =
|
|
6
|
+
export type AstLanguage =
|
|
7
|
+
| "typeScript"
|
|
8
|
+
| "tsx"
|
|
9
|
+
| "odin"
|
|
10
|
+
| "go"
|
|
11
|
+
| "rust"
|
|
12
|
+
| "cSharp"
|
|
13
|
+
| "java"
|
|
14
|
+
| "kotlin"
|
|
15
|
+
| "swift"
|
|
16
|
+
| "markdown";
|
|
7
17
|
|
|
8
18
|
export type OutlineTarget = { kind: "file"; path: string; language: AstLanguage } | { kind: "directory"; path: string };
|
|
9
19
|
|
|
@@ -23,13 +33,22 @@ export interface OutlineEntry {
|
|
|
23
33
|
role: "item" | "member";
|
|
24
34
|
symbolType: string;
|
|
25
35
|
name: string;
|
|
36
|
+
qualifiedName: string;
|
|
26
37
|
range: SourceRange;
|
|
38
|
+
nameRange: SourceRange;
|
|
39
|
+
receiverRange?: SourceRange;
|
|
40
|
+
bodyRange?: SourceRange;
|
|
27
41
|
signature: string;
|
|
28
42
|
astKind: string;
|
|
29
|
-
|
|
43
|
+
certainty: "certain" | "recovered" | "nearRecovery";
|
|
44
|
+
certaintyReason?: string;
|
|
45
|
+
locator?: string;
|
|
30
46
|
}
|
|
31
47
|
|
|
48
|
+
export type SymbolView = "signature" | "declaration" | "declarationWithImports";
|
|
49
|
+
|
|
32
50
|
export interface OutlineItem extends OutlineEntry {
|
|
51
|
+
rowKind: "package" | "import" | "declaration" | "export" | "sideEffect";
|
|
33
52
|
isImport: boolean;
|
|
34
53
|
isExported: boolean;
|
|
35
54
|
members: Array<OutlineEntry & { isPublic: boolean }>;
|
|
@@ -73,20 +92,27 @@ export interface SymbolBatchResult {
|
|
|
73
92
|
}
|
|
74
93
|
|
|
75
94
|
export interface AstClient {
|
|
95
|
+
getGeneration(): number;
|
|
76
96
|
outline(
|
|
77
97
|
target: OutlineTarget,
|
|
78
98
|
includePrivate: boolean,
|
|
99
|
+
includeDocs: boolean,
|
|
79
100
|
names: string[],
|
|
80
101
|
signal: AbortSignal | undefined,
|
|
81
102
|
): Promise<OutlineTargetResult>;
|
|
82
|
-
symbol(
|
|
103
|
+
symbol(
|
|
104
|
+
locators: string[],
|
|
105
|
+
view: SymbolView,
|
|
106
|
+
contextLines: number,
|
|
107
|
+
signal: AbortSignal | undefined,
|
|
108
|
+
): Promise<SymbolBatchResult>;
|
|
83
109
|
shutdown(): Promise<void>;
|
|
84
110
|
}
|
|
85
111
|
|
|
86
112
|
type WorkerRequestPayload =
|
|
87
113
|
| { operation: "handshake" }
|
|
88
|
-
| { operation: "outline"; target: OutlineTarget; includePrivate: boolean; names: string[] }
|
|
89
|
-
| { operation: "symbol"; locators: string[]; contextLines: number };
|
|
114
|
+
| { operation: "outline"; target: OutlineTarget; includePrivate: boolean; includeDocs: boolean; names: string[] }
|
|
115
|
+
| { operation: "symbol"; locators: string[]; view: SymbolView; contextLines: number };
|
|
90
116
|
|
|
91
117
|
interface WorkerResponse {
|
|
92
118
|
requestId: number;
|
|
@@ -102,7 +128,7 @@ interface PendingRequest {
|
|
|
102
128
|
removeAbortListener(): void;
|
|
103
129
|
}
|
|
104
130
|
|
|
105
|
-
const PROTOCOL_VERSION =
|
|
131
|
+
const PROTOCOL_VERSION = 5;
|
|
106
132
|
const MAX_FRAME_BYTES = 8 * 1024 * 1024;
|
|
107
133
|
const STDERR_BYTES = 16 * 1024;
|
|
108
134
|
|
|
@@ -142,25 +168,36 @@ export class AstWorkerClient implements AstClient {
|
|
|
142
168
|
private nextRequestId = 1;
|
|
143
169
|
private incoming = Buffer.alloc(0);
|
|
144
170
|
private stderr = "";
|
|
171
|
+
private generation = 0;
|
|
145
172
|
|
|
146
173
|
constructor(command: string | undefined = undefined, args: readonly string[] = []) {
|
|
147
174
|
this.command = command;
|
|
148
175
|
this.args = args;
|
|
149
176
|
}
|
|
150
177
|
|
|
178
|
+
getGeneration(): number {
|
|
179
|
+
return this.generation;
|
|
180
|
+
}
|
|
181
|
+
|
|
151
182
|
async outline(
|
|
152
183
|
target: OutlineTarget,
|
|
153
184
|
includePrivate: boolean,
|
|
185
|
+
includeDocs: boolean,
|
|
154
186
|
names: string[],
|
|
155
187
|
signal: AbortSignal | undefined,
|
|
156
188
|
): Promise<OutlineTargetResult> {
|
|
157
|
-
const result = await this.request({ operation: "outline", target, includePrivate, names }, signal);
|
|
189
|
+
const result = await this.request({ operation: "outline", target, includePrivate, includeDocs, names }, signal);
|
|
158
190
|
if (result.kind !== "outline") throw new Error("tau-ast returned the wrong result for outline");
|
|
159
191
|
return result as unknown as OutlineTargetResult;
|
|
160
192
|
}
|
|
161
193
|
|
|
162
|
-
async symbol(
|
|
163
|
-
|
|
194
|
+
async symbol(
|
|
195
|
+
locators: string[],
|
|
196
|
+
view: SymbolView,
|
|
197
|
+
contextLines: number,
|
|
198
|
+
signal: AbortSignal | undefined,
|
|
199
|
+
): Promise<SymbolBatchResult> {
|
|
200
|
+
const result = await this.request({ operation: "symbol", locators, view, contextLines }, signal);
|
|
164
201
|
if (result.kind !== "symbol") throw new Error("tau-ast returned the wrong result for symbol");
|
|
165
202
|
return result as unknown as SymbolBatchResult;
|
|
166
203
|
}
|
|
@@ -206,6 +243,7 @@ export class AstWorkerClient implements AstClient {
|
|
|
206
243
|
: resolveAstWorkerCommand(fileURLToPath(new URL("../../", import.meta.url)), process.platform, process.arch);
|
|
207
244
|
if ("error" in resolution) throw resolution.error;
|
|
208
245
|
const child = spawn(resolution.command, this.args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
246
|
+
this.generation += 1;
|
|
209
247
|
this.child = child;
|
|
210
248
|
this.incoming = Buffer.alloc(0);
|
|
211
249
|
this.stderr = "";
|
|
@@ -302,6 +340,7 @@ export class AstWorkerClient implements AstClient {
|
|
|
302
340
|
private fail(child: ChildProcessWithoutNullStreams, error: Error, kill = true): void {
|
|
303
341
|
if (this.child !== child) return;
|
|
304
342
|
this.child = undefined;
|
|
343
|
+
this.generation += 1;
|
|
305
344
|
this.incoming = Buffer.alloc(0);
|
|
306
345
|
this.rejectPending(error);
|
|
307
346
|
if (kill && child.exitCode === null) child.kill();
|
|
@@ -118,7 +118,7 @@ export async function createSubagentSessionResource(
|
|
|
118
118
|
const modelRuntime = await ModelRuntime.create();
|
|
119
119
|
modelRuntime.registerNativeProvider(inputs.provider);
|
|
120
120
|
if (inputs.runtimeApiKey !== undefined)
|
|
121
|
-
await modelRuntime.setRuntimeApiKey(inputs.model.provider, inputs.runtimeApiKey);
|
|
121
|
+
await modelRuntime.setRuntimeApiKey(inputs.model.provider, inputs.runtimeApiKey, { allowNetwork: false });
|
|
122
122
|
if (signal.aborted) throw new Error(`Agent ${inputs.definition.name} startup aborted`);
|
|
123
123
|
const resourceLoader = new DefaultResourceLoader({
|
|
124
124
|
cwd: inputs.cwd,
|
|
@@ -40,7 +40,7 @@ Gives the agent `context_prune` for creating a hard context checkpoint after bro
|
|
|
40
40
|
|
|
41
41
|
## explore
|
|
42
42
|
|
|
43
|
-
Replaces Pi’s filesystem inspection tools with compact Tau versions: `ls`, `find`, `grep`, and `read`. It also adds `outline` for public declarations in TypeScript, TSX, Odin, Go, Rust, C#, Java, Kotlin, and
|
|
43
|
+
Replaces Pi’s filesystem inspection tools with compact Tau versions: `ls`, `find`, `grep`, and `read`. It also adds `outline` for public declarations in TypeScript, TSX, Odin, Go, Rust, C#, Java, Kotlin, Swift, and Markdown files or package directories. Markdown headings locate their complete sections. Exact-name filters and `includePrivate` narrow or expand that surface. Attached documentation comments are omitted by default; `includeDocs` adds them without hiding annotations or attributes. Parenthesized numbers identify declarations for `symbol`, which retrieves several complete declarations with optional surrounding lines and rejects stale batches. Installed packages support `outline` and `symbol` on Apple Silicon Macs without requiring Rust or Cargo; other platforms retain the rest of Explore and report the limit when an AST tool is invoked. These tools produce smaller model payloads and readable tool rows. Repeated `read` calls return unchanged markers or useful diffs when branch history proves the agent already saw the base content. A failed patch unlocks one normal reread of the affected path. `/read-stats` shows estimated token and cost savings for the current chat and whole session.
|
|
44
44
|
|
|
45
45
|
## footer
|
|
46
46
|
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shanepadgett/tau-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Tau is a custom agentic harness built with pi extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"README.md"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@shanepadgett/tau-tui": "0.
|
|
38
|
+
"@shanepadgett/tau-tui": "0.26.0",
|
|
39
39
|
"@toon-format/toon": "2.3.0",
|
|
40
40
|
"image-size": "2.0.2",
|
|
41
41
|
"smol-toml": "1.7.0"
|