tau-coding-agent 0.1.4 → 0.1.5
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 +48 -42
- package/extensions/branch-term.ts +8 -13
- package/extensions/btw.ts +4 -4
- package/extensions/ghostty.ts +64 -11
- package/extensions/insights.ts +4 -3
- package/extensions/loop.ts +37 -9
- package/extensions/memory.ts +5 -10
- 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 +66 -0
- package/extensions/review/submit-triage-tool.ts +59 -0
- package/extensions/review/triage.ts +746 -0
- package/extensions/sandbox/index.ts +399 -102
- package/extensions/{interlude.ts → stash.ts} +13 -11
- package/extensions/tool-display-mode.ts +2 -3
- package/extensions/usage/index.ts +8 -4
- 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/worktree.ts +33 -33
- package/package.json +3 -3
- package/skills/git-clean-history/SKILL.md +1 -0
- package/skills/git-commit/SKILL.md +2 -2
- package/skills/oracle/scripts/oracle +28 -11
- package/themes/tau-dark.json +2 -1
- package/extensions/review.ts +0 -4420
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
2
|
import path from "node:path";
|
|
4
|
-
import
|
|
3
|
+
import {
|
|
4
|
+
getAgentDir,
|
|
5
|
+
type ExtensionAPI,
|
|
6
|
+
type ExtensionContext,
|
|
7
|
+
} from "@earendil-works/pi-coding-agent";
|
|
5
8
|
|
|
6
9
|
const DEFAULT_SHORTCUTS = ["alt+x"] as const;
|
|
7
|
-
const STATUS_KEY = "
|
|
8
|
-
const KEYBINDINGS_PATH = path.join(
|
|
10
|
+
const STATUS_KEY = "stash";
|
|
11
|
+
const KEYBINDINGS_PATH = path.join(getAgentDir(), "keybindings.json");
|
|
9
12
|
|
|
10
13
|
type ShortcutConfig = string | string[] | undefined;
|
|
11
14
|
|
|
12
15
|
type KeybindingsConfig = {
|
|
13
|
-
|
|
16
|
+
stash?: ShortcutConfig;
|
|
14
17
|
[key: string]: unknown;
|
|
15
18
|
};
|
|
16
19
|
|
|
@@ -37,9 +40,9 @@ function normalizeShortcuts(value: ShortcutConfig): string[] {
|
|
|
37
40
|
return normalized.length > 0 ? [...new Set(normalized)] : [...DEFAULT_SHORTCUTS];
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
export default function
|
|
43
|
+
export default function stashExtension(pi: ExtensionAPI): void {
|
|
41
44
|
const keybindings = readKeybindings(KEYBINDINGS_PATH);
|
|
42
|
-
const shortcuts = normalizeShortcuts(keybindings.
|
|
45
|
+
const shortcuts = normalizeShortcuts(keybindings.stash);
|
|
43
46
|
|
|
44
47
|
let stashedDraft: string | null = null;
|
|
45
48
|
let armed = false;
|
|
@@ -52,7 +55,7 @@ export default function interludeExtension(pi: ExtensionAPI): void {
|
|
|
52
55
|
if (!ctx.hasUI) return;
|
|
53
56
|
|
|
54
57
|
if (armed && stashedDraft !== null) {
|
|
55
|
-
ctx.ui.setStatus(STATUS_KEY, `
|
|
58
|
+
ctx.ui.setStatus(STATUS_KEY, `stash (${shortcutLabel()})`);
|
|
56
59
|
return;
|
|
57
60
|
}
|
|
58
61
|
|
|
@@ -80,7 +83,7 @@ export default function interludeExtension(pi: ExtensionAPI): void {
|
|
|
80
83
|
|
|
81
84
|
if (armed && stashedDraft !== null) {
|
|
82
85
|
restoreDraft(ctx);
|
|
83
|
-
ctx.ui.notify("
|
|
86
|
+
ctx.ui.notify("Stashed draft restored", "info");
|
|
84
87
|
return;
|
|
85
88
|
}
|
|
86
89
|
|
|
@@ -101,8 +104,7 @@ export default function interludeExtension(pi: ExtensionAPI): void {
|
|
|
101
104
|
|
|
102
105
|
for (const shortcut of shortcuts) {
|
|
103
106
|
pi.registerShortcut(shortcut as never, {
|
|
104
|
-
description:
|
|
105
|
-
"Stash the current message draft, send one interlude message, then restore the draft",
|
|
107
|
+
description: "Stash the current message draft, send one message, then restore it",
|
|
106
108
|
handler: async (ctx) => {
|
|
107
109
|
stashOrRestore(ctx);
|
|
108
110
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import os from "node:os";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import {
|
|
4
|
+
getAgentDir,
|
|
5
5
|
type AppKeybinding,
|
|
6
6
|
CustomEditor,
|
|
7
7
|
createBashToolDefinition,
|
|
@@ -77,8 +77,7 @@ function emptyConfig(): ToolDisplayModeConfig {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
function getConfigPath(): string {
|
|
80
|
-
|
|
81
|
-
return path.join(agentDir, CONFIG_FILE);
|
|
80
|
+
return path.join(getAgentDir(), CONFIG_FILE);
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
function isObject(value: unknown): value is JsonObject {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
BorderedLoader,
|
|
3
|
+
getAgentDir,
|
|
4
|
+
type ExtensionAPI,
|
|
5
|
+
type ExtensionContext,
|
|
6
|
+
} from "@earendil-works/pi-coding-agent";
|
|
3
7
|
import {
|
|
4
8
|
Key,
|
|
5
9
|
matchesKey,
|
|
@@ -31,7 +35,7 @@ import type {
|
|
|
31
35
|
} from "./types.js";
|
|
32
36
|
|
|
33
37
|
const ALL_TAB_ID = "all";
|
|
34
|
-
const SESSION_ROOT = path.join(
|
|
38
|
+
const SESSION_ROOT = path.join(getAgentDir(), "sessions");
|
|
35
39
|
const RANGE_DAYS = [7, 30, 90] as const;
|
|
36
40
|
const FILE_PARSE_CONCURRENCY = 16;
|
|
37
41
|
|
|
@@ -2205,7 +2209,7 @@ export default function usageBreakdownExtension(pi: ExtensionAPI) {
|
|
|
2205
2209
|
handler: async (_args, ctx: ExtensionContext) => {
|
|
2206
2210
|
const liveUsageAvailability = await getLiveUsageAvailabilityMap(ctx);
|
|
2207
2211
|
|
|
2208
|
-
if (
|
|
2212
|
+
if (ctx.mode !== "tui") {
|
|
2209
2213
|
const [data, liveUsage] = await Promise.all([
|
|
2210
2214
|
computeBreakdown(liveUsageAvailability, undefined),
|
|
2211
2215
|
fetchLiveUsage(SUPPORTED_PROVIDERS, liveUsageAvailability, ctx, undefined),
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createDecipheriv, createHash,
|
|
3
|
-
import {
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { createDecipheriv, createHash, pbkdf2 } from "node:crypto";
|
|
3
|
+
import { readdir } from "node:fs/promises";
|
|
4
4
|
import { DatabaseSync } from "node:sqlite";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import { promisify } from "node:util";
|
|
7
8
|
|
|
8
9
|
import type { BrowserCookie, BrowserProfile } from "../types.js";
|
|
10
|
+
import { pathExists } from "./fs.js";
|
|
9
11
|
import { withSqliteSnapshot } from "./sqlite.js";
|
|
10
12
|
|
|
11
13
|
const MACOS_BROWSER_CONFIGS: ChromiumBrowserConfig[] = [
|
|
@@ -72,6 +74,8 @@ interface ChromiumBrowserConfig {
|
|
|
72
74
|
secretToolApp?: string;
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
const execFileAsync = promisify(execFile);
|
|
78
|
+
const pbkdf2Async = promisify(pbkdf2);
|
|
75
79
|
const passwordCache = new Map<string, string | null>();
|
|
76
80
|
|
|
77
81
|
function browserConfigs(): ChromiumBrowserConfig[] {
|
|
@@ -80,13 +84,15 @@ function browserConfigs(): ChromiumBrowserConfig[] {
|
|
|
80
84
|
return [];
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
export function discoverChromiumProfiles(
|
|
87
|
+
export async function discoverChromiumProfiles(
|
|
88
|
+
preferredProfileName?: string,
|
|
89
|
+
): Promise<BrowserProfile[]> {
|
|
84
90
|
const profiles: BrowserProfile[] = [];
|
|
85
91
|
|
|
86
92
|
for (const config of browserConfigs()) {
|
|
87
|
-
if (!
|
|
93
|
+
if (!(await pathExists(config.baseDir))) continue;
|
|
88
94
|
|
|
89
|
-
const candidates = ["Default", ...discoverProfileDirectories(config.baseDir)];
|
|
95
|
+
const candidates = ["Default", ...(await discoverProfileDirectories(config.baseDir))];
|
|
90
96
|
const seen = new Set<string>();
|
|
91
97
|
for (const directoryName of candidates) {
|
|
92
98
|
if (!directoryName || seen.has(directoryName)) continue;
|
|
@@ -94,7 +100,7 @@ export function discoverChromiumProfiles(preferredProfileName?: string): Browser
|
|
|
94
100
|
|
|
95
101
|
const profilePath = path.join(config.baseDir, directoryName);
|
|
96
102
|
const cookiesPath = path.join(profilePath, "Cookies");
|
|
97
|
-
if (!
|
|
103
|
+
if (!(await pathExists(cookiesPath))) continue;
|
|
98
104
|
|
|
99
105
|
profiles.push({
|
|
100
106
|
family: "chromium",
|
|
@@ -136,9 +142,9 @@ function isPreferredChromiumProfile(
|
|
|
136
142
|
return profile.profileName === preferredProfileName;
|
|
137
143
|
}
|
|
138
144
|
|
|
139
|
-
function discoverProfileDirectories(baseDir: string): string[] {
|
|
145
|
+
async function discoverProfileDirectories(baseDir: string): Promise<string[]> {
|
|
140
146
|
try {
|
|
141
|
-
return
|
|
147
|
+
return (await readdir(baseDir, { withFileTypes: true }))
|
|
142
148
|
.filter((entry) => entry.isDirectory())
|
|
143
149
|
.map((entry) => entry.name)
|
|
144
150
|
.filter((name) => name === "Default" || /^Profile\s+\d+$/.test(name));
|
|
@@ -147,27 +153,28 @@ function discoverProfileDirectories(baseDir: string): string[] {
|
|
|
147
153
|
}
|
|
148
154
|
}
|
|
149
155
|
|
|
150
|
-
export function loadChromiumCookies(profile: BrowserProfile): BrowserCookie[] {
|
|
156
|
+
export async function loadChromiumCookies(profile: BrowserProfile): Promise<BrowserCookie[]> {
|
|
151
157
|
const browserConfig = browserConfigs().find((config) =>
|
|
152
158
|
profile.profilePath.startsWith(config.baseDir),
|
|
153
159
|
);
|
|
154
160
|
if (!browserConfig) return [];
|
|
155
161
|
|
|
156
|
-
const
|
|
162
|
+
const cookiesPath = path.join(profile.profilePath, "Cookies");
|
|
163
|
+
if (!(await pathExists(cookiesPath))) return [];
|
|
164
|
+
|
|
165
|
+
const password = await readBrowserPassword(browserConfig);
|
|
157
166
|
if (!password) return [];
|
|
158
167
|
|
|
159
|
-
const keyMaterial =
|
|
168
|
+
const keyMaterial = await pbkdf2Async(
|
|
160
169
|
password,
|
|
161
170
|
"saltysalt",
|
|
162
171
|
process.platform === "darwin" ? 1003 : 1,
|
|
163
172
|
16,
|
|
164
173
|
"sha1",
|
|
165
174
|
);
|
|
166
|
-
const cookiesPath = path.join(profile.profilePath, "Cookies");
|
|
167
|
-
if (!existsSync(cookiesPath)) return [];
|
|
168
175
|
|
|
169
176
|
try {
|
|
170
|
-
return withSqliteSnapshot(cookiesPath, "websearch-chromium", (tempDbPath) => {
|
|
177
|
+
return await withSqliteSnapshot(cookiesPath, "websearch-chromium", (tempDbPath) => {
|
|
171
178
|
const db = new DatabaseSync(tempDbPath, { readOnly: true });
|
|
172
179
|
try {
|
|
173
180
|
const rows = db
|
|
@@ -277,7 +284,7 @@ function stripHostKeyDigest(buffer: Buffer, hostKey: string | null): Buffer {
|
|
|
277
284
|
return buffer.subarray(0, 32).equals(digest) ? buffer.subarray(32) : buffer;
|
|
278
285
|
}
|
|
279
286
|
|
|
280
|
-
function readBrowserPassword(config: ChromiumBrowserConfig): string | null {
|
|
287
|
+
async function readBrowserPassword(config: ChromiumBrowserConfig): Promise<string | null> {
|
|
281
288
|
const cacheKey = `${config.name}:${config.baseDir}`;
|
|
282
289
|
if (passwordCache.has(cacheKey)) {
|
|
283
290
|
return passwordCache.get(cacheKey) ?? null;
|
|
@@ -286,9 +293,13 @@ function readBrowserPassword(config: ChromiumBrowserConfig): string | null {
|
|
|
286
293
|
let password: string | null = null;
|
|
287
294
|
if (process.platform === "darwin") {
|
|
288
295
|
if (!config.keychainAccount || !config.keychainService) return null;
|
|
289
|
-
password = readMacKeychainPassword(
|
|
296
|
+
password = await readMacKeychainPassword(
|
|
297
|
+
config.name,
|
|
298
|
+
config.keychainAccount,
|
|
299
|
+
config.keychainService,
|
|
300
|
+
);
|
|
290
301
|
} else if (process.platform === "linux") {
|
|
291
|
-
password = readLinuxPassword(config.secretToolApp);
|
|
302
|
+
password = await readLinuxPassword(config.secretToolApp);
|
|
292
303
|
}
|
|
293
304
|
|
|
294
305
|
if (password) {
|
|
@@ -297,33 +308,33 @@ function readBrowserPassword(config: ChromiumBrowserConfig): string | null {
|
|
|
297
308
|
return password;
|
|
298
309
|
}
|
|
299
310
|
|
|
300
|
-
function readMacKeychainPassword(
|
|
311
|
+
async function readMacKeychainPassword(
|
|
301
312
|
browserName: string,
|
|
302
313
|
account: string,
|
|
303
314
|
service: string,
|
|
304
|
-
): string | null {
|
|
315
|
+
): Promise<string | null> {
|
|
305
316
|
try {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}).trim() || null
|
|
317
|
+
const { stdout } = await execFileAsync(
|
|
318
|
+
"security",
|
|
319
|
+
["find-generic-password", "-w", "-a", account, "-s", service],
|
|
320
|
+
{ encoding: "utf8" },
|
|
311
321
|
);
|
|
322
|
+
return stdout.trim() || null;
|
|
312
323
|
} catch {
|
|
313
324
|
throw new Error(`Could not read ${browserName} cookie decryption password from Keychain.`);
|
|
314
325
|
}
|
|
315
326
|
}
|
|
316
327
|
|
|
317
|
-
function readLinuxPassword(secretToolApp?: string): string | null {
|
|
328
|
+
async function readLinuxPassword(secretToolApp?: string): Promise<string | null> {
|
|
318
329
|
if (!secretToolApp) return "peanuts";
|
|
319
330
|
|
|
320
331
|
try {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}).trim() || "peanuts"
|
|
332
|
+
const { stdout } = await execFileAsync(
|
|
333
|
+
"secret-tool",
|
|
334
|
+
["lookup", "application", secretToolApp],
|
|
335
|
+
{ encoding: "utf8" },
|
|
326
336
|
);
|
|
337
|
+
return stdout.trim() || "peanuts";
|
|
327
338
|
} catch {
|
|
328
339
|
return "peanuts";
|
|
329
340
|
}
|
|
@@ -11,32 +11,34 @@ import type {
|
|
|
11
11
|
const COOKIE_CACHE_TTL_MS = 30_000;
|
|
12
12
|
const cookieCache = new Map<string, { cookies: BrowserCookie[]; expiresAt: number }>();
|
|
13
13
|
|
|
14
|
-
export function discoverProfiles(
|
|
14
|
+
export async function discoverProfiles(
|
|
15
15
|
browserFamilies: WebsearchBrowserFamily[],
|
|
16
16
|
preferredProfiles: WebsearchConfig["profiles"],
|
|
17
|
-
): BrowserProfile[] {
|
|
17
|
+
): Promise<BrowserProfile[]> {
|
|
18
18
|
const profiles: BrowserProfile[] = [];
|
|
19
19
|
|
|
20
20
|
for (const family of browserFamilies) {
|
|
21
21
|
if (family === "firefox") {
|
|
22
|
-
profiles.push(...discoverFirefoxProfiles(preferredProfiles.firefox));
|
|
22
|
+
profiles.push(...(await discoverFirefoxProfiles(preferredProfiles.firefox)));
|
|
23
23
|
continue;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
profiles.push(...discoverChromiumProfiles(preferredProfiles.chromium));
|
|
26
|
+
profiles.push(...(await discoverChromiumProfiles(preferredProfiles.chromium)));
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
return profiles;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export function loadCookies(profile: BrowserProfile): BrowserCookie[] {
|
|
32
|
+
export async function loadCookies(profile: BrowserProfile): Promise<BrowserCookie[]> {
|
|
33
33
|
const cacheKey = `${profile.family}:${profile.profilePath}`;
|
|
34
34
|
const cached = cookieCache.get(cacheKey);
|
|
35
35
|
if (cached && cached.expiresAt > Date.now()) return cached.cookies;
|
|
36
36
|
if (cached) cookieCache.delete(cacheKey);
|
|
37
37
|
|
|
38
38
|
const cookies =
|
|
39
|
-
profile.family === "firefox"
|
|
39
|
+
profile.family === "firefox"
|
|
40
|
+
? await loadFirefoxCookies(profile)
|
|
41
|
+
: await loadChromiumCookies(profile);
|
|
40
42
|
|
|
41
43
|
if (cookies.length > 0) {
|
|
42
44
|
cookieCache.set(cacheKey, {
|
|
@@ -48,11 +50,11 @@ export function loadCookies(profile: BrowserProfile): BrowserCookie[] {
|
|
|
48
50
|
return cookies;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
export function createBrowserSession(
|
|
53
|
+
export async function createBrowserSession(
|
|
52
54
|
profile: BrowserProfile,
|
|
53
55
|
domains: string[],
|
|
54
|
-
): BrowserSession | null {
|
|
55
|
-
const cookies = loadCookies(profile).filter((cookie) => {
|
|
56
|
+
): Promise<BrowserSession | null> {
|
|
57
|
+
const cookies = (await loadCookies(profile)).filter((cookie) => {
|
|
56
58
|
return domains.some((domain) => matchesBrowserDomain(domain, cookie.domain));
|
|
57
59
|
});
|
|
58
60
|
if (cookies.length === 0) return null;
|
|
@@ -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({
|
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));
|