tau-coding-agent 0.1.6 → 0.2.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/README.md +15 -12
- package/extensions/answer.ts +129 -79
- package/extensions/branch-term/README.md +7 -0
- package/extensions/{branch-term.ts → branch-term/index.ts} +113 -104
- package/extensions/btw.ts +17 -2
- package/extensions/caffeinate/README.md +5 -0
- package/extensions/caffeinate/index.ts +144 -0
- package/extensions/fast.ts +292 -0
- package/extensions/ghostty.ts +214 -211
- package/extensions/git-diff-stats.ts +124 -84
- package/extensions/git-pr-status.ts +274 -208
- package/extensions/insights.ts +109 -191
- package/extensions/loop.ts +149 -145
- package/extensions/memory.ts +138 -86
- package/extensions/notify.ts +14 -26
- package/extensions/openai-verbosity.ts +108 -42
- package/extensions/review/fix.ts +15 -6
- package/extensions/review/git.ts +93 -103
- package/extensions/review/index.ts +60 -62
- package/extensions/review/interrupt.ts +117 -0
- package/extensions/review/message-queue.ts +45 -12
- package/extensions/review/models.ts +79 -3
- package/extensions/review/prompts.ts +41 -38
- package/extensions/review/review.ts +161 -166
- package/extensions/review/runner.ts +139 -147
- package/extensions/review/runtime.ts +173 -120
- package/extensions/review/triage.ts +41 -44
- package/extensions/sandbox/bash.ts +775 -0
- package/extensions/sandbox/command.ts +605 -0
- package/extensions/sandbox/config.ts +764 -0
- package/extensions/sandbox/index.ts +132 -3338
- package/extensions/sandbox/permissions/dialog.ts +118 -0
- package/extensions/sandbox/permissions/filesystem.ts +559 -0
- package/extensions/sandbox/permissions/mach-lookup.ts +186 -0
- package/extensions/sandbox/permissions/network.ts +164 -0
- package/extensions/sandbox/permissions/unsandboxed.ts +270 -0
- package/extensions/sandbox/runtime.ts +616 -0
- package/extensions/stash.ts +28 -15
- package/extensions/subagent/README.md +73 -0
- package/extensions/subagent/index.ts +822 -0
- package/extensions/subagent/interrupt.ts +117 -0
- package/extensions/subagent/permissions.ts +101 -0
- package/extensions/subagent/rpc.ts +177 -0
- package/extensions/tool-display-mode.ts +267 -64
- package/extensions/usage/index.ts +249 -356
- package/extensions/usage/openrouter.ts +10 -2
- package/extensions/websearch/README.md +12 -47
- package/extensions/websearch/config.ts +5 -2
- package/extensions/websearch/index.ts +94 -109
- package/extensions/websearch/output.ts +51 -0
- package/extensions/websearch/providers/anthropic.pi.ts +27 -44
- package/extensions/websearch/providers/gemini.browser.ts +68 -77
- package/extensions/websearch/providers/gemini.pi.ts +17 -17
- package/extensions/websearch/providers/openai-codex.pi.ts +145 -7
- package/extensions/websearch/providers/pi-model.shared.ts +33 -39
- package/extensions/websearch/providers/shared.ts +87 -2
- package/extensions/websearch/types.ts +0 -3
- package/extensions/worktree.ts +132 -172
- package/package.json +10 -5
- package/skills/browser-tools/SKILL.md +29 -234
- package/skills/browser-tools/references/cookies.md +36 -0
- package/skills/browser-tools/references/interaction.md +90 -0
- package/skills/browser-tools/references/logging.md +34 -0
- package/skills/git-clean-history/SKILL.md +6 -6
- package/skills/git-commit/SKILL.md +5 -3
- package/skills/github-pull-request/SKILL.md +60 -0
- package/skills/github-pull-request/references/create.md +40 -0
- package/skills/github-pull-request/references/stewardship.md +60 -0
- package/skills/oracle/SKILL.md +3 -3
- package/skills/oracle/scripts/oracle +10 -1
- package/skills/sentry/SKILL.md +15 -185
- package/skills/sentry/references/events.md +79 -0
- package/skills/sentry/references/issues.md +62 -0
- package/skills/sentry/references/logs.md +46 -0
- package/skills/update-changelog/SKILL.md +27 -121
- package/skills/web-design/SKILL.md +16 -105
- package/themes/tau-dark.json +4 -0
- package/extensions/openai-fast.ts +0 -229
- package/extensions/websearch/providers/openai-codex.browser.ts +0 -77
- package/extensions/websearch/providers/openai-codex.shared.ts +0 -123
|
@@ -12,6 +12,8 @@ import path from "node:path";
|
|
|
12
12
|
const STATUS_KEY = "1-git-diff-stats";
|
|
13
13
|
const REFRESH_DEBOUNCE_MS = 250;
|
|
14
14
|
const WRITE_REFRESH_DEBOUNCE_MS = 1_000;
|
|
15
|
+
const GIT_KILL_GRACE_MS = 500;
|
|
16
|
+
const GIT_PIPE_GRACE_MS = 100;
|
|
15
17
|
|
|
16
18
|
type DiffStats = {
|
|
17
19
|
added: number;
|
|
@@ -26,7 +28,9 @@ function isInsideGitRepo(startDir: string): boolean {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
function mergeNumstatEntries(output: string, statsByPath: Map<string, DiffStats>): void {
|
|
29
|
-
|
|
31
|
+
const records = output.split("\0");
|
|
32
|
+
for (let index = 0; index < records.length; index++) {
|
|
33
|
+
const line = records[index];
|
|
30
34
|
if (!line) continue;
|
|
31
35
|
|
|
32
36
|
const firstTab = line.indexOf("\t");
|
|
@@ -35,8 +39,13 @@ function mergeNumstatEntries(output: string, statsByPath: Map<string, DiffStats>
|
|
|
35
39
|
const secondTab = line.indexOf("\t", firstTab + 1);
|
|
36
40
|
if (secondTab === -1) continue;
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
if (
|
|
42
|
+
let filePath = line.slice(secondTab + 1);
|
|
43
|
+
if (!filePath) {
|
|
44
|
+
// Renames carry separate old and new paths after the numstat record.
|
|
45
|
+
index += 2;
|
|
46
|
+
filePath = records[index];
|
|
47
|
+
}
|
|
48
|
+
if (!filePath || statsByPath.has(filePath)) continue;
|
|
40
49
|
|
|
41
50
|
const addedToken = line.slice(0, firstTab);
|
|
42
51
|
const removedToken = line.slice(firstTab + 1, secondTab);
|
|
@@ -50,22 +59,48 @@ function mergeNumstatEntries(output: string, statsByPath: Map<string, DiffStats>
|
|
|
50
59
|
async function gitText(
|
|
51
60
|
cwd: string,
|
|
52
61
|
args: string[],
|
|
62
|
+
signal: AbortSignal,
|
|
53
63
|
options?: {
|
|
54
64
|
env?: NodeJS.ProcessEnv;
|
|
55
65
|
stdin?: string;
|
|
66
|
+
allowExitCode?: number;
|
|
56
67
|
},
|
|
57
68
|
): Promise<string> {
|
|
69
|
+
signal.throwIfAborted();
|
|
58
70
|
return new Promise((resolve, reject) => {
|
|
59
71
|
const child = spawn("git", args, {
|
|
60
72
|
cwd,
|
|
73
|
+
detached: true,
|
|
61
74
|
stdio: ["pipe", "pipe", "pipe"],
|
|
62
75
|
env: options?.env ? { ...process.env, ...options.env } : process.env,
|
|
63
76
|
});
|
|
64
77
|
|
|
65
78
|
let stdout = "";
|
|
66
79
|
let stderr = "";
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
let processError: Error | undefined;
|
|
81
|
+
let killTimer: NodeJS.Timeout | undefined;
|
|
82
|
+
let pipeTimer: NodeJS.Timeout | undefined;
|
|
83
|
+
let escalated = false;
|
|
84
|
+
const releaseOrphanedPipes = () => {
|
|
85
|
+
if (!escalated || pipeTimer || (child.exitCode === null && child.signalCode === null)) return;
|
|
86
|
+
pipeTimer = setTimeout(() => {
|
|
87
|
+
child.stdin?.destroy();
|
|
88
|
+
child.stdout?.destroy();
|
|
89
|
+
child.stderr?.destroy();
|
|
90
|
+
}, GIT_PIPE_GRACE_MS);
|
|
91
|
+
};
|
|
92
|
+
const onAbort = () => {
|
|
93
|
+
killProcessGroup(child, "SIGTERM");
|
|
94
|
+
killTimer = setTimeout(() => {
|
|
95
|
+
killProcessGroup(child, "SIGKILL");
|
|
96
|
+
escalated = true;
|
|
97
|
+
releaseOrphanedPipes();
|
|
98
|
+
}, GIT_KILL_GRACE_MS);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
child.on("error", (error) => {
|
|
102
|
+
processError = error;
|
|
103
|
+
});
|
|
69
104
|
child.stdin?.on("error", () => {});
|
|
70
105
|
child.stdout?.on("data", (chunk: Buffer) => {
|
|
71
106
|
stdout += chunk.toString("utf8");
|
|
@@ -73,25 +108,56 @@ async function gitText(
|
|
|
73
108
|
child.stderr?.on("data", (chunk: Buffer) => {
|
|
74
109
|
stderr += chunk.toString("utf8");
|
|
75
110
|
});
|
|
111
|
+
// Drain normally, but don't let a detached daemon retain cancelled work after Git exits.
|
|
112
|
+
child.once("exit", releaseOrphanedPipes);
|
|
76
113
|
child.on("close", (code) => {
|
|
114
|
+
clearTimeout(killTimer);
|
|
115
|
+
clearTimeout(pipeTimer);
|
|
116
|
+
child.removeListener("exit", releaseOrphanedPipes);
|
|
117
|
+
signal.removeEventListener("abort", onAbort);
|
|
118
|
+
if (signal.aborted) {
|
|
119
|
+
killProcessGroup(child, "SIGKILL");
|
|
120
|
+
reject(signal.reason);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (processError) {
|
|
124
|
+
reject(processError);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
77
127
|
const exitCode = code ?? -1;
|
|
78
|
-
if (exitCode !== 0) {
|
|
128
|
+
if (exitCode !== 0 && exitCode !== options?.allowExitCode) {
|
|
79
129
|
const details = [stdout.trim(), stderr.trim()].filter(Boolean).join("\n");
|
|
80
130
|
reject(new Error(details || `git ${args.join(" ")} failed`));
|
|
81
131
|
return;
|
|
82
132
|
}
|
|
83
|
-
|
|
84
|
-
resolve(stdout.trim());
|
|
133
|
+
resolve(stdout);
|
|
85
134
|
});
|
|
86
135
|
|
|
136
|
+
if (signal.aborted) onAbort();
|
|
137
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
87
138
|
child.stdin?.end(options?.stdin);
|
|
88
139
|
});
|
|
89
140
|
}
|
|
90
141
|
|
|
91
|
-
|
|
142
|
+
function killProcessGroup(child: ReturnType<typeof spawn>, signal: NodeJS.Signals): void {
|
|
143
|
+
if (!child.pid) return;
|
|
144
|
+
try {
|
|
145
|
+
process.kill(-child.pid, signal);
|
|
146
|
+
} catch {
|
|
147
|
+
try {
|
|
148
|
+
child.kill(signal);
|
|
149
|
+
} catch {
|
|
150
|
+
// Process likely already exited.
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function computeLocalStats(cwd: string, signal: AbortSignal): Promise<DiffStats | undefined> {
|
|
92
156
|
if (!isInsideGitRepo(cwd)) return undefined;
|
|
93
157
|
|
|
94
|
-
const gitDir =
|
|
158
|
+
const gitDir = (
|
|
159
|
+
await gitText(cwd, ["rev-parse", "--path-format=absolute", "--git-dir"], signal)
|
|
160
|
+
).replace(/\n$/, "");
|
|
95
161
|
const tempDir = await mkdtemp(path.join(gitDir, "pi-git-diff-stats-"));
|
|
96
162
|
const tempIndex = path.join(tempDir, "index");
|
|
97
163
|
const realIndex = path.join(gitDir, "index");
|
|
@@ -104,48 +170,27 @@ async function computeLocalStats(cwd: string): Promise<DiffStats | undefined> {
|
|
|
104
170
|
if (code !== "ENOENT") throw error;
|
|
105
171
|
}
|
|
106
172
|
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
let headStdout = "";
|
|
114
|
-
let headStderr = "";
|
|
115
|
-
head.stdout?.on("data", (chunk: Buffer) => {
|
|
116
|
-
headStdout += chunk.toString("utf8");
|
|
117
|
-
});
|
|
118
|
-
head.stderr?.on("data", (chunk: Buffer) => {
|
|
119
|
-
headStderr += chunk.toString("utf8");
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const headOidPromise = new Promise<string | undefined>((resolve, reject) => {
|
|
123
|
-
head.on("error", reject);
|
|
124
|
-
head.on("close", (code) => {
|
|
125
|
-
if (code === 0) {
|
|
126
|
-
resolve(headStdout.trim());
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (code === 1) {
|
|
130
|
-
resolve(undefined);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const details = [headStdout.trim(), headStderr.trim()].filter(Boolean).join("\n");
|
|
135
|
-
reject(new Error(details || "git rev-parse --verify --quiet HEAD failed"));
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
const [, headOid] = await Promise.all([addIntent, headOidPromise]);
|
|
173
|
+
const headOid = (
|
|
174
|
+
await gitText(cwd, ["rev-parse", "--verify", "--quiet", "HEAD"], signal, {
|
|
175
|
+
allowExitCode: 1,
|
|
176
|
+
})
|
|
177
|
+
).trim();
|
|
140
178
|
const baseOid =
|
|
141
|
-
headOid
|
|
179
|
+
headOid ||
|
|
180
|
+
(await gitText(cwd, ["hash-object", "-t", "tree", "--stdin"], signal, { stdin: "" })).trim();
|
|
142
181
|
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
182
|
+
const stagedDiff = await gitText(
|
|
183
|
+
cwd,
|
|
184
|
+
["diff", "--cached", "--numstat", "-z", baseOid, "--"],
|
|
185
|
+
signal,
|
|
186
|
+
{
|
|
146
187
|
env: { GIT_INDEX_FILE: tempIndex },
|
|
147
|
-
}
|
|
148
|
-
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
await gitText(cwd, ["add", "-N", "--all"], signal, { env: { GIT_INDEX_FILE: tempIndex } });
|
|
191
|
+
const workingTreeDiff = await gitText(cwd, ["diff", "--numstat", "-z", baseOid, "--"], signal, {
|
|
192
|
+
env: { GIT_INDEX_FILE: tempIndex },
|
|
193
|
+
});
|
|
149
194
|
|
|
150
195
|
const statsByPath = new Map<string, DiffStats>();
|
|
151
196
|
mergeNumstatEntries(workingTreeDiff, statsByPath);
|
|
@@ -172,15 +217,23 @@ export default function gitDiffStatsExtension(pi: ExtensionAPI) {
|
|
|
172
217
|
let refreshTimer: NodeJS.Timeout | undefined;
|
|
173
218
|
let refreshInFlight: Promise<void> | null = null;
|
|
174
219
|
let refreshQueued = false;
|
|
220
|
+
let refreshController: AbortController | undefined;
|
|
175
221
|
|
|
176
|
-
function reset(nextCtx?: ExtensionContext): void {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
222
|
+
async function reset(nextCtx?: ExtensionContext): Promise<void> {
|
|
223
|
+
const resetGeneration = ++generation;
|
|
224
|
+
if (refreshTimer) clearTimeout(refreshTimer);
|
|
225
|
+
refreshTimer = undefined;
|
|
226
|
+
refreshQueued = false;
|
|
227
|
+
const pending = refreshInFlight;
|
|
228
|
+
refreshController?.abort();
|
|
229
|
+
try {
|
|
230
|
+
clearStatus();
|
|
231
|
+
ctx = nextCtx;
|
|
232
|
+
stats = undefined;
|
|
233
|
+
} finally {
|
|
234
|
+
await pending;
|
|
235
|
+
}
|
|
236
|
+
if (generation === resetGeneration && nextCtx?.hasUI) scheduleRefresh(0);
|
|
184
237
|
}
|
|
185
238
|
|
|
186
239
|
function clearStatus(): void {
|
|
@@ -197,13 +250,6 @@ export default function gitDiffStatsExtension(pi: ExtensionAPI) {
|
|
|
197
250
|
);
|
|
198
251
|
}
|
|
199
252
|
|
|
200
|
-
function resetRefreshState(): void {
|
|
201
|
-
if (refreshTimer) clearTimeout(refreshTimer);
|
|
202
|
-
refreshTimer = undefined;
|
|
203
|
-
refreshInFlight = null;
|
|
204
|
-
refreshQueued = false;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
253
|
function scheduleRefresh(delay = REFRESH_DEBOUNCE_MS): void {
|
|
208
254
|
const activeCtx = ctx;
|
|
209
255
|
if (!activeCtx?.hasUI) return;
|
|
@@ -214,19 +260,21 @@ export default function gitDiffStatsExtension(pi: ExtensionAPI) {
|
|
|
214
260
|
const refreshGeneration = generation;
|
|
215
261
|
refreshTimer = setTimeout(() => {
|
|
216
262
|
refreshTimer = undefined;
|
|
217
|
-
|
|
263
|
+
refresh(cwd, refreshGeneration);
|
|
218
264
|
}, delay);
|
|
219
265
|
}
|
|
220
266
|
|
|
221
|
-
|
|
267
|
+
function refresh(cwd: string, refreshGeneration: number): void {
|
|
222
268
|
if (refreshInFlight) {
|
|
223
269
|
refreshQueued = true;
|
|
224
270
|
return;
|
|
225
271
|
}
|
|
226
272
|
|
|
227
|
-
const
|
|
273
|
+
const controller = new AbortController();
|
|
274
|
+
refreshController = controller;
|
|
275
|
+
refreshInFlight = (async () => {
|
|
228
276
|
try {
|
|
229
|
-
const nextStats = await computeLocalStats(cwd);
|
|
277
|
+
const nextStats = await computeLocalStats(cwd, controller.signal);
|
|
230
278
|
if (generation !== refreshGeneration) return;
|
|
231
279
|
|
|
232
280
|
stats = nextStats;
|
|
@@ -236,26 +284,18 @@ export default function gitDiffStatsExtension(pi: ExtensionAPI) {
|
|
|
236
284
|
|
|
237
285
|
stats = undefined;
|
|
238
286
|
clearStatus();
|
|
239
|
-
}
|
|
240
|
-
})();
|
|
241
|
-
|
|
242
|
-
refreshInFlight = promise;
|
|
243
|
-
|
|
244
|
-
try {
|
|
245
|
-
await promise;
|
|
246
|
-
} finally {
|
|
247
|
-
if (refreshInFlight === promise) {
|
|
287
|
+
} finally {
|
|
248
288
|
refreshInFlight = null;
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
289
|
+
refreshController = undefined;
|
|
290
|
+
const queued = refreshQueued;
|
|
291
|
+
refreshQueued = false;
|
|
292
|
+
if (queued && generation === refreshGeneration) scheduleRefresh(0);
|
|
253
293
|
}
|
|
254
|
-
}
|
|
294
|
+
})();
|
|
255
295
|
}
|
|
256
296
|
|
|
257
297
|
pi.on("session_start", async (_event, nextCtx) => {
|
|
258
|
-
reset(nextCtx);
|
|
298
|
+
await reset(nextCtx);
|
|
259
299
|
});
|
|
260
300
|
|
|
261
301
|
pi.on("tool_result", async (event, nextCtx) => {
|
|
@@ -272,6 +312,6 @@ export default function gitDiffStatsExtension(pi: ExtensionAPI) {
|
|
|
272
312
|
});
|
|
273
313
|
|
|
274
314
|
pi.on("session_shutdown", async () => {
|
|
275
|
-
reset(undefined);
|
|
315
|
+
await reset(undefined);
|
|
276
316
|
});
|
|
277
317
|
}
|