1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
/**
* Tool registrations for pi-browser-harness.
*
* Maps browser-harness daemon methods to pi tools with TypeBox schemas,
* prompt snippets, guidelines, and TUI renderers.
*/
import {
DEFAULT_MAX_BYTES,
DEFAULT_MAX_LINES,
formatSize,
truncateHead,
withFileMutationQueue,
} from "@mariozechner/pi-coding-agent";
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Text } from "@mariozechner/pi-tui";
import { Type } from "typebox";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { BrowserDaemon } from "./daemon";
import type { TabInfo } from "./protocol";
// ── AsyncFunction constructor ────────────────────────────────────────────────
const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor as new (...args: string[]) => (...args: unknown[]) => Promise<unknown>;
// ── Key code map ─────────────────────────────────────────────────────────────
const SPECIAL_KEYS = [
"Enter", "Tab", "Backspace", "Escape", "Delete",
"ArrowLeft", "ArrowUp", "ArrowRight", "ArrowDown",
"Home", "End", "PageUp", "PageDown", " ",
];
// ── Screenshot path ──────────────────────────────────────────────────────────
let screenshotCounter = 0;
function nextScreenshotPath(): string {
screenshotCounter++;
return join(tmpdir(), `pi-browser-screenshot-${Date.now()}-${screenshotCounter}.png`);
}
// ── Output truncation ────────────────────────────────────────────────────────
/** Track temp directories for cleanup on session shutdown. */
const tempDirs: string[] = [];
/** Best-effort cleanup of all tracked temp directories. */
export async function cleanupTempDirs(): Promise<void> {
const dirs = tempDirs.splice(0);
await Promise.all(
dirs.map((d) => rm(d, { recursive: true, force: true }).catch(() => {})),
);
}
/**
* Truncate output to pi's built-in limits (50KB / 2000 lines).
* When truncated, writes the full output to a temp file so the LLM
* can read it via the read tool and so CTRL+O expand can show it.
*/
async function applyTruncation(
output: string,
prefix: string,
): Promise<{
text: string;
fullOutputPath?: string;
wasTruncated: boolean;
}> {
const truncation = truncateHead(output, {
maxLines: DEFAULT_MAX_LINES,
maxBytes: DEFAULT_MAX_BYTES,
});
if (!truncation.truncated) {
return { text: truncation.content, wasTruncated: false };
}
const tempDir = await mkdtemp(join(tmpdir(), `pi-bh-${prefix}-`));
tempDirs.push(tempDir);
const tempFile = join(tempDir, "output.txt");
await withFileMutationQueue(tempFile, async () => {
await writeFile(tempFile, output, "utf8");
});
const omittedBytes = truncation.totalBytes - truncation.outputBytes;
let text = truncation.content;
text += `\n\n[Output truncated: ${truncation.outputLines} of ${truncation.totalLines} lines`;
text += ` (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}).`;
text += ` ${formatSize(omittedBytes)} omitted. Full output: ${tempFile}]`;
return { text, fullOutputPath: tempFile, wasTruncated: true };
}
// ── Registration ─────────────────────────────────────────────────────────────
// All tools migrated to src/domains/* in v0.3 — see registerAllTools.
export function registerTools(_pi: ExtensionAPI, _daemon: BrowserDaemon): void {
// intentionally empty
}
|