@xiaoso/dsh-tool-plus 0.1.0-beta.5 → 0.1.0-beta.7

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 (71) hide show
  1. package/README.en.md +1 -1
  2. package/README.md +2 -2
  3. package/THIRD_PARTY_NOTICES.md +42 -42
  4. package/lib/{abort-dIkZBV7Q.mjs → abort-CsjZitYv.mjs} +1 -1
  5. package/lib/{amazon-bedrock-WQhvjHYu.mjs → amazon-bedrock-CfNIDi5k.mjs} +7 -7
  6. package/lib/{anthropic-CJCauvYz.mjs → anthropic-BVfZdnMp.mjs} +12 -12
  7. package/lib/{anthropic-tTntD8BD.mjs → anthropic-DGXmmg43.mjs} +2 -2
  8. package/lib/{api-key-login-BzqNdM2y.mjs → api-key-login-BikQAfD-.mjs} +1 -1
  9. package/lib/{aws-credentials-CXryMobh.mjs → aws-credentials-DrssNMn_.mjs} +4 -4
  10. package/lib/{azure-openai-responses-DuukbD0F.mjs → azure-openai-responses-C-5q-5K2.mjs} +8 -8
  11. package/lib/browser-render-z6b8Ljo9.mjs +183 -0
  12. package/lib/{bundled-references-Cavaffah.mjs → bundled-references-D71z7_T3.mjs} +2 -2
  13. package/lib/{callback-server-CQ5Jo8F-.mjs → callback-server-Di6C4RqI.mjs} +1 -1
  14. package/lib/client.js +613 -107
  15. package/lib/{cursor-B6TUEmgP.mjs → cursor-CQQtygwl.mjs} +1 -1
  16. package/lib/{cursor-DkSX2h3H.mjs → cursor-DXJxwdcg.mjs} +7 -7
  17. package/lib/{cursor-aJrXeYCb.mjs → cursor-Hr5Zw1gt.mjs} +3 -3
  18. package/lib/{device-code-DBo3B5nh.mjs → device-code-wZUbgPdB.mjs} +1 -1
  19. package/lib/{devin-BHZZyMhO.mjs → devin-CF6UhCFw.mjs} +2 -2
  20. package/lib/{devin-BK7DHeca.mjs → devin-DXJoDlW2.mjs} +1 -1
  21. package/lib/{devin-vNZTNUON.mjs → devin-P37gURs_.mjs} +5 -5
  22. package/lib/{dom-DGdYAVTv.mjs → dom-CawWi8fa.mjs} +14 -1
  23. package/lib/{error-Dd8DIqs-.mjs → error-DoHuatWw.mjs} +1 -1
  24. package/lib/{event-stream-BbFZJy-z.mjs → event-stream-C-FmBYUt.mjs} +1 -1
  25. package/lib/{github-copilot-CYkjeTi3.mjs → github-copilot-C2dTonBX.mjs} +3 -3
  26. package/lib/{github-copilot-B1yiyVRh.mjs → github-copilot-qETlj_bD.mjs} +1 -1
  27. package/lib/{gitlab-duo-CM7Ref1H.mjs → gitlab-duo-FQZiFRQe.mjs} +3 -3
  28. package/lib/{gitlab-duo-workflow-BGAF6JDw.mjs → gitlab-duo-workflow-DLTabzOy.mjs} +2 -2
  29. package/lib/{google-BS4T25Pn.mjs → google-BilwFSzx.mjs} +3 -3
  30. package/lib/{google-antigravity-DGmwC2KV.mjs → google-antigravity-BfBiINdu.mjs} +2 -2
  31. package/lib/{google-auth-B6gjsZvg.mjs → google-auth-CWlKXJ-H.mjs} +3 -3
  32. package/lib/{google-gemini-cli-Dsztq0N5.mjs → google-gemini-cli-CB89ljNz.mjs} +3 -3
  33. package/lib/{google-gemini-cli-Cqds_Q5T.mjs → google-gemini-cli-CygFcR6N.mjs} +8 -8
  34. package/lib/{google-oauth-shared-CuEHaDDj.mjs → google-oauth-shared-Cn82h1o2.mjs} +2 -2
  35. package/lib/{google-shared-Dr-VG-ap.mjs → google-shared-CF1JrqTM.mjs} +6 -6
  36. package/lib/{google-vertex-BDN2tLaD.mjs → google-vertex-DgMhhFw8.mjs} +5 -5
  37. package/lib/{identity-Da1GPU9a.mjs → identity-C0SPVyIZ.mjs} +1 -1
  38. package/lib/{idle-iterator-DUjKQEGT.mjs → idle-iterator-HzbbfXP4.mjs} +2 -2
  39. package/lib/index.mjs +310 -29
  40. package/lib/{kimi-Dh0pv3WZ.mjs → kimi-DHDO6Fde.mjs} +3 -3
  41. package/lib/launch-DwpE70Pv.mjs +148 -0
  42. package/lib/{markit-BL3cH47U.mjs → markit-B9exZ0ZV.mjs} +1 -1
  43. package/lib/{minimax-code-Uq8Qu9LW.mjs → minimax-code-vAhEXhev.mjs} +1 -1
  44. package/lib/{models-SGASygJ3.mjs → models-Cp3PzU3l.mjs} +1 -1
  45. package/lib/{ollama-DPzBmq89.mjs → ollama-TrkXTNqE.mjs} +8 -8
  46. package/lib/{openai-codex-Ch8CkT3p.mjs → openai-codex-CrjHtSH4.mjs} +3 -3
  47. package/lib/{openai-codex-responses-pejZgdZo.mjs → openai-codex-responses-C4uMpWNy.mjs} +10 -10
  48. package/lib/{openai-completions-DIf361kF.mjs → openai-completions-1ydXm5vF.mjs} +13 -13
  49. package/lib/{openai-reasoning-fallback-Nj6yB2rY.mjs → openai-reasoning-fallback-CigKCyzc.mjs} +2 -2
  50. package/lib/{openai-responses-Db_a-U4-.mjs → openai-responses-CN79gRGQ.mjs} +10 -10
  51. package/lib/{opencode-Rq_Q2vAO.mjs → opencode-CEGlimzL.mjs} +1 -1
  52. package/lib/{perplexity-C4iLVMUj.mjs → perplexity-BLJN-8dM.mjs} +2 -2
  53. package/lib/{proxy-pM_NIv-H.mjs → proxy-XOYi58TM.mjs} +1 -1
  54. package/lib/readability-Cpm-ocau.mjs +455 -0
  55. package/lib/{scrapers-1FTlHNQV.mjs → scrapers-DrSYgPyf.mjs} +11 -10
  56. package/lib/{src-BgpusnsB.mjs → src-D_e1qMwi.mjs} +5 -1
  57. package/lib/{stream-CRtexYLe.mjs → stream-DveNFWSN.mjs} +56 -56
  58. package/lib/{stream-markup-healing-KMkiAA00.mjs → stream-markup-healing-C9eC7Gf8.mjs} +3 -3
  59. package/lib/tool-errors-DoyxivAR.mjs +47 -0
  60. package/lib/types-BUO0JXO-.mjs +251 -0
  61. package/lib/{utils-D2YknRk4.mjs → utils-B-mTYQBc.mjs} +1 -1
  62. package/lib/{utils-C616y7eF.mjs → utils-DSEEKy3G.mjs} +28 -268
  63. package/lib/{utils-Cp76gCyk.mjs → utils-fnV2zzp3.mjs} +3 -3
  64. package/lib/{vision-guard-01qSwFpf.mjs → vision-guard-BsvOWmfB.mjs} +2 -2
  65. package/lib/{wafer-CgtPHerf.mjs → wafer-Dpy7tL3l.mjs} +1 -1
  66. package/lib/{xai-oauth-Buk8XVvM.mjs → xai-oauth-BkPNQS-q.mjs} +2 -2
  67. package/lib/{xiaomi-D2qT5cAC.mjs → xiaomi-CG3TsYRz.mjs} +1 -1
  68. package/lib/{zai-C59YnomG.mjs → zai--cewobzN.mjs} +2 -2
  69. package/lib/{zip-Cg9ANN_U.mjs → zip-CqKW2-TK.mjs} +3 -46
  70. package/package.json +3 -1
  71. package/presets/package.json +1 -1
package/lib/index.mjs CHANGED
@@ -1,11 +1,14 @@
1
1
  import { installBunShim as __installBunShim } from "./bun-shim.mjs";
2
2
  __installBunShim();
3
3
  import { n as fs_promises_shim_exports, t as exists } from "./fs-promises-shim-Bx0fpxuk.mjs";
4
- import { A as formatAge, C as error, F as $envpos, I as $flag, M as formatCount, P as $env, Q as peekFile, S as debug, T as warn, U as getAutoQaDbPath, V as VERSION, Y as getRemoteDir, Z as isProbablyBinary, b as isEnotdir, c as combineSignals, d as $which, et as untilAborted, j as formatBytes$1, k as installBunShim, l as exec, p as readImageMetadata, q as getInstallId, u as render, v as hasFsCode, y as isEnoent } from "./src-BgpusnsB.mjs";
5
- import { A as shortenPath, C as Settings, D as capParseErrors, E as OMP_DEFAULTS, M as padding, N as replaceTabs, O as formatCodeFrameLine, P as truncateToWidth$1, S as convertFileWithMarkit, T as settings, _ as extractWithParallel, a as fetchBinary, b as ensureTool, g as looksLikeHtml, h as loadPage, i as convertWithMarkit, j as LRUCache, k as formatParseErrors, o as MAX_BYTES, s as MAX_OUTPUT_CHARS, u as finalizeOutput, v as findParallelApiKey, w as isSettingsInitialized, x as CONVERTIBLE_EXTENSIONS, y as getParallelExtractContent } from "./utils-C616y7eF.mjs";
4
+ import { i as setRuntimeLogger, n as systemChromiumCandidatesForTest, r as runtimeLogger } from "./launch-DwpE70Pv.mjs";
5
+ import { $ as peekFile, A as formatAge, C as error, F as $envpos, I as $flag, M as formatCount, P as $env, Q as isProbablyBinary, S as debug, T as warn, U as getAutoQaDbPath, V as VERSION, X as getRemoteDir, Y as getPuppeteerDir, b as isEnotdir, c as combineSignals, d as $which, j as formatBytes$1, k as installBunShim, l as exec, p as readImageMetadata, q as getInstallId, tt as untilAborted, u as render, v as hasFsCode, y as isEnoent } from "./src-D_e1qMwi.mjs";
6
+ import { C as truncateToWidth$1, S as replaceTabs, _ as formatCodeFrameLine, a as fetchBinary, b as LRUCache, c as getParallelExtractContent, d as convertFileWithMarkit, f as Settings, g as capParseErrors, h as OMP_DEFAULTS, i as convertWithMarkit, l as ensureTool, m as settings, o as extractWithParallel, p as isSettingsInitialized, s as findParallelApiKey, u as CONVERTIBLE_EXTENSIONS, v as formatParseErrors, x as padding, y as shortenPath } from "./utils-DSEEKy3G.mjs";
6
7
  import { t as sanitizeText } from "./sanitize-text-CfcXzw5X.mjs";
7
8
  import { n as isRecord$2 } from "./type-guards-B9VVjRDK.mjs";
8
- import { a as openArchive, c as sniffArchiveFormat, d as writeArchive, f as ToolAbortError, i as listArchiveRoot, m as throwIfAborted, o as parseArchivePathCandidates, p as ToolError, r as formatArchiveEntryLines, s as readArchiveEntries, t as archiveFormatFromPath } from "./zip-Cg9ANN_U.mjs";
9
+ import { n as ToolError, r as throwIfAborted, t as ToolAbortError } from "./tool-errors-DoyxivAR.mjs";
10
+ import { a as openArchive, c as sniffArchiveFormat, d as writeArchive, i as listArchiveRoot, o as parseArchivePathCandidates, r as formatArchiveEntryLines, s as readArchiveEntries, t as archiveFormatFromPath } from "./zip-CqKW2-TK.mjs";
11
+ import { a as finalizeOutput, d as looksLikeHtml, n as MAX_OUTPUT_CHARS, t as MAX_BYTES, u as loadPage } from "./types-BUO0JXO-.mjs";
9
12
  import * as path from "node:path";
10
13
  import { TOOL_ABORTED, defineTool } from "@deepseek-ai/dsh-tools";
11
14
  import { HarnessError } from "@deepseek-ai/dsh-llm";
@@ -385,22 +388,6 @@ function checkBashInterception(command, availableTools, rules = DEFAULT_BASH_INT
385
388
  return { block: false };
386
389
  }
387
390
  //#endregion
388
- //#region src/tools/bash/logger.ts
389
- let current = {
390
- info: (message, ...args) => console.info(`[bash-plus] ${message}`, ...args),
391
- warn: (message, ...args) => console.warn(`[bash-plus] ${message}`, ...args),
392
- error: (message, ...args) => console.error(`[bash-plus] ${message}`, ...args),
393
- debug: (message, ...args) => console.debug(`[bash-plus] ${message}`, ...args)
394
- };
395
- /** Install the composition logger (Cordis `ctx.logger`); replaces the console adapter. */
396
- function setRuntimeLogger(logger) {
397
- current = logger;
398
- }
399
- /** The installed logger; defaults to the console adapter. */
400
- function runtimeLogger() {
401
- return current;
402
- }
403
- //#endregion
404
391
  //#region src/tools/bash/which.ts
405
392
  /**
406
393
  * Minimal PATH (+ PATHEXT on Windows) executable lookup, standing in for
@@ -3610,6 +3597,64 @@ const TOOL_PLUS_FIELDS = [
3610
3597
  tool: "read",
3611
3598
  visibility: { requiresEnabled: "fetchEnabled" }
3612
3599
  },
3600
+ {
3601
+ name: "fetchReader",
3602
+ kind: "select",
3603
+ default: "auto",
3604
+ labelKey: "fetchReader",
3605
+ hintKey: "fetchReaderHint",
3606
+ group: "fetch",
3607
+ tool: "read",
3608
+ options: [
3609
+ {
3610
+ value: "auto",
3611
+ labelKey: "optReaderAuto"
3612
+ },
3613
+ {
3614
+ value: "native",
3615
+ labelKey: "optReaderNative"
3616
+ },
3617
+ {
3618
+ value: "trafilatura",
3619
+ labelKey: "optReaderTrafilatura"
3620
+ },
3621
+ {
3622
+ value: "lynx",
3623
+ labelKey: "optReaderLynx"
3624
+ },
3625
+ {
3626
+ value: "parallel",
3627
+ labelKey: "optReaderParallel"
3628
+ },
3629
+ {
3630
+ value: "jina",
3631
+ labelKey: "optReaderJina"
3632
+ },
3633
+ {
3634
+ value: "browser",
3635
+ labelKey: "optReaderBrowser"
3636
+ }
3637
+ ]
3638
+ },
3639
+ {
3640
+ name: "browserReaderEnabled",
3641
+ kind: "boolean",
3642
+ default: true,
3643
+ labelKey: "browserReaderEnabled",
3644
+ hintKey: "browserReaderEnabledHint",
3645
+ group: "fetch",
3646
+ tool: "read"
3647
+ },
3648
+ {
3649
+ name: "browserProbe",
3650
+ kind: "action",
3651
+ default: "",
3652
+ labelKey: "browserProbe",
3653
+ hintKey: "browserProbeHint",
3654
+ group: "fetch",
3655
+ tool: "read",
3656
+ actionKey: "browser/detect"
3657
+ },
3613
3658
  {
3614
3659
  name: "imagesBlockImages",
3615
3660
  kind: "boolean",
@@ -3896,6 +3941,16 @@ const Config = z.object({
3896
3941
  astEditEnabled: z.boolean().default(fieldDefault("astEditEnabled", true)),
3897
3942
  fetchEnabled: z.boolean().default(fieldDefault("fetchEnabled", true)),
3898
3943
  fetchMaxTimeoutSeconds: z.number().default(fieldDefault("fetchMaxTimeoutSeconds", 0)),
3944
+ fetchReader: z.union([
3945
+ "auto",
3946
+ "native",
3947
+ "trafilatura",
3948
+ "lynx",
3949
+ "parallel",
3950
+ "jina",
3951
+ "browser"
3952
+ ]).default(fieldDefault("fetchReader", "auto")),
3953
+ browserReaderEnabled: z.boolean().default(fieldDefault("browserReaderEnabled", true)),
3899
3954
  imagesAutoResize: z.boolean().default(fieldDefault("imagesAutoResize", true)),
3900
3955
  imagesBlockImages: z.boolean().default(fieldDefault("imagesBlockImages", false)),
3901
3956
  imagesExcludeWebp: z.boolean().default(fieldDefault("imagesExcludeWebp", false)),
@@ -3971,6 +4026,8 @@ function resolveConfig(config) {
3971
4026
  astEditEnabled: config.astEditEnabled ?? fieldDefault("astEditEnabled", true),
3972
4027
  fetchEnabled: config.fetchEnabled ?? fieldDefault("fetchEnabled", true),
3973
4028
  fetchMaxTimeoutSeconds: config.fetchMaxTimeoutSeconds ?? fieldDefault("fetchMaxTimeoutSeconds", 0),
4029
+ fetchReader: config.fetchReader ?? fieldDefault("fetchReader", "auto"),
4030
+ browserReaderEnabled: config.browserReaderEnabled ?? fieldDefault("browserReaderEnabled", true),
3974
4031
  imagesAutoResize: config.imagesAutoResize ?? fieldDefault("imagesAutoResize", true),
3975
4032
  imagesBlockImages: config.imagesBlockImages ?? fieldDefault("imagesBlockImages", false),
3976
4033
  imagesExcludeWebp: config.imagesExcludeWebp ?? fieldDefault("imagesExcludeWebp", false),
@@ -4006,6 +4063,183 @@ function installBashPlusSettings(ctx, entry, onSource) {
4006
4063
  });
4007
4064
  }
4008
4065
  //#endregion
4066
+ //#region src/tools/shared/browser-rpc-channel.ts
4067
+ /**
4068
+ * Shared RPC channel constants for the browser-detection round trip between
4069
+ * the settings panel (client half) and the host. Both halves import this
4070
+ * module so the channel/endpoint never drift.
4071
+ *
4072
+ * The channel rides the official `@deepseek-ai/dsh-client-connection` generic
4073
+ * RPC transport (host registers with `ctx.connection.rpc.handle`; the client
4074
+ * calls via `createWebConnectionRpc().call`). Channel names must match
4075
+ * `/^\/[A-Za-z0-9._~-]+$/` and `/api` is reserved — `/tool-plus` is ours.
4076
+ * @module @xiaoso/dsh-tool-plus/browser-rpc-channel
4077
+ */
4078
+ /** Logical RPC channel registered by the host half of this plugin. */
4079
+ const TOOL_PLUS_RPC_CHANNEL = "/tool-plus";
4080
+ //#endregion
4081
+ //#region src/tools/shared/browser-detect.ts
4082
+ /**
4083
+ * Browser executable detection for the read browser renderer — a thin,
4084
+ * fully-injectable shell over the OMP probe family (`src/tools/omp/browser/launch.ts`).
4085
+ *
4086
+ * Two entry points share one candidate pipeline:
4087
+ * - {@link probeBrowsers} — no caching; every call walks the candidate table.
4088
+ * Backs the settings-panel "探测浏览器" button via the host RPC, so a user
4089
+ * can re-probe after installing a browser.
4090
+ * - {@link detectBrowser} — memoizes the first success as the renderer's
4091
+ * executable choice, so repeated reads do not re-stat the filesystem.
4092
+ *
4093
+ * Candidate kinds are annotated for display (`edge` / `chrome` / `chromium` /
4094
+ * `cfr` / `env`); the renderer itself only needs the resolved path, so the
4095
+ * detection result and the launch path never drift.
4096
+ *
4097
+ * Host-side only: this module imports node:fs, so it must never be reached
4098
+ * from the browser client half (it isn't — the client imports the channel
4099
+ * constants module, not this one).
4100
+ * @module @xiaoso/dsh-tool-plus/browser-detect
4101
+ */
4102
+ /** Default environment: the real platform/homedir/which/filesystem. */
4103
+ function defaultBrowserDetectEnv() {
4104
+ return {
4105
+ platform: process.platform,
4106
+ home: process.env.HOME ?? process.env.USERPROFILE ?? "",
4107
+ which: $which,
4108
+ exists: (p) => {
4109
+ try {
4110
+ return fs.existsSync(p);
4111
+ } catch {
4112
+ return false;
4113
+ }
4114
+ },
4115
+ readdir: (p) => fs.readdirSync(p),
4116
+ envPath: process.env.PUPPETEER_EXECUTABLE_PATH,
4117
+ cacheDirs: [getPuppeteerDir()]
4118
+ };
4119
+ }
4120
+ const FAMILY_NAMES = {
4121
+ edge: "Microsoft Edge",
4122
+ chrome: "Google Chrome",
4123
+ chromium: "Chromium",
4124
+ cfr: "Chrome for Testing",
4125
+ env: "PUPPETEER_EXECUTABLE_PATH"
4126
+ };
4127
+ /** Label a candidate path with its browser family (by filename). */
4128
+ function kindOf(executablePath) {
4129
+ const lower = (executablePath.replaceAll("\\", "/").split("/").pop() ?? executablePath).toLowerCase();
4130
+ if (lower.includes("msedge")) return "edge";
4131
+ if (lower.includes("chromium")) return "chromium";
4132
+ return "chrome";
4133
+ }
4134
+ /**
4135
+ * Candidate paths for the given platform with family labels, in the OMP
4136
+ * system table order (see `systemChromiumCandidates`). `env` paths are
4137
+ * injected separately by the caller.
4138
+ */
4139
+ function tableCandidates(env) {
4140
+ const out = [];
4141
+ for (const p of systemChromiumCandidatesForTest(env.platform, env.home, env.which)) {
4142
+ if (!p) continue;
4143
+ out.push({
4144
+ kind: kindOf(p),
4145
+ path: p
4146
+ });
4147
+ }
4148
+ return out;
4149
+ }
4150
+ /**
4151
+ * Scan a Chrome-for-Testing cache root for installed browsers
4152
+ * (`<root>/chrome/<platform>-<build>/chrome(.exe)` layout used by
4153
+ * @puppeteer/browsers and OMP's getPuppeteerDir cache).
4154
+ */
4155
+ function cfrCandidates(env) {
4156
+ const out = [];
4157
+ const execName = env.platform === "win32" ? "chrome.exe" : "chrome";
4158
+ const sep = env.platform === "win32" ? "\\" : "/";
4159
+ for (const root of env.cacheDirs ?? []) try {
4160
+ const chromeDir = `${root}${root.endsWith("/") || root.endsWith("\\") ? "" : sep}chrome`;
4161
+ if (!env.exists(chromeDir)) continue;
4162
+ const list = env.readdir?.(chromeDir) ?? [];
4163
+ for (const build of list) {
4164
+ if (build === "." || build === "..") continue;
4165
+ const exe = `${chromeDir}${sep}${build}${sep}${execName}`;
4166
+ if (env.exists(exe)) out.push({
4167
+ kind: "cfr",
4168
+ path: exe
4169
+ });
4170
+ }
4171
+ } catch {}
4172
+ return out;
4173
+ }
4174
+ /** Detect every usable browser on the machine, best first. No caching. */
4175
+ function probeBrowsers(env = defaultBrowserDetectEnv()) {
4176
+ const found = [];
4177
+ const seen = /* @__PURE__ */ new Set();
4178
+ const push = (browser) => {
4179
+ if (browser === void 0 || seen.has(browser.path)) return;
4180
+ seen.add(browser.path);
4181
+ found.push(browser);
4182
+ };
4183
+ if (env.envPath) push({
4184
+ kind: "env",
4185
+ name: FAMILY_NAMES.env,
4186
+ path: env.envPath
4187
+ });
4188
+ for (const candidate of [...tableCandidates(env), ...cfrCandidates(env)]) {
4189
+ if (!env.exists(candidate.path)) continue;
4190
+ push({
4191
+ kind: candidate.kind,
4192
+ name: FAMILY_NAMES[candidate.kind],
4193
+ path: candidate.path
4194
+ });
4195
+ }
4196
+ return found;
4197
+ }
4198
+ //#endregion
4199
+ //#region src/host/browser-probe-rpc.ts
4200
+ /**
4201
+ * Install the browser/detect RPC endpoint. Returns a disposer that removes
4202
+ * the registration (call it from the plugin's `ctx.effect` teardown).
4203
+ * Never throws for "unavailable" — the return value tells the caller whether
4204
+ * the channel is actually live.
4205
+ */
4206
+ function installBrowserProbeRpc(ctx) {
4207
+ let disposer;
4208
+ ctx.inject(["connection"], (connectionCtx) => {
4209
+ try {
4210
+ const handler = async (endpoint, _payload, _signal) => {
4211
+ if (endpoint !== "browser/detect") return {
4212
+ ok: false,
4213
+ error: {
4214
+ code: "bad-request",
4215
+ message: `Unknown tool-plus endpoint: ${endpoint}`,
4216
+ details: { issues: [] }
4217
+ }
4218
+ };
4219
+ try {
4220
+ return {
4221
+ ok: true,
4222
+ value: { found: probeBrowsers() }
4223
+ };
4224
+ } catch (error) {
4225
+ return {
4226
+ ok: false,
4227
+ error: {
4228
+ code: "internal",
4229
+ message: error instanceof Error ? error.message : String(error),
4230
+ details: {}
4231
+ }
4232
+ };
4233
+ }
4234
+ };
4235
+ disposer = connectionCtx.connection.rpc.handle(TOOL_PLUS_RPC_CHANNEL, handler, { authority: "loopback" });
4236
+ } catch {
4237
+ disposer = void 0;
4238
+ }
4239
+ });
4240
+ return () => disposer?.();
4241
+ }
4242
+ //#endregion
4009
4243
  //#region src/config/truncate.ts
4010
4244
  /**
4011
4245
  * Config-driven output truncation for background→foreground completion text.
@@ -13938,7 +14172,7 @@ function cleanFeedText(text) {
13938
14172
  * Parse RSS/Atom feed to markdown
13939
14173
  */
13940
14174
  async function parseFeedToMarkdown(content, maxItems = 10) {
13941
- const { parseHTML } = await import("./dom-DGdYAVTv.mjs");
14175
+ const { parseHTML } = await import("./dom-CawWi8fa.mjs");
13942
14176
  try {
13943
14177
  const doc = parseHTML(content).document;
13944
14178
  const channel = doc.querySelector("channel");
@@ -14000,16 +14234,32 @@ const FETCH_PROVIDER_ORDER = [
14000
14234
  "trafilatura",
14001
14235
  "lynx",
14002
14236
  "parallel",
14003
- "jina"
14237
+ "jina",
14238
+ "browser"
14004
14239
  ];
14240
+ /** Backends that cannot execute page JavaScript — held back on CSR shells. */
14241
+ const CSR_SUSCEPTIBLE = /* @__PURE__ */ new Set([
14242
+ "native",
14243
+ "trafilatura",
14244
+ "lynx"
14245
+ ]);
14005
14246
  /**
14006
14247
  * Render HTML to markdown by trying reader backends in priority order: native
14007
- * (in-process), trafilatura, lynx, Parallel, then Jina. The `providers.fetch`
14008
- * setting picks the order — `auto` uses the default above; any specific backend
14009
- * is tried first, then the remaining backends as fallbacks. Every backend's
14010
- * output must clear the same quality gate (>100 non-whitespace chars and not
14011
- * {@link isLowQualityOutput}) before it is accepted, otherwise the next backend
14012
- * is tried.
14248
+ * (in-process), trafilatura, lynx, Parallel, Jina, then the browser
14249
+ * (headless-Chromium JS rendering, see {@link renderUrlWithBrowser} — gated
14250
+ * behind `browser.readerEnabled`). The `providers.fetch` setting picks the
14251
+ * order — `auto` uses the default above; any specific backend is tried first,
14252
+ * then the remaining backends as fallbacks. Every backend's output must clear
14253
+ * the same quality gate (>100 non-whitespace chars and not
14254
+ * {@link isLowQualityOutput}) before it is accepted, otherwise the next
14255
+ * backend is tried.
14256
+ *
14257
+ * When the raw HTML is a CSR app shell (script-heavy, text-light), output
14258
+ * from {@link CSR_SUSCEPTIBLE} backends (which cannot execute page
14259
+ * JavaScript) is held back as low quality so the chain still reaches the
14260
+ * browser backend; with no usable browser the parked shell is returned as
14261
+ * before, so degradation is unchanged. The browser's own output on a shell
14262
+ * is accepted as-is (a noscript remnant is expected in it).
14013
14263
  *
14014
14264
  * The overall `timeout` budget bounds the whole call; remote backends (Parallel,
14015
14265
  * Jina) are additionally capped at `REMOTE_READER_MAX_MS` so a hung endpoint
@@ -14081,17 +14331,31 @@ async function renderHtmlToText(url, html, timeout, settings, userSignal, storag
14081
14331
  const contentLength = Number(response.headers.get("content-length"));
14082
14332
  if (Number.isFinite(contentLength) && contentLength > JINA_READER_MAX_BYTES) return null;
14083
14333
  return parseJinaReaderContent(await response.text());
14334
+ },
14335
+ browser: async () => {
14336
+ if (settings.get("browser.readerEnabled") === false) return null;
14337
+ const { renderUrlWithBrowser } = await import("./browser-render-z6b8Ljo9.mjs");
14338
+ return renderUrlWithBrowser(url, {
14339
+ timeoutMs: remoteBudgetMs,
14340
+ signal: userSignal
14341
+ });
14084
14342
  }
14085
14343
  };
14086
14344
  const preference = settings.get("providers.fetch");
14087
14345
  const order = preference === "auto" ? FETCH_PROVIDER_ORDER : [preference, ...FETCH_PROVIDER_ORDER.filter((method) => method !== preference)];
14088
14346
  let lowQuality = null;
14347
+ const csrShell = isCsrHtmlShell(html);
14089
14348
  for (const method of order) {
14090
14349
  userSignal?.throwIfAborted();
14091
14350
  try {
14092
14351
  const content = await runners[method]();
14093
14352
  if (!content || content.trim().length <= 100) continue;
14094
- if (!isLowQualityOutput(content)) return {
14353
+ if (csrShell && method === "browser") return {
14354
+ content,
14355
+ ok: true,
14356
+ method
14357
+ };
14358
+ if (!(csrShell && CSR_SUSCEPTIBLE.has(method)) && !isLowQualityOutput(content)) return {
14095
14359
  content,
14096
14360
  ok: true,
14097
14361
  method
@@ -14133,6 +14397,21 @@ function isLowQualityOutput(content) {
14133
14397
  return false;
14134
14398
  }
14135
14399
  /**
14400
+ * Detect a client-side-rendered (CSR) app shell in raw HTML: script-dense
14401
+ * markup with almost no statically visible text. Such pages only produce body
14402
+ * content after JavaScript executes, so JS-unaware extractors (native/
14403
+ * trafilatura/lynx) necessarily return a shell remnant (title/meta remnants)
14404
+ * while the browser backend can render the real body. Deliberately
14405
+ * conservative — compact HTML, multiple script tags, sparse static text — so
14406
+ * genuine SSR pages are never flagged and their rendering order is unchanged.
14407
+ * @returns true when the HTML looks like an unrendered SPA shell.
14408
+ */
14409
+ function isCsrHtmlShell(html) {
14410
+ if (html.length > 2e5) return false;
14411
+ if ((html.match(/<script\b/gi)?.length ?? 0) < 3) return false;
14412
+ return html.replace(/<script\b[\s\S]*?<\/script>/gi, " ").replace(/<style\b[\s\S]*?<\/style>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<[^>]+>/g, " ").replace(/&[a-z#0-9]+;/gi, " ").replace(/\s+/g, " ").trim().length < 2e3;
14413
+ }
14414
+ /**
14136
14415
  * Format JSON
14137
14416
  */
14138
14417
  function formatJson(content) {
@@ -14276,7 +14555,7 @@ let specialHandlersPromise;
14276
14555
  * requires a special handler, so we keep them out of the cold-startup graph.
14277
14556
  */
14278
14557
  function loadSpecialHandlers() {
14279
- specialHandlersPromise ??= import("./scrapers-1FTlHNQV.mjs").then((m) => m.specialHandlers);
14558
+ specialHandlersPromise ??= import("./scrapers-DrSYgPyf.mjs").then((m) => m.specialHandlers);
14280
14559
  return specialHandlersPromise;
14281
14560
  }
14282
14561
  /**
@@ -25493,6 +25772,7 @@ function apply(ctx, config = {}) {
25493
25772
  cfg = current();
25494
25773
  registerModeSensitive();
25495
25774
  });
25775
+ const disposeBrowserProbe = installBrowserProbeRpc(ctx);
25496
25776
  ctx.systemPrompt.section({
25497
25777
  name: "tool:bash",
25498
25778
  order: 105,
@@ -25502,6 +25782,7 @@ function apply(ctx, config = {}) {
25502
25782
  closeSessionShells("");
25503
25783
  cleanupSnapshots();
25504
25784
  sessionStates.clear();
25785
+ disposeBrowserProbe();
25505
25786
  }, "bash-plus teardown");
25506
25787
  ctx.on("agent/disposed", ({ agent }) => {
25507
25788
  closeSessionShells(agent.session.id);
@@ -1,9 +1,9 @@
1
1
  import { installBunShim as __installBunShim } from "./bun-shim.mjs";
2
2
  __installBunShim();
3
3
  import { r as __exportAll } from "./fs-promises-shim-Bx0fpxuk.mjs";
4
- import { H as getAgentDir, P as $env } from "./src-BgpusnsB.mjs";
5
- import { c as OAuthError, h as LoginCancelledError } from "./error-Dd8DIqs-.mjs";
6
- import "./utils-Cp76gCyk.mjs";
4
+ import { H as getAgentDir, P as $env } from "./src-D_e1qMwi.mjs";
5
+ import { c as OAuthError, h as LoginCancelledError } from "./error-DoHuatWw.mjs";
6
+ import "./utils-fnV2zzp3.mjs";
7
7
  import { n as isRecord } from "./type-guards-B9VVjRDK.mjs";
8
8
  import * as path from "node:path";
9
9
  import * as fs from "node:fs";
@@ -0,0 +1,148 @@
1
+ import { installBunShim as __installBunShim } from "./bun-shim.mjs";
2
+ __installBunShim();
3
+ import { d as $which, l as exec } from "./src-D_e1qMwi.mjs";
4
+ import * as path from "node:path";
5
+ import * as fs from "node:fs";
6
+ import * as os from "node:os";
7
+ //#region src/tools/bash/logger.ts
8
+ let current = {
9
+ info: (message, ...args) => console.info(`[bash-plus] ${message}`, ...args),
10
+ warn: (message, ...args) => console.warn(`[bash-plus] ${message}`, ...args),
11
+ error: (message, ...args) => console.error(`[bash-plus] ${message}`, ...args),
12
+ debug: (message, ...args) => console.debug(`[bash-plus] ${message}`, ...args)
13
+ };
14
+ /** Install the composition logger (Cordis `ctx.logger`); replaces the console adapter. */
15
+ function setRuntimeLogger(logger) {
16
+ current = logger;
17
+ }
18
+ /** The installed logger; defaults to the console adapter. */
19
+ function runtimeLogger() {
20
+ return current;
21
+ }
22
+ //#endregion
23
+ //#region src/tools/omp/browser/launch.ts
24
+ /**
25
+ * Chromium executable resolution for the read browser renderer — the probe
26
+ * family of OMP's browser launch machinery, trimmed to what rendering needs:
27
+ * `PUPPETEER_EXECUTABLE_PATH` wins, then the system Chrome/Edge/Chromium
28
+ * candidate table. The OMP download fallback (Chrome for Testing via
29
+ * @puppeteer/browsers) and the whole launch/stealth/worker surface are
30
+ * intentionally absent — this plugin never installs a browser; rendering
31
+ * degrades when none is found.
32
+ *
33
+ * The one behavioral adaptation vs. upstream: `isChromiumExecutable` uses
34
+ * `ptree.exec` instead of `Bun.spawn` so the probe runs under Node too.
35
+ */
36
+ function isExecutableFile(p) {
37
+ try {
38
+ if (!fs.statSync(p).isFile()) return false;
39
+ if (process.platform === "win32") return true;
40
+ fs.accessSync(p, fs.constants.X_OK);
41
+ return true;
42
+ } catch {
43
+ return false;
44
+ }
45
+ }
46
+ async function isChromiumExecutable(p) {
47
+ if (!isExecutableFile(p)) return false;
48
+ if (process.platform !== "linux") return true;
49
+ try {
50
+ const result = await exec([p, "--version"], {
51
+ stderr: "buffer",
52
+ allowNonZero: true,
53
+ allowAbort: true,
54
+ signal: AbortSignal.timeout(3e3)
55
+ });
56
+ return result.exitCode === 0 && /Chrom|Edg/i.test(result.stdout);
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+ /** Flatpak application id published by the Ungoogled Chromium project. */
62
+ const UNGOOGLED_CHROMIUM_FLATPAK_ID = "io.github.ungoogled_software.ungoogled_chromium";
63
+ function systemChromiumCandidates(platform = process.platform, home = os.homedir(), which = $which) {
64
+ const candidates = [];
65
+ switch (platform) {
66
+ case "darwin":
67
+ for (const root of ["/Applications", path.join(home, "Applications")]) candidates.push(path.join(root, "Google Chrome.app/Contents/MacOS/Google Chrome"), path.join(root, "Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta"), path.join(root, "Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev"), path.join(root, "Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"), path.join(root, "Chromium.app/Contents/MacOS/Chromium"), path.join(root, "Microsoft Edge.app/Contents/MacOS/Microsoft Edge"));
68
+ break;
69
+ case "linux": {
70
+ for (const name of [
71
+ "google-chrome-stable",
72
+ "google-chrome",
73
+ "chromium",
74
+ "chromium-browser",
75
+ "chrome"
76
+ ]) {
77
+ const found = which(name);
78
+ if (found) candidates.push(found);
79
+ }
80
+ candidates.push("/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/usr/bin/chromium", "/usr/bin/chromium-browser", "/snap/bin/chromium", "/var/lib/flatpak/exports/bin/com.google.Chrome", "/var/lib/flatpak/exports/bin/org.chromium.Chromium");
81
+ let onNixos = false;
82
+ try {
83
+ onNixos = fs.existsSync("/etc/NIXOS");
84
+ } catch {}
85
+ if (onNixos) candidates.push(path.join(home, ".nix-profile/bin/chromium"), "/run/current-system/sw/bin/chromium");
86
+ for (const name of ["ungoogled-chromium", "ungoogled-chromium-browser"]) {
87
+ const found = which(name);
88
+ if (found) candidates.push(found);
89
+ }
90
+ candidates.push("/usr/bin/ungoogled-chromium", "/usr/bin/ungoogled-chromium-browser", `/var/lib/flatpak/exports/bin/${UNGOOGLED_CHROMIUM_FLATPAK_ID}`, path.join(home, ".local/share/flatpak/exports/bin", UNGOOGLED_CHROMIUM_FLATPAK_ID));
91
+ break;
92
+ }
93
+ case "win32": {
94
+ const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
95
+ const programFilesX86 = process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
96
+ const localAppData = process.env.LOCALAPPDATA ?? path.join(home, "AppData\\Local");
97
+ candidates.push(path.join(programFiles, "Google\\Chrome\\Application\\chrome.exe"), path.join(programFilesX86, "Google\\Chrome\\Application\\chrome.exe"), path.join(localAppData, "Google\\Chrome\\Application\\chrome.exe"), path.join(programFiles, "Chromium\\Application\\chrome.exe"), path.join(localAppData, "Chromium\\Application\\chrome.exe"), path.join(programFiles, "Microsoft\\Edge\\Application\\msedge.exe"), path.join(programFilesX86, "Microsoft\\Edge\\Application\\msedge.exe"));
98
+ break;
99
+ }
100
+ }
101
+ return candidates;
102
+ }
103
+ let resolvedChromium;
104
+ async function resolveSystemChromium() {
105
+ if (resolvedChromium !== void 0) return resolvedChromium ?? void 0;
106
+ const seen = /* @__PURE__ */ new Set();
107
+ for (const candidate of systemChromiumCandidates()) {
108
+ if (!candidate || seen.has(candidate)) continue;
109
+ seen.add(candidate);
110
+ if (await isChromiumExecutable(candidate)) {
111
+ resolvedChromium = candidate;
112
+ runtimeLogger().debug("Using system Chrome/Chromium", { path: candidate });
113
+ return candidate;
114
+ }
115
+ }
116
+ resolvedChromium = null;
117
+ }
118
+ let chromiumExecutablePromise;
119
+ /**
120
+ * Resolve the Chromium executable the browser renderer will launch.
121
+ * `PUPPETEER_EXECUTABLE_PATH` always wins, then the system
122
+ * Chrome/Edge/Chromium candidate table (Windows and macOS check mere file
123
+ * existence; Linux additionally version-probes, see {@link isChromiumExecutable}).
124
+ * Returns undefined when nothing is found — puppeteer-core would fall back to
125
+ * its own bundled-Chromium search, which for this package means failure, so
126
+ * callers should treat undefined as "browser rendering unavailable".
127
+ *
128
+ * Unlike OMP upstream there is no Chrome-for-Testing download fallback: this
129
+ * plugin ships no browser installer, and the detection button only reports
130
+ * what is already on the machine.
131
+ */
132
+ async function ensureChromiumExecutable() {
133
+ const envPath = process.env.PUPPETEER_EXECUTABLE_PATH;
134
+ if (envPath) return envPath;
135
+ chromiumExecutablePromise ??= resolveSystemChromium();
136
+ try {
137
+ return await chromiumExecutablePromise;
138
+ } catch {
139
+ chromiumExecutablePromise = void 0;
140
+ return;
141
+ }
142
+ }
143
+ /** Exposes executable candidates for detection tests (OMP parity export). */
144
+ function systemChromiumCandidatesForTest(platform = process.platform, home, which) {
145
+ return systemChromiumCandidates(platform, home, which);
146
+ }
147
+ //#endregion
148
+ export { setRuntimeLogger as i, systemChromiumCandidatesForTest as n, runtimeLogger as r, ensureChromiumExecutable as t };
@@ -1,7 +1,7 @@
1
1
  import { installBunShim as __installBunShim } from "./bun-shim.mjs";
2
2
  __installBunShim();
3
3
  import { n as fs_promises_shim_exports } from "./fs-promises-shim-Bx0fpxuk.mjs";
4
- import { l as unzip, u as unzipText } from "./zip-Cg9ANN_U.mjs";
4
+ import { l as unzip, u as unzipText } from "./zip-CqKW2-TK.mjs";
5
5
  import { n as normalizeTablesHtml, t as createTurndown } from "./turndown-DN7ByzEI.mjs";
6
6
  import * as path from "node:path";
7
7
  import { pdfToMarkdown } from "@oh-my-pi/pi-natives";
@@ -1,6 +1,6 @@
1
1
  import { installBunShim as __installBunShim } from "./bun-shim.mjs";
2
2
  __installBunShim();
3
- import { t as createApiKeyLogin } from "./api-key-login-BzqNdM2y.mjs";
3
+ import { t as createApiKeyLogin } from "./api-key-login-BikQAfD-.mjs";
4
4
  //#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/registry/oauth/minimax-code.ts
5
5
  /**
6
6
  * MiniMax Token Plan login flow.
@@ -1,6 +1,6 @@
1
1
  import { installBunShim as __installBunShim } from "./bun-shim.mjs";
2
2
  __installBunShim();
3
- import { t as cleanModelName } from "./utils-D2YknRk4.mjs";
3
+ import { t as cleanModelName } from "./utils-B-mTYQBc.mjs";
4
4
  //#region node_modules/.pnpm/@oh-my-pi+pi-catalog@17.3.5/node_modules/@oh-my-pi/pi-catalog/src/fireworks-model-id.ts
5
5
  const FIREWORKS_WIRE_PREFIX = "accounts/fireworks/models/";
6
6
  const FIREPASS_WIRE_PREFIX = "accounts/fireworks/routers/";
@@ -1,14 +1,14 @@
1
1
  import { installBunShim as __installBunShim } from "./bun-shim.mjs";
2
2
  __installBunShim();
3
3
  import { r as __exportAll } from "./fs-promises-shim-Bx0fpxuk.mjs";
4
- import { g as parseStreamingJson, i as readJsonl } from "./src-BgpusnsB.mjs";
5
- import { G as fetchWithRetry, b as LLAMA_CPP_TOOL_CALL_PARSE_PATTERN, g as MissingApiKeyError, l as finalize, z as OllamaApiError } from "./error-Dd8DIqs-.mjs";
6
- import { C as kStreamingPartialJson, I as sanitizeSchemaForOllama, a as normalizeSystemPrompts, h as clearStreamingPartialJson, k as toolWireSchema, m as transformMessages } from "./utils-Cp76gCyk.mjs";
7
- import { Ct as withEmptyCompletionRetry, n as getEnvApiKey } from "./stream-CRtexYLe.mjs";
8
- import { i as partitionVisionContent, r as joinTextWithImagePlaceholder } from "./vision-guard-01qSwFpf.mjs";
9
- import { t as AssistantMessageEventStream } from "./event-stream-BbFZJy-z.mjs";
10
- import { n as getOpenAIStreamFirstEventTimeoutMs, r as getOpenAIStreamIdleTimeoutMs, t as armPreResponseTimeout } from "./idle-iterator-DUjKQEGT.mjs";
11
- import { n as getStreamMarkupHealingPattern, t as StreamMarkupHealing } from "./stream-markup-healing-KMkiAA00.mjs";
4
+ import { g as parseStreamingJson, i as readJsonl } from "./src-D_e1qMwi.mjs";
5
+ import { G as fetchWithRetry, b as LLAMA_CPP_TOOL_CALL_PARSE_PATTERN, g as MissingApiKeyError, l as finalize, z as OllamaApiError } from "./error-DoHuatWw.mjs";
6
+ import { C as kStreamingPartialJson, I as sanitizeSchemaForOllama, a as normalizeSystemPrompts, h as clearStreamingPartialJson, k as toolWireSchema, m as transformMessages } from "./utils-fnV2zzp3.mjs";
7
+ import { Ct as withEmptyCompletionRetry, n as getEnvApiKey } from "./stream-DveNFWSN.mjs";
8
+ import { i as partitionVisionContent, r as joinTextWithImagePlaceholder } from "./vision-guard-BsvOWmfB.mjs";
9
+ import { t as AssistantMessageEventStream } from "./event-stream-C-FmBYUt.mjs";
10
+ import { n as getOpenAIStreamFirstEventTimeoutMs, r as getOpenAIStreamIdleTimeoutMs, t as armPreResponseTimeout } from "./idle-iterator-HzbbfXP4.mjs";
11
+ import { n as getStreamMarkupHealingPattern, t as StreamMarkupHealing } from "./stream-markup-healing-C9eC7Gf8.mjs";
12
12
  //#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/providers/ollama.ts
13
13
  var ollama_exports = /* @__PURE__ */ __exportAll({ streamOllama: () => streamOllama });
14
14
  function normalizeBaseUrl(baseUrl) {