@shanepadgett/tau-agent 0.5.1 → 0.6.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 -2
- package/extensions/context/README.md +1 -1
- package/extensions/context/definitions.ts +2 -142
- package/extensions/context/index.ts +65 -388
- package/extensions/context/panel.ts +43 -108
- package/extensions/context/sync.ts +683 -0
- package/extensions/explore/README.md +2 -2
- package/extensions/explore/autoread.ts +20 -3
- package/extensions/explore/index.ts +23 -2
- package/extensions/explore/read-cache.ts +209 -0
- package/extensions/explore/read-snapshots.ts +44 -0
- package/extensions/explore/read-stats-panel.ts +154 -0
- package/extensions/explore/read-stats.ts +120 -0
- package/extensions/explore/read.ts +198 -42
- package/extensions/subagent/README.md +0 -1
- package/extensions/subagent/agents.ts +0 -4
- package/extensions/tau-help/help.md +3 -3
- package/package.json +2 -2
- package/shared/git.ts +2 -2
- package/extensions/subagent/agents/context-maintenance.md +0 -23
package/docs/subagents.md
CHANGED
|
@@ -9,7 +9,7 @@ Tau's `subagent` tool delegates one focused task to an isolated child Pi session
|
|
|
9
9
|
| **User (global)** | `~/.pi/agent/tau/agents/*.md` | You want the agent in every project |
|
|
10
10
|
| **Project** | nearest trusted `.pi/tau/agents/*.md` | Repo-specific helpers |
|
|
11
11
|
|
|
12
|
-
Precedence: **project overrides user**, which overrides Tau's built-ins (`scout`, `
|
|
12
|
+
Precedence: **project overrides user**, which overrides Tau's built-ins (`scout`, `web-research`). Duplicate names in one scope are invalid.
|
|
13
13
|
|
|
14
14
|
## Definition format
|
|
15
15
|
|
|
@@ -53,7 +53,6 @@ Named tools and configured models must exist in the normally loaded child Pi env
|
|
|
53
53
|
## Built-ins
|
|
54
54
|
|
|
55
55
|
- `scout` — local exploration with `read`, `grep`, `find`, `ls`
|
|
56
|
-
- `context-maintenance` — repository context reconciliation with interactive user approval
|
|
57
56
|
- `web-research` — `websearch`, `codesearch`, `webfetch`
|
|
58
57
|
|
|
59
58
|
Ask Tau to delegate, or let it call `subagent` with an agent name and task.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Context stores reusable repository work scopes in `.pi/contexts`. Folder names become selector tabs, TOML files become concepts, and TOML sections become selectable entries.
|
|
4
4
|
|
|
5
|
-
Use `/context` to select entries and inject their files through Tau autoread. Use `/context-
|
|
5
|
+
Use `/context` to select entries and inject their files through Tau autoread. Use `/context-sync` to reconcile affected scopes from the current Git changes. Sync is automatic, constrained to the changed scope, and may report `no-change` when existing mappings already fit.
|
|
6
6
|
|
|
7
7
|
```toml
|
|
8
8
|
name = "Player"
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { access,
|
|
1
|
+
import { access, readFile, readdir, stat } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
4
|
-
import { parse
|
|
5
|
-
import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { parse } from "smol-toml";
|
|
6
5
|
|
|
7
6
|
export interface ContextEntry {
|
|
8
7
|
id: string;
|
|
@@ -16,38 +15,6 @@ export interface ContextEntry {
|
|
|
16
15
|
path: string;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
export interface ContextProposal {
|
|
20
|
-
tab: string;
|
|
21
|
-
concept: string;
|
|
22
|
-
conceptName: string;
|
|
23
|
-
conceptDescription: string;
|
|
24
|
-
entry: string;
|
|
25
|
-
description: string;
|
|
26
|
-
files: string[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export type ContextOperation =
|
|
30
|
-
| (ContextProposal & { id: string; kind: "create"; reason: string })
|
|
31
|
-
| {
|
|
32
|
-
id: string;
|
|
33
|
-
kind: "add-files" | "remove-files";
|
|
34
|
-
tab: string;
|
|
35
|
-
concept: string;
|
|
36
|
-
entry: string;
|
|
37
|
-
files: string[];
|
|
38
|
-
reason: string;
|
|
39
|
-
}
|
|
40
|
-
| {
|
|
41
|
-
id: string;
|
|
42
|
-
kind: "replace-file";
|
|
43
|
-
tab: string;
|
|
44
|
-
concept: string;
|
|
45
|
-
entry: string;
|
|
46
|
-
from: string;
|
|
47
|
-
to: string;
|
|
48
|
-
reason: string;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
18
|
export async function pathExists(path: string): Promise<boolean> {
|
|
52
19
|
try {
|
|
53
20
|
await access(path);
|
|
@@ -149,110 +116,3 @@ export async function loadContextEntries(root: string): Promise<ContextEntry[]>
|
|
|
149
116
|
}
|
|
150
117
|
return result;
|
|
151
118
|
}
|
|
152
|
-
|
|
153
|
-
export async function writeContextEntry(root: string, proposal: ContextProposal, replace: boolean): Promise<void> {
|
|
154
|
-
const tab = validSlug(proposal.tab, "Context tab");
|
|
155
|
-
const concept = validSlug(proposal.concept, "Context concept");
|
|
156
|
-
const entry = validSlug(proposal.entry, "Context entry");
|
|
157
|
-
const conceptName = proposal.conceptName.trim();
|
|
158
|
-
const description = proposal.description.trim();
|
|
159
|
-
if (!conceptName) throw new Error("Context concept name is required");
|
|
160
|
-
if (!description) throw new Error("Context entry description is required");
|
|
161
|
-
const files = await requireFiles(root, proposal.files);
|
|
162
|
-
const path = join(root, ".pi", "contexts", tab, `${concept}.toml`);
|
|
163
|
-
await withFileMutationQueue(path, async () => {
|
|
164
|
-
let raw: Record<string, unknown> = {};
|
|
165
|
-
if (await pathExists(path)) raw = parse(await readFile(path, "utf8")) as Record<string, unknown>;
|
|
166
|
-
if (!replace && raw[entry] !== undefined)
|
|
167
|
-
throw new Error(`Context entry already exists: ${tab}/${concept}/${entry}`);
|
|
168
|
-
if (raw.name === undefined) raw.name = conceptName;
|
|
169
|
-
if (raw.description === undefined) raw.description = proposal.conceptDescription.trim();
|
|
170
|
-
raw[entry] = { description, files };
|
|
171
|
-
await mkdir(dirname(path), { recursive: true });
|
|
172
|
-
const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
173
|
-
await writeFile(temporary, stringify(raw), "utf8");
|
|
174
|
-
await rename(temporary, path);
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export async function updateContextFiles(
|
|
179
|
-
root: string,
|
|
180
|
-
tabInput: string,
|
|
181
|
-
conceptInput: string,
|
|
182
|
-
entryInput: string,
|
|
183
|
-
paths: readonly string[],
|
|
184
|
-
action: "add" | "remove",
|
|
185
|
-
): Promise<string[]> {
|
|
186
|
-
const tab = validSlug(tabInput, "Context tab");
|
|
187
|
-
const concept = validSlug(conceptInput, "Context concept");
|
|
188
|
-
const entry = validSlug(entryInput, "Context entry");
|
|
189
|
-
const current = (await loadContextEntries(root)).find((item) => item.id === `${tab}/${concept}/${entry}`);
|
|
190
|
-
if (!current) throw new Error(`Unknown context entry: ${tab}/${concept}/${entry}`);
|
|
191
|
-
const changed =
|
|
192
|
-
action === "add" ? await requireFiles(root, paths) : paths.map((path) => normalizeProjectPath(root, path));
|
|
193
|
-
const changedSet = new Set(changed);
|
|
194
|
-
const files =
|
|
195
|
-
action === "add" ? [...current.files, ...changed] : current.files.filter((file) => !changedSet.has(file));
|
|
196
|
-
if (files.length === 0) throw new Error("Context entries must contain at least one file");
|
|
197
|
-
await writeContextEntry(
|
|
198
|
-
root,
|
|
199
|
-
{
|
|
200
|
-
tab,
|
|
201
|
-
concept,
|
|
202
|
-
conceptName: current.conceptName,
|
|
203
|
-
conceptDescription: current.conceptDescription,
|
|
204
|
-
entry,
|
|
205
|
-
description: current.description,
|
|
206
|
-
files,
|
|
207
|
-
},
|
|
208
|
-
true,
|
|
209
|
-
);
|
|
210
|
-
return sortedUnique(changed);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export async function replaceContextFile(
|
|
214
|
-
root: string,
|
|
215
|
-
tab: string,
|
|
216
|
-
concept: string,
|
|
217
|
-
entry: string,
|
|
218
|
-
fromInput: string,
|
|
219
|
-
toInput: string,
|
|
220
|
-
): Promise<void> {
|
|
221
|
-
const from = normalizeProjectPath(root, fromInput);
|
|
222
|
-
const [to] = await requireFiles(root, [toInput]);
|
|
223
|
-
const current = (await loadContextEntries(root)).find((item) => item.id === `${tab}/${concept}/${entry}`);
|
|
224
|
-
if (!current) throw new Error(`Unknown context entry: ${tab}/${concept}/${entry}`);
|
|
225
|
-
if (!current.files.includes(from)) throw new Error(`Context entry does not contain: ${from}`);
|
|
226
|
-
await writeContextEntry(
|
|
227
|
-
root,
|
|
228
|
-
{
|
|
229
|
-
tab: current.tab,
|
|
230
|
-
concept: current.concept,
|
|
231
|
-
conceptName: current.conceptName,
|
|
232
|
-
conceptDescription: current.conceptDescription,
|
|
233
|
-
entry: current.name,
|
|
234
|
-
description: current.description,
|
|
235
|
-
files: current.files.map((file) => (file === from ? to : file)),
|
|
236
|
-
},
|
|
237
|
-
true,
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export async function applyContextOperation(root: string, operation: ContextOperation): Promise<void> {
|
|
242
|
-
if (operation.kind === "create") {
|
|
243
|
-
await writeContextEntry(root, operation, false);
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
if (operation.kind === "replace-file") {
|
|
247
|
-
await replaceContextFile(root, operation.tab, operation.concept, operation.entry, operation.from, operation.to);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
await updateContextFiles(
|
|
251
|
-
root,
|
|
252
|
-
operation.tab,
|
|
253
|
-
operation.concept,
|
|
254
|
-
operation.entry,
|
|
255
|
-
operation.files,
|
|
256
|
-
operation.kind === "add-files" ? "add" : "remove",
|
|
257
|
-
);
|
|
258
|
-
}
|