tau-coding-agent 0.1.5 → 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 -73
- package/extensions/branch-term/README.md +7 -0
- package/extensions/{branch-term.ts → branch-term/index.ts} +113 -104
- package/extensions/btw.ts +90 -4
- 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 +138 -183
- package/extensions/loop.ts +150 -135
- package/extensions/memory.ts +172 -97
- 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 +43 -40
- package/extensions/review/review.ts +161 -166
- package/extensions/review/runner.ts +139 -147
- package/extensions/review/runtime.ts +173 -120
- package/extensions/review/submit-review-tool.ts +1 -5
- package/extensions/review/submit-triage-tool.ts +1 -0
- 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 +138 -2850
- package/extensions/sandbox/macos-sandbox-shell.mjs +202 -0
- 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/anthropic.ts +5 -6
- package/extensions/usage/github-copilot.ts +30 -9
- package/extensions/usage/index.ts +250 -357
- package/extensions/usage/openai-codex.ts +2 -2
- 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 +44 -32
- 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 +4 -4
- package/skills/oracle/scripts/oracle +64 -51
- 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
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compatibility workaround for sandbox-runtime on macOS.
|
|
5
|
+
*
|
|
6
|
+
* The runtime routes Git-over-SSH through BSD nc, which cannot authenticate to
|
|
7
|
+
* its SOCKS5 proxy. This shell preserves normal bash execution while replacing
|
|
8
|
+
* that transport with an authenticated HTTP CONNECT tunnel.
|
|
9
|
+
*
|
|
10
|
+
* Remove once the upstream fix ships:
|
|
11
|
+
* https://github.com/anthropic-experimental/sandbox-runtime/pull/385
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { spawn } from "node:child_process";
|
|
15
|
+
import { connect } from "node:net";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
|
|
18
|
+
const PROXY_CONNECT_MODE = "--proxy-connect";
|
|
19
|
+
const CONNECT_TIMEOUT_MS = 15_000;
|
|
20
|
+
const MAX_RESPONSE_HEADER_BYTES = 64 * 1024;
|
|
21
|
+
|
|
22
|
+
if (process.argv[2] === PROXY_CONNECT_MODE) {
|
|
23
|
+
connectThroughSandboxProxy(process.argv[3], process.argv[4]);
|
|
24
|
+
} else {
|
|
25
|
+
runBash(process.argv.slice(2));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function runBash(args) {
|
|
29
|
+
if (hasAuthenticatedSandboxProxy()) {
|
|
30
|
+
const scriptPath = fileURLToPath(import.meta.url);
|
|
31
|
+
const proxyCommand = [
|
|
32
|
+
shellQuote(escapeSshPercent(process.execPath)),
|
|
33
|
+
shellQuote(escapeSshPercent(scriptPath)),
|
|
34
|
+
PROXY_CONNECT_MODE,
|
|
35
|
+
"%h",
|
|
36
|
+
"%p",
|
|
37
|
+
].join(" ");
|
|
38
|
+
process.env.GIT_SSH_COMMAND = [
|
|
39
|
+
"ssh",
|
|
40
|
+
"-o ControlMaster=no",
|
|
41
|
+
"-o ControlPath=none",
|
|
42
|
+
`-o ProxyCommand=${shellQuote(proxyCommand)}`,
|
|
43
|
+
].join(" ");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const child = spawn("bash", args, {
|
|
47
|
+
env: process.env,
|
|
48
|
+
stdio: "inherit",
|
|
49
|
+
});
|
|
50
|
+
child.once("error", (error) => {
|
|
51
|
+
console.error(error.message);
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
});
|
|
54
|
+
child.once("exit", (code, signal) => {
|
|
55
|
+
if (signal) {
|
|
56
|
+
process.kill(process.pid, signal);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
process.exitCode = code ?? 1;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function hasAuthenticatedSandboxProxy() {
|
|
64
|
+
try {
|
|
65
|
+
const proxy = getSandboxProxyUrl();
|
|
66
|
+
return proxy.protocol === "http:" && Boolean(proxy.username || proxy.password);
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function connectThroughSandboxProxy(targetHost, targetPortText) {
|
|
73
|
+
const targetPort = Number(targetPortText);
|
|
74
|
+
if (
|
|
75
|
+
!targetHost ||
|
|
76
|
+
/[\r\n]/u.test(targetHost) ||
|
|
77
|
+
!Number.isInteger(targetPort) ||
|
|
78
|
+
targetPort < 1 ||
|
|
79
|
+
targetPort > 65_535
|
|
80
|
+
) {
|
|
81
|
+
fail("Invalid SSH proxy target.");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let proxy;
|
|
85
|
+
try {
|
|
86
|
+
proxy = getSandboxProxyUrl();
|
|
87
|
+
} catch (error) {
|
|
88
|
+
fail(error);
|
|
89
|
+
}
|
|
90
|
+
if (proxy.protocol !== "http:") {
|
|
91
|
+
fail(`Unsupported sandbox proxy protocol: ${proxy.protocol}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const proxyPort = Number(proxy.port || 80);
|
|
95
|
+
const authority = targetHost.includes(":")
|
|
96
|
+
? `[${targetHost}]:${targetPort}`
|
|
97
|
+
: `${targetHost}:${targetPort}`;
|
|
98
|
+
let username;
|
|
99
|
+
let password;
|
|
100
|
+
try {
|
|
101
|
+
username = decodeURIComponent(proxy.username);
|
|
102
|
+
password = decodeURIComponent(proxy.password);
|
|
103
|
+
} catch {
|
|
104
|
+
fail("Sandbox HTTP proxy credentials are invalid.");
|
|
105
|
+
}
|
|
106
|
+
const authorization =
|
|
107
|
+
username || password
|
|
108
|
+
? `Proxy-Authorization: Basic ${Buffer.from(`${username}:${password}`).toString("base64")}\r\n`
|
|
109
|
+
: "";
|
|
110
|
+
|
|
111
|
+
const socket = connect({ host: proxy.hostname, port: proxyPort });
|
|
112
|
+
const connectTimeout = setTimeout(() => {
|
|
113
|
+
socket.destroy(new Error("Sandbox proxy connection timed out."));
|
|
114
|
+
}, CONNECT_TIMEOUT_MS);
|
|
115
|
+
let response = Buffer.alloc(0);
|
|
116
|
+
let tunnelEstablished = false;
|
|
117
|
+
let connectionFailed = false;
|
|
118
|
+
|
|
119
|
+
socket.once("connect", () => {
|
|
120
|
+
clearTimeout(connectTimeout);
|
|
121
|
+
socket.write(
|
|
122
|
+
`CONNECT ${authority} HTTP/1.1\r\nHost: ${authority}\r\n${authorization}Proxy-Connection: Keep-Alive\r\n\r\n`,
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const failConnection = (error) => {
|
|
127
|
+
if (connectionFailed) return;
|
|
128
|
+
connectionFailed = true;
|
|
129
|
+
clearTimeout(connectTimeout);
|
|
130
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
131
|
+
process.exitCode = 1;
|
|
132
|
+
socket.destroy();
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const handleResponse = (chunk) => {
|
|
136
|
+
response = Buffer.concat([response, chunk]);
|
|
137
|
+
if (response.length > MAX_RESPONSE_HEADER_BYTES) {
|
|
138
|
+
failConnection(new Error("Sandbox proxy returned oversized headers."));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const headerEnd = response.indexOf("\r\n\r\n");
|
|
143
|
+
if (headerEnd === -1) return;
|
|
144
|
+
|
|
145
|
+
const header = response.subarray(0, headerEnd).toString("latin1");
|
|
146
|
+
const status = header.match(/^HTTP\/\d(?:\.\d)?\s+(\d{3})/u)?.[1];
|
|
147
|
+
if (status !== "200") {
|
|
148
|
+
failConnection(new Error(`Sandbox proxy CONNECT failed${status ? ` (${status})` : ""}.`));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
tunnelEstablished = true;
|
|
153
|
+
socket.off("data", handleResponse);
|
|
154
|
+
const remainder = response.subarray(headerEnd + 4);
|
|
155
|
+
if (remainder.length > 0) process.stdout.write(remainder);
|
|
156
|
+
process.stdin.pipe(socket);
|
|
157
|
+
socket.pipe(process.stdout);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
socket.on("data", handleResponse);
|
|
161
|
+
socket.once("error", failConnection);
|
|
162
|
+
socket.once("close", () => {
|
|
163
|
+
clearTimeout(connectTimeout);
|
|
164
|
+
process.stdin.unpipe(socket);
|
|
165
|
+
process.stdin.pause();
|
|
166
|
+
if (!tunnelEstablished && !connectionFailed) {
|
|
167
|
+
failConnection(new Error("Sandbox proxy closed before establishing the SSH tunnel."));
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
process.stdin.on("error", failConnection);
|
|
171
|
+
process.stdout.on("error", (error) => {
|
|
172
|
+
if (error?.code === "EPIPE") {
|
|
173
|
+
socket.destroy();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
failConnection(error);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function getSandboxProxyUrl() {
|
|
181
|
+
const value = process.env.HTTPS_PROXY ?? process.env.HTTP_PROXY;
|
|
182
|
+
if (!value) throw new Error("Sandbox HTTP proxy is unavailable.");
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
return new URL(value);
|
|
186
|
+
} catch {
|
|
187
|
+
throw new Error("Sandbox HTTP proxy URL is invalid.");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function escapeSshPercent(value) {
|
|
192
|
+
return value.replaceAll("%", "%%");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function shellQuote(value) {
|
|
196
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function fail(error) {
|
|
200
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
type PermissionDecision = "allow-retry" | "allow-adapt" | "deny";
|
|
4
|
+
|
|
5
|
+
export type PermissionResolution =
|
|
6
|
+
| {
|
|
7
|
+
kind: "allow-retry";
|
|
8
|
+
message: string;
|
|
9
|
+
retrySuccessMessage: string;
|
|
10
|
+
retryFailureMessage: string;
|
|
11
|
+
retrySkippedMessage: string;
|
|
12
|
+
}
|
|
13
|
+
| { kind: "allow-adapt"; message: string }
|
|
14
|
+
| { kind: "deny"; message: string };
|
|
15
|
+
|
|
16
|
+
const ALLOW_RETRY_OPTION = "Allow and retry now";
|
|
17
|
+
const ALLOW_ADAPT_OPTION = "Allow but adapt for side-effects";
|
|
18
|
+
const DENY_OPTION = "Deny";
|
|
19
|
+
|
|
20
|
+
export async function showPermissionDialog<T>(options: {
|
|
21
|
+
ctx: ExtensionContext;
|
|
22
|
+
title: string;
|
|
23
|
+
promptKey: string;
|
|
24
|
+
pendingDialogs?: Map<string, Promise<T | null>>;
|
|
25
|
+
autoRetryAvailable: boolean;
|
|
26
|
+
onDecision: (decision: PermissionDecision) => T | Promise<T>;
|
|
27
|
+
}): Promise<T | null> {
|
|
28
|
+
const { ctx, title, promptKey, pendingDialogs, autoRetryAvailable, onDecision } = options;
|
|
29
|
+
const existingDialog = pendingDialogs?.get(promptKey);
|
|
30
|
+
if (existingDialog) return existingDialog;
|
|
31
|
+
|
|
32
|
+
const dialogTask: Promise<T | null> = (async () => {
|
|
33
|
+
try {
|
|
34
|
+
const selection = await ctx.ui.select(title, getPermissionDialogOptions(autoRetryAvailable));
|
|
35
|
+
return await onDecision(parsePermissionDialogSelection(selection, autoRetryAvailable));
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
})();
|
|
40
|
+
|
|
41
|
+
if (!pendingDialogs) return dialogTask;
|
|
42
|
+
|
|
43
|
+
pendingDialogs.set(promptKey, dialogTask);
|
|
44
|
+
try {
|
|
45
|
+
return await dialogTask;
|
|
46
|
+
} finally {
|
|
47
|
+
pendingDialogs.delete(promptKey);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function createPermissionResolution(
|
|
52
|
+
decision: PermissionDecision,
|
|
53
|
+
blockedTarget: string,
|
|
54
|
+
changed = true,
|
|
55
|
+
): PermissionResolution {
|
|
56
|
+
if (decision === "deny") {
|
|
57
|
+
return { kind: "deny", message: formatDeniedMessage(blockedTarget) };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (decision === "allow-retry") {
|
|
61
|
+
return {
|
|
62
|
+
kind: "allow-retry",
|
|
63
|
+
message: formatAllowRetryMessage(blockedTarget),
|
|
64
|
+
retrySuccessMessage: formatRetrySucceededMessage(blockedTarget),
|
|
65
|
+
retryFailureMessage: formatRetryFailedMessage(blockedTarget),
|
|
66
|
+
retrySkippedMessage: formatRetrySkippedMessage(blockedTarget),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
kind: "allow-adapt",
|
|
72
|
+
message: changed
|
|
73
|
+
? formatAllowAdaptMessage(blockedTarget)
|
|
74
|
+
: formatAlreadyAllowedMessage(blockedTarget),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getPermissionDialogOptions(autoRetryAvailable: boolean): string[] {
|
|
79
|
+
if (!autoRetryAvailable) return [ALLOW_ADAPT_OPTION, DENY_OPTION];
|
|
80
|
+
return [ALLOW_RETRY_OPTION, ALLOW_ADAPT_OPTION, DENY_OPTION];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function parsePermissionDialogSelection(
|
|
84
|
+
selection: string | undefined,
|
|
85
|
+
autoRetryAvailable: boolean,
|
|
86
|
+
): PermissionDecision {
|
|
87
|
+
if (selection === ALLOW_ADAPT_OPTION) return "allow-adapt";
|
|
88
|
+
if (selection === ALLOW_RETRY_OPTION && autoRetryAvailable) return "allow-retry";
|
|
89
|
+
return "deny";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function formatAllowRetryMessage(blockedTarget: string): string {
|
|
93
|
+
return `\n${blockedTarget}\n\nGranting access and retrying the command per user request...\n\n`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function formatAllowAdaptMessage(blockedTarget: string): string {
|
|
97
|
+
return `\n${blockedTarget}\n\nAccess granted for this session. Retry the command manually if appropriate.`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function formatDeniedMessage(blockedTarget: string): string {
|
|
101
|
+
return `\n${blockedTarget}\n\nAccess remains denied for this session.`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function formatAlreadyAllowedMessage(blockedTarget: string): string {
|
|
105
|
+
return `\n${blockedTarget}\n\nAccess had already been granted for this session. The remaining failure may be unrelated to sandbox policy.`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function formatRetrySucceededMessage(blockedTarget: string): string {
|
|
109
|
+
return `\n${blockedTarget}\n\nAccess granted for this session and the command was retried successfully.`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function formatRetryFailedMessage(blockedTarget: string): string {
|
|
113
|
+
return `\n${blockedTarget}\n\nAccess granted for this session and the command was retried per user request, but the command still exited non-zero. The sandbox block was resolved; the remaining failure may be unrelated.`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function formatRetrySkippedMessage(blockedTarget: string): string {
|
|
117
|
+
return `\n${blockedTarget}\n\nAccess granted for this session, but automatic retry was skipped because the timeout was exhausted. Retry the command manually if needed.`;
|
|
118
|
+
}
|