better-dsh 0.2.3-c → 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 (25) 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/eval-description.md +33 -0
  12. package/lib/client/index.js +1 -1
  13. package/lib/fs-aware/sandbox-plugin.d.ts +71 -0
  14. package/lib/fs-aware/sandbox-plugin.js +249 -0
  15. package/lib/index.d.ts +11 -18
  16. package/lib/index.js +555 -757
  17. package/lib/py-sdk-Chvy92MB.js +178 -0
  18. package/lib/py-sdk.d.ts +19 -2
  19. package/lib/py-sdk.js +2 -2
  20. package/lib/wrap-DC8O3SYz.js +721 -0
  21. package/package.json +7 -2
  22. package/url-schemes-instruction.md +22 -0
  23. package/control-prompt.md +0 -37
  24. package/lib/py-sdk-BCaOGYz7.d.ts +0 -125
  25. package/lib/py-sdk-CbgYiX8O.js +0 -691
@@ -0,0 +1,249 @@
1
+ import { f as UrlSchemesError, t as buildFsLayerResolver } from "../wrap-DC8O3SYz.js";
2
+ import { Context } from "@deepseek-ai/cordis";
3
+ import { dirname, sep } from "node:path";
4
+ import { stat } from "node:fs/promises";
5
+ import { FsError } from "@deepseek-ai/dsh-fs";
6
+ import { writableRoots } from "@deepseek-ai/dsh-sandbox";
7
+ import { LocalFileSystem } from "@deepseek-ai/dsh-fs-local";
8
+
9
+ //#region node_modules/@deepseek-ai/dsh-fs-sandbox/lib/index.js
10
+ /**
11
+ * Path-containment mechanics for the filesystem sandbox. Canonical spellings
12
+ * take the fast lexical path; filesystem identity supplies the conservative
13
+ * fallback for alias-equivalent roots such as Windows 8.3 names and casing.
14
+ * @module @deepseek-ai/dsh-fs-sandbox/containment
15
+ */
16
+ const MISSING_CODES = new Set(["ENOENT", "ENOTDIR"]);
17
+ function isMissing(error) {
18
+ const code = error.code;
19
+ return MISSING_CODES.has(code);
20
+ }
21
+ function comparablePath(path, caseSensitive) {
22
+ return caseSensitive ? path : path.toLowerCase();
23
+ }
24
+ function isLexicallyUnder(path, root, caseSensitive) {
25
+ const comparableTarget = comparablePath(path, caseSensitive);
26
+ const comparableRoot = comparablePath(root, caseSensitive);
27
+ if (comparableTarget === comparableRoot) return true;
28
+ const prefix = comparableRoot.endsWith(sep) ? comparableRoot : comparableRoot + sep;
29
+ return comparableTarget.startsWith(prefix);
30
+ }
31
+ async function statIfPresent(path) {
32
+ try {
33
+ return await stat(path, { bigint: true });
34
+ } catch (error) {
35
+ /* v8 ignore else -- a non-missing stat failure requires a host permission or I/O fault after resolve reached this ancestor. */
36
+ if (isMissing(error)) return void 0;
37
+ /* v8 ignore next -- requires a host permission or I/O fault after resolve already reached this ancestor. */
38
+ throw error;
39
+ }
40
+ }
41
+ function sameIdentity(left, right) {
42
+ return left.dev === right.dev && left.ino === right.ino;
43
+ }
44
+ /**
45
+ * Determine whether a canonical target is a writable root or lies beneath it.
46
+ * The lexical fast path handles normal canonical spellings. When spellings
47
+ * differ, walk the target's existing ancestors and compare filesystem identity
48
+ * with the root; this recognizes Windows long-name/8.3 aliases and casing
49
+ * without weakening containment to a textual approximation.
50
+ * @param path - canonical target key, which may end in a missing suffix.
51
+ * @param root - canonical writable root.
52
+ * @param caseSensitive - whether lexical comparison preserves case; defaults
53
+ * to the host filesystem convention used by supported platforms.
54
+ * @returns whether the target is the root or a descendant of it.
55
+ */
56
+ async function isPathUnder(path, root, caseSensitive = process.platform !== "win32") {
57
+ if (isLexicallyUnder(path, root, caseSensitive)) return true;
58
+ const rootInfo = await statIfPresent(root);
59
+ if (!rootInfo) return false;
60
+ let ancestor = path;
61
+ while (true) {
62
+ const ancestorInfo = await statIfPresent(ancestor);
63
+ if (ancestorInfo && sameIdentity(ancestorInfo, rootInfo)) return true;
64
+ const parent = dirname(ancestor);
65
+ if (parent === ancestor) return false;
66
+ ancestor = parent;
67
+ }
68
+ }
69
+ /**
70
+ * `SandboxedFileSystem`: the sandbox-enforcing implementation of the
71
+ * `@deepseek-ai/dsh-fs` Service Definition. It extends `LocalFileSystem` so all
72
+ * text-storage mechanics — resolve, stat, read/stream, list, the atomic
73
+ * write and the read-match-write edit critical section — are the local
74
+ * implementation's, verbatim; this package adds only the per-call POLICY fence
75
+ * on the two mutations. Reads pass through untouched: every mode permits
76
+ * reading.
77
+ *
78
+ * The fence is a policy check in TRUSTED code over a MODEL-CONTROLLED path,
79
+ * NOT a kernel boundary — the operations are the seam's own (open, rename),
80
+ * and only the target path is untrusted, so canonicalize-then-contain is the
81
+ * complete answer to this surface. This is containment, not a security
82
+ * boundary; kernel-grade isolation of untrusted CODE stays `ctx.shell`'s job
83
+ * (`@deepseek-ai/dsh-bash-sandbox`). The residual
84
+ * TOCTOU (an ancestor symlink swapped between the containment re-check and the
85
+ * syscall) is narrowed by re-canonicalizing immediately before delegating and
86
+ * is accepted for this threat model.
87
+ *
88
+ * Per-call policy: `read-only` denies every mutation; `workspace-write` allows
89
+ * a mutation only when the target canonicalizes under the policy's workspace
90
+ * root or a platform temp area from the shared `writableRoots` policy;
91
+ * `danger-full-access` delegates unfenced. A denial throws the structured
92
+ * `FS_SANDBOX_DENIED`.
93
+ *
94
+ * @module @deepseek-ai/dsh-fs-sandbox
95
+ */
96
+ /**
97
+ * Sandbox-enforcing filesystem backend. Registers as `ctx.fs` (loading it
98
+ * INSTEAD OF `dsh-fs-local`, together with a `ctx.sandboxPolicy`, is the whole
99
+ * swap — the model-facing tools are untouched). Its configured default mode is
100
+ * the capability fact exposed by {@link sandboxMode}; `dsh-tool-fs` resolves
101
+ * each session's mode and cwd into a policy for every mutation, while an
102
+ * approved escalation may stamp a strictly wider mode for one call.
103
+ */
104
+ var SandboxedFileSystem = class extends LocalFileSystem {
105
+ static inject = ["sandboxPolicy"];
106
+ defaultMode;
107
+ constructor(ctx, config) {
108
+ super(ctx, config);
109
+ this.defaultMode = ctx.sandboxPolicy.defaultMode;
110
+ }
111
+ /** The deployment default mode — the capability fact the tool layer reads to advertise escalation. */
112
+ get sandboxMode() {
113
+ return this.defaultMode;
114
+ }
115
+ /**
116
+ * Fence the write by the per-call policy, then delegate to the inherited
117
+ * atomic write. See {@link checkedTarget}.
118
+ * @param target - the resolved target to write.
119
+ * @param content - the full new file content.
120
+ * @param expected - the write intent guarding the write; omit for unconditional.
121
+ * @param signal - aborts before atomic publication takes effect.
122
+ * @param sandboxPolicy - the per-call mode and workspace root; omit to use
123
+ * the deployment fallback.
124
+ * @returns the write outcome from the inherited backend.
125
+ */
126
+ async writeText(target, content, expected, signal, sandboxPolicy) {
127
+ return super.writeText(await this.checkedTarget(target, sandboxPolicy), content, expected, signal);
128
+ }
129
+ /**
130
+ * Fence the edit by the per-call policy, then delegate to the inherited
131
+ * atomic edit. See {@link checkedTarget}.
132
+ * @param target - the resolved target to edit.
133
+ * @param edit - the literal search/replace request.
134
+ * @param expected - the version guard; omit for an unconditional edit.
135
+ * @param signal - aborts before atomic publication takes effect.
136
+ * @param sandboxPolicy - the per-call mode and workspace root; omit to use
137
+ * the deployment fallback.
138
+ * @returns the edit outcome from the inherited backend.
139
+ */
140
+ async editText(target, edit, expected, signal, sandboxPolicy) {
141
+ return super.editText(await this.checkedTarget(target, sandboxPolicy), edit, expected, signal);
142
+ }
143
+ /**
144
+ * Enforce the per-call policy against `target` and return the EXACT target the
145
+ * mutation must use, so the checked identity is the mutated one (no
146
+ * check-here-write-there TOCTOU). `read-only` denies; `workspace-write`
147
+ * re-canonicalizes NOW (`resolve` realpaths the deepest existing ancestor,
148
+ * reflecting a concurrently swapped symlink), requires containment under a
149
+ * writable root, and returns THAT fresh target; `danger-full-access` returns
150
+ * the caller's target unfenced. Throws the structured `FS_SANDBOX_DENIED` on
151
+ * refusal — the tool layer maps it to the model-facing `[sandbox: …]` marker
152
+ * and the escalation hint.
153
+ */
154
+ async checkedTarget(target, sandboxPolicy) {
155
+ const policy = sandboxPolicy ?? this.ctx.sandboxPolicy.resolve();
156
+ const { mode } = policy;
157
+ if (mode === "danger-full-access") return target;
158
+ if (mode === "read-only") throw new FsError(`cannot write "${target.displayPath}": file access denied under read-only mode`, "FS_SANDBOX_DENIED");
159
+ const fresh = await this.resolve(target.displayPath);
160
+ let contained = false;
161
+ for (const root of writableRoots(policy)) if (await isPathUnder(fresh.targetKey, root)) {
162
+ contained = true;
163
+ break;
164
+ }
165
+ if (!contained) throw new FsError(`cannot write "${target.displayPath}": file access denied under workspace-write mode`, "FS_SANDBOX_DENIED");
166
+ return fresh;
167
+ }
168
+ };
169
+
170
+ //#endregion
171
+ //#region src/fs-aware/sandbox-plugin.ts
172
+ /** Scheme prefix test — mirrors the resolver's own grammar. */
173
+ function isSchemePath(path) {
174
+ return /^[a-z][a-z0-9]*:\/\//.test(path);
175
+ }
176
+ /** Session-layer schemes are excluded from FS-layer resolution (design D2). */
177
+ function isSessionLayerScheme(path) {
178
+ return path.startsWith("ctx://") || path.startsWith("agent://") || path.startsWith("skill://");
179
+ }
180
+ function sessionLayerError(url) {
181
+ if (url.startsWith("skill://")) return new UrlSchemesError("CTX_SESSION_LAYER", `${url}: session-layer scheme — skill discovery (which roots load, scan depth, layered scope merge) is the host skill provider's business logic and resolves only against a calling agent, which the filesystem layer does not have. Load skills with the native \`skill\` tool; grep/glob with path=skill://… keep working through the tool layer`);
182
+ return new UrlSchemesError("CTX_SESSION_LAYER", `${url}: session-layer scheme — read it through the read tool (the session-layer resolver environment carries the live agent this filesystem layer does not have)`);
183
+ }
184
+ const Base = SandboxedFileSystem;
185
+ var FsAwareSandboxFileSystem = class extends Base {
186
+ static inject = [
187
+ "sandboxPolicy",
188
+ "settings",
189
+ "agents"
190
+ ];
191
+ resolver;
192
+ schemeResolution;
193
+ constructor(ctx, config) {
194
+ super(ctx, config);
195
+ this.schemeResolution = config?.urlSchemes !== false;
196
+ this.resolver = this.buildResolver(ctx);
197
+ }
198
+ /**
199
+ * FS-layer resolver — the SHARED builder from `wrap.ts` (design D2). One
200
+ * registration source for both FS consumers: this backend and the instance
201
+ * wrap. (A private duplicate here once drifted — a doubled `dsh` register
202
+ * and a literal `'http'` that would silently drop `https` if this backend
203
+ * were ever the outermost layer.) The `ctx`/`agent` handlers it registers
204
+ * are unreachable here: session-layer schemes are guarded before resolve.
205
+ */
206
+ buildResolver(ctx) {
207
+ return buildFsLayerResolver({ settings: ctx.settings }, this);
208
+ }
209
+ /** FS-layer resolver env: no live agent — session-layer schemes are excluded upstream. */
210
+ envFor(url) {
211
+ return {
212
+ fs: this,
213
+ rawUrl: url
214
+ };
215
+ }
216
+ async resolve(path, opts) {
217
+ if (!this.schemeResolution || !isSchemePath(path)) return super.resolve(path, opts);
218
+ if (isSessionLayerScheme(path)) throw sessionLayerError(path);
219
+ await this.resolver.resolve(this.envFor(path), path);
220
+ return {
221
+ targetKey: path,
222
+ displayPath: path
223
+ };
224
+ }
225
+ async stat(target, signal) {
226
+ if (!this.schemeResolution || !isSchemePath(target.targetKey)) return super.stat(target, signal);
227
+ if (isSessionLayerScheme(target.targetKey)) throw sessionLayerError(target.targetKey);
228
+ return {
229
+ type: "file",
230
+ size: (await this.resolver.resolve(this.envFor(target.targetKey), target.targetKey)).length
231
+ };
232
+ }
233
+ async readText(target, signal) {
234
+ if (!this.schemeResolution || !isSchemePath(target.targetKey)) return super.readText(target, signal);
235
+ if (isSessionLayerScheme(target.targetKey)) throw sessionLayerError(target.targetKey);
236
+ return this.resolver.resolve(this.envFor(target.targetKey), target.targetKey);
237
+ }
238
+ async writeText(target, ...rest) {
239
+ if (this.schemeResolution && isSchemePath(target.targetKey)) throw new UrlSchemesError("FS_VIRTUAL_READONLY", `cannot write "${target.targetKey}": scheme resources are read-only at the filesystem layer`);
240
+ return super.writeText(target, ...rest);
241
+ }
242
+ async editText(target, ...rest) {
243
+ if (this.schemeResolution && isSchemePath(target.targetKey)) throw new UrlSchemesError("FS_VIRTUAL_READONLY", `cannot edit "${target.targetKey}": scheme resources are read-only at the filesystem layer`);
244
+ return super.editText(target, ...rest);
245
+ }
246
+ };
247
+
248
+ //#endregion
249
+ export { FsAwareSandboxFileSystem as default };
package/lib/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- import { t as DASHRSdkSchema } from "./py-sdk-BCaOGYz7.js";
2
1
  import { Context, Service } from "@deepseek-ai/cordis";
3
2
  import z from "@deepseek-ai/schemastery";
4
3
  import { ToolDefinition, ToolExecutionInput, ToolRunContext, ToolRuntime } from "@deepseek-ai/dsh-tools";
5
4
  import { ContentBlock, HarnessError } from "@deepseek-ai/dsh-llm";
6
- import { ScopeKey, Scoped } from "@deepseek-ai/dsh-scope";
5
+ import { Scoped } from "@deepseek-ai/dsh-scope";
7
6
  import { Agent } from "@deepseek-ai/dsh-agent";
8
7
 
9
8
  //#region src/vendored/types.d.ts
@@ -578,6 +577,10 @@ declare const inject: string[];
578
577
  /** Plugin config. */
579
578
  interface Config extends Config$1 {
580
579
  maxParallelSubCalls?: number;
580
+ /** URL scheme resolution (read/write/grep/glob scheme branches + `ctx://`). */
581
+ urlSchemes?: boolean;
582
+ /** Hashline feature: read anchors + the `edit`/`undo` tool family. */
583
+ hashline?: boolean;
581
584
  /** Page hostnames this operator declares their own (web-trust boot script). */
582
585
  trustedPageAuthorities?: string[];
583
586
  /** Mobile responsiveness knobs (delivered to the client half as a page global). */
@@ -603,6 +606,11 @@ declare const EVAL_NAME = "eval";
603
606
  * (`modelSelectionSettings: true`) lands on the agent's OWN layer, outside
604
607
  * registry restriction reach, so it stays visible on every surface and the
605
608
  * control prompt annotates it as an alias of the `agent` delegation tool.
609
+ * `skill` was REMOVED from this list (2026-09-12, user ruling): the native
610
+ * skill tool stays visible and self-surfaces its session catalog
611
+ * (`<available_skills>` is gated on the tool's registry visibility — masking
612
+ * the tool silenced the catalog). dashr's dual pipeline keeps `read
613
+ * skill://<name>` as the addressing form alongside native invocation.
606
614
  * own URL wrappers) is exempt from restrictions by construction — the
607
615
  * capability it carries stays reachable through the captured-definition
608
616
  * bridges below, never through the masked name.
@@ -616,10 +624,6 @@ declare const MASKED_TOOL_NAMES: ReadonlySet<string>;
616
624
  * bridges that read captured definitions.
617
625
  */
618
626
  declare const WIRE_MASKED_NAMES: ReadonlySet<string>;
619
- /** The `dashr:control-prompt` section order: the FIRST section in the 100–199 tool-guidance band, so the cell paradigm is taught before the Tool Catalog renders its signatures. */
620
- declare const CONTROL_SECTION_ORDER = 100;
621
- /** The `dashr:tool-catalog` section order: the 100–199 tool-guidance band's SDK position, matching upstream `tools:sdk`. */
622
- declare const SDK_SECTION_ORDER = 150;
623
627
  /**
624
628
  * The `dashr:escalation-guidance` CONTEXT order: sits inside the runtime-context snapshot
625
629
  * band between upstream `approval:policy` (115) and `subagent:delegation` (120) — the
@@ -721,17 +725,6 @@ interface RunCellBridgeOptions {
721
725
  * @returns the registry-ready definition.
722
726
  */
723
727
  declare function createRunCellTool(registry: ToolRuntime, options: RunCellBridgeOptions): ToolDefinition;
724
- /**
725
- * Collect one calling scope's bridge-declaration schemas through the
726
- * registry's public projection APIs: `schemas(scope)` for the model-facing
727
- * view (scoped tools join, restrictions apply — the wire mask is a
728
- * registry-level restriction installed at session-start, so every masked
729
- * name is ALREADY absent from this projection; no second name filter
730
- * exists to drift), `get(name, scope)` for the canonical output schema,
731
- * snapshotted so a live definition cannot mutate under the render. `eval`
732
- * itself is excluded — it is the transport, not a binding.
733
- */
734
- declare function collectSdkSchemas(registry: ToolRuntime, scope?: ScopeKey): DASHRSdkSchema[];
735
728
  declare function apply(ctx: Context, config: Config): void;
736
729
  declare const _default: {
737
730
  name: string;
@@ -740,4 +733,4 @@ declare const _default: {
740
733
  apply: typeof apply;
741
734
  };
742
735
  //#endregion
743
- export { CONTROL_SECTION_ORDER, Config, DASHRRunFailedError, DashrRuntime, ESCALATION_GUIDANCE_ORDER, EVAL_NAME, MASKED_TOOL_NAMES, ReplDispatchLog, ReplRuntime, RunCellBridgeOptions, type Config$1 as RuntimeConfig, SDK_SECTION_ORDER, WIRE_MASKED_NAMES, apply, collectSdkSchemas, createRunCellTool, _default as default, inject, name, resolveMaxParallelSubCalls };
736
+ export { Config, DASHRRunFailedError, DashrRuntime, ESCALATION_GUIDANCE_ORDER, EVAL_NAME, MASKED_TOOL_NAMES, ReplDispatchLog, ReplRuntime, RunCellBridgeOptions, type Config$1 as RuntimeConfig, WIRE_MASKED_NAMES, apply, createRunCellTool, _default as default, inject, name, resolveMaxParallelSubCalls };