@dsh-electron/dsh-plugin-multi-root-workspace 0.1.11 → 0.1.12

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 (49) hide show
  1. package/CHANGELOG.en.md +13 -1
  2. package/CHANGELOG.md +13 -1
  3. package/README.en.md +9 -8
  4. package/README.md +9 -8
  5. package/cordis.patch.yml +3 -2
  6. package/lib/auto-review.js +1 -1
  7. package/lib/client.js +96 -49
  8. package/lib/client.js.map +1 -1
  9. package/lib/{command-BGdz7VBH.js → command-CmLekG1M.js} +3 -2
  10. package/lib/command.js +1 -1
  11. package/lib/{compat-BiW-m_q0.js → compat-Bg2HFVMX.js} +3 -0
  12. package/lib/compat.js +1 -1
  13. package/lib/{sandbox-CXk5h-ct.js → dialects-Dr4rDR-H.js} +6 -264
  14. package/lib/{fs-BTXySwSG.js → fs-CfzJq2wd.js} +23 -8
  15. package/lib/fs.js +1 -1
  16. package/lib/index.js +7 -6
  17. package/lib/{instructions-MePoRigy.js → instructions-KsUwUFE0.js} +53 -37
  18. package/lib/instructions.js +1 -1
  19. package/lib/lsp.js +19 -16
  20. package/lib/native-win32-2waCRkPI.js +312 -0
  21. package/lib/process-fence.js +62 -12
  22. package/lib/protocol-4zdwV7u4.js +641 -0
  23. package/lib/{registry-KSsmU393.js → registry-YTUOjmVu.js} +368 -25
  24. package/lib/registry-operation-C2ZV3Jhw.js +116 -0
  25. package/lib/registry.js +3 -2
  26. package/lib/{review-context-AH-SXHmj.js → review-context-NDpKeMCc.js} +1 -1
  27. package/lib/review-context.js +1 -1
  28. package/lib/sandbox-C-vCJCp3.js +278 -0
  29. package/lib/sandbox.js +1 -1
  30. package/lib/scope.js +65 -9
  31. package/lib/skills.js +48 -29
  32. package/lib/types/authority/native-win32.d.ts +7 -0
  33. package/lib/types/authority/native.d.ts +23 -0
  34. package/lib/types/authority/protocol.d.ts +70 -0
  35. package/lib/types/compat/authority-storage.d.ts +6 -0
  36. package/lib/types/compat/dsh-version.d.ts +4 -4
  37. package/lib/types/compat/open-in-app-device.d.ts +1 -1
  38. package/lib/types/compat/process-entrypoints.d.ts +2 -0
  39. package/lib/types/compat/process-profile.d.ts +3 -0
  40. package/lib/types/compat/sandbox-confine.d.ts +2 -0
  41. package/lib/types/fs.d.ts +4 -1
  42. package/lib/types/instructions.d.ts +0 -47
  43. package/lib/types/registry-operation.d.ts +31 -0
  44. package/lib/types/registry.d.ts +47 -6
  45. package/lib/types/scope.d.ts +10 -0
  46. package/lib/types/skills/catalog.d.ts +3 -0
  47. package/lib/types/skills/pool.d.ts +1 -1
  48. package/lib/workspace-files.js +63 -7
  49. package/package.json +38 -31
package/lib/skills.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { t as wrapMethod } from "./method-wrapper-CFY3p3j2.js";
2
+ import { c as withRegistryRead } from "./registry-operation-C2ZV3Jhw.js";
2
3
  import { r as isPackageInstalled, t as createInstructionMessage } from "./llm-message-fYORacvh.js";
3
4
  import { n as skillsMetadataValid } from "./metadata-DgqSdVX_.js";
4
5
  import { constants, statSync } from "node:fs";
@@ -6,8 +7,8 @@ import { createHash } from "node:crypto";
6
7
  import { isAbsolute, join, relative, resolve, sep } from "node:path";
7
8
  import { Service } from "@deepseek-ai/cordis";
8
9
  import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
9
- import { lstat, open, readdir, stat } from "node:fs/promises";
10
10
  import { AsyncLocalStorage } from "node:async_hooks";
11
+ import { lstat, open, readdir, stat } from "node:fs/promises";
11
12
  import z from "@deepseek-ai/schemastery";
12
13
  //#region \0@oxc-project+runtime@0.149.0/helpers/esm/usingCtx.js
13
14
  function _usingCtx() {
@@ -191,9 +192,10 @@ var SkillReaderPool = class {
191
192
  return this.entries.size;
192
193
  }
193
194
  get roots() {
194
- return [...this.entries.keys()];
195
+ return [...new Set([...this.entries.values()].map((entry) => entry.root))];
195
196
  }
196
- async use(key, create, run, signal) {
197
+ async use(root, create, run, signal, identity = root) {
198
+ const key = identity;
197
199
  for (;;) {
198
200
  signal?.throwIfAborted();
199
201
  if (this.closing) throw new Error("multi-root skills: reader pool is closing");
@@ -225,6 +227,7 @@ var SkillReaderPool = class {
225
227
  const controller = new AbortController();
226
228
  entry = {
227
229
  key,
230
+ root,
228
231
  controller,
229
232
  reader: create(controller.signal),
230
233
  busy: 0
@@ -235,7 +238,7 @@ var SkillReaderPool = class {
235
238
  this.entries.set(key, entry);
236
239
  entry.busy++;
237
240
  try {
238
- if (entry.stamp === void 0) entry.stamp = await skillTreeStamp(key);
241
+ if (entry.stamp === void 0) entry.stamp = await skillTreeStamp(entry.root);
239
242
  signal?.throwIfAborted();
240
243
  return await run(await entry.reader);
241
244
  } finally {
@@ -249,7 +252,7 @@ var SkillReaderPool = class {
249
252
  this.polling ??= (async () => {
250
253
  let changed = false;
251
254
  for (const entry of [...this.entries.values()]) {
252
- const stamp = await skillTreeStamp(entry.key);
255
+ const stamp = await skillTreeStamp(entry.root);
253
256
  if (entry.stamp !== void 0 && entry.stamp !== stamp) changed = true;
254
257
  entry.stamp = stamp;
255
258
  }
@@ -285,10 +288,10 @@ var SkillReaderPool = class {
285
288
  /** No symlink target outside the Root is read or enumerated. Content reads stay on demand. */
286
289
  async function skillTreeStamp(root) {
287
290
  const rows = [canonicalPath(root)];
288
- const stamp = async (path) => {
291
+ const stamp = async (path, identityOnly = false) => {
289
292
  try {
290
293
  const link = await lstat(path, { bigint: true });
291
- rows.push(`${path}:${link.dev}:${link.ino}:${link.size}:${link.mtimeNs}:${link.ctimeNs}:${canonicalPath(path)}`);
294
+ rows.push(identityOnly ? `${path}:${link.dev}:${link.ino}:${link.mode}:${canonicalPath(path)}` : `${path}:${link.dev}:${link.ino}:${link.size}:${link.mtimeNs}:${link.ctimeNs}:${canonicalPath(path)}`);
292
295
  if (!containsPath(root, canonicalPath(path))) return void 0;
293
296
  return await stat(path);
294
297
  } catch {
@@ -296,7 +299,7 @@ async function skillTreeStamp(root) {
296
299
  return;
297
300
  }
298
301
  };
299
- await stamp(root);
302
+ await stamp(root, true);
300
303
  for (const directory of [".dsh/skills", ".agents/skills"]) {
301
304
  const path = join(root, directory);
302
305
  if (!(await stamp(path))?.isDirectory()) continue;
@@ -336,7 +339,7 @@ var AdditionalSkillCatalog = class {
336
339
  return this.generation();
337
340
  }
338
341
  current(primary, id) {
339
- if (this.signal.aborted || !this.enabled() || this.ctx.multiRootRegistry.authority.kind !== "active" || this.ctx.multiRootScope.resolve({
342
+ if (this.signal.aborted || !this.enabled() || !this.ctx.multiRootRegistry.available || this.ctx.multiRootScope.snapshot({
340
343
  mode: "read-only",
341
344
  workspaceRoot: primary
342
345
  }).authority !== "available") return void 0;
@@ -355,13 +358,17 @@ var AdditionalSkillCatalog = class {
355
358
  if (root === void 0) return await run(void 0);
356
359
  return await this.requests.run({
357
360
  primary,
358
- id
361
+ id,
362
+ epoch: this.ctx.multiRootRegistry.epoch
359
363
  }, () => this.pool.use(root.path, (signal) => rootReader(this.ctx, root.path, AbortSignal.any([signal, this.signal]), () => {
360
364
  const request = this.requests.getStore();
361
- return request !== void 0 && this.current(request.primary, request.id)?.path === root.path;
362
- }), run, options.signal));
365
+ return request !== void 0 && request.epoch === this.ctx.multiRootRegistry.epoch && this.current(request.primary, request.id)?.path === root.path;
366
+ }), run, options.signal, `${this.ctx.multiRootRegistry.epoch ?? "unavailable"}\0${root.path}`));
363
367
  }
364
368
  async list(primary, options) {
369
+ return withRegistryRead(this.ctx, primary, "skills", () => this.listAdmitted(primary, options));
370
+ }
371
+ async listAdmitted(primary, options) {
365
372
  options.signal?.throwIfAborted();
366
373
  if (!this.enabled()) return [];
367
374
  const roots = await this.ctx.multiRootRegistry.refresh(primary);
@@ -381,6 +388,7 @@ var AdditionalSkillCatalog = class {
381
388
  seen.add(candidate.name);
382
389
  const locator = {
383
390
  primary,
391
+ authorityEpoch: this.ctx.multiRootRegistry.epoch,
384
392
  rootId: root.id,
385
393
  aliasId: root.aliasId,
386
394
  candidate
@@ -408,8 +416,11 @@ var AdditionalSkillCatalog = class {
408
416
  }
409
417
  /** Locators are created here and are not accepted from a client path or object. */
410
418
  async load(primary, entry, options, user) {
419
+ return withRegistryRead(this.ctx, primary, "skills", () => this.loadAdmitted(primary, entry, options, user));
420
+ }
421
+ async loadAdmitted(primary, entry, options, user) {
411
422
  const locator = entry.locator;
412
- if (typeof locator !== "object" || locator === null || !this.locators.has(locator) || locator.primary !== primary) return void 0;
423
+ if (typeof locator !== "object" || locator === null || !this.locators.has(locator) || locator.primary !== primary || locator.authorityEpoch !== this.ctx.multiRootRegistry.epoch) return void 0;
413
424
  const root = this.current(primary, locator.rootId);
414
425
  if (root === void 0 || root.aliasId !== locator.aliasId || entry.name !== internalSkillName(primary, root.id, locator.candidate.name)) return void 0;
415
426
  const revision = this.generation();
@@ -446,14 +457,16 @@ var AdditionalSkillCatalog = class {
446
457
  }
447
458
  async admissionValid(definition) {
448
459
  const receipt = this.loadedReceipts.get(definition);
449
- if (receipt === void 0) return false;
450
- const current = this.current(receipt.locator.primary, receipt.locator.rootId);
451
- if (current === void 0 || current.aliasId !== receipt.locator.aliasId || receipt.revision !== this.generation()) return false;
452
- try {
453
- return receipt.stamp === await fileStamp(definition.path) && receipt.revision === this.generation() && this.current(receipt.locator.primary, receipt.locator.rootId)?.aliasId === receipt.locator.aliasId;
454
- } catch {
455
- return false;
456
- }
460
+ if (receipt === void 0 || receipt.locator.authorityEpoch !== this.ctx.multiRootRegistry.epoch) return false;
461
+ return withRegistryRead(this.ctx, receipt.locator.primary, "skills", async () => {
462
+ const current = this.current(receipt.locator.primary, receipt.locator.rootId);
463
+ if (current === void 0 || current.aliasId !== receipt.locator.aliasId || receipt.revision !== this.generation()) return false;
464
+ try {
465
+ return receipt.stamp === await fileStamp(definition.path) && receipt.revision === this.generation() && this.current(receipt.locator.primary, receipt.locator.rootId)?.aliasId === receipt.locator.aliasId;
466
+ } catch {
467
+ return false;
468
+ }
469
+ });
457
470
  }
458
471
  async dispose() {
459
472
  await this.pool.dispose();
@@ -574,16 +587,22 @@ function installSkillRouting(ctx) {
574
587
  fibers.push(ctx.inject(["skills"], (scope) => {
575
588
  const registry = scope.skills;
576
589
  scope.effect(() => wrapMethod(registry, "snapshot", (original) => async function(options = {}) {
577
- const result = await original.call(this, optionsFor(options));
578
- const primary = skillOrigin(scope, options);
579
- if (primary !== void 0) for (const skill of result.skills) {
580
- if (skill.provider === "multi-root-workspace") continue;
581
- if (scope.multiRootRegistry.list(primary).some((root) => skill.name.startsWith(internalSkillName(primary, root.id, "")))) scope.logger.warn(`multi-root skills: internal ID conflict; native winner retained: ${skill.name} (${skill.provider})`);
582
- }
583
- return result;
590
+ const primaryOrigin = skillOrigin(scope, options) ?? cold.getStore();
591
+ const run = async () => {
592
+ const result = await original.call(this, optionsFor(options));
593
+ const primary = skillOrigin(scope, options);
594
+ if (primary !== void 0) for (const skill of result.skills) {
595
+ if (skill.provider === "multi-root-workspace") continue;
596
+ if (scope.multiRootRegistry.list(primary).some((root) => skill.name.startsWith(internalSkillName(primary, root.id, "")))) scope.logger.warn(`multi-root skills: internal ID conflict; native winner retained: ${skill.name} (${skill.provider})`);
597
+ }
598
+ return result;
599
+ };
600
+ return primaryOrigin === void 0 ? run() : withRegistryRead(scope, primaryOrigin, "skills", run);
584
601
  }));
585
602
  scope.effect(() => wrapMethod(registry, "get", (original) => function(name, options = {}) {
586
- return original.call(this, name, optionsFor(options));
603
+ const primary = skillOrigin(scope, options) ?? cold.getStore();
604
+ const run = () => original.call(this, name, optionsFor(options));
605
+ return primary === void 0 ? run() : withRegistryRead(scope, primary, "skills", run);
587
606
  }));
588
607
  }));
589
608
  fibers.push(ctx.inject(["sessionSkillCatalog", "sessionQuery"], (scope) => {
@@ -0,0 +1,7 @@
1
+ import type { Channel, Listener } from './native.ts';
2
+ export declare function connectWindows(endpoint: string): Promise<Channel>;
3
+ export declare function listenWindows(endpoint: string, accepted: (channel: Channel) => void): Promise<Listener>;
4
+ export declare function windowsHostAvailability(): Promise<{
5
+ available: boolean;
6
+ reason?: string;
7
+ }>;
@@ -0,0 +1,23 @@
1
+ import type { EventEmitter } from 'node:events';
2
+ export interface Channel {
3
+ on: EventEmitter['on'];
4
+ readonly writableLength?: number;
5
+ write(data: string): boolean;
6
+ destroy(): unknown;
7
+ }
8
+ export interface Listener {
9
+ close(): void;
10
+ }
11
+ export declare function storeIdentity(root: string): {
12
+ root: string;
13
+ id: string;
14
+ endpoint: string;
15
+ };
16
+ export declare function connectNative(endpoint: string): Promise<Channel>;
17
+ /** Called only by the kernel lease holder. Never removes another epoch's socket. */
18
+ export declare function listenNative(endpoint: string, accepted: (channel: Channel) => void): Promise<Listener>;
19
+ /** Capability probe for native tests and diagnostics; not an authorization bypass. */
20
+ export declare function nativeHostAvailability(): Promise<{
21
+ available: boolean;
22
+ reason?: string;
23
+ }>;
@@ -0,0 +1,70 @@
1
+ import { z } from 'zod';
2
+ declare const methods: readonly ["hello", "snapshot", "admit", "add", "removeAt", "setAccess", "setAlias", "setSkillsEnabled", "setAliasId", "move", "recheck"];
3
+ export type Method = typeof methods[number];
4
+ export declare const snapshotSchema: z.ZodObject<{
5
+ protocol: z.ZodLiteral<1>;
6
+ store: z.ZodString;
7
+ epoch: z.ZodString;
8
+ revision: z.ZodNumber;
9
+ primary: z.ZodString;
10
+ roots: z.ZodArray<z.ZodObject<{
11
+ id: z.ZodString;
12
+ path: z.ZodString;
13
+ access: z.ZodEnum<{
14
+ inherit: "inherit";
15
+ "read-only": "read-only";
16
+ "read-write": "read-write";
17
+ }>;
18
+ addedAt: z.ZodString;
19
+ recordedPath: z.ZodOptional<z.ZodString>;
20
+ readOnlyRecordedPath: z.ZodOptional<z.ZodString>;
21
+ readWriteRecordedPath: z.ZodOptional<z.ZodString>;
22
+ alias: z.ZodOptional<z.ZodString>;
23
+ aliasId: z.ZodOptional<z.ZodUnknown>;
24
+ skillsEnabled: z.ZodOptional<z.ZodUnknown>;
25
+ state: z.ZodEnum<{
26
+ available: "available";
27
+ missing: "missing";
28
+ redirected: "redirected";
29
+ invalid: "invalid";
30
+ }>;
31
+ detail: z.ZodOptional<z.ZodString>;
32
+ invalidReason: z.ZodOptional<z.ZodLiteral<"membership-conflict">>;
33
+ }, z.core.$strict>>;
34
+ available: z.ZodBoolean;
35
+ }, z.core.$strict>;
36
+ export type Snapshot = z.infer<typeof snapshotSchema>;
37
+ export declare class AuthorityRpcError extends Error {
38
+ readonly code: string;
39
+ constructor(code: string, message: string);
40
+ }
41
+ export declare class AuthorityClient {
42
+ private readonly channel;
43
+ private readonly store;
44
+ private readonly limit;
45
+ private readonly pending;
46
+ private closed;
47
+ epoch: string | undefined;
48
+ private constructor();
49
+ static connect(endpoint: string, store: string, limit: number, onChange: (snapshot: Snapshot) => void, disconnected: () => void): Promise<AuthorityClient>;
50
+ request(method: Method, args: readonly unknown[]): Promise<unknown>;
51
+ close(): void;
52
+ }
53
+ export declare class AuthorityServer {
54
+ private readonly store;
55
+ private readonly epoch;
56
+ private readonly limit;
57
+ private readonly dispatch;
58
+ private listener;
59
+ private readonly peers;
60
+ private readonly readyPeers;
61
+ private closed;
62
+ private inFlight;
63
+ private constructor();
64
+ static listen(endpoint: string, store: string, epoch: string, limit: number, dispatch: (method: Method, args: unknown[]) => Promise<unknown>): Promise<AuthorityServer>;
65
+ private accept;
66
+ changed(snapshot: Snapshot): void;
67
+ close(): void;
68
+ }
69
+ export declare function parseArguments(method: Method, args: unknown[]): unknown[];
70
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { DomainSpec } from '@deepseek-ai/dsh-storage-domain';
3
+ export declare function openAuthorityStorage<S extends DomainSpec>(ctx: Context, root: string, spec: S): Promise<{
4
+ table: <N extends keyof S["tables"] & string>(name: N) => import("@deepseek-ai/dsh-storage-domain").KvTable<import("@deepseek-ai/dsh-storage-domain").TableKeyOf<S, N>, import("@deepseek-ai/dsh-storage-domain").TableValueOf<S, N>>;
5
+ close(): Promise<void>;
6
+ }>;
@@ -28,7 +28,7 @@
28
28
  * the upgrade smoke passed on that exact version — a green CI run on an
29
29
  * unlisted release is evidence, not authorization (ADR-0009).
30
30
  */
31
- export declare const SUPPORTED_DSH_RELEASES: readonly ["0.1.5-rc.2", "0.1.6-alpha.1", "0.1.6-alpha.2", "0.1.7-alpha.1", "0.1.7-alpha.2", "0.1.7-rc.1", "0.1.7-rc.2", "0.2.0-rc.1", "0.2.0-rc.2", "0.2.1-alpha.1", "0.2.1-alpha.2", "0.2.1-alpha.3", "0.2.1-alpha.4", "0.2.1-alpha.5", "0.2.1-rc.1", "0.2.1-rc.2", "0.2.1-rc.3", "0.2.1-rc.4", "0.2.2-alpha.2"];
31
+ export declare const SUPPORTED_DSH_RELEASES: readonly ["0.1.5-rc.2", "0.1.6-alpha.1", "0.1.6-alpha.2", "0.1.7-alpha.1", "0.1.7-alpha.2", "0.1.7-rc.1", "0.1.7-rc.2", "0.2.0-rc.1", "0.2.0-rc.2", "0.2.1-alpha.1", "0.2.1-alpha.2", "0.2.1-alpha.3", "0.2.1-alpha.4", "0.2.1-alpha.5", "0.2.1-rc.1", "0.2.1-rc.2", "0.2.1-rc.3", "0.2.1-rc.4", "0.2.1-rc.5", "0.2.2-alpha.2"];
32
32
  /** One release this plugin claims to support. */
33
33
  export type SupportedDshRelease = typeof SUPPORTED_DSH_RELEASES[number];
34
34
  /**
@@ -42,7 +42,7 @@ export declare const REQUIRED_CORE_PACKAGES: readonly ["@deepseek-ai/dsh-fs", "@
42
42
  * Absence is fine; a version that DISAGREES with the required packages is not —
43
43
  * that is the mixed-installation failure this contract exists to catch.
44
44
  */
45
- export declare const OPTIONAL_CORE_PACKAGES: readonly ["@deepseek-ai/dsh-skill", "@deepseek-ai/dsh-skill-filesystem", "@deepseek-ai/dsh-settings", "@deepseek-ai/dsh-api-session-controller", "@deepseek-ai/dsh-session-query", "@deepseek-ai/dsh-tools", "@deepseek-ai/dsh-working-directory", "@deepseek-ai/dsh-lsp", "@deepseek-ai/dsh-lsp-stdio", "@deepseek-ai/dsh-api-workspace-files", "@deepseek-ai/dsh-typert-protocol", "@deepseek-ai/dsh-sandbox-windows-acl", "@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-client-connection", "@deepseek-ai/dsh-commands", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-subprocess", "@deepseek-ai/dsh-system-prompt"];
45
+ export declare const OPTIONAL_CORE_PACKAGES: readonly ["@deepseek-ai/dsh-storage-json", "@deepseek-ai/dsh-skill", "@deepseek-ai/dsh-skill-filesystem", "@deepseek-ai/dsh-settings", "@deepseek-ai/dsh-api-session-controller", "@deepseek-ai/dsh-session-query", "@deepseek-ai/dsh-tools", "@deepseek-ai/dsh-working-directory", "@deepseek-ai/dsh-lsp", "@deepseek-ai/dsh-lsp-stdio", "@deepseek-ai/dsh-api-workspace-files", "@deepseek-ai/dsh-typert-protocol", "@deepseek-ai/dsh-sandbox-windows-acl", "@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-client-connection", "@deepseek-ai/dsh-commands", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-subprocess", "@deepseek-ai/dsh-system-prompt"];
46
46
  /**
47
47
  * The optional packages this plugin loads itself at run time, through dynamic
48
48
  * `import()`s inside this package's own modules. The copy this module resolves
@@ -50,7 +50,7 @@ export declare const OPTIONAL_CORE_PACKAGES: readonly ["@deepseek-ai/dsh-skill",
50
50
  * private copy inside this package's own tree genuinely loads, and a version
51
51
  * that disagrees with the host is a real mixed installation, not a phantom.
52
52
  */
53
- export declare const RUNTIME_OPTIONAL_PACKAGES: readonly ["@deepseek-ai/dsh-skill", "@deepseek-ai/dsh-skill-filesystem", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-typert-protocol"];
53
+ export declare const RUNTIME_OPTIONAL_PACKAGES: readonly ["@deepseek-ai/dsh-storage-json", "@deepseek-ai/dsh-skill", "@deepseek-ai/dsh-skill-filesystem", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-typert-protocol"];
54
54
  /**
55
55
  * The optional packages this plugin consumes only as mounted host services or
56
56
  * ambient types — it never imports them, so a copy inside this package's own
@@ -79,7 +79,7 @@ export declare const HOST_SERVICE_PACKAGES: readonly ["@deepseek-ai/dsh-settings
79
79
  */
80
80
  export declare const RUNNER_ANCHORED_PACKAGES: readonly ["@deepseek-ai/dsh-sandbox-windows-acl"];
81
81
  /** Every package the contract inspects, required first. */
82
- export declare const CORE_PACKAGES: readonly ["@deepseek-ai/dsh-fs", "@deepseek-ai/dsh-fs-local", "@deepseek-ai/dsh-sandbox", "@deepseek-ai/dsh-sandbox-local", "@deepseek-ai/dsh-sandbox-policy", "@deepseek-ai/dsh-storage-domain", "@deepseek-ai/dsh-skill", "@deepseek-ai/dsh-skill-filesystem", "@deepseek-ai/dsh-settings", "@deepseek-ai/dsh-api-session-controller", "@deepseek-ai/dsh-session-query", "@deepseek-ai/dsh-tools", "@deepseek-ai/dsh-working-directory", "@deepseek-ai/dsh-lsp", "@deepseek-ai/dsh-lsp-stdio", "@deepseek-ai/dsh-api-workspace-files", "@deepseek-ai/dsh-typert-protocol", "@deepseek-ai/dsh-sandbox-windows-acl", "@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-client-connection", "@deepseek-ai/dsh-commands", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-subprocess", "@deepseek-ai/dsh-system-prompt"];
82
+ export declare const CORE_PACKAGES: readonly ["@deepseek-ai/dsh-fs", "@deepseek-ai/dsh-fs-local", "@deepseek-ai/dsh-sandbox", "@deepseek-ai/dsh-sandbox-local", "@deepseek-ai/dsh-sandbox-policy", "@deepseek-ai/dsh-storage-domain", "@deepseek-ai/dsh-storage-json", "@deepseek-ai/dsh-skill", "@deepseek-ai/dsh-skill-filesystem", "@deepseek-ai/dsh-settings", "@deepseek-ai/dsh-api-session-controller", "@deepseek-ai/dsh-session-query", "@deepseek-ai/dsh-tools", "@deepseek-ai/dsh-working-directory", "@deepseek-ai/dsh-lsp", "@deepseek-ai/dsh-lsp-stdio", "@deepseek-ai/dsh-api-workspace-files", "@deepseek-ai/dsh-typert-protocol", "@deepseek-ai/dsh-sandbox-windows-acl", "@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-agent-instructions", "@deepseek-ai/dsh-client-connection", "@deepseek-ai/dsh-commands", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-subprocess", "@deepseek-ai/dsh-system-prompt"];
83
83
  /** How this plugin reacts to an installation it does not support. */
84
84
  export type CompatEnforcement = 'enforce' | 'warn';
85
85
  /** The environment variable that relaxes the gate. */
@@ -3,7 +3,7 @@
3
3
  * `@deepseek-ai/dsh-client-ui-primitives`.
4
4
  *
5
5
  * ```text
6
- * 0.1.5-rc.2 through 0.2.1-rc.4 no `isMobileDevice` export
6
+ * 0.1.5-rc.2 through 0.2.1-rc.5 no `isMobileDevice` export
7
7
  * 0.2.2-alpha.2 + `isMobileDevice` exported
8
8
  * ```
9
9
  *
@@ -29,3 +29,5 @@ export declare function shellEntrypoints(shell: object): readonly ProcessEntrypo
29
29
  * @returns the present entries — a structural probe only.
30
30
  */
31
31
  export declare function ptcEntrypoints(runtime: object): readonly ProcessEntrypoint[];
32
+ /** Async public entries can acquire admission before calling their implementation. */
33
+ export declare function isAsyncProcessEntry(entry: unknown): boolean;
@@ -0,0 +1,3 @@
1
+ export declare function hasConfinedProfile(argv: readonly string[]): boolean;
2
+ /** Agent PTYs are re-confined after their own fresh admission, never replayed. */
3
+ export declare function originalProcessArgv(argv: readonly string[]): readonly string[];
@@ -58,3 +58,5 @@ export declare function mapConfined<T>(value: UpstreamConfined, map: (confined:
58
58
  * @returns the widened result, in the shape the installed release uses.
59
59
  */
60
60
  export declare function widenConfined(base: ConfineCall, widen: (confined: ConfinedArgv) => ConfinedArgv, argv: readonly string[], policy: SandboxPolicy, signal?: AbortSignal): UpstreamConfined;
61
+ /** Public consumer call with the installed provider's arity and return shape. */
62
+ export declare function confineInstalled(provider: Pick<LocalSandboxProvider, 'confine'>, argv: readonly string[], policy: SandboxPolicy, signal?: AbortSignal): ConfinedArgv | Promise<ConfinedArgv>;
package/lib/types/fs.d.ts CHANGED
@@ -64,6 +64,8 @@ export declare class MultiRootFileSystem extends LocalFileSystem {
64
64
  editText(target: FsTarget, edit: FsEditRequest, expected?: {
65
65
  version: FsVersion;
66
66
  }, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy): Promise<FsEditOutcome>;
67
+ /** The provider must also work without the gated registry (ADR-0009). */
68
+ private withOperation;
67
69
  /**
68
70
  * The writable roots of one confined call: the upstream derivation (primary
69
71
  * root plus the platform temp areas, canonical and deduplicated — empty
@@ -104,7 +106,8 @@ export declare class MultiRootFileSystem extends LocalFileSystem {
104
106
  * to primary-root-only writes (no shared temp or additional-root grants)
105
107
  * until the authority returns.
106
108
  * @param target - the resolved target about to be mutated.
107
- * @param sandboxPolicy - the per-call policy; omit for the deployment fallback.
109
+ * @param policy - the per-call policy captured before admission.
110
+ * @param scope - the admission snapshot shared by every fence check.
108
111
  * @returns the fresh canonical target the mutation must use.
109
112
  */
110
113
  private checkedTarget;
@@ -1,50 +1,3 @@
1
- /**
2
- * `multi-root-instructions`: the additional roots' own instruction files, made
3
- * visible to the model.
4
- *
5
- * Upstream `agent-instructions` discovers instruction files by walking UP from
6
- * the session cwd (plus the one user-global file). In a multi-root workspace
7
- * that walk never reaches an additional root, so `repo-b/AGENTS.md` is
8
- * invisible to a model that is nevertheless allowed to write `repo-b`. This row
9
- * closes exactly that gap, and nothing else: the primary root's chain — nested
10
- * files included — and the user-global file stay upstream's business.
11
- *
12
- * Four deliberate choices:
13
- *
14
- * 1. **Not a system-prompt contribution.** Instruction text is
15
- * producer-supplied context, not system authority, so it enters as a
16
- * user-role message tagged `form: 'instructions'`. Through session format 3
17
- * the source kind is `plugin`; format 4 retired that shared kind, so the
18
- * adapter persists this plugin's own (`multi-root-workspace`). It is also
19
- * the only channel that is logged, and model-visible content must be logged.
20
- * 2. **Injected from `agent/pre-step`, not a session lifecycle event.**
21
- * `pre-step` is an awaited waterfall, so discovery, reading and rendering all
22
- * complete BEFORE the step it feeds — deterministically, on the first step.
23
- * A synchronous prompt callback could not await them, and an emit-mode
24
- * lifecycle listener would race the first step. It is also the one hook whose
25
- * shape is identical across every supported release, which keeps the
26
- * instruction feature out of the compatibility matrix (ADR-0009).
27
- * 3. **Discovery is pinned to the root.** `projectRoot` is the additional root,
28
- * so the walk can never leave it, and every candidate is then required to be
29
- * canonically INSIDE that root. That is what keeps `$DSH_HOME/AGENTS.md`, the
30
- * primary root's `AGENTS.md`, and any ancestor's file from being injected a
31
- * second time by this plugin.
32
- * 4. **Nested files are driven by the session's own tool activity.** A
33
- * subdirectory's `AGENTS.md` only matters once the session has worked in that
34
- * subdirectory, so the directories worth examining are derived from the
35
- * session's persisted events: `tool/call` and `tool/result` are paired, and a
36
- * successful `read`/`write`/`edit` makes the path from the root down to the
37
- * touched file's directory relevant (ADR-0010). A file that disappears is
38
- * retracted explicitly, because the text it contributed earlier is still in
39
- * the conversation and silence does not withdraw it.
40
- *
41
- * The byte budget is shared: `maxBytes` bounds the WHOLE additional-root
42
- * snapshot, not each root, because ten roots must not quietly cost ten budgets
43
- * of context. Roots consume it in scope order. Retractions are exempt, because a
44
- * truncated withdrawal is worse than a slightly over-budget message.
45
- *
46
- * @module @dsh-electron/dsh-plugin-multi-root-workspace/instructions
47
- */
48
1
  import { Context } from '@deepseek-ai/cordis';
49
2
  import type { Agent } from '@deepseek-ai/dsh-agent';
50
3
  declare module '@deepseek-ai/cordis' {
@@ -0,0 +1,31 @@
1
+ import type { SandboxMode } from '@deepseek-ai/dsh-sandbox';
2
+ import type { FilesystemScope } from './scope.ts';
3
+ export type RegistryOperationKind = 'fs-write' | 'fs-edit' | 'process' | 'pty' | 'lsp' | 'files' | 'instructions' | 'skills';
4
+ export declare function currentRegistryOperation(proof: object): RegistryOperation | undefined;
5
+ export interface RegistryOperationIdentity {
6
+ readonly operationId: string;
7
+ readonly kind: RegistryOperationKind;
8
+ readonly mode: SandboxMode | undefined;
9
+ readonly primaryRoot: string;
10
+ /** Absent when the registry cannot grant additional authority. */
11
+ readonly authorityEpoch: string | undefined;
12
+ /** Effective publication revision, meaningful only within this epoch. */
13
+ readonly revision: number;
14
+ }
15
+ export interface RegistryOperation {
16
+ readonly identity: RegistryOperationIdentity;
17
+ /** Only readable during this operation's callback. */
18
+ readonly scope: FilesystemScope;
19
+ }
20
+ /** Capture by value: neither later publication nor a caller can mutate admission. */
21
+ export declare function freezeOperationScope(scope: FilesystemScope): FilesystemScope;
22
+ /** Execute outside the authority queue; completion also expires async descendants. */
23
+ export declare function runRegistryOperation<T>(scope: FilesystemScope, kind: RegistryOperationKind, authorityEpoch: string | undefined, revision: number, body: (operation: RegistryOperation) => T | Promise<T>, proof?: object, mode?: SandboxMode): Promise<T>;
24
+ /** Optional Registry seam for public consumers; the legacy composition stays exact. */
25
+ export declare function withRegistryRead<T>(ctx: import('@deepseek-ai/cordis').Context, primary: string, kind: RegistryOperationKind, body: () => T | Promise<T>): Promise<T>;
26
+ export declare function withRegistryAdmission<T>(ctx: import('@deepseek-ai/cordis').Context, policy: import('@deepseek-ai/dsh-sandbox').SandboxExecutionPolicy, kind: RegistryOperationKind, body: () => T | Promise<T>): Promise<T>;
27
+ /** Preserve a legacy synchronous entry while granting only the safe primary-root fallback. */
28
+ export declare function runPrimaryOperation<T>(scope: FilesystemScope, mode: SandboxMode, proof: object, body: () => T): T;
29
+ /** Only profiles produced within this exact admission can reach a sync spawn. */
30
+ export declare function rememberOperationProfile(proof: object, argv: readonly string[], capped: boolean): void;
31
+ export declare function operationPermitsProfile(proof: object, argv: readonly string[], capped: boolean): boolean;
@@ -22,8 +22,8 @@
22
22
  * only when the operator removes it (or when the store is repaired). Storage
23
23
  * that cannot be parsed at all is backed up and skipped by the domain layer,
24
24
  * with a warning.
25
- * - **Mutations are serialized per primary root.** Every mutation runs its
26
- * whole read → validate → persist sequence inside one queue per key. The
25
+ * - **Mutations and fs admissions are serialized store-wide.** Every mutation
26
+ * runs its whole read → validate → persist sequence inside the store queue. The
27
27
  * storage domain serializes individual `put()`/`delete()` calls, which is not
28
28
  * enough: two concurrent `add()` calls would both read the same snapshot and
29
29
  * the later write would drop the earlier one, and a removal racing an add
@@ -45,7 +45,10 @@
45
45
  */
46
46
  import { Context, Service } from '@deepseek-ai/cordis';
47
47
  import type { SandboxExecutionPolicy } from '@deepseek-ai/dsh-sandbox';
48
+ import type { RegistryOperation, RegistryOperationKind } from './registry-operation.ts';
48
49
  import { type AdditionalRootId, type RegisteredRoot, type RootAccess, type RootRef, type RootStatus } from './roots.ts';
50
+ /** Operator/test capability diagnostics; never bypasses peer authentication. */
51
+ export { nativeHostAvailability } from './authority/native.ts';
49
52
  /** The domain name; also the store file stem (`$DSH_HOME/storages/<name>.json`). */
50
53
  export declare const DOMAIN_NAME = "multi_root_workspace";
51
54
  /** The single table name inside the domain. */
@@ -76,6 +79,9 @@ export type RegistryAuthorityState = {
76
79
  readonly kind: 'active';
77
80
  } | {
78
81
  readonly kind: 'contended';
82
+ } | {
83
+ readonly kind: 'follower';
84
+ readonly epoch: string;
79
85
  } | {
80
86
  readonly kind: 'storage-failed';
81
87
  readonly reason: string;
@@ -92,6 +98,8 @@ export type RegistryAuthorityState = {
92
98
  export interface Config {
93
99
  /** Absolute path of the store-wide kernel lock file. */
94
100
  readonly leasePath?: string;
101
+ /** Explicit medium binding for the multi-Host authority service. */
102
+ readonly sharedStorageRoot?: string;
95
103
  /**
96
104
  * Cap on STORED additional-root records per primary root (every state
97
105
  * counts, including withheld and unusable ones, so the count is stable and
@@ -191,6 +199,16 @@ export declare class MultiRootRegistry extends Service {
191
199
  static inject: string[];
192
200
  /** Absolute lock path; absent means the row was composed without `leasePath`. */
193
201
  private readonly leasePath;
202
+ private readonly sharedStorageRoot;
203
+ private readonly authorityStorageContext;
204
+ private medium;
205
+ private server;
206
+ private client;
207
+ private connectionFailure;
208
+ private connectionGeneration;
209
+ private remoteProjectionReady;
210
+ private readonly remoteRevisions;
211
+ private readonly operationProof;
194
212
  /** Whether this process currently owns the store. */
195
213
  private authorityState;
196
214
  /** Held kernel lease; present once acquire succeeded, including after a failed open. */
@@ -203,8 +221,12 @@ export declare class MultiRootRegistry extends Service {
203
221
  private readonly cache;
204
222
  /** Signature of the last published grant per key, so a no-op refresh stays a no-op. */
205
223
  private readonly published;
206
- /** One promise chain per canonical primary root: mutations never interleave. */
224
+ /** Pending store-ordered work, grouped by primary for teardown accounting. */
207
225
  private readonly chains;
226
+ /** Admissions and mutations share one order, including across primary roots. */
227
+ private operationChain;
228
+ private operationEpoch;
229
+ private operationRevision;
208
230
  /** Store-wide queue for lease acquire / open / release. */
209
231
  private authorityChain;
210
232
  /** Change listeners, keyed by their own disposer identity. */
@@ -245,6 +267,9 @@ export declare class MultiRootRegistry extends Service {
245
267
  * Whether this process is the Registry Authority for the store-wide medium.
246
268
  * Tests and diagnostics read this; surfaces use {@link unavailable}.
247
269
  */
270
+ get epoch(): string | undefined;
271
+ get shared(): boolean;
272
+ get available(): boolean;
248
273
  get authority(): RegistryAuthorityState;
249
274
  /**
250
275
  * The configured cap on stored additional-root records per primary root.
@@ -289,6 +314,13 @@ export declare class MultiRootRegistry extends Service {
289
314
  * @returns the refreshed status list.
290
315
  */
291
316
  refresh(primaryRoot: string): Promise<readonly RootStatus[]>;
317
+ /**
318
+ * Admit one fs invocation in the same order as mutations, then run its I/O
319
+ * OUTSIDE the queue. Revocation does not wait for already-admitted work.
320
+ * Shared followers obtain the same ordered snapshot through attested IPC.
321
+ */
322
+ withOperation<T>(policy: SandboxExecutionPolicy, kind: RegistryOperationKind, body: (operation: RegistryOperation) => T | Promise<T>): Promise<T>;
323
+ withPrimaryOperation<T>(policy: SandboxExecutionPolicy, body: () => T): T;
292
324
  /**
293
325
  * Registers one additional root.
294
326
  * @param primaryRoot - the session workspace root the root belongs to.
@@ -419,8 +451,8 @@ export declare class MultiRootRegistry extends Service {
419
451
  */
420
452
  private persist;
421
453
  /**
422
- * Run one job for one primary root after every job already queued for that
423
- * root. The whole read → validate → write sequence belongs inside the job:
454
+ * Run one job after every store job already queued. The whole
455
+ * read → validate → write sequence belongs inside the job:
424
456
  * the storage domain serializes individual writes, so without this the loser
425
457
  * of a race reads a snapshot that is already stale and writes it back.
426
458
  */
@@ -446,6 +478,15 @@ export declare class MultiRootRegistry extends Service {
446
478
  */
447
479
  private readonly metadataPublished;
448
480
  private publish;
481
+ private projectRoots;
482
+ private snapshotOf;
483
+ private captureSnapshot;
484
+ private notifyShared;
485
+ private applySnapshot;
486
+ private remoteUnavailable;
487
+ private remoteSnapshot;
488
+ private remote;
489
+ private dispatchShared;
449
490
  /**
450
491
  * The open table.
451
492
  * @returns the live table.
@@ -494,7 +535,7 @@ export declare class MultiRootRegistry extends Service {
494
535
  */
495
536
  private releaseAuthority;
496
537
  /**
497
- * Wait until no per-primary-root mutation is queued or running.
538
+ * Wait until no store work is queued or running (operation I/O is outside).
498
539
  *
499
540
  * One pass is enough, and only because the authority state has already been
500
541
  * flipped: no further mutation can be admitted (it would fail in
@@ -208,6 +208,7 @@ export declare class MultiRootScopeService extends Service {
208
208
  /** Additional roots keyed by canonical primary root; empty until roots are registered. */
209
209
  private readonly rootsByPrimary;
210
210
  private readonly listeners;
211
+ private admissionProof;
211
212
  /**
212
213
  * The registry-authority fact the registry row publishes. Defaults to
213
214
  * `available`: a composition without a registry row can never have
@@ -244,6 +245,15 @@ export declare class MultiRootScopeService extends Service {
244
245
  * @param policy - the upstream policy whose workspace root is the primary root.
245
246
  * @returns the canonical scope; `additionalRoots` is empty when none are registered.
246
247
  */
248
+ requireAdmission(proof: object): void;
249
+ rememberProfile(argv: readonly string[]): void;
250
+ permitsProfile(argv: readonly string[]): boolean;
251
+ get admissionRequired(): boolean;
252
+ admissionPathsValid(): boolean;
253
+ hasAdmission(): boolean;
254
+ /** Trusted display/review projection, never an execution permit. */
255
+ snapshot(policy: SandboxExecutionPolicy): FilesystemScope;
256
+ resolveSnapshot(policy: SandboxExecutionPolicy, roots: readonly AdditionalWorkspaceRoot[], available: boolean): FilesystemScope;
247
257
  resolve(policy: SandboxExecutionPolicy): FilesystemScope;
248
258
  /** Read-only pending-dispatch projection. Does not mint an ALS execution grant. */
249
259
  previewForTool(policy: SandboxExecutionPolicy, toolName: string): FilesystemScope;
@@ -7,6 +7,7 @@ export declare const SKILLS_PROVIDER = "multi-root-workspace";
7
7
  export declare function internalSkillName(primary: string, rootId: string, skillName: string): string;
8
8
  export interface LocatedSkill {
9
9
  readonly primary: string;
10
+ readonly authorityEpoch?: string | undefined;
10
11
  readonly rootId: string;
11
12
  readonly aliasId: string;
12
13
  readonly candidate: SkillCandidate;
@@ -27,8 +28,10 @@ export declare class AdditionalSkillCatalog {
27
28
  current(primary: string, id: string): RootStatus | undefined;
28
29
  private withReader;
29
30
  list(primary: string, options: SkillLookupOptions): Promise<SkillCandidate[]>;
31
+ private listAdmitted;
30
32
  /** Locators are created here and are not accepted from a client path or object. */
31
33
  load(primary: string, entry: SkillCandidate, options: SkillLookupOptions, user: boolean): Promise<SkillDefinition | undefined>;
34
+ private loadAdmitted;
32
35
  admissionValid(definition: SkillDefinition): Promise<boolean>;
33
36
  dispose(): Promise<void>;
34
37
  }