@trusty-squire/mcp 1.1.34 → 1.1.35-rc.2

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.
Files changed (65) hide show
  1. package/dist/bin.js +15 -0
  2. package/dist/bin.js.map +1 -1
  3. package/dist/bot/act/act.d.ts.map +1 -1
  4. package/dist/bot/act/act.js +25 -4
  5. package/dist/bot/act/act.js.map +1 -1
  6. package/dist/bot/broker/mcp-socket.d.ts.map +1 -1
  7. package/dist/bot/broker/mcp-socket.js +5 -0
  8. package/dist/bot/broker/mcp-socket.js.map +1 -1
  9. package/dist/bot/broker/operator.d.ts.map +1 -1
  10. package/dist/bot/broker/operator.js +1 -0
  11. package/dist/bot/broker/operator.js.map +1 -1
  12. package/dist/bot/browser.d.ts +2 -1
  13. package/dist/bot/browser.d.ts.map +1 -1
  14. package/dist/bot/browser.js +74 -19
  15. package/dist/bot/browser.js.map +1 -1
  16. package/dist/bot/download-transfer.d.ts +12 -0
  17. package/dist/bot/download-transfer.d.ts.map +1 -0
  18. package/dist/bot/download-transfer.js +103 -0
  19. package/dist/bot/download-transfer.js.map +1 -0
  20. package/dist/bot/downloads.d.ts +64 -0
  21. package/dist/bot/downloads.d.ts.map +1 -0
  22. package/dist/bot/downloads.js +300 -0
  23. package/dist/bot/downloads.js.map +1 -0
  24. package/dist/bot/file-picker-route.d.ts +8 -0
  25. package/dist/bot/file-picker-route.d.ts.map +1 -0
  26. package/dist/bot/file-picker-route.js +94 -0
  27. package/dist/bot/file-picker-route.js.map +1 -0
  28. package/dist/bot/observe/observe.d.ts +1 -0
  29. package/dist/bot/observe/observe.d.ts.map +1 -1
  30. package/dist/bot/observe/observe.js +17 -0
  31. package/dist/bot/observe/observe.js.map +1 -1
  32. package/dist/bot/operate-drive.d.ts +4 -0
  33. package/dist/bot/operate-drive.d.ts.map +1 -1
  34. package/dist/bot/operate-drive.js +49 -2
  35. package/dist/bot/operate-drive.js.map +1 -1
  36. package/dist/bot/owned-pages.d.ts +2 -1
  37. package/dist/bot/owned-pages.d.ts.map +1 -1
  38. package/dist/bot/owned-pages.js +4 -1
  39. package/dist/bot/owned-pages.js.map +1 -1
  40. package/dist/bot/page-driver.d.ts +2 -0
  41. package/dist/bot/page-driver.d.ts.map +1 -1
  42. package/dist/bot/page-driver.js +4 -1
  43. package/dist/bot/page-driver.js.map +1 -1
  44. package/dist/bot/provision-session.d.ts +1 -0
  45. package/dist/bot/provision-session.d.ts.map +1 -1
  46. package/dist/bot/screenshot-click.d.ts +4 -2
  47. package/dist/bot/screenshot-click.d.ts.map +1 -1
  48. package/dist/bot/screenshot-click.js +22 -14
  49. package/dist/bot/screenshot-click.js.map +1 -1
  50. package/dist/bot/session/lifecycle.d.ts.map +1 -1
  51. package/dist/bot/session/lifecycle.js +2 -0
  52. package/dist/bot/session/lifecycle.js.map +1 -1
  53. package/dist/bot/upload-error.d.ts +11 -0
  54. package/dist/bot/upload-error.d.ts.map +1 -0
  55. package/dist/bot/upload-error.js +29 -0
  56. package/dist/bot/upload-error.js.map +1 -0
  57. package/dist/tools/provision-drive-schema.d.ts +1 -0
  58. package/dist/tools/provision-drive-schema.d.ts.map +1 -1
  59. package/dist/tools/provision-drive-schema.js +44 -5
  60. package/dist/tools/provision-drive-schema.js.map +1 -1
  61. package/dist/tools/provision-drive.d.ts +67 -4
  62. package/dist/tools/provision-drive.d.ts.map +1 -1
  63. package/dist/tools/provision-drive.js +160 -8
  64. package/dist/tools/provision-drive.js.map +1 -1
  65. package/package.json +1 -1
@@ -0,0 +1,12 @@
1
+ import { type Socket } from "node:net";
2
+ export declare const DOWNLOAD_HANDLE_PREFIX = "trusty-squire-download ";
3
+ /** Broker side: stream one redeemed handle, then close the socket. */
4
+ export declare function serveDownloadHandle(socket: Socket, handle: string): void;
5
+ export interface FetchedDownload {
6
+ path: string;
7
+ size_bytes: number;
8
+ sha256: string;
9
+ }
10
+ /** Agent side: redeem a handle and write the file into destDir. */
11
+ export declare function fetchDownloadHandle(socketPath: string, handle: string, destDir: string): Promise<FetchedDownload>;
12
+ //# sourceMappingURL=download-transfer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download-transfer.d.ts","sourceRoot":"","sources":["../../src/bot/download-transfer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AASzD,eAAO,MAAM,sBAAsB,4BAA4B,CAAC;AAUhE,sEAAsE;AACtE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAcxE;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,mEAAmE;AACnE,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,eAAe,CAAC,CAmD1B"}
@@ -0,0 +1,103 @@
1
+ import { createHash } from "node:crypto";
2
+ import { createReadStream, createWriteStream } from "node:fs";
3
+ import { link, rm } from "node:fs/promises";
4
+ import { createConnection } from "node:net";
5
+ import { join, parse as parsePath } from "node:path";
6
+ import { pipeline } from "node:stream/promises";
7
+ import { redeemDownloadHandle } from "./downloads.js";
8
+ // Delivers a download handle over the shared MCP socket to a process on the
9
+ // agent's machine. The client sends this prefix plus the handle as its first
10
+ // line, instead of an agent identity. The broker replies with one JSON header
11
+ // line, then the raw file bytes.
12
+ export const DOWNLOAD_HANDLE_PREFIX = "trusty-squire-download ";
13
+ /** Broker side: stream one redeemed handle, then close the socket. */
14
+ export function serveDownloadHandle(socket, handle) {
15
+ const redeemed = redeemDownloadHandle(handle);
16
+ if (redeemed === null) {
17
+ socket.end(`${JSON.stringify({ ok: false, error: "unknown_or_expired_handle" })}\n`);
18
+ return;
19
+ }
20
+ const header = {
21
+ ok: true,
22
+ filename: redeemed.filename,
23
+ size_bytes: redeemed.size_bytes,
24
+ sha256: redeemed.sha256,
25
+ };
26
+ socket.write(`${JSON.stringify(header)}\n`);
27
+ void pipeline(createReadStream(redeemed.path), socket).catch(() => socket.destroy());
28
+ }
29
+ /** Agent side: redeem a handle and write the file into destDir. */
30
+ export async function fetchDownloadHandle(socketPath, handle, destDir) {
31
+ const socket = createConnection(socketPath);
32
+ socket.write(`${DOWNLOAD_HANDLE_PREFIX}${handle}\n`);
33
+ const header = await new Promise((resolve, reject) => {
34
+ let buffered = Buffer.alloc(0);
35
+ const onData = (chunk) => {
36
+ buffered = Buffer.concat([buffered, chunk]);
37
+ const end = buffered.indexOf(10);
38
+ if (end < 0) {
39
+ if (buffered.length > 4096)
40
+ reject(new Error("download handle reply has no header"));
41
+ return;
42
+ }
43
+ socket.off("data", onData);
44
+ socket.off("error", reject);
45
+ socket.pause();
46
+ try {
47
+ resolve({
48
+ header: JSON.parse(buffered.subarray(0, end).toString("utf8")),
49
+ rest: buffered.subarray(end + 1),
50
+ });
51
+ }
52
+ catch (error) {
53
+ reject(error);
54
+ }
55
+ };
56
+ socket.on("data", onData);
57
+ socket.once("error", reject);
58
+ socket.once("end", () => reject(new Error("broker closed the connection before replying")));
59
+ });
60
+ if (!header.header.ok || !header.header.filename || !header.header.sha256) {
61
+ socket.destroy();
62
+ throw new Error(`download handle refused: ${header.header.error ?? "invalid reply"}`);
63
+ }
64
+ const { filename, sha256: expected } = header.header;
65
+ const partial = join(destDir, `.${parsePath(filename).base}.${process.pid}.partial`);
66
+ const hash = createHash("sha256");
67
+ let size = 0;
68
+ const out = createWriteStream(partial, { flags: "wx", mode: 0o600 });
69
+ try {
70
+ socket.unshift(header.rest);
71
+ socket.on("data", (chunk) => {
72
+ hash.update(chunk);
73
+ size += chunk.length;
74
+ });
75
+ await pipeline(socket, out);
76
+ const sha256 = hash.digest("hex");
77
+ if (sha256 !== expected)
78
+ throw new Error("received file does not match the download sha256");
79
+ const path = await claimName(partial, destDir, filename);
80
+ return { path, size_bytes: size, sha256 };
81
+ }
82
+ finally {
83
+ await rm(partial, { force: true });
84
+ }
85
+ }
86
+ // Hard-link the finished file under the first free name, so an existing file
87
+ // is never overwritten.
88
+ async function claimName(partial, destDir, filename) {
89
+ const { name, ext } = parsePath(filename);
90
+ for (let attempt = 0; attempt < 100; attempt += 1) {
91
+ const path = join(destDir, attempt === 0 ? filename : `${name} (${attempt})${ext}`);
92
+ try {
93
+ await link(partial, path);
94
+ return path;
95
+ }
96
+ catch (error) {
97
+ if (error.code !== "EEXIST")
98
+ throw error;
99
+ }
100
+ }
101
+ throw new Error("dest_dir already holds 100 files with this name");
102
+ }
103
+ //# sourceMappingURL=download-transfer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download-transfer.js","sourceRoot":"","sources":["../../src/bot/download-transfer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import type { Page } from "playwright";
2
+ export type DownloadState = "in_progress" | "complete" | "failed";
3
+ export interface DownloadRecord {
4
+ download_id: string;
5
+ state: DownloadState;
6
+ filename: string;
7
+ mime_type: string;
8
+ /** Absolute path on the broker host. Set once the file is in the session folder. */
9
+ path: string | null;
10
+ size_bytes: number | null;
11
+ sha256: string | null;
12
+ error?: string;
13
+ }
14
+ export declare function downloadsRoot(): string;
15
+ /** A single path segment: no separators, control characters, or dot names. */
16
+ export declare function safeDownloadFilename(suggested: string): string;
17
+ export declare function sha256File(path: string): Promise<string>;
18
+ /** One session's downloads. Records never cross to another tracker. */
19
+ export declare class SessionDownloads {
20
+ private readonly entries;
21
+ private readonly watched;
22
+ private readonly contentTypes;
23
+ private folder;
24
+ private committedBytes;
25
+ private sequence;
26
+ /** Record every download this page starts. Idempotent per page. */
27
+ watch(page: Page): void;
28
+ /** Records whose state changed since the last report, oldest first. */
29
+ takeReport(): DownloadRecord[];
30
+ get(downloadId: string): DownloadRecord | undefined;
31
+ list(): DownloadRecord[];
32
+ /** Sequence marker; `since(marker)` returns downloads started after it. */
33
+ marker(): number;
34
+ since(marker: number): DownloadRecord[];
35
+ hasInProgress(): boolean;
36
+ /** Wait until no download started after `marker` is in progress, or until `timeoutMs` passes. */
37
+ settle(timeoutMs: number, signal?: AbortSignal, marker?: number): Promise<void>;
38
+ /** Wait for one download to finish. Returns the record in its latest state. */
39
+ wait(downloadId: string, timeoutMs: number, signal?: AbortSignal): Promise<DownloadRecord | undefined>;
40
+ /** Cancel running downloads, revoke handles, and delete the session folder. */
41
+ dispose(): Promise<void>;
42
+ private sessionFolder;
43
+ private begin;
44
+ private finish;
45
+ }
46
+ export interface DownloadHandle {
47
+ id: string;
48
+ expires_at: string;
49
+ }
50
+ interface HandleEntry {
51
+ owner: SessionDownloads;
52
+ path: string;
53
+ filename: string;
54
+ size_bytes: number;
55
+ sha256: string;
56
+ expiresAt: number;
57
+ }
58
+ /** A short-lived, single-use bearer for one completed download. */
59
+ export declare function mintDownloadHandle(owner: SessionDownloads, record: DownloadRecord): DownloadHandle;
60
+ export type RedeemedHandle = Omit<HandleEntry, "owner" | "expiresAt">;
61
+ /** Consume a handle. Unknown, used, and expired handles all refuse alike. */
62
+ export declare function redeemDownloadHandle(id: string): RedeemedHandle | null;
63
+ export {};
64
+ //# sourceMappingURL=downloads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"downloads.d.ts","sourceRoot":"","sources":["../../src/bot/downloads.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAY,IAAI,EAAY,MAAM,YAAY,CAAC;AAO3D,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,UAAU,GAAG,QAAQ,CAAC;AAElE,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAoCD,wBAAgB,aAAa,IAAI,MAAM,CAGtC;AAYD,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAM9D;AAQD,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAI9D;AAMD,uEAAuE;AACvE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAC1D,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,QAAQ,CAAK;IAErB,mEAAmE;IACnE,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAsBvB,uEAAuE;IACvE,UAAU,IAAI,cAAc,EAAE;IAU9B,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAKnD,IAAI,IAAI,cAAc,EAAE;IAIxB,2EAA2E;IAC3E,MAAM,IAAI,MAAM;IAIhB,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,EAAE;IAIvC,aAAa,IAAI,OAAO;IAIxB,iGAAiG;IAC3F,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,MAAM,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAShF,+EAA+E;IACzE,IAAI,CACR,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAOtC,+EAA+E;IACzE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB9B,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,KAAK;YAuBC,MAAM;CAmCrB;AA4BD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,WAAW;IACnB,KAAK,EAAE,gBAAgB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAQD,mEAAmE;AACnE,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,cAAc,GACrB,cAAc,CAgBhB;AAED,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,WAAW,CAAC,CAAC;AAEtE,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAUtE"}
@@ -0,0 +1,300 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { createReadStream } from "node:fs";
3
+ import { mkdir, mkdtemp, rm, stat } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import { basename, extname, join } from "node:path";
6
+ import { pipeline } from "node:stream/promises";
7
+ const DEFAULT_SESSION_CAP_BYTES = 2 * 1024 * 1024 * 1024;
8
+ const DEFAULT_HANDLE_TTL_MS = 5 * 60_000;
9
+ const RECENT_RESPONSE_LIMIT = 64;
10
+ const MIME_BY_EXTENSION = {
11
+ ".bin": "application/octet-stream",
12
+ ".csv": "text/csv",
13
+ ".gif": "image/gif",
14
+ ".gz": "application/gzip",
15
+ ".html": "text/html",
16
+ ".jpeg": "image/jpeg",
17
+ ".jpg": "image/jpeg",
18
+ ".json": "application/json",
19
+ ".mov": "video/quicktime",
20
+ ".mp3": "audio/mpeg",
21
+ ".mp4": "video/mp4",
22
+ ".pdf": "application/pdf",
23
+ ".png": "image/png",
24
+ ".svg": "image/svg+xml",
25
+ ".txt": "text/plain",
26
+ ".wav": "audio/wav",
27
+ ".webm": "video/webm",
28
+ ".webp": "image/webp",
29
+ ".zip": "application/zip",
30
+ };
31
+ export function downloadsRoot() {
32
+ const configured = process.env.TRUSTY_SQUIRE_DOWNLOADS_DIR?.trim();
33
+ return configured ? configured : join(homedir(), ".trusty-squire", "downloads");
34
+ }
35
+ function sessionCapBytes() {
36
+ const parsed = Number(process.env.TRUSTY_SQUIRE_DOWNLOAD_SESSION_CAP_BYTES);
37
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_SESSION_CAP_BYTES;
38
+ }
39
+ function handleTtlMs() {
40
+ const parsed = Number(process.env.TRUSTY_SQUIRE_DOWNLOAD_HANDLE_TTL_MS);
41
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_HANDLE_TTL_MS;
42
+ }
43
+ /** A single path segment: no separators, control characters, or dot names. */
44
+ export function safeDownloadFilename(suggested) {
45
+ const cleaned = basename(suggested.replace(/[\\/]/g, "_"))
46
+ .replace(/[\u0000-\u001f\u007f]/g, "")
47
+ .trim()
48
+ .slice(0, 200);
49
+ return cleaned === "" || cleaned === "." || cleaned === ".." ? "download" : cleaned;
50
+ }
51
+ function mimeFor(filename, contentType) {
52
+ const declared = contentType?.split(";")[0]?.trim().toLowerCase();
53
+ if (declared)
54
+ return declared;
55
+ return MIME_BY_EXTENSION[extname(filename).toLowerCase()] ?? "application/octet-stream";
56
+ }
57
+ export async function sha256File(path) {
58
+ const hash = createHash("sha256");
59
+ await pipeline(createReadStream(path), hash);
60
+ return hash.digest("hex");
61
+ }
62
+ function errorText(error) {
63
+ return error instanceof Error ? error.message : String(error);
64
+ }
65
+ /** One session's downloads. Records never cross to another tracker. */
66
+ export class SessionDownloads {
67
+ entries = new Map();
68
+ watched = new WeakSet();
69
+ contentTypes = new Map();
70
+ folder = null;
71
+ committedBytes = 0;
72
+ sequence = 0;
73
+ /** Record every download this page starts. Idempotent per page. */
74
+ watch(page) {
75
+ if (this.watched.has(page))
76
+ return;
77
+ this.watched.add(page);
78
+ const onResponse = (response) => {
79
+ const contentType = response.headers()["content-type"];
80
+ if (contentType === undefined)
81
+ return;
82
+ this.contentTypes.delete(response.url());
83
+ this.contentTypes.set(response.url(), contentType);
84
+ if (this.contentTypes.size > RECENT_RESPONSE_LIMIT) {
85
+ const oldest = this.contentTypes.keys().next().value;
86
+ if (oldest !== undefined)
87
+ this.contentTypes.delete(oldest);
88
+ }
89
+ };
90
+ const onDownload = (download) => this.begin(download);
91
+ page.on("response", onResponse);
92
+ page.on("download", onDownload);
93
+ page.once("close", () => {
94
+ page.off("response", onResponse);
95
+ page.off("download", onDownload);
96
+ });
97
+ }
98
+ /** Records whose state changed since the last report, oldest first. */
99
+ takeReport() {
100
+ const changed = [];
101
+ for (const entry of this.entries.values()) {
102
+ if (entry.reported === entry.version)
103
+ continue;
104
+ entry.reported = entry.version;
105
+ changed.push({ ...entry.record });
106
+ }
107
+ return changed;
108
+ }
109
+ get(downloadId) {
110
+ const entry = this.entries.get(downloadId);
111
+ return entry === undefined ? undefined : { ...entry.record };
112
+ }
113
+ list() {
114
+ return [...this.entries.values()].map((entry) => ({ ...entry.record }));
115
+ }
116
+ /** Sequence marker; `since(marker)` returns downloads started after it. */
117
+ marker() {
118
+ return this.sequence;
119
+ }
120
+ since(marker) {
121
+ return [...this.entries.values()].slice(marker).map((entry) => ({ ...entry.record }));
122
+ }
123
+ hasInProgress() {
124
+ return [...this.entries.values()].some((entry) => entry.record.state === "in_progress");
125
+ }
126
+ /** Wait until no download started after `marker` is in progress, or until `timeoutMs` passes. */
127
+ async settle(timeoutMs, signal, marker = 0) {
128
+ const pending = [...this.entries.values()]
129
+ .slice(marker)
130
+ .filter((entry) => entry.record.state === "in_progress")
131
+ .map((entry) => entry.settled);
132
+ if (pending.length === 0)
133
+ return;
134
+ await waitBounded(Promise.all(pending), timeoutMs, signal);
135
+ }
136
+ /** Wait for one download to finish. Returns the record in its latest state. */
137
+ async wait(downloadId, timeoutMs, signal) {
138
+ const entry = this.entries.get(downloadId);
139
+ if (entry === undefined)
140
+ return undefined;
141
+ if (entry.record.state === "in_progress")
142
+ await waitBounded(entry.settled, timeoutMs, signal);
143
+ return { ...entry.record };
144
+ }
145
+ /** Cancel running downloads, revoke handles, and delete the session folder. */
146
+ async dispose() {
147
+ const entries = [...this.entries.values()];
148
+ this.entries.clear();
149
+ this.sequence = 0;
150
+ this.committedBytes = 0;
151
+ revokeHandlesFor(this);
152
+ await Promise.all(entries.map(async (entry) => {
153
+ await entry.download?.cancel().catch(() => undefined);
154
+ await entry.download?.delete().catch(() => undefined);
155
+ }));
156
+ const folder = this.folder;
157
+ this.folder = null;
158
+ if (folder !== null) {
159
+ const path = await folder.catch(() => null);
160
+ if (path !== null)
161
+ await rm(path, { recursive: true, force: true });
162
+ }
163
+ }
164
+ sessionFolder() {
165
+ if (this.folder === null) {
166
+ const root = downloadsRoot();
167
+ this.folder = mkdir(root, { recursive: true, mode: 0o700 }).then(async () => await mkdtemp(join(root, "session-")));
168
+ }
169
+ return this.folder;
170
+ }
171
+ begin(download) {
172
+ const downloadId = `dl-${randomBytes(6).toString("hex")}`;
173
+ const filename = safeDownloadFilename(download.suggestedFilename());
174
+ const entry = {
175
+ record: {
176
+ download_id: downloadId,
177
+ state: "in_progress",
178
+ filename,
179
+ mime_type: mimeFor(filename, this.contentTypes.get(download.url())),
180
+ path: null,
181
+ size_bytes: null,
182
+ sha256: null,
183
+ },
184
+ version: 1,
185
+ reported: 0,
186
+ settled: Promise.resolve(),
187
+ download,
188
+ };
189
+ this.entries.set(downloadId, entry);
190
+ this.sequence += 1;
191
+ entry.settled = this.finish(entry, download);
192
+ }
193
+ async finish(entry, download) {
194
+ const update = (patch) => {
195
+ if (this.entries.get(entry.record.download_id) !== entry)
196
+ return;
197
+ entry.record = { ...entry.record, ...patch };
198
+ entry.version += 1;
199
+ };
200
+ let counted = 0;
201
+ try {
202
+ const staged = await download.path();
203
+ const failure = await download.failure();
204
+ if (failure !== null)
205
+ throw new Error(failure);
206
+ const size = (await stat(staged)).size;
207
+ if (this.committedBytes + size > sessionCapBytes())
208
+ throw new Error("session_download_cap_exceeded");
209
+ this.committedBytes += size;
210
+ counted = size;
211
+ const folder = join(await this.sessionFolder(), entry.record.download_id);
212
+ await mkdir(folder, { mode: 0o700 });
213
+ const path = join(folder, entry.record.filename);
214
+ await download.saveAs(path);
215
+ await download.delete().catch(() => undefined);
216
+ const sha256 = await sha256File(path);
217
+ if (this.entries.get(entry.record.download_id) !== entry) {
218
+ await rm(folder, { recursive: true, force: true });
219
+ return;
220
+ }
221
+ update({ state: "complete", path, size_bytes: size, sha256 });
222
+ }
223
+ catch (error) {
224
+ if (this.entries.get(entry.record.download_id) === entry)
225
+ this.committedBytes -= counted;
226
+ await download.delete().catch(() => undefined);
227
+ update({ state: "failed", error: errorText(error) });
228
+ }
229
+ finally {
230
+ entry.download = null;
231
+ }
232
+ }
233
+ }
234
+ async function waitBounded(work, timeoutMs, signal) {
235
+ let timer;
236
+ let onAbort;
237
+ try {
238
+ await Promise.race([
239
+ work,
240
+ new Promise((resolve) => {
241
+ timer = setTimeout(resolve, timeoutMs);
242
+ }),
243
+ new Promise((_, reject) => {
244
+ if (signal === undefined)
245
+ return;
246
+ onAbort = () => reject(signal.reason ?? new Error("operation_cancelled"));
247
+ if (signal.aborted)
248
+ onAbort();
249
+ else
250
+ signal.addEventListener("abort", onAbort, { once: true });
251
+ }),
252
+ ]);
253
+ }
254
+ finally {
255
+ if (timer !== undefined)
256
+ clearTimeout(timer);
257
+ if (onAbort !== undefined)
258
+ signal?.removeEventListener("abort", onAbort);
259
+ }
260
+ }
261
+ const handles = new Map();
262
+ function revokeHandlesFor(owner) {
263
+ for (const [id, entry] of handles)
264
+ if (entry.owner === owner)
265
+ handles.delete(id);
266
+ }
267
+ /** A short-lived, single-use bearer for one completed download. */
268
+ export function mintDownloadHandle(owner, record) {
269
+ if (record.state !== "complete" || record.path === null || record.sha256 === null)
270
+ throw new Error("Only a complete download can be handed out");
271
+ const now = Date.now();
272
+ for (const [id, entry] of handles)
273
+ if (entry.expiresAt <= now)
274
+ handles.delete(id);
275
+ const id = `dlh-${randomBytes(24).toString("base64url")}`;
276
+ const expiresAt = now + handleTtlMs();
277
+ handles.set(id, {
278
+ owner,
279
+ path: record.path,
280
+ filename: record.filename,
281
+ size_bytes: record.size_bytes ?? 0,
282
+ sha256: record.sha256,
283
+ expiresAt,
284
+ });
285
+ return { id, expires_at: new Date(expiresAt).toISOString() };
286
+ }
287
+ /** Consume a handle. Unknown, used, and expired handles all refuse alike. */
288
+ export function redeemDownloadHandle(id) {
289
+ const entry = handles.get(id);
290
+ handles.delete(id);
291
+ if (entry === undefined || entry.expiresAt <= Date.now())
292
+ return null;
293
+ return {
294
+ path: entry.path,
295
+ filename: entry.filename,
296
+ size_bytes: entry.size_bytes,
297
+ sha256: entry.sha256,
298
+ };
299
+ }
300
+ //# sourceMappingURL=downloads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"downloads.js","sourceRoot":"","sources":["../../src/bot/downloads.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Page } from "playwright";
2
+ export interface FilePickerRoute {
3
+ restore(): Promise<{
4
+ pickerCalled: boolean;
5
+ }>;
6
+ }
7
+ export declare function routeFilePickerToChooser(page: Page): Promise<FilePickerRoute>;
8
+ //# sourceMappingURL=file-picker-route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-picker-route.d.ts","sourceRoot":"","sources":["../../src/bot/file-picker-route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,IAAI,EAAE,MAAM,YAAY,CAAC;AAgG9C,MAAM,WAAW,eAAe;IAE9B,OAAO,IAAI,OAAO,CAAC;QAAE,YAAY,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC/C;AAED,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC,CAwBnF"}
@@ -0,0 +1,94 @@
1
+ // Some upload controls call window.showOpenFilePicker (File System Access API)
2
+ // instead of clicking an <input type=file>. Chromium opens that picker without
3
+ // a file input, so Playwright never emits "filechooser" and the upload times
4
+ // out. MEASURED 2026-10-14 on Dreamina's "Add first frame" card: one trusted
5
+ // click calls showOpenFilePicker with user activation and nothing else.
6
+ //
7
+ // While an upload is armed, replace showOpenFilePicker in each frame with a
8
+ // version that clicks a detached file input instead. The click still runs
9
+ // inside the page's activated call, so the existing chooser interception
10
+ // receives it, and the page gets back handles whose getFile() returns the
11
+ // attached file. The original function is put back once the chooser wait ends.
12
+ const ROUTE_KEY = "trusty-squire.file-picker-route";
13
+ function installRoute(key) {
14
+ const w = window;
15
+ const slot = Symbol.for(key);
16
+ const original = w.showOpenFilePicker;
17
+ if (typeof original !== "function" || w[slot] !== undefined)
18
+ return false;
19
+ const state = { original, calls: 0 };
20
+ Object.defineProperty(w, slot, { value: state, configurable: true });
21
+ w.showOpenFilePicker = function showOpenFilePicker(options) {
22
+ state.calls += 1;
23
+ return new Promise((resolve, reject) => {
24
+ const input = document.createElement("input");
25
+ input.type = "file";
26
+ input.multiple = options?.multiple === true;
27
+ input.accept = (options?.types ?? [])
28
+ .flatMap((type) => Object.entries(type?.accept ?? {}))
29
+ .flatMap(([mime, extensions]) => [mime, ...[].concat(extensions)])
30
+ .join(",");
31
+ const abort = () => reject(new DOMException("The user aborted a request.", "AbortError"));
32
+ input.addEventListener("cancel", abort, { once: true });
33
+ input.addEventListener("change", () => {
34
+ const files = Array.from(input.files ?? []);
35
+ if (files.length === 0)
36
+ return abort();
37
+ resolve(files.map((file) => {
38
+ const handle = Object.create(FileSystemFileHandle.prototype);
39
+ Object.defineProperties(handle, {
40
+ kind: { value: "file" },
41
+ name: { value: file.name },
42
+ getFile: { value: async () => file },
43
+ });
44
+ return handle;
45
+ }));
46
+ }, { once: true });
47
+ input.click();
48
+ });
49
+ };
50
+ return true;
51
+ }
52
+ function removeRoute(key) {
53
+ const w = window;
54
+ const slot = Symbol.for(key);
55
+ const state = w[slot];
56
+ if (state === undefined)
57
+ return 0;
58
+ w.showOpenFilePicker = state.original;
59
+ delete w[slot];
60
+ return state.calls;
61
+ }
62
+ // Patchright runs frame.evaluate in an isolated world unless the third argument
63
+ // is false, and the page's own showOpenFilePicker lives in the main world.
64
+ // Playwright rejects a third argument and already evaluates in the main world.
65
+ async function evaluateInMainWorld(frame, fn, key) {
66
+ const evaluate = frame.evaluate;
67
+ try {
68
+ return await evaluate.call(frame, fn, key, false);
69
+ }
70
+ catch (error) {
71
+ if (error instanceof Error && error.message.startsWith("Too many arguments"))
72
+ return await frame.evaluate(fn, key);
73
+ throw error;
74
+ }
75
+ }
76
+ export async function routeFilePickerToChooser(page) {
77
+ let frames = [];
78
+ try {
79
+ frames = page.frames();
80
+ }
81
+ catch {
82
+ // A page without frames has no picker to route.
83
+ }
84
+ const routed = (await Promise.all(frames.map(async (frame) => (await evaluateInMainWorld(frame, installRoute, ROUTE_KEY).catch(() => false))
85
+ ? frame
86
+ : null))).filter((frame) => frame !== null);
87
+ return {
88
+ async restore() {
89
+ const calls = await Promise.all(routed.map((frame) => evaluateInMainWorld(frame, removeRoute, ROUTE_KEY).catch(() => 0)));
90
+ return { pickerCalled: calls.some((count) => count > 0) };
91
+ },
92
+ };
93
+ }
94
+ //# sourceMappingURL=file-picker-route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-picker-route.js","sourceRoot":"","sources":["../../src/bot/file-picker-route.ts"],"names":[],"mappings":"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"}
@@ -32,6 +32,7 @@ export declare function compactV2PublicObservation(session: Session, fields: {
32
32
  hint?: string;
33
33
  page_readiness?: Observation["page_readiness"];
34
34
  }, outputFormat?: "compact" | "full"): Observation;
35
+ export declare const CHROME_DOWNLOADS_HINT = "chrome://downloads is Chrome's internal downloads page, which observe cannot read. Use operate_download with session_id and a download_id from the downloads list in an operate_* step result.";
35
36
  export declare function observeQuery(sessionId: string, query: string, role?: SafeControlV2["role"], cursor?: string): Promise<Record<string, unknown>>;
36
37
  export declare function observedOAuthChallenge(sessionId: string): Promise<Observation["oauth"] | undefined>;
37
38
  export declare function observedThreeDsChallenge(sessionId: string): Promise<Observation["three_ds"] | undefined>;