@humain/terminal 0.1.1 → 0.2.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 (193) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/bundle/chunks/{anthropic-messages-XULXUBIG.js → anthropic-messages-QFCEKGI3.js} +1 -1
  3. package/dist/bundle/chunks/{azure-openai-responses-JEG2XOYC.js → azure-openai-responses-G4ZMHV37.js} +1 -1
  4. package/dist/bundle/chunks/chunk-7T6XCLJF.js +22 -0
  5. package/dist/bundle/chunks/chunk-D6HHRSB5.js +103 -0
  6. package/dist/bundle/chunks/chunk-EY4DWHQU.js +2 -0
  7. package/dist/bundle/chunks/{chunk-LYAMSPR2.js → chunk-I47ZWR55.js} +483 -506
  8. package/dist/bundle/chunks/{chunk-JUPSJTFQ.js → chunk-K4JAZYQH.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-HPPQHK3J.js → chunk-KKIB2TVR.js} +1 -1
  10. package/dist/bundle/chunks/chunk-LI2YVPWP.js +2 -0
  11. package/dist/bundle/chunks/{chunk-BZQ53VGE.js → chunk-XSR434CO.js} +1 -1
  12. package/dist/bundle/chunks/compat-QCV26HNS.js +2 -0
  13. package/dist/bundle/chunks/dist-PRPIUY3U.js +2 -0
  14. package/dist/bundle/chunks/esm-E7UVEYTO.js +4 -0
  15. package/dist/bundle/chunks/{extract-TRW5FBY6.js → extract-CTIFQZDJ.js} +15 -14
  16. package/dist/bundle/chunks/{google-generative-ai-OX6VSCA6.js → google-generative-ai-RSUU4MWO.js} +1 -1
  17. package/dist/bundle/chunks/{google-vertex-GW76GSS2.js → google-vertex-CKQPOITO.js} +1 -1
  18. package/dist/bundle/chunks/host-WP3MVKS2.js +2 -0
  19. package/dist/bundle/chunks/{node-KOIP55YZ.js → node-NF43OPII.js} +1 -1
  20. package/dist/bundle/chunks/{node-XY3CBY3C.js → node-UMFM7CDZ.js} +1 -1
  21. package/dist/bundle/chunks/{openai-codex-responses-NPLWP6ZC.js → openai-codex-responses-S34V5ZSD.js} +1 -1
  22. package/dist/bundle/chunks/{openai-completions-OT2GTJKC.js → openai-completions-T2IGVUEI.js} +1 -1
  23. package/dist/bundle/chunks/{openai-responses-5A6CR7GI.js → openai-responses-GYZODCT3.js} +1 -1
  24. package/dist/bundle/chunks/readability-LAGF34IB.js +3 -0
  25. package/dist/bundle/chunks/turndown.cjs-HRMZVRUL.js +2 -0
  26. package/dist/bundle/cli.js +1 -1
  27. package/dist/bundle/index.js +1 -1
  28. package/dist/bundle/rpc-entry.js +1 -1
  29. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  30. package/dist/core/tools/file-mutation-queue.js +21 -8
  31. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  32. package/dist/core/tools/subagent-env.d.ts +18 -0
  33. package/dist/core/tools/subagent-env.d.ts.map +1 -0
  34. package/dist/core/tools/subagent-env.js +43 -0
  35. package/dist/core/tools/subagent-env.js.map +1 -0
  36. package/dist/core/tools/subagent-scheduler.d.ts +31 -0
  37. package/dist/core/tools/subagent-scheduler.d.ts.map +1 -0
  38. package/dist/core/tools/subagent-scheduler.js +203 -0
  39. package/dist/core/tools/subagent-scheduler.js.map +1 -0
  40. package/dist/core/tools/subagent.d.ts +83 -3
  41. package/dist/core/tools/subagent.d.ts.map +1 -1
  42. package/dist/core/tools/subagent.js +214 -76
  43. package/dist/core/tools/subagent.js.map +1 -1
  44. package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
  45. package/dist/humain/mcp-adapter/vendor/README.md +125 -19
  46. package/dist/humain/mcp-adapter/vendor/agent-plugin-loader.ts +69 -30
  47. package/dist/humain/mcp-adapter/vendor/agent-plugin-provenance.ts +36 -0
  48. package/dist/humain/mcp-adapter/vendor/claude-plugin-loader.ts +365 -0
  49. package/dist/humain/mcp-adapter/vendor/commands.ts +13 -15
  50. package/dist/humain/mcp-adapter/vendor/config.ts +198 -30
  51. package/dist/humain/mcp-adapter/vendor/consent-manager.ts +40 -9
  52. package/dist/humain/mcp-adapter/vendor/direct-tool-surface.ts +250 -0
  53. package/dist/humain/mcp-adapter/vendor/direct-tools.ts +9 -228
  54. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js +74 -32
  55. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js.map +1 -1
  56. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.d.ts +1 -0
  57. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js +31 -0
  58. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js.map +1 -0
  59. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.d.ts +9 -0
  60. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js +362 -0
  61. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js.map +1 -0
  62. package/dist/humain/mcp-adapter/vendor/dist/config.d.ts +3 -1
  63. package/dist/humain/mcp-adapter/vendor/dist/config.js +189 -27
  64. package/dist/humain/mcp-adapter/vendor/dist/config.js.map +1 -1
  65. package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js +7 -16
  66. package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js.map +1 -1
  67. package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js +5 -17
  68. package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js.map +1 -1
  69. package/dist/humain/mcp-adapter/vendor/dist/types.d.ts +30 -4
  70. package/dist/humain/mcp-adapter/vendor/dist/types.js +26 -0
  71. package/dist/humain/mcp-adapter/vendor/dist/types.js.map +1 -1
  72. package/dist/humain/mcp-adapter/vendor/dist/utils.d.ts +6 -0
  73. package/dist/humain/mcp-adapter/vendor/dist/utils.js +65 -6
  74. package/dist/humain/mcp-adapter/vendor/dist/utils.js.map +1 -1
  75. package/dist/humain/mcp-adapter/vendor/host-html-template.ts +20 -19
  76. package/dist/humain/mcp-adapter/vendor/http-ca.ts +84 -0
  77. package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +37102 -15103
  78. package/dist/humain/mcp-adapter/vendor/index.ts +828 -164
  79. package/dist/humain/mcp-adapter/vendor/init.ts +48 -10
  80. package/dist/humain/mcp-adapter/vendor/lazy-loader.ts +7 -0
  81. package/dist/humain/mcp-adapter/vendor/mcp-auth-fetch.ts +113 -0
  82. package/dist/humain/mcp-adapter/vendor/mcp-auth-flow.ts +256 -77
  83. package/dist/humain/mcp-adapter/vendor/mcp-auth.ts +316 -54
  84. package/dist/humain/mcp-adapter/vendor/mcp-code.ts +45 -18
  85. package/dist/humain/mcp-adapter/vendor/mcp-install.ts +60 -0
  86. package/dist/humain/mcp-adapter/vendor/mcp-oauth-provider.ts +70 -6
  87. package/dist/humain/mcp-adapter/vendor/mcp-output-guard.ts +53 -42
  88. package/dist/humain/mcp-adapter/vendor/mcp-panel-theme.ts +105 -0
  89. package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +366 -331
  90. package/dist/humain/mcp-adapter/vendor/mcp-references.ts +9 -14
  91. package/dist/humain/mcp-adapter/vendor/mcp-script-worker.mjs +3 -1
  92. package/dist/humain/mcp-adapter/vendor/mcp-setup-panel.ts +383 -341
  93. package/dist/humain/mcp-adapter/vendor/mcp-status.ts +5 -0
  94. package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +7 -16
  95. package/dist/humain/mcp-adapter/vendor/namespace-tools.ts +61 -3
  96. package/dist/humain/mcp-adapter/vendor/oauth.ts +6 -2
  97. package/dist/humain/mcp-adapter/vendor/package-mcp-loader.ts +5 -17
  98. package/dist/humain/mcp-adapter/vendor/package.json +13 -3
  99. package/dist/humain/mcp-adapter/vendor/prompts.ts +35 -3
  100. package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +108 -7
  101. package/dist/humain/mcp-adapter/vendor/runtime-owner.ts +16 -1
  102. package/dist/humain/mcp-adapter/vendor/sampling-handler.ts +9 -32
  103. package/dist/humain/mcp-adapter/vendor/sandbox-proxy-template.ts +20 -93
  104. package/dist/humain/mcp-adapter/vendor/server-manager.ts +227 -40
  105. package/dist/humain/mcp-adapter/vendor/session-approvals.ts +187 -0
  106. package/dist/humain/mcp-adapter/vendor/skills/mcp-scripting/SKILL.md +9 -1
  107. package/dist/humain/mcp-adapter/vendor/state.ts +10 -1
  108. package/dist/humain/mcp-adapter/vendor/tool-approval.ts +10 -22
  109. package/dist/humain/mcp-adapter/vendor/tool-metadata.ts +14 -0
  110. package/dist/humain/mcp-adapter/vendor/types.ts +57 -4
  111. package/dist/humain/mcp-adapter/vendor/ui-server.ts +36 -28
  112. package/dist/humain/mcp-adapter/vendor/utils.ts +64 -6
  113. package/dist/humain/web-access/vendor/abortable.d.ts +3 -0
  114. package/dist/humain/web-access/vendor/abortable.d.ts.map +1 -0
  115. package/dist/humain/web-access/vendor/abortable.js +22 -0
  116. package/dist/humain/web-access/vendor/abortable.js.map +1 -0
  117. package/dist/humain/web-access/vendor/crawl4ai.d.ts +9 -0
  118. package/dist/humain/web-access/vendor/crawl4ai.d.ts.map +1 -0
  119. package/dist/humain/web-access/vendor/crawl4ai.js +187 -0
  120. package/dist/humain/web-access/vendor/crawl4ai.js.map +1 -0
  121. package/dist/humain/web-access/vendor/credential-source.d.ts.map +1 -1
  122. package/dist/humain/web-access/vendor/credential-source.js +1 -0
  123. package/dist/humain/web-access/vendor/credential-source.js.map +1 -1
  124. package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
  125. package/dist/humain/web-access/vendor/curator-page.js +20 -2
  126. package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
  127. package/dist/humain/web-access/vendor/curator-run.d.ts +11 -0
  128. package/dist/humain/web-access/vendor/curator-run.d.ts.map +1 -0
  129. package/dist/humain/web-access/vendor/curator-run.js +38 -0
  130. package/dist/humain/web-access/vendor/curator-run.js.map +1 -0
  131. package/dist/humain/web-access/vendor/curator-server.d.ts +1 -0
  132. package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
  133. package/dist/humain/web-access/vendor/curator-server.js +2 -0
  134. package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
  135. package/dist/humain/web-access/vendor/duckduckgo.d.ts.map +1 -1
  136. package/dist/humain/web-access/vendor/duckduckgo.js +1 -1
  137. package/dist/humain/web-access/vendor/duckduckgo.js.map +1 -1
  138. package/dist/humain/web-access/vendor/extract.d.ts +1 -1
  139. package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
  140. package/dist/humain/web-access/vendor/extract.js +58 -11
  141. package/dist/humain/web-access/vendor/extract.js.map +1 -1
  142. package/dist/humain/web-access/vendor/fetch-params.js +1 -1
  143. package/dist/humain/web-access/vendor/fetch-params.js.map +1 -1
  144. package/dist/humain/web-access/vendor/gemini-search.d.ts +2 -2
  145. package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
  146. package/dist/humain/web-access/vendor/gemini-search.js +12 -5
  147. package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
  148. package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
  149. package/dist/humain/web-access/vendor/index.js +23 -19
  150. package/dist/humain/web-access/vendor/index.js.map +1 -1
  151. package/dist/humain/web-access/vendor/openai-search.d.ts.map +1 -1
  152. package/dist/humain/web-access/vendor/openai-search.js +40 -14
  153. package/dist/humain/web-access/vendor/openai-search.js.map +1 -1
  154. package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
  155. package/dist/humain/web-access/vendor/page-query.js +6 -2
  156. package/dist/humain/web-access/vendor/page-query.js.map +1 -1
  157. package/dist/humain/web-access/vendor/query-rewrite.d.ts.map +1 -1
  158. package/dist/humain/web-access/vendor/query-rewrite.js +6 -2
  159. package/dist/humain/web-access/vendor/query-rewrite.js.map +1 -1
  160. package/dist/humain/web-access/vendor/serpapi.d.ts +4 -0
  161. package/dist/humain/web-access/vendor/serpapi.d.ts.map +1 -0
  162. package/dist/humain/web-access/vendor/serpapi.js +215 -0
  163. package/dist/humain/web-access/vendor/serpapi.js.map +1 -0
  164. package/dist/humain/web-access/vendor/ssrf-protection.d.ts.map +1 -1
  165. package/dist/humain/web-access/vendor/ssrf-protection.js +5 -1
  166. package/dist/humain/web-access/vendor/ssrf-protection.js.map +1 -1
  167. package/dist/humain/web-access/vendor/storage.d.ts.map +1 -1
  168. package/dist/humain/web-access/vendor/storage.js +9 -0
  169. package/dist/humain/web-access/vendor/storage.js.map +1 -1
  170. package/dist/humain/web-access/vendor/summary-review.d.ts +1 -1
  171. package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
  172. package/dist/humain/web-access/vendor/summary-review.js +35 -24
  173. package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
  174. package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
  175. package/dist/humain/web-access/vendor/utils.js +5 -3
  176. package/dist/humain/web-access/vendor/utils.js.map +1 -1
  177. package/dist/modes/json-event.d.ts +8 -0
  178. package/dist/modes/json-event.d.ts.map +1 -1
  179. package/dist/modes/json-event.js +17 -0
  180. package/dist/modes/json-event.js.map +1 -1
  181. package/dist/modes/print-mode.d.ts.map +1 -1
  182. package/dist/modes/print-mode.js +3 -2
  183. package/dist/modes/print-mode.js.map +1 -1
  184. package/docs/environment-variables.md +8 -0
  185. package/docs/json.md +16 -0
  186. package/npm-shrinkwrap.json +6 -6
  187. package/package.json +2 -2
  188. package/src/humain/mcp-adapter/UPSTREAM.md +56 -7
  189. package/src/humain/mcp-adapter/upstream.json +6 -4
  190. package/src/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
  191. package/src/humain/web-access/UPSTREAM.md +18 -8
  192. package/src/humain/web-access/upstream.json +6 -5
  193. package/dist/bundle/chunks/chunk-2LC3S3VV.js +0 -105
@@ -1,9 +1,50 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { spawnSync } from "node:child_process";
3
+ import { existsSync, realpathSync } from "node:fs";
3
4
  import { homedir, platform } from "node:os";
4
- import { extname, isAbsolute, join } from "node:path";
5
+ import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
+ import stripJsonComments from "strip-json-comments";
5
7
  import type { McpConfig, ServerEntry } from "./types.ts";
6
8
 
9
+ export function parseJsonWithComments(raw: string): unknown {
10
+ return JSON.parse(stripJsonComments(raw, { trailingCommas: true }));
11
+ }
12
+
13
+ /** Resolve a candidate only when its real path stays within the real root. */
14
+ export function resolveRealContainedPath(root: string, candidate: string, allowMissing = false): string | null {
15
+ const contained = resolveContainedPath(root, candidate);
16
+ if (!contained) return null;
17
+ const canonical = (path: string): string => {
18
+ let existing = path;
19
+ while (allowMissing && !existsSync(existing)) {
20
+ const parent = dirname(existing);
21
+ if (parent === existing) throw new Error("No existing path ancestor");
22
+ existing = parent;
23
+ }
24
+ return resolve(realpathSync(existing), relative(existing, path));
25
+ };
26
+ try {
27
+ return resolveContainedPath(canonical(root), canonical(contained));
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ export function resolveContainedPath(root: string, candidate: string): string | null {
34
+ const resolved = resolve(root, candidate);
35
+ const rel = relative(root, resolved);
36
+ return rel === "" || (!rel.startsWith("..") && !rel.startsWith(sep) && !isAbsolute(rel)) ? resolved : null;
37
+ }
38
+
39
+ export function stableStringify(value: unknown): string {
40
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
41
+ if (Array.isArray(value)) {
42
+ return `[${value.map(item => stableStringify(item)).join(",")}]`;
43
+ }
44
+ const object = value as Record<string, unknown>;
45
+ return `{${Object.keys(object).sort().map(key => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
46
+ }
47
+
7
48
  async function execOpen(pi: ExtensionAPI, target: string, browser?: string, signal?: AbortSignal) {
8
49
  const os = platform();
9
50
 
@@ -64,11 +105,28 @@ export async function parallelLimit<T, R>(
64
105
  }
65
106
 
66
107
  export function getConfigPathFromArgv(): string | undefined {
67
- const idx = process.argv.indexOf("--mcp-config");
68
- if (idx >= 0 && idx + 1 < process.argv.length) {
69
- return process.argv[idx + 1];
108
+ let configPath: string | undefined;
109
+ for (let index = 2; index < process.argv.length; index++) {
110
+ const arg = process.argv[index];
111
+ if (arg === undefined) continue;
112
+ if (arg === "--") break;
113
+
114
+ if (arg === "--mcp-config") {
115
+ const value = process.argv[index + 1];
116
+ if (value !== undefined && !value.startsWith("-") && !value.startsWith("@")) {
117
+ configPath = value;
118
+ index++;
119
+ } else {
120
+ configPath = undefined;
121
+ }
122
+ continue;
123
+ }
124
+
125
+ if (arg.startsWith("--mcp-config=")) {
126
+ configPath = arg.slice("--mcp-config=".length);
127
+ }
70
128
  }
71
- return undefined;
129
+ return configPath;
72
130
  }
73
131
 
74
132
  export function interpolateEnvVars(value: string): string;
@@ -80,7 +138,7 @@ export function interpolateEnvVars(value: string, environment: NodeJS.ProcessEnv
80
138
  .replace(/\{env:(\w+)\}/g, (_, name) => environment[name] ?? "");
81
139
  }
82
140
 
83
- function getMissingEnvVars(value: string, environment: NodeJS.ProcessEnv): string[] {
141
+ export function getMissingEnvVars(value: string, environment: NodeJS.ProcessEnv = process.env): string[] {
84
142
  const missing = new Set<string>();
85
143
  for (const match of value.matchAll(/\$\{(\w+)\}|\$env:(\w+)|\{env:(\w+)\}/g)) {
86
144
  const name = match[1] ?? match[2] ?? match[3];
@@ -0,0 +1,3 @@
1
+ /** Observe cancellation while awaiting work that cannot itself be cancelled (such as import()). */
2
+ export declare function awaitWithAbort<T>(operation: Promise<T>, signal?: AbortSignal): Promise<T>;
3
+ //# sourceMappingURL=abortable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abortable.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/abortable.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,wBAAsB,cAAc,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAe/F"}
@@ -0,0 +1,22 @@
1
+ /** Observe cancellation while awaiting work that cannot itself be cancelled (such as import()). */
2
+ export async function awaitWithAbort(operation, signal) {
3
+ if (!signal)
4
+ return operation;
5
+ let onAbort = () => { };
6
+ const aborted = new Promise((_resolve, reject) => {
7
+ onAbort = () => reject(new Error("Aborted"));
8
+ signal.addEventListener("abort", onAbort, { once: true });
9
+ if (signal.aborted)
10
+ onAbort();
11
+ });
12
+ try {
13
+ const result = await Promise.race([operation, aborted]);
14
+ if (signal.aborted)
15
+ throw new Error("Aborted");
16
+ return result;
17
+ }
18
+ finally {
19
+ signal.removeEventListener("abort", onAbort);
20
+ }
21
+ }
22
+ //# sourceMappingURL=abortable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abortable.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/abortable.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,MAAM,CAAC,KAAK,UAAU,cAAc,CAAI,SAAqB,EAAE,MAAoB;IAClF,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,IAAI,OAAO,GAAe,GAAG,EAAE,GAAE,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;QACvD,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,IAAI,MAAM,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;IAC/B,CAAC,CAAC,CAAC;IACH,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;QACxD,IAAI,MAAM,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC;QAC/C,OAAO,MAAM,CAAC;IACf,CAAC;YAAS,CAAC;QACV,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;AACF,CAAC","sourcesContent":["/** Observe cancellation while awaiting work that cannot itself be cancelled (such as import()). */\nexport async function awaitWithAbort<T>(operation: Promise<T>, signal?: AbortSignal): Promise<T> {\n\tif (!signal) return operation;\n\tlet onAbort: () => void = () => {};\n\tconst aborted = new Promise<never>((_resolve, reject) => {\n\t\tonAbort = () => reject(new Error(\"Aborted\"));\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\tif (signal.aborted) onAbort();\n\t});\n\ttry {\n\t\tconst result = await Promise.race([operation, aborted]);\n\t\tif (signal.aborted) throw new Error(\"Aborted\");\n\t\treturn result;\n\t} finally {\n\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t}\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import type { ExtractedContent, ExtractOptions } from "./extract.ts";
2
+ import { type SsrfConfig } from "./ssrf-protection.ts";
3
+ export interface Crawl4aiExtractOptions extends Pick<ExtractOptions, "timeoutMs" | "lookup"> {
4
+ ssrf?: SsrfConfig;
5
+ }
6
+ export declare function clearCrawl4aiConfigCache(): void;
7
+ export declare function isCrawl4aiAvailable(): boolean;
8
+ export declare function extractWithCrawl4ai(url: string, signal?: AbortSignal, options?: Crawl4aiExtractOptions): Promise<ExtractedContent | null>;
9
+ //# sourceMappingURL=crawl4ai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crawl4ai.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/crawl4ai.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAAkE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAOvH,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,cAAc,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC3F,IAAI,CAAC,EAAE,UAAU,CAAC;CAClB;AA8BD,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAgFD,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED,wBAAsB,mBAAmB,CACxC,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,sBAAsB,GAC9B,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAqElC"}
@@ -0,0 +1,187 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import net from "node:net";
3
+ import { activityMonitor } from "./activity.js";
4
+ import { redactCredential, resolveCredential } from "./credential-source.js";
5
+ import { fetchRemoteUrl, loadSsrfConfig, validateRemoteUrl } from "./ssrf-protection.js";
6
+ import { getWebSearchConfigPath } from "./utils.js";
7
+ const CONFIG_PATH = getWebSearchConfigPath();
8
+ const EXTRACT_TIMEOUT_MS = 60_000;
9
+ const MARKDOWN_FILTER = "fit";
10
+ let cachedConfig = null;
11
+ function loadConfig() {
12
+ if (cachedConfig)
13
+ return cachedConfig;
14
+ if (!existsSync(CONFIG_PATH)) {
15
+ cachedConfig = {};
16
+ return cachedConfig;
17
+ }
18
+ const raw = readFileSync(CONFIG_PATH, "utf8");
19
+ let parsed;
20
+ try {
21
+ parsed = JSON.parse(raw);
22
+ }
23
+ catch (err) {
24
+ const message = err instanceof Error ? err.message : String(err);
25
+ throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);
26
+ }
27
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
28
+ throw new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);
29
+ }
30
+ cachedConfig = parsed;
31
+ return cachedConfig;
32
+ }
33
+ export function clearCrawl4aiConfigCache() {
34
+ cachedConfig = null;
35
+ }
36
+ function normalizeBaseUrl(value) {
37
+ if (typeof value !== "string")
38
+ return null;
39
+ const trimmed = value.trim();
40
+ if (!trimmed)
41
+ return null;
42
+ let parsed;
43
+ try {
44
+ parsed = new URL(trimmed);
45
+ }
46
+ catch {
47
+ throw new Error(`Invalid Crawl4AI base URL in ${CONFIG_PATH}: expected an HTTP or HTTPS URL`);
48
+ }
49
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
50
+ throw new Error(`Invalid Crawl4AI base URL in ${CONFIG_PATH}: expected an HTTP or HTTPS URL`);
51
+ }
52
+ if (parsed.username || parsed.password) {
53
+ throw new Error(`Invalid Crawl4AI base URL in ${CONFIG_PATH}: URL credentials are not allowed`);
54
+ }
55
+ parsed.pathname = parsed.pathname.replace(/\/+$/, "");
56
+ parsed.search = "";
57
+ parsed.hash = "";
58
+ return parsed.toString().replace(/\/+$/, "");
59
+ }
60
+ function getBaseUrl() {
61
+ return normalizeBaseUrl(process.env.CRAWL4AI_BASE_URL) ?? normalizeBaseUrl(loadConfig().crawl4aiBaseUrl);
62
+ }
63
+ function requireBaseUrl() {
64
+ const baseUrl = getBaseUrl();
65
+ if (!baseUrl) {
66
+ throw new Error("Crawl4AI base URL not configured. Either:\n" +
67
+ ` 1. Set crawl4aiBaseUrl in ${CONFIG_PATH}\n` +
68
+ " 2. Set CRAWL4AI_BASE_URL environment variable");
69
+ }
70
+ return baseUrl;
71
+ }
72
+ async function getApiToken(signal) {
73
+ return resolveCredential({
74
+ provider: "Crawl4AI",
75
+ configuredValue: loadConfig().crawl4aiApiToken,
76
+ environmentValue: process.env.CRAWL4AI_API_TOKEN,
77
+ signal,
78
+ });
79
+ }
80
+ function requestSignal(timeoutMs, signal) {
81
+ const timeout = AbortSignal.timeout(timeoutMs);
82
+ return signal ? AbortSignal.any([timeout, signal]) : timeout;
83
+ }
84
+ function errorMessage(err) {
85
+ return err instanceof Error ? err.message : String(err);
86
+ }
87
+ function isAbortException(err) {
88
+ return err instanceof DOMException && (err.name === "AbortError" || err.name === "TimeoutError");
89
+ }
90
+ function ssrfOptions(options) {
91
+ return {
92
+ ...(options?.ssrf ?? loadSsrfConfig()),
93
+ ...(options?.lookup ? { lookup: options.lookup } : {}),
94
+ };
95
+ }
96
+ function isLoopbackApiUrl(url) {
97
+ const hostname = url.hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
98
+ if (hostname === "localhost" || hostname === "::1")
99
+ return true;
100
+ if (net.isIP(hostname) !== 4)
101
+ return false;
102
+ return hostname.split(".")[0] === "127";
103
+ }
104
+ function firstHeadingTitle(markdown) {
105
+ return /^[ \t]*#[ \t]+(\S.*?)[ \t\r]*$/m.exec(markdown)?.[1] ?? "";
106
+ }
107
+ export function isCrawl4aiAvailable() {
108
+ return getBaseUrl() !== null;
109
+ }
110
+ export async function extractWithCrawl4ai(url, signal, options) {
111
+ const baseUrl = requireBaseUrl();
112
+ const ssrf = ssrfOptions(options);
113
+ await validateRemoteUrl(url, ssrf);
114
+ const token = await getApiToken(signal);
115
+ const headers = { "Content-Type": "application/json" };
116
+ if (token)
117
+ headers.Authorization = `Bearer ${token}`;
118
+ const requestUrl = new URL(`${baseUrl}/md`);
119
+ const init = {
120
+ method: "POST",
121
+ headers,
122
+ body: JSON.stringify({ url, f: MARKDOWN_FILTER }),
123
+ signal: requestSignal(options?.timeoutMs ?? EXTRACT_TIMEOUT_MS, signal),
124
+ };
125
+ let seeOther = false;
126
+ const activityId = activityMonitor.logStart({ type: "fetch", url: requestUrl.toString() });
127
+ try {
128
+ const response = await fetchRemoteUrl(requestUrl, init, {
129
+ ...ssrf,
130
+ allowLoopback: isLoopbackApiUrl(requestUrl),
131
+ onRedirect: ({ to, init: redirectInit, response }) => {
132
+ if (to.origin !== requestUrl.origin) {
133
+ throw new Error(`Crawl4AI refused cross-origin redirect to ${to.origin}`);
134
+ }
135
+ // 303 genuinely means "GET the other resource", and it is final: once a hop has turned the
136
+ // chain into a GET, no later hop may resurrect the extraction body.
137
+ if (response.status === 303)
138
+ seeOther = true;
139
+ // 301/302 are the hops a reverse proxy in front of a self-hosted instance actually emits, and
140
+ // fetchRemoteUrl turns those into a bodyless GET that /md cannot serve, so replay the POST.
141
+ // 307/308 already keep the method, so their init needs no help.
142
+ const replayPost = !seeOther && (response.status === 301 || response.status === 302);
143
+ return replayPost ? init : redirectInit;
144
+ },
145
+ });
146
+ if (!response.ok) {
147
+ const text = await response.text().catch(() => "");
148
+ throw new Error(`Crawl4AI md error ${response.status}: ${redactCredential(text, token).slice(0, 300)}`);
149
+ }
150
+ let data;
151
+ try {
152
+ data = await response.json();
153
+ }
154
+ catch {
155
+ // The parser message quotes the offending body, which can echo the configured token, so it never
156
+ // reaches the thrown error or the activity log.
157
+ throw new Error("Crawl4AI md returned invalid JSON");
158
+ }
159
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
160
+ throw new Error("Crawl4AI md returned an unexpected response shape");
161
+ }
162
+ const envelope = data;
163
+ if (envelope.success === false) {
164
+ const detail = typeof envelope.error === "string" ? envelope.error : typeof envelope.detail === "string" ? envelope.detail : "";
165
+ throw new Error(`Crawl4AI md unsuccessful: ${redactCredential(detail.trim() || "unknown error", token)}`);
166
+ }
167
+ if (envelope.success !== true) {
168
+ throw new Error("Crawl4AI md returned an unexpected response shape");
169
+ }
170
+ if (typeof envelope.markdown !== "string") {
171
+ throw new Error("Crawl4AI md returned markdown in an unexpected shape");
172
+ }
173
+ activityMonitor.logComplete(activityId, response.status);
174
+ const content = envelope.markdown.trim();
175
+ if (!content)
176
+ return null;
177
+ return { url, title: firstHeadingTitle(content), content, error: null };
178
+ }
179
+ catch (err) {
180
+ if (signal?.aborted || isAbortException(err))
181
+ activityMonitor.logComplete(activityId, 0);
182
+ else
183
+ activityMonitor.logError(activityId, errorMessage(err));
184
+ throw err;
185
+ }
186
+ }
187
+ //# sourceMappingURL=crawl4ai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crawl4ai.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/crawl4ai.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport net from \"node:net\";\nimport { activityMonitor } from \"./activity.ts\";\nimport { redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport type { ExtractedContent, ExtractOptions } from \"./extract.ts\";\nimport { fetchRemoteUrl, loadSsrfConfig, validateRemoteUrl, type Lookup, type SsrfConfig } from \"./ssrf-protection.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst EXTRACT_TIMEOUT_MS = 60_000;\nconst MARKDOWN_FILTER = \"fit\";\n\nexport interface Crawl4aiExtractOptions extends Pick<ExtractOptions, \"timeoutMs\" | \"lookup\"> {\n\tssrf?: SsrfConfig;\n}\n\ninterface Crawl4aiConfig {\n\tcrawl4aiBaseUrl?: unknown;\n\tcrawl4aiApiToken?: unknown;\n}\n\nlet cachedConfig: Crawl4aiConfig | null = null;\n\nfunction loadConfig(): Crawl4aiConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\tconst raw = readFileSync(CONFIG_PATH, \"utf8\");\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);\n\t}\n\tcachedConfig = parsed as Crawl4aiConfig;\n\treturn cachedConfig;\n}\n\nexport function clearCrawl4aiConfigCache(): void {\n\tcachedConfig = null;\n}\n\nfunction normalizeBaseUrl(value: unknown): string | null {\n\tif (typeof value !== \"string\") return null;\n\tconst trimmed = value.trim();\n\tif (!trimmed) return null;\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(trimmed);\n\t} catch {\n\t\tthrow new Error(`Invalid Crawl4AI base URL in ${CONFIG_PATH}: expected an HTTP or HTTPS URL`);\n\t}\n\tif (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n\t\tthrow new Error(`Invalid Crawl4AI base URL in ${CONFIG_PATH}: expected an HTTP or HTTPS URL`);\n\t}\n\tif (parsed.username || parsed.password) {\n\t\tthrow new Error(`Invalid Crawl4AI base URL in ${CONFIG_PATH}: URL credentials are not allowed`);\n\t}\n\tparsed.pathname = parsed.pathname.replace(/\\/+$/, \"\");\n\tparsed.search = \"\";\n\tparsed.hash = \"\";\n\treturn parsed.toString().replace(/\\/+$/, \"\");\n}\n\nfunction getBaseUrl(): string | null {\n\treturn normalizeBaseUrl(process.env.CRAWL4AI_BASE_URL) ?? normalizeBaseUrl(loadConfig().crawl4aiBaseUrl);\n}\n\nfunction requireBaseUrl(): string {\n\tconst baseUrl = getBaseUrl();\n\tif (!baseUrl) {\n\t\tthrow new Error(\n\t\t\t\"Crawl4AI base URL not configured. Either:\\n\" +\n\t\t\t` 1. Set crawl4aiBaseUrl in ${CONFIG_PATH}\\n` +\n\t\t\t\" 2. Set CRAWL4AI_BASE_URL environment variable\",\n\t\t);\n\t}\n\treturn baseUrl;\n}\n\nasync function getApiToken(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"Crawl4AI\",\n\t\tconfiguredValue: loadConfig().crawl4aiApiToken,\n\t\tenvironmentValue: process.env.CRAWL4AI_API_TOKEN,\n\t\tsignal,\n\t});\n}\n\nfunction requestSignal(timeoutMs: number, signal?: AbortSignal): AbortSignal {\n\tconst timeout = AbortSignal.timeout(timeoutMs);\n\treturn signal ? AbortSignal.any([timeout, signal]) : timeout;\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction isAbortException(err: unknown): boolean {\n\treturn err instanceof DOMException && (err.name === \"AbortError\" || err.name === \"TimeoutError\");\n}\n\nfunction ssrfOptions(options?: Crawl4aiExtractOptions): SsrfConfig & { lookup?: Lookup } {\n\treturn {\n\t\t...(options?.ssrf ?? loadSsrfConfig()),\n\t\t...(options?.lookup ? { lookup: options.lookup } : {}),\n\t};\n}\n\nfunction isLoopbackApiUrl(url: URL): boolean {\n\tconst hostname = url.hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\").replace(/\\.$/, \"\");\n\tif (hostname === \"localhost\" || hostname === \"::1\") return true;\n\tif (net.isIP(hostname) !== 4) return false;\n\treturn hostname.split(\".\")[0] === \"127\";\n}\n\nfunction firstHeadingTitle(markdown: string): string {\n\treturn /^[ \\t]*#[ \\t]+(\\S.*?)[ \\t\\r]*$/m.exec(markdown)?.[1] ?? \"\";\n}\n\nexport function isCrawl4aiAvailable(): boolean {\n\treturn getBaseUrl() !== null;\n}\n\nexport async function extractWithCrawl4ai(\n\turl: string,\n\tsignal?: AbortSignal,\n\toptions?: Crawl4aiExtractOptions,\n): Promise<ExtractedContent | null> {\n\tconst baseUrl = requireBaseUrl();\n\tconst ssrf = ssrfOptions(options);\n\tawait validateRemoteUrl(url, ssrf);\n\tconst token = await getApiToken(signal);\n\tconst headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n\tif (token) headers.Authorization = `Bearer ${token}`;\n\tconst requestUrl = new URL(`${baseUrl}/md`);\n\tconst init = {\n\t\tmethod: \"POST\",\n\t\theaders,\n\t\tbody: JSON.stringify({ url, f: MARKDOWN_FILTER }),\n\t\tsignal: requestSignal(options?.timeoutMs ?? EXTRACT_TIMEOUT_MS, signal),\n\t};\n\tlet seeOther = false;\n\tconst activityId = activityMonitor.logStart({ type: \"fetch\", url: requestUrl.toString() });\n\ttry {\n\t\tconst response = await fetchRemoteUrl(requestUrl, init, {\n\t\t\t...ssrf,\n\t\t\tallowLoopback: isLoopbackApiUrl(requestUrl),\n\t\t\tonRedirect: ({ to, init: redirectInit, response }) => {\n\t\t\t\tif (to.origin !== requestUrl.origin) {\n\t\t\t\t\tthrow new Error(`Crawl4AI refused cross-origin redirect to ${to.origin}`);\n\t\t\t\t}\n\t\t\t\t// 303 genuinely means \"GET the other resource\", and it is final: once a hop has turned the\n\t\t\t\t// chain into a GET, no later hop may resurrect the extraction body.\n\t\t\t\tif (response.status === 303) seeOther = true;\n\t\t\t\t// 301/302 are the hops a reverse proxy in front of a self-hosted instance actually emits, and\n\t\t\t\t// fetchRemoteUrl turns those into a bodyless GET that /md cannot serve, so replay the POST.\n\t\t\t\t// 307/308 already keep the method, so their init needs no help.\n\t\t\t\tconst replayPost = !seeOther && (response.status === 301 || response.status === 302);\n\t\t\t\treturn replayPost ? init : redirectInit;\n\t\t\t},\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tconst text = await response.text().catch(() => \"\");\n\t\t\tthrow new Error(`Crawl4AI md error ${response.status}: ${redactCredential(text, token).slice(0, 300)}`);\n\t\t}\n\t\tlet data: unknown;\n\t\ttry {\n\t\t\tdata = await response.json();\n\t\t} catch {\n\t\t\t// The parser message quotes the offending body, which can echo the configured token, so it never\n\t\t\t// reaches the thrown error or the activity log.\n\t\t\tthrow new Error(\"Crawl4AI md returned invalid JSON\");\n\t\t}\n\t\tif (!data || typeof data !== \"object\" || Array.isArray(data)) {\n\t\t\tthrow new Error(\"Crawl4AI md returned an unexpected response shape\");\n\t\t}\n\t\tconst envelope = data as Record<string, unknown>;\n\t\tif (envelope.success === false) {\n\t\t\tconst detail = typeof envelope.error === \"string\" ? envelope.error : typeof envelope.detail === \"string\" ? envelope.detail : \"\";\n\t\t\tthrow new Error(`Crawl4AI md unsuccessful: ${redactCredential(detail.trim() || \"unknown error\", token)}`);\n\t\t}\n\t\tif (envelope.success !== true) {\n\t\t\tthrow new Error(\"Crawl4AI md returned an unexpected response shape\");\n\t\t}\n\t\tif (typeof envelope.markdown !== \"string\") {\n\t\t\tthrow new Error(\"Crawl4AI md returned markdown in an unexpected shape\");\n\t\t}\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tconst content = envelope.markdown.trim();\n\t\tif (!content) return null;\n\t\treturn { url, title: firstHeadingTitle(content), content, error: null };\n\t} catch (err) {\n\t\tif (signal?.aborted || isAbortException(err)) activityMonitor.logComplete(activityId, 0);\n\t\telse activityMonitor.logError(activityId, errorMessage(err));\n\t\tthrow err;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"credential-source.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/credential-source.ts"],"names":[],"mappings":"AA0BA,MAAM,MAAM,yBAAyB,GAClC,gBAAgB,GAChB,gBAAgB,GAChB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,GACf,wBAAwB,GACxB,0BAA0B,GAC1B,mBAAmB,GACnB,2BAA2B,CAAC;AAE/B,qBAAa,yBAA0B,SAAQ,KAAK;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,yBAAyB,CAAC;IAE7C,YAAY,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,yBAAyB,EAShE;CACD;AAED,MAAM,WAAW,uBAAuB;IACvC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,wBAAwB;IACxC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,MAAM,MAAM,uBAAuB,GAAG,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,wBAAwB,KAC7B,OAAO,CAAC,uBAAuB,CAAC,CAAC;AAEtC,MAAM,WAAW,iBAAiB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACjD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,uBAAuB,CAAC;CACrC;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAE5F;AA+DD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAKvE;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwC1F"}
1
+ {"version":3,"file":"credential-source.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/credential-source.ts"],"names":[],"mappings":"AA2BA,MAAM,MAAM,yBAAyB,GAClC,gBAAgB,GAChB,gBAAgB,GAChB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,GACf,wBAAwB,GACxB,0BAA0B,GAC1B,mBAAmB,GACnB,2BAA2B,CAAC;AAE/B,qBAAa,yBAA0B,SAAQ,KAAK;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,yBAAyB,CAAC;IAE7C,YAAY,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,yBAAyB,EAShE;CACD;AAED,MAAM,WAAW,uBAAuB;IACvC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,wBAAwB;IACxC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,MAAM,MAAM,uBAAuB,GAAG,CACrC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,wBAAwB,KAC7B,OAAO,CAAC,uBAAuB,CAAC,CAAC;AAEtC,MAAM,WAAW,iBAAiB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACjD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,uBAAuB,CAAC;CACrC;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAE5F;AA+DD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAKvE;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwC1F"}
@@ -9,6 +9,7 @@ const COMMAND_ENVIRONMENT_NAMES = [
9
9
  "HOME",
10
10
  "USER",
11
11
  "LOGNAME",
12
+ "OP_SERVICE_ACCOUNT_TOKEN",
12
13
  "PATH",
13
14
  "LANG",
14
15
  "LC_ALL",
@@ -1 +1 @@
1
- {"version":3,"file":"credential-source.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/credential-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { exec } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execAsync = promisify(exec);\nconst COMMAND_TIMEOUT_MS = 5_000;\nconst MAX_CREDENTIAL_BYTES = 16_384;\nconst ENV_SOURCE = /^\\$(?:([A-Za-z_][A-Za-z0-9_]*)|\\{([A-Za-z_][A-Za-z0-9_]*)\\})$/;\nconst OP_SESSION_NAME = /^OP_SESSION_[A-Za-z0-9_]+$/;\nconst COMMAND_ENVIRONMENT_NAMES = [\n\t\"HOME\",\n\t\"USER\",\n\t\"LOGNAME\",\n\t\"PATH\",\n\t\"LANG\",\n\t\"LC_ALL\",\n\t\"LC_CTYPE\",\n\t\"TERM\",\n\t\"TMPDIR\",\n\t\"XDG_CONFIG_HOME\",\n\t\"XDG_RUNTIME_DIR\",\n\t\"DBUS_SESSION_BUS_ADDRESS\",\n\t\"SSH_AUTH_SOCK\",\n\t\"WSL_DISTRO_NAME\",\n\t\"WSL_INTEROP\",\n] as const;\n\nexport type CredentialFailureCategory =\n\t| \"invalid-source\"\n\t| \"command-failed\"\n\t| \"command-timeout\"\n\t| \"command-aborted\"\n\t| \"command-empty\"\n\t| \"command-invalid-output\"\n\t| \"command-output-too-large\"\n\t| \"environment-empty\"\n\t| \"oauth-credential-rejected\";\n\nexport class CredentialResolutionError extends Error {\n\treadonly provider: string;\n\treadonly category: CredentialFailureCategory;\n\n\tconstructor(provider: string, category: CredentialFailureCategory) {\n\t\tconst suffix =\n\t\t\tcategory === \"command-aborted\" ? \"aborted\" :\n\t\t\tcategory === \"oauth-credential-rejected\" ? \"OAuth token exchange rejected the credentials\" :\n\t\t\tcategory;\n\t\tsuper(`${provider} credential resolution failed: ${suffix}`);\n\t\tthis.name = \"CredentialResolutionError\";\n\t\tthis.provider = provider;\n\t\tthis.category = category;\n\t}\n}\n\nexport interface CredentialCommandResult {\n\tstdout: string | Buffer;\n}\n\nexport interface CredentialCommandOptions {\n\tsignal?: AbortSignal;\n\ttimeoutMs: number;\n\tmaxOutputBytes: number;\n\tenvironment: Record<string, string>;\n}\n\nexport type CredentialCommandRunner = (\n\tcommand: string,\n\toptions: CredentialCommandOptions,\n) => Promise<CredentialCommandResult>;\n\nexport interface CredentialOptions {\n\tprovider: string;\n\tconfiguredValue?: unknown;\n\tenvironmentValue?: unknown;\n\tenvironment?: Record<string, string | undefined>;\n\tsignal?: AbortSignal;\n\trunCommand?: CredentialCommandRunner;\n}\n\nexport function redactCredential(text: string, credential: string | null | undefined): string {\n\treturn credential ? text.split(credential).join(\"[redacted]\") : text;\n}\n\nfunction normalize(value: unknown): string | null {\n\tif (typeof value !== \"string\") return null;\n\tconst normalized = value.trim();\n\treturn normalized.length > 0 ? normalized : null;\n}\n\nfunction commandEnvironment(source: Record<string, string | undefined>): Record<string, string> {\n\tconst environment: Record<string, string> = {};\n\tfor (const name of COMMAND_ENVIRONMENT_NAMES) {\n\t\tconst value = source[name];\n\t\tif (value !== undefined) environment[name] = value;\n\t}\n\tfor (const [name, value] of Object.entries(source)) {\n\t\tif (value !== undefined && OP_SESSION_NAME.test(name)) environment[name] = value;\n\t}\n\treturn environment;\n}\n\nfunction configuredSource(options: CredentialOptions): string | null {\n\treturn normalize(options.configuredValue);\n}\n\nfunction explicitEnvironmentName(source: string): string | null {\n\tconst match = source.match(ENV_SOURCE);\n\treturn match ? match[1] ?? match[2] : null;\n}\n\nfunction escapedSource(source: string): string | null {\n\tif (source.startsWith(\"$$\") || source.startsWith(\"$!\")) return source.slice(1);\n\treturn null;\n}\n\nfunction isMalformedExplicitSource(source: string): boolean {\n\treturn source.startsWith(\"$\") && escapedSource(source) === null && explicitEnvironmentName(source) === null;\n}\n\nasync function defaultRunCommand(\n\tcommand: string,\n\toptions: CredentialCommandOptions,\n): Promise<CredentialCommandResult> {\n\tconst result = await execAsync(command, {\n\t\tencoding: \"utf8\",\n\t\tenv: options.environment,\n\t\tmaxBuffer: options.maxOutputBytes + 1,\n\t\tsignal: options.signal,\n\t\ttimeout: options.timeoutMs,\n\t\twindowsHide: true,\n\t});\n\treturn { stdout: result.stdout };\n}\n\nfunction commandFailureCategory(error: unknown, signal?: AbortSignal): CredentialFailureCategory {\n\tif (signal?.aborted) return \"command-aborted\";\n\tif (error && typeof error === \"object\") {\n\t\tconst code = (error as { code?: string }).code;\n\t\tif (code === \"ERR_CHILD_PROCESS_STDIO_MAXBUFFER\") return \"command-output-too-large\";\n\t\tif ((error as { killed?: boolean }).killed || code === \"ETIMEDOUT\") return \"command-timeout\";\n\t}\n\treturn \"command-failed\";\n}\n\nexport function hasCredentialSource(options: CredentialOptions): boolean {\n\tconst source = configuredSource(options);\n\tif (source?.startsWith(\"!\")) return true;\n\tif (source?.startsWith(\"$\")) return true;\n\treturn normalize(options.environmentValue) !== null || source !== null;\n}\n\nexport async function resolveCredential(options: CredentialOptions): Promise<string | null> {\n\tconst source = configuredSource(options);\n\tconst escaped = source ? escapedSource(source) : null;\n\tif (escaped !== null) return escaped;\n\tif (source?.startsWith(\"!\")) {\n\t\tconst command = source.slice(1).trim();\n\t\tif (!command) throw new CredentialResolutionError(options.provider, \"invalid-source\");\n\t\tlet result: CredentialCommandResult;\n\t\ttry {\n\t\t\tresult = await (options.runCommand ?? defaultRunCommand)(command, {\n\t\t\t\tsignal: options.signal,\n\t\t\t\ttimeoutMs: COMMAND_TIMEOUT_MS,\n\t\t\t\tmaxOutputBytes: MAX_CREDENTIAL_BYTES,\n\t\t\t\tenvironment: commandEnvironment(options.environment ?? process.env),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow new CredentialResolutionError(options.provider, commandFailureCategory(error, options.signal));\n\t\t}\n\t\tconst stdout = Buffer.isBuffer(result.stdout) ? result.stdout.toString(\"utf8\") : result.stdout;\n\t\tif (Buffer.byteLength(stdout, \"utf8\") > MAX_CREDENTIAL_BYTES) {\n\t\t\tthrow new CredentialResolutionError(options.provider, \"command-output-too-large\");\n\t\t}\n\t\tconst value = stdout.trim();\n\t\tif (!value) throw new CredentialResolutionError(options.provider, \"command-empty\");\n\t\tif (/[\\0-\\x1f\\x7f]/.test(value)) {\n\t\t\tthrow new CredentialResolutionError(options.provider, \"command-invalid-output\");\n\t\t}\n\t\treturn value;\n\t}\n\tif (source && isMalformedExplicitSource(source)) {\n\t\tthrow new CredentialResolutionError(options.provider, \"invalid-source\");\n\t}\n\tif (source?.startsWith(\"$\")) {\n\t\tconst name = explicitEnvironmentName(source);\n\t\tif (!name) throw new CredentialResolutionError(options.provider, \"invalid-source\");\n\t\tconst value = normalize((options.environment ?? process.env)[name]);\n\t\tif (!value) throw new CredentialResolutionError(options.provider, \"environment-empty\");\n\t\treturn value;\n\t}\n\treturn normalize(options.environmentValue) ?? source;\n}\n"]}
1
+ {"version":3,"file":"credential-source.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/credential-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { exec } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execAsync = promisify(exec);\nconst COMMAND_TIMEOUT_MS = 5_000;\nconst MAX_CREDENTIAL_BYTES = 16_384;\nconst ENV_SOURCE = /^\\$(?:([A-Za-z_][A-Za-z0-9_]*)|\\{([A-Za-z_][A-Za-z0-9_]*)\\})$/;\nconst OP_SESSION_NAME = /^OP_SESSION_[A-Za-z0-9_]+$/;\nconst COMMAND_ENVIRONMENT_NAMES = [\n\t\"HOME\",\n\t\"USER\",\n\t\"LOGNAME\",\n\t\"OP_SERVICE_ACCOUNT_TOKEN\",\n\t\"PATH\",\n\t\"LANG\",\n\t\"LC_ALL\",\n\t\"LC_CTYPE\",\n\t\"TERM\",\n\t\"TMPDIR\",\n\t\"XDG_CONFIG_HOME\",\n\t\"XDG_RUNTIME_DIR\",\n\t\"DBUS_SESSION_BUS_ADDRESS\",\n\t\"SSH_AUTH_SOCK\",\n\t\"WSL_DISTRO_NAME\",\n\t\"WSL_INTEROP\",\n] as const;\n\nexport type CredentialFailureCategory =\n\t| \"invalid-source\"\n\t| \"command-failed\"\n\t| \"command-timeout\"\n\t| \"command-aborted\"\n\t| \"command-empty\"\n\t| \"command-invalid-output\"\n\t| \"command-output-too-large\"\n\t| \"environment-empty\"\n\t| \"oauth-credential-rejected\";\n\nexport class CredentialResolutionError extends Error {\n\treadonly provider: string;\n\treadonly category: CredentialFailureCategory;\n\n\tconstructor(provider: string, category: CredentialFailureCategory) {\n\t\tconst suffix =\n\t\t\tcategory === \"command-aborted\" ? \"aborted\" :\n\t\t\tcategory === \"oauth-credential-rejected\" ? \"OAuth token exchange rejected the credentials\" :\n\t\t\tcategory;\n\t\tsuper(`${provider} credential resolution failed: ${suffix}`);\n\t\tthis.name = \"CredentialResolutionError\";\n\t\tthis.provider = provider;\n\t\tthis.category = category;\n\t}\n}\n\nexport interface CredentialCommandResult {\n\tstdout: string | Buffer;\n}\n\nexport interface CredentialCommandOptions {\n\tsignal?: AbortSignal;\n\ttimeoutMs: number;\n\tmaxOutputBytes: number;\n\tenvironment: Record<string, string>;\n}\n\nexport type CredentialCommandRunner = (\n\tcommand: string,\n\toptions: CredentialCommandOptions,\n) => Promise<CredentialCommandResult>;\n\nexport interface CredentialOptions {\n\tprovider: string;\n\tconfiguredValue?: unknown;\n\tenvironmentValue?: unknown;\n\tenvironment?: Record<string, string | undefined>;\n\tsignal?: AbortSignal;\n\trunCommand?: CredentialCommandRunner;\n}\n\nexport function redactCredential(text: string, credential: string | null | undefined): string {\n\treturn credential ? text.split(credential).join(\"[redacted]\") : text;\n}\n\nfunction normalize(value: unknown): string | null {\n\tif (typeof value !== \"string\") return null;\n\tconst normalized = value.trim();\n\treturn normalized.length > 0 ? normalized : null;\n}\n\nfunction commandEnvironment(source: Record<string, string | undefined>): Record<string, string> {\n\tconst environment: Record<string, string> = {};\n\tfor (const name of COMMAND_ENVIRONMENT_NAMES) {\n\t\tconst value = source[name];\n\t\tif (value !== undefined) environment[name] = value;\n\t}\n\tfor (const [name, value] of Object.entries(source)) {\n\t\tif (value !== undefined && OP_SESSION_NAME.test(name)) environment[name] = value;\n\t}\n\treturn environment;\n}\n\nfunction configuredSource(options: CredentialOptions): string | null {\n\treturn normalize(options.configuredValue);\n}\n\nfunction explicitEnvironmentName(source: string): string | null {\n\tconst match = source.match(ENV_SOURCE);\n\treturn match ? match[1] ?? match[2] : null;\n}\n\nfunction escapedSource(source: string): string | null {\n\tif (source.startsWith(\"$$\") || source.startsWith(\"$!\")) return source.slice(1);\n\treturn null;\n}\n\nfunction isMalformedExplicitSource(source: string): boolean {\n\treturn source.startsWith(\"$\") && escapedSource(source) === null && explicitEnvironmentName(source) === null;\n}\n\nasync function defaultRunCommand(\n\tcommand: string,\n\toptions: CredentialCommandOptions,\n): Promise<CredentialCommandResult> {\n\tconst result = await execAsync(command, {\n\t\tencoding: \"utf8\",\n\t\tenv: options.environment,\n\t\tmaxBuffer: options.maxOutputBytes + 1,\n\t\tsignal: options.signal,\n\t\ttimeout: options.timeoutMs,\n\t\twindowsHide: true,\n\t});\n\treturn { stdout: result.stdout };\n}\n\nfunction commandFailureCategory(error: unknown, signal?: AbortSignal): CredentialFailureCategory {\n\tif (signal?.aborted) return \"command-aborted\";\n\tif (error && typeof error === \"object\") {\n\t\tconst code = (error as { code?: string }).code;\n\t\tif (code === \"ERR_CHILD_PROCESS_STDIO_MAXBUFFER\") return \"command-output-too-large\";\n\t\tif ((error as { killed?: boolean }).killed || code === \"ETIMEDOUT\") return \"command-timeout\";\n\t}\n\treturn \"command-failed\";\n}\n\nexport function hasCredentialSource(options: CredentialOptions): boolean {\n\tconst source = configuredSource(options);\n\tif (source?.startsWith(\"!\")) return true;\n\tif (source?.startsWith(\"$\")) return true;\n\treturn normalize(options.environmentValue) !== null || source !== null;\n}\n\nexport async function resolveCredential(options: CredentialOptions): Promise<string | null> {\n\tconst source = configuredSource(options);\n\tconst escaped = source ? escapedSource(source) : null;\n\tif (escaped !== null) return escaped;\n\tif (source?.startsWith(\"!\")) {\n\t\tconst command = source.slice(1).trim();\n\t\tif (!command) throw new CredentialResolutionError(options.provider, \"invalid-source\");\n\t\tlet result: CredentialCommandResult;\n\t\ttry {\n\t\t\tresult = await (options.runCommand ?? defaultRunCommand)(command, {\n\t\t\t\tsignal: options.signal,\n\t\t\t\ttimeoutMs: COMMAND_TIMEOUT_MS,\n\t\t\t\tmaxOutputBytes: MAX_CREDENTIAL_BYTES,\n\t\t\t\tenvironment: commandEnvironment(options.environment ?? process.env),\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow new CredentialResolutionError(options.provider, commandFailureCategory(error, options.signal));\n\t\t}\n\t\tconst stdout = Buffer.isBuffer(result.stdout) ? result.stdout.toString(\"utf8\") : result.stdout;\n\t\tif (Buffer.byteLength(stdout, \"utf8\") > MAX_CREDENTIAL_BYTES) {\n\t\t\tthrow new CredentialResolutionError(options.provider, \"command-output-too-large\");\n\t\t}\n\t\tconst value = stdout.trim();\n\t\tif (!value) throw new CredentialResolutionError(options.provider, \"command-empty\");\n\t\tif (/[\\0-\\x1f\\x7f]/.test(value)) {\n\t\t\tthrow new CredentialResolutionError(options.provider, \"command-invalid-output\");\n\t\t}\n\t\treturn value;\n\t}\n\tif (source && isMalformedExplicitSource(source)) {\n\t\tthrow new CredentialResolutionError(options.provider, \"invalid-source\");\n\t}\n\tif (source?.startsWith(\"$\")) {\n\t\tconst name = explicitEnvironmentName(source);\n\t\tif (!name) throw new CredentialResolutionError(options.provider, \"invalid-source\");\n\t\tconst value = normalize((options.environment ?? process.env)[name]);\n\t\tif (!value) throw new CredentialResolutionError(options.provider, \"environment-empty\");\n\t\treturn value;\n\t}\n\treturn normalize(options.environmentValue) ?? source;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"curator-page.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/curator-page.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AA6D/D,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,MAAM,EAAE,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,kBAAkB,EAAE,oBAAoB,EACxC,eAAe,EAAE,MAAM,EACvB,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EACtD,mBAAmB,EAAE,MAAM,GAAG,IAAI,GAChC,MAAM,CAyIR"}
1
+ {"version":3,"file":"curator-page.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/curator-page.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AA8D/D,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,MAAM,EAAE,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,kBAAkB,EAAE,oBAAoB,EACxC,eAAe,EAAE,MAAM,EACvB,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,EACtD,mBAAmB,EAAE,MAAM,GAAG,IAAI,GAChC,MAAM,CA0IR"}
@@ -36,6 +36,7 @@ function buildProviderButtons(available, selected, hasInitialQueries) {
36
36
  { value: "mistral", label: "Mistral", available: available.mistral },
37
37
  { value: "brightdata", label: "Bright Data", available: available.brightdata },
38
38
  { value: "serpbase", label: "SerpBase", available: available.serpbase },
39
+ { value: "serpapi", label: "SerpApi", available: available.serpapi },
39
40
  { value: "serper", label: "Serper", available: available.serper },
40
41
  { value: "valyu", label: "Valyu", available: available.valyu },
41
42
  ];
@@ -130,6 +131,7 @@ ${CSS}
130
131
  <button class="btn btn-secondary" id="btn-summary-regenerate">Regenerate</button>
131
132
  <button class="btn btn-secondary" id="btn-summary-preview" title="Preview rendered summary">Preview</button>
132
133
  <button class="btn btn-submit" id="btn-summary-approve">Approve</button>
134
+ <button class="btn btn-submit" id="btn-summary-approve-remaining">Approve + auto-summary remaining searches for this prompt</button>
133
135
  </div>
134
136
  </section>
135
137
  </main>
@@ -1177,6 +1179,7 @@ main {
1177
1179
  }
1178
1180
  .summary-actions {
1179
1181
  display: flex;
1182
+ flex-wrap: wrap;
1180
1183
  align-items: center;
1181
1184
  justify-content: flex-end;
1182
1185
  gap: 8px;
@@ -1448,7 +1451,7 @@ const SCRIPT = `(function() {
1448
1451
  var token = DATA.sessionToken;
1449
1452
  var timeoutSec = DATA.timeout;
1450
1453
  var queries = Array.isArray(DATA.queries) ? DATA.queries : [];
1451
- var providers = ["all", "openai", "exa", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "duckduckgo", "perplexity", "gemini", "kimi", "anysearch", "xcrawl", "xai", "mistral", "brightdata", "serpbase", "serper", "valyu"];
1454
+ var providers = ["all", "openai", "exa", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "duckduckgo", "perplexity", "gemini", "kimi", "anysearch", "xcrawl", "xai", "mistral", "brightdata", "serpbase", "serpapi", "serper", "valyu"];
1452
1455
  var availProviders = DATA.availableProviders && typeof DATA.availableProviders === "object" ? DATA.availableProviders : {};
1453
1456
  var workflow = "summary-review";
1454
1457
  var initialDefaultProvider = typeof DATA.defaultProvider === "string" ? DATA.defaultProvider : "exa";
@@ -1513,6 +1516,7 @@ const SCRIPT = `(function() {
1513
1516
  var btnSummaryRegenerate = document.getElementById("btn-summary-regenerate");
1514
1517
  var btnSummaryPreview = document.getElementById("btn-summary-preview");
1515
1518
  var btnSummaryApprove = document.getElementById("btn-summary-approve");
1519
+ var btnSummaryApproveRemaining = document.getElementById("btn-summary-approve-remaining");
1516
1520
  var successOverlay = document.getElementById("success-overlay");
1517
1521
  var successText = document.getElementById("success-text");
1518
1522
  var expiredOverlay = document.getElementById("expired-overlay");
@@ -1678,6 +1682,9 @@ const SCRIPT = `(function() {
1678
1682
  if (provider === "mistral") return "Mistral";
1679
1683
  if (provider === "brightdata") return "Bright Data";
1680
1684
  if (provider === "serpbase") return "SerpBase";
1685
+ if (provider === "serpapi") return "SerpApi";
1686
+ if (provider === "serper") return "Serper";
1687
+ if (provider === "valyu") return "Valyu";
1681
1688
  return "Unknown";
1682
1689
  }
1683
1690
 
@@ -2093,6 +2100,9 @@ const SCRIPT = `(function() {
2093
2100
  if (btnSummaryApprove) {
2094
2101
  btnSummaryApprove.disabled = submitted || timerExpired || submitInFlight || stage === "generating-summary" || isRegenerating || !hasSelection || !hasDraft;
2095
2102
  }
2103
+ if (btnSummaryApproveRemaining) {
2104
+ btnSummaryApproveRemaining.disabled = submitted || timerExpired || submitInFlight || stage === "generating-summary" || isRegenerating || !hasSelection || !hasDraft;
2105
+ }
2096
2106
 
2097
2107
  applyProviderInterlocks();
2098
2108
  }
@@ -3184,7 +3194,7 @@ const SCRIPT = `(function() {
3184
3194
  requestSummary(selected);
3185
3195
  }
3186
3196
 
3187
- function doApprove() {
3197
+ function doApprove(autoApproveRemainingSearches) {
3188
3198
  if (submitted || timerExpired || submitInFlight || stage !== "summary-review") return;
3189
3199
 
3190
3200
  var selected = getSelectedIndices();
@@ -3196,6 +3206,7 @@ const SCRIPT = `(function() {
3196
3206
 
3197
3207
  var draft = getSummaryDraftText();
3198
3208
  var payload = { selected: selected };
3209
+ if (autoApproveRemainingSearches === true) payload.autoApproveRemainingSearches = true;
3199
3210
  if (draft.length > 0) {
3200
3211
  payload.summary = draft;
3201
3212
  payload.summaryMeta = normalizeSummaryMeta(summaryMeta, summaryMeta && summaryMeta.edited === true);
@@ -3434,6 +3445,13 @@ const SCRIPT = `(function() {
3434
3445
  });
3435
3446
  }
3436
3447
 
3448
+ if (btnSummaryApproveRemaining) {
3449
+ btnSummaryApproveRemaining.addEventListener("click", function() {
3450
+ doApprove(true);
3451
+ resetTimer();
3452
+ });
3453
+ }
3454
+
3437
3455
  if (summaryInput) {
3438
3456
  summaryInput.addEventListener("input", function() {
3439
3457
  if (!summaryMeta || typeof summaryMeta !== "object") {