@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.
Files changed (121) hide show
  1. package/docs/subagents.md +1 -1
  2. package/extensions/appshot/README.md +1 -1
  3. package/extensions/appshot/index.ts +5 -11
  4. package/extensions/context/sync.ts +1 -9
  5. package/extensions/context-pruning/README.md +2 -2
  6. package/extensions/context-pruning/index.ts +58 -57
  7. package/extensions/context-pruning/prune.ts +2 -2
  8. package/extensions/context-pruning/render.ts +51 -39
  9. package/extensions/context-pruning/settings.ts +4 -5
  10. package/extensions/explore/README.md +13 -14
  11. package/extensions/explore/ast/adapter.ts +97 -0
  12. package/extensions/explore/ast/engine.ts +470 -0
  13. package/extensions/explore/ast/format/ast-search.ts +97 -0
  14. package/extensions/explore/ast/format/composite.ts +26 -0
  15. package/extensions/explore/ast/format/context.ts +66 -0
  16. package/extensions/explore/ast/format/deps.ts +86 -0
  17. package/extensions/explore/ast/format/discover.ts +85 -0
  18. package/extensions/explore/ast/format/impact.ts +105 -0
  19. package/extensions/explore/ast/format/outline.ts +66 -0
  20. package/extensions/explore/ast/format/relationships.ts +81 -0
  21. package/extensions/explore/ast/format/show.ts +38 -0
  22. package/extensions/explore/ast/grammars/kotlin.wasm +0 -0
  23. package/extensions/explore/ast/grammars/manifest.json +41 -0
  24. package/extensions/explore/ast/grammars/manifest.ts +99 -0
  25. package/extensions/explore/ast/grammars/odin.wasm +0 -0
  26. package/extensions/explore/ast/grammars/swift.wasm +0 -0
  27. package/extensions/explore/ast/graph/file-graph.ts +360 -0
  28. package/extensions/explore/ast/graph/relationships.ts +597 -0
  29. package/extensions/explore/ast/identity.ts +119 -0
  30. package/extensions/explore/ast/ir.ts +130 -0
  31. package/extensions/explore/ast/languages/csharp-file-deps.ts +189 -0
  32. package/extensions/explore/ast/languages/csharp.ts +493 -0
  33. package/extensions/explore/ast/languages/file-dep-util.ts +120 -0
  34. package/extensions/explore/ast/languages/fixtures/sample.cs +17 -0
  35. package/extensions/explore/ast/languages/fixtures/sample.java +21 -0
  36. package/extensions/explore/ast/languages/fixtures/sample.kt +15 -0
  37. package/extensions/explore/ast/languages/fixtures/sample.odin +28 -0
  38. package/extensions/explore/ast/languages/fixtures/sample.rs +31 -0
  39. package/extensions/explore/ast/languages/fixtures/sample.swift +25 -0
  40. package/extensions/explore/ast/languages/go-file-deps.ts +139 -0
  41. package/extensions/explore/ast/languages/go.ts +545 -0
  42. package/extensions/explore/ast/languages/java.ts +412 -0
  43. package/extensions/explore/ast/languages/jvm-file-deps.ts +217 -0
  44. package/extensions/explore/ast/languages/kotlin.ts +611 -0
  45. package/extensions/explore/ast/languages/odin-file-deps.ts +175 -0
  46. package/extensions/explore/ast/languages/odin.ts +407 -0
  47. package/extensions/explore/ast/languages/rust-file-deps.ts +159 -0
  48. package/extensions/explore/ast/languages/rust.ts +506 -0
  49. package/extensions/explore/ast/languages/swift-file-deps.ts +59 -0
  50. package/extensions/explore/ast/languages/swift.ts +466 -0
  51. package/extensions/explore/ast/languages/tree.ts +220 -0
  52. package/extensions/explore/ast/languages/typescript-file-deps.ts +259 -0
  53. package/extensions/explore/ast/languages/typescript-package-surface.ts +440 -0
  54. package/extensions/explore/ast/languages/typescript.ts +716 -0
  55. package/extensions/explore/ast/markdown.ts +145 -0
  56. package/extensions/explore/ast/package-surface.ts +42 -0
  57. package/extensions/explore/ast/queries/ast-search.ts +306 -0
  58. package/extensions/explore/ast/queries/composite-target.ts +77 -0
  59. package/extensions/explore/ast/queries/context.ts +406 -0
  60. package/extensions/explore/ast/queries/discover.ts +427 -0
  61. package/extensions/explore/ast/queries/impact.ts +250 -0
  62. package/extensions/explore/ast/queries/outline.ts +203 -0
  63. package/extensions/explore/ast/queries/show.ts +262 -0
  64. package/extensions/explore/ast/query.ts +48 -0
  65. package/extensions/explore/ast/read/hook.ts +125 -0
  66. package/extensions/explore/ast/read/policy.ts +55 -0
  67. package/extensions/explore/ast/registry.ts +120 -0
  68. package/extensions/explore/ast/scan.ts +76 -0
  69. package/extensions/explore/ast/slice.ts +31 -0
  70. package/extensions/explore/ast/tools/ast-search.ts +138 -0
  71. package/extensions/explore/ast/tools/context.ts +126 -0
  72. package/extensions/explore/ast/tools/deps.ts +72 -0
  73. package/extensions/explore/ast/tools/discover.ts +208 -0
  74. package/extensions/explore/ast/tools/impact.ts +135 -0
  75. package/extensions/explore/ast/tools/outline.ts +200 -0
  76. package/extensions/explore/ast/tools/relationships.ts +160 -0
  77. package/extensions/explore/ast/tools/render.ts +121 -0
  78. package/extensions/explore/ast/tools/reverse-deps.ts +72 -0
  79. package/extensions/explore/ast/tools/show.ts +116 -0
  80. package/extensions/explore/guidance.ts +52 -0
  81. package/extensions/explore/index.ts +91 -45
  82. package/extensions/explore/read/autoread.ts +118 -0
  83. package/extensions/explore/settings.ts +64 -0
  84. package/extensions/explore/traverse.ts +260 -86
  85. package/extensions/handoff/index.ts +1 -1
  86. package/extensions/patch/executor.ts +95 -1
  87. package/extensions/patch/index.ts +1 -0
  88. package/extensions/publish/index.ts +18 -85
  89. package/extensions/publish/publish-ui.ts +0 -4
  90. package/extensions/soul/README.md +7 -4
  91. package/extensions/soul/index.ts +11 -5
  92. package/extensions/soul/prompt.ts +30 -14
  93. package/extensions/soul/settings.ts +7 -2
  94. package/extensions/subagent/agents/context-sync.md +6 -10
  95. package/extensions/subagent/agents/review.md +44 -24
  96. package/extensions/subagent/agents/scout.md +53 -34
  97. package/extensions/subagent/run.ts +1 -1
  98. package/extensions/tau-help/help.md +2 -2
  99. package/package.json +6 -5
  100. package/schemas/tau.schema.json +51 -6
  101. package/{extensions/explore → shared}/autoread.ts +21 -7
  102. package/shared/bounded-text-result.ts +304 -0
  103. package/shared/events.ts +1 -0
  104. package/shared/full-file-knowledge.ts +49 -0
  105. package/shared/temporary-output-store.ts +275 -0
  106. package/extensions/explore/ast-tools.ts +0 -828
  107. package/extensions/explore/ast-worker.ts +0 -356
  108. package/extensions/explore/find.ts +0 -147
  109. package/extensions/explore/full-file-knowledge.ts +0 -234
  110. package/extensions/explore/grep.ts +0 -749
  111. package/extensions/explore/limits.ts +0 -22
  112. package/extensions/explore/ls.ts +0 -113
  113. package/extensions/explore/path-display.ts +0 -41
  114. package/extensions/explore/path-tree.ts +0 -150
  115. package/extensions/explore/read-cache.ts +0 -273
  116. package/extensions/explore/read-snapshots.ts +0 -57
  117. package/extensions/explore/read-stats-panel.ts +0 -154
  118. package/extensions/explore/read-stats.ts +0 -120
  119. package/extensions/explore/read.ts +0 -335
  120. package/extensions/explore/result.ts +0 -29
  121. package/native-bin/darwin-arm64/tau-ast +0 -0
@@ -0,0 +1,49 @@
1
+ type ReadCacheMode = "baseline" | "recovery" | "unchanged" | "diff";
2
+ type ReadCachePresentation = "plain" | "line-numbered";
3
+
4
+ export interface ReadCacheMetaV1 {
5
+ v: 1;
6
+ pathKey: string;
7
+ scopeKey: string;
8
+ presentation: ReadCachePresentation;
9
+ servedHash: string;
10
+ baseHash?: string;
11
+ mode: ReadCacheMode;
12
+ baselineTokens: number;
13
+ returnedTokens: number;
14
+ totalLines: number;
15
+ summary: string;
16
+ }
17
+
18
+ const COMPLETE_FILE_SCOPE = "full";
19
+ export const MAX_COMPLETE_FILE_SNAPSHOT_BYTES = 1024 * 1024;
20
+
21
+ export function createCompleteFileMeta(options: {
22
+ pathKey: string;
23
+ presentation: ReadCachePresentation;
24
+ servedHash: string;
25
+ mode: ReadCacheMode;
26
+ sourceText: string;
27
+ returnedText: string;
28
+ totalLines: number;
29
+ summary: string;
30
+ baseHash?: string;
31
+ }): ReadCacheMetaV1 {
32
+ return {
33
+ v: 1,
34
+ pathKey: options.pathKey,
35
+ scopeKey: COMPLETE_FILE_SCOPE,
36
+ presentation: options.presentation,
37
+ servedHash: options.servedHash,
38
+ baseHash: options.baseHash,
39
+ mode: options.mode,
40
+ baselineTokens: estimateTokens(options.sourceText),
41
+ returnedTokens: estimateTokens(options.returnedText),
42
+ totalLines: options.totalLines,
43
+ summary: options.summary,
44
+ };
45
+ }
46
+
47
+ function estimateTokens(text: string): number {
48
+ return Math.ceil(text.length / 4);
49
+ }
@@ -0,0 +1,275 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { constants } from "node:fs";
3
+ import { mkdir, open, opendir, readFile, rename, rm, stat } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+
7
+ const DIRECTORY_PREFIX = "tau-tool-output-v1-";
8
+ const OWNER_FILE = ".tau-owner.json";
9
+
10
+ const TEMPORARY_OUTPUT_FILE_QUOTA_BYTES = 64 * 1024 * 1024;
11
+ const TEMPORARY_OUTPUT_SESSION_QUOTA_BYTES = 256 * 1024 * 1024;
12
+ const TEMPORARY_OUTPUT_ORPHAN_LIFETIME_MS = 24 * 60 * 60 * 1000;
13
+
14
+ export function createTemporaryOutputStore(): TemporaryOutputStore {
15
+ return new TemporaryOutputStore(
16
+ tmpdir(),
17
+ TEMPORARY_OUTPUT_FILE_QUOTA_BYTES,
18
+ TEMPORARY_OUTPUT_SESSION_QUOTA_BYTES,
19
+ TEMPORARY_OUTPUT_ORPHAN_LIFETIME_MS,
20
+ );
21
+ }
22
+
23
+ interface OwnerMarker {
24
+ kind: "tau-tool-output";
25
+ version: 1;
26
+ pid: number;
27
+ createdAt: number;
28
+ }
29
+
30
+ export class TemporaryOutputError extends Error {
31
+ readonly code: "quota" | "write";
32
+
33
+ constructor(code: "quota" | "write", message: string) {
34
+ super(message);
35
+ this.name = "TemporaryOutputError";
36
+ this.code = code;
37
+ }
38
+ }
39
+
40
+ export class TemporaryOutputStore {
41
+ private readonly baseDirectory: string;
42
+ private readonly fileQuotaBytes: number;
43
+ private readonly sessionQuotaBytes: number;
44
+ private readonly orphanLifetimeMs: number;
45
+ private started = false;
46
+ private startPromise: Promise<void> | undefined;
47
+ private directory: string | undefined;
48
+ private directoryPromise: Promise<string> | undefined;
49
+ private sessionBytes = 0;
50
+ private readonly active = new Set<TemporaryOutputWriter>();
51
+ private readonly creating = new Set<Promise<TemporaryOutputWriter>>();
52
+ private shuttingDown = false;
53
+ private shutdownPromise: Promise<void> | undefined;
54
+
55
+ constructor(baseDirectory: string, fileQuotaBytes: number, sessionQuotaBytes: number, orphanLifetimeMs: number) {
56
+ this.baseDirectory = baseDirectory;
57
+ this.fileQuotaBytes = fileQuotaBytes;
58
+ this.sessionQuotaBytes = sessionQuotaBytes;
59
+ this.orphanLifetimeMs = orphanLifetimeMs;
60
+ }
61
+
62
+ async start(): Promise<void> {
63
+ if (this.started) return;
64
+ this.startPromise ??= this.removeStaleOrphans().then(() => {
65
+ this.started = true;
66
+ });
67
+ try {
68
+ await this.startPromise;
69
+ } finally {
70
+ this.startPromise = undefined;
71
+ }
72
+ }
73
+
74
+ async createOutput(): Promise<TemporaryOutputWriter> {
75
+ if (this.shuttingDown) throw new TemporaryOutputError("write", "Temporary output session is shutting down");
76
+ const creation = this.createOutputFile();
77
+ this.creating.add(creation);
78
+ try {
79
+ return await creation;
80
+ } finally {
81
+ this.creating.delete(creation);
82
+ }
83
+ }
84
+
85
+ private async createOutputFile(): Promise<TemporaryOutputWriter> {
86
+ await this.start();
87
+ if (this.shuttingDown) throw new TemporaryOutputError("write", "Temporary output session is shutting down");
88
+ this.directoryPromise ??= (async () => {
89
+ const directory = join(this.baseDirectory, `${DIRECTORY_PREFIX}${randomUUID()}`);
90
+ try {
91
+ await mkdir(directory, { mode: 0o700 });
92
+ const marker = {
93
+ kind: "tau-tool-output",
94
+ version: 1,
95
+ pid: process.pid,
96
+ createdAt: Date.now(),
97
+ } satisfies OwnerMarker;
98
+ const markerFile = await open(
99
+ join(directory, OWNER_FILE),
100
+ constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY,
101
+ 0o600,
102
+ );
103
+ try {
104
+ await markerFile.writeFile(JSON.stringify(marker));
105
+ } finally {
106
+ await markerFile.close();
107
+ }
108
+ return directory;
109
+ } catch (error) {
110
+ await rm(directory, { recursive: true, force: true });
111
+ throw error;
112
+ }
113
+ })();
114
+ const directory = await this.directoryPromise;
115
+ this.directory = directory;
116
+
117
+ const partialPath = join(directory, `${randomUUID()}.partial`);
118
+ let file;
119
+ try {
120
+ file = await open(partialPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
121
+ } catch (error) {
122
+ throw new TemporaryOutputError("write", `Cannot create temporary output: ${String(error)}`);
123
+ }
124
+ const writer = new TemporaryOutputWriter(this, file, partialPath, partialPath.replace(/\.partial$/, ".txt"));
125
+ this.active.add(writer);
126
+ return writer;
127
+ }
128
+
129
+ async shutdown(): Promise<void> {
130
+ if (this.shutdownPromise) return this.shutdownPromise;
131
+ this.shutdownPromise = this.performShutdown();
132
+ try {
133
+ await this.shutdownPromise;
134
+ } finally {
135
+ this.shutdownPromise = undefined;
136
+ }
137
+ }
138
+
139
+ private async performShutdown(): Promise<void> {
140
+ this.shuttingDown = true;
141
+ await Promise.allSettled(this.creating);
142
+ for (const writer of this.active) await writer.abort();
143
+ const directory = this.directory;
144
+ this.directory = undefined;
145
+ this.sessionBytes = 0;
146
+ this.started = false;
147
+ this.startPromise = undefined;
148
+ this.directoryPromise = undefined;
149
+ if (directory) await rm(directory, { recursive: true, force: true });
150
+ this.shuttingDown = false;
151
+ }
152
+
153
+ reserve(writer: TemporaryOutputWriter, bytes: number): void {
154
+ if (writer.byteLength + bytes > this.fileQuotaBytes) {
155
+ throw new TemporaryOutputError(
156
+ "quota",
157
+ `Temporary output exceeds the ${this.fileQuotaBytes}-byte per-file quota`,
158
+ );
159
+ }
160
+ if (this.sessionBytes + bytes > this.sessionQuotaBytes) {
161
+ throw new TemporaryOutputError(
162
+ "quota",
163
+ `Temporary output exceeds the ${this.sessionQuotaBytes}-byte per-session quota`,
164
+ );
165
+ }
166
+ writer.byteLength += bytes;
167
+ this.sessionBytes += bytes;
168
+ }
169
+
170
+ release(writer: TemporaryOutputWriter): void {
171
+ this.sessionBytes = Math.max(0, this.sessionBytes - writer.byteLength);
172
+ writer.byteLength = 0;
173
+ this.active.delete(writer);
174
+ }
175
+
176
+ complete(writer: TemporaryOutputWriter): void {
177
+ this.active.delete(writer);
178
+ }
179
+
180
+ private async removeStaleOrphans(): Promise<void> {
181
+ let directory;
182
+ try {
183
+ directory = await opendir(this.baseDirectory);
184
+ } catch {
185
+ return;
186
+ }
187
+ for await (const entry of directory) {
188
+ if (!entry.isDirectory() || !entry.name.startsWith(DIRECTORY_PREFIX)) continue;
189
+ const path = join(this.baseDirectory, entry.name);
190
+ try {
191
+ const metadata = await stat(path);
192
+ if (Date.now() - metadata.mtimeMs < this.orphanLifetimeMs) continue;
193
+ const marker = JSON.parse(await readFile(join(path, OWNER_FILE), "utf8")) as Partial<OwnerMarker>;
194
+ if (marker.kind !== "tau-tool-output" || marker.version !== 1 || typeof marker.pid !== "number") continue;
195
+ try {
196
+ process.kill(marker.pid, 0);
197
+ continue;
198
+ } catch (error) {
199
+ if (!isErrorCode(error, "ESRCH")) continue;
200
+ }
201
+ await rm(path, { recursive: true, force: true });
202
+ } catch {
203
+ // An untrusted or concurrently changing directory is not ours to remove.
204
+ }
205
+ }
206
+ }
207
+ }
208
+
209
+ export class TemporaryOutputWriter {
210
+ private readonly store: TemporaryOutputStore;
211
+ private readonly file: Awaited<ReturnType<typeof open>>;
212
+ private readonly partialPath: string;
213
+ private readonly completePath: string;
214
+ byteLength = 0;
215
+ private closed = false;
216
+
217
+ constructor(
218
+ store: TemporaryOutputStore,
219
+ file: Awaited<ReturnType<typeof open>>,
220
+ partialPath: string,
221
+ completePath: string,
222
+ ) {
223
+ this.store = store;
224
+ this.file = file;
225
+ this.partialPath = partialPath;
226
+ this.completePath = completePath;
227
+ }
228
+
229
+ async write(text: string): Promise<void> {
230
+ if (this.closed) throw new TemporaryOutputError("write", "Temporary output is already closed");
231
+ const buffer = Buffer.from(text);
232
+ try {
233
+ this.store.reserve(this, buffer.byteLength);
234
+ let offset = 0;
235
+ while (offset < buffer.byteLength) {
236
+ const result = await this.file.write(buffer, offset, buffer.byteLength - offset);
237
+ if (result.bytesWritten === 0) throw new Error("write returned zero bytes");
238
+ offset += result.bytesWritten;
239
+ }
240
+ } catch (error) {
241
+ await this.abort();
242
+ if (error instanceof TemporaryOutputError) throw error;
243
+ throw new TemporaryOutputError("write", `Cannot write temporary output: ${String(error)}`);
244
+ }
245
+ }
246
+
247
+ async finish(): Promise<string> {
248
+ if (this.closed) throw new TemporaryOutputError("write", "Temporary output is already closed");
249
+ this.closed = true;
250
+ try {
251
+ await this.file.close();
252
+ await rename(this.partialPath, this.completePath);
253
+ this.store.complete(this);
254
+ return this.completePath;
255
+ } catch (error) {
256
+ this.closed = false;
257
+ await this.abort();
258
+ throw new TemporaryOutputError("write", `Cannot complete temporary output: ${String(error)}`);
259
+ }
260
+ }
261
+
262
+ async abort(): Promise<void> {
263
+ if (!this.closed) {
264
+ this.closed = true;
265
+ await this.file.close().catch(() => {});
266
+ }
267
+ await rm(this.partialPath, { force: true }).catch(() => {});
268
+ await rm(this.completePath, { force: true }).catch(() => {});
269
+ this.store.release(this);
270
+ }
271
+ }
272
+
273
+ function isErrorCode(error: unknown, code: string): boolean {
274
+ return error instanceof Error && "code" in error && error.code === code;
275
+ }