better-dsh 0.2.3-d → 0.2.3-e

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 (17) hide show
  1. package/docs/50_test-reports/2026-09-11-control-prompt-into-eval-description/345/256/236/346/265/213/346/212/245/345/221/212.md +222 -0
  2. package/docs/50_test-reports/2026-09-12-fs-scheme-resolution-/345/256/236/346/265/213/346/212/245/345/221/212.md +81 -0
  3. package/docs/50_test-reports/2026-09-12-url-schemes-grammar-matrix/344/270/216catalog-centralize-/345/256/236/346/265/213/346/212/245/345/221/212.md +234 -0
  4. package/docs/50_test-reports/2026-09-12-url-schemes-recallable-context-design/351/252/214/350/257/201/346/212/245/345/221/212.md +160 -0
  5. package/docs/50_test-reports/2026-09-12-url-schemes-recallable-context-/345/256/236/346/265/213/345/211/247/346/234/254.md +44 -0
  6. package/docs/50_test-reports/2026-09-12-url-schemes-recallable-context-/345/256/236/346/265/213/346/212/245/345/221/212.md +89 -0
  7. package/docs/50_test-reports/2026-09-12-url-schemes-/345/205/255scheme/345/206/222/347/203/237/344/270/216/350/276/271/347/225/214/345/256/236/346/265/213/346/212/245/345/221/212.md +198 -0
  8. package/docs/50_test-reports/2026-09-13-hashline-off/344/270/213scheme/345/217/257/350/276/276/346/200/247/345/267/245/345/205/267/351/235/242/344/270/215/345/257/271/347/247/260-/345/256/236/346/265/213/346/212/245/345/221/212.md +246 -0
  9. package/docs/50_test-reports/2026-09-13-preact-ui-shell/345/256/236/346/265/213/346/212/245/345/221/212.md +50 -0
  10. package/docs/50_test-reports/v0.2.3c-mobile-wave/345/256/236/346/265/213/346/212/245/345/221/212.md +47 -0
  11. package/lib/fs-aware/sandbox-plugin.d.ts +71 -0
  12. package/lib/fs-aware/sandbox-plugin.js +249 -0
  13. package/lib/index.d.ts +9 -0
  14. package/lib/index.js +547 -705
  15. package/lib/wrap-DC8O3SYz.js +721 -0
  16. package/package.json +6 -1
  17. package/url-schemes-instruction.md +22 -0
package/lib/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { a as RESERVED_ERROR_MEMBERS, i as RESERVED_BINDING_GLOBALS, n as DUNDER_MEMBER, o as ReplRuntime, r as PORTABLE_RESERVED_WORDS, t as isFlatBindableName } from "./py-sdk-Chvy92MB.js";
2
2
  import { s as resolveKernelEnv } from "./kernel-env-hxaihi9C.js";
3
+ import { a as createDshHandler, c as createDvcHandler, d as registerDvcDevice, f as UrlSchemesError, i as createHttpHandler, l as dispatchDvcWrite, m as SCHEME_NAMES, n as wrapFsWithSchemes, o as UrlResolver, p as parseUrl, r as HTTP_SCHEMES, s as resolveDocsDir, u as listDvcDevices } from "./wrap-DC8O3SYz.js";
3
4
  import { createRequire } from "node:module";
4
5
  import { Context } from "@deepseek-ai/cordis";
5
6
  import { createHmac, randomBytes, randomUUID } from "node:crypto";
6
- import { accessSync, constants, existsSync, mkdirSync, mkdtempSync, promises, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
7
+ import { accessSync, constants, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
7
8
  import * as path from "node:path";
8
9
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
9
10
  import z from "@deepseek-ai/schemastery";
@@ -1728,7 +1729,7 @@ const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
1728
1729
  const KERNEL_OWNED_NAME = /^_+dashr/i;
1729
1730
  /** Manifest format version; a snapshot written under a different version is not replayable. */
1730
1731
  const SNAPSHOT_FORMAT = 1;
1731
- function messageOf$3(error) {
1732
+ function messageOf$2(error) {
1732
1733
  return error instanceof Error ? error.message : String(error);
1733
1734
  }
1734
1735
  /** Key under which the per-session kernel map stores agentless runs. */
@@ -1841,7 +1842,7 @@ var DashrRuntime = class extends ReplRuntime {
1841
1842
  } catch (error) {
1842
1843
  return finalizeOutput([], void 0, {
1843
1844
  kind: "worker-exit",
1844
- message: `kernel failed to start: ${messageOf$3(error)}`
1845
+ message: `kernel failed to start: ${messageOf$2(error)}`
1845
1846
  }, this.config.maxOutputBytes);
1846
1847
  }
1847
1848
  if (!entry.restoreAttempted) {
@@ -2031,7 +2032,7 @@ var DashrRuntime = class extends ReplRuntime {
2031
2032
  } catch (error) {
2032
2033
  return {
2033
2034
  ok: false,
2034
- message: messageOf$3(error)
2035
+ message: messageOf$2(error)
2035
2036
  };
2036
2037
  }
2037
2038
  const value = snapshotJsonValue(resolved);
@@ -2157,7 +2158,7 @@ var DashrRuntime = class extends ReplRuntime {
2157
2158
  } catch (error) {
2158
2159
  return finalizeOutput([], void 0, {
2159
2160
  kind: "worker-exit",
2160
- message: `kernel died and respawn failed (${messageOf$3(error)}); the next run will retry a fresh kernel`
2161
+ message: `kernel died and respawn failed (${messageOf$2(error)}); the next run will retry a fresh kernel`
2161
2162
  }, this.config.maxOutputBytes);
2162
2163
  }
2163
2164
  entry.restoreAttempted = true;
@@ -2258,252 +2259,14 @@ var DashrRuntime = class extends ReplRuntime {
2258
2259
  const saved = await kernel.snapshotState(join(dir, "state.dill"), join(dir, "manifest.json"), spec);
2259
2260
  if (saved.kind !== "ok" && saved.kind !== "skipped") this.logger.warn(`kernel namespace snapshot for ${JSON.stringify(entry.key)} failed: ${saved.reason}`);
2260
2261
  } catch (error) {
2261
- this.logger.warn(`kernel namespace snapshot for ${JSON.stringify(entry.key)} failed: ${messageOf$3(error)}`);
2262
+ this.logger.warn(`kernel namespace snapshot for ${JSON.stringify(entry.key)} failed: ${messageOf$2(error)}`);
2262
2263
  }
2263
2264
  await kernel.dispose();
2264
2265
  }
2265
2266
  };
2266
2267
 
2267
2268
  //#endregion
2268
- //#region src/url-schema/selector.ts
2269
- /** Structured error thrown by the URL-schema layer. */
2270
- var UrlSchemaError = class extends Error {
2271
- code;
2272
- constructor(code, message) {
2273
- super(message);
2274
- this.name = "UrlSchemaError";
2275
- this.code = code;
2276
- }
2277
- };
2278
- /** Matches `scheme://rest` with a lowercase alphanumeric scheme. */
2279
- const SCHEME_RE = /^([a-z][a-z0-9]*):\/\/(.*)$/;
2280
- /** One line range token: `N`, `N-M`, or open `N-`. */
2281
- const RANGE_RE = /^(\d+)(?:-(\d*))?$/;
2282
- /**
2283
- * Schemes whose URL syntax reserves `:` and `?` (ports, query strings): the
2284
- * whole remainder is the path and NO selector is parsed. The http(s) handler
2285
- * is documented as selector-free for exactly this reason — `:8443` is a port
2286
- * and `?q=` a server query, never a line range or content filter.
2287
- */
2288
- const SELECTOR_EXEMPT = {
2289
- http: true,
2290
- https: true
2291
- };
2292
- /**
2293
- * Parse a `scheme://` URL into `{ scheme, path, selector }`.
2294
- *
2295
- * Throws a structured {@link UrlSchemaError} (code `URL_NO_SCHEME`) when the
2296
- * URL has no `scheme://` prefix. Any lowercase scheme is accepted here; the
2297
- * resolver rejects schemes with no registered handler. Selector-exempt
2298
- * schemes ({@link SELECTOR_EXEMPT}) return the whole remainder as the path.
2299
- */
2300
- function parseUrl(raw) {
2301
- const m = SCHEME_RE.exec(raw);
2302
- if (m === null) throw new UrlSchemaError("URL_NO_SCHEME", `URL "${raw}" has no scheme — expected "scheme://" (skill://, agent://, dsh://, ctx://, dvc://, http://, https://)`);
2303
- const scheme = m[1];
2304
- const rest = m[2];
2305
- if (SELECTOR_EXEMPT[scheme] === true) return {
2306
- scheme,
2307
- path: rest,
2308
- selector: null
2309
- };
2310
- const colon = rest.indexOf(":");
2311
- const question = rest.indexOf("?");
2312
- const hasColon = colon !== -1;
2313
- const hasQuestion = question !== -1;
2314
- if (!hasColon && !hasQuestion) return {
2315
- scheme,
2316
- path: rest,
2317
- selector: null
2318
- };
2319
- const isQuery = hasQuestion && (!hasColon || question < colon);
2320
- const marker = isQuery ? question : colon;
2321
- const path$1 = rest.slice(0, marker);
2322
- const selPart = rest.slice(marker + 1);
2323
- return {
2324
- scheme,
2325
- path: path$1,
2326
- selector: isQuery ? {
2327
- kind: "query",
2328
- q: selPart.startsWith("q=") ? selPart.slice(2) : selPart
2329
- } : parseColonSelector(selPart)
2330
- };
2331
- }
2332
- /** `:raw`, `:path/<subpath>`, or a `:N-M` line range list. */
2333
- function parseColonSelector(selPart) {
2334
- if (selPart === "raw") return { kind: "raw" };
2335
- if (selPart.startsWith("path/")) return {
2336
- kind: "path",
2337
- value: selPart.slice(5)
2338
- };
2339
- return {
2340
- kind: "lines",
2341
- ranges: parseRanges(selPart)
2342
- };
2343
- }
2344
- /** `N`, `N-M`, `N-`, comma-separated → `[[start, end], ...]` (1-based). */
2345
- function parseRanges(spec) {
2346
- return spec.split(",").map((part) => {
2347
- const token = part.trim();
2348
- const m = RANGE_RE.exec(token);
2349
- if (m === null) throw new UrlSchemaError("URL_BAD_SELECTOR", `invalid line selector ":${spec}" — expected N, N-M, or N-M,N2-M2`);
2350
- const start = Number(m[1]);
2351
- if (start < 1) throw new UrlSchemaError("URL_BAD_SELECTOR", `line numbers are 1-based — got ${start} in ":${spec}"`);
2352
- const end = m[2] === void 0 ? start : m[2] === "" ? Infinity : Number(m[2]);
2353
- if (end !== Infinity && end < start) throw new UrlSchemaError("URL_BAD_SELECTOR", `line range ${start}-${end} is empty — end must be >= start`);
2354
- return [start, end];
2355
- });
2356
- }
2357
- /**
2358
- * Apply a selector to already-resolved text.
2359
- *
2360
- * `null` and `{ kind: 'raw' }` return the text unchanged. `lines` performs a
2361
- * 1-based inclusive slice (open tail via `Infinity`). `path` navigates a JSON
2362
- * sub-resource by dot-path. `query` navigates JSON by dot-path when the text
2363
- * parses as JSON, and otherwise keeps the lines containing the query string.
2364
- */
2365
- function applySelector(text, sel) {
2366
- if (sel === null) return text;
2367
- switch (sel.kind) {
2368
- case "raw": return text;
2369
- case "lines": return applyLines(text, sel.ranges);
2370
- case "path": return applyPath(text, sel.value);
2371
- case "query": return applyQuery(text, sel.q);
2372
- }
2373
- }
2374
- /** 1-based inclusive line slicing; `Infinity` end = open tail. */
2375
- function applyLines(text, ranges) {
2376
- if (ranges.length === 0) return "";
2377
- const lines = text.split("\n");
2378
- const out = [];
2379
- for (const [startRaw, endRaw] of ranges) {
2380
- const start = Math.max(1, startRaw);
2381
- const end = endRaw === Infinity ? lines.length : Math.min(lines.length, endRaw);
2382
- if (start <= end) out.push(...lines.slice(start - 1, end));
2383
- }
2384
- return out.join("\n");
2385
- }
2386
- /** Navigate a JSON sub-resource by dot-path; non-JSON text passes through. */
2387
- function applyPath(text, value) {
2388
- if (value === "") return text;
2389
- const parsed = tryParseJson(text);
2390
- if (parsed === void 0) return text;
2391
- const node = navigate(parsed, value);
2392
- return node === void 0 ? text : stringifyNode(node);
2393
- }
2394
- /** JSON dot-path query when the text is JSON, else a substring line filter. */
2395
- function applyQuery(text, q) {
2396
- if (q === "") return text;
2397
- const parsed = tryParseJson(text);
2398
- if (parsed !== void 0) {
2399
- const node = navigate(parsed, q);
2400
- return node === void 0 ? text : stringifyNode(node);
2401
- }
2402
- return text.split("\n").filter((line) => line.includes(q)).join("\n");
2403
- }
2404
- function tryParseJson(text) {
2405
- try {
2406
- return JSON.parse(text);
2407
- } catch {
2408
- return;
2409
- }
2410
- }
2411
- /** Walk a dot-path through a parsed JSON value (numeric segments index arrays). */
2412
- function navigate(node, path$1) {
2413
- let current$1 = node;
2414
- for (const segment of path$1.split(".")) {
2415
- if (segment === "") return void 0;
2416
- if (Array.isArray(current$1)) {
2417
- const idx = Number(segment);
2418
- if (!Number.isInteger(idx) || idx < 0) return void 0;
2419
- current$1 = current$1[idx];
2420
- } else if (current$1 !== null && typeof current$1 === "object") current$1 = current$1[segment];
2421
- else return;
2422
- }
2423
- return current$1;
2424
- }
2425
- /** Render a navigated leaf as text. */
2426
- function stringifyNode(node) {
2427
- if (typeof node === "string") return node;
2428
- try {
2429
- return JSON.stringify(node) ?? "";
2430
- } catch {
2431
- return String(node);
2432
- }
2433
- }
2434
-
2435
- //#endregion
2436
- //#region src/url-schema/handlers/dvc.ts
2437
- /** Module-level device registry: insertion-ordered, shared by every handler instance. */
2438
- const devices = /* @__PURE__ */ new Map();
2439
- /** Register (or replace) the device mounted under `name`. */
2440
- function registerDvcDevice(name$2, device) {
2441
- devices.set(name$2, device);
2442
- }
2443
- /** Read-only view of the registered devices, in registration order. */
2444
- function listDvcDevices() {
2445
- return devices;
2446
- }
2447
- /**
2448
- * The first path segment is the device name (`dvc://name`, `dvc://name/sub`).
2449
- * A leading `dvc://` is tolerated so callers may pass either the parsed path
2450
- * (the write tool's contract) or the full URL.
2451
- */
2452
- function deviceNameFromPath(path$1) {
2453
- const trimmed = (path$1.startsWith("dvc://") ? path$1.slice(6) : path$1).replace(/^\/+/, "");
2454
- const slash = trimmed.indexOf("/");
2455
- return slash === -1 ? trimmed : trimmed.slice(0, slash);
2456
- }
2457
- /** Uniform `unknown`-error rendering for structured error messages. */
2458
- function messageOf$2(error) {
2459
- return error instanceof Error ? error.message : String(error);
2460
- }
2461
- /**
2462
- * Build the `dvc://` scheme handler over the module-level device registry.
2463
- * Bare `dvc://` yields the roster (`no devices mounted` while empty); a
2464
- * registered `<device>` yields its summary plus a usage hint; an unregistered
2465
- * name keeps the `unknown device` placeholder text.
2466
- */
2467
- function createDvcHandler(_deps = {}) {
2468
- return { async resolve(_env, path$1) {
2469
- const name$2 = deviceNameFromPath(path$1);
2470
- if (name$2 === "") {
2471
- if (devices.size === 0) return "no devices mounted";
2472
- return [...devices].map(([n, device$1]) => `${n}\t${device$1.summary}`).join("\n");
2473
- }
2474
- const device = devices.get(name$2);
2475
- if (device === void 0) return `unknown device: ${name$2}`;
2476
- return `${device.summary}\nusage: write dvc://${name$2} with a JSON args object to execute this device`;
2477
- } };
2478
- }
2479
- /**
2480
- * Write dispatch for `dvc://` URLs — called by the write tool's URL branch.
2481
- * `path` addresses the device (parsed path or full `dvc://` URL); `content`
2482
- * must be the JSON args payload.
2483
- *
2484
- * Routing and args failures (`DVC_NO_DEVICE`, `DVC_UNKNOWN_DEVICE`,
2485
- * `DVC_BAD_ARGS`) throw synchronously — the placeholder wave's observable
2486
- * contract — while a device-reported failure rejects the returned promise as
2487
- * `DVC_DEVICE_ERROR` carrying the device name.
2488
- */
2489
- function dispatchDvcWrite(path$1, content) {
2490
- if (devices.size === 0) throw new UrlSchemaError("DVC_NO_DEVICE", "dvc:// write dispatch: no devices mounted to route the write to");
2491
- const name$2 = deviceNameFromPath(path$1);
2492
- const device = devices.get(name$2);
2493
- if (device === void 0) throw new UrlSchemaError("DVC_UNKNOWN_DEVICE", `dvc:// write dispatch: no device named "${name$2}" (registered: ${[...devices.keys()].sort().join(", ")})`);
2494
- let args;
2495
- try {
2496
- args = JSON.parse(content);
2497
- } catch (error) {
2498
- throw new UrlSchemaError("DVC_BAD_ARGS", `dvc:// write dispatch: device "${name$2}" requires a JSON args payload (${messageOf$2(error)})`);
2499
- }
2500
- return Promise.resolve().then(() => device.execute(args)).catch((error) => {
2501
- throw new UrlSchemaError("DVC_DEVICE_ERROR", `dvc:// device "${name$2}" execute failed: ${messageOf$2(error)}`);
2502
- });
2503
- }
2504
-
2505
- //#endregion
2506
- //#region src/url-schema/vendored/devices/ast/natives-loader.ts
2269
+ //#region src/url-schemes/vendored/devices/ast/natives-loader.ts
2507
2270
  /** Platform tags with a published leaf package (mirrors the upstream loader). */
2508
2271
  const SUPPORTED_PLATFORM_TAGS = [
2509
2272
  "linux-x64",
@@ -2616,7 +2379,7 @@ function loadPiNatives(options = {}) {
2616
2379
  }
2617
2380
 
2618
2381
  //#endregion
2619
- //#region src/url-schema/vendored/devices/ast/ast-device.ts
2382
+ //#region src/url-schemes/vendored/devices/ast/ast-device.ts
2620
2383
  /** Per-run cap on distinct rewritten files (upstream `PI_MAX_AST_FILES` default). */
2621
2384
  const MAX_FILES = 1e3;
2622
2385
  /** Glob metacharacters that mark a path segment as a pattern (upstream `GLOB_PATH_CHARS`). */
@@ -2809,14 +2572,14 @@ const summaries = {
2809
2572
  };
2810
2573
 
2811
2574
  //#endregion
2812
- //#region src/url-schema/vendored/devices/browser/browser-device.ts
2575
+ //#region src/url-schemes/vendored/devices/browser/browser-device.ts
2813
2576
  let puppeteerModule;
2814
2577
  /** Load puppeteer-core once; missing package → structured BROWSER_NO_PUPPETEER. */
2815
2578
  async function loadPuppeteer() {
2816
2579
  if (puppeteerModule === void 0) try {
2817
2580
  puppeteerModule = await import("puppeteer-core");
2818
2581
  } catch (err) {
2819
- throw new UrlSchemaError("BROWSER_NO_PUPPETEER", `puppeteer-core is not installed — the browser device needs it as a dependency (npm install puppeteer-core@^25.3.0): ${messageOf$1(err)}`);
2582
+ throw new UrlSchemesError("BROWSER_NO_PUPPETEER", `puppeteer-core is not installed — the browser device needs it as a dependency (npm install puppeteer-core@^25.3.0): ${messageOf$1(err)}`);
2820
2583
  }
2821
2584
  return puppeteerModule;
2822
2585
  }
@@ -2844,18 +2607,18 @@ function withCause(error, cause) {
2844
2607
  }
2845
2608
  /** Assert the args payload is a plain JSON object and return it as a record. */
2846
2609
  function requireObjectArgs$1(args) {
2847
- if (args === null || typeof args !== "object" || Array.isArray(args)) throw new UrlSchemaError("BROWSER_BAD_ARGS", `browser device: args must be a JSON object with an "action" ("open" | "run" | "close"), got ${args === null ? "null" : Array.isArray(args) ? "array" : typeof args}`);
2610
+ if (args === null || typeof args !== "object" || Array.isArray(args)) throw new UrlSchemesError("BROWSER_BAD_ARGS", `browser device: args must be a JSON object with an "action" ("open" | "run" | "close"), got ${args === null ? "null" : Array.isArray(args) ? "array" : typeof args}`);
2848
2611
  return args;
2849
2612
  }
2850
2613
  /** Reject fields outside the action's allowlist (typo guard, DVC_BAD_ARGS-style). */
2851
2614
  function rejectUnknownFields$1(action, args, allowed) {
2852
2615
  const unknown = Object.keys(args).filter((key) => !allowed.includes(key));
2853
- if (unknown.length > 0) throw new UrlSchemaError("BROWSER_BAD_ARGS", `browser device: unknown field(s) ${unknown.map((key) => JSON.stringify(key)).join(", ")} for action "${action}" — allowed: ${allowed.map((key) => JSON.stringify(key)).join(", ")}`);
2616
+ if (unknown.length > 0) throw new UrlSchemesError("BROWSER_BAD_ARGS", `browser device: unknown field(s) ${unknown.map((key) => JSON.stringify(key)).join(", ")} for action "${action}" — allowed: ${allowed.map((key) => JSON.stringify(key)).join(", ")}`);
2854
2617
  }
2855
2618
  /** Require a non-empty string field; `field` names it in the error. */
2856
2619
  function requireString(args, field) {
2857
2620
  const value = args[field];
2858
- if (typeof value !== "string" || value === "") throw new UrlSchemaError("BROWSER_BAD_ARGS", `browser device: this action requires a non-empty "${field}" string`);
2621
+ if (typeof value !== "string" || value === "") throw new UrlSchemesError("BROWSER_BAD_ARGS", `browser device: this action requires a non-empty "${field}" string`);
2859
2622
  return value;
2860
2623
  }
2861
2624
  /** Resolve the Chrome executable: open args → env `DASHR_BROWSER_BIN` → distro default. */
@@ -2878,7 +2641,7 @@ async function gotoUrl(target, url) {
2878
2641
  try {
2879
2642
  await target.goto(url, { waitUntil: "domcontentloaded" });
2880
2643
  } catch (error) {
2881
- throw withCause(new UrlSchemaError("BROWSER_GOTO_FAILED", `browser device: goto(${url}) failed: ${messageOf$1(error)}`), error);
2644
+ throw withCause(new UrlSchemesError("BROWSER_GOTO_FAILED", `browser device: goto(${url}) failed: ${messageOf$1(error)}`), error);
2882
2645
  }
2883
2646
  }
2884
2647
  /**
@@ -2890,7 +2653,7 @@ async function gotoUrl(target, url) {
2890
2653
  */
2891
2654
  async function ensurePage(executablePath) {
2892
2655
  if (page !== void 0) return page;
2893
- if (!existsSync(executablePath)) throw new UrlSchemaError("BROWSER_NO_CHROME", `browser device: Chrome executable not found at ${executablePath} — install Google Chrome (expected at ${DEFAULT_CHROME_PATH}), set DASHR_BROWSER_BIN, or pass "executablePath" in the open args`);
2656
+ if (!existsSync(executablePath)) throw new UrlSchemesError("BROWSER_NO_CHROME", `browser device: Chrome executable not found at ${executablePath} — install Google Chrome (expected at ${DEFAULT_CHROME_PATH}), set DASHR_BROWSER_BIN, or pass "executablePath" in the open args`);
2894
2657
  const puppeteer = await loadPuppeteer();
2895
2658
  let launched;
2896
2659
  try {
@@ -2901,13 +2664,13 @@ async function ensurePage(executablePath) {
2901
2664
  protocolTimeout: PROTOCOL_TIMEOUT_MS
2902
2665
  });
2903
2666
  } catch (error) {
2904
- throw withCause(new UrlSchemaError("BROWSER_LAUNCH_FAILED", `browser device: Chrome launch failed (${executablePath}): ${messageOf$1(error)}`), error);
2667
+ throw withCause(new UrlSchemesError("BROWSER_LAUNCH_FAILED", `browser device: Chrome launch failed (${executablePath}): ${messageOf$1(error)}`), error);
2905
2668
  }
2906
2669
  try {
2907
2670
  page = await launched.newPage();
2908
2671
  } catch (error) {
2909
2672
  await closeQuietly(launched, () => launched.close());
2910
- throw withCause(new UrlSchemaError("BROWSER_LAUNCH_FAILED", `browser device: opening a page in the launched Chrome failed: ${messageOf$1(error)}`), error);
2673
+ throw withCause(new UrlSchemesError("BROWSER_LAUNCH_FAILED", `browser device: opening a page in the launched Chrome failed: ${messageOf$1(error)}`), error);
2911
2674
  }
2912
2675
  browser = launched;
2913
2676
  return page;
@@ -2945,11 +2708,11 @@ function jsonSafe(value) {
2945
2708
  }
2946
2709
  /** Validate run's `type` sub-action shape: `{selector: string, text: string}`. */
2947
2710
  function requireTypeSpec(action, value) {
2948
- if (value === null || typeof value !== "object" || Array.isArray(value)) throw new UrlSchemaError("BROWSER_BAD_ARGS", `browser device: "${action}" field "type" must be {"selector": string, "text": string}`);
2711
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new UrlSchemesError("BROWSER_BAD_ARGS", `browser device: "${action}" field "type" must be {"selector": string, "text": string}`);
2949
2712
  const record = value;
2950
2713
  const selector = record["selector"];
2951
2714
  const text = record["text"];
2952
- if (typeof selector !== "string" || selector === "" || typeof text !== "string") throw new UrlSchemaError("BROWSER_BAD_ARGS", `browser device: "${action}" field "type" must be {"selector": string, "text": string}`);
2715
+ if (typeof selector !== "string" || selector === "" || typeof text !== "string") throw new UrlSchemesError("BROWSER_BAD_ARGS", `browser device: "${action}" field "type" must be {"selector": string, "text": string}`);
2953
2716
  return {
2954
2717
  selector,
2955
2718
  text
@@ -2957,7 +2720,7 @@ function requireTypeSpec(action, value) {
2957
2720
  }
2958
2721
  /** The live page for a run action — every run shape needs an open tab. */
2959
2722
  function requireLivePage() {
2960
- if (page === void 0) throw new UrlSchemaError("BROWSER_NO_PAGE", "browser device: no page open — write {\"action\":\"open\",\"url\":\"...\"} to dvc://browser first");
2723
+ if (page === void 0) throw new UrlSchemesError("BROWSER_NO_PAGE", "browser device: no page open — write {\"action\":\"open\",\"url\":\"...\"} to dvc://browser first");
2961
2724
  return page;
2962
2725
  }
2963
2726
  /** `{"action":"run"}` — evaluate `code`, or run the goto/type/click convenience steps. */
@@ -2971,8 +2734,8 @@ async function runAction(args) {
2971
2734
  ]);
2972
2735
  const hasCode = "code" in args;
2973
2736
  const hasConvenience = "click" in args || "type" in args || "goto" in args;
2974
- if (hasCode && hasConvenience) throw new UrlSchemaError("BROWSER_BAD_ARGS", "browser device: \"run\" takes either \"code\" or the convenience fields (\"goto\"/\"type\"/\"click\"), not both");
2975
- if (!hasCode && !hasConvenience) throw new UrlSchemaError("BROWSER_BAD_ARGS", "browser device: \"run\" requires \"code\" or at least one of \"goto\"/\"type\"/\"click\"");
2737
+ if (hasCode && hasConvenience) throw new UrlSchemesError("BROWSER_BAD_ARGS", "browser device: \"run\" takes either \"code\" or the convenience fields (\"goto\"/\"type\"/\"click\"), not both");
2738
+ if (!hasCode && !hasConvenience) throw new UrlSchemesError("BROWSER_BAD_ARGS", "browser device: \"run\" requires \"code\" or at least one of \"goto\"/\"type\"/\"click\"");
2976
2739
  const goto = "goto" in args ? requireString(args, "goto") : void 0;
2977
2740
  const typeSpec = "type" in args ? requireTypeSpec("run", args["type"]) : void 0;
2978
2741
  const click = "click" in args ? requireString(args, "click") : void 0;
@@ -2986,7 +2749,7 @@ async function runAction(args) {
2986
2749
  result: jsonSafe(await target.evaluate(expression))
2987
2750
  };
2988
2751
  } catch (error) {
2989
- throw withCause(new UrlSchemaError("BROWSER_EVAL_FAILED", `browser device: evaluate failed: ${messageOf$1(error)}`), error);
2752
+ throw withCause(new UrlSchemesError("BROWSER_EVAL_FAILED", `browser device: evaluate failed: ${messageOf$1(error)}`), error);
2990
2753
  }
2991
2754
  }
2992
2755
  try {
@@ -2994,8 +2757,8 @@ async function runAction(args) {
2994
2757
  if (typeSpec !== void 0) await target.type(typeSpec.selector, typeSpec.text);
2995
2758
  if (click !== void 0) await target.click(click);
2996
2759
  } catch (error) {
2997
- if (error instanceof UrlSchemaError) throw error;
2998
- throw withCause(new UrlSchemaError("BROWSER_STEP_FAILED", `browser device: run step failed: ${messageOf$1(error)}`), error);
2760
+ if (error instanceof UrlSchemesError) throw error;
2761
+ throw withCause(new UrlSchemesError("BROWSER_STEP_FAILED", `browser device: run step failed: ${messageOf$1(error)}`), error);
2999
2762
  }
3000
2763
  return {
3001
2764
  ok: true,
@@ -3006,7 +2769,7 @@ async function runAction(args) {
3006
2769
  /** `{"action":"close"}` — teardown; state clears before any await so a hung close never wedges it. */
3007
2770
  async function closeAction(args) {
3008
2771
  rejectUnknownFields$1("close", args, ["action"]);
3009
- if (page === void 0 && browser === void 0) throw new UrlSchemaError("BROWSER_NOT_OPEN", "browser device: nothing to close — no browser is open (open one with {\"action\":\"open\",\"url\":\"...\"})");
2772
+ if (page === void 0 && browser === void 0) throw new UrlSchemesError("BROWSER_NOT_OPEN", "browser device: nothing to close — no browser is open (open one with {\"action\":\"open\",\"url\":\"...\"})");
3010
2773
  const openPage = page;
3011
2774
  const openBrowser = browser;
3012
2775
  page = void 0;
@@ -3023,7 +2786,7 @@ const browserDevice = {
3023
2786
  case "open": return openAction(record);
3024
2787
  case "run": return runAction(record);
3025
2788
  case "close": return closeAction(record);
3026
- default: throw new UrlSchemaError("BROWSER_BAD_ARGS", `browser device: unknown action ${JSON.stringify(record["action"] ?? null)} — expected "open", "run", or "close"`);
2789
+ default: throw new UrlSchemesError("BROWSER_BAD_ARGS", `browser device: unknown action ${JSON.stringify(record["action"] ?? null)} — expected "open", "run", or "close"`);
3027
2790
  }
3028
2791
  },
3029
2792
  summary: "headless Chrome via puppeteer-core — open {url} / run {code|goto,type,click} / close; one tab per process (single-tab subset of omp's multi-tab browser)"
@@ -3039,7 +2802,7 @@ function registerBrowserDevice(registry = registerDvcDevice) {
3039
2802
  }
3040
2803
 
3041
2804
  //#endregion
3042
- //#region src/url-schema/vendored/devices/lsp/lsp-jsonrpc.ts
2805
+ //#region src/url-schemes/vendored/devices/lsp/lsp-jsonrpc.ts
3043
2806
  /**
3044
2807
  * Content-Length JSON-RPC framing for the vendored `dvc://lsp` device.
3045
2808
  *
@@ -3167,7 +2930,7 @@ var MessageFramer = class {
3167
2930
  };
3168
2931
 
3169
2932
  //#endregion
3170
- //#region src/url-schema/vendored/devices/lsp/lsp-language.ts
2933
+ //#region src/url-schemes/vendored/devices/lsp/lsp-language.ts
3171
2934
  /** Extension (without dot) → LSP language id (defaults.json coverage). */
3172
2935
  const EXTENSION_LANGUAGE_ID = {
3173
2936
  rs: "rust",
@@ -3270,7 +3033,7 @@ function detectLanguageId(filePath) {
3270
3033
  }
3271
3034
 
3272
3035
  //#endregion
3273
- //#region src/url-schema/vendored/devices/lsp/lsp-client.ts
3036
+ //#region src/url-schemes/vendored/devices/lsp/lsp-client.ts
3274
3037
  /** Default per-request timeout when no explicit budget is given (upstream 30s). */
3275
3038
  const DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
3276
3039
  /** Max time to wait for the `initialize` response (upstream WARMUP/init budget). */
@@ -3801,7 +3564,7 @@ if (typeof process !== "undefined") process.on("beforeExit", () => {
3801
3564
  });
3802
3565
 
3803
3566
  //#endregion
3804
- //#region src/url-schema/vendored/devices/lsp/defaults.json
3567
+ //#region src/url-schemes/vendored/devices/lsp/defaults.json
3805
3568
  var defaults_default = {
3806
3569
  "rust-analyzer": {
3807
3570
  "command": "rust-analyzer",
@@ -4552,7 +4315,7 @@ var defaults_default = {
4552
4315
  };
4553
4316
 
4554
4317
  //#endregion
4555
- //#region src/url-schema/vendored/devices/lsp/lsp-server-registry.ts
4318
+ //#region src/url-schemes/vendored/devices/lsp/lsp-server-registry.ts
4556
4319
  /** The verbatim upstream registry (54 servers), keyed by server name. */
4557
4320
  const REGISTRY = defaults_default;
4558
4321
  /** Server names in registry (insertion) order — deterministic selection. */
@@ -4674,7 +4437,7 @@ function installHintFor(command) {
4674
4437
  }
4675
4438
 
4676
4439
  //#endregion
4677
- //#region src/url-schema/vendored/devices/lsp/lsp-device.ts
4440
+ //#region src/url-schemes/vendored/devices/lsp/lsp-device.ts
4678
4441
  const ACTIONS = new Set([
4679
4442
  "diagnostics",
4680
4443
  "definition",
@@ -4694,16 +4457,16 @@ function messageOf(error) {
4694
4457
  }
4695
4458
  /** Assert the args payload is a plain JSON object and return it as a record. */
4696
4459
  function requireObjectArgs(args) {
4697
- if (args === null || typeof args !== "object" || Array.isArray(args)) throw new UrlSchemaError("LSP_BAD_ARGS", `lsp device: args must be a JSON object with an "action" ("diagnostics" | "definition" | "references" | "hover"), got ${args === null ? "null" : Array.isArray(args) ? "array" : typeof args}`);
4460
+ if (args === null || typeof args !== "object" || Array.isArray(args)) throw new UrlSchemesError("LSP_BAD_ARGS", `lsp device: args must be a JSON object with an "action" ("diagnostics" | "definition" | "references" | "hover"), got ${args === null ? "null" : Array.isArray(args) ? "array" : typeof args}`);
4698
4461
  return args;
4699
4462
  }
4700
4463
  /** Resolve the request's server config: named `server` → `command` override → file-type lookup. */
4701
4464
  function resolveServerConfig(args, filePath) {
4702
4465
  const server = args.server;
4703
4466
  if (server !== void 0) {
4704
- if (typeof server !== "string" || server === "") throw new UrlSchemaError("LSP_BAD_ARGS", "lsp device: \"server\" must be a non-empty server name");
4467
+ if (typeof server !== "string" || server === "") throw new UrlSchemesError("LSP_BAD_ARGS", "lsp device: \"server\" must be a non-empty server name");
4705
4468
  const config = serverByName(server);
4706
- if (config === void 0) throw new UrlSchemaError("LSP_NO_SERVER", `lsp device: no defaults.json entry named "${server}"`);
4469
+ if (config === void 0) throw new UrlSchemesError("LSP_NO_SERVER", `lsp device: no defaults.json entry named "${server}"`);
4707
4470
  return {
4708
4471
  name: server,
4709
4472
  config
@@ -4711,9 +4474,9 @@ function resolveServerConfig(args, filePath) {
4711
4474
  }
4712
4475
  const command = args.command;
4713
4476
  if (command !== void 0) {
4714
- if (typeof command !== "string" || command === "") throw new UrlSchemaError("LSP_BAD_ARGS", "lsp device: \"command\" override must be a non-empty string");
4477
+ if (typeof command !== "string" || command === "") throw new UrlSchemesError("LSP_BAD_ARGS", "lsp device: \"command\" override must be a non-empty string");
4715
4478
  const commandArgs = args.args;
4716
- if (commandArgs !== void 0 && (!Array.isArray(commandArgs) || commandArgs.some((a) => typeof a !== "string"))) throw new UrlSchemaError("LSP_BAD_ARGS", "lsp device: \"args\" override must be an array of strings");
4479
+ if (commandArgs !== void 0 && (!Array.isArray(commandArgs) || commandArgs.some((a) => typeof a !== "string"))) throw new UrlSchemesError("LSP_BAD_ARGS", "lsp device: \"args\" override must be an array of strings");
4717
4480
  return {
4718
4481
  name: command,
4719
4482
  config: {
@@ -4725,7 +4488,7 @@ function resolveServerConfig(args, filePath) {
4725
4488
  };
4726
4489
  }
4727
4490
  const primary = primaryServerForFile(filePath);
4728
- if (primary === null) throw new UrlSchemaError("LSP_NO_SERVER", `lsp device: no defaults.json language server covers "${path.basename(filePath)}" — pass "server" or "command"/"args" to pick one explicitly`);
4491
+ if (primary === null) throw new UrlSchemesError("LSP_NO_SERVER", `lsp device: no defaults.json language server covers "${path.basename(filePath)}" — pass "server" or "command"/"args" to pick one explicitly`);
4729
4492
  return {
4730
4493
  name: primary[0],
4731
4494
  config: primary[1]
@@ -4736,7 +4499,7 @@ function requirePosition(args) {
4736
4499
  const read = (field) => {
4737
4500
  const value = args[field];
4738
4501
  if (value === void 0) return 1;
4739
- if (typeof value !== "number" || !Number.isInteger(value) || value < 1) throw new UrlSchemaError("LSP_BAD_ARGS", `lsp device: "${field}" must be a 1-based positive integer (got ${JSON.stringify(value ?? null)})`);
4502
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) throw new UrlSchemesError("LSP_BAD_ARGS", `lsp device: "${field}" must be a 1-based positive integer (got ${JSON.stringify(value ?? null)})`);
4740
4503
  return value;
4741
4504
  };
4742
4505
  return {
@@ -4753,13 +4516,13 @@ function requirePosition(args) {
4753
4516
  async function obtainClient(args, filePath) {
4754
4517
  const { name: name$2, config } = resolveServerConfig(args, filePath);
4755
4518
  const resolvedCommand = resolveCommandPath(config.command, path.dirname(filePath));
4756
- if (resolvedCommand === null) throw new UrlSchemaError("LSP_SERVER_MISSING", `lsp device: language server binary "${config.command}" not found (server "${name$2}") — ${installHintFor(config.command)}`);
4519
+ if (resolvedCommand === null) throw new UrlSchemesError("LSP_SERVER_MISSING", `lsp device: language server binary "${config.command}" not found (server "${name$2}") — ${installHintFor(config.command)}`);
4757
4520
  const root = findWorkspaceRoot(filePath, config.rootMarkers) ?? path.dirname(path.resolve(filePath));
4758
4521
  let client;
4759
4522
  try {
4760
4523
  client = await getOrCreateClient(config, root, resolvedCommand);
4761
4524
  } catch (error) {
4762
- throw new UrlSchemaError("LSP_INIT_FAILED", `lsp device: language server "${name$2}" (${config.command}) failed to initialize: ${messageOf(error)}`);
4525
+ throw new UrlSchemesError("LSP_INIT_FAILED", `lsp device: language server "${name$2}" (${config.command}) failed to initialize: ${messageOf(error)}`);
4763
4526
  }
4764
4527
  return {
4765
4528
  name: name$2,
@@ -4871,7 +4634,7 @@ async function lspPositionRequest(client, method, params, retryWhileEmpty) {
4871
4634
  result = await sendRequest(client, method, params);
4872
4635
  } catch (error) {
4873
4636
  const message = messageOf(error);
4874
- if (!(retryWhileEmpty && /-3280[01]/.test(message) && Date.now() - client.readyAt <= COLD_START_RETRY_WINDOW_MS)) throw new UrlSchemaError("LSP_REQUEST_FAILED", `lsp device: ${method} failed on ${client.config.command}: ${message}`);
4637
+ if (!(retryWhileEmpty && /-3280[01]/.test(message) && Date.now() - client.readyAt <= COLD_START_RETRY_WINDOW_MS)) throw new UrlSchemesError("LSP_REQUEST_FAILED", `lsp device: ${method} failed on ${client.config.command}: ${message}`);
4875
4638
  await new Promise((resolve$1) => setTimeout(resolve$1, COLD_START_RETRY_DELAY_MS));
4876
4639
  continue;
4877
4640
  }
@@ -4897,16 +4660,16 @@ const lspDevice = {
4897
4660
  async execute(args) {
4898
4661
  const record = requireObjectArgs(args);
4899
4662
  const action = record.action;
4900
- if (typeof action !== "string" || !ACTIONS.has(action)) throw new UrlSchemaError("LSP_BAD_ARGS", `lsp device: unknown action ${JSON.stringify(action ?? null)} — expected "diagnostics", "definition", "references", "hover", or "format"`);
4663
+ if (typeof action !== "string" || !ACTIONS.has(action)) throw new UrlSchemesError("LSP_BAD_ARGS", `lsp device: unknown action ${JSON.stringify(action ?? null)} — expected "diagnostics", "definition", "references", "hover", or "format"`);
4901
4664
  const file = record.file;
4902
- if (typeof file !== "string" || file === "") throw new UrlSchemaError("LSP_BAD_ARGS", "lsp device: this action requires a non-empty \"file\" string");
4665
+ if (typeof file !== "string" || file === "") throw new UrlSchemesError("LSP_BAD_ARGS", "lsp device: this action requires a non-empty \"file\" string");
4903
4666
  const content = record.content;
4904
- if (content !== void 0 && typeof content !== "string") throw new UrlSchemaError("LSP_BAD_ARGS", "lsp device: \"content\" override must be a string");
4667
+ if (content !== void 0 && typeof content !== "string") throw new UrlSchemesError("LSP_BAD_ARGS", "lsp device: \"content\" override must be a string");
4905
4668
  const saved = record.saved;
4906
- if (saved !== void 0 && typeof saved !== "boolean") throw new UrlSchemaError("LSP_BAD_ARGS", "lsp device: \"saved\" flag must be a boolean");
4907
- if (saved === true && content === void 0) throw new UrlSchemaError("LSP_BAD_ARGS", "lsp device: \"saved\" requires a \"content\" override (the save signal rides a content sync)");
4669
+ if (saved !== void 0 && typeof saved !== "boolean") throw new UrlSchemesError("LSP_BAD_ARGS", "lsp device: \"saved\" flag must be a boolean");
4670
+ if (saved === true && content === void 0) throw new UrlSchemesError("LSP_BAD_ARGS", "lsp device: \"saved\" requires a \"content\" override (the save signal rides a content sync)");
4908
4671
  const filePath = path.resolve(file);
4909
- if (content === void 0 && !existsSync(filePath)) throw new UrlSchemaError("LSP_BAD_ARGS", `lsp device: file not found: ${filePath}`);
4672
+ if (content === void 0 && !existsSync(filePath)) throw new UrlSchemesError("LSP_BAD_ARGS", `lsp device: file not found: ${filePath}`);
4910
4673
  const position = requirePosition(record);
4911
4674
  const { name: name$2, client } = await obtainClient(record, filePath);
4912
4675
  const base = {
@@ -4988,7 +4751,7 @@ const lspDevice = {
4988
4751
  };
4989
4752
  }
4990
4753
  case "format": {
4991
- if (content === void 0) throw new UrlSchemaError("LSP_BAD_ARGS", "lsp device: \"format\" requires a \"content\" string to format");
4754
+ if (content === void 0) throw new UrlSchemesError("LSP_BAD_ARGS", "lsp device: \"format\" requires a \"content\" string to format");
4992
4755
  const formatting = client.serverCapabilities?.documentFormattingProvider ?? client.serverCapabilities?.formattingProvider;
4993
4756
  if (formatting === void 0 || formatting === false || formatting === null) return {
4994
4757
  ...base,
@@ -5014,7 +4777,7 @@ const lspDevice = {
5014
4777
  changed: true
5015
4778
  };
5016
4779
  }
5017
- default: throw new UrlSchemaError("LSP_BAD_ARGS", `lsp device: unhandled action "${String(action)}"`);
4780
+ default: throw new UrlSchemesError("LSP_BAD_ARGS", `lsp device: unhandled action "${String(action)}"`);
5018
4781
  }
5019
4782
  },
5020
4783
  summary: "LSP queries over stdio language servers (defaults.json registry) — diagnostics / definition / references / hover on {file,line,character}"
@@ -5028,22 +4791,7 @@ function installLspDevices(registry = registerDvcDevice) {
5028
4791
  }
5029
4792
 
5030
4793
  //#endregion
5031
- //#region src/url-schema/docs-dir.ts
5032
- function resolveDocsDir() {
5033
- let dir = dirname(fileURLToPath(import.meta.url));
5034
- for (;;) {
5035
- const candidate = join(dir, "docs");
5036
- try {
5037
- if (statSync(candidate).isDirectory()) return candidate;
5038
- } catch {}
5039
- const parent = dirname(dir);
5040
- if (parent === dir) return void 0;
5041
- dir = parent;
5042
- }
5043
- }
5044
-
5045
- //#endregion
5046
- //#region src/url-schema/handlers/agent.ts
4794
+ //#region src/url-schemes/handlers/agent.ts
5047
4795
  /**
5048
4796
  * Build the `agent://` scheme handler. Handlers return the FULL text of the
5049
4797
  * resource; the resolver applies any `:raw`/`:N-M`/`/path`/`?q=` selector.
@@ -5067,13 +4815,13 @@ function createAgentHandler(deps) {
5067
4815
  const grandChildren = children.filter((entry) => String(entry.parentId) === parent.rawId);
5068
4816
  return await outputFor(requireAddress(await resolveAddress(rest, parent.rawId, grandChildren, family), rest, `unknown child agent "${rest}" of "${parent.rawId}"`), family);
5069
4817
  }
5070
- throw new UrlSchemaError("AGENT_BAD_PATH", `agent:// path "${path$1}" has too many segments — expected <id>, <id>/transcript, or <id>/<child>`);
4818
+ throw new UrlSchemesError("AGENT_BAD_PATH", `agent:// path "${path$1}" has too many segments — expected <id>, <id>/transcript, or <id>/<child>`);
5071
4819
  } };
5072
4820
  }
5073
4821
  /** The calling agent's raw session id, or a structured error without one. */
5074
4822
  function requireCaller(env) {
5075
4823
  const { agent } = env;
5076
- if (agent === void 0) throw new UrlSchemaError("AGENT_NO_AGENT", "agent:// requires the calling agent in the resolver env (this context has none)");
4824
+ if (agent === void 0) throw new UrlSchemesError("AGENT_NO_AGENT", "agent:// requires the calling agent in the resolver env (this context has none)");
5077
4825
  return String(agent.id);
5078
4826
  }
5079
4827
  /**
@@ -5101,7 +4849,7 @@ async function resolveAddress(segment, selfRaw, candidates, family) {
5101
4849
  }
5102
4850
  /** Turn a failed address resolution into its structured error. */
5103
4851
  function requireAddress(target, segment, message = `unknown agent "${segment}" — addressing is scoped to your family tree (self and descendants)`) {
5104
- if (target === void 0) throw new UrlSchemaError("AGENT_UNKNOWN_ID", message);
4852
+ if (target === void 0) throw new UrlSchemesError("AGENT_UNKNOWN_ID", message);
5105
4853
  return target;
5106
4854
  }
5107
4855
  /** The newest entry by creation time (live header, else persisted metadata). */
@@ -5157,7 +4905,7 @@ function requireLiveSession(target, family) {
5157
4905
  }
5158
4906
  /** A structured AGENT_UNKNOWN_ID with the label-ambiguity hint appended. */
5159
4907
  function unknownId(target, message) {
5160
- return new UrlSchemaError("AGENT_UNKNOWN_ID", `${message}${target.ambiguous === void 0 ? "" : `; label "${target.ambiguous.label}" is ambiguous (${target.ambiguous.matches} children share it) — address the raw session id to disambiguate`}`);
4908
+ return new UrlSchemesError("AGENT_UNKNOWN_ID", `${message}${target.ambiguous === void 0 ? "" : `; label "${target.ambiguous.label}" is ambiguous (${target.ambiguous.matches} children share it) — address the raw session id to disambiguate`}`);
5161
4909
  }
5162
4910
  /**
5163
4911
  * Roster table: one row per continuable descendant, in enumeration (stable
@@ -5248,277 +4996,385 @@ function renderBlock(block) {
5248
4996
  }
5249
4997
 
5250
4998
  //#endregion
5251
- //#region src/url-schema/handlers/ctx.ts
5252
- /** The snapshot keys, in listing order. */
5253
- const KEYS = [
5254
- "session",
5255
- "model",
5256
- "cwd"
5257
- ];
5258
- /** Build the `ctx://` scheme handler over the resolver env. */
5259
- function createCtxHandler() {
5260
- return { async resolve(env, path$1) {
5261
- const key = path$1.replace(/^\/+/, "").trim();
5262
- if (key === "") return KEYS.join("\n");
5263
- const { agent } = env;
5264
- if (agent === void 0) throw new UrlSchemaError("CTX_NO_AGENT", "ctx:// requires a live agent in the resolver env (this context has none)");
5265
- switch (key) {
5266
- case "session": return JSON.stringify({
5267
- id: agent.id,
5268
- status: agent.status,
5269
- origin: agent.session.header.origin,
5270
- delegationDepth: agent.session.header.delegationDepth
5271
- }, null, 2);
5272
- case "model": return JSON.stringify({
5273
- provider: agent.options.provider,
5274
- model: agent.options.model,
5275
- maxTokens: agent.options.maxTokens
5276
- }, null, 2);
5277
- case "cwd": return agent.session.header.cwd ?? "";
5278
- default: throw new UrlSchemaError("CTX_UNKNOWN_KEY", `ctx://${key}: unknown snapshot key (known: ${KEYS.join(", ")})`);
5279
- }
5280
- } };
4999
+ //#region src/url-schemes/handlers/ctx.ts
5000
+ function blocksOf(event) {
5001
+ const message = event.data?.message;
5002
+ const content = event.data?.content;
5003
+ return message?.content ?? content ?? [];
5281
5004
  }
5282
-
5283
- //#endregion
5284
- //#region src/url-schema/handlers/dsh.ts
5285
- /** File extensions considered readable harness documentation. */
5286
- const DOC_EXTENSIONS = {
5287
- ".md": true,
5288
- ".markdown": true,
5289
- ".txt": true
5290
- };
5291
- /**
5292
- * Key-name patterns for the defensive secret denylist, matched against a
5293
- * normalized key (lowercased, separators removed). Complements schema-declared
5294
- * `role('secret')` redaction for fields a schema did not mark secret.
5295
- */
5296
- const SECRET_KEY_PATTERNS = [
5297
- /secret/,
5298
- /password/,
5299
- /passwd/,
5300
- /credential/,
5301
- /token/,
5302
- /apikey/,
5303
- /authorization/,
5304
- /privatekey/,
5305
- /accesskey/
5306
- ];
5307
- /** True when a settings field name names credentials/env/API-key material. */
5308
- function isSecretKey(key) {
5309
- const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
5310
- if (normalized === "env" || normalized === "environment") return true;
5311
- return SECRET_KEY_PATTERNS.some((re) => re.test(normalized));
5312
- }
5313
- /** Deep-copy a settings value, dropping every key the denylist names. */
5314
- function stripSecrets(value) {
5315
- if (Array.isArray(value)) return value.map(stripSecrets);
5316
- if (value !== null && typeof value === "object") {
5317
- const out = {};
5318
- for (const [key, item] of Object.entries(value)) {
5319
- if (isSecretKey(key)) continue;
5320
- out[key] = stripSecrets(item);
5321
- }
5322
- return out;
5323
- }
5324
- return value;
5005
+ function textOf(blocks) {
5006
+ return blocks.filter((b) => b.type === "text").map((b) => b.text ?? "").join("");
5325
5007
  }
5326
- /** True when `name` has a doc extension (case-insensitive). */
5327
- function isDocFile(name$2) {
5328
- const dot = name$2.lastIndexOf(".");
5329
- if (dot === -1) return false;
5330
- return DOC_EXTENSIONS[name$2.slice(dot).toLowerCase()] === true;
5008
+ function sourceKind(event) {
5009
+ return event.data?.source?.kind ?? "";
5331
5010
  }
5332
- /** Recursive, sorted list of readable doc paths relative to `dir`. */
5333
- async function listDocs(dir) {
5011
+ function sourcePlugin(event) {
5012
+ return event.data?.source?.plugin ?? "";
5013
+ }
5014
+ function resultTextOf(event) {
5015
+ return blocksOf(event).flatMap((b) => b.content ?? []).map((c) => c.text ?? "").join("\n");
5016
+ }
5017
+ /** All reasoning blocks in event order (one message may carry several). */
5018
+ function thinkingItems(events) {
5334
5019
  const out = [];
5335
- const entries = await promises.readdir(dir, { withFileTypes: true });
5336
- entries.sort((a, b) => a.name.localeCompare(b.name));
5337
- for (const entry of entries) {
5338
- if (entry.name.startsWith(".")) continue;
5339
- const abs = join(dir, entry.name);
5340
- if (entry.isDirectory()) for (const child of await listDocs(abs)) out.push(join(entry.name, child));
5341
- else if (isDocFile(entry.name)) out.push(entry.name);
5020
+ for (const e of events) {
5021
+ if (e.type !== "assistant/message") continue;
5022
+ const turnStep = e.data;
5023
+ for (const b of blocksOf(e)) {
5024
+ if (b.type !== "reasoning") continue;
5025
+ out.push({
5026
+ seq: e.seq,
5027
+ turn: turnStep?.turn,
5028
+ step: turnStep?.step,
5029
+ text: b.text ?? ""
5030
+ });
5031
+ }
5342
5032
  }
5343
5033
  return out;
5344
5034
  }
5345
- /** Read one `<doc>` path inside `dir`, guarding against path traversal. */
5346
- async function readDoc(dir, docPath) {
5347
- const abs = resolve(dir, docPath);
5348
- const rel = relative(dir, abs);
5349
- if (rel === "" || rel === ".." || rel.startsWith(".." + sep) || isAbsolute(rel)) throw new UrlSchemaError("URL_DOC_NOT_FOUND", `dsh://docs/${docPath}: path escapes the docs directory`);
5350
- let stat$1;
5351
- try {
5352
- stat$1 = await promises.stat(abs);
5353
- } catch {
5354
- throw new UrlSchemaError("URL_DOC_NOT_FOUND", `dsh://docs/${docPath}: document not found`);
5355
- }
5356
- if (stat$1.isDirectory()) return JSON.stringify(await listDocs(abs), null, 2);
5357
- if (stat$1.isFile()) return await promises.readFile(abs, "utf8");
5358
- throw new UrlSchemaError("URL_DOC_NOT_FOUND", `dsh://docs/${docPath}: not a regular file`);
5359
- }
5360
- /** Create the `dsh://` scheme handler, capturing `deps` by closure. */
5361
- function createDshHandler(deps) {
5362
- const { settings, docsDir } = deps;
5363
- let docsDirPromise;
5364
- function findDocsDir() {
5365
- if (docsDirPromise === void 0) docsDirPromise = (async () => {
5366
- const candidates = [];
5367
- if (docsDir !== void 0) candidates.push(docsDir);
5368
- const pkgRoot = dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))));
5369
- candidates.push(join(pkgRoot, "docs"), join(pkgRoot, "..", "docs"));
5370
- for (const dir of candidates) try {
5371
- if ((await promises.stat(dir)).isDirectory()) return dir;
5372
- } catch {}
5373
- })();
5374
- return docsDirPromise;
5375
- }
5376
- return { async resolve(_env, path$1) {
5377
- const [root, ...rest] = path$1.split("/");
5378
- const restPath = rest.join("/");
5379
- if (root === "docs") {
5380
- const dir = await findDocsDir();
5381
- if (dir === void 0) throw new UrlSchemaError("URL_DOCS_UNAVAILABLE", "dsh://docs: no docs directory found (provide `docsDir` to createDshHandler)");
5382
- if (restPath === "") return JSON.stringify(await listDocs(dir), null, 2);
5383
- return await readDoc(dir, restPath);
5384
- }
5385
- if (root === "config") {
5386
- if (settings === void 0) throw new UrlSchemaError("URL_SETTINGS_UNAVAILABLE", "dsh://config: no ctx.settings service is mounted");
5387
- const descriptors = settings.describe({ redactSecrets: true });
5388
- if (restPath === "") {
5389
- const out = {};
5390
- for (const descriptor of descriptors) out[String(descriptor.ns)] = stripSecrets(descriptor.value);
5391
- return JSON.stringify(out, null, 2);
5392
- }
5393
- const found = descriptors.find((descriptor) => String(descriptor.ns) === restPath);
5394
- if (found === void 0) throw new UrlSchemaError("URL_UNKNOWN_SETTINGS_NAMESPACE", `dsh://config/${restPath}: unknown settings namespace`);
5395
- return JSON.stringify(stripSecrets(found.value), null, 2);
5396
- }
5397
- throw new UrlSchemaError("URL_UNKNOWN_RESOURCE", `dsh://: unknown resource "${root || "(empty)"}" — expected "docs" or "config"`);
5398
- } };
5035
+ /** A system message: `data.text` when present, else the message content blocks. */
5036
+ function systemTextOf(event) {
5037
+ const d = event.data;
5038
+ if (typeof d?.text === "string") return d.text;
5039
+ return textOf(d?.message?.content ?? d?.content ?? []);
5399
5040
  }
5400
-
5401
- //#endregion
5402
- //#region src/url-schema/handlers/http.ts
5403
- /** The schemes one handler instance serves; register it under each of these. */
5404
- const HTTP_SCHEMES = ["http", "https"];
5405
- /** Hard cap on a fetched body: 2 MiB. */
5406
- const MAX_BODY_BYTES = 2 * 1024 * 1024;
5407
- /** Abort the GET after this many milliseconds. */
5408
- const TIMEOUT_MS = 2e4;
5409
- /** First line of every resolved http(s) URL: flags plain-fetch semantics. */
5410
- const DISCLAIMER = "[url-fetch] plain-text result of a direct HTTP GET (curl-equivalent). No JS execution or interaction — use browser tools, if any, for that.";
5411
- /** `application/` MIME types treated as text (any `text/*` also is). */
5412
- const APPLICATION_TEXT_TYPES = {
5413
- "application/json": true,
5414
- "application/xml": true,
5415
- "application/yaml": true,
5416
- "application/toml": true,
5417
- "application/xhtml+xml": true,
5418
- "application/javascript": true,
5419
- "application/plain": true
5420
- };
5421
- /** Strict whitelist: `text/*` or a known textual `application/*` MIME. A
5422
- * missing header fails the check — binary decoding is never guessed. */
5423
- function isTextContentType(headerValue) {
5424
- if (headerValue === null) return false;
5425
- const mime = headerValue.split(";", 1)[0].trim().toLowerCase();
5426
- return mime.startsWith("text/") || mime in APPLICATION_TEXT_TYPES;
5427
- }
5428
- /** Best-effort `err.cause` message — undici nests DNS/socket failures there. */
5429
- function causeMessageOf(err) {
5430
- const cause = err?.cause;
5431
- if (cause instanceof Error) return cause.message;
5432
- if (typeof cause === "string" && cause !== "") return cause;
5433
- return null;
5434
- }
5435
- /** Stream the body as UTF-8 text, throwing past the 2 MiB byte cap. */
5436
- async function readBodyCapped(body, href) {
5437
- const reader = body.getReader();
5438
- const decoder = new TextDecoder();
5439
- let received = 0;
5440
- let text = "";
5441
- try {
5442
- for (;;) {
5443
- const { done, value } = await reader.read().catch((err) => {
5444
- if (err instanceof Error && err.name === "AbortError") throw new UrlSchemaError("URL_HTTP_TIMEOUT", `HTTP GET ${href} aborted after the ${TIMEOUT_MS / 1e3} s deadline`);
5445
- throw err;
5446
- });
5447
- if (done) break;
5448
- received += value.byteLength;
5449
- if (received > MAX_BODY_BYTES) throw new UrlSchemaError("URL_HTTP_TOO_LARGE", `body of ${href} exceeded the ${MAX_BODY_BYTES}-byte (2 MiB) text limit after ${received} bytes`);
5450
- text += decoder.decode(value, { stream: true });
5451
- }
5452
- return text + decoder.decode();
5453
- } finally {
5454
- await reader.cancel().catch(() => {});
5041
+ /** All `system/message` events in log order. */
5042
+ function systemItems(events) {
5043
+ return events.filter((e) => e.type === "system/message").map((e) => {
5044
+ const source = [sourceKind(e), sourcePlugin(e)].filter((p) => p !== "").join("/");
5045
+ return {
5046
+ seq: e.seq,
5047
+ source: source === "" ? "-" : source,
5048
+ text: systemTextOf(e)
5049
+ };
5050
+ });
5051
+ }
5052
+ /** All injected `user/message` events in log order (the complement of `user_prompts`). */
5053
+ function injectionItems(events) {
5054
+ return events.filter((e) => e.type === "user/message" && sourceKind(e) !== "user").map((e) => {
5055
+ const kind = sourceKind(e) || "unknown";
5056
+ return {
5057
+ seq: e.seq,
5058
+ source: kind,
5059
+ text: `[${String(e.seq).padStart(7, "0")}] INJECTED ${kind}\n${textOf(blocksOf(e))}`
5060
+ };
5061
+ });
5062
+ }
5063
+ /** ~100-char single-line preview with an ellipsis when truncated. */
5064
+ function previewOf(text, n = 100) {
5065
+ const flat = text.replace(/\s+/g, " ").trim();
5066
+ return flat.length > n ? flat.slice(0, n) + "…" : flat;
5067
+ }
5068
+ function renderEntry(event, toolNameOf) {
5069
+ const seq = String(event.seq ?? 0).padStart(7, "0");
5070
+ if (event.type === "user/message") {
5071
+ if (sourcePlugin(event) === "compact") return `[${seq}] CHECKPOINT\n${textOf(blocksOf(event))}`;
5072
+ if (sourceKind(event) === "user") return `[${seq}] USER\n${textOf(blocksOf(event))}`;
5073
+ return `[${seq}] USER-INJECTED (${sourcePlugin(event) || sourceKind(event)})\n${textOf(blocksOf(event))}`;
5074
+ }
5075
+ if (event.type === "assistant/message") {
5076
+ const blocks = blocksOf(event);
5077
+ const text = textOf(blocks);
5078
+ const calls = blocks.filter((b) => b.type === "tool-call");
5079
+ const parts = [];
5080
+ if (text !== "") parts.push(`[${seq}] ASSISTANT\n${text}`);
5081
+ for (const call of calls) parts.push(`[${seq}] TOOL-CALL ${call.name ?? "?"}\n${call.arguments ?? ""}`);
5082
+ return parts.length > 0 ? parts.join("\n\n") : `[${seq}] ASSISTANT (no content)`;
5083
+ }
5084
+ if (event.type === "tool/result") {
5085
+ const callSeq = event.sourceEventSeqs?.[0];
5086
+ return `[${seq}] TOOL ${toolNameOf(callSeq) ?? "unknown-tool"}\n${resultTextOf(event)}`;
5455
5087
  }
5088
+ return "";
5456
5089
  }
5457
- /** One capped, whitelisted GET of `url`; returns the decoded body text. */
5458
- async function getUrl(url) {
5459
- const controller = new AbortController();
5460
- let timedOut = false;
5461
- const timer = setTimeout(() => {
5462
- timedOut = true;
5463
- controller.abort();
5464
- }, TIMEOUT_MS);
5465
- try {
5466
- let response;
5090
+ function sectionPreviews(text, n = 100) {
5091
+ const out = {};
5092
+ for (const part of text.split(/^## /m).slice(1)) {
5093
+ const head = part.slice(0, part.indexOf("\n")).trim();
5094
+ out[head] = part.slice(part.indexOf("\n") + 1).replace(/\s+/g, " ").trim().slice(0, n);
5095
+ }
5096
+ return out;
5097
+ }
5098
+ function applyLines(canonical, ranges) {
5099
+ const lines = canonical.split("\n");
5100
+ return ranges.map(([a, b]) => lines.slice(Math.max(0, a - 1), b).join("\n")).join("\n");
5101
+ }
5102
+ function applyFace(prepared, canonical, selector, face = "prepared") {
5103
+ if (selector === void 0 || selector === null) return face === "canonical" ? canonical : prepared;
5104
+ if (selector.kind === "raw") return canonical;
5105
+ if (selector.kind === "lines") return applyLines(canonical, selector.ranges);
5106
+ throw new UrlSchemesError("CTX_BAD_SELECTOR", `ctx://: unsupported selector kind "${selector.kind}"`);
5107
+ }
5108
+ function createCtxHandler(deps) {
5109
+ const persistence = deps.sessionPersistence;
5110
+ async function loadEvents(agent) {
5111
+ if (persistence === void 0) throw new UrlSchemesError("CTX_NO_PERSISTENCE", "ctx:// recall requires the host session-persistence service (not mounted in this composition)");
5112
+ const handle = await persistence.open(agent.id, "read");
5467
5113
  try {
5468
- response = await fetch(url, {
5469
- method: "GET",
5470
- redirect: "follow",
5471
- signal: controller.signal
5472
- });
5473
- } catch (err) {
5474
- if (timedOut || err instanceof Error && err.name === "AbortError") throw new UrlSchemaError("URL_HTTP_TIMEOUT", `HTTP GET ${url.href} aborted after the ${TIMEOUT_MS / 1e3} s deadline`);
5475
- const cause = causeMessageOf(err);
5476
- const message = err instanceof Error ? err.message : String(err);
5477
- throw new UrlSchemaError("URL_HTTP_FETCH_FAILED", `HTTP GET ${url.href} failed: ${message}${cause === null ? "" : ` (${cause})`}`);
5478
- }
5479
- if (!response.ok) {
5480
- const statusText = response.statusText === "" ? "" : ` ${response.statusText}`;
5481
- throw new UrlSchemaError("URL_HTTP_STATUS", `HTTP GET ${url.href} returned ${response.status}${statusText}`);
5482
- }
5483
- const declared = response.headers.get("content-length");
5484
- if (declared !== null) {
5485
- const bytes = Number(declared);
5486
- if (Number.isFinite(bytes) && bytes > MAX_BODY_BYTES) throw new UrlSchemaError("URL_HTTP_TOO_LARGE", `body of ${url.href} is ${bytes} bytes, over the ${MAX_BODY_BYTES}-byte (2 MiB) text limit`);
5487
- }
5488
- const contentType = response.headers.get("content-type");
5489
- if (!isTextContentType(contentType)) throw new UrlSchemaError("URL_HTTP_UNSUPPORTED_MEDIA", `content-type of ${url.href} is "${contentType ?? "(none)"}", outside the text whitelist (text/* or application/{json,xml,yaml,toml,xhtml+xml,javascript,plain}) — binary decoding is deliberately not guessed`);
5490
- if (response.body === null) {
5491
- const text = await response.text();
5492
- const bytes = Buffer.byteLength(text, "utf8");
5493
- if (bytes > MAX_BODY_BYTES) throw new UrlSchemaError("URL_HTTP_TOO_LARGE", `body of ${url.href} is ${bytes} bytes, over the ${MAX_BODY_BYTES}-byte (2 MiB) text limit`);
5494
- return text;
5114
+ return (await handle.read()).events ?? [];
5115
+ } finally {
5116
+ await handle.close().catch(() => {});
5495
5117
  }
5496
- return await readBodyCapped(response.body, url.href);
5497
- } finally {
5498
- clearTimeout(timer);
5499
5118
  }
5119
+ return {
5120
+ selectorAware: true,
5121
+ async resolve(env, path$1, selector) {
5122
+ const sel = selector ?? null;
5123
+ const raw = path$1.replace(/^\/+/, "").trim();
5124
+ if (raw === "") return [
5125
+ "ctx://session statistics snapshot of this session (:raw = full transcript)",
5126
+ "ctx://session/transcript full session transcript (line windows via :N-M)",
5127
+ "ctx://session/compactions compaction manifest (labels, nested chain, previews)",
5128
+ "ctx://session/compactions[<label|n>] one episode's summary (:raw[:N-M] = its original span)",
5129
+ "ctx://session/user_prompts[<n|seq>] the n-th real user prompt (0-based)",
5130
+ "ctx://session/tool_calls[<n|seq>] the n-th tool call (name + arguments + result)",
5131
+ "ctx://session/agent_responses[<n|seq>] the n-th assistant response",
5132
+ "ctx://session/thinking[<n|seq>] the n-th reasoning block (:raw = all blocks joined)",
5133
+ "ctx://session/system[<n|seq>] the n-th system message (:raw = all messages)",
5134
+ "ctx://session/injections[<n|seq>] the n-th injected user message (:raw = all joined)"
5135
+ ].join("\n");
5136
+ const { agent } = env;
5137
+ if (agent === void 0) throw new UrlSchemesError("CTX_NO_AGENT", "ctx:// requires a live agent in the resolver env (this context has none)");
5138
+ if (raw.split("/")[0] !== "session") throw new UrlSchemesError("CTX_UNKNOWN_KEY", `ctx://${raw}: unknown key (known: session — model/cwd folded into the session info card; bare ctx:// lists the full roster)`);
5139
+ const events = await loadEvents(agent);
5140
+ const toolCallEvents = events.filter((e) => e.type === "tool/call");
5141
+ const toolNameByCallSeq = new Map(toolCallEvents.map((e) => [e.seq, e.data.name ?? "?"]));
5142
+ const toolNameOf = (seq) => seq === void 0 ? void 0 : toolNameByCallSeq.get(seq);
5143
+ const userPromptEvents = events.filter((e) => e.type === "user/message" && sourceKind(e) === "user");
5144
+ const agentResponseEvents = events.filter((e) => e.type === "assistant/message");
5145
+ const transcript = () => events.map((e) => renderEntry(e, toolNameOf)).filter((t) => t !== "").join("\n\n");
5146
+ const episodes = [];
5147
+ {
5148
+ let prevCheckpoint = null;
5149
+ const nameOf = toolNameOf;
5150
+ for (const e of events) {
5151
+ if (e.type !== "compaction/summary") continue;
5152
+ const d = e.data;
5153
+ const originalText = d.shadowedSeqs.map((seq) => events.find((ev) => ev.seq === seq)).filter((ev) => ev !== void 0).map((ev) => renderEntry(ev, nameOf)).filter((t) => t !== "").join("\n\n");
5154
+ episodes.push({
5155
+ label: e.seq,
5156
+ checkpointSeq: e.seq + 1,
5157
+ compactionId: d.compactionId,
5158
+ at: e.time,
5159
+ shadowedRange: d.shadowedRange,
5160
+ shadowedSeqs: d.shadowedSeqs,
5161
+ shadowedTokenCount: d.shadowedTokenCount,
5162
+ replacesCheckpoint: prevCheckpoint !== null && d.shadowedSeqs.includes(prevCheckpoint) ? prevCheckpoint : null,
5163
+ summaryText: d.summary.map((s) => s.text).join(""),
5164
+ originalText
5165
+ });
5166
+ prevCheckpoint = e.seq + 1;
5167
+ }
5168
+ }
5169
+ const sub = raw.slice(7).replace(/^\/+/, "");
5170
+ const segments = sub === "" ? [] : sub.split("/").filter((x) => x !== "");
5171
+ const parseSeg = (seg) => {
5172
+ const m = /^([a-z_]+)(?:\[(.+)\])?$/.exec(seg);
5173
+ if (m === null) throw new UrlSchemesError("CTX_BAD_PATH", `ctx://${raw}: unparseable segment "${seg}"`);
5174
+ return {
5175
+ name: m[1],
5176
+ bracket: m[2]
5177
+ };
5178
+ };
5179
+ const seg0 = segments[0] === void 0 ? void 0 : parseSeg(segments[0]);
5180
+ const seg1 = segments[1] === void 0 ? void 0 : parseSeg(segments[1]);
5181
+ if (seg0 === void 0 || seg0.name === "transcript") {
5182
+ seg0?.name;
5183
+ const canonical = transcript();
5184
+ return applyFace(buildSnapshot(agent, events, episodes), canonical, sel, seg0?.name === "transcript" ? "canonical" : "prepared");
5185
+ }
5186
+ if (seg0.name === "compactions") {
5187
+ if (seg0.bracket === void 0) {
5188
+ if (sel !== null && sel.kind !== "raw") throw new UrlSchemesError("CTX_BAD_SELECTOR", "ctx://session/compactions: only :raw (or no selector) is supported on the manifest");
5189
+ const body = episodes.map((e) => [
5190
+ `label=${e.label} (checkpoint_seq=${e.checkpointSeq})`,
5191
+ `compactionId=${e.compactionId}`,
5192
+ `at=${new Date(e.at ?? 0).toISOString()}`,
5193
+ `shadowed=${e.shadowedRange.start}..${e.shadowedRange.end} (${e.shadowedSeqs.length} items, ${e.shadowedTokenCount} tokens)`,
5194
+ `replaces_checkpoint=${e.replacesCheckpoint ?? "null"}`,
5195
+ `preview=${e.summaryText.slice(0, 120)}…`
5196
+ ].join(" | ")).join("\n");
5197
+ return body === "" ? "(no compactions recorded)" : body;
5198
+ }
5199
+ const episode = pickEpisode(episodes, seg0.bracket);
5200
+ if (seg1 === void 0) {
5201
+ if (sel !== null && sel.kind === "raw" && episode.originalText.length > 65536) return episode.originalText + `\n\n[ctx:// note: original span is ${episode.originalText.length} chars — page with :N-M line windows or grep this URL]`;
5202
+ return applyFace(episode.summaryText, episode.originalText, sel, "prepared");
5203
+ }
5204
+ throw new UrlSchemesError("CTX_BAD_PATH", `ctx://${raw}: unknown sub-path "/${seg1.name}" ("/original" was superseded by the :raw / :raw:N-M selectors)`);
5205
+ }
5206
+ if (seg0.name === "thinking" || seg0.name === "system" || seg0.name === "injections") {
5207
+ const items = seg0.name === "thinking" ? thinkingItems(events) : seg0.name === "system" ? systemItems(events) : injectionItems(events);
5208
+ if (seg0.bracket === void 0) {
5209
+ const index = items.map((it, i) => seg0.name === "thinking" ? `[${i}] seq=${it.seq} turn=${it.turn ?? "-"} step=${it.step ?? "-"} ${previewOf(it.text)}` : `seq=${it.seq} source=${it.source ?? "-"} ${previewOf(it.text)}`).join("\n");
5210
+ return applyFace(index === "" ? `(no ${seg0.name} items recorded)` : index, items.map((it) => it.text).join("\n\n"), sel, "prepared");
5211
+ }
5212
+ const bySeq = items.find((it) => it.seq === Number(seg0.bracket));
5213
+ if (bySeq !== void 0) return applyFace(bySeq.text, bySeq.text, sel, "prepared");
5214
+ const ordinal = Number(seg0.bracket);
5215
+ if (!Number.isInteger(ordinal) || ordinal < 0 || ordinal >= items.length) throw new UrlSchemesError("CTX_NO_SUCH_ELEMENT", `ctx://…[${seg0.bracket}]: no such element (collection "${seg0.name}" has ${items.length} items, 0-based; labels are event seqs)`);
5216
+ return applyFace(items[ordinal].text, items[ordinal].text, sel, "prepared");
5217
+ }
5218
+ const collection = (name$2) => {
5219
+ if (name$2 === "user_prompts") return userPromptEvents.map((e) => ({
5220
+ seq: e.seq,
5221
+ text: `[${String(e.seq).padStart(7, "0")}] USER\n${textOf(blocksOf(e))}`
5222
+ }));
5223
+ if (name$2 === "tool_calls") {
5224
+ const resultByCallSeq = /* @__PURE__ */ new Map();
5225
+ for (const e of events) {
5226
+ if (e.type !== "tool/result") continue;
5227
+ const callSeq = e.sourceEventSeqs?.[0];
5228
+ if (callSeq !== void 0) resultByCallSeq.set(callSeq, resultTextOf(e));
5229
+ }
5230
+ return toolCallEvents.map((e) => ({
5231
+ seq: e.seq,
5232
+ text: `[${String(e.seq).padStart(7, "0")}] TOOL ${e.data.name ?? "?"}\narguments: ${e.data.arguments ?? ""}\nresult: ${resultByCallSeq.get(e.seq) ?? "(no result captured)"}`
5233
+ }));
5234
+ }
5235
+ if (name$2 === "agent_responses") return agentResponseEvents.map((e) => ({
5236
+ seq: e.seq,
5237
+ text: `[${String(e.seq).padStart(7, "0")}] ASSISTANT\n${textOf(blocksOf(e)) || "(tool-call only)"}`
5238
+ }));
5239
+ throw new UrlSchemesError("CTX_BAD_PATH", `ctx://${raw}: unknown collection "${name$2}"`);
5240
+ };
5241
+ if (seg0.bracket !== void 0 || seg0.name === "user_prompts" || seg0.name === "tool_calls" || seg0.name === "agent_responses") {
5242
+ const items = collection(seg0.name);
5243
+ const bracket = seg0.bracket ?? "";
5244
+ const bySeq = items.find((it) => it.seq === Number(bracket));
5245
+ if (bySeq !== void 0) return applyFace(bySeq.text, bySeq.text, sel, "prepared");
5246
+ const ordinal = Number(bracket);
5247
+ if (bracket === "" || !Number.isInteger(ordinal) || ordinal < 0 || ordinal >= items.length) throw new UrlSchemesError("CTX_NO_SUCH_ELEMENT", `ctx://…[${bracket}]: no such element (collection "${seg0.name}" has ${items.length} items, 0-based; labels are event seqs)`);
5248
+ return applyFace(items[ordinal].text, items[ordinal].text, sel, "prepared");
5249
+ }
5250
+ throw new UrlSchemesError("CTX_UNKNOWN_KEY", `ctx://${raw}: unknown path (known: session, session/transcript, session/compactions[...], session/user_prompts[...], session/tool_calls[...], session/agent_responses[...], session/thinking[...], session/system[...], session/injections[...]; bare ctx:// lists the full roster)`);
5251
+ function pickEpisode(list, bracket) {
5252
+ const byLabel = list.find((e) => String(e.label) === bracket);
5253
+ if (byLabel !== void 0) return byLabel;
5254
+ const ordinal = Number(bracket);
5255
+ if (Number.isInteger(ordinal) && ordinal >= 0 && ordinal < list.length) return list[ordinal];
5256
+ throw new UrlSchemesError("CTX_NO_SUCH_ELEMENT", `ctx://…[${bracket}]: no such compaction episode (labels: ${list.map((e) => e.label).join(", ") || "none"})`);
5257
+ }
5258
+ function buildSnapshot(agent$1, all, list) {
5259
+ const headers = all.filter((e) => e.type === "request/header");
5260
+ const system = (headers[headers.length - 1]?.data)?.header?.system;
5261
+ const counts = {};
5262
+ for (const e of all) if (e.type === "user/message") {
5263
+ const k = sourceKind(e) === "user" ? "user_prompts" : "injected_user_messages";
5264
+ counts[k] = (counts[k] ?? 0) + 1;
5265
+ } else if (e.type === "assistant/message") {
5266
+ counts.agent_messages = (counts.agent_messages ?? 0) + 1;
5267
+ for (const b of blocksOf(e)) counts[`block_${b.type}`] = (counts[`block_${b.type}`] ?? 0) + 1;
5268
+ } else if (e.type === "tool/call") counts.tool_calls = (counts.tool_calls ?? 0) + 1;
5269
+ else if (e.type === "tool/result") counts.tool_results = (counts.tool_results ?? 0) + 1;
5270
+ counts.compactions = list.length;
5271
+ for (const key of [
5272
+ "user_prompts",
5273
+ "injected_user_messages",
5274
+ "agent_messages",
5275
+ "block_reasoning",
5276
+ "block_text",
5277
+ "block_tool-call",
5278
+ "tool_calls",
5279
+ "tool_results"
5280
+ ]) counts[key] = counts[key] ?? 0;
5281
+ const header = agent$1.session.header;
5282
+ return JSON.stringify({
5283
+ resource: "ctx://session",
5284
+ syntax: "/sub-path [<label|n>] [:N-M]; :raw = canonical full content; :raw:N-M ≡ :N-M",
5285
+ session: {
5286
+ id: header.id ?? agent$1.id,
5287
+ createdAt: header.createdAt,
5288
+ cwd: header.cwd,
5289
+ agentPreset: header.agentPreset,
5290
+ status: agent$1.status,
5291
+ origin: header.origin,
5292
+ delegationDepth: header.delegationDepth
5293
+ },
5294
+ storage: { format: "session.jsonl.zstd (read via sessionPersistence)" },
5295
+ totals: counts,
5296
+ segments: [...list.map((e) => ({
5297
+ segment: `compaction:${e.label}`,
5298
+ start: e.shadowedRange?.start ?? null,
5299
+ end: e.shadowedRange?.end ?? null,
5300
+ items: e.shadowedSeqs.length
5301
+ })), (() => {
5302
+ const lastSeq = all.reduce((m, e) => Math.max(m, e.seq), 0);
5303
+ const start = (list.at(-1)?.shadowedRange?.end ?? -1) + 1;
5304
+ return {
5305
+ segment: "live",
5306
+ start,
5307
+ end: lastSeq,
5308
+ items: all.filter((e) => e.seq >= start).length
5309
+ };
5310
+ })()],
5311
+ system_prompt: {
5312
+ chars: system?.length ?? 0,
5313
+ preview: system === void 0 ? "" : system.slice(0, 200)
5314
+ },
5315
+ compacted: list.map((e) => ({
5316
+ label: e.label,
5317
+ checkpoint_seq: e.checkpointSeq,
5318
+ compactionId: e.compactionId,
5319
+ at: e.at,
5320
+ shadowed_range: e.shadowedRange,
5321
+ shadowed_items: e.shadowedSeqs.length,
5322
+ shadowed_token_count: e.shadowedTokenCount,
5323
+ replaces_checkpoint: e.replacesCheckpoint,
5324
+ summary_preview: sectionPreviews(e.summaryText)
5325
+ })),
5326
+ hints: [
5327
+ "ctx://session:raw → full transcript",
5328
+ "ctx://session/compactions[<label>]:raw → original span",
5329
+ "ctx://session/thinking / ctx://session/system / ctx://session/injections → index-faced collections"
5330
+ ]
5331
+ }, void 0, 2);
5332
+ }
5333
+ }
5334
+ };
5500
5335
  }
5336
+
5337
+ //#endregion
5338
+ //#region src/url-schemes/general-section.ts
5339
+ const GENERAL_SECTION_NAME = "url-schema:general";
5340
+ const GENERAL_SECTION_ORDER = 129;
5341
+ /** The section file's name, resolved against the nearest ancestor directory. */
5342
+ const SECTION_FILE = "url-schemes-instruction.md";
5501
5343
  /**
5502
- * Build the `http://`/`https://` handler. Register the returned instance
5503
- * under BOTH schemes ({@link HTTP_SCHEMES}); it is stateless and safe to
5504
- * share.
5344
+ * Load the section text at module load, walking up to the nearest copy.
5345
+ *
5346
+ * A fixed-depth `new URL('../…', import.meta.url)` breaks here: tsdown
5347
+ * inlines this module into `lib/index.js`, so the depth from the module to
5348
+ * the package root differs between source (`src/url-schemes/general-section.ts`)
5349
+ * and build (`lib/index.js`) — the same reason `docs-dir.ts` walks up. Near
5350
+ * layers are probed first, so an unrelated ancestor copy is only ever reached
5351
+ * when the package's own file is absent.
5505
5352
  */
5506
- function createHttpHandler() {
5507
- return { async resolve(env, path$1) {
5508
- const raw = env.rawUrl ?? `https://${path$1}`;
5509
- let url;
5353
+ function loadSectionText() {
5354
+ const here$1 = fileURLToPath(import.meta.url);
5355
+ let dir = dirname(here$1);
5356
+ for (;;) {
5510
5357
  try {
5511
- url = new URL(raw);
5512
- } catch (err) {
5513
- throw new UrlSchemaError("URL_INVALID", `invalid http(s) URL "${raw}": ${err instanceof Error ? err.message : String(err)}`);
5514
- }
5515
- if (url.protocol !== "http:" && url.protocol !== "https:") throw new UrlSchemaError("URL_INVALID", `not an http(s) URL: "${raw}"`);
5516
- return `${DISCLAIMER}\n\n${await getUrl(url)}`;
5517
- } };
5358
+ return readFileSync(join(dir, SECTION_FILE), "utf8");
5359
+ } catch {}
5360
+ const parent = dirname(dir);
5361
+ if (parent === dir) throw new Error(`${SECTION_FILE} not found from ${here$1} — it must ship with the package (package.json "files")`);
5362
+ dir = parent;
5363
+ }
5364
+ }
5365
+ const SECTION_TEXT = loadSectionText();
5366
+ /** Render the section when the URL capability is on; `undefined` gates it off. */
5367
+ function generalSection(gates) {
5368
+ if (!gates.urlSchemes) return void 0;
5369
+ return {
5370
+ name: GENERAL_SECTION_NAME,
5371
+ order: GENERAL_SECTION_ORDER,
5372
+ text: SECTION_TEXT
5373
+ };
5518
5374
  }
5519
5375
 
5520
5376
  //#endregion
5521
- //#region src/url-schema/handlers/skill.ts
5377
+ //#region src/url-schemes/handlers/skill.ts
5522
5378
  /** Split `path` into `{ name, subpath }`; `subpath` is `null` for a bare name. */
5523
5379
  function splitSkillPath(path$1) {
5524
5380
  const slash = path$1.indexOf("/");
@@ -5536,22 +5392,24 @@ function splitSkillPath(path$1) {
5536
5392
  function resolveResourcePath(baseDir, subpath) {
5537
5393
  const full = join(baseDir, subpath);
5538
5394
  const rel = relative(baseDir, full);
5539
- if (rel === "" || rel === ".." || rel.startsWith("../")) throw new UrlSchemaError("URL_SKILL_RESOURCE_ESCAPE", `skill resource path "${subpath}" escapes its directory`);
5395
+ if (rel === "" || rel === ".." || rel.startsWith("../")) throw new UrlSchemesError("URL_SKILL_RESOURCE_ESCAPE", `skill resource path "${subpath}" escapes its directory`);
5540
5396
  return full;
5541
5397
  }
5542
5398
  /**
5543
- * Registry lookup options for one env, mirroring `dsh-tool-skill`'s lookup:
5544
- * the workspace cwd (explicit `cwd`, else the agent session's) plus the
5545
- * calling agent as the viewing `scope` — without it a scoped skill layer
5546
- * reads as absent. Both absent → `undefined`, the exact call shape of the
5547
- * cwd-less era.
5399
+ * Registry lookup options for one env — the exact call shape of
5400
+ * `dsh-tool-skill`: the calling agent is the viewing `scope` (the agent is
5401
+ * its own scope key, so the layered registry resolves precisely as this
5402
+ * agent's composition sees it) and the workspace cwd comes from the agent's
5403
+ * session header. No agent in the env → `undefined` (the cwd-less global
5404
+ * view); the handler then reports the registry's own verdict.
5548
5405
  */
5549
5406
  function lookupOptions(env) {
5550
- const cwd = env.cwd ?? env.agent?.session.header.cwd;
5551
- if (cwd === void 0 && env.agent === void 0) return void 0;
5407
+ const agent = env.agent;
5408
+ if (agent === void 0) return void 0;
5409
+ const cwd = agent.session.header.cwd;
5552
5410
  return {
5553
5411
  ...cwd === void 0 ? {} : { cwd },
5554
- ...env.agent === void 0 ? {} : { scope: env.agent }
5412
+ scope: agent
5555
5413
  };
5556
5414
  }
5557
5415
  /**
@@ -5565,9 +5423,9 @@ function createSkillHandler(deps) {
5565
5423
  async resolve(env, path$1) {
5566
5424
  const { name: name$2, subpath } = splitSkillPath(path$1);
5567
5425
  const skill = await deps.skills.get(name$2, lookupOptions(env));
5568
- if (skill === void 0) throw new UrlSchemaError("URL_SKILL_NOT_FOUND", `skill "${name$2}" is unknown or no longer available`);
5426
+ if (skill === void 0) throw new UrlSchemesError("URL_SKILL_NOT_FOUND", `skill "${name$2}" is unknown or no longer available`);
5569
5427
  if (subpath === null) return skill.content;
5570
- if (skill.resourceBase === void 0 || skill.resourceBase.kind !== "directory") throw new UrlSchemaError("URL_SKILL_NO_RESOURCE_BASE", `skill "${name$2}" has no filesystem resource directory`);
5428
+ if (skill.resourceBase === void 0 || skill.resourceBase.kind !== "directory") throw new UrlSchemesError("URL_SKILL_NO_RESOURCE_BASE", `skill "${name$2}" has no filesystem resource directory`);
5571
5429
  const resourcePath = resolveResourcePath(skill.resourceBase.path, subpath);
5572
5430
  const target = await deps.fs.resolve(resourcePath);
5573
5431
  return await deps.fs.readText(target);
@@ -5589,9 +5447,10 @@ function createSkillHandler(deps) {
5589
5447
  }
5590
5448
 
5591
5449
  //#endregion
5592
- //#region src/url-schema/native-capture.ts
5593
- /** Tool names the wrappers shadow and delegate back to. */
5450
+ //#region src/url-schemes/native-capture.ts
5451
+ /** Tool names the chassis/wrappers shadow and delegate back to. */
5594
5452
  const NATIVE_TOOL_NAMES = [
5453
+ "read",
5595
5454
  "write",
5596
5455
  "grep",
5597
5456
  "glob"
@@ -5652,50 +5511,7 @@ function captureNativeTools(ctx, agent) {
5652
5511
  }
5653
5512
 
5654
5513
  //#endregion
5655
- //#region src/url-schema/resolver.ts
5656
- /** Scheme→handler registry that resolves `scheme://` URLs end-to-end. */
5657
- var UrlResolver = class {
5658
- handlers = /* @__PURE__ */ new Map();
5659
- /** Register (or replace) the handler for `scheme`. */
5660
- register(scheme, handler) {
5661
- this.handlers.set(scheme, handler);
5662
- }
5663
- /**
5664
- * Resolve a `scheme://` URL to its selected text: parse the URL, dispatch
5665
- * to the registered handler for the scheme, then apply the selector.
5666
- *
5667
- * Throws a structured {@link UrlSchemaError} for a scheme-less URL
5668
- * (`URL_NO_SCHEME`, from {@link parseUrl}) or an unregistered scheme
5669
- * (`URL_UNREGISTERED_SCHEME`).
5670
- */
5671
- async resolve(env, url) {
5672
- const parsed = parseUrl(url);
5673
- const handler = this.handlers.get(parsed.scheme);
5674
- if (handler === void 0) {
5675
- const registered = [...this.handlers.keys()].sort().join(", ");
5676
- throw new UrlSchemaError("URL_UNREGISTERED_SCHEME", `no handler registered for scheme "${parsed.scheme}" (registered: ${registered || "none"})`);
5677
- }
5678
- return applySelector(await handler.resolve(env, parsed.path), parsed.selector);
5679
- }
5680
- /**
5681
- * Resolve a `scheme://` URL to its on-disk path when its handler is
5682
- * path-backed: parse the URL, dispatch to the handler's optional
5683
- * `resolvePath`, and return the real disk location. Returns `undefined`
5684
- * for an unregistered scheme, a handler without `resolvePath`, or a path
5685
- * the handler cannot map — callers then fall back to text resolution
5686
- * (whose unregistered-scheme error is the structured generic one).
5687
- * Selectors are NOT applied: they operate on resolved text, not paths.
5688
- */
5689
- async resolvePath(env, url) {
5690
- const parsed = parseUrl(url);
5691
- const handler = this.handlers.get(parsed.scheme);
5692
- if (handler?.resolvePath === void 0) return void 0;
5693
- return await handler.resolvePath(env, parsed.path);
5694
- }
5695
- };
5696
-
5697
- //#endregion
5698
- //#region src/url-schema/tools/materialize.ts
5514
+ //#region src/url-schemes/tools/materialize.ts
5699
5515
  /** Root preferred for materialization; temp dirs are `dashr-url-*` inside it. */
5700
5516
  const SHM_ROOT = "/dev/shm";
5701
5517
  /** Per-materialization shm ceiling (8 MiB); strictly larger goes to disk. */
@@ -5739,7 +5555,7 @@ async function withTempMaterialization(text, body) {
5739
5555
  }
5740
5556
 
5741
5557
  //#endregion
5742
- //#region src/url-schema/tools/glob.ts
5558
+ //#region src/url-schemes/tools/glob.ts
5743
5559
  /** Build the resolver env from one tool execution (agent + session cwd + raw URL). */
5744
5560
  function execEnv$1(exec, rawUrl) {
5745
5561
  return {
@@ -5754,13 +5570,13 @@ function isSchemeUrl$2(raw) {
5754
5570
  parseUrl(raw);
5755
5571
  return true;
5756
5572
  } catch (error) {
5757
- if (error instanceof UrlSchemaError && error.code === "URL_NO_SCHEME") return false;
5573
+ if (error instanceof UrlSchemesError && error.code === "URL_NO_SCHEME") return false;
5758
5574
  throw error;
5759
5575
  }
5760
5576
  }
5761
5577
  /** Structured error for calls that cannot run without the native delegate. */
5762
5578
  function nativeUnavailable$1() {
5763
- return new UrlSchemaError("NATIVE_GLOB_UNAVAILABLE", "the host did not deploy a native glob tool — URL-aware glob cannot delegate discovery");
5579
+ return new UrlSchemesError("NATIVE_GLOB_UNAVAILABLE", "the host did not deploy a native glob tool — URL-aware glob cannot delegate discovery");
5764
5580
  }
5765
5581
  /**
5766
5582
  * Build the `glob` {@link ToolDefinition}: a `scheme://` URL lists the
@@ -5772,16 +5588,16 @@ function createGlobTool(deps) {
5772
5588
  const { resolver, nativeGlob } = deps;
5773
5589
  return defineTool({
5774
5590
  name: "glob",
5775
- description: "Discover files matching a glob pattern. A scheme:// URL addresses a resource instead: in `pattern` it lists that resource, in `path` it scopes the glob to the resource.",
5591
+ description: "Find files whose paths match a glob pattern. Returns matching file paths — never directories — including hidden and ignored files (VCS metadata directories are excluded). Up to 100 paths come back in modification-time order; a larger result returns the first 100 paths in modification-time order, says so, and reports where the complete sorted list was saved. This tool does not enumerate directory entries.",
5776
5592
  parameters: {
5777
5593
  pattern: {
5778
5594
  type: "string",
5779
5595
  required: true,
5780
- description: "Glob pattern, or a scheme:// URL addressing the resource to list."
5596
+ description: "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth."
5781
5597
  },
5782
5598
  path: {
5783
5599
  type: "string",
5784
- description: "Optional search root directory, or a scheme:// URL scoping the glob."
5600
+ description: "Directory to search in. Defaults to the session workspace; a relative path resolves against it."
5785
5601
  }
5786
5602
  },
5787
5603
  output: {
@@ -5845,7 +5661,7 @@ function createGlobTool(deps) {
5845
5661
  }
5846
5662
 
5847
5663
  //#endregion
5848
- //#region src/url-schema/tools/grep.ts
5664
+ //#region src/url-schemes/tools/grep.ts
5849
5665
  /** Build the resolver env from one tool execution (agent + session cwd + raw URL). */
5850
5666
  function execEnv(exec, rawUrl) {
5851
5667
  return {
@@ -5860,13 +5676,13 @@ function isSchemeUrl$1(raw) {
5860
5676
  parseUrl(raw);
5861
5677
  return true;
5862
5678
  } catch (error) {
5863
- if (error instanceof UrlSchemaError && error.code === "URL_NO_SCHEME") return false;
5679
+ if (error instanceof UrlSchemesError && error.code === "URL_NO_SCHEME") return false;
5864
5680
  throw error;
5865
5681
  }
5866
5682
  }
5867
5683
  /** Structured error for calls that cannot run without the native delegate. */
5868
5684
  function nativeUnavailable() {
5869
- return new UrlSchemaError("NATIVE_GREP_UNAVAILABLE", "the host did not deploy a native grep tool — URL-aware grep cannot delegate searches");
5685
+ return new UrlSchemesError("NATIVE_GREP_UNAVAILABLE", "the host did not deploy a native grep tool — URL-aware grep cannot delegate searches");
5870
5686
  }
5871
5687
  /**
5872
5688
  * Build the `grep` {@link ToolDefinition}: a `scheme://` `path` translates
@@ -5878,7 +5694,7 @@ function createGrepTool(deps) {
5878
5694
  const { resolver, nativeGrep } = deps;
5879
5695
  return defineTool({
5880
5696
  name: "grep",
5881
- description: "Search file contents with a regular expression. When `path` is a scheme:// URL, search the resolved resource: path-backed schemes search their real disk location, content-backed schemes search the resolved text.",
5697
+ description: "Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns the first 250 matches inline; a capped result reports where the complete match list was saved. Use read on a matched file for surrounding context.",
5882
5698
  parameters: {
5883
5699
  pattern: {
5884
5700
  type: "string",
@@ -5887,11 +5703,11 @@ function createGrepTool(deps) {
5887
5703
  },
5888
5704
  path: {
5889
5705
  type: "string",
5890
- description: "Search root: a filesystem path, or a scheme:// URL to search the resolved resource."
5706
+ description: "File or directory to search. Defaults to the session workspace; a relative path resolves against it."
5891
5707
  },
5892
5708
  include: {
5893
5709
  type: "string",
5894
- description: "Optional file-glob filter."
5710
+ description: "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported."
5895
5711
  }
5896
5712
  },
5897
5713
  output: {
@@ -5951,7 +5767,7 @@ function createGrepTool(deps) {
5951
5767
  }
5952
5768
 
5953
5769
  //#endregion
5954
- //#region src/url-schema/vendored/hashline/constants.js
5770
+ //#region src/url-schemes/vendored/hashline/constants.js
5955
5771
  const MAX_BYTES = 100 * 1024 * 1024;
5956
5772
  const MAX_READ_LINE_BYTES = 200 * 1024;
5957
5773
  const HASH_STORE_BUSY_TIMEOUT = 1e3;
@@ -5963,7 +5779,7 @@ const NOOP_LOOP_THRESHOLD = 3;
5963
5779
  const NEW_CONTENT_NOT_STRING_MSG = "[E_BAD_SHAPE] \"replacement_text\" must be a string with \\n line separators, not an array. Do not pass an array of lines — pass the replacement text as one string: \"line1\\nline2\". Use \"\" to delete a range.";
5964
5780
 
5965
5781
  //#endregion
5966
- //#region src/url-schema/vendored/hashline/utils.js
5782
+ //#region src/url-schemes/vendored/hashline/utils.js
5967
5783
  function isRec(value) {
5968
5784
  return typeof value === "object" && value !== null && !Array.isArray(value);
5969
5785
  }
@@ -6016,7 +5832,7 @@ function clipLine(line, maxLen = 200) {
6016
5832
  }
6017
5833
 
6018
5834
  //#endregion
6019
- //#region src/url-schema/vendored/hashline/contract.js
5835
+ //#region src/url-schemes/vendored/hashline/contract.js
6020
5836
  const normalizedEdit = Symbol("normalizedEdit");
6021
5837
  function isNormalizedEdit(input) {
6022
5838
  return isRec(input) && input[normalizedEdit] === true;
@@ -6107,7 +5923,7 @@ function assertUndoRequest(request) {
6107
5923
  }
6108
5924
 
6109
5925
  //#endregion
6110
- //#region src/url-schema/vendored/hashline/paths.js
5926
+ //#region src/url-schemes/vendored/hashline/paths.js
6111
5927
  /**
6112
5928
  * On-disk home for dsh-better-edit state: ONE centralized store per harness
6113
5929
  * home — `$DSH_HOME/storages/dsh-better-edit` (default
@@ -6124,11 +5940,11 @@ function hashStorePath() {
6124
5940
  }
6125
5941
 
6126
5942
  //#endregion
6127
- //#region src/url-schema/vendored/hashline/fs-write.js
5943
+ //#region src/url-schemes/vendored/hashline/fs-write.js
6128
5944
  const STALE_TEMP_MS = 3600 * 1e3;
6129
5945
 
6130
5946
  //#endregion
6131
- //#region src/url-schema/vendored/hashline/hashline/hash-assign.js
5947
+ //#region src/url-schemes/vendored/hashline/hashline/hash-assign.js
6132
5948
  let hasher = null;
6133
5949
  function getH() {
6134
5950
  if (hasher) return hasher;
@@ -6342,7 +6158,7 @@ function mapStableHashes(oldContent, oldHashes, newContent, removedHashes) {
6342
6158
  }
6343
6159
 
6344
6160
  //#endregion
6345
- //#region src/url-schema/vendored/hashline/hash-store.js
6161
+ //#region src/url-schemes/vendored/hashline/hash-store.js
6346
6162
  function isValidHashList(value) {
6347
6163
  if (!Array.isArray(value)) return false;
6348
6164
  for (const hash of value) if (typeof hash !== "string" || !HASH_RE.test(hash)) return false;
@@ -6832,7 +6648,7 @@ async function upsertSnapshotFor(path$1, checksum, lineCount, hashes) {
6832
6648
  }
6833
6649
 
6834
6650
  //#endregion
6835
- //#region src/url-schema/vendored/hashline/hashline/hash.js
6651
+ //#region src/url-schemes/vendored/hashline/hashline/hash.js
6836
6652
  let defaultHashSnapshotIO;
6837
6653
  function snapshotIOFor(store) {
6838
6654
  if (store) return {
@@ -6881,7 +6697,7 @@ async function lineHashes(content, path$1, previous, ioOrStore, persist) {
6881
6697
  }
6882
6698
 
6883
6699
  //#endregion
6884
- //#region src/url-schema/vendored/hashline/session-view.js
6700
+ //#region src/url-schemes/vendored/hashline/session-view.js
6885
6701
  const current = new AsyncLocalStorage();
6886
6702
  function withWorkspace(cwd, fn) {
6887
6703
  return current.run(cwd, fn);
@@ -7102,7 +6918,7 @@ async function scanDrift(input) {
7102
6918
  }
7103
6919
 
7104
6920
  //#endregion
7105
- //#region src/url-schema/vendored/hashline/hashline/anchor-pipeline.js
6921
+ //#region src/url-schemes/vendored/hashline/hashline/anchor-pipeline.js
7106
6922
  function diagRef(ref) {
7107
6923
  const trimmed = ref.trim();
7108
6924
  if (!trimmed.length) return `[E_BAD_REF] Invalid anchor. Expected a 3-char alphanumeric anchor (e.g. "aB3").`;
@@ -7745,7 +7561,7 @@ function changedRange(original, result) {
7745
7561
  }
7746
7562
 
7747
7563
  //#endregion
7748
- //#region src/url-schema/vendored/hashline/edit-diff.js
7564
+ //#region src/url-schemes/vendored/hashline/edit-diff.js
7749
7565
  function detectEnding(content) {
7750
7566
  const lfIdx = content.indexOf("\n");
7751
7567
  if (lfIdx === -1) return content.indexOf("\r") >= 0 ? "\r" : "\n";
@@ -7860,7 +7676,7 @@ function genDiff(oldContent, newContent, contextLines = 2, newContentHashes, old
7860
7676
  }
7861
7677
 
7862
7678
  //#endregion
7863
- //#region src/url-schema/vendored/hashline/file-view.js
7679
+ //#region src/url-schemes/vendored/hashline/file-view.js
7864
7680
  const MAX_HASH_LINES$1 = HASH_SPACE;
7865
7681
  const DEFAULT_MAX_LINES = 2e3;
7866
7682
  const DEFAULT_MAX_BYTES = 50 * 1024;
@@ -8084,7 +7900,7 @@ async function readView(io, path$1, cwd, opts = {}) {
8084
7900
  }
8085
7901
 
8086
7902
  //#endregion
8087
- //#region src/url-schema/vendored/hashline/fs-bridge.js
7903
+ //#region src/url-schemes/vendored/hashline/fs-bridge.js
8088
7904
  /**
8089
7905
  * Map an `ctx.fs` failure onto the hashline model-facing vocabulary so the
8090
7906
  * model sees the same structured error codes as the pure pipeline.
@@ -8159,7 +7975,7 @@ function ctxFsIO(fs, ctx) {
8159
7975
  }
8160
7976
 
8161
7977
  //#endregion
8162
- //#region src/url-schema/vendored/hashline/read-and-serve.js
7978
+ //#region src/url-schemes/vendored/hashline/read-and-serve.js
8163
7979
  /** Appended when the file had non-UTF-8 bytes; editing rewrites it as UTF-8. */
8164
7980
  const UTF8_REWRITE_NOTE = "[Non-UTF-8 bytes shown as U+FFFD; editing rewrites the file as UTF-8.]";
8165
7981
  /**
@@ -8191,7 +8007,7 @@ async function readAndServe(io, rawPath, cwd, options) {
8191
8007
  }
8192
8008
 
8193
8009
  //#endregion
8194
- //#region src/url-schema/tools/read.ts
8010
+ //#region src/url-schemes/tools/read.ts
8195
8011
  /** Mirrors `parseUrl`'s scheme prefix so the fork matches the resolver exactly. */
8196
8012
  const SCHEME_URL_RE = /^[a-z][a-z0-9]*:\/\//;
8197
8013
  /**
@@ -8201,15 +8017,15 @@ const SCHEME_URL_RE = /^[a-z][a-z0-9]*:\/\//;
8201
8017
  * scheme handlers read whichever fields they need off it.
8202
8018
  */
8203
8019
  function createReadTool(deps) {
8204
- const { resolver, fs, ctx } = deps;
8020
+ const { resolver, fs, ctx, gates, capturedRead } = deps;
8205
8021
  const io = ctxFsIO(fs, ctx);
8206
8022
  return defineTool({
8207
8023
  name: "read",
8208
- description: "Read a text file (each line returned as `HASH│content` with a 3-char hash anchor for later edit calls) or resolve a `scheme://` URL (skill/agent/dsh/dvc/http) to its selected text. File reads page with offset/limit; URL reads resolve end-to-end and ignore offset/limit.",
8024
+ description: "Read a text file (each line returned as `HASH│content` with a 3-char hash anchor for later edit calls). File reads page with offset/limit.",
8209
8025
  parameters: {
8210
8026
  path: {
8211
8027
  type: "string",
8212
- description: "File path (hashline-anchored read), or a `scheme://` URL to resolve (e.g. skill://name/path, agent://id/transcript, dsh://docs/doc, dvc://device, https://host/path)."
8028
+ description: "File path (hashline-anchored read)."
8213
8029
  },
8214
8030
  offset: {
8215
8031
  type: "number",
@@ -8231,7 +8047,8 @@ function createReadTool(deps) {
8231
8047
  const canonical = normalizeRequest(args);
8232
8048
  assertReadRequest(canonical);
8233
8049
  const rawPath = canonical.path;
8234
- if (SCHEME_URL_RE.test(rawPath)) {
8050
+ const isScheme = SCHEME_URL_RE.test(rawPath);
8051
+ if (gates.urlSchemes && isScheme) {
8235
8052
  const cwd = exec.agent?.session.header.cwd;
8236
8053
  const env = cwd === void 0 ? {
8237
8054
  agent: exec.agent,
@@ -8243,7 +8060,7 @@ function createReadTool(deps) {
8243
8060
  };
8244
8061
  return resolver.resolve(env, rawPath);
8245
8062
  }
8246
- return withWorkspace(execCwd(exec), async () => {
8063
+ if (gates.hashline && !isScheme) return withWorkspace(execCwd(exec), async () => {
8247
8064
  const cwd = execCwd(exec);
8248
8065
  const signal = exec.signal;
8249
8066
  const { text, absolutePath } = await readAndServe(io, rawPath, cwd, {
@@ -8254,33 +8071,30 @@ function createReadTool(deps) {
8254
8071
  await io.emitObserved(absolutePath, exec, signal);
8255
8072
  return text;
8256
8073
  });
8074
+ if (capturedRead !== void 0 && capturedRead.execute !== void 0) return capturedRead.execute(args, exec);
8075
+ throw new UrlSchemesError("NATIVE_READ_UNAVAILABLE", "no read delegate is available: urlSchemes and hashline gates are both disabled");
8257
8076
  }
8258
8077
  });
8259
8078
  }
8260
8079
 
8261
8080
  //#endregion
8262
- //#region src/url-schema/tools/write.ts
8081
+ //#region src/url-schemes/tools/write.ts
8263
8082
  /**
8264
8083
  * Schemes the URL schema registers (v0.1.8d): the default write dispatch
8265
- * keys off this static table; the integration step can replace the whole
8084
+ * keys off this table; the integration step can replace the whole
8266
8085
  * dispatch through `writeScheme` when it needs the live registry.
8086
+ *
8087
+ * Rendered from `catalog.ts` rather than written out here — this literal was
8088
+ * one of the hand-maintained copies that drifted from the scheme set.
8267
8089
  */
8268
- const REGISTERED_SCHEMES = [
8269
- "agent",
8270
- "ctx",
8271
- "dsh",
8272
- "dvc",
8273
- "http",
8274
- "https",
8275
- "skill"
8276
- ];
8090
+ const REGISTERED_SCHEMES = SCHEME_NAMES;
8277
8091
  /** Detects a `scheme://` prefix with the resolver layer's own parser. */
8278
8092
  function isSchemeUrl(raw) {
8279
8093
  try {
8280
8094
  parseUrl(raw);
8281
8095
  return true;
8282
8096
  } catch (error) {
8283
- if (error instanceof UrlSchemaError && error.code === "URL_NO_SCHEME") return false;
8097
+ if (error instanceof UrlSchemesError && error.code === "URL_NO_SCHEME") return false;
8284
8098
  throw error;
8285
8099
  }
8286
8100
  }
@@ -8298,9 +8112,9 @@ async function defaultSchemeWrite(scheme, path$1, content) {
8298
8112
  after: JSON.stringify(result, null, 2)
8299
8113
  };
8300
8114
  }
8301
- if (scheme === "ctx") throw new UrlSchemaError("URL_READ_ONLY", `ctx:// is a curated read-only snapshot — write to ctx://${path$1} is not supported`);
8302
- if (REGISTERED_SCHEMES.includes(scheme)) throw new UrlSchemaError("URL_WRITE_UNSUPPORTED", `write to ${scheme}:// is not supported (read-only scheme, or its write channel is not wired yet)`);
8303
- throw new UrlSchemaError("URL_UNREGISTERED_SCHEME", `no handler registered for scheme "${scheme}" (registered: ${REGISTERED_SCHEMES.join(", ")})`);
8115
+ if (scheme === "ctx") throw new UrlSchemesError("URL_READ_ONLY", `ctx:// is a curated read-only snapshot — write to ctx://${path$1} is not supported`);
8116
+ if (REGISTERED_SCHEMES.includes(scheme)) throw new UrlSchemesError("URL_WRITE_UNSUPPORTED", `write to ${scheme}:// is not supported (read-only scheme, or its write channel is not wired yet)`);
8117
+ throw new UrlSchemesError("URL_UNREGISTERED_SCHEME", `no handler registered for scheme "${scheme}" (registered: ${REGISTERED_SCHEMES.join(", ")})`);
8304
8118
  }
8305
8119
  /**
8306
8120
  * Build the `write` {@link ToolDefinition}: `scheme://` paths route to the
@@ -8312,17 +8126,17 @@ function createWriteTool(deps) {
8312
8126
  const writeScheme = deps.writeScheme ?? defaultSchemeWrite;
8313
8127
  return defineTool({
8314
8128
  name: "write",
8315
- description: "Create or fully replace a file. `file_path` may be a filesystem path or a scheme:// URL (`dvc://<device>` executes the device with a JSON args payload; other scheme writes are rejected).",
8129
+ description: "Create or fully replace a UTF-8 text file.",
8316
8130
  parameters: {
8317
8131
  file_path: {
8318
8132
  type: "string",
8319
8133
  required: true,
8320
- description: "Target filesystem path or scheme:// URL to write."
8134
+ description: "Path to write, resolved by the filesystem backend."
8321
8135
  },
8322
8136
  content: {
8323
8137
  type: "string",
8324
8138
  required: true,
8325
- description: "Full new content (an empty string writes an empty file)."
8139
+ description: "Full UTF-8 text content to write."
8326
8140
  },
8327
8141
  ...sandbox !== void 0 && sandbox.escalationModes.length > 0 ? sandbox.schemaFields() : {}
8328
8142
  },
@@ -8357,9 +8171,11 @@ function createWriteTool(deps) {
8357
8171
  },
8358
8172
  render: (_args, value) => {
8359
8173
  const verb = value.operation === "update" ? "Updated" : value.operation === "execute" ? "Executed" : "Created";
8174
+ let text = value.diagnostics === void 0 ? `${verb} ${value.path}` : `${verb} ${value.path}\n${value.diagnostics}`;
8175
+ if (value.operation === "execute" && value.after !== void 0) text += `\n${value.after}`;
8360
8176
  return [{
8361
8177
  type: "text",
8362
- text: value.diagnostics === void 0 ? `${verb} ${value.path}` : `${verb} ${value.path}\n${value.diagnostics}`
8178
+ text
8363
8179
  }];
8364
8180
  }
8365
8181
  },
@@ -8368,7 +8184,7 @@ function createWriteTool(deps) {
8368
8184
  const parsed = parseUrl(args.file_path);
8369
8185
  return writeScheme(parsed.scheme, parsed.path, args.content, {});
8370
8186
  }
8371
- if (nativeWrite === void 0) throw new UrlSchemaError("NATIVE_WRITE_UNAVAILABLE", "the host did not deploy a native write tool — URL-aware write cannot delegate filesystem writes");
8187
+ if (nativeWrite === void 0) throw new UrlSchemesError("NATIVE_WRITE_UNAVAILABLE", "the host did not deploy a native write tool — URL-aware write cannot delegate filesystem writes");
8372
8188
  let content = args.content;
8373
8189
  try {
8374
8190
  const formatted = await preWriteFormat?.(args.file_path, content);
@@ -8392,7 +8208,7 @@ function createWriteTool(deps) {
8392
8208
  }
8393
8209
 
8394
8210
  //#endregion
8395
- //#region src/url-schema/vendored/hashline/sandbox.js
8211
+ //#region src/url-schemes/vendored/hashline/sandbox.js
8396
8212
  /**
8397
8213
  * The filesystem escalation API: advertisement gating, per-call policy
8398
8214
  * resolution, the one-approved wider retry, and denial-marker mapping. A pure
@@ -8478,7 +8294,7 @@ var FsSandboxController = class {
8478
8294
  };
8479
8295
 
8480
8296
  //#endregion
8481
- //#region src/url-schema/vendored/hashline/undo-edit.js
8297
+ //#region src/url-schemes/vendored/hashline/undo-edit.js
8482
8298
  /** Load the last undo row for a path from the active store, if any. */
8483
8299
  async function readUndo(path$1) {
8484
8300
  return (await loadHashStore()).getUndo(path$1);
@@ -8560,7 +8376,7 @@ async function clearUndo(path$1) {
8560
8376
  }
8561
8377
 
8562
8378
  //#endregion
8563
- //#region src/url-schema/vendored/hashline/noop-guard.js
8379
+ //#region src/url-schemes/vendored/hashline/noop-guard.js
8564
8380
  const noopLoopTracker = /* @__PURE__ */ new Map();
8565
8381
  function noopPayloadKey(absolutePath, removeFrom, removeTo, replacementText) {
8566
8382
  return JSON.stringify([
@@ -8584,7 +8400,7 @@ function clearNoopLoop(absolutePath) {
8584
8400
  }
8585
8401
 
8586
8402
  //#endregion
8587
- //#region src/url-schema/vendored/hashline/edit-engine.js
8403
+ //#region src/url-schemes/vendored/hashline/edit-engine.js
8588
8404
  /** The hashes a range edit removes, for stable re-hash bookkeeping. */
8589
8405
  function collectRemovedHashes(edit, originalHashes) {
8590
8406
  const removedHashes = /* @__PURE__ */ new Set();
@@ -8919,7 +8735,7 @@ async function persistUndoAndWrite(opts) {
8919
8735
  }
8920
8736
 
8921
8737
  //#endregion
8922
- //#region src/url-schema/vendored/hashline/edit-response.js
8738
+ //#region src/url-schemes/vendored/hashline/edit-response.js
8923
8739
  function buildMetrics(args) {
8924
8740
  const metrics = {
8925
8741
  edits_attempted: args.editsAttempted,
@@ -9010,7 +8826,7 @@ function buildBatchResult(sections) {
9010
8826
  }
9011
8827
 
9012
8828
  //#endregion
9013
- //#region src/url-schema/vendored/hashline/mutation.js
8829
+ //#region src/url-schemes/vendored/hashline/mutation.js
9014
8830
  /**
9015
8831
  * Deep seam: execute the full mutation lifecycle.
9016
8832
  *
@@ -9113,7 +8929,7 @@ async function commit(opts) {
9113
8929
  }
9114
8930
 
9115
8931
  //#endregion
9116
- //#region src/url-schema/vendored/hashline/prompts.js
8932
+ //#region src/url-schemes/vendored/hashline/prompts.js
9117
8933
  /**
9118
8934
  * Model-facing prompt text for the hashline tools, embedded so the bundle
9119
8935
  * ships no external prompt files. Each tool's schema `description` is short;
@@ -9148,7 +8964,7 @@ const UNDO_GUIDANCE = {
9148
8964
  };
9149
8965
 
9150
8966
  //#endregion
9151
- //#region src/url-schema/vendored/hashline/tool-edit.js
8967
+ //#region src/url-schemes/vendored/hashline/tool-edit.js
9152
8968
  async function resolveNullPath(edits) {
9153
8969
  if (edits.length === 0) return void 0;
9154
8970
  const first = edits[0];
@@ -9248,7 +9064,7 @@ function registerEditTool(_rootCtx, agentCtx, io, sandbox) {
9248
9064
  }
9249
9065
 
9250
9066
  //#endregion
9251
- //#region src/url-schema/vendored/hashline/tool-undo.js
9067
+ //#region src/url-schemes/vendored/hashline/tool-undo.js
9252
9068
  /**
9253
9069
  * Register the `undo_last_edit` tool on the calling agent's scope.
9254
9070
  * @param _rootCtx - host context.
@@ -9348,7 +9164,7 @@ function registerUndoTool(_rootCtx, agentCtx, io, sandbox) {
9348
9164
  }
9349
9165
 
9350
9166
  //#endregion
9351
- //#region src/url-schema/vendored/hashline/guidance/parse.js
9167
+ //#region src/url-schemes/vendored/hashline/guidance/parse.js
9352
9168
  /**
9353
9169
  * Pure parser for per-preset guidance override files.
9354
9170
  *
@@ -9419,7 +9235,7 @@ function isBlankOverride(content) {
9419
9235
  }
9420
9236
 
9421
9237
  //#endregion
9422
- //#region src/url-schema/vendored/hashline/guidance/resolve.js
9238
+ //#region src/url-schemes/vendored/hashline/guidance/resolve.js
9423
9239
  /**
9424
9240
  * Render one tool's guidance as its intro line, a blank line, then bullets.
9425
9241
  * Uniform across the four sections: no tool-schema description is duplicated
@@ -9517,7 +9333,7 @@ async function composeSections(presetId, homeDir) {
9517
9333
  }
9518
9334
 
9519
9335
  //#endregion
9520
- //#region src/url-schema/vendored/hashline/guidance/materialize.js
9336
+ //#region src/url-schemes/vendored/hashline/guidance/materialize.js
9521
9337
  /** The presets shipped by the harness, each seeded with editable guidance. */
9522
9338
  const DEFAULT_PRESETS = [
9523
9339
  "standard",
@@ -9693,9 +9509,9 @@ async function ensurePresetGuidance(homeDir) {
9693
9509
  }
9694
9510
 
9695
9511
  //#endregion
9696
- //#region src/url-schema/index.ts
9512
+ //#region src/url-schemes/index.ts
9697
9513
  /** Cordis plugin name. */
9698
- const name$1 = "dsh-url-schema";
9514
+ const name$1 = "dsh-url-schemes";
9699
9515
  /**
9700
9516
  * Required host-plane services — including the live agent registry
9701
9517
  * (`ctx.agents`, mounted by the `dsh-agent` row of `dsh-base`), which powers
@@ -9713,6 +9529,13 @@ const inject$1 = [
9713
9529
  "agents",
9714
9530
  "sessionPersistence"
9715
9531
  ];
9532
+ /** Every key defaults on — the service is opt-out, not opt-in. */
9533
+ function resolveGates(config) {
9534
+ return {
9535
+ urlSchemes: config?.urlSchemes !== false,
9536
+ hashline: config?.hashline !== false
9537
+ };
9538
+ }
9716
9539
  /** Register the four URL-aware tools on one agent's own scope layer. */
9717
9540
  /**
9718
9541
  * The lsp feedback loop (native-tools Wave3): both hooks route through the
@@ -9765,25 +9588,29 @@ function buildLspWriteFeedback() {
9765
9588
  postWrite: (filePath, content) => lspFeedback("diagnostics", filePath, content)
9766
9589
  };
9767
9590
  }
9768
- function installAgentTools(rootCtx, agent, resolver) {
9591
+ function installAgentTools(rootCtx, agent, resolver, gates) {
9769
9592
  agent.ctx.effect(async () => {
9770
9593
  const native = captureNativeTools(rootCtx, agent);
9771
9594
  const disposers = [];
9772
9595
  const hashlineSandbox = new FsSandboxController(rootCtx);
9773
- disposers.push(agent.ctx.tools.register(createReadTool({
9596
+ const hashlineIo = ctxFsIO(rootCtx.fs, rootCtx);
9597
+ if (gates.hashline) disposers.push(agent.ctx.tools.register(createReadTool({
9774
9598
  resolver,
9775
9599
  fs: rootCtx.fs,
9776
- ctx: rootCtx
9600
+ ctx: rootCtx,
9601
+ gates,
9602
+ capturedRead: native.read
9777
9603
  })));
9778
- disposers.push(agent.ctx.tools.register(createWriteTool({
9604
+ if (gates.urlSchemes) disposers.push(agent.ctx.tools.register(createWriteTool({
9779
9605
  nativeWrite: native.write,
9780
9606
  sandbox: hashlineSandbox,
9781
9607
  ...buildLspWriteFeedback()
9782
9608
  })));
9783
- const hashlineIo = ctxFsIO(rootCtx.fs, rootCtx);
9784
- disposers.push(registerEditTool(rootCtx, agent.ctx, hashlineIo, hashlineSandbox));
9785
- disposers.push(registerUndoTool(rootCtx, agent.ctx, hashlineIo, hashlineSandbox));
9786
- {
9609
+ if (gates.hashline) {
9610
+ disposers.push(registerEditTool(rootCtx, agent.ctx, hashlineIo, hashlineSandbox));
9611
+ disposers.push(registerUndoTool(rootCtx, agent.ctx, hashlineIo, hashlineSandbox));
9612
+ }
9613
+ if (gates.hashline) {
9787
9614
  const diagnosticsHook = buildLspWriteFeedback().postWrite;
9788
9615
  disposers.push(agent.ctx.on("tools/post-execute", async (exec, result, next) => {
9789
9616
  const decision = await next();
@@ -9822,15 +9649,19 @@ function installAgentTools(rootCtx, agent, resolver) {
9822
9649
  }));
9823
9650
  } catch {}
9824
9651
  for (const section of sections) disposers.push(agent.ctx.systemPrompt.section(section));
9652
+ const general = generalSection(gates);
9653
+ if (general !== void 0) disposers.push(agent.ctx.systemPrompt.section(general));
9825
9654
  } catch {}
9826
- disposers.push(agent.ctx.tools.register(createGrepTool({
9827
- resolver,
9828
- nativeGrep: native.grep
9829
- })));
9830
- disposers.push(agent.ctx.tools.register(createGlobTool({
9831
- resolver,
9832
- nativeGlob: native.glob
9833
- })));
9655
+ if (gates.urlSchemes) {
9656
+ disposers.push(agent.ctx.tools.register(createGrepTool({
9657
+ resolver,
9658
+ nativeGrep: native.grep
9659
+ })));
9660
+ disposers.push(agent.ctx.tools.register(createGlobTool({
9661
+ resolver,
9662
+ nativeGlob: native.glob
9663
+ })));
9664
+ }
9834
9665
  return () => {
9835
9666
  for (const dispose of disposers) dispose();
9836
9667
  };
@@ -9841,6 +9672,11 @@ function apply$1(ctx, config) {
9841
9672
  initHasher().catch(() => {});
9842
9673
  ensurePresetGuidance(configDir()).catch(() => {});
9843
9674
  const resolver = new UrlResolver();
9675
+ if (resolveGates(config).urlSchemes && ctx.fs) try {
9676
+ wrapFsWithSchemes(ctx.fs, { settings: ctx.settings });
9677
+ } catch (error) {
9678
+ ctx.logger("dsh-url-schemes").warn(`fs scheme wrap failed; running on the stock filesystem service: ${error instanceof Error ? error.message : String(error)}`);
9679
+ }
9844
9680
  resolver.register("skill", createSkillHandler({
9845
9681
  skills: ctx.skills,
9846
9682
  fs: ctx.fs
@@ -9856,7 +9692,7 @@ function apply$1(ctx, config) {
9856
9692
  docsDir: resolveDocsDir()
9857
9693
  }));
9858
9694
  resolver.register("dvc", createDvcHandler());
9859
- resolver.register("ctx", createCtxHandler());
9695
+ resolver.register("ctx", createCtxHandler({ sessionPersistence: ctx.sessionPersistence }));
9860
9696
  registerAstDevices();
9861
9697
  registerBrowserDevice();
9862
9698
  installLspDevices();
@@ -9867,13 +9703,13 @@ function apply$1(ctx, config) {
9867
9703
  if (registered.has(agent)) return;
9868
9704
  registered.add(agent);
9869
9705
  try {
9870
- installAgentTools(ctx, agent, resolver);
9706
+ installAgentTools(ctx, agent, resolver, resolveGates(config));
9871
9707
  } catch (error) {
9872
- ctx.logger("dsh-url-schema").warn(`failed to install URL-aware tools for agent ${agent.id}: ${error instanceof Error ? error.message : String(error)}`);
9708
+ ctx.logger("dsh-url-schemes").warn(`failed to install URL-aware tools for agent ${agent.id}: ${error instanceof Error ? error.message : String(error)}`);
9873
9709
  }
9874
9710
  });
9875
9711
  }
9876
- var url_schema_default = {
9712
+ var url_schemes_default = {
9877
9713
  name: name$1,
9878
9714
  inject: inject$1,
9879
9715
  apply: apply$1
@@ -10982,6 +10818,8 @@ const MOBILE_CONFIG = z.object({
10982
10818
  /** Runtime schema. */
10983
10819
  const Config = z.intersect([DashrRuntime.Config, z.object({
10984
10820
  maxParallelSubCalls: z.natural().min(1).default(10),
10821
+ urlSchemes: z.boolean().default(true),
10822
+ hashline: z.boolean().default(true),
10985
10823
  trustedPageAuthorities: z.array(String).default(deriveDefaultPageAuthorities(process.env.DSH_TRUSTED_HOSTS)),
10986
10824
  mobile: MOBILE_CONFIG
10987
10825
  })]);
@@ -11003,12 +10841,16 @@ const EVAL_NAME = "eval";
11003
10841
  * (`modelSelectionSettings: true`) lands on the agent's OWN layer, outside
11004
10842
  * registry restriction reach, so it stays visible on every surface and the
11005
10843
  * control prompt annotates it as an alias of the `agent` delegation tool.
10844
+ * `skill` was REMOVED from this list (2026-09-12, user ruling): the native
10845
+ * skill tool stays visible and self-surfaces its session catalog
10846
+ * (`<available_skills>` is gated on the tool's registry visibility — masking
10847
+ * the tool silenced the catalog). dashr's dual pipeline keeps `read
10848
+ * skill://<name>` as the addressing form alongside native invocation.
11006
10849
  * own URL wrappers) is exempt from restrictions by construction — the
11007
10850
  * capability it carries stays reachable through the captured-definition
11008
10851
  * bridges below, never through the masked name.
11009
10852
  */
11010
10853
  const MASKED_TOOL_NAMES = new Set([
11011
- "skill",
11012
10854
  "send_message",
11013
10855
  "report",
11014
10856
  "list_agents",
@@ -11547,7 +11389,7 @@ function pickRuntimeConfig(config) {
11547
11389
  }
11548
11390
  function apply(ctx, config) {
11549
11391
  ctx.plugin(DashrRuntime, pickRuntimeConfig(config));
11550
- ctx.plugin(url_schema_default, config);
11392
+ ctx.plugin(url_schemes_default, config);
11551
11393
  installFailover(ctx);
11552
11394
  installWebTrust(ctx, config);
11553
11395
  const logger = ctx.logger("dashr-repl");