@hy-sde-org/dsh-tool-fs-internal-urls 0.1.1-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +37 -0
  2. package/README.md +56 -0
  3. package/dist/diff.d.ts +38 -0
  4. package/dist/diff.d.ts.map +1 -0
  5. package/dist/diff.js +71 -0
  6. package/dist/diff.js.map +1 -0
  7. package/dist/edit.d.ts +43 -0
  8. package/dist/edit.d.ts.map +1 -0
  9. package/dist/edit.js +137 -0
  10. package/dist/edit.js.map +1 -0
  11. package/dist/error.d.ts +20 -0
  12. package/dist/error.d.ts.map +1 -0
  13. package/dist/error.js +33 -0
  14. package/dist/error.js.map +1 -0
  15. package/dist/index.d.ts +29 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +59 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/internal-routing.d.ts +61 -0
  20. package/dist/internal-routing.d.ts.map +1 -0
  21. package/dist/internal-routing.js +95 -0
  22. package/dist/internal-routing.js.map +1 -0
  23. package/dist/invariant.d.ts +16 -0
  24. package/dist/invariant.d.ts.map +1 -0
  25. package/dist/invariant.js +22 -0
  26. package/dist/invariant.js.map +1 -0
  27. package/dist/read-image.d.ts +71 -0
  28. package/dist/read-image.d.ts.map +1 -0
  29. package/dist/read-image.js +240 -0
  30. package/dist/read-image.js.map +1 -0
  31. package/dist/read-render.d.ts +113 -0
  32. package/dist/read-render.d.ts.map +1 -0
  33. package/dist/read-render.js +196 -0
  34. package/dist/read-render.js.map +1 -0
  35. package/dist/read-target.d.ts +19 -0
  36. package/dist/read-target.d.ts.map +1 -0
  37. package/dist/read-target.js +26 -0
  38. package/dist/read-target.js.map +1 -0
  39. package/dist/read.d.ts +49 -0
  40. package/dist/read.d.ts.map +1 -0
  41. package/dist/read.js +205 -0
  42. package/dist/read.js.map +1 -0
  43. package/dist/sandbox.d.ts +81 -0
  44. package/dist/sandbox.d.ts.map +1 -0
  45. package/dist/sandbox.js +110 -0
  46. package/dist/sandbox.js.map +1 -0
  47. package/dist/session-cwd.d.ts +30 -0
  48. package/dist/session-cwd.d.ts.map +1 -0
  49. package/dist/session-cwd.js +39 -0
  50. package/dist/session-cwd.js.map +1 -0
  51. package/dist/write.d.ts +36 -0
  52. package/dist/write.d.ts.map +1 -0
  53. package/dist/write.js +152 -0
  54. package/dist/write.js.map +1 -0
  55. package/package.json +86 -0
package/LICENSE ADDED
@@ -0,0 +1,37 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hy-sde
4
+
5
+ This repository ports the FS-shaped internal-URL system (conflict://, issue://,
6
+ pr:// — one resolver registry behind read/grep/write) from oh-my-pi's
7
+ coding-agent internal-urls and conflict-detect modules, adapted to the
8
+ DeepSeek Harness file-services seam. Ported portions are distributed under the
9
+ same MIT license with their original copyright held by their respective
10
+ authors:
11
+
12
+ Copyright (c) 2025 Mario Zechner
13
+ Copyright (c) 2025-2026 Can Bölük
14
+
15
+ See THIRD-PARTY-NOTICES.md for full provenance per module.
16
+
17
+ Parts of this repository are derived from DeepSeek Harness
18
+ (https://github.com/deepseek-ai/deepseek-harness), MIT License,
19
+ Copyright (c) 2026 DeepSeek.
20
+
21
+ Permission is hereby granted, free of charge, to any person obtaining a copy
22
+ of this software and associated documentation files (the "Software"), to deal
23
+ in the Software without restriction, including without limitation the rights
24
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25
+ copies of the Software, and to permit persons to whom the Software is
26
+ furnished to do so, subject to the following conditions:
27
+
28
+ The above copyright notice and this permission notice shall be included in all
29
+ copies or substantial portions of the Software.
30
+
31
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # @hy-sde-org/dsh-tool-fs-internal-urls
2
+
3
+ The `read` / `write` / `edit` filesystem tool suite for DeepSeek Harness with
4
+ **internal-URL routing built in**: when `ctx.internalUrls` is mounted, these
5
+ tools resolve `conflict://`, `pr://`, `issue://` URLs (and `<path>:conflicts`
6
+ selectors) through the registry instead of a filesystem path, and scan plain
7
+ filesystem reads for git conflict blocks — registering them with the session
8
+ history and appending a resolution notice. This is the hy-sde fork's
9
+ `dsh-tool-fs` (with `src/internal-routing.ts` + the `read.ts`/`write.ts`
10
+ hunks) shipped as an agent-scope shadow so it works on **stock** DeepSeek
11
+ Harness releases (`dsh-v0.1.1-rc.2` and later).
12
+
13
+ Mount it in an agent preset (see `examples/agent-preset/` in
14
+ `@hy-sde-org/dsh-internal-urls`): agent-scope shadowing makes these THE
15
+ `read`/`write`/`edit` for that session, while plain filesystem behavior is
16
+ identical to the stock tools. Without a mounted `ctx.internalUrls` registry
17
+ the routing branch never triggers and the tools are stock-equivalent.
18
+
19
+ - `read` — line-numbered windows (caps: `readLimit` 2000 lines,
20
+ `readMaxLineLength` 2000 chars, `readMaxBytes` 50 KiB, stream threshold
21
+ 10 MiB), `fs/observed` emission, internal-URL virtual reads, conflict
22
+ surfacing + notice.
23
+ - `write` — create/overwrite with sandbox-policy escalation
24
+ (`fs/write-intent` waterfall, error remediation). Internal-URL writes
25
+ (`conflict://<N>` resolution) dispatch to the handler with the session
26
+ context and the resolved policy so the backing-file splice stays fenced.
27
+ - `edit` — literal single-match (or replace-all) edits over `ctx.fs` with
28
+ guard + remediation, unchanged from the harness (this package keeps the
29
+ trio together).
30
+ - `read_image` — registers only while `attachments` is mounted, unchanged.
31
+
32
+ ## Install
33
+
34
+ As a routing surface this package is useless without
35
+ `@hy-sde-org/dsh-internal-urls`; follow that package's README — all three
36
+ packages install together:
37
+
38
+ ```bash
39
+ dsh plugin --profile web add @hy-sde-org/dsh-internal-urls \
40
+ @hy-sde-org/dsh-tool-fs-internal-urls \
41
+ @hy-sde-org/dsh-tool-fs-search-internal-urls
42
+ ```
43
+
44
+ Then copy the preset from `packages/internal-urls/examples/agent-preset/` in
45
+ this repo (or the installed package) to `~/.dsh/.agent-presets/<id>/` — its
46
+ `tool-fs-internal-urls` row is this package.
47
+
48
+ ## Defaults & caps
49
+
50
+ | Key | Default | Meaning |
51
+ |---|---|---|
52
+ | `readLimit` | `2000` | lines per `read` call |
53
+ | `readMaxLineLength` | `2000` | chars per returned line |
54
+ | `readMaxBytes` | `50 KiB` | bytes of selected output |
55
+ | `readStreamMinSize` | `10 MiB` | stream files at/above this size |
56
+ | `enableEdit` | `true` | set `false` to leave `edit` to another provider |
package/dist/diff.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Result-time contextual diff presentation for write and edit. Storage returns before/after
3
+ * text; this model-facing layer derives one three-line-context card per applied hunk.
4
+ * @module @hy-sde-org/dsh-tool-fs-internal-urls/src/diff
5
+ */
6
+ import type { FileDiff } from '@deepseek-ai/dsh-tools';
7
+ /** Context lines shown on each side of an applied hunk. */
8
+ export declare const DIFF_CONTEXT = 3;
9
+ /**
10
+ * The `write`/`edit` tools' private `tool/result` `meta` payload: the applied
11
+ * contextual-diff hunks. Attached opaquely (as `unknown`) on the tool result and
12
+ * persisted with the session log — it must be JSON-serializable (the session
13
+ * validates this at `append`), so `presentResult` reproduces the diff card on
14
+ * replay. The producing tool owns and narrows this opaque shape.
15
+ */
16
+ export type FsDiffMeta = {
17
+ diffs: FileDiff[];
18
+ };
19
+ /**
20
+ * Compute one {@link FileDiff} per hunk between `before` and `after`, each carrying the
21
+ * applied change plus {@link DIFF_CONTEXT} context lines. Pure insertions use `oldText: null`,
22
+ * patch-only no-newline markers are omitted, and scattered replacements remain separate hunks.
23
+ *
24
+ * @param path - the path stamped on every produced diff (the model-facing `file_path`; the
25
+ * bridge relativizes it).
26
+ * @param before - the file text before the change (the backend's LF-normalized diff basis).
27
+ * @param after - the file text after the change, on the same basis.
28
+ * @returns one diff per applied hunk, in file order; empty when the texts are identical.
29
+ */
30
+ export declare function computeHunkDiffs(path: string, before: string, after: string): FileDiff[];
31
+ /**
32
+ * Narrow opaque live or replayed result metadata to non-empty file diffs. Malformed metadata
33
+ * returns `undefined` so presentation can fall back instead of throwing during replay.
34
+ * @param meta - result metadata.
35
+ * @returns validated hunks, or `undefined` for absent or malformed data.
36
+ */
37
+ export declare function diffsFromMeta(meta: unknown): FileDiff[] | undefined;
38
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAEtD,2DAA2D;AAC3D,eAAO,MAAM,YAAY,IAAI,CAAA;AAE7B;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,KAAK,EAAE,QAAQ,EAAE,CAAA;CAAE,CAAA;AAE9C;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,EAAE,CAwBxF;AAWD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,EAAE,GAAG,SAAS,CAKnE"}
package/dist/diff.js ADDED
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Result-time contextual diff presentation for write and edit. Storage returns before/after
3
+ * text; this model-facing layer derives one three-line-context card per applied hunk.
4
+ * @module @hy-sde-org/dsh-tool-fs-internal-urls/src/diff
5
+ */
6
+ import { structuredPatch } from 'diff';
7
+ /** Context lines shown on each side of an applied hunk. */
8
+ export const DIFF_CONTEXT = 3;
9
+ /**
10
+ * Compute one {@link FileDiff} per hunk between `before` and `after`, each carrying the
11
+ * applied change plus {@link DIFF_CONTEXT} context lines. Pure insertions use `oldText: null`,
12
+ * patch-only no-newline markers are omitted, and scattered replacements remain separate hunks.
13
+ *
14
+ * @param path - the path stamped on every produced diff (the model-facing `file_path`; the
15
+ * bridge relativizes it).
16
+ * @param before - the file text before the change (the backend's LF-normalized diff basis).
17
+ * @param after - the file text after the change, on the same basis.
18
+ * @returns one diff per applied hunk, in file order; empty when the texts are identical.
19
+ */
20
+ export function computeHunkDiffs(path, before, after) {
21
+ const patch = structuredPatch('', '', before, after, undefined, undefined, { context: DIFF_CONTEXT });
22
+ const diffs = [];
23
+ for (const hunk of patch.hunks) {
24
+ const oldLines = [];
25
+ const newLines = [];
26
+ for (const line of hunk.lines) {
27
+ // The unified-diff marker for a missing trailing newline annotates the
28
+ // patch, not the content — skip it so it never leaks into a diff block.
29
+ if (line.startsWith('\\'))
30
+ continue;
31
+ const text = line.slice(1);
32
+ if (line.startsWith('-')) {
33
+ oldLines.push(text);
34
+ }
35
+ else if (line.startsWith('+')) {
36
+ newLines.push(text);
37
+ }
38
+ else {
39
+ // A context (unchanged) line appears on both sides.
40
+ oldLines.push(text);
41
+ newLines.push(text);
42
+ }
43
+ }
44
+ diffs.push({ path, oldText: oldLines.length > 0 ? oldLines.join('\n') : null, newText: newLines.join('\n') });
45
+ }
46
+ return diffs;
47
+ }
48
+ /** Whether `value` is a valid {@link FileDiff} (defensive narrowing from opaque `meta`). */
49
+ function isFileDiff(value) {
50
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
51
+ return false;
52
+ const { path, oldText, newText } = value;
53
+ return typeof path === 'string'
54
+ && (oldText === null || typeof oldText === 'string')
55
+ && typeof newText === 'string';
56
+ }
57
+ /**
58
+ * Narrow opaque live or replayed result metadata to non-empty file diffs. Malformed metadata
59
+ * returns `undefined` so presentation can fall back instead of throwing during replay.
60
+ * @param meta - result metadata.
61
+ * @returns validated hunks, or `undefined` for absent or malformed data.
62
+ */
63
+ export function diffsFromMeta(meta) {
64
+ if (typeof meta !== 'object' || meta === null || Array.isArray(meta))
65
+ return undefined;
66
+ const diffs = meta.diffs;
67
+ if (!Array.isArray(diffs) || diffs.length === 0 || !diffs.every(isFileDiff))
68
+ return undefined;
69
+ return diffs;
70
+ }
71
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAGtC,2DAA2D;AAC3D,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAA;AAW7B;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,MAAc,EAAE,KAAa;IAC1E,MAAM,KAAK,GAAG,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAA;IACrG,MAAM,KAAK,GAAe,EAAE,CAAA;IAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAa,EAAE,CAAA;QAC7B,MAAM,QAAQ,GAAa,EAAE,CAAA;QAC7B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,uEAAuE;YACvE,wEAAwE;YACxE,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,SAAQ;YACnC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YAC1B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACzB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACrB,CAAC;iBAAM,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACrB,CAAC;iBAAM,CAAC;gBACN,oDAAoD;gBACpD,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBACnB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACrB,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC/G,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,4FAA4F;AAC5F,SAAS,UAAU,CAAC,KAAc;IAChC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACrF,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,KAAgC,CAAA;IACnE,OAAO,OAAO,IAAI,KAAK,QAAQ;WAC1B,CAAC,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,CAAC;WACjD,OAAO,OAAO,KAAK,QAAQ,CAAA;AAClC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,IAAa;IACzC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAA;IACtF,MAAM,KAAK,GAAI,IAAgC,CAAC,KAAK,CAAA;IACrD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC;QAAE,OAAO,SAAS,CAAA;IAC7F,OAAO,KAAK,CAAA;AACd,CAAC"}
package/dist/edit.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Model-facing literal edit, unique-match by default. It obtains an optional guard from the
3
+ * single intent slot, calls `ctx.fs.editText` without a separate stat, then records the observed
4
+ * version; no policy means an unconditional atomic edit.
5
+ * @module @hy-sde-org/dsh-tool-fs-internal-urls/src/edit
6
+ */
7
+ import type { Context } from '@deepseek-ai/cordis';
8
+ import type { FsSandboxController } from './sandbox.ts';
9
+ /** Validated `edit` arguments after defaulting. */
10
+ interface EditInput {
11
+ filePath: string;
12
+ oldString: string;
13
+ newString: string;
14
+ replaceAll: boolean;
15
+ }
16
+ /**
17
+ * Validate value constraints the schema DSL can't express: a non-blank
18
+ * `file_path`, a non-empty `old_string`, and `old_string !== new_string`
19
+ * (an equal pair would be a guaranteed no-op edit).
20
+ * @param args - the schema-validated raw tool arguments.
21
+ * @returns the camelCased input with `replace_all` defaulted to false.
22
+ */
23
+ export declare function parseEditArgs(args: {
24
+ file_path: string;
25
+ old_string: string;
26
+ new_string: string;
27
+ replace_all?: boolean;
28
+ }): EditInput;
29
+ /**
30
+ * Format an edit success (single-match or replace-all) as a Claude-style model-facing message.
31
+ * @param displayPath - the backend-resolved path shown to the model.
32
+ * @param replaceAll - selects the all-occurrences wording over the single-replacement one.
33
+ * @returns the confirmation sentence the model sees as the tool result.
34
+ */
35
+ export declare function formatEditOutput(displayPath: string, replaceAll: boolean): string;
36
+ /**
37
+ * Register the `edit` tool and its system-prompt guidance.
38
+ * @param ctx - the plugin context; registrations are effects scoped to it, and execution uses its `fs` service.
39
+ * @param sandbox - the shared sandbox-escalation API (advertisement, mode stamping, denial mapping).
40
+ */
41
+ export declare function applyEditTool(ctx: Context, sandbox: FsSandboxController): void;
42
+ export {};
43
+ //# sourceMappingURL=edit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../src/edit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAQlD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAEvD,mDAAmD;AACnD,UAAU,SAAS;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;CACpB;AAgBD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAUnI;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,CAIjF;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI,CA4F9E"}
package/dist/edit.js ADDED
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Model-facing literal edit, unique-match by default. It obtains an optional guard from the
3
+ * single intent slot, calls `ctx.fs.editText` without a separate stat, then records the observed
4
+ * version; no policy means an unconditional atomic edit.
5
+ * @module @hy-sde-org/dsh-tool-fs-internal-urls/src/edit
6
+ */
7
+ import { defineTool } from '@deepseek-ai/dsh-tools';
8
+ import { computeHunkDiffs, diffsFromMeta } from "./diff.js";
9
+ import { remediateFsError } from "./error.js";
10
+ import { sessionResolveOptions } from "./session-cwd.js";
11
+ /**
12
+ * Validate value constraints the schema DSL can't express: a non-blank
13
+ * `file_path`, a non-empty `old_string`, and `old_string !== new_string`
14
+ * (an equal pair would be a guaranteed no-op edit).
15
+ * @param args - the schema-validated raw tool arguments.
16
+ * @returns the camelCased input with `replace_all` defaulted to false.
17
+ */
18
+ export function parseEditArgs(args) {
19
+ if (args.file_path.trim().length === 0)
20
+ throw new Error('file_path must be a non-empty string');
21
+ if (args.old_string.length === 0)
22
+ throw new Error('old_string must be a non-empty string');
23
+ if (args.old_string === args.new_string)
24
+ throw new Error('old_string and new_string must differ');
25
+ return {
26
+ filePath: args.file_path,
27
+ oldString: args.old_string,
28
+ newString: args.new_string,
29
+ replaceAll: args.replace_all ?? false,
30
+ };
31
+ }
32
+ /**
33
+ * Format an edit success (single-match or replace-all) as a Claude-style model-facing message.
34
+ * @param displayPath - the backend-resolved path shown to the model.
35
+ * @param replaceAll - selects the all-occurrences wording over the single-replacement one.
36
+ * @returns the confirmation sentence the model sees as the tool result.
37
+ */
38
+ export function formatEditOutput(displayPath, replaceAll) {
39
+ return replaceAll
40
+ ? `The file ${displayPath} has been updated. All occurrences were successfully replaced.`
41
+ : `The file ${displayPath} has been updated successfully.`;
42
+ }
43
+ /**
44
+ * Register the `edit` tool and its system-prompt guidance.
45
+ * @param ctx - the plugin context; registrations are effects scoped to it, and execution uses its `fs` service.
46
+ * @param sandbox - the shared sandbox-escalation API (advertisement, mode stamping, denial mapping).
47
+ */
48
+ export function applyEditTool(ctx, sandbox) {
49
+ ctx.systemPrompt.section({
50
+ name: 'tool:edit',
51
+ order: 102,
52
+ text: 'Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-observation-policy requires it), unless you just created or edited it in this session.',
53
+ });
54
+ ctx.tools.register(defineTool({
55
+ name: 'edit',
56
+ description: 'Edit an existing UTF-8 text file by replacing literal text.',
57
+ parameters: {
58
+ file_path: { type: 'string', required: true, description: 'Path to edit, resolved by the filesystem backend.' },
59
+ old_string: { type: 'string', required: true, description: 'Literal text to replace. Must match exactly.' },
60
+ new_string: { type: 'string', required: true, description: 'Literal replacement text. Use an empty string to delete the match.' },
61
+ replace_all: { type: 'boolean', description: 'Replace all matches. Defaults to false; when false, old_string must appear exactly once.' },
62
+ ...sandbox.escalationModes.length > 0 ? sandbox.schemaFields() : {},
63
+ },
64
+ output: {
65
+ schema: {
66
+ type: 'object',
67
+ additionalProperties: false,
68
+ properties: {
69
+ path: { type: 'string', required: true },
70
+ before: { type: 'string', required: true },
71
+ after: { type: 'string', required: true },
72
+ },
73
+ },
74
+ render: (args, value) => [{
75
+ type: 'text',
76
+ text: formatEditOutput(value.path, args.replace_all ?? false),
77
+ }],
78
+ presentationMeta: (args, value) => ({
79
+ diffs: computeHunkDiffs(args.file_path, value.before, value.after)
80
+ .map(({ path, oldText, newText }) => ({ path, oldText, newText })),
81
+ }),
82
+ },
83
+ async execute(args, exec) {
84
+ const input = parseEditArgs(args);
85
+ // Resolve the per-call sandbox policy (approved mode > session override
86
+ // > backend default, plus the session cwd root) BEFORE anything executes.
87
+ const sandboxPolicy = await sandbox.resolvePolicy('edit', args, exec);
88
+ const target = await ctx.fs.resolve(input.filePath, sessionResolveOptions(exec, input.filePath, sandboxPolicy?.workspaceRoot));
89
+ // Single-slot decision: the policy plugin returns { version: vObserved } or
90
+ // throws FS_NOT_OBSERVED; the bare default is undefined (unconditional edit).
91
+ // No stat — the bare default never manufactures a version basis. The intent
92
+ // slot itself can throw FS_NOT_OBSERVED for an unread target, so it sits
93
+ // inside the try: both that refusal and the provider's guarded-mutation
94
+ // failure get the model-facing remedy below.
95
+ let outcome;
96
+ try {
97
+ const intent = await ctx.waterfall('fs/edit-intent', target, exec, () => undefined);
98
+ outcome = await ctx.fs.editText(target, { oldString: input.oldString, newString: input.newString, replaceAll: input.replaceAll }, intent, exec.signal, sandboxPolicy);
99
+ }
100
+ catch (error) {
101
+ // A sandbox denial becomes the shared [sandbox: …] marker (the model
102
+ // recognizes it from bash); stale/not-observed failures gain their
103
+ // model-facing remedy; anything else passes through.
104
+ throw remediateFsError(sandbox.mapError(error, sandboxPolicy));
105
+ }
106
+ // Record the present observation (a no-op when no policy plugin listens).
107
+ ctx.emit('fs/observed', target, { kind: 'present', version: outcome.version }, exec);
108
+ return {
109
+ path: target.displayPath,
110
+ before: outcome.before,
111
+ after: outcome.after,
112
+ };
113
+ },
114
+ // Pure display: a diff card of the literal replacement (old_string → new_string), derived
115
+ // from the call args. `oldText: old_string || null` matches claude-agent-acp's Edit arm;
116
+ // new_string is a required arg here, so it maps straight to newText.
117
+ presentCall(args) {
118
+ return {
119
+ card: 'diff',
120
+ title: `Edit ${args.file_path}`,
121
+ diffs: [{ path: args.file_path, oldText: args.old_string || null, newText: args.new_string }],
122
+ locations: [{ path: args.file_path }],
123
+ };
124
+ },
125
+ // Applied metadata replaces the call-time snippet; errors or malformed replay metadata use
126
+ // the generic result rendering.
127
+ presentResult(args, result) {
128
+ if (result.isError)
129
+ return undefined;
130
+ const diffs = diffsFromMeta(result.meta);
131
+ if (diffs === undefined)
132
+ return undefined;
133
+ return { card: 'diff', title: `Edit ${args.file_path}`, diffs };
134
+ },
135
+ }));
136
+ }
137
+ //# sourceMappingURL=edit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit.js","sourceRoot":"","sources":["../src/edit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Model-facing remediation for guarded-mutation failures. The provider's
3
+ * `FS_STALE_VERSION` and `FS_NOT_OBSERVED` messages state the condition but
4
+ * not the only correct recovery (re-read / read the file), so this package
5
+ * appends the remedy at the model boundary; provider messages stay
6
+ * machine-oriented and unchanged.
7
+ * @module @hy-sde-org/dsh-tool-fs-internal-urls/src/error
8
+ */
9
+ /**
10
+ * Append the correct recovery instruction to a guarded-mutation failure's
11
+ * message. `FS_STALE_VERSION` (the file changed since this session's last
12
+ * observation, including a missing target) recovers only by re-reading;
13
+ * `FS_NOT_OBSERVED` (no prior read by this session) by reading. The `FsError`
14
+ * code is preserved so retry/permission/UI layers keep routing on it, and the
15
+ * original error chains as `cause`. Anything else passes through untouched.
16
+ * @param error - the caught value from a write/edit execution.
17
+ * @returns a remediated `FsError` for the two guarded-mutation codes, else the original value.
18
+ */
19
+ export declare function remediateFsError(error: unknown): unknown;
20
+ //# sourceMappingURL=error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKxD"}
package/dist/error.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Model-facing remediation for guarded-mutation failures. The provider's
3
+ * `FS_STALE_VERSION` and `FS_NOT_OBSERVED` messages state the condition but
4
+ * not the only correct recovery (re-read / read the file), so this package
5
+ * appends the remedy at the model boundary; provider messages stay
6
+ * machine-oriented and unchanged.
7
+ * @module @hy-sde-org/dsh-tool-fs-internal-urls/src/error
8
+ */
9
+ import { FsError } from '@deepseek-ai/dsh-fs';
10
+ /** The remedy appended to each remediable failure code's message. */
11
+ const REMEDIES = {
12
+ FS_STALE_VERSION: 're-read the file, then retry',
13
+ FS_NOT_OBSERVED: 'read the file, then retry',
14
+ };
15
+ /**
16
+ * Append the correct recovery instruction to a guarded-mutation failure's
17
+ * message. `FS_STALE_VERSION` (the file changed since this session's last
18
+ * observation, including a missing target) recovers only by re-reading;
19
+ * `FS_NOT_OBSERVED` (no prior read by this session) by reading. The `FsError`
20
+ * code is preserved so retry/permission/UI layers keep routing on it, and the
21
+ * original error chains as `cause`. Anything else passes through untouched.
22
+ * @param error - the caught value from a write/edit execution.
23
+ * @returns a remediated `FsError` for the two guarded-mutation codes, else the original value.
24
+ */
25
+ export function remediateFsError(error) {
26
+ if (!(error instanceof FsError))
27
+ return error;
28
+ const remedy = REMEDIES[error.code];
29
+ if (!remedy)
30
+ return error;
31
+ return new FsError(`${error.message} — ${remedy}`, error.code, { cause: error });
32
+ }
33
+ //# sourceMappingURL=error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAG7C,qEAAqE;AACrE,MAAM,QAAQ,GAAyC;IACrD,gBAAgB,EAAE,8BAA8B;IAChD,eAAe,EAAE,2BAA2B;CAC7C,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,CAAC,CAAC,KAAK,YAAY,OAAO,CAAC;QAAE,OAAO,KAAK,CAAA;IAC7C,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACnC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACzB,OAAO,IAAI,OAAO,CAAC,GAAG,KAAK,CAAC,OAAO,MAAM,MAAM,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAA;AAClF,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Model-facing read, read_image, write, and edit tools over `ctx.fs`. This package owns schemas, validation,
3
+ * read windows, formatting, and observation events, never a concrete provider. An optional
4
+ * event policy supplies mutation guards; without one the tools use unconditional provider calls.
5
+ * @module @hy-sde-org/dsh-tool-fs-internal-urls
6
+ */
7
+ import type { Context } from '@deepseek-ai/cordis';
8
+ import z from '@deepseek-ai/schemastery';
9
+ /** Cordis plugin name used by loader diagnostics. */
10
+ export declare const name = "tool-fs";
11
+ /** Services required by the filesystem tool suite. */
12
+ export declare const inject: string[];
13
+ /** Plugin config (all optional — `Config` supplies the defaults). */
14
+ export interface Config {
15
+ /** Default and maximum number of lines returned by one `read` call. */
16
+ readLimit?: number;
17
+ /** Maximum characters returned for a single line before truncation. */
18
+ readMaxLineLength?: number;
19
+ /** Maximum bytes returned for the selected lines of one `read` call. */
20
+ readMaxBytes?: number;
21
+ /** Files at or above this size stream instead of loading whole into memory. */
22
+ readStreamMinSize?: number;
23
+ /** Register the literal `edit` tool. Set false when a rich editor replaces it; default true. */
24
+ enableEdit?: boolean;
25
+ }
26
+ export declare const Config: z<Config>;
27
+ /** Register the full `read`/`write`/`edit` filesystem tool suite, plus `read_image` while `attachments` is mounted. */
28
+ export declare function apply(ctx: Context, config: Config): void;
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,CAAC,MAAM,0BAA0B,CAAA;AASxC,qDAAqD;AACrD,eAAO,MAAM,IAAI,YAAY,CAAA;AAE7B,sDAAsD;AACtD,eAAO,MAAM,MAAM,UAAkC,CAAA;AAErD,qEAAqE;AACrE,MAAM,WAAW,MAAM;IACrB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uEAAuE;IACvE,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,+EAA+E;IAC/E,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,gGAAgG;IAChG,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAM3B,CAAA;AAYF,uHAAuH;AACvH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAyBxD"}
package/dist/index.js ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Model-facing read, read_image, write, and edit tools over `ctx.fs`. This package owns schemas, validation,
3
+ * read windows, formatting, and observation events, never a concrete provider. An optional
4
+ * event policy supplies mutation guards; without one the tools use unconditional provider calls.
5
+ * @module @hy-sde-org/dsh-tool-fs-internal-urls
6
+ */
7
+ import z from '@deepseek-ai/schemastery';
8
+ import { applyReadTool, READ_LIMIT, STREAM_MIN_SIZE } from "./read.js";
9
+ import { applyWriteTool } from "./write.js";
10
+ import { applyEditTool } from "./edit.js";
11
+ import { applyReadImageTool } from "./read-image.js";
12
+ import { READ_MAX_BYTES, READ_MAX_LINE_LENGTH } from "./read-render.js";
13
+ import { FsSandboxController } from "./sandbox.js";
14
+ /** Cordis plugin name used by loader diagnostics. */
15
+ export const name = 'tool-fs';
16
+ /** Services required by the filesystem tool suite. */
17
+ export const inject = ['tools', 'fs', 'systemPrompt'];
18
+ export const Config = z.object({
19
+ readLimit: z.number().default(READ_LIMIT),
20
+ readMaxLineLength: z.number().default(READ_MAX_LINE_LENGTH),
21
+ readMaxBytes: z.number().default(READ_MAX_BYTES),
22
+ readStreamMinSize: z.number().default(STREAM_MIN_SIZE),
23
+ enableEdit: z.boolean().default(true),
24
+ });
25
+ /** Every read cap counts lines/chars/bytes — a positive integer, or windowing arithmetic misbehaves silently. */
26
+ function assertPositiveInteger(name, value) {
27
+ if (!Number.isInteger(value) || value < 1) {
28
+ throw new Error(`tool-fs: ${name} must be a positive integer`);
29
+ }
30
+ }
31
+ /** Register the full `read`/`write`/`edit` filesystem tool suite, plus `read_image` while `attachments` is mounted. */
32
+ export function apply(ctx, config) {
33
+ // schemastery (Config) has already filled every defaulted field.
34
+ const resolved = config;
35
+ assertPositiveInteger('readLimit', resolved.readLimit);
36
+ assertPositiveInteger('readMaxLineLength', resolved.readMaxLineLength);
37
+ assertPositiveInteger('readMaxBytes', resolved.readMaxBytes);
38
+ assertPositiveInteger('readStreamMinSize', resolved.readStreamMinSize);
39
+ applyReadTool(ctx, {
40
+ limit: resolved.readLimit,
41
+ maxLineLength: resolved.readMaxLineLength,
42
+ maxBytes: resolved.readMaxBytes,
43
+ streamMinSize: resolved.readStreamMinSize,
44
+ });
45
+ // read_image is composition-conditional: without a mounted attachment store
46
+ // the deployment cannot durably commit image bytes, so the tool never
47
+ // registers; the execute body keeps a defensive re-check for direct callers.
48
+ ctx.inject(['attachments'], (imageCtx) => {
49
+ applyReadImageTool(imageCtx);
50
+ });
51
+ // One escalation API shared by both mutating tools: advertisement gating,
52
+ // per-call policy resolution, and denial-marker mapping, all keyed off whether
53
+ // the mounted ctx.fs confines (ctx.fs.sandboxMode).
54
+ const sandbox = new FsSandboxController(ctx);
55
+ applyWriteTool(ctx, sandbox);
56
+ if (resolved.enableEdit)
57
+ applyEditTool(ctx, sandbox);
58
+ }
59
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAExC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAElD,qDAAqD;AACrD,MAAM,CAAC,MAAM,IAAI,GAAG,SAAS,CAAA;AAE7B,sDAAsD;AACtD,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,cAAc,CAAC,CAAA;AAgBrD,MAAM,CAAC,MAAM,MAAM,GAAc,CAAC,CAAC,MAAM,CAAC;IACxC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC;IACzC,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,oBAAoB,CAAC;IAC3D,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,cAAc,CAAC;IAChD,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,eAAe,CAAC;IACtD,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;CACtC,CAAC,CAAA;AAKF,iHAAiH;AACjH,SAAS,qBAAqB,CAAC,IAAY,EAAE,KAAa;IACxD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,YAAY,IAAI,6BAA6B,CAAC,CAAA;IAChE,CAAC;AACH,CAAC;AAED,uHAAuH;AACvH,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,MAAc;IAChD,iEAAiE;IACjE,MAAM,QAAQ,GAAG,MAAwB,CAAA;IACzC,qBAAqB,CAAC,WAAW,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAA;IACtD,qBAAqB,CAAC,mBAAmB,EAAE,QAAQ,CAAC,iBAAiB,CAAC,CAAA;IACtE,qBAAqB,CAAC,cAAc,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAA;IAC5D,qBAAqB,CAAC,mBAAmB,EAAE,QAAQ,CAAC,iBAAiB,CAAC,CAAA;IACtE,aAAa,CAAC,GAAG,EAAE;QACjB,KAAK,EAAE,QAAQ,CAAC,SAAS;QACzB,aAAa,EAAE,QAAQ,CAAC,iBAAiB;QACzC,QAAQ,EAAE,QAAQ,CAAC,YAAY;QAC/B,aAAa,EAAE,QAAQ,CAAC,iBAAiB;KAC1C,CAAC,CAAA;IACF,4EAA4E;IAC5E,sEAAsE;IACtE,6EAA6E;IAC7E,GAAG,CAAC,MAAM,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,EAAE;QACvC,kBAAkB,CAAC,QAAQ,CAAC,CAAA;IAC9B,CAAC,CAAC,CAAA;IACF,0EAA0E;IAC1E,+EAA+E;IAC/E,oDAAoD;IACpD,MAAM,OAAO,GAAG,IAAI,mBAAmB,CAAC,GAAG,CAAC,CAAA;IAC5C,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAC5B,IAAI,QAAQ,CAAC,UAAU;QAAE,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;AACtD,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The read tool's internal-URL routing: when `ctx.internalUrls` is mounted and
3
+ * the requested path is a handled `scheme://` URL (or a `<path>:conflicts`
4
+ * selector), the read resolves through the registry instead of the filesystem,
5
+ * renders the virtual content as a line-numbered window, and — for regular
6
+ * filesystem reads — scans the surfaced lines for git conflict blocks,
7
+ * registering them with the session history and appending a resolution notice.
8
+ * @module @hy-sde-org/dsh-tool-fs-internal-urls/internal-routing
9
+ */
10
+ import type { Context } from '@deepseek-ai/cordis';
11
+ import type { FsTarget } from '@deepseek-ai/dsh-fs';
12
+ import type { InternalUrlsService } from '@hy-sde-org/dsh-internal-urls';
13
+ import type { ToolExecution } from '@deepseek-ai/dsh-tools';
14
+ import type { FileTextLine } from './read-render.ts';
15
+ /** The subset of read caps the routing needs to build a bounded window. */
16
+ export interface InternalReadCaps {
17
+ maxLineLength: number;
18
+ maxBytes: number;
19
+ }
20
+ /** Tool-owned window request (validated read args). */
21
+ export interface ReadRequest {
22
+ filePath: string;
23
+ offset: number;
24
+ limit: number;
25
+ }
26
+ /** A fully-formed read outcome, virtual or filesystem-backed. */
27
+ export interface InternalReadOutcome {
28
+ /** Model-facing path (the internal URL for virtual reads). */
29
+ path: string;
30
+ offset: number;
31
+ lines: FileTextLine[];
32
+ totalLines: number;
33
+ /** Optional conflict-resolution notice, rendered after the file body. */
34
+ notice?: string;
35
+ }
36
+ /** The calling agent's session id, used to key session-scoped handler state. */
37
+ export declare function sessionKeyOf(exec: ToolExecution): string | undefined;
38
+ /** Build a {@link ResolveContext} for the calling tool execution. */
39
+ export declare function resolveContextOf(exec: ToolExecution, path: string): {
40
+ cwd?: string;
41
+ signal: AbortSignal;
42
+ sessionKey?: string;
43
+ };
44
+ /**
45
+ * Resolve a scheme URL (or `<path>:conflicts` selector) into a virtual window.
46
+ * Returns `undefined` when the path is not an internal URL, so the caller
47
+ * falls through to the filesystem path untouched.
48
+ */
49
+ export declare function tryReadInternal(ctx: Context, iu: InternalUrlsService, exec: ToolExecution, request: ReadRequest, caps: InternalReadCaps): Promise<InternalReadOutcome | undefined>;
50
+ /**
51
+ * Scan a completed filesystem read's window for conflict blocks, register them
52
+ * with the calling session's history (id reuse by path+start line), and render
53
+ * the resolution notice. Returns `undefined` when nothing to report or no
54
+ * session key / registry are available.
55
+ */
56
+ export declare function conflictNoticeForRead(ctx: Context, iu: InternalUrlsService, exec: ToolExecution, target: FsTarget, outcome: {
57
+ path: string;
58
+ offset: number;
59
+ lines: FileTextLine[];
60
+ }): string | undefined;
61
+ //# sourceMappingURL=internal-routing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-routing.d.ts","sourceRoot":"","sources":["../src/internal-routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAQxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAE3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAGpD,2EAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,uDAAuD;AACvD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;CACd;AAED,iEAAiE;AACjE,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,YAAY,EAAE,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAEpE;AAED,qEAAqE;AACrE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAQ9H;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,OAAO,EACZ,EAAE,EAAE,mBAAmB,EACvB,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAmB1C;AAqCD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,EAAE,EAAE,mBAAmB,EACvB,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,QAAQ,EAChB,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,YAAY,EAAE,CAAA;CAAE,GAC/D,MAAM,GAAG,SAAS,CAapB"}