@hraness/kb 0.17.1

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 (200) hide show
  1. package/DISCLOSURE +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +545 -0
  4. package/dist/agent-context.js +35 -0
  5. package/dist/agent-guide-audit.js +18 -0
  6. package/dist/attachments.js +19 -0
  7. package/dist/authoring.js +33 -0
  8. package/dist/benchmark.js +19 -0
  9. package/dist/capture.js +49 -0
  10. package/dist/cli.js +3646 -0
  11. package/dist/clip/acquire.js +57 -0
  12. package/dist/clip/args.js +21 -0
  13. package/dist/clip/bounded-byte-buffer.js +9 -0
  14. package/dist/clip/browser-profiles.js +17 -0
  15. package/dist/clip/bundle-reader.js +11 -0
  16. package/dist/clip/cli.js +27 -0
  17. package/dist/clip/cookies.js +21 -0
  18. package/dist/clip/defuddle-worker.js +84 -0
  19. package/dist/clip/doctor.js +32 -0
  20. package/dist/clip/jobs.js +784 -0
  21. package/dist/clip/network-proxy.js +9 -0
  22. package/dist/clip/network.js +28 -0
  23. package/dist/clip/persist.js +30 -0
  24. package/dist/clip/refresh.js +11 -0
  25. package/dist/clip/terminal.js +11 -0
  26. package/dist/evaluation-builder.js +12238 -0
  27. package/dist/evaluation-kb.js +26 -0
  28. package/dist/evaluation.js +35 -0
  29. package/dist/git.js +39 -0
  30. package/dist/graph.js +39 -0
  31. package/dist/index-01jj6rbv.js +1103 -0
  32. package/dist/index-06c9ctr6.js +738 -0
  33. package/dist/index-0kavxzqj.js +2480 -0
  34. package/dist/index-1gwbassd.js +743 -0
  35. package/dist/index-1n418kb9.js +827 -0
  36. package/dist/index-1vrd1rmn.js +56 -0
  37. package/dist/index-1xxnjn0d.js +171 -0
  38. package/dist/index-3rm7cz6h.js +373 -0
  39. package/dist/index-3v2z4f0q.js +328 -0
  40. package/dist/index-48pz4jpc.js +392 -0
  41. package/dist/index-4j3tt0c3.js +234 -0
  42. package/dist/index-5n05se68.js +307 -0
  43. package/dist/index-5vwpzb5a.js +764 -0
  44. package/dist/index-6g2pv9d2.js +370 -0
  45. package/dist/index-7fzc32gf.js +712 -0
  46. package/dist/index-84x0vjjp.js +432 -0
  47. package/dist/index-adx6khj5.js +488 -0
  48. package/dist/index-b88v3vtm.js +805 -0
  49. package/dist/index-cv6fh7z5.js +462 -0
  50. package/dist/index-cxfrakt7.js +1067 -0
  51. package/dist/index-d13v9ckt.js +150 -0
  52. package/dist/index-dycjbhw2.js +1943 -0
  53. package/dist/index-dyqwejk5.js +531 -0
  54. package/dist/index-e5fbsywq.js +658 -0
  55. package/dist/index-f984hw45.js +927 -0
  56. package/dist/index-gh719d91.js +63 -0
  57. package/dist/index-hgve9rh2.js +2046 -0
  58. package/dist/index-hya40gb2.js +474 -0
  59. package/dist/index-j4zgmzjr.js +152 -0
  60. package/dist/index-jsmvyyvf.js +1166 -0
  61. package/dist/index-kh0pwvdd.js +1974 -0
  62. package/dist/index-mqx4nd6v.js +119 -0
  63. package/dist/index-mxxxytys.js +1254 -0
  64. package/dist/index-n5dd7r0v.js +711 -0
  65. package/dist/index-npg9z1a4.js +431 -0
  66. package/dist/index-pj501bh1.js +140 -0
  67. package/dist/index-qry4vhxk.js +1041 -0
  68. package/dist/index-s2gw5aw9.js +199 -0
  69. package/dist/index-vvdj6kca.js +70 -0
  70. package/dist/index-w2zc0vwa.js +571 -0
  71. package/dist/index-x3fthpsc.js +585 -0
  72. package/dist/index-zr53sf63.js +44 -0
  73. package/dist/index-zxdy5pby.js +2728 -0
  74. package/dist/index-zzhgcwyt.js +966 -0
  75. package/dist/index.js +453 -0
  76. package/dist/navigation.js +13 -0
  77. package/dist/pdf.js +85 -0
  78. package/dist/percolate.js +24 -0
  79. package/dist/portfolio.js +77 -0
  80. package/dist/query.js +29 -0
  81. package/dist/repository-memory.js +60 -0
  82. package/dist/sdk.js +36 -0
  83. package/dist/search-rules.js +34 -0
  84. package/dist/search.js +22 -0
  85. package/dist/semantic.js +39 -0
  86. package/dist/source-inbox.js +17 -0
  87. package/dist/untrusted-content.js +32 -0
  88. package/dist/url-intelligence.js +74 -0
  89. package/dist/workflow.js +23 -0
  90. package/dist/workflows/decision-context.js +20 -0
  91. package/dist/workflows/explain-change.js +8 -0
  92. package/dist/workflows/index.js +28 -0
  93. package/dist/workflows/plan-radar.js +10 -0
  94. package/package.json +409 -0
  95. package/skills/kb/AGENTS.md +19 -0
  96. package/skills/kb/SKILL.md +82 -0
  97. package/skills/kb/agents/openai.yaml +4 -0
  98. package/skills/kb/references/pdf-review.md +51 -0
  99. package/skills/kb/references/percolate.md +144 -0
  100. package/skills/kb/references/plan-structure.md +105 -0
  101. package/skills/kb/references/plan.md +115 -0
  102. package/skills/kb/references/query.md +231 -0
  103. package/skills/kb/references/refresh.md +133 -0
  104. package/skills/kb/references/save-pdf.md +125 -0
  105. package/skills/kb/references/save-url.md +178 -0
  106. package/skills/kb/references/url-authentication.md +75 -0
  107. package/skills/kb/references/url-platforms.md +25 -0
  108. package/src/agent-context.ts +1241 -0
  109. package/src/agent-guide-audit.ts +750 -0
  110. package/src/attachments.ts +723 -0
  111. package/src/authoring.ts +1725 -0
  112. package/src/benchmark.ts +362 -0
  113. package/src/capture.ts +68 -0
  114. package/src/cli.ts +3660 -0
  115. package/src/clip/acquire.ts +1273 -0
  116. package/src/clip/archive-today.ts +402 -0
  117. package/src/clip/args.ts +423 -0
  118. package/src/clip/assets.ts +262 -0
  119. package/src/clip/bounded-byte-buffer.ts +75 -0
  120. package/src/clip/browser-profiles.ts +372 -0
  121. package/src/clip/bundle-reader.ts +608 -0
  122. package/src/clip/capture.ts +1054 -0
  123. package/src/clip/cli.ts +181 -0
  124. package/src/clip/cookies.ts +512 -0
  125. package/src/clip/doctor.ts +1110 -0
  126. package/src/clip/extract.ts +868 -0
  127. package/src/clip/jobs.ts +959 -0
  128. package/src/clip/lib.ts +861 -0
  129. package/src/clip/media.ts +1474 -0
  130. package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
  131. package/src/clip/metadata-search-tool/Cargo.toml +18 -0
  132. package/src/clip/metadata-search-tool/runner.ts +685 -0
  133. package/src/clip/metadata-search-tool/src/main.rs +389 -0
  134. package/src/clip/metadata-search.ts +687 -0
  135. package/src/clip/network-proxy.ts +627 -0
  136. package/src/clip/network.ts +950 -0
  137. package/src/clip/package-root.ts +54 -0
  138. package/src/clip/persist.ts +1472 -0
  139. package/src/clip/platforms.ts +1824 -0
  140. package/src/clip/refresh.ts +197 -0
  141. package/src/clip/structured.ts +432 -0
  142. package/src/clip/terminal.ts +197 -0
  143. package/src/clip/url-intelligence.ts +581 -0
  144. package/src/clip/url-metadata-backfill.ts +616 -0
  145. package/src/clip/url-metadata-cli.ts +251 -0
  146. package/src/clip/url-metadata.ts +1397 -0
  147. package/src/evaluation-analysis-v2.ts +1440 -0
  148. package/src/evaluation-builder.ts +2539 -0
  149. package/src/evaluation-corpus-authoring.ts +2052 -0
  150. package/src/evaluation-evidence.ts +1147 -0
  151. package/src/evaluation-execution-v2.ts +426 -0
  152. package/src/evaluation-implementation.ts +131 -0
  153. package/src/evaluation-kb-closure.ts +1430 -0
  154. package/src/evaluation-kb-runner-v2.ts +735 -0
  155. package/src/evaluation-kb-v2.ts +2567 -0
  156. package/src/evaluation-kb.ts +1018 -0
  157. package/src/evaluation-measurement-v2.ts +1462 -0
  158. package/src/evaluation-packing-v2.ts +452 -0
  159. package/src/evaluation-redaction.ts +28 -0
  160. package/src/evaluation-v2.ts +3857 -0
  161. package/src/evaluation.ts +1220 -0
  162. package/src/git.ts +1276 -0
  163. package/src/graph.ts +1487 -0
  164. package/src/index.ts +20 -0
  165. package/src/init.ts +123 -0
  166. package/src/navigation.ts +277 -0
  167. package/src/note-lock.ts +529 -0
  168. package/src/pdf/args.ts +202 -0
  169. package/src/pdf/capture.ts +298 -0
  170. package/src/pdf/cli.ts +159 -0
  171. package/src/pdf/extract.ts +303 -0
  172. package/src/pdf/layout.ts +618 -0
  173. package/src/pdf/markdown.ts +154 -0
  174. package/src/pdf/model.ts +234 -0
  175. package/src/pdf/ocr.ts +190 -0
  176. package/src/pdf/persist.ts +314 -0
  177. package/src/pdf/source.ts +135 -0
  178. package/src/pdf/tools.ts +146 -0
  179. package/src/pdf.ts +92 -0
  180. package/src/percolate.ts +877 -0
  181. package/src/portfolio-audit.ts +574 -0
  182. package/src/portfolio-identity.ts +162 -0
  183. package/src/portfolio-registry.ts +473 -0
  184. package/src/portfolio.ts +765 -0
  185. package/src/query.ts +646 -0
  186. package/src/repository-memory.ts +1150 -0
  187. package/src/sdk.ts +1452 -0
  188. package/src/search-rules.ts +712 -0
  189. package/src/search.ts +710 -0
  190. package/src/semantic-runtime.ts +1139 -0
  191. package/src/semantic.ts +2480 -0
  192. package/src/source-inbox.ts +232 -0
  193. package/src/untrusted-content.ts +401 -0
  194. package/src/url-intelligence.ts +104 -0
  195. package/src/vault.ts +636 -0
  196. package/src/workflow.ts +653 -0
  197. package/src/workflows/decision-context.ts +81 -0
  198. package/src/workflows/explain-change.ts +73 -0
  199. package/src/workflows/index.ts +3 -0
  200. package/src/workflows/plan-radar.ts +101 -0
@@ -0,0 +1,1041 @@
1
+ // @bun
2
+ import {
3
+ classifyPlatformUrl
4
+ } from "./index-hgve9rh2.js";
5
+ import {
6
+ startNetworkProxy
7
+ } from "./index-w2zc0vwa.js";
8
+ import {
9
+ assertSafeNetworkUrl,
10
+ decodeBytes,
11
+ safeFetch
12
+ } from "./index-e5fbsywq.js";
13
+ import {
14
+ captureUrl
15
+ } from "./index-6g2pv9d2.js";
16
+ import {
17
+ readBoundedByteStream
18
+ } from "./index-gh719d91.js";
19
+ import {
20
+ sanitizeArtifactUrl
21
+ } from "./index-mxxxytys.js";
22
+ import {
23
+ filterCookieProviderResult,
24
+ readCookieFile,
25
+ renderCookieHeader
26
+ } from "./index-84x0vjjp.js";
27
+
28
+ // src/clip/acquire.ts
29
+ import {
30
+ chmodSync,
31
+ existsSync as existsSync2,
32
+ lstatSync,
33
+ mkdtempSync,
34
+ readFileSync as readFileSync2,
35
+ realpathSync,
36
+ rmSync,
37
+ statSync,
38
+ writeFileSync
39
+ } from "fs";
40
+ import { homedir, tmpdir } from "os";
41
+ import { basename, dirname as dirname2, isAbsolute, join as join2, relative, resolve as resolve2, sep } from "path";
42
+ import { getCookies } from "@steipete/sweet-cookie";
43
+
44
+ // src/clip/package-root.ts
45
+ import { existsSync, readFileSync } from "fs";
46
+ import { createRequire } from "module";
47
+ import { dirname, join, resolve } from "path";
48
+ function isPackageManifest(value) {
49
+ return typeof value === "object" && value !== null && !Array.isArray(value);
50
+ }
51
+ function findKbPackageRoot(startDirectory = import.meta.dir, dependencies = {}) {
52
+ const exists = dependencies.exists ?? existsSync;
53
+ const readText = dependencies.readText ?? ((path) => readFileSync(path, "utf8"));
54
+ let directory = resolve(startDirectory);
55
+ for (let depth = 0;depth < 8; depth += 1) {
56
+ const manifestPath = join(directory, "package.json");
57
+ if (exists(manifestPath)) {
58
+ try {
59
+ const parsed = JSON.parse(readText(manifestPath));
60
+ if (isPackageManifest(parsed) && typeof parsed.name === "string" && parsed.name.endsWith("/kb") && typeof parsed.version === "string")
61
+ return directory;
62
+ } catch {}
63
+ }
64
+ const parent = dirname(directory);
65
+ if (parent === directory)
66
+ break;
67
+ directory = parent;
68
+ }
69
+ throw new Error("Could not locate the kb package root.");
70
+ }
71
+ function resolvePackageDirectory(packageName, parentUrl = import.meta.url) {
72
+ const manifest = createRequire(parentUrl).resolve(`${packageName}/package.json`);
73
+ return dirname(manifest);
74
+ }
75
+
76
+ // src/clip/acquire.ts
77
+ var agentBrowserBinDirectory = join2(resolvePackageDirectory("agent-browser"), "bin");
78
+ function agentBrowserCommand() {
79
+ return [process.execPath, join2(agentBrowserBinDirectory, "agent-browser.js")];
80
+ }
81
+ var inheritedProxyKeys = new Set([
82
+ "ALL_PROXY",
83
+ "HTTP_PROXY",
84
+ "HTTPS_PROXY",
85
+ "NO_PROXY",
86
+ "all_proxy",
87
+ "http_proxy",
88
+ "https_proxy",
89
+ "no_proxy"
90
+ ]);
91
+ function isolatedAgentBrowserEnvironment(source, socketDirectory) {
92
+ const environment = {};
93
+ for (const [key, value] of Object.entries(source)) {
94
+ if (value === undefined || key.startsWith("AGENT_BROWSER_") || inheritedProxyKeys.has(key))
95
+ continue;
96
+ environment[key] = value;
97
+ }
98
+ environment.AGENT_BROWSER_SOCKET_DIR = socketDirectory;
99
+ return environment;
100
+ }
101
+ function createAgentBrowserIsolation(directory) {
102
+ const configPath = join2(directory, "agent-browser.config.json");
103
+ const socketRoot = process.platform === "win32" ? tmpdir() : "/tmp";
104
+ const socketDirectory = mkdtempSync(join2(socketRoot, "jc-ab-"));
105
+ try {
106
+ chmodSync(socketDirectory, 448);
107
+ writeFileSync(configPath, `{}
108
+ `, { encoding: "utf8", flag: "wx", mode: 384 });
109
+ chmodSync(configPath, 384);
110
+ return {
111
+ configPath,
112
+ cwd: directory,
113
+ socketDirectory,
114
+ environment: isolatedAgentBrowserEnvironment(process.env, socketDirectory)
115
+ };
116
+ } catch (error) {
117
+ rmSync(socketDirectory, { recursive: true, force: true });
118
+ throw error;
119
+ }
120
+ }
121
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
122
+ async function readBoundedStream(stream, maxBytes) {
123
+ const bytes = await readBoundedByteStream(stream, maxBytes, "process output");
124
+ return new TextDecoder().decode(bytes);
125
+ }
126
+ async function runCommand(command, timeoutMs, maxOutputBytes, isolation, stdin) {
127
+ const child = Bun.spawn([...command], {
128
+ stdin: stdin === undefined ? "ignore" : new Blob([stdin]),
129
+ stdout: "pipe",
130
+ stderr: "pipe",
131
+ ...isolation === undefined ? {} : { cwd: isolation.cwd, env: isolation.environment }
132
+ });
133
+ let timedOut = false;
134
+ let forceKill = null;
135
+ const timeout = setTimeout(() => {
136
+ timedOut = true;
137
+ child.kill("SIGTERM");
138
+ forceKill = setTimeout(() => child.kill("SIGKILL"), 1000);
139
+ }, timeoutMs);
140
+ try {
141
+ const [stdout, stderr, exitCode] = await Promise.all([
142
+ readBoundedStream(child.stdout, maxOutputBytes),
143
+ readBoundedStream(child.stderr, Math.min(maxOutputBytes, 2 * 1024 * 1024)),
144
+ child.exited
145
+ ]);
146
+ if (timedOut)
147
+ throw new Error(`command timed out after ${timeoutMs}ms`);
148
+ return { stdout, stderr, exitCode };
149
+ } catch (error) {
150
+ child.kill("SIGKILL");
151
+ await child.exited;
152
+ throw error;
153
+ } finally {
154
+ clearTimeout(timeout);
155
+ if (forceKill !== null)
156
+ clearTimeout(forceKill);
157
+ }
158
+ }
159
+ function parseJsonValueOutput(output, label) {
160
+ let lineEnd = output.length;
161
+ while (lineEnd >= 0) {
162
+ const newline = output.lastIndexOf(`
163
+ `, lineEnd - 1);
164
+ const line = output.slice(newline + 1, lineEnd).trim();
165
+ lineEnd = newline;
166
+ if (line[0] !== "{" && line[0] !== "[")
167
+ continue;
168
+ try {
169
+ return JSON.parse(line);
170
+ } catch {}
171
+ }
172
+ throw new Error(`${label} did not return JSON`);
173
+ }
174
+ function parseJsonOutput(output, label) {
175
+ const parsed = parseJsonValueOutput(output, label);
176
+ if (isRecord(parsed))
177
+ return parsed;
178
+ throw new Error(`${label} did not return a JSON object`);
179
+ }
180
+ function parseAgentBrowserData(output, label) {
181
+ const parsed = parseJsonOutput(output, label);
182
+ if (parsed.success !== true) {
183
+ throw new Error(`${label} failed`);
184
+ }
185
+ if (!isRecord(parsed.data))
186
+ throw new Error(`${label} returned no data`);
187
+ return parsed.data;
188
+ }
189
+ async function runAgentBrowser(globalArgs, command, options) {
190
+ let result;
191
+ try {
192
+ result = await runCommand([...agentBrowserCommand(), ...globalArgs, ...command, "--json"], options.timeoutMs, options.maxOutputBytes, options);
193
+ } catch (error) {
194
+ const message = error instanceof Error ? error.message : String(error);
195
+ throw new Error(`agent-browser ${command[0] ?? "command"} failed: ${message}`, { cause: error });
196
+ }
197
+ if (result.exitCode !== 0) {
198
+ throw new Error(`agent-browser ${command[0] ?? "command"} failed with exit code ${result.exitCode}`);
199
+ }
200
+ return parseAgentBrowserData(result.stdout, `agent-browser ${command[0] ?? "command"}`);
201
+ }
202
+ async function runAgentBrowserBatch(globalArgs, commands, options) {
203
+ const result = await runCommand([...agentBrowserCommand(), ...globalArgs, "batch", "--bail", "--json"], options.timeoutMs, options.maxOutputBytes, options, JSON.stringify(commands));
204
+ if (result.exitCode !== 0)
205
+ throw new Error(`agent-browser batch failed with exit code ${result.exitCode}`);
206
+ const parsed = parseJsonValueOutput(result.stdout, "agent-browser batch");
207
+ if (!Array.isArray(parsed) || parsed.length !== commands.length || parsed.some((entry) => !isRecord(entry) || entry.success !== true))
208
+ throw new Error("agent-browser batch failed");
209
+ }
210
+ async function discoverChromeProfiles(timeoutMs = 15000) {
211
+ const directory = mkdtempSync(join2(tmpdir(), "hraness-kb-profiles-"));
212
+ chmodSync(directory, 448);
213
+ let socketDirectory = null;
214
+ try {
215
+ const isolation = createAgentBrowserIsolation(directory);
216
+ socketDirectory = isolation.socketDirectory;
217
+ const result = await runCommand([...agentBrowserCommand(), "--config", isolation.configPath, "profiles", "--json"], timeoutMs, 1024 * 1024, isolation);
218
+ if (result.exitCode !== 0)
219
+ return [];
220
+ const parsed = parseJsonOutput(result.stdout, "agent-browser profiles");
221
+ if (parsed.success !== true || !Array.isArray(parsed.data))
222
+ return [];
223
+ const profiles = [];
224
+ for (const entry of parsed.data) {
225
+ if (!isRecord(entry))
226
+ continue;
227
+ if (typeof entry.directory !== "string" || typeof entry.name !== "string")
228
+ continue;
229
+ profiles.push({ directory: entry.directory, name: entry.name });
230
+ }
231
+ return profiles;
232
+ } finally {
233
+ if (socketDirectory !== null)
234
+ rmSync(socketDirectory, { recursive: true, force: true });
235
+ rmSync(directory, { recursive: true, force: true });
236
+ }
237
+ }
238
+ function selectedProfile(profiles) {
239
+ const defaultProfile = profiles.find(({ directory }) => directory === "Default");
240
+ if (defaultProfile !== undefined)
241
+ return defaultProfile.directory;
242
+ return profiles.length === 1 ? profiles[0]?.directory : undefined;
243
+ }
244
+ function shouldExpand(url, options, method) {
245
+ if (options.scope === "page")
246
+ return false;
247
+ const platform = classifyPlatformUrl(url.href)?.platform ?? "generic";
248
+ const hasExplicitCookies = options.cookieSources.length > 0 || options.cookiesFile !== undefined;
249
+ if (platform === "x" && method === "browser-fresh" && !hasExplicitCookies)
250
+ return false;
251
+ return platform === "x" || platform === "hacker-news" || platform === "reddit" || platform === "bluesky" || platform === "linkedin" || platform === "facebook" || platform === "instagram" || platform === "tiktok" || platform === "threads" || platform === "whatsapp" || platform === "youtube" || platform === "github" || platform === "discourse" || platform === "substack";
252
+ }
253
+ function renderedTextLines(snapshot) {
254
+ const lines = snapshot.replace(/\r\n?/g, `
255
+ `).split(`
256
+ `);
257
+ let start = 0;
258
+ while (start < lines.length && lines[start]?.trim() === "")
259
+ start += 1;
260
+ let end = lines.length;
261
+ while (end > start && lines[end - 1]?.trim() === "")
262
+ end -= 1;
263
+ return lines.slice(start, end);
264
+ }
265
+ function suffixPrefixOverlap(source, prefix) {
266
+ if (source.length === 0 || prefix.length === 0)
267
+ return 0;
268
+ const fallback = new Array(prefix.length).fill(0);
269
+ for (let index = 1;index < prefix.length; index += 1) {
270
+ let matched2 = fallback[index - 1] ?? 0;
271
+ while (matched2 > 0 && prefix[index] !== prefix[matched2]) {
272
+ matched2 = fallback[matched2 - 1] ?? 0;
273
+ }
274
+ if (prefix[index] === prefix[matched2])
275
+ matched2 += 1;
276
+ fallback[index] = matched2;
277
+ }
278
+ let matched = 0;
279
+ for (let index = 0;index < source.length; index += 1) {
280
+ while (matched > 0 && source[index] !== prefix[matched]) {
281
+ matched = fallback[matched - 1] ?? 0;
282
+ }
283
+ if (source[index] === prefix[matched])
284
+ matched += 1;
285
+ if (matched === prefix.length && index < source.length - 1) {
286
+ matched = fallback[matched - 1] ?? 0;
287
+ }
288
+ }
289
+ return matched;
290
+ }
291
+ function truncateUtf8(value, maxBytes) {
292
+ const encoded = new TextEncoder().encode(value);
293
+ if (encoded.byteLength <= maxBytes)
294
+ return { content: value, truncated: false };
295
+ let end = maxBytes;
296
+ while (end > 0 && (encoded[end] ?? 0) >>> 6 === 2)
297
+ end -= 1;
298
+ return {
299
+ content: new TextDecoder("utf-8", { fatal: true }).decode(encoded.subarray(0, end)),
300
+ truncated: true
301
+ };
302
+ }
303
+ function mergeRenderedTextSnapshots(snapshots, maxBytes) {
304
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
305
+ throw new RangeError("rendered text byte limit must be a non-negative safe integer");
306
+ }
307
+ const merged = [];
308
+ let hasBaseline = false;
309
+ let addedLines = 0;
310
+ for (const snapshot of snapshots) {
311
+ const lines = renderedTextLines(snapshot);
312
+ if (lines.length === 0)
313
+ continue;
314
+ if (!hasBaseline) {
315
+ for (const line of lines)
316
+ merged.push(line);
317
+ hasBaseline = true;
318
+ continue;
319
+ }
320
+ let commonPrefix = 0;
321
+ const prefixLimit = Math.min(merged.length, lines.length);
322
+ while (commonPrefix < prefixLimit && merged[commonPrefix] === lines[commonPrefix]) {
323
+ commonPrefix += 1;
324
+ }
325
+ const remaining = lines.slice(commonPrefix);
326
+ if (remaining.length === 0)
327
+ continue;
328
+ const overlap = suffixPrefixOverlap(merged, remaining);
329
+ const additions = remaining.slice(overlap);
330
+ if (additions.length === 0)
331
+ continue;
332
+ if (commonPrefix === 0 && overlap === 0 && merged.at(-1) !== "")
333
+ merged.push("");
334
+ for (const line of additions)
335
+ merged.push(line);
336
+ addedLines += additions.length;
337
+ }
338
+ const bounded = truncateUtf8(merged.join(`
339
+ `), maxBytes);
340
+ return {
341
+ ...bounded,
342
+ observedSnapshots: snapshots.length,
343
+ addedLines
344
+ };
345
+ }
346
+ function browserExpansionLimits(maxItems, maxObservedTextBytes = 4 * 1024 * 1024) {
347
+ const boundedItems = Number.isSafeInteger(maxItems) ? Math.max(1, Math.min(maxItems, 1e4)) : 500;
348
+ const boundedObservationBytes = Number.isSafeInteger(maxObservedTextBytes) && maxObservedTextBytes > 0 ? Math.min(maxObservedTextBytes, 4 * 1024 * 1024) : 4 * 1024 * 1024;
349
+ return {
350
+ maxScrolls: Math.max(3, Math.min(40, Math.ceil(boundedItems / 20))),
351
+ maxObservedTextBytes: boundedObservationBytes
352
+ };
353
+ }
354
+ function browserExpansionScript(limits) {
355
+ return `(async () => {
356
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
357
+ const renderedTextSnapshots = [];
358
+ let observedTextBytes = 0;
359
+ let renderedTextObservationTruncated = false;
360
+ let stable = 0;
361
+ let previousHeight = 0;
362
+ let scrolls = 0;
363
+ let settled = false;
364
+ const boundedRenderedText = (value, maxBytes) => {
365
+ let bytes = 0;
366
+ let end = 0;
367
+ while (end < value.length) {
368
+ const first = value.charCodeAt(end);
369
+ let width = 3;
370
+ let codeUnits = 1;
371
+ if (first <= 0x7f) width = 1;
372
+ else if (first <= 0x7ff) width = 2;
373
+ else if (first >= 0xd800 && first <= 0xdbff) {
374
+ const second = value.charCodeAt(end + 1);
375
+ if (second >= 0xdc00 && second <= 0xdfff) {
376
+ width = 4;
377
+ codeUnits = 2;
378
+ }
379
+ }
380
+ if (bytes + width > maxBytes) break;
381
+ bytes += width;
382
+ end += codeUnits;
383
+ }
384
+ return { text: value.slice(0, end), bytes, truncated: end < value.length };
385
+ };
386
+ for (let pass = 0; pass < ${limits.maxScrolls}; pass += 1) {
387
+ window.scrollTo(0, document.documentElement.scrollHeight);
388
+ scrolls += 1;
389
+ await sleep(700);
390
+ try {
391
+ const root = document.body || document.documentElement;
392
+ const renderedValue = root ? root.innerText : '';
393
+ const rendered = typeof renderedValue === 'string' ? renderedValue : '';
394
+ const remainingBytes = ${limits.maxObservedTextBytes} - observedTextBytes;
395
+ const remainingPasses = ${limits.maxScrolls} - pass;
396
+ const passBytes = Math.max(0, Math.ceil(remainingBytes / remainingPasses));
397
+ const observation = boundedRenderedText(rendered, passBytes);
398
+ if (observation.text.trim() !== '') renderedTextSnapshots.push(observation.text);
399
+ observedTextBytes += observation.bytes;
400
+ if (observation.truncated) renderedTextObservationTruncated = true;
401
+ } catch {
402
+ renderedTextObservationTruncated = true;
403
+ }
404
+ const height = document.documentElement.scrollHeight;
405
+ stable = height === previousHeight ? stable + 1 : 0;
406
+ previousHeight = height;
407
+ if (stable >= 2) {
408
+ settled = true;
409
+ break;
410
+ }
411
+ }
412
+ window.scrollTo(0, 0);
413
+ return {
414
+ scrolls,
415
+ scrollBudgetReached: !settled && scrolls >= ${limits.maxScrolls},
416
+ renderedTextSnapshots,
417
+ renderedTextObservationTruncated
418
+ };
419
+ })()`;
420
+ }
421
+ var nonNegativeInteger = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
422
+ function readBrowserExpansionTelemetry(value, limits) {
423
+ if (!isRecord(value))
424
+ return null;
425
+ const scrolls = nonNegativeInteger(value.scrolls);
426
+ if (!Array.isArray(value.renderedTextSnapshots))
427
+ return null;
428
+ if (value.renderedTextSnapshots.length > limits.maxScrolls)
429
+ return null;
430
+ const renderedTextSnapshots = [];
431
+ let observedTextBytes = 0;
432
+ for (const snapshot of value.renderedTextSnapshots) {
433
+ if (typeof snapshot !== "string")
434
+ return null;
435
+ if (snapshot.length > limits.maxObservedTextBytes)
436
+ return null;
437
+ observedTextBytes += new TextEncoder().encode(snapshot).byteLength;
438
+ if (observedTextBytes > limits.maxObservedTextBytes)
439
+ return null;
440
+ renderedTextSnapshots.push(snapshot);
441
+ }
442
+ if (scrolls === null || scrolls > limits.maxScrolls || renderedTextSnapshots.length > scrolls || typeof value.scrollBudgetReached !== "boolean" || typeof value.renderedTextObservationTruncated !== "boolean")
443
+ return null;
444
+ return {
445
+ scrolls,
446
+ scrollBudgetReached: value.scrollBudgetReached,
447
+ renderedTextSnapshots,
448
+ renderedTextObservationTruncated: value.renderedTextObservationTruncated
449
+ };
450
+ }
451
+ function browserExpansionWarnings(telemetry, limits) {
452
+ const warnings = [
453
+ "Browser capture left disclosure controls untouched; collapsed content may remain unavailable."
454
+ ];
455
+ if (telemetry.scrollBudgetReached) {
456
+ warnings.push(`Browser capture reached its ${limits.maxScrolls}-scroll budget before the document stabilized; lazy content may remain unloaded.`);
457
+ }
458
+ if (telemetry.renderedTextObservationTruncated) {
459
+ warnings.push(`Rendered-text observations reached their ${limits.maxObservedTextBytes}-byte capture budget; some virtualized content may be missing.`);
460
+ }
461
+ return warnings;
462
+ }
463
+ function browserCaptureScript() {
464
+ return `({
465
+ url: location.href,
466
+ title: document.title,
467
+ html: '<!doctype html>\\n' + document.documentElement.outerHTML
468
+ })`;
469
+ }
470
+ function readBrowserContent(data) {
471
+ if (typeof data.content !== "string" || data.content.trim() === "") {
472
+ throw new Error("agent-browser read returned no rendered content");
473
+ }
474
+ const url = typeof data.finalUrl === "string" ? data.finalUrl : data.url;
475
+ if (typeof url !== "string")
476
+ throw new Error("agent-browser read returned no final URL");
477
+ return { content: data.content, finalUrl: new URL(url), truncated: data.truncated === true };
478
+ }
479
+ function readBrowserUrl(data) {
480
+ const value = typeof data.url === "string" ? data.url : data.finalUrl;
481
+ if (typeof value !== "string")
482
+ throw new Error("agent-browser returned no current URL");
483
+ return new URL(value);
484
+ }
485
+ function navigationIdentity(url) {
486
+ const comparable = new URL(url);
487
+ comparable.hash = "";
488
+ return comparable.href;
489
+ }
490
+ function browserExpansionStayedOnPage(before, after) {
491
+ return (after.protocol === "http:" || after.protocol === "https:") && navigationIdentity(before) === navigationIdentity(after);
492
+ }
493
+ function browserNavigationReachedTarget(target, before, after, navigationCommandSucceeded) {
494
+ if (after.protocol !== "http:" && after.protocol !== "https:")
495
+ return false;
496
+ const targetIdentity = navigationIdentity(target);
497
+ const afterIdentity = navigationIdentity(after);
498
+ if (afterIdentity === targetIdentity)
499
+ return true;
500
+ return navigationCommandSucceeded && before !== null && navigationIdentity(before) !== afterIdentity;
501
+ }
502
+ async function terminateAgentBrowserSession(session, socketDirectory) {
503
+ const pidPath = join2(socketDirectory, `${session}.pid`);
504
+ if (!existsSync2(pidPath))
505
+ return;
506
+ const rawPid = readFileSync2(pidPath, "utf8").trim();
507
+ if (!/^\d+$/.test(rawPid))
508
+ return;
509
+ const pid = Number(rawPid);
510
+ if (!Number.isSafeInteger(pid) || pid <= 1 || pid === process.pid)
511
+ return;
512
+ const signal = (name) => {
513
+ try {
514
+ process.kill(process.platform === "win32" ? pid : -pid, name);
515
+ } catch {
516
+ try {
517
+ process.kill(pid, name);
518
+ } catch {}
519
+ }
520
+ };
521
+ signal("SIGTERM");
522
+ await Bun.sleep(500);
523
+ signal("SIGKILL");
524
+ }
525
+ function pathInside(root, target) {
526
+ const child = relative(root, target);
527
+ return child === "" || !isAbsolute(child) && child !== ".." && !child.startsWith(`..${sep}`);
528
+ }
529
+ function canonicalPotentialPath(value, label) {
530
+ const suffix = [];
531
+ let ancestor = resolve2(value);
532
+ while (true) {
533
+ try {
534
+ lstatSync(ancestor);
535
+ let canonicalAncestor;
536
+ try {
537
+ canonicalAncestor = realpathSync(ancestor);
538
+ } catch {
539
+ throw new Error(`${label} contains an unresolved symbolic link.`);
540
+ }
541
+ return resolve2(canonicalAncestor, ...suffix);
542
+ } catch (error) {
543
+ if (error.code !== "ENOENT")
544
+ throw error;
545
+ }
546
+ const parent = dirname2(ancestor);
547
+ if (parent === ancestor)
548
+ throw new Error(`${label} has no resolvable filesystem ancestor.`);
549
+ suffix.unshift(basename(ancestor));
550
+ ancestor = parent;
551
+ }
552
+ }
553
+ function profilePath(value) {
554
+ const pathLike = isAbsolute(value) || value.startsWith(`.${sep}`) || value.startsWith(`..${sep}`) || value.startsWith(`~${sep}`) || value.includes("/") || value.includes("\\");
555
+ if (!pathLike)
556
+ return null;
557
+ const expanded = value.startsWith(`~${sep}`) ? join2(homedir(), value.slice(2)) : resolve2(value);
558
+ return canonicalPotentialPath(expanded, "Persistent browser profile");
559
+ }
560
+ function assertSafePersistentProfile(options) {
561
+ if (options.browserProfile === undefined)
562
+ return null;
563
+ const path = profilePath(options.browserProfile);
564
+ if (path === null)
565
+ return null;
566
+ const repositoryRoot = realpathSync(findKbPackageRoot());
567
+ const outputRoot = canonicalPotentialPath(options.outputBase, "Capture output root");
568
+ if (pathInside(repositoryRoot, path) || pathInside(outputRoot, path) || pathInside(path, outputRoot)) {
569
+ throw new Error("Persistent browser profiles must live outside the repository and capture output roots.");
570
+ }
571
+ return path;
572
+ }
573
+ function browserCookieCommands(cookies, target) {
574
+ return cookies.map((cookie) => {
575
+ const command = ["cookies", "set", cookie.name, cookie.value];
576
+ if (cookie.hostOnly)
577
+ command.push("--url", target.origin);
578
+ else
579
+ command.push("--domain", `.${cookie.domain}`);
580
+ command.push("--path", cookie.path);
581
+ if (cookie.httpOnly)
582
+ command.push("--httpOnly");
583
+ if (cookie.secure)
584
+ command.push("--secure");
585
+ if (cookie.sameSite !== null)
586
+ command.push("--sameSite", cookie.sameSite);
587
+ if (cookie.expires > 0)
588
+ command.push("--expires", String(cookie.expires));
589
+ return command;
590
+ });
591
+ }
592
+ async function seedOwnedBrowserCookies(options, globalArgs, commandOptions, dependencies = {}) {
593
+ const selected = options.cookieSources.length > 0 || options.cookiesFile !== undefined;
594
+ if (!selected)
595
+ return [];
596
+ const target = captureUrl(options);
597
+ const result = await (dependencies.readCookies ?? acquireCookieRecords)(options, target);
598
+ await (dependencies.runBatch ?? runAgentBrowserBatch)(globalArgs, browserCookieCommands(result.cookies, target), commandOptions);
599
+ return [
600
+ ...result.warnings,
601
+ "Seeded explicitly selected cookies into the owned browser without broadening their domain, path, Secure, HttpOnly, SameSite, or expiry attributes."
602
+ ];
603
+ }
604
+ function browserProxyArguments(proxyUrl, profileDirectory) {
605
+ const chromiumArguments = [
606
+ ...profileDirectory === undefined ? [] : [`--profile-directory=${profileDirectory}`],
607
+ "--disable-quic",
608
+ "--disable-dns-prefetch",
609
+ "--disable-background-networking",
610
+ "--disable-component-update",
611
+ "--disable-default-apps",
612
+ "--disable-sync",
613
+ "--disable-features=AsyncDns",
614
+ "--force-webrtc-ip-handling-policy=disable_non_proxied_udp",
615
+ "--proxy-bypass-list=<-loopback>"
616
+ ].join(`
617
+ `);
618
+ return ["--proxy", proxyUrl, "--args", chromiumArguments];
619
+ }
620
+ async function acquireBrowser(options, temporaryDirectory, useDiscoveredProfile = false, dependencies = {}) {
621
+ if (options.currentTab && (!options.browserLive && options.cdp === undefined)) {
622
+ throw new Error("current-tab capture requires --browser-live or --cdp");
623
+ }
624
+ if (options.currentTab && options.browserProfile !== undefined) {
625
+ throw new Error("current-tab capture cannot use a browser profile; attach with --browser-live or --cdp");
626
+ }
627
+ const assertNetworkUrl = dependencies.assertNetworkUrl ?? assertSafeNetworkUrl;
628
+ const requestedUrl = options.currentTab ? null : captureUrl(options);
629
+ if (requestedUrl !== null) {
630
+ await assertNetworkUrl(requestedUrl, options.allowPrivateNetwork, options.timeoutMs);
631
+ }
632
+ const runBrowser = dependencies.run ?? runAgentBrowser;
633
+ const runBrowserBatch = dependencies.runBatch ?? runAgentBrowserBatch;
634
+ const sleep = dependencies.sleep ?? ((milliseconds) => Bun.sleep(milliseconds));
635
+ const warnings = [];
636
+ const persistentProfilePath = assertSafePersistentProfile(options);
637
+ const ownedProfile = options.browserProfileOwnership === "owned";
638
+ if (ownedProfile && persistentProfilePath === null) {
639
+ throw new Error("owned browser-profile execution requires an explicit path-backed profile");
640
+ }
641
+ const session = `clip-${process.pid}-${Date.now().toString(36)}-${crypto.randomUUID().slice(0, 8)}`;
642
+ const isolation = createAgentBrowserIsolation(temporaryDirectory);
643
+ try {
644
+ const globalArgs = ["--config", isolation.configPath, "--session", session];
645
+ if (options.browserExecutable !== undefined) {
646
+ globalArgs.push("--executable-path", options.browserExecutable);
647
+ }
648
+ let method = "browser-fresh";
649
+ let ownsBrowser = true;
650
+ if (options.cdp !== undefined) {
651
+ globalArgs.push("--cdp", options.cdp);
652
+ method = "browser-cdp";
653
+ ownsBrowser = false;
654
+ } else if (options.browserLive) {
655
+ globalArgs.push("--auto-connect");
656
+ method = "browser-live";
657
+ ownsBrowser = false;
658
+ } else {
659
+ const profile = options.browserProfile ?? (useDiscoveredProfile ? selectedProfile(await discoverChromeProfiles(options.timeoutMs)) : undefined);
660
+ if (profile !== undefined) {
661
+ globalArgs.push("--profile", profile);
662
+ method = "browser-profile";
663
+ warnings.push(ownedProfile ? "Used an owned private browser-profile snapshot; page activity cannot modify the source profile." : persistentProfilePath === null ? "A named Chrome profile can expose broad all-origin browser state to public subresources loaded by the target page; prefer a dedicated per-site profile." : "The selected persistent browser profile can be updated by page activity; keep dedicated capture profiles outside the repository.");
664
+ } else if (options.browserProfile !== undefined || useDiscoveredProfile) {
665
+ warnings.push("No unambiguous Chrome profile was found; used a fresh browser session.");
666
+ }
667
+ }
668
+ let networkProxy = null;
669
+ const commandOptions = {
670
+ cwd: isolation.cwd,
671
+ environment: isolation.environment,
672
+ timeoutMs: options.timeoutMs,
673
+ maxOutputBytes: Math.max(options.maxHtmlBytes * 2 + 1024 * 1024, 4 * 1024 * 1024)
674
+ };
675
+ try {
676
+ if (ownsBrowser) {
677
+ networkProxy = await startNetworkProxy({
678
+ allowPrivateNetwork: options.allowPrivateNetwork,
679
+ timeoutMs: options.timeoutMs,
680
+ maxTransferredBytes: Math.max(64 * 1024 * 1024, Math.min(Number.MAX_SAFE_INTEGER, (options.maxHtmlBytes + options.maxTotalAssetBytes) * 2))
681
+ });
682
+ globalArgs.push(...browserProxyArguments(networkProxy.url, options.browserProfileDirectory));
683
+ }
684
+ if (!options.currentTab) {
685
+ try {
686
+ await runBrowser(globalArgs, ["open", "about:blank"], commandOptions);
687
+ } catch (error) {
688
+ const message = error instanceof Error ? error.message : String(error);
689
+ warnings.push(`Browser startup did not settle cleanly; attempted to use the isolated session: ${message}`);
690
+ }
691
+ }
692
+ if (ownsBrowser && (method === "browser-fresh" || ownedProfile)) {
693
+ warnings.push(...await seedOwnedBrowserCookies(options, globalArgs, commandOptions));
694
+ } else if (options.cookieSources.length > 0 || options.cookiesFile !== undefined) {
695
+ warnings.push("Explicit cookie input remained a separate HTTP/media lane and was not imported into the selected profile or attached browser.");
696
+ }
697
+ if (!ownsBrowser) {
698
+ warnings.push(options.currentTab ? "Captured the current attached tab without navigation or interaction; the external browser itself was left open." : "Attached browser capture navigated and scrolled the active tab; the external browser itself was left open.");
699
+ }
700
+ let beforeNavigation = null;
701
+ try {
702
+ beforeNavigation = readBrowserUrl(await runBrowser(globalArgs, ["get", "url"], commandOptions));
703
+ } catch (error) {
704
+ const message = error instanceof Error ? error.message : String(error);
705
+ warnings.push(`Could not establish a pre-navigation browser URL: ${message}`);
706
+ }
707
+ let navigationCommandSucceeded = false;
708
+ if (!options.currentTab) {
709
+ try {
710
+ await runBrowserBatch(globalArgs, [["open", captureUrl(options).href]], commandOptions);
711
+ navigationCommandSucceeded = true;
712
+ } catch (error) {
713
+ const message = error instanceof Error ? error.message : String(error);
714
+ warnings.push(`Browser navigation command ended during page transition: ${message}`);
715
+ }
716
+ await sleep(Math.min(5000, Math.max(1500, Math.floor(options.timeoutMs / 6))));
717
+ }
718
+ let readable;
719
+ try {
720
+ readable = readBrowserContent(await runBrowser(globalArgs, ["read"], commandOptions));
721
+ } catch (error) {
722
+ const message = error instanceof Error ? error.message : String(error);
723
+ warnings.push(`Rendered readable text was unavailable; continuing with the bounded DOM: ${message}`);
724
+ readable = {
725
+ content: "",
726
+ finalUrl: readBrowserUrl(await runBrowser(globalArgs, ["get", "url"], commandOptions)),
727
+ truncated: false
728
+ };
729
+ }
730
+ if (options.currentTab) {
731
+ if (readable.finalUrl.protocol !== "http:" && readable.finalUrl.protocol !== "https:") {
732
+ throw new Error("the current tab must have an HTTP or HTTPS URL");
733
+ }
734
+ if (beforeNavigation !== null && !browserExpansionStayedOnPage(beforeNavigation, readable.finalUrl)) {
735
+ throw new Error("the current tab changed pages while it was being read; retry on the intended page");
736
+ }
737
+ } else if (!browserNavigationReachedTarget(captureUrl(options), beforeNavigation, readable.finalUrl, navigationCommandSucceeded)) {
738
+ throw new Error("browser did not establish the requested navigation; refusing to capture a pre-existing tab");
739
+ }
740
+ let browserPageProvenanceIntact = true;
741
+ if (!options.currentTab && shouldExpand(readable.finalUrl, options, method)) {
742
+ const expansionLimits = browserExpansionLimits(options.maxItems, options.maxHtmlBytes);
743
+ try {
744
+ const expansion = await runBrowser(globalArgs, ["eval", browserExpansionScript(expansionLimits)], {
745
+ ...commandOptions,
746
+ timeoutMs: Math.min(commandOptions.timeoutMs, 30000)
747
+ });
748
+ const telemetry = readBrowserExpansionTelemetry(expansion.result, expansionLimits);
749
+ if (telemetry === null) {
750
+ warnings.push("Browser expansion returned no trustworthy bounded-work telemetry; conversation completeness cannot be confirmed.");
751
+ } else {
752
+ warnings.push(...browserExpansionWarnings(telemetry, expansionLimits));
753
+ }
754
+ const expandedReadable = readBrowserContent(await runBrowser(globalArgs, ["read"], commandOptions));
755
+ if (browserExpansionStayedOnPage(readable.finalUrl, expandedReadable.finalUrl)) {
756
+ const merged = mergeRenderedTextSnapshots([
757
+ readable.content,
758
+ ...telemetry?.renderedTextSnapshots ?? [],
759
+ expandedReadable.content
760
+ ], options.maxHtmlBytes);
761
+ readable = {
762
+ content: merged.content,
763
+ finalUrl: expandedReadable.finalUrl,
764
+ truncated: readable.truncated || expandedReadable.truncated || telemetry?.renderedTextObservationTruncated === true || merged.truncated
765
+ };
766
+ if (merged.addedLines > 0) {
767
+ warnings.push(`Merged ${merged.addedLines} newly observed rendered-text line(s) with the pre-expansion snapshot so virtualized content remains available.`);
768
+ }
769
+ } else {
770
+ browserPageProvenanceIntact = false;
771
+ warnings.push("Browser expansion navigated away from the captured page; preserved the proven baseline and skipped post-expansion DOM and screenshot capture.");
772
+ }
773
+ } catch (error) {
774
+ browserPageProvenanceIntact = false;
775
+ const message = error instanceof Error ? error.message : String(error);
776
+ warnings.push(`Browser expansion stopped early; preserved the baseline rendered text and skipped post-expansion DOM and screenshot capture: ${message}`);
777
+ }
778
+ }
779
+ if (!options.currentTab) {
780
+ await assertNetworkUrl(readable.finalUrl, options.allowPrivateNetwork, options.timeoutMs);
781
+ }
782
+ const renderedText = readable.content;
783
+ let body = renderedText;
784
+ let contentType = "text/plain; charset=utf-8";
785
+ let contentTruncated = readable.truncated;
786
+ let sourceEvidence;
787
+ let browserTitle;
788
+ if (browserPageProvenanceIntact) {
789
+ try {
790
+ const capture = await runBrowser(globalArgs, ["eval", browserCaptureScript()], commandOptions);
791
+ if (isRecord(capture.result)) {
792
+ const html = capture.result.html;
793
+ const title = capture.result.title;
794
+ const captureUrl2 = typeof capture.result.url === "string" ? new URL(capture.result.url) : null;
795
+ if (captureUrl2 === null || !browserExpansionStayedOnPage(readable.finalUrl, captureUrl2)) {
796
+ browserPageProvenanceIntact = false;
797
+ warnings.push("Rendered DOM capture changed pages; preserved the proven readable baseline.");
798
+ } else if (typeof html === "string") {
799
+ const byteLength = new TextEncoder().encode(html).byteLength;
800
+ if (byteLength <= options.maxHtmlBytes) {
801
+ body = html;
802
+ contentType = "text/html; charset=utf-8";
803
+ contentTruncated = false;
804
+ if (options.evidence === "source" || options.evidence === "all")
805
+ sourceEvidence = html;
806
+ } else {
807
+ warnings.push(`Rendered DOM exceeded ${options.maxHtmlBytes} bytes; extracted the bounded readable fallback.`);
808
+ }
809
+ }
810
+ if (browserPageProvenanceIntact && typeof title === "string" && title.trim() !== "")
811
+ browserTitle = title;
812
+ } else {
813
+ browserPageProvenanceIntact = false;
814
+ warnings.push("Rendered DOM capture returned no trustworthy page provenance; preserved the readable baseline.");
815
+ }
816
+ } catch (error) {
817
+ browserPageProvenanceIntact = false;
818
+ const message = error instanceof Error ? error.message : String(error);
819
+ warnings.push(`Rendered DOM was unavailable; extracted the bounded readable fallback: ${message}`);
820
+ }
821
+ } else {
822
+ warnings.push("Rendered DOM capture was skipped because post-expansion page provenance was not established.");
823
+ }
824
+ if (body.trim() === "")
825
+ throw new Error("browser returned neither readable text nor a bounded rendered DOM");
826
+ if (readable.truncated)
827
+ warnings.push("Rendered text was truncated at its configured output boundary.");
828
+ let screenshotPath;
829
+ if ((options.evidence === "screenshot" || options.evidence === "all") && browserPageProvenanceIntact) {
830
+ const requestedScreenshotPath = join2(temporaryDirectory, "page.png");
831
+ screenshotPath = requestedScreenshotPath;
832
+ try {
833
+ await runBrowser(globalArgs, ["screenshot", requestedScreenshotPath], {
834
+ ...commandOptions,
835
+ timeoutMs: options.timeoutMs,
836
+ maxOutputBytes: 2 * 1024 * 1024
837
+ });
838
+ const afterScreenshot = readBrowserUrl(await runBrowser(globalArgs, ["get", "url"], commandOptions));
839
+ if (!browserExpansionStayedOnPage(readable.finalUrl, afterScreenshot)) {
840
+ rmSync(requestedScreenshotPath, { force: true });
841
+ warnings.push("Browser screenshot changed pages during capture and was discarded.");
842
+ screenshotPath = undefined;
843
+ } else if (!existsSync2(requestedScreenshotPath)) {
844
+ warnings.push("Browser screenshot was requested but agent-browser did not create it.");
845
+ screenshotPath = undefined;
846
+ }
847
+ } catch (error) {
848
+ rmSync(requestedScreenshotPath, { force: true });
849
+ const message = error instanceof Error ? error.message : String(error);
850
+ warnings.push(`Browser screenshot was unavailable or had no trustworthy page provenance: ${message}`);
851
+ screenshotPath = undefined;
852
+ }
853
+ } else if (options.evidence === "screenshot" || options.evidence === "all") {
854
+ warnings.push("Browser screenshot was skipped because post-expansion page provenance was not established.");
855
+ }
856
+ return {
857
+ body,
858
+ contentType,
859
+ finalUrl: options.currentTab ? new URL(sanitizeArtifactUrl(readable.finalUrl.href)) : readable.finalUrl,
860
+ method,
861
+ warnings,
862
+ ...browserTitle === undefined ? {} : { browserTitle },
863
+ ...screenshotPath === undefined ? {} : { screenshotPath },
864
+ ...sourceEvidence === undefined ? {} : { sourceEvidence },
865
+ ...contentTruncated ? { contentTruncated: true } : {},
866
+ renderedText,
867
+ ...readable.truncated ? { renderedTextTruncated: true } : {},
868
+ renderedTextByteLimit: options.maxHtmlBytes
869
+ };
870
+ } finally {
871
+ try {
872
+ if (ownsBrowser) {
873
+ try {
874
+ await runBrowser(["--config", isolation.configPath, "--session", session], ["close"], {
875
+ cwd: isolation.cwd,
876
+ environment: isolation.environment,
877
+ timeoutMs: 15000,
878
+ maxOutputBytes: 1024 * 1024
879
+ });
880
+ } catch {
881
+ warnings.push("Browser session did not close cleanly; terminated its isolated process group.");
882
+ }
883
+ }
884
+ await terminateAgentBrowserSession(session, isolation.socketDirectory);
885
+ } finally {
886
+ await networkProxy?.close();
887
+ }
888
+ }
889
+ } finally {
890
+ rmSync(isolation.socketDirectory, { recursive: true, force: true });
891
+ }
892
+ }
893
+ async function acquireHttp(options) {
894
+ const response = await safeFetch(captureUrl(options), {
895
+ timeoutMs: options.timeoutMs,
896
+ maxBytes: options.maxHtmlBytes,
897
+ allowPrivateNetwork: options.allowPrivateNetwork,
898
+ userAgent: options.userAgent,
899
+ retries: 2
900
+ });
901
+ return {
902
+ body: decodeBytes(response.bytes, response.contentType),
903
+ contentType: response.contentType,
904
+ finalUrl: response.finalUrl,
905
+ method: "http",
906
+ warnings: []
907
+ };
908
+ }
909
+ async function acquireCookieHttp(options) {
910
+ if (options.cookieSources.length === 0 && options.cookiesFile === undefined) {
911
+ throw new Error("cookie capture requires --cookie-source or --cookies-file");
912
+ }
913
+ const target = captureUrl(options);
914
+ const cookieResult = await acquireCookieHeader(options, target);
915
+ const response = await safeFetch(target, {
916
+ timeoutMs: options.timeoutMs,
917
+ maxBytes: options.maxHtmlBytes,
918
+ allowPrivateNetwork: options.allowPrivateNetwork,
919
+ userAgent: options.userAgent,
920
+ cookieHeader: cookieResult.header,
921
+ retries: 2
922
+ });
923
+ return {
924
+ body: decodeBytes(response.bytes, response.contentType),
925
+ contentType: response.contentType,
926
+ finalUrl: response.finalUrl,
927
+ method: "cookie-http",
928
+ warnings: cookieResult.warnings
929
+ };
930
+ }
931
+ function createCookieRecordReader(reader) {
932
+ return async (options, url) => {
933
+ if (options.cookieSources.length === 0 && options.cookiesFile === undefined) {
934
+ throw new Error("cookie capture requires --cookie-source or --cookies-file");
935
+ }
936
+ if (options.cookiesFile !== undefined) {
937
+ const parsed = readCookieFile(options.cookiesFile, url, {
938
+ requirePrivate: options.requireExplicitCookieScope === true
939
+ });
940
+ if (!parsed.ok) {
941
+ throw new Error("the explicitly selected cookie file contained no usable cookies for this request");
942
+ }
943
+ if (options.requireExplicitCookieScope === true && parsed.scopeProvenance !== "explicit") {
944
+ throw new Error("authenticated API cookie files require an explicit domain or URL on every cookie record");
945
+ }
946
+ const warnings2 = [];
947
+ if (options.cookieSources.length > 0) {
948
+ warnings2.push("The explicit cookie file took precedence over the selected browser cookie source.");
949
+ }
950
+ if (parsed.rejected > 0) {
951
+ warnings2.push(`Ignored ${parsed.rejected} malformed, expired, or out-of-scope cookie record(s).`);
952
+ }
953
+ if (parsed.format === "cookie-header" || parsed.format === "curl") {
954
+ warnings2.push("The cookie header did not encode attributes; browser replay inferred restrictive host-only, target-path, HTTPS-Secure, HttpOnly, and SameSite=Strict attributes. Use Cookie-Editor JSON or Netscape format when exact attributes matter.");
955
+ }
956
+ return { cookies: parsed.cookies, warnings: warnings2 };
957
+ }
958
+ if (options.cookieSources.length === 0) {
959
+ throw new Error("cookie capture requires at least one explicit browser cookie source");
960
+ }
961
+ const chromiumSource = options.cookieSources.find((source) => source === "chrome" || source === "arc" || source === "brave" || source === "chromium");
962
+ const selectedBrowsers = [];
963
+ for (const source of options.cookieSources) {
964
+ const backend = source === "arc" || source === "brave" || source === "chromium" ? "chrome" : source;
965
+ if (!selectedBrowsers.includes(backend))
966
+ selectedBrowsers.push(backend);
967
+ }
968
+ const cookieOptions = {
969
+ url: url.href,
970
+ mode: "first",
971
+ timeoutMs: options.timeoutMs,
972
+ debug: false,
973
+ browsers: selectedBrowsers,
974
+ profile: options.cookieProfile ?? "",
975
+ chromeProfile: options.cookieProfile ?? "",
976
+ edgeProfile: options.cookieProfile ?? "",
977
+ firefoxProfile: options.cookieProfile ?? "",
978
+ ...chromiumSource === undefined ? {} : { chromiumBrowser: chromiumSource },
979
+ ...options.cookieProfile === undefined ? {} : {
980
+ ...options.cookieSources.includes("safari") ? { safariCookiesFile: options.cookieProfile } : {}
981
+ }
982
+ };
983
+ let provided;
984
+ try {
985
+ provided = await reader(cookieOptions);
986
+ } catch {
987
+ throw new Error("the explicitly selected browser cookie source could not be read");
988
+ }
989
+ const filtered = filterCookieProviderResult(provided, url);
990
+ if (!filtered.validShape)
991
+ throw new Error("the selected browser cookie provider returned malformed data");
992
+ if (filtered.cookies.length === 0) {
993
+ throw new Error(filtered.rejected === 0 ? "no matching cookies were found in the explicitly selected browser" : `no usable origin-scoped cookies were found; rejected ${filtered.rejected} malformed, expired, or out-of-scope record(s)`);
994
+ }
995
+ const warnings = [];
996
+ if (filtered.rejected > 0) {
997
+ warnings.push(`Ignored ${filtered.rejected} malformed, expired, or out-of-scope browser cookie record(s).`);
998
+ }
999
+ if (filtered.providerWarningCount > 0) {
1000
+ warnings.push(`The browser cookie provider reported ${filtered.providerWarningCount} non-fatal warning(s).`);
1001
+ }
1002
+ return { cookies: filtered.cookies, warnings };
1003
+ };
1004
+ }
1005
+ function createCookieHeaderReader(reader) {
1006
+ const records = createCookieRecordReader(reader);
1007
+ return async (options, url) => {
1008
+ const result = await records(options, url);
1009
+ return { header: renderCookieHeader(result.cookies), warnings: result.warnings };
1010
+ };
1011
+ }
1012
+ var acquireCookieRecords = createCookieRecordReader((options) => getCookies(options));
1013
+ var acquireCookieHeader = async (options, url) => {
1014
+ const result = await acquireCookieRecords(options, url);
1015
+ return { header: renderCookieHeader(result.cookies), warnings: result.warnings };
1016
+ };
1017
+ async function readStdinBounded(maxBytes) {
1018
+ return readBoundedStream(Bun.stdin.stream(), maxBytes);
1019
+ }
1020
+ async function acquireFile(options) {
1021
+ if (options.htmlFile === undefined)
1022
+ throw new Error("file capture requires --html <path|->");
1023
+ const body = options.htmlFile === "-" ? await readStdinBounded(options.maxHtmlBytes) : (() => {
1024
+ const stats = statSync(options.htmlFile);
1025
+ if (!stats.isFile())
1026
+ throw new Error(`HTML input is not a regular file: ${options.htmlFile}`);
1027
+ if (stats.size > options.maxHtmlBytes) {
1028
+ throw new Error(`HTML input is ${stats.size} bytes; limit is ${options.maxHtmlBytes}`);
1029
+ }
1030
+ return readFileSync2(options.htmlFile, "utf8");
1031
+ })();
1032
+ return {
1033
+ body,
1034
+ contentType: "text/html; charset=utf-8",
1035
+ finalUrl: captureUrl(options),
1036
+ method: "file",
1037
+ warnings: options.htmlFile === "-" ? [] : [`Parsed rendered HTML from ${basename(options.htmlFile)}.`]
1038
+ };
1039
+ }
1040
+
1041
+ export { findKbPackageRoot, agentBrowserCommand, isolatedAgentBrowserEnvironment, discoverChromeProfiles, mergeRenderedTextSnapshots, browserExpansionLimits, browserExpansionScript, readBrowserExpansionTelemetry, browserExpansionWarnings, browserExpansionStayedOnPage, browserNavigationReachedTarget, assertSafePersistentProfile, browserCookieCommands, seedOwnedBrowserCookies, browserProxyArguments, acquireBrowser, acquireHttp, acquireCookieHttp, createCookieRecordReader, createCookieHeaderReader, acquireCookieRecords, acquireCookieHeader, acquireFile };