tau-coding-agent 0.1.4 → 0.1.6
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 +1 -1
- package/extensions/answer.ts +59 -47
- package/extensions/branch-term.ts +8 -13
- package/extensions/btw.ts +77 -6
- package/extensions/ghostty.ts +64 -11
- package/extensions/insights.ts +42 -4
- package/extensions/loop.ts +51 -12
- package/extensions/memory.ts +35 -17
- package/extensions/notify.ts +24 -5
- package/extensions/openai-fast.ts +6 -4
- package/extensions/openai-verbosity.ts +6 -4
- package/extensions/review/fix.ts +239 -0
- package/extensions/review/git.ts +517 -0
- package/extensions/review/index.ts +232 -0
- package/extensions/review/message-queue.ts +249 -0
- package/extensions/review/models.ts +589 -0
- package/extensions/review/prompts.ts +337 -0
- package/extensions/review/renderers/inline.ts +126 -0
- package/extensions/review/request.ts +504 -0
- package/extensions/review/review.ts +1305 -0
- package/extensions/review/runner.ts +522 -0
- package/extensions/review/runtime.ts +275 -0
- package/extensions/review/schema.ts +160 -0
- package/extensions/review/submit-review-tool.ts +62 -0
- package/extensions/review/submit-triage-tool.ts +60 -0
- package/extensions/review/triage.ts +746 -0
- package/extensions/sandbox/index.ts +976 -185
- package/extensions/sandbox/macos-sandbox-shell.mjs +202 -0
- package/extensions/{interlude.ts → stash.ts} +13 -11
- package/extensions/tool-display-mode.ts +2 -3
- package/extensions/usage/anthropic.ts +5 -6
- package/extensions/usage/github-copilot.ts +30 -9
- package/extensions/usage/index.ts +9 -5
- package/extensions/usage/openai-codex.ts +2 -2
- package/extensions/websearch/README.md +1 -1
- package/extensions/websearch/browser/chromium.ts +42 -31
- package/extensions/websearch/browser/discovery.ts +11 -9
- package/extensions/websearch/browser/firefox.ts +10 -7
- package/extensions/websearch/browser/fs.ts +10 -0
- package/extensions/websearch/browser/sqlite.ts +22 -15
- package/extensions/websearch/config.ts +2 -2
- package/extensions/websearch/index.ts +6 -6
- package/extensions/websearch/providers/pi-model.shared.ts +19 -1
- package/extensions/worktree.ts +33 -33
- package/package.json +4 -4
- package/skills/git-clean-history/SKILL.md +1 -0
- package/skills/git-commit/SKILL.md +2 -2
- package/skills/oracle/SKILL.md +3 -3
- package/skills/oracle/scripts/oracle +82 -61
- package/themes/tau-dark.json +2 -1
- package/extensions/review.ts +0 -4420
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { DatabaseSync } from "node:sqlite";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
6
|
import type { BrowserCookie, BrowserProfile } from "../types.js";
|
|
7
|
+
import { pathExists } from "./fs.js";
|
|
7
8
|
import { withSqliteSnapshot } from "./sqlite.js";
|
|
8
9
|
|
|
9
10
|
function firefoxBaseDir(): string | null {
|
|
@@ -45,7 +46,9 @@ function parseIni(text: string): Array<Record<string, string>> {
|
|
|
45
46
|
return sections;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
export function discoverFirefoxProfiles(
|
|
49
|
+
export async function discoverFirefoxProfiles(
|
|
50
|
+
preferredProfileName?: string,
|
|
51
|
+
): Promise<BrowserProfile[]> {
|
|
49
52
|
const baseDir = firefoxBaseDir();
|
|
50
53
|
if (!baseDir) return [];
|
|
51
54
|
|
|
@@ -53,7 +56,7 @@ export function discoverFirefoxProfiles(preferredProfileName?: string): BrowserP
|
|
|
53
56
|
|
|
54
57
|
let sections: Array<Record<string, string>> = [];
|
|
55
58
|
try {
|
|
56
|
-
sections = parseIni(
|
|
59
|
+
sections = parseIni(await readFile(profilesIniPath, "utf8"));
|
|
57
60
|
} catch {
|
|
58
61
|
return [];
|
|
59
62
|
}
|
|
@@ -67,7 +70,7 @@ export function discoverFirefoxProfiles(preferredProfileName?: string): BrowserP
|
|
|
67
70
|
const profilePath =
|
|
68
71
|
section.IsRelative === "1" ? path.join(baseDir, profilePathValue) : profilePathValue;
|
|
69
72
|
const cookiesPath = path.join(profilePath, "cookies.sqlite");
|
|
70
|
-
if (!
|
|
73
|
+
if (!(await pathExists(cookiesPath))) continue;
|
|
71
74
|
|
|
72
75
|
profiles.push({
|
|
73
76
|
family: "firefox",
|
|
@@ -100,12 +103,12 @@ function isPreferredFirefoxProfile(
|
|
|
100
103
|
);
|
|
101
104
|
}
|
|
102
105
|
|
|
103
|
-
export function loadFirefoxCookies(profile: BrowserProfile): BrowserCookie[] {
|
|
106
|
+
export async function loadFirefoxCookies(profile: BrowserProfile): Promise<BrowserCookie[]> {
|
|
104
107
|
const cookiesPath = path.join(profile.profilePath, "cookies.sqlite");
|
|
105
|
-
if (!
|
|
108
|
+
if (!(await pathExists(cookiesPath))) return [];
|
|
106
109
|
|
|
107
110
|
try {
|
|
108
|
-
return withSqliteSnapshot(cookiesPath, "websearch-firefox", (tempDbPath) => {
|
|
111
|
+
return await withSqliteSnapshot(cookiesPath, "websearch-firefox", (tempDbPath) => {
|
|
109
112
|
const db = new DatabaseSync(tempDbPath, { readOnly: true });
|
|
110
113
|
try {
|
|
111
114
|
const rows = db
|
|
@@ -1,33 +1,40 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { copyFile, mkdtemp, rm } from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import { pathExists } from "./fs.js";
|
|
6
|
+
|
|
7
|
+
export async function withSqliteSnapshot<T>(
|
|
6
8
|
sourceDbPath: string,
|
|
7
9
|
tempPrefix: string,
|
|
8
|
-
operation: (snapshotPath: string) => T
|
|
9
|
-
): T {
|
|
10
|
-
const tempDir = path.join(os.tmpdir(), `${tempPrefix}-${process.pid}
|
|
10
|
+
operation: (snapshotPath: string) => T | Promise<T>,
|
|
11
|
+
): Promise<T> {
|
|
12
|
+
const tempDir = await mkdtemp(path.join(os.tmpdir(), `${tempPrefix}-${process.pid}-`));
|
|
11
13
|
const snapshotPath = path.join(tempDir, path.basename(sourceDbPath));
|
|
12
14
|
|
|
13
15
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
await copyFile(sourceDbPath, snapshotPath);
|
|
17
|
+
await Promise.all([
|
|
18
|
+
copySidecar(sourceDbPath, snapshotPath, "-wal"),
|
|
19
|
+
copySidecar(sourceDbPath, snapshotPath, "-shm"),
|
|
20
|
+
copySidecar(sourceDbPath, snapshotPath, "-journal"),
|
|
21
|
+
]);
|
|
22
|
+
return await operation(snapshotPath);
|
|
20
23
|
} finally {
|
|
21
|
-
|
|
24
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
export function copySidecar(
|
|
28
|
+
export async function copySidecar(
|
|
29
|
+
sourceDbPath: string,
|
|
30
|
+
targetDbPath: string,
|
|
31
|
+
suffix: string,
|
|
32
|
+
): Promise<void> {
|
|
26
33
|
const source = `${sourceDbPath}${suffix}`;
|
|
27
|
-
if (!
|
|
34
|
+
if (!(await pathExists(source))) return;
|
|
28
35
|
|
|
29
36
|
try {
|
|
30
|
-
|
|
37
|
+
await copyFile(source, `${targetDbPath}${suffix}`);
|
|
31
38
|
} catch (error) {
|
|
32
39
|
const code =
|
|
33
40
|
error && typeof error === "object" && "code" in error
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
2
|
import path from "node:path";
|
|
3
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
|
|
5
5
|
import type { WebsearchConfig, WebsearchRouteId } from "./types.js";
|
|
6
6
|
|
|
7
|
-
const WEBSEARCH_CONFIG_PATH = path.join(
|
|
7
|
+
const WEBSEARCH_CONFIG_PATH = path.join(getAgentDir(), "websearch.json");
|
|
8
8
|
|
|
9
9
|
const DEFAULT_ROUTES: WebsearchRouteId[] = [
|
|
10
10
|
"pi:openai-codex",
|
|
@@ -128,15 +128,15 @@ function throwIfAborted(signal?: AbortSignal): void {
|
|
|
128
128
|
throw signal.reason ?? new Error("The operation was aborted.");
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
function getBrowserProfiles(
|
|
131
|
+
async function getBrowserProfiles(
|
|
132
132
|
cache: Partial<Record<WebsearchBrowserFamily, BrowserProfile[]>>,
|
|
133
133
|
family: WebsearchBrowserFamily,
|
|
134
134
|
config: WebsearchConfig,
|
|
135
|
-
): BrowserProfile[] {
|
|
135
|
+
): Promise<BrowserProfile[]> {
|
|
136
136
|
const cached = cache[family];
|
|
137
137
|
if (cached) return cached;
|
|
138
138
|
|
|
139
|
-
const profiles = discoverProfiles([family], config.profiles);
|
|
139
|
+
const profiles = await discoverProfiles([family], config.profiles);
|
|
140
140
|
cache[family] = profiles;
|
|
141
141
|
return profiles;
|
|
142
142
|
}
|
|
@@ -153,7 +153,7 @@ async function searchBrowserRoute(
|
|
|
153
153
|
: "chromium";
|
|
154
154
|
const browserProvider = route.endsWith(":gemini") ? browserGemini : browserOpenAICodex;
|
|
155
155
|
const configuredProfileName = config.profiles[profileFamily];
|
|
156
|
-
const profiles = getBrowserProfiles(profileCache, profileFamily, config);
|
|
156
|
+
const profiles = await getBrowserProfiles(profileCache, profileFamily, config);
|
|
157
157
|
let lastError: string | null = null;
|
|
158
158
|
|
|
159
159
|
if (configuredProfileName && profiles.length === 0) {
|
|
@@ -164,7 +164,7 @@ async function searchBrowserRoute(
|
|
|
164
164
|
throwIfAborted(signal);
|
|
165
165
|
|
|
166
166
|
try {
|
|
167
|
-
const session = createBrowserSession(profile, browserProvider.domains);
|
|
167
|
+
const session = await createBrowserSession(profile, browserProvider.domains);
|
|
168
168
|
if (!session) {
|
|
169
169
|
if (configuredProfileName) {
|
|
170
170
|
throw new Error(
|
|
@@ -265,7 +265,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
265
265
|
promptSnippet:
|
|
266
266
|
"Search the web for current or external information unavailable in local files",
|
|
267
267
|
promptGuidelines: [
|
|
268
|
-
"Use
|
|
268
|
+
"Use websearch for recent facts, live service behavior, or external documentation that is not already present in the repo.",
|
|
269
269
|
"Do not use websearch when repository files or supplied context already answer the question.",
|
|
270
270
|
],
|
|
271
271
|
parameters: Type.Object({
|
|
@@ -5,6 +5,7 @@ export interface PiModelSelection {
|
|
|
5
5
|
model: Model<Api>;
|
|
6
6
|
apiKey?: string;
|
|
7
7
|
headers?: Record<string, string>;
|
|
8
|
+
env?: Record<string, string>;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export async function selectCurrentPiModel(
|
|
@@ -42,7 +43,24 @@ async function resolvePiModelSelection(
|
|
|
42
43
|
ctx: Pick<ExtensionContext, "modelRegistry">,
|
|
43
44
|
): Promise<PiModelSelection | null> {
|
|
44
45
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
45
|
-
return auth.ok
|
|
46
|
+
return auth.ok
|
|
47
|
+
? {
|
|
48
|
+
model: resolveModelEnv(model, auth.env),
|
|
49
|
+
apiKey: auth.apiKey,
|
|
50
|
+
headers: auth.headers,
|
|
51
|
+
env: auth.env,
|
|
52
|
+
}
|
|
53
|
+
: null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolveModelEnv(model: Model<Api>, env?: Record<string, string>): Model<Api> {
|
|
57
|
+
if (!model.baseUrl || !env) return model;
|
|
58
|
+
|
|
59
|
+
let baseUrl = model.baseUrl;
|
|
60
|
+
for (const [name, value] of Object.entries(env)) {
|
|
61
|
+
baseUrl = baseUrl.replaceAll(`{${name}}`, value);
|
|
62
|
+
}
|
|
63
|
+
return baseUrl === model.baseUrl ? model : { ...model, baseUrl };
|
|
46
64
|
}
|
|
47
65
|
|
|
48
66
|
function rankPiModels(models: Model<Api>[]): Model<Api>[] {
|
package/extensions/worktree.ts
CHANGED
|
@@ -40,9 +40,9 @@ import * as fs from "node:fs";
|
|
|
40
40
|
import * as path from "node:path";
|
|
41
41
|
|
|
42
42
|
const STATUS_KEY = "0-worktree";
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
43
|
+
const MANUAL_OPEN_ENTRY_TYPE = "worktree-open-command";
|
|
44
|
+
const RESTORE_STASH_ENTRY_TYPE = "worktree-restore-command";
|
|
45
|
+
const SCRIPT_RERUN_ENTRY_TYPE = "worktree-script-rerun-command";
|
|
46
46
|
const MANUAL_OPEN_INTRO =
|
|
47
47
|
"Worktree ready. Open it in a separate terminal or tmux pane with this command";
|
|
48
48
|
|
|
@@ -224,15 +224,10 @@ function showCommandMessage(
|
|
|
224
224
|
return;
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
pi.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
details: {
|
|
232
|
-
intro,
|
|
233
|
-
command,
|
|
234
|
-
copiedToClipboard,
|
|
235
|
-
} satisfies CommandMessageDetails,
|
|
227
|
+
pi.appendEntry<CommandMessageDetails>(customType, {
|
|
228
|
+
intro,
|
|
229
|
+
command,
|
|
230
|
+
copiedToClipboard,
|
|
236
231
|
});
|
|
237
232
|
}
|
|
238
233
|
|
|
@@ -241,7 +236,7 @@ function showManualOpenCommand(
|
|
|
241
236
|
ctx: ExtensionCommandContext,
|
|
242
237
|
command: string,
|
|
243
238
|
): void {
|
|
244
|
-
showCommandMessage(pi, ctx,
|
|
239
|
+
showCommandMessage(pi, ctx, MANUAL_OPEN_ENTRY_TYPE, MANUAL_OPEN_INTRO, command);
|
|
245
240
|
}
|
|
246
241
|
|
|
247
242
|
function showRestoreStashCommand(
|
|
@@ -253,7 +248,7 @@ function showRestoreStashCommand(
|
|
|
253
248
|
showCommandMessage(
|
|
254
249
|
pi,
|
|
255
250
|
ctx,
|
|
256
|
-
|
|
251
|
+
RESTORE_STASH_ENTRY_TYPE,
|
|
257
252
|
`Main-worktree changes were stashed as ${stashSpec}. Restore them in the new worktree with this command`,
|
|
258
253
|
`cd ${shellQuoteCompact(targetPath)} && git stash apply ${shellQuote(stashSpec)}`,
|
|
259
254
|
);
|
|
@@ -270,7 +265,7 @@ function showScriptRerunCommand(
|
|
|
270
265
|
showCommandMessage(
|
|
271
266
|
pi,
|
|
272
267
|
ctx,
|
|
273
|
-
|
|
268
|
+
SCRIPT_RERUN_ENTRY_TYPE,
|
|
274
269
|
`${label} failed (exit ${exitCode}). Re-run it manually with this command`,
|
|
275
270
|
`cd ${shellQuoteCompact(worktreeRoot)} && ${command}`,
|
|
276
271
|
);
|
|
@@ -2008,18 +2003,23 @@ async function handleList(pi: ExtensionAPI, ctx: ExtensionCommandContext): Promi
|
|
|
2008
2003
|
return { repo, items };
|
|
2009
2004
|
});
|
|
2010
2005
|
|
|
2011
|
-
if (
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2006
|
+
if (ctx.mode !== "tui") {
|
|
2007
|
+
const output = items
|
|
2008
|
+
.map((item) => {
|
|
2009
|
+
const dirty = item.status === "dirty" ? " *" : "";
|
|
2010
|
+
const meta: string[] = [];
|
|
2011
|
+
if (item.tracked) meta.push("↑");
|
|
2012
|
+
if (item.isMain) meta.push("[primary]");
|
|
2013
|
+
if (item.pathState !== "ok") meta.push(item.pathState);
|
|
2014
|
+
if (item.wt.locked)
|
|
2015
|
+
meta.push(item.wt.lockedReason ? `locked:${item.wt.lockedReason}` : "locked");
|
|
2016
|
+
const suffix = meta.length > 0 ? ` ${meta.join(" ")}` : "";
|
|
2017
|
+
return ` ${item.branch}${dirty}${suffix} ${collapsePath(item.wt.path)}`;
|
|
2018
|
+
})
|
|
2019
|
+
.join("\n");
|
|
2020
|
+
|
|
2021
|
+
if (ctx.mode === "print") console.log(output);
|
|
2022
|
+
else ctx.ui.notify(output, "info");
|
|
2023
2023
|
return;
|
|
2024
2024
|
}
|
|
2025
2025
|
|
|
@@ -2114,14 +2114,14 @@ function parseSubcommand(args: string): { subcommand: Subcommand | null; rest: s
|
|
|
2114
2114
|
|
|
2115
2115
|
export default function worktreeExtension(pi: ExtensionAPI) {
|
|
2116
2116
|
for (const customType of [
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2117
|
+
MANUAL_OPEN_ENTRY_TYPE,
|
|
2118
|
+
RESTORE_STASH_ENTRY_TYPE,
|
|
2119
|
+
SCRIPT_RERUN_ENTRY_TYPE,
|
|
2120
2120
|
]) {
|
|
2121
|
-
pi.
|
|
2122
|
-
const details =
|
|
2121
|
+
pi.registerEntryRenderer<CommandMessageDetails>(customType, (entry, _options, theme) => {
|
|
2122
|
+
const details = entry.data;
|
|
2123
2123
|
if (!details || typeof details.intro !== "string" || typeof details.command !== "string") {
|
|
2124
|
-
return
|
|
2124
|
+
return undefined;
|
|
2125
2125
|
}
|
|
2126
2126
|
|
|
2127
2127
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tau-coding-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Opinionated Pi package with web search, review workflows, sandboxing, memory, usage reporting, skills, and a theme",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-tools",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"type": "module",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@anthropic-ai/sandbox-runtime": "^0.0.
|
|
30
|
+
"@anthropic-ai/sandbox-runtime": "^0.0.67",
|
|
31
31
|
"@mozilla/readability": "^0.6.0",
|
|
32
32
|
"jsdom": "^29.0.2",
|
|
33
33
|
"puppeteer-core": "^24.40.0",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"extensions/git-diff-stats.ts",
|
|
54
54
|
"extensions/git-pr-status.ts",
|
|
55
55
|
"extensions/insights.ts",
|
|
56
|
-
"extensions/
|
|
56
|
+
"extensions/stash.ts",
|
|
57
57
|
"extensions/loop.ts",
|
|
58
58
|
"extensions/memory.ts",
|
|
59
59
|
"extensions/notify.ts",
|
|
60
60
|
"extensions/openai-fast.ts",
|
|
61
61
|
"extensions/openai-verbosity.ts",
|
|
62
|
-
"extensions/review.ts",
|
|
62
|
+
"extensions/review/index.ts",
|
|
63
63
|
"extensions/sandbox/index.ts",
|
|
64
64
|
"extensions/tool-display-mode.ts",
|
|
65
65
|
"extensions/usage/index.ts",
|
|
@@ -12,6 +12,7 @@ Use this skill to reimplement the current branch on a new branch with a clean, n
|
|
|
12
12
|
1. **Validate the source branch**
|
|
13
13
|
- Ensure no uncommitted changes or merge conflicts
|
|
14
14
|
- Confirm it is up to date with `main`
|
|
15
|
+
- Confirm `main` is up to date with `origin/main`
|
|
15
16
|
|
|
16
17
|
2. **Analyze the diff**
|
|
17
18
|
- Study all changes between source branch and `main`
|
|
@@ -11,7 +11,7 @@ Use this skill as the playbook for producing reviewable commits and a clean, con
|
|
|
11
11
|
|
|
12
12
|
1. Check what files have changed
|
|
13
13
|
2. If there are no changes to commit, inform the user and stop
|
|
14
|
-
3. If there are unstaged changes, stage
|
|
14
|
+
3. If there are unstaged changes, stage relevant hunks or files with `git add -p` or `git add`
|
|
15
15
|
|
|
16
16
|
Rules:
|
|
17
17
|
|
|
@@ -21,7 +21,7 @@ Rules:
|
|
|
21
21
|
|
|
22
22
|
## Phase 2: Generate commit message
|
|
23
23
|
|
|
24
|
-
1. Remember commit
|
|
24
|
+
1. Remember commit message best practices
|
|
25
25
|
2. Write a short subject line:
|
|
26
26
|
- Up to 50 chars
|
|
27
27
|
- Start subject with a capital letter, don’t end with a period
|
package/skills/oracle/SKILL.md
CHANGED
|
@@ -43,14 +43,14 @@ Run commands from this skill directory:
|
|
|
43
43
|
./scripts/oracle-bundle -p "<task>" --file "src/**" --file "!**/*.test.*"
|
|
44
44
|
|
|
45
45
|
# Show which oracle model would be selected
|
|
46
|
-
./scripts/oracle --list-models --current openai-codex/gpt-5.
|
|
46
|
+
./scripts/oracle --list-models --current openai-codex/gpt-5.6-sol
|
|
47
47
|
|
|
48
48
|
# Ask the automatically selected oracle model
|
|
49
|
-
./scripts/oracle --current openai-codex/gpt-5.
|
|
49
|
+
./scripts/oracle --current openai-codex/gpt-5.6-sol \
|
|
50
50
|
-p "<task>" --file "src/**" --file "!**/*.test.*"
|
|
51
51
|
|
|
52
52
|
# Override the oracle model when the automatic choice is wrong
|
|
53
|
-
./scripts/oracle --model
|
|
53
|
+
./scripts/oracle --model anthropic/claude-fable-5 \
|
|
54
54
|
-p "<task>" --file "src/**" --file "!**/*.test.*"
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
4
|
import path from "node:path";
|
|
5
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
|
-
const THINKING_LEVEL = "
|
|
8
|
+
const THINKING_LEVEL = "max";
|
|
9
9
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const BARE_PI_ARGS = [
|
|
11
|
+
"-p",
|
|
12
|
+
"--no-session",
|
|
13
|
+
"--no-tools",
|
|
14
|
+
"--no-extensions",
|
|
15
|
+
"--no-skills",
|
|
16
|
+
"--no-prompt-templates",
|
|
17
|
+
"--no-themes",
|
|
18
|
+
];
|
|
10
19
|
|
|
11
20
|
const FAMILY_ORDER = {
|
|
12
21
|
openai: ["anthropic", "google", "openai"],
|
|
@@ -47,11 +56,15 @@ if (bundle.status !== 0) {
|
|
|
47
56
|
process.exit(bundle.status ?? 1);
|
|
48
57
|
}
|
|
49
58
|
|
|
50
|
-
const pi = spawnSync(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
const pi = spawnSync(
|
|
60
|
+
"pi",
|
|
61
|
+
[...BARE_PI_ARGS, "--model", oracleModel, "--thinking", THINKING_LEVEL],
|
|
62
|
+
{
|
|
63
|
+
cwd: repoRoot(),
|
|
64
|
+
input: bundle.stdout,
|
|
65
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
66
|
+
},
|
|
67
|
+
);
|
|
55
68
|
|
|
56
69
|
if (pi.error) die(pi.error.message);
|
|
57
70
|
process.exit(pi.status ?? 1);
|
|
@@ -75,7 +88,12 @@ function parseArgs(argv) {
|
|
|
75
88
|
}
|
|
76
89
|
}
|
|
77
90
|
|
|
78
|
-
return {
|
|
91
|
+
return {
|
|
92
|
+
bundleArgs,
|
|
93
|
+
currentModel: currentModel ? normalizeModelSpec(currentModel) : undefined,
|
|
94
|
+
model,
|
|
95
|
+
listModels,
|
|
96
|
+
};
|
|
79
97
|
}
|
|
80
98
|
|
|
81
99
|
function requireValue(argv, index, flag) {
|
|
@@ -85,8 +103,7 @@ function requireValue(argv, index, flag) {
|
|
|
85
103
|
}
|
|
86
104
|
|
|
87
105
|
function readSettings() {
|
|
88
|
-
const
|
|
89
|
-
const settingsPath = path.join(agentDir, "settings.json");
|
|
106
|
+
const settingsPath = path.join(getAgentDir(), "settings.json");
|
|
90
107
|
if (!existsSync(settingsPath)) return {};
|
|
91
108
|
|
|
92
109
|
try {
|
|
@@ -127,82 +144,86 @@ function selectOracleModel(models, enabledModels, currentFamily) {
|
|
|
127
144
|
|
|
128
145
|
function bestModelForFamily(models, enabledModels, family) {
|
|
129
146
|
return models
|
|
130
|
-
.
|
|
131
|
-
.
|
|
132
|
-
.sort((a, b) => b.score - a.score || a.model.spec.localeCompare(b.model.spec))[0]?.model;
|
|
147
|
+
.filter((model) => modelFamily(model.spec) === family)
|
|
148
|
+
.sort((left, right) => compareModels(left, right, enabledModels, family))[0];
|
|
133
149
|
}
|
|
134
150
|
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
151
|
+
function compareModels(left, right, enabledModels, family) {
|
|
152
|
+
return (
|
|
153
|
+
Number(enabledModels.has(normalizeModelSpec(right.spec))) -
|
|
154
|
+
Number(enabledModels.has(normalizeModelSpec(left.spec))) ||
|
|
155
|
+
modelRank(right.model, family) - modelRank(left.model, family) ||
|
|
156
|
+
providerRank(right.provider, family) - providerRank(left.provider, family) ||
|
|
157
|
+
left.spec.localeCompare(right.spec)
|
|
158
|
+
);
|
|
142
159
|
}
|
|
143
160
|
|
|
144
|
-
function
|
|
161
|
+
function providerRank(provider, family) {
|
|
145
162
|
const normalized = provider.toLowerCase();
|
|
146
163
|
if (family === "openai") {
|
|
147
|
-
if (normalized === "openai-codex") return
|
|
148
|
-
if (normalized === "openai") return
|
|
149
|
-
if (normalized === "github-copilot") return
|
|
150
|
-
if (normalized.includes("azure")) return
|
|
151
|
-
if (normalized === "openrouter") return
|
|
152
|
-
return
|
|
164
|
+
if (normalized === "openai-codex") return 5;
|
|
165
|
+
if (normalized === "openai") return 4;
|
|
166
|
+
if (normalized === "github-copilot") return 3;
|
|
167
|
+
if (normalized.includes("azure")) return 2;
|
|
168
|
+
if (normalized === "openrouter") return 1;
|
|
169
|
+
return 0;
|
|
153
170
|
}
|
|
154
171
|
|
|
155
172
|
if (family === "anthropic") {
|
|
156
|
-
if (normalized === "github-copilot") return
|
|
157
|
-
if (normalized === "anthropic") return
|
|
158
|
-
if (normalized === "amazon-bedrock") return
|
|
159
|
-
if (normalized === "openrouter") return
|
|
160
|
-
return
|
|
173
|
+
if (normalized === "github-copilot") return 4;
|
|
174
|
+
if (normalized === "anthropic") return 3;
|
|
175
|
+
if (normalized === "amazon-bedrock") return 2;
|
|
176
|
+
if (normalized === "openrouter") return 1;
|
|
177
|
+
return 0;
|
|
161
178
|
}
|
|
162
179
|
|
|
163
180
|
if (family === "google") {
|
|
164
|
-
if (normalized === "google") return
|
|
165
|
-
if (normalized === "github-copilot") return
|
|
166
|
-
if (normalized.includes("vertex")) return
|
|
167
|
-
if (normalized === "openrouter") return
|
|
168
|
-
return
|
|
181
|
+
if (normalized === "google") return 4;
|
|
182
|
+
if (normalized === "github-copilot") return 3;
|
|
183
|
+
if (normalized.includes("vertex")) return 2;
|
|
184
|
+
if (normalized === "openrouter") return 1;
|
|
185
|
+
return 0;
|
|
169
186
|
}
|
|
170
187
|
|
|
171
188
|
return 0;
|
|
172
189
|
}
|
|
173
190
|
|
|
174
|
-
function
|
|
191
|
+
function modelRank(model, family) {
|
|
175
192
|
const normalized = model.toLowerCase();
|
|
176
193
|
|
|
177
194
|
if (family === "openai") {
|
|
178
|
-
if (/gpt-5\.
|
|
179
|
-
if (/gpt-5\.
|
|
180
|
-
if (/gpt-5\.
|
|
181
|
-
if (/gpt-5\.
|
|
182
|
-
if (/gpt-5\.
|
|
183
|
-
if (/gpt-5\.
|
|
184
|
-
if (/gpt-5/.test(normalized)) return
|
|
185
|
-
if (/gpt-
|
|
186
|
-
return
|
|
195
|
+
if (/gpt-5\.6[-_.]sol/.test(normalized)) return 10;
|
|
196
|
+
if (/gpt-5\.6/.test(normalized)) return 9;
|
|
197
|
+
if (/gpt-5\.5/.test(normalized)) return 8;
|
|
198
|
+
if (/gpt-5\.4/.test(normalized)) return 7;
|
|
199
|
+
if (/gpt-5\.3.*codex/.test(normalized)) return 6;
|
|
200
|
+
if (/gpt-5\.3/.test(normalized)) return 5;
|
|
201
|
+
if (/gpt-5\.2/.test(normalized)) return 4;
|
|
202
|
+
if (/gpt-5\.1/.test(normalized)) return 3;
|
|
203
|
+
if (/gpt-5/.test(normalized)) return 2;
|
|
204
|
+
if (/gpt-4\.1/.test(normalized)) return 1;
|
|
205
|
+
return 0;
|
|
187
206
|
}
|
|
188
207
|
|
|
189
208
|
if (family === "anthropic") {
|
|
190
|
-
if (/claude[-_.]
|
|
191
|
-
if (/claude[-_.]opus[-_.]4[-_.]
|
|
192
|
-
if (/claude[-_.]opus[-_.]4[-_.]
|
|
193
|
-
if (/claude[-_.]opus[-_.]4[-_.]
|
|
194
|
-
if (/claude[-_.]opus[-_.]4/.test(normalized)) return
|
|
195
|
-
if (/claude[-_.]
|
|
196
|
-
if (/claude[-_.]
|
|
197
|
-
if (/claude[-_.]sonnet[-_.]4/.test(normalized)) return
|
|
198
|
-
return
|
|
209
|
+
if (/claude[-_.]fable[-_.]5/.test(normalized)) return 10;
|
|
210
|
+
if (/claude[-_.]opus[-_.]4[-_.]8/.test(normalized)) return 9;
|
|
211
|
+
if (/claude[-_.]opus[-_.]4[-_.]7/.test(normalized)) return 8;
|
|
212
|
+
if (/claude[-_.]opus[-_.]4[-_.]6/.test(normalized)) return 7;
|
|
213
|
+
if (/claude[-_.]opus[-_.]4[-_.]5/.test(normalized)) return 6;
|
|
214
|
+
if (/claude[-_.]opus[-_.]4[-_.]1/.test(normalized)) return 5;
|
|
215
|
+
if (/claude[-_.]opus[-_.]4/.test(normalized)) return 4;
|
|
216
|
+
if (/claude[-_.]sonnet[-_.]4[-_.]6/.test(normalized)) return 3;
|
|
217
|
+
if (/claude[-_.]sonnet[-_.]4[-_.]5/.test(normalized)) return 2;
|
|
218
|
+
if (/claude[-_.]sonnet[-_.]4/.test(normalized)) return 1;
|
|
219
|
+
return 0;
|
|
199
220
|
}
|
|
200
221
|
|
|
201
222
|
if (family === "google") {
|
|
202
|
-
if (/gemini[-_.]3\.1[-_.]pro/.test(normalized)) return
|
|
203
|
-
if (/gemini[-_.]3[-_.]pro/.test(normalized)) return
|
|
204
|
-
if (/gemini[-_.]2\.5[-_.]pro/.test(normalized)) return
|
|
205
|
-
return
|
|
223
|
+
if (/gemini[-_.]3\.1[-_.]pro/.test(normalized)) return 3;
|
|
224
|
+
if (/gemini[-_.]3[-_.]pro/.test(normalized)) return 2;
|
|
225
|
+
if (/gemini[-_.]2\.5[-_.]pro/.test(normalized)) return 1;
|
|
226
|
+
return 0;
|
|
206
227
|
}
|
|
207
228
|
|
|
208
229
|
return 0;
|
|
@@ -218,7 +239,7 @@ function modelFamily(spec) {
|
|
|
218
239
|
}
|
|
219
240
|
|
|
220
241
|
function normalizeModelSpec(spec) {
|
|
221
|
-
return String(spec).trim().replace(/:(off|minimal|low|medium|high|xhigh)$/i, "");
|
|
242
|
+
return String(spec).trim().replace(/:(off|minimal|low|medium|high|xhigh|max)$/i, "");
|
|
222
243
|
}
|
|
223
244
|
|
|
224
245
|
function printModelPlan(models, enabledModels, currentModel, currentFamily) {
|
package/themes/tau-dark.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://raw.githubusercontent.com/
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
3
|
"name": "tau-dark",
|
|
4
4
|
"vars": {
|
|
5
5
|
"accent": "#8abeb7",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"thinkingMedium": "#6f9bb3",
|
|
75
75
|
"thinkingHigh": "#78a8a1",
|
|
76
76
|
"thinkingXhigh": "accent",
|
|
77
|
+
"thinkingMax": "#a8d8d1",
|
|
77
78
|
|
|
78
79
|
"bashMode": "green"
|
|
79
80
|
},
|