@shanepadgett/tau-agent 0.1.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 (138) hide show
  1. package/README.md +30 -0
  2. package/docs/extending-tau-agent.md +119 -0
  3. package/docs/subagents.md +57 -0
  4. package/docs/tui.md +107 -0
  5. package/extensions/appshot/README.md +11 -0
  6. package/extensions/appshot/capture.swift +191 -0
  7. package/extensions/appshot/index.ts +245 -0
  8. package/extensions/appshot/native-helper.ts +102 -0
  9. package/extensions/attention/README.md +23 -0
  10. package/extensions/attention/index.ts +71 -0
  11. package/extensions/auto-name/README.md +26 -0
  12. package/extensions/auto-name/index.ts +163 -0
  13. package/extensions/branch/README.md +22 -0
  14. package/extensions/branch/index.ts +145 -0
  15. package/extensions/branch/panel.ts +148 -0
  16. package/extensions/clear-screen/README.md +29 -0
  17. package/extensions/clear-screen/index.ts +75 -0
  18. package/extensions/commit/README.md +15 -0
  19. package/extensions/commit/commit-plan.ts +328 -0
  20. package/extensions/commit/git-change-set.ts +268 -0
  21. package/extensions/commit/index.ts +138 -0
  22. package/extensions/commit/review-ui.ts +745 -0
  23. package/extensions/explore/README.md +7 -0
  24. package/extensions/explore/autoread.ts +140 -0
  25. package/extensions/explore/find.ts +147 -0
  26. package/extensions/explore/grep.ts +749 -0
  27. package/extensions/explore/index.ts +17 -0
  28. package/extensions/explore/limits.ts +22 -0
  29. package/extensions/explore/ls.ts +113 -0
  30. package/extensions/explore/path-display.ts +41 -0
  31. package/extensions/explore/path-tree.ts +150 -0
  32. package/extensions/explore/read.ts +160 -0
  33. package/extensions/explore/result.ts +29 -0
  34. package/extensions/explore/traverse.ts +226 -0
  35. package/extensions/footer/README.md +23 -0
  36. package/extensions/footer/index.ts +534 -0
  37. package/extensions/footer/settings.ts +13 -0
  38. package/extensions/ideas/README.md +10 -0
  39. package/extensions/ideas/browser.ts +66 -0
  40. package/extensions/ideas/index.ts +31 -0
  41. package/extensions/ideas/store.ts +32 -0
  42. package/extensions/image-gen/README.md +11 -0
  43. package/extensions/image-gen/client.ts +186 -0
  44. package/extensions/image-gen/index.ts +152 -0
  45. package/extensions/manage-sessions/README.md +12 -0
  46. package/extensions/manage-sessions/index.ts +58 -0
  47. package/extensions/manage-sessions/manager-ui.ts +323 -0
  48. package/extensions/manage-sessions/sessions.ts +126 -0
  49. package/extensions/patch/README.md +48 -0
  50. package/extensions/patch/executor.ts +355 -0
  51. package/extensions/patch/index.ts +170 -0
  52. package/extensions/patch/matcher.ts +236 -0
  53. package/extensions/patch/parser.ts +347 -0
  54. package/extensions/patch/render.ts +247 -0
  55. package/extensions/patch/summary.ts +35 -0
  56. package/extensions/publish/README.md +11 -0
  57. package/extensions/publish/index.ts +225 -0
  58. package/extensions/qna/README.md +45 -0
  59. package/extensions/qna/additional-context-body.ts +51 -0
  60. package/extensions/qna/body-render.ts +6 -0
  61. package/extensions/qna/choice-question-body.ts +322 -0
  62. package/extensions/qna/index.ts +245 -0
  63. package/extensions/qna/inline-editor-row.ts +56 -0
  64. package/extensions/qna/input-question-body.ts +89 -0
  65. package/extensions/qna/model.ts +317 -0
  66. package/extensions/qna/panel.ts +240 -0
  67. package/extensions/qna/ui.ts +16 -0
  68. package/extensions/reference/README.md +47 -0
  69. package/extensions/reference/index.ts +76 -0
  70. package/extensions/reference/panel.ts +874 -0
  71. package/extensions/reference/settings.ts +29 -0
  72. package/extensions/run-summary/README.md +5 -0
  73. package/extensions/run-summary/index.ts +98 -0
  74. package/extensions/silent-command-runner/README.md +25 -0
  75. package/extensions/silent-command-runner/index.ts +468 -0
  76. package/extensions/silent-command-runner/settings.ts +65 -0
  77. package/extensions/soul/README.md +19 -0
  78. package/extensions/soul/index.ts +20 -0
  79. package/extensions/soul/prompt.ts +254 -0
  80. package/extensions/soul/settings.ts +13 -0
  81. package/extensions/stash/README.md +48 -0
  82. package/extensions/stash/browser.ts +51 -0
  83. package/extensions/stash/index.ts +55 -0
  84. package/extensions/stash/store.ts +36 -0
  85. package/extensions/subagent/README.md +34 -0
  86. package/extensions/subagent/agents/scout.md +85 -0
  87. package/extensions/subagent/agents/web-research.md +95 -0
  88. package/extensions/subagent/agents.ts +196 -0
  89. package/extensions/subagent/index.ts +214 -0
  90. package/extensions/subagent/render.ts +89 -0
  91. package/extensions/subagent/run.ts +325 -0
  92. package/extensions/tau/README.md +17 -0
  93. package/extensions/tau/index.ts +122 -0
  94. package/extensions/tau-help/README.md +3 -0
  95. package/extensions/tau-help/help.md +125 -0
  96. package/extensions/tau-help/index.ts +68 -0
  97. package/extensions/turn-budget/README.md +14 -0
  98. package/extensions/turn-budget/index.ts +150 -0
  99. package/extensions/turn-budget/settings.ts +35 -0
  100. package/extensions/web/README.md +13 -0
  101. package/extensions/web/codesearch.ts +79 -0
  102. package/extensions/web/exa.ts +101 -0
  103. package/extensions/web/html.ts +67 -0
  104. package/extensions/web/index.ts +13 -0
  105. package/extensions/web/limits.ts +11 -0
  106. package/extensions/web/tool-output.ts +54 -0
  107. package/extensions/web/webfetch.ts +177 -0
  108. package/extensions/web/websearch.ts +100 -0
  109. package/package.json +57 -0
  110. package/prompts/.gitkeep +1 -0
  111. package/prompts/cavemanify.md +25 -0
  112. package/prompts/implement.md +25 -0
  113. package/prompts/interview.md +48 -0
  114. package/prompts/plan-feature.md +30 -0
  115. package/prompts/plan-implementation.md +50 -0
  116. package/schemas/tau.schema.json +205 -0
  117. package/shared/agent-blocked.ts +12 -0
  118. package/shared/description.ts +28 -0
  119. package/shared/events.ts +135 -0
  120. package/shared/git.ts +41 -0
  121. package/shared/injected-context.ts +69 -0
  122. package/shared/jsonl-store.ts +108 -0
  123. package/shared/model-fallback/index.ts +278 -0
  124. package/shared/model-fallback/settings.ts +18 -0
  125. package/shared/model-fallback/types.ts +8 -0
  126. package/shared/ranges.ts +21 -0
  127. package/shared/settings/define.ts +15 -0
  128. package/shared/settings/files.ts +20 -0
  129. package/shared/settings/json.ts +41 -0
  130. package/shared/settings/load.ts +59 -0
  131. package/shared/settings/merge.ts +21 -0
  132. package/shared/settings/paths.ts +41 -0
  133. package/shared/settings/schema.ts +25 -0
  134. package/shared/settings/specs.ts +23 -0
  135. package/shared/text.ts +11 -0
  136. package/shared/tool-row-state.ts +43 -0
  137. package/skills/writing-preferences/SKILL.md +136 -0
  138. package/themes/.gitkeep +1 -0
@@ -0,0 +1,7 @@
1
+ # Explore
2
+
3
+ Explore is Tau's first-party filesystem exploration extension.
4
+
5
+ It exists so agents can inspect paths, discover files, search text, and read file contents with compact model payloads and readable tool rows.
6
+
7
+ Agents invoke it with `ls`, `find`, `grep`, and `read`.
@@ -0,0 +1,140 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
4
+ import { onTauEvent, type TauAgentEvents } from "../../shared/events.js";
5
+ import type { ToolRowStateStore } from "../../shared/tool-row-state.js";
6
+ import { Marker, type MarkerState } from "@shanepadgett/tau-tui";
7
+
8
+ const AUTOREAD_MESSAGE_TYPE = "tau.autoread";
9
+
10
+ type AutoreadStatus = "reading" | "read" | "failed";
11
+
12
+ interface AutoreadDetails {
13
+ rowId: string;
14
+ path: string;
15
+ cwd: string;
16
+ source: string;
17
+ batchId: string;
18
+ status: AutoreadStatus;
19
+ error?: string;
20
+ }
21
+
22
+ export function registerAutoread(pi: ExtensionAPI, rowState: ToolRowStateStore): void {
23
+ onTauEvent(pi, "explore.autoread", "tau:autoread.requested", async (data) => {
24
+ const event = readAutoreadRequestedEvent(data);
25
+ if (!event) return;
26
+ await Promise.all(
27
+ event.files.map(async (file, index) => {
28
+ const rowId = `${event.batchId}:${index}`;
29
+ const details = {
30
+ rowId,
31
+ path: file.path,
32
+ cwd: event.cwd,
33
+ source: event.source,
34
+ batchId: event.batchId,
35
+ } satisfies Omit<AutoreadDetails, "status" | "error">;
36
+ try {
37
+ const content = await readFile(join(event.cwd, file.path), "utf8");
38
+ pi.sendMessage({
39
+ customType: AUTOREAD_MESSAGE_TYPE,
40
+ content: `${file.path}\n${content}`,
41
+ display: true,
42
+ details: { ...details, status: "read" },
43
+ });
44
+ } catch (error) {
45
+ const message = error instanceof Error ? error.message : String(error);
46
+ pi.sendMessage({
47
+ customType: AUTOREAD_MESSAGE_TYPE,
48
+ content: `${file.path}\nAutoread failed: ${message}`,
49
+ display: true,
50
+ details: { ...details, status: "failed", error: message },
51
+ });
52
+ }
53
+ }),
54
+ );
55
+ });
56
+
57
+ pi.registerMessageRenderer<AutoreadDetails>(AUTOREAD_MESSAGE_TYPE, (message, _options, theme) => {
58
+ const details = readDetails(message.details);
59
+ if (!details) return undefined;
60
+ return new AutoreadMessageComponent(rowState, details.rowId, details.path, details.status, theme);
61
+ });
62
+ }
63
+
64
+ function readAutoreadRequestedEvent(value: unknown): TauAgentEvents["tau:autoread.requested"] | undefined {
65
+ if (!value || typeof value !== "object") return undefined;
66
+ const record = value as Record<string, unknown>;
67
+ if (typeof record.source !== "string") return undefined;
68
+ if (typeof record.cwd !== "string") return undefined;
69
+ if (typeof record.batchId !== "string") return undefined;
70
+ if (record.title !== undefined && typeof record.title !== "string") return undefined;
71
+ if (!Array.isArray(record.files)) return undefined;
72
+ const files: Array<{ path: string }> = [];
73
+ for (const file of record.files) {
74
+ if (!file || typeof file !== "object") return undefined;
75
+ const path = (file as Record<string, unknown>).path;
76
+ if (typeof path !== "string") return undefined;
77
+ files.push({ path });
78
+ }
79
+
80
+ return {
81
+ source: record.source,
82
+ ...(record.title === undefined ? {} : { title: record.title }),
83
+ cwd: record.cwd,
84
+ batchId: record.batchId,
85
+ files,
86
+ };
87
+ }
88
+
89
+ function readDetails(value: unknown): AutoreadDetails | undefined {
90
+ if (!value || typeof value !== "object") return undefined;
91
+ const record = value as Record<string, unknown>;
92
+ if (typeof record.rowId !== "string") return undefined;
93
+ if (typeof record.path !== "string") return undefined;
94
+ if (typeof record.cwd !== "string") return undefined;
95
+ if (typeof record.source !== "string") return undefined;
96
+ if (typeof record.batchId !== "string") return undefined;
97
+ if (record.status !== "reading" && record.status !== "read" && record.status !== "failed") return undefined;
98
+ return {
99
+ rowId: record.rowId,
100
+ path: record.path,
101
+ cwd: record.cwd,
102
+ source: record.source,
103
+ batchId: record.batchId,
104
+ status: record.status,
105
+ ...(typeof record.error === "string" ? { error: record.error } : {}),
106
+ };
107
+ }
108
+
109
+ class AutoreadMessageComponent {
110
+ private readonly rowState: ToolRowStateStore;
111
+ private readonly rowId: string;
112
+ private readonly path: string;
113
+ private readonly status: AutoreadStatus;
114
+ private readonly theme: Theme;
115
+
116
+ constructor(rowState: ToolRowStateStore, rowId: string, path: string, status: AutoreadStatus, theme: Theme) {
117
+ this.rowState = rowState;
118
+ this.rowId = rowId;
119
+ this.path = path;
120
+ this.status = status;
121
+ this.theme = theme;
122
+ }
123
+
124
+ render(width: number): string[] {
125
+ return new Marker({
126
+ theme: this.theme,
127
+ state: this.markerState(),
128
+ label: "autoread",
129
+ parts: [this.path],
130
+ }).render(width);
131
+ }
132
+
133
+ invalidate(): void {}
134
+
135
+ private markerState(): MarkerState {
136
+ if (this.rowState.get(this.rowId) === "pruned") return "warning";
137
+ if (this.status === "failed") return "error";
138
+ return this.status === "reading" ? "busy" : "complete";
139
+ }
140
+ }
@@ -0,0 +1,147 @@
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
+ }