@shanepadgett/tau-agent 0.26.0 → 0.28.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/sync.ts +1 -9
- package/extensions/context-pruning/README.md +2 -2
- package/extensions/context-pruning/index.ts +58 -57
- package/extensions/context-pruning/prune.ts +2 -2
- package/extensions/context-pruning/render.ts +51 -39
- package/extensions/context-pruning/settings.ts +4 -5
- package/extensions/explore/README.md +13 -14
- package/extensions/explore/ast/adapter.ts +97 -0
- package/extensions/explore/ast/engine.ts +470 -0
- package/extensions/explore/ast/format/ast-search.ts +97 -0
- package/extensions/explore/ast/format/composite.ts +26 -0
- package/extensions/explore/ast/format/context.ts +66 -0
- package/extensions/explore/ast/format/deps.ts +86 -0
- package/extensions/explore/ast/format/discover.ts +85 -0
- package/extensions/explore/ast/format/impact.ts +105 -0
- package/extensions/explore/ast/format/outline.ts +66 -0
- package/extensions/explore/ast/format/relationships.ts +81 -0
- package/extensions/explore/ast/format/show.ts +38 -0
- package/extensions/explore/ast/grammars/kotlin.wasm +0 -0
- package/extensions/explore/ast/grammars/manifest.json +41 -0
- package/extensions/explore/ast/grammars/manifest.ts +99 -0
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/grammars/swift.wasm +0 -0
- package/extensions/explore/ast/graph/file-graph.ts +360 -0
- package/extensions/explore/ast/graph/relationships.ts +597 -0
- package/extensions/explore/ast/identity.ts +119 -0
- package/extensions/explore/ast/ir.ts +130 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +189 -0
- package/extensions/explore/ast/languages/csharp.ts +493 -0
- package/extensions/explore/ast/languages/file-dep-util.ts +120 -0
- package/extensions/explore/ast/languages/fixtures/sample.cs +17 -0
- package/extensions/explore/ast/languages/fixtures/sample.java +21 -0
- package/extensions/explore/ast/languages/fixtures/sample.kt +15 -0
- package/extensions/explore/ast/languages/fixtures/sample.odin +28 -0
- package/extensions/explore/ast/languages/fixtures/sample.rs +31 -0
- package/extensions/explore/ast/languages/fixtures/sample.swift +25 -0
- package/extensions/explore/ast/languages/go-file-deps.ts +139 -0
- package/extensions/explore/ast/languages/go.ts +545 -0
- package/extensions/explore/ast/languages/java.ts +412 -0
- package/extensions/explore/ast/languages/jvm-file-deps.ts +217 -0
- package/extensions/explore/ast/languages/kotlin.ts +611 -0
- package/extensions/explore/ast/languages/odin-file-deps.ts +175 -0
- package/extensions/explore/ast/languages/odin.ts +407 -0
- package/extensions/explore/ast/languages/rust-file-deps.ts +159 -0
- package/extensions/explore/ast/languages/rust.ts +506 -0
- package/extensions/explore/ast/languages/swift-file-deps.ts +59 -0
- package/extensions/explore/ast/languages/swift.ts +466 -0
- package/extensions/explore/ast/languages/tree.ts +220 -0
- package/extensions/explore/ast/languages/typescript-file-deps.ts +259 -0
- package/extensions/explore/ast/languages/typescript-package-surface.ts +440 -0
- package/extensions/explore/ast/languages/typescript.ts +716 -0
- package/extensions/explore/ast/markdown.ts +145 -0
- package/extensions/explore/ast/package-surface.ts +42 -0
- package/extensions/explore/ast/queries/ast-search.ts +306 -0
- package/extensions/explore/ast/queries/composite-target.ts +77 -0
- package/extensions/explore/ast/queries/context.ts +406 -0
- package/extensions/explore/ast/queries/discover.ts +427 -0
- package/extensions/explore/ast/queries/impact.ts +250 -0
- package/extensions/explore/ast/queries/outline.ts +203 -0
- package/extensions/explore/ast/queries/show.ts +262 -0
- package/extensions/explore/ast/query.ts +48 -0
- package/extensions/explore/ast/read/hook.ts +125 -0
- package/extensions/explore/ast/read/policy.ts +55 -0
- package/extensions/explore/ast/registry.ts +120 -0
- package/extensions/explore/ast/scan.ts +76 -0
- package/extensions/explore/ast/slice.ts +31 -0
- package/extensions/explore/ast/tools/ast-search.ts +138 -0
- package/extensions/explore/ast/tools/context.ts +126 -0
- package/extensions/explore/ast/tools/deps.ts +72 -0
- package/extensions/explore/ast/tools/discover.ts +208 -0
- package/extensions/explore/ast/tools/impact.ts +135 -0
- package/extensions/explore/ast/tools/outline.ts +200 -0
- package/extensions/explore/ast/tools/relationships.ts +160 -0
- package/extensions/explore/ast/tools/render.ts +121 -0
- package/extensions/explore/ast/tools/reverse-deps.ts +72 -0
- package/extensions/explore/ast/tools/show.ts +116 -0
- package/extensions/explore/guidance.ts +52 -0
- package/extensions/explore/index.ts +91 -45
- package/extensions/explore/read/autoread.ts +118 -0
- package/extensions/explore/settings.ts +64 -0
- package/extensions/explore/traverse.ts +260 -86
- package/extensions/handoff/index.ts +1 -1
- package/extensions/patch/executor.ts +95 -1
- package/extensions/patch/index.ts +1 -0
- package/extensions/publish/index.ts +18 -85
- package/extensions/publish/publish-ui.ts +0 -4
- package/extensions/soul/README.md +7 -4
- package/extensions/soul/index.ts +11 -5
- package/extensions/soul/prompt.ts +30 -14
- package/extensions/soul/settings.ts +7 -2
- package/extensions/subagent/agents/context-sync.md +6 -10
- package/extensions/subagent/agents/review.md +44 -24
- package/extensions/subagent/agents/scout.md +53 -34
- package/extensions/subagent/run.ts +1 -1
- package/extensions/tau-help/help.md +2 -2
- package/package.json +6 -5
- package/schemas/tau.schema.json +51 -6
- package/{extensions/explore → shared}/autoread.ts +21 -7
- package/shared/bounded-text-result.ts +304 -0
- package/shared/events.ts +1 -0
- package/shared/full-file-knowledge.ts +49 -0
- package/shared/temporary-output-store.ts +275 -0
- package/extensions/explore/ast-tools.ts +0 -828
- package/extensions/explore/ast-worker.ts +0 -356
- package/extensions/explore/find.ts +0 -147
- package/extensions/explore/full-file-knowledge.ts +0 -234
- package/extensions/explore/grep.ts +0 -749
- package/extensions/explore/limits.ts +0 -22
- package/extensions/explore/ls.ts +0 -113
- package/extensions/explore/path-display.ts +0 -41
- package/extensions/explore/path-tree.ts +0 -150
- package/extensions/explore/read-cache.ts +0 -273
- package/extensions/explore/read-snapshots.ts +0 -57
- package/extensions/explore/read-stats-panel.ts +0 -154
- package/extensions/explore/read-stats.ts +0 -120
- package/extensions/explore/read.ts +0 -335
- package/extensions/explore/result.ts +0 -29
- package/native-bin/darwin-arm64/tau-ast +0 -0
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
export type AstLanguage =
|
|
7
|
-
| "typeScript"
|
|
8
|
-
| "tsx"
|
|
9
|
-
| "odin"
|
|
10
|
-
| "go"
|
|
11
|
-
| "rust"
|
|
12
|
-
| "cSharp"
|
|
13
|
-
| "java"
|
|
14
|
-
| "kotlin"
|
|
15
|
-
| "swift"
|
|
16
|
-
| "markdown";
|
|
17
|
-
|
|
18
|
-
export type OutlineTarget = { kind: "file"; path: string; language: AstLanguage } | { kind: "directory"; path: string };
|
|
19
|
-
|
|
20
|
-
export interface SourcePosition {
|
|
21
|
-
line: number;
|
|
22
|
-
column: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface SourceRange {
|
|
26
|
-
startByte: number;
|
|
27
|
-
endByte: number;
|
|
28
|
-
start: SourcePosition;
|
|
29
|
-
end: SourcePosition;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface OutlineEntry {
|
|
33
|
-
role: "item" | "member";
|
|
34
|
-
symbolType: string;
|
|
35
|
-
name: string;
|
|
36
|
-
qualifiedName: string;
|
|
37
|
-
range: SourceRange;
|
|
38
|
-
nameRange: SourceRange;
|
|
39
|
-
receiverRange?: SourceRange;
|
|
40
|
-
bodyRange?: SourceRange;
|
|
41
|
-
signature: string;
|
|
42
|
-
astKind: string;
|
|
43
|
-
certainty: "certain" | "recovered" | "nearRecovery";
|
|
44
|
-
certaintyReason?: string;
|
|
45
|
-
locator?: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export type SymbolView = "signature" | "declaration" | "declarationWithImports";
|
|
49
|
-
|
|
50
|
-
export interface OutlineItem extends OutlineEntry {
|
|
51
|
-
rowKind: "package" | "import" | "declaration" | "export" | "sideEffect";
|
|
52
|
-
isImport: boolean;
|
|
53
|
-
isExported: boolean;
|
|
54
|
-
members: Array<OutlineEntry & { isPublic: boolean }>;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface OutlineFileResult {
|
|
58
|
-
path: string;
|
|
59
|
-
language: AstLanguage;
|
|
60
|
-
sourceFingerprint: string;
|
|
61
|
-
byteLength: number;
|
|
62
|
-
lineCount: number;
|
|
63
|
-
diagnostics: { errorNodes: number; missingNodes: number };
|
|
64
|
-
items: OutlineItem[];
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface OutlineTargetResult {
|
|
68
|
-
path: string;
|
|
69
|
-
files: OutlineFileResult[];
|
|
70
|
-
totalByteLength: number;
|
|
71
|
-
totalLineCount: number;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export interface SymbolDeclaration {
|
|
75
|
-
locator: string;
|
|
76
|
-
path: string;
|
|
77
|
-
language: AstLanguage;
|
|
78
|
-
sourceFingerprint: string;
|
|
79
|
-
declarationRange: SourceRange;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface SymbolBlock {
|
|
83
|
-
path: string;
|
|
84
|
-
returnedRange: SourceRange;
|
|
85
|
-
declarationIndexes: number[];
|
|
86
|
-
source: string;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export interface SymbolBatchResult {
|
|
90
|
-
declarations: SymbolDeclaration[];
|
|
91
|
-
blocks: SymbolBlock[];
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export interface AstClient {
|
|
95
|
-
getGeneration(): number;
|
|
96
|
-
outline(
|
|
97
|
-
target: OutlineTarget,
|
|
98
|
-
includePrivate: boolean,
|
|
99
|
-
includeDocs: boolean,
|
|
100
|
-
names: string[],
|
|
101
|
-
signal: AbortSignal | undefined,
|
|
102
|
-
): Promise<OutlineTargetResult>;
|
|
103
|
-
symbol(
|
|
104
|
-
locators: string[],
|
|
105
|
-
view: SymbolView,
|
|
106
|
-
contextLines: number,
|
|
107
|
-
signal: AbortSignal | undefined,
|
|
108
|
-
): Promise<SymbolBatchResult>;
|
|
109
|
-
shutdown(): Promise<void>;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
type WorkerRequestPayload =
|
|
113
|
-
| { operation: "handshake" }
|
|
114
|
-
| { operation: "outline"; target: OutlineTarget; includePrivate: boolean; includeDocs: boolean; names: string[] }
|
|
115
|
-
| { operation: "symbol"; locators: string[]; view: SymbolView; contextLines: number };
|
|
116
|
-
|
|
117
|
-
interface WorkerResponse {
|
|
118
|
-
requestId: number;
|
|
119
|
-
protocolVersion: number;
|
|
120
|
-
success: boolean;
|
|
121
|
-
result?: Record<string, unknown>;
|
|
122
|
-
error?: { code?: string; message?: string };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
interface PendingRequest {
|
|
126
|
-
resolve(value: Record<string, unknown>): void;
|
|
127
|
-
reject(error: Error): void;
|
|
128
|
-
removeAbortListener(): void;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const PROTOCOL_VERSION = 5;
|
|
132
|
-
const MAX_FRAME_BYTES = 8 * 1024 * 1024;
|
|
133
|
-
const STDERR_BYTES = 16 * 1024;
|
|
134
|
-
|
|
135
|
-
export type AstWorkerResolution = { command: string } | { error: Error };
|
|
136
|
-
|
|
137
|
-
export function resolveAstWorkerCommand(
|
|
138
|
-
packageRoot: string,
|
|
139
|
-
platform: NodeJS.Platform,
|
|
140
|
-
arch: string,
|
|
141
|
-
): AstWorkerResolution {
|
|
142
|
-
const packagedCommand = join(packageRoot, "native-bin", "darwin-arm64", "tau-ast");
|
|
143
|
-
if (platform === "darwin" && arch === "arm64" && existsSync(packagedCommand)) return { command: packagedCommand };
|
|
144
|
-
|
|
145
|
-
const sourceRoot = join(packageRoot, "native", "tau-ast");
|
|
146
|
-
if (existsSync(join(sourceRoot, "Cargo.toml")))
|
|
147
|
-
return { command: join(sourceRoot, "target", "release", `tau-ast${platform === "win32" ? ".exe" : ""}`) };
|
|
148
|
-
|
|
149
|
-
if (platform === "darwin" && arch === "arm64")
|
|
150
|
-
return {
|
|
151
|
-
error: new Error(
|
|
152
|
-
"tau-ast is missing from this @shanepadgett/tau-agent installation. Reinstall the package before using outline or symbol.",
|
|
153
|
-
),
|
|
154
|
-
};
|
|
155
|
-
return {
|
|
156
|
-
error: new Error(
|
|
157
|
-
`Packaged AST tools currently require an Apple Silicon Mac (darwin-arm64); this host is ${platform}-${arch}.`,
|
|
158
|
-
),
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export class AstWorkerClient implements AstClient {
|
|
163
|
-
private readonly command: string | undefined;
|
|
164
|
-
private readonly args: readonly string[];
|
|
165
|
-
private child: ChildProcessWithoutNullStreams | undefined;
|
|
166
|
-
private startPromise: Promise<void> | undefined;
|
|
167
|
-
private pending = new Map<number, PendingRequest>();
|
|
168
|
-
private nextRequestId = 1;
|
|
169
|
-
private incoming = Buffer.alloc(0);
|
|
170
|
-
private stderr = "";
|
|
171
|
-
private generation = 0;
|
|
172
|
-
|
|
173
|
-
constructor(command: string | undefined = undefined, args: readonly string[] = []) {
|
|
174
|
-
this.command = command;
|
|
175
|
-
this.args = args;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
getGeneration(): number {
|
|
179
|
-
return this.generation;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async outline(
|
|
183
|
-
target: OutlineTarget,
|
|
184
|
-
includePrivate: boolean,
|
|
185
|
-
includeDocs: boolean,
|
|
186
|
-
names: string[],
|
|
187
|
-
signal: AbortSignal | undefined,
|
|
188
|
-
): Promise<OutlineTargetResult> {
|
|
189
|
-
const result = await this.request({ operation: "outline", target, includePrivate, includeDocs, names }, signal);
|
|
190
|
-
if (result.kind !== "outline") throw new Error("tau-ast returned the wrong result for outline");
|
|
191
|
-
return result as unknown as OutlineTargetResult;
|
|
192
|
-
}
|
|
193
|
-
|
|
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);
|
|
201
|
-
if (result.kind !== "symbol") throw new Error("tau-ast returned the wrong result for symbol");
|
|
202
|
-
return result as unknown as SymbolBatchResult;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async shutdown(): Promise<void> {
|
|
206
|
-
const child = this.child;
|
|
207
|
-
if (!child) return;
|
|
208
|
-
this.child = undefined;
|
|
209
|
-
this.startPromise = undefined;
|
|
210
|
-
this.rejectPending(new Error("tau-ast worker shut down"));
|
|
211
|
-
child.stdin.end();
|
|
212
|
-
if (child.exitCode !== null) return;
|
|
213
|
-
await Promise.race([
|
|
214
|
-
new Promise<void>((resolve) => child.once("exit", () => resolve())),
|
|
215
|
-
new Promise<void>((resolve) => setTimeout(resolve, 250)),
|
|
216
|
-
]);
|
|
217
|
-
if (child.exitCode === null) child.kill();
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
private async request(
|
|
221
|
-
request: WorkerRequestPayload,
|
|
222
|
-
signal: AbortSignal | undefined,
|
|
223
|
-
): Promise<Record<string, unknown>> {
|
|
224
|
-
await this.ensureStarted();
|
|
225
|
-
return this.send(request, signal);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
private async ensureStarted(): Promise<void> {
|
|
229
|
-
if (this.startPromise) {
|
|
230
|
-
await this.startPromise;
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
if (this.child) return;
|
|
234
|
-
this.startPromise ??= this.start().finally(() => {
|
|
235
|
-
this.startPromise = undefined;
|
|
236
|
-
});
|
|
237
|
-
await this.startPromise;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
private async start(): Promise<void> {
|
|
241
|
-
const resolution = this.command
|
|
242
|
-
? { command: this.command }
|
|
243
|
-
: resolveAstWorkerCommand(fileURLToPath(new URL("../../", import.meta.url)), process.platform, process.arch);
|
|
244
|
-
if ("error" in resolution) throw resolution.error;
|
|
245
|
-
const child = spawn(resolution.command, this.args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
246
|
-
this.generation += 1;
|
|
247
|
-
this.child = child;
|
|
248
|
-
this.incoming = Buffer.alloc(0);
|
|
249
|
-
this.stderr = "";
|
|
250
|
-
child.stdout.on("data", (chunk: Buffer) => this.receive(child, chunk));
|
|
251
|
-
child.stderr.on("data", (chunk: Buffer) => {
|
|
252
|
-
this.stderr = Buffer.from(`${this.stderr}${chunk.toString("utf8")}`)
|
|
253
|
-
.subarray(-STDERR_BYTES)
|
|
254
|
-
.toString("utf8");
|
|
255
|
-
});
|
|
256
|
-
child.on("error", (error) => this.fail(child, new Error(`Failed to start tau-ast: ${error.message}`)));
|
|
257
|
-
child.on("exit", (code, signal) => {
|
|
258
|
-
if (this.child !== child) return;
|
|
259
|
-
const suffix = this.stderr.trim() ? `: ${this.stderr.trim()}` : "";
|
|
260
|
-
this.fail(child, new Error(`tau-ast exited (${signal ?? code ?? "unknown"})${suffix}`), false);
|
|
261
|
-
});
|
|
262
|
-
try {
|
|
263
|
-
const result = await this.send({ operation: "handshake" }, undefined);
|
|
264
|
-
if (result.kind !== "handshake") throw new Error("tau-ast handshake returned the wrong result");
|
|
265
|
-
} catch (error) {
|
|
266
|
-
const failure = error instanceof Error ? error : new Error(String(error));
|
|
267
|
-
this.fail(child, failure);
|
|
268
|
-
throw failure;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
private send(request: WorkerRequestPayload, signal: AbortSignal | undefined): Promise<Record<string, unknown>> {
|
|
273
|
-
const child = this.child;
|
|
274
|
-
if (!child) return Promise.reject(new Error("tau-ast worker is not running"));
|
|
275
|
-
if (signal?.aborted) return Promise.reject(new Error("tau-ast request cancelled"));
|
|
276
|
-
const requestId = this.nextRequestId++;
|
|
277
|
-
const payload = Buffer.from(JSON.stringify({ ...request, requestId, protocolVersion: PROTOCOL_VERSION }));
|
|
278
|
-
const frame = Buffer.allocUnsafe(payload.length + 4);
|
|
279
|
-
frame.writeUInt32BE(payload.length, 0);
|
|
280
|
-
payload.copy(frame, 4);
|
|
281
|
-
|
|
282
|
-
return new Promise<Record<string, unknown>>((resolve, reject) => {
|
|
283
|
-
const onAbort = (): void => this.fail(child, new Error("tau-ast request cancelled"));
|
|
284
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
285
|
-
this.pending.set(requestId, {
|
|
286
|
-
resolve,
|
|
287
|
-
reject,
|
|
288
|
-
removeAbortListener: () => signal?.removeEventListener("abort", onAbort),
|
|
289
|
-
});
|
|
290
|
-
child.stdin.write(frame, (error) => {
|
|
291
|
-
if (error) this.fail(child, new Error(`Failed to write tau-ast request: ${error.message}`));
|
|
292
|
-
});
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
private receive(child: ChildProcessWithoutNullStreams, chunk: Buffer): void {
|
|
297
|
-
if (this.child !== child) return;
|
|
298
|
-
this.incoming = Buffer.concat([this.incoming, chunk]);
|
|
299
|
-
while (this.incoming.length >= 4) {
|
|
300
|
-
const length = this.incoming.readUInt32BE(0);
|
|
301
|
-
if (length > MAX_FRAME_BYTES) {
|
|
302
|
-
this.fail(child, new Error(`tau-ast response frame exceeds ${MAX_FRAME_BYTES} bytes`));
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
if (this.incoming.length < length + 4) return;
|
|
306
|
-
const payload = this.incoming.subarray(4, length + 4);
|
|
307
|
-
this.incoming = this.incoming.subarray(length + 4);
|
|
308
|
-
let response: WorkerResponse;
|
|
309
|
-
try {
|
|
310
|
-
response = JSON.parse(payload.toString("utf8")) as WorkerResponse;
|
|
311
|
-
} catch (error) {
|
|
312
|
-
this.fail(child, new Error(`tau-ast returned malformed JSON: ${String(error)}`));
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
const pending = this.pending.get(response.requestId);
|
|
316
|
-
if (!pending) {
|
|
317
|
-
this.fail(child, new Error(`tau-ast returned unknown request id ${String(response.requestId)}`));
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
this.pending.delete(response.requestId);
|
|
321
|
-
pending.removeAbortListener();
|
|
322
|
-
if (response.protocolVersion !== PROTOCOL_VERSION) {
|
|
323
|
-
const error = new Error(`tau-ast response used protocol ${response.protocolVersion}`);
|
|
324
|
-
pending.reject(error);
|
|
325
|
-
this.fail(child, error);
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
if (!response.success) {
|
|
329
|
-
pending.reject(new Error(response.error?.message ?? response.error?.code ?? "tau-ast request failed"));
|
|
330
|
-
continue;
|
|
331
|
-
}
|
|
332
|
-
if (!response.result || typeof response.result !== "object") {
|
|
333
|
-
pending.reject(new Error("tau-ast response omitted its result"));
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
pending.resolve(response.result);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
private fail(child: ChildProcessWithoutNullStreams, error: Error, kill = true): void {
|
|
341
|
-
if (this.child !== child) return;
|
|
342
|
-
this.child = undefined;
|
|
343
|
-
this.generation += 1;
|
|
344
|
-
this.incoming = Buffer.alloc(0);
|
|
345
|
-
this.rejectPending(error);
|
|
346
|
-
if (kill && child.exitCode === null) child.kill();
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
private rejectPending(error: Error): void {
|
|
350
|
-
for (const request of this.pending.values()) {
|
|
351
|
-
request.removeAbortListener();
|
|
352
|
-
request.reject(error);
|
|
353
|
-
}
|
|
354
|
-
this.pending.clear();
|
|
355
|
-
}
|
|
356
|
-
}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { lstat } from "node:fs/promises";
|
|
2
|
-
import { basename, matchesGlob } from "node:path";
|
|
3
|
-
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
5
|
-
import { type Static, Type } from "typebox";
|
|
6
|
-
import { formatToolRowTitle, type ToolRowStateStore } from "../../shared/tool-row-state.js";
|
|
7
|
-
import { divideBudget, normalizeCountLimit, normalizeNonNegativeInteger } from "./limits.ts";
|
|
8
|
-
import {
|
|
9
|
-
formatPathForDisplay,
|
|
10
|
-
pathResolutionError,
|
|
11
|
-
relativeSlash,
|
|
12
|
-
resolveExplorePath,
|
|
13
|
-
stripLeadingAt,
|
|
14
|
-
toSlashPath,
|
|
15
|
-
} from "./path-display.ts";
|
|
16
|
-
import { type PathTreeEntry, renderPathTree } from "./path-tree.ts";
|
|
17
|
-
import { createExploreTextResult, type ExploreTextDetails, expandedExploreText } from "./result.ts";
|
|
18
|
-
import { collectPaths, type TraversalEntry } from "./traverse.ts";
|
|
19
|
-
|
|
20
|
-
const findQueryParams = Type.Object(
|
|
21
|
-
{
|
|
22
|
-
path: Type.Optional(Type.String()),
|
|
23
|
-
patterns: Type.Optional(Type.Array(Type.String())),
|
|
24
|
-
type: Type.Optional(Type.Union([Type.Literal("file"), Type.Literal("dir"), Type.Literal("any")])),
|
|
25
|
-
maxDepth: Type.Optional(Type.Number()),
|
|
26
|
-
hidden: Type.Optional(Type.Boolean()),
|
|
27
|
-
noIgnore: Type.Optional(Type.Boolean()),
|
|
28
|
-
},
|
|
29
|
-
{ additionalProperties: false },
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
const findParams = Type.Object(
|
|
33
|
-
{
|
|
34
|
-
queries: Type.Array(findQueryParams, { minItems: 1 }),
|
|
35
|
-
limit: Type.Optional(Type.Number()),
|
|
36
|
-
},
|
|
37
|
-
{ additionalProperties: false },
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
type FindParams = Static<typeof findParams>;
|
|
41
|
-
type FindQuery = Static<typeof findQueryParams>;
|
|
42
|
-
|
|
43
|
-
function globMatches(pattern: string, value: string): boolean {
|
|
44
|
-
const normalizedValue = toSlashPath(value);
|
|
45
|
-
const normalizedPattern = toSlashPath(pattern);
|
|
46
|
-
return (
|
|
47
|
-
matchesGlob(normalizedValue, normalizedPattern) ||
|
|
48
|
-
(normalizedValue.startsWith(".") && matchesGlob(normalizedValue.slice(1), normalizedPattern))
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function queryMatches(entry: TraversalEntry, queryRoot: string, query: FindQuery): boolean {
|
|
53
|
-
const type = query.type ?? "any";
|
|
54
|
-
if (type !== "any" && entry.type !== type) return false;
|
|
55
|
-
const patterns = query.patterns?.map(stripLeadingAt).filter((pattern) => pattern.length > 0) ?? [];
|
|
56
|
-
if (patterns.length === 0) return true;
|
|
57
|
-
const rel = relativeSlash(queryRoot, entry.absolutePath);
|
|
58
|
-
return patterns.some((pattern) => globMatches(pattern, pattern.includes("/") ? rel : basename(entry.displayPath)));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function toTreeEntry(entry: TraversalEntry): PathTreeEntry {
|
|
62
|
-
return { displayPath: entry.displayPath, type: entry.type };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function renderCallSummary(args: FindParams | undefined): string {
|
|
66
|
-
const queries = args?.queries ?? [];
|
|
67
|
-
const limit = normalizeCountLimit(args?.limit, 100);
|
|
68
|
-
if (queries.length !== 1) return `${queries.length} queries limit=${limit}`;
|
|
69
|
-
const query = queries[0];
|
|
70
|
-
const path = stripLeadingAt(query?.path ?? ".");
|
|
71
|
-
const patterns = query?.patterns?.map(stripLeadingAt).join(",") || "*";
|
|
72
|
-
const type = query?.type ?? "any";
|
|
73
|
-
const depth = query?.maxDepth === undefined ? "∞" : String(normalizeNonNegativeInteger(query.maxDepth, 0));
|
|
74
|
-
const flags = [query?.hidden ? "hidden" : "", query?.noIgnore ? "noIgnore" : ""].filter(Boolean).join(" ");
|
|
75
|
-
return `path=${path} patterns=${patterns} type=${type} depth=${depth} limit=${limit}${flags ? ` ${flags}` : ""}`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function createFindTool(rowState: ToolRowStateStore) {
|
|
79
|
-
return defineTool<typeof findParams, ExploreTextDetails | undefined>({
|
|
80
|
-
name: "find",
|
|
81
|
-
label: "find",
|
|
82
|
-
description: "Find files and directories by structured path queries.",
|
|
83
|
-
promptSnippet: "Find files and directories",
|
|
84
|
-
parameters: findParams,
|
|
85
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
86
|
-
if (params.queries.length === 0) throw new Error("find requires at least one query");
|
|
87
|
-
const limit = normalizeCountLimit(params.limit, 100);
|
|
88
|
-
const budgets = divideBudget(limit, params.queries.length);
|
|
89
|
-
const humanParts: string[] = [];
|
|
90
|
-
const agentParts: string[] = [];
|
|
91
|
-
|
|
92
|
-
for (let i = 0; i < params.queries.length; i += 1) {
|
|
93
|
-
const query = params.queries[i];
|
|
94
|
-
if (!query) continue;
|
|
95
|
-
const inputPath = query.path ?? ".";
|
|
96
|
-
const root = resolveExplorePath(ctx.cwd, inputPath);
|
|
97
|
-
const rootStats = await lstat(root).catch((error: unknown) => {
|
|
98
|
-
throw pathResolutionError(error, inputPath);
|
|
99
|
-
});
|
|
100
|
-
const entries = await collectPaths({
|
|
101
|
-
cwd: ctx.cwd,
|
|
102
|
-
root,
|
|
103
|
-
maxDepth: query.maxDepth === undefined ? undefined : normalizeNonNegativeInteger(query.maxDepth, 0),
|
|
104
|
-
includeRoot: rootStats.isFile(),
|
|
105
|
-
includeHidden: query.hidden === true,
|
|
106
|
-
includeIgnored: query.noIgnore === true,
|
|
107
|
-
includeNoise: query.noIgnore === true,
|
|
108
|
-
}).catch((error: unknown) => {
|
|
109
|
-
throw new Error(`Query ${i + 1} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
110
|
-
});
|
|
111
|
-
const matches = entries.filter((entry) => queryMatches(entry, root, query));
|
|
112
|
-
const budget = budgets[i] ?? limit;
|
|
113
|
-
const shown = matches.slice(0, budget);
|
|
114
|
-
const omitted = matches.length - shown.length;
|
|
115
|
-
const notice = omitted > 0 ? `… omitted ${omitted} matches (limit ${budget})` : undefined;
|
|
116
|
-
const rootPath = formatPathForDisplay(root, ctx.cwd);
|
|
117
|
-
const tree =
|
|
118
|
-
shown.length > 0
|
|
119
|
-
? renderPathTree(shown.map(toTreeEntry), { rootPath, omissionNotice: notice })
|
|
120
|
-
: undefined;
|
|
121
|
-
const humanText = tree?.humanText ?? "No matches";
|
|
122
|
-
const agentText = tree?.agentText ?? "No matches";
|
|
123
|
-
if (params.queries.length === 1) {
|
|
124
|
-
humanParts.push(humanText);
|
|
125
|
-
agentParts.push(agentText);
|
|
126
|
-
} else {
|
|
127
|
-
humanParts.push(`query ${i + 1}\n${humanText}`);
|
|
128
|
-
agentParts.push(`q${i + 1}\n${agentText}`);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return createExploreTextResult(agentParts.join("\n"), humanParts.join("\n"));
|
|
133
|
-
},
|
|
134
|
-
renderCall(args, theme, context) {
|
|
135
|
-
rowState.watch(context.toolCallId, context.invalidate);
|
|
136
|
-
const text = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
137
|
-
const title = formatToolRowTitle(rowState, context.toolCallId, "find", theme);
|
|
138
|
-
text.setText(`${title} ${theme.fg("muted", renderCallSummary(args))}`);
|
|
139
|
-
return text;
|
|
140
|
-
},
|
|
141
|
-
renderResult(result, _options, _theme, context) {
|
|
142
|
-
const text = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
143
|
-
text.setText(context.expanded ? expandedExploreText(result) : "");
|
|
144
|
-
return text;
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
}
|