@dsh-electron/dsh-plugin-multi-root-workspace 0.1.7 → 0.1.9

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 (48) hide show
  1. package/CHANGELOG.en.md +16 -1
  2. package/CHANGELOG.md +16 -1
  3. package/README.en.md +21 -13
  4. package/README.md +29 -21
  5. package/cordis.patch.yml +18 -4
  6. package/lib/auto-review-packages-nRjnb4pU.js +15 -0
  7. package/lib/auto-review.js +166 -0
  8. package/lib/{command-CESuwlVt.js → command-Bwd6EGNV.js} +3 -3
  9. package/lib/command.js +1 -1
  10. package/lib/{compat-DF_yL1bB.js → compat-CagUNzRB.js} +176 -29
  11. package/lib/compat.js +1 -1
  12. package/lib/fork-CzbeWxHp.js +670 -0
  13. package/lib/{fs-9JIzGaf3.js → fs-BTXySwSG.js} +2 -6
  14. package/lib/fs.js +1 -1
  15. package/lib/index.js +8 -8
  16. package/lib/{instructions-D19jRxN0.js → instructions-CGNS3H0H.js} +211 -57
  17. package/lib/instructions.js +1 -1
  18. package/lib/lsp.js +1 -1
  19. package/lib/{registry-DCxC5Lay.js → registry-BClm6wfh.js} +6 -6
  20. package/lib/registry.js +1 -1
  21. package/lib/review-context-D-ohSVmA.js +98 -0
  22. package/lib/review-context.js +2 -0
  23. package/lib/{roots-B-0l8mNG.js → roots-Ca_W1S9n.js} +1 -1
  24. package/lib/{sandbox-Kup4_ZIb.js → sandbox-CXk5h-ct.js} +4 -8
  25. package/lib/sandbox.js +1 -1
  26. package/lib/scope.js +14 -5
  27. package/lib/types/auto-review/entry.d.ts +5 -0
  28. package/lib/types/compat/auto-review-contract.d.ts +3 -0
  29. package/lib/types/compat/auto-review-loader.d.ts +33 -0
  30. package/lib/types/compat/auto-review-packages.d.ts +1 -0
  31. package/lib/types/compat/dsh-version.d.ts +56 -8
  32. package/lib/types/compat/instruction-activity.d.ts +17 -0
  33. package/lib/types/compat/pty-marker.d.ts +1 -1
  34. package/lib/types/compat/review-session.d.ts +2 -0
  35. package/lib/types/compat.d.ts +15 -5
  36. package/lib/types/index.d.ts +1 -1
  37. package/lib/types/instructions.d.ts +18 -1
  38. package/lib/types/review-context.d.ts +49 -0
  39. package/lib/types/scope.d.ts +5 -0
  40. package/lib/windows-runner.js +2 -2
  41. package/lib/workspace-files.js +2 -2
  42. package/package.json +47 -7
  43. package/src/auto-review/FORK.md +26 -0
  44. package/src/auto-review/LICENSE +21 -0
  45. package/src/auto-review/README.en.md +22 -0
  46. package/src/auto-review/README.md +22 -0
  47. package/lib/{registry-lease-win32-DTO6QpT9.js → registry-lease-win32-DVTq9d4D.js} +1 -1
  48. package/lib/{windows-runner-contract-CEfmZzu0.js → windows-runner-contract-B5R2K3mM.js} +1 -1
@@ -1,9 +1,9 @@
1
1
  import { brandMultiRootProvider } from "./providers.js";
2
2
  import { dirname, sep } from "node:path";
3
3
  import { writableRoots } from "@deepseek-ai/dsh-sandbox";
4
+ import { stat } from "node:fs/promises";
4
5
  import { FsError } from "@deepseek-ai/dsh-fs";
5
6
  import { LocalFileSystem } from "@deepseek-ai/dsh-fs-local";
6
- import { stat } from "node:fs/promises";
7
7
  //#region src/containment.ts
8
8
  const MISSING_CODES = /* @__PURE__ */ new Set(["ENOENT", "ENOTDIR"]);
9
9
  function isMissing(error) {
@@ -65,11 +65,7 @@ async function isPathUnder(path, root, caseSensitive = process.platform !== "win
65
65
  * roots.
66
66
  */
67
67
  var MultiRootFileSystem = class extends LocalFileSystem {
68
- static inject = [
69
- "multiRootCompat",
70
- "sandboxPolicy",
71
- "multiRootScope"
72
- ];
68
+ static inject = ["sandboxPolicy", "multiRootScope"];
73
69
  defaultMode;
74
70
  constructor(ctx, config) {
75
71
  super(ctx, config);
package/lib/fs.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import "./providers.js";
2
- import { t as MultiRootFileSystem } from "./fs-9JIzGaf3.js";
2
+ import { t as MultiRootFileSystem } from "./fs-BTXySwSG.js";
3
3
  export { MultiRootFileSystem, MultiRootFileSystem as default };
package/lib/index.js CHANGED
@@ -1,13 +1,13 @@
1
- import { c as agentPtyMarkerVerdict, d as OPTIONAL_CORE_PACKAGES, f as REQUIRED_CORE_PACKAGES, g as readInstalledVersion, h as compatEnforcement, i as assertSupportedInstallation, l as assertAgentPtyMarker, m as classifyInstallation, n as MultiRootCompatService, o as AGENT_PTY_MARKER, p as SUPPORTED_DSH_RELEASES, s as PtyMarkerContractError, t as DshCompatUnsupportedError, u as CORE_PACKAGES } from "./compat-DF_yL1bB.js";
2
- import { a as composeInstructionMessage, c as createInstructionMessage, l as instructionsApi, r as PLUGIN_SOURCE, t as DEFAULT_MAX_BYTES, u as renderInstructions } from "./instructions-D19jRxN0.js";
3
- import { n as widenConfined, t as MultiRootSandboxProvider } from "./sandbox-Kup4_ZIb.js";
4
- import { a as classifyStoredRoots, c as indexedStatuses, i as canonicalRoot, l as isCanonicallyUnder, m as validateRootCandidate, n as additionalRootId, p as resolveRootRef, r as availableRoots, s as expandRootInput, t as RootValidationError, u as removeStatusAt } from "./roots-B-0l8mNG.js";
1
+ import { a as composeInstructionMessage, c as createInstructionMessage, l as instructionsApi, r as PLUGIN_SOURCE, t as DEFAULT_MAX_BYTES, u as renderInstructions } from "./instructions-CGNS3H0H.js";
2
+ import { a as classifyStoredRoots, c as indexedStatuses, i as canonicalRoot, l as isCanonicallyUnder, m as validateRootCandidate, n as additionalRootId, p as resolveRootRef, r as availableRoots, s as expandRootInput, t as RootValidationError, u as removeStatusAt } from "./roots-Ca_W1S9n.js";
3
+ import { _ as classifyInstallation, c as agentPtyMarkerVerdict, d as HOST_SERVICE_PACKAGES, f as OPTIONAL_CORE_PACKAGES, g as SUPPORTED_DSH_RELEASES, h as RUNTIME_OPTIONAL_PACKAGES, i as assertSupportedInstallation, l as assertAgentPtyMarker, m as RUNNER_ANCHORED_PACKAGES, n as MultiRootCompatService, o as AGENT_PTY_MARKER, p as REQUIRED_CORE_PACKAGES, s as PtyMarkerContractError, t as DshCompatUnsupportedError, u as CORE_PACKAGES, v as compatEnforcement, y as readInstalledVersion } from "./compat-CagUNzRB.js";
4
+ import { n as widenConfined, t as MultiRootSandboxProvider } from "./sandbox-CXk5h-ct.js";
5
5
  import { MULTI_ROOT_PROVIDER_BRAND, MultiRootProviders, brandMultiRootProvider, isMultiRootProvider } from "./providers.js";
6
- import { t as MultiRootFileSystem } from "./fs-9JIzGaf3.js";
6
+ import { t as MultiRootFileSystem } from "./fs-BTXySwSG.js";
7
7
  import { a as parseRevealedView, i as parsePanelCall, n as PANEL_ENDPOINTS, o as parseRootsView, t as PANEL_CHANNEL } from "./contract-DZ4zczDT.js";
8
8
  import { MultiRootScopeService, renderWorkspaceRootsContext, sanitizeAdditionalRoots } from "./scope.js";
9
- import { a as MultiRootRegistry, c as multiRootDomainSpec, i as MAX_ALIAS_LENGTH, l as RegistryAuthorityLease, n as DOMAIN_NAME, o as REGISTRY_CONTENDED_MESSAGE, r as MAX_ADDITIONAL_ROOTS_CEILING, t as DEFAULT_MAX_ADDITIONAL_ROOTS, u as RegistryLeaseContendedError } from "./registry-DCxC5Lay.js";
10
- import { a as renderRootsReport, c as revealArgv, i as parseFoldersCommand, o as resolvePanelPrimaryRoot, t as COMMAND_NAME } from "./command-CESuwlVt.js";
9
+ import { a as MultiRootRegistry, c as multiRootDomainSpec, i as MAX_ALIAS_LENGTH, l as RegistryAuthorityLease, n as DOMAIN_NAME, o as REGISTRY_CONTENDED_MESSAGE, r as MAX_ADDITIONAL_ROOTS_CEILING, t as DEFAULT_MAX_ADDITIONAL_ROOTS, u as RegistryLeaseContendedError } from "./registry-BClm6wfh.js";
10
+ import { a as renderRootsReport, c as revealArgv, i as parseFoldersCommand, o as resolvePanelPrimaryRoot, t as COMMAND_NAME } from "./command-Bwd6EGNV.js";
11
11
  //#region src/index.ts
12
12
  /**
13
13
  * Carrier plugin face for the bare-package-name loader row (see the module
@@ -18,4 +18,4 @@ import { a as renderRootsReport, c as revealArgv, i as parseFoldersCommand, o as
18
18
  const inject = [];
19
19
  function apply() {}
20
20
  //#endregion
21
- export { AGENT_PTY_MARKER, COMMAND_NAME, CORE_PACKAGES, DEFAULT_MAX_ADDITIONAL_ROOTS, DEFAULT_MAX_BYTES, DOMAIN_NAME, DshCompatUnsupportedError, MAX_ADDITIONAL_ROOTS_CEILING, MAX_ALIAS_LENGTH, MULTI_ROOT_PROVIDER_BRAND, MultiRootCompatService, MultiRootFileSystem, MultiRootProviders, MultiRootRegistry, MultiRootSandboxProvider, MultiRootScopeService, OPTIONAL_CORE_PACKAGES, PANEL_CHANNEL, PANEL_ENDPOINTS, PLUGIN_SOURCE, PtyMarkerContractError, REGISTRY_CONTENDED_MESSAGE, REQUIRED_CORE_PACKAGES, RegistryAuthorityLease, RegistryLeaseContendedError, RootValidationError, SUPPORTED_DSH_RELEASES, additionalRootId, agentPtyMarkerVerdict, apply, assertAgentPtyMarker, assertSupportedInstallation, availableRoots, brandMultiRootProvider, canonicalRoot, classifyInstallation, classifyStoredRoots, compatEnforcement, composeInstructionMessage, createInstructionMessage, expandRootInput, indexedStatuses, inject, instructionsApi, isCanonicallyUnder, isMultiRootProvider, multiRootDomainSpec, parseFoldersCommand, parsePanelCall, parseRevealedView, parseRootsView, readInstalledVersion, removeStatusAt, renderInstructions, renderRootsReport, renderWorkspaceRootsContext, resolvePanelPrimaryRoot, resolveRootRef, revealArgv, sanitizeAdditionalRoots, validateRootCandidate, widenConfined };
21
+ export { AGENT_PTY_MARKER, COMMAND_NAME, CORE_PACKAGES, DEFAULT_MAX_ADDITIONAL_ROOTS, DEFAULT_MAX_BYTES, DOMAIN_NAME, DshCompatUnsupportedError, HOST_SERVICE_PACKAGES, MAX_ADDITIONAL_ROOTS_CEILING, MAX_ALIAS_LENGTH, MULTI_ROOT_PROVIDER_BRAND, MultiRootCompatService, MultiRootFileSystem, MultiRootProviders, MultiRootRegistry, MultiRootSandboxProvider, MultiRootScopeService, OPTIONAL_CORE_PACKAGES, PANEL_CHANNEL, PANEL_ENDPOINTS, PLUGIN_SOURCE, PtyMarkerContractError, REGISTRY_CONTENDED_MESSAGE, REQUIRED_CORE_PACKAGES, RUNNER_ANCHORED_PACKAGES, RUNTIME_OPTIONAL_PACKAGES, RegistryAuthorityLease, RegistryLeaseContendedError, RootValidationError, SUPPORTED_DSH_RELEASES, additionalRootId, agentPtyMarkerVerdict, apply, assertAgentPtyMarker, assertSupportedInstallation, availableRoots, brandMultiRootProvider, canonicalRoot, classifyInstallation, classifyStoredRoots, compatEnforcement, composeInstructionMessage, createInstructionMessage, expandRootInput, indexedStatuses, inject, instructionsApi, isCanonicallyUnder, isMultiRootProvider, multiRootDomainSpec, parseFoldersCommand, parsePanelCall, parseRevealedView, parseRootsView, readInstalledVersion, removeStatusAt, renderInstructions, renderRootsReport, renderWorkspaceRootsContext, resolvePanelPrimaryRoot, resolveRootRef, revealArgv, sanitizeAdditionalRoots, validateRootCandidate, widenConfined };
@@ -1,12 +1,13 @@
1
1
  import { t as effectiveDirectory } from "./session-directory-BLOstut1.js";
2
2
  import { t as aroundTool } from "./tool-context-CpgIFcCB.js";
3
- import { l as isCanonicallyUnder } from "./roots-B-0l8mNG.js";
3
+ import { l as isCanonicallyUnder } from "./roots-Ca_W1S9n.js";
4
4
  import { createRequire } from "node:module";
5
- import "@deepseek-ai/cordis";
6
- import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
7
5
  import { createHash } from "node:crypto";
8
- import { AsyncLocalStorage } from "node:async_hooks";
6
+ import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
7
+ import "@deepseek-ai/cordis";
9
8
  import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
9
+ import { stat } from "node:fs/promises";
10
+ import { AsyncLocalStorage } from "node:async_hooks";
10
11
  //#region src/compat/agent-instructions.ts
11
12
  /**
12
13
  * Compatibility adapter for upstream's instruction discovery and rendering.
@@ -224,6 +225,95 @@ function toolResultFailed(message) {
224
225
  return block.isError === true;
225
226
  }
226
227
  //#endregion
228
+ //#region src/compat/instruction-activity.ts
229
+ /** Public durable file activity, normalized without loading an optional tool runtime. */
230
+ function ptcFileActivity(event) {
231
+ const row = event;
232
+ const data = row.data;
233
+ if (typeof data?.subCallId !== "string") return void 0;
234
+ const id = `ptc:${data.subCallId}`;
235
+ if (row.type === "tool/ptc-dispatch-start" && typeof data.name === "string") {
236
+ const args = JSON.stringify(data.arguments);
237
+ if (args === void 0) throw new Error("review-context: incomplete durable PTC action");
238
+ return {
239
+ kind: "start",
240
+ id,
241
+ name: data.name,
242
+ arguments: args
243
+ };
244
+ }
245
+ if (row.type === "tool/ptc-dispatch") return {
246
+ kind: "end",
247
+ id,
248
+ failed: data.isError !== false || data.error !== void 0
249
+ };
250
+ }
251
+ /** Rebuild read-only discovery candidates from successful durable activity after resume. */
252
+ function replayedInstructionTargets(events, origin) {
253
+ let cwd = origin;
254
+ const pending = /* @__PURE__ */ new Map();
255
+ const paths = /* @__PURE__ */ new Set();
256
+ for (const raw of events) {
257
+ const event = raw;
258
+ if (event.type === "working-directory/change" && typeof event.data.cwd === "string") {
259
+ cwd = event.data.cwd;
260
+ for (const call of pending.values()) call.directories.add(cwd);
261
+ continue;
262
+ }
263
+ const ptc = ptcFileActivity(event);
264
+ if (event.type === "tool/call" || ptc?.kind === "start") {
265
+ const call = ptc?.kind === "start" ? {
266
+ callId: ptc.id,
267
+ name: ptc.name,
268
+ arguments: ptc.arguments
269
+ } : event.data;
270
+ if (typeof call.callId !== "string" || typeof call.name !== "string" || typeof call.arguments !== "string") throw new Error("review-context: incomplete durable file activity");
271
+ pending.set(call.callId, {
272
+ name: call.name,
273
+ arguments: call.arguments,
274
+ directories: new Set(cwd === void 0 ? [] : [cwd])
275
+ });
276
+ if (pending.size > 512) throw new Error("review-context: durable activity index exceeded");
277
+ continue;
278
+ }
279
+ if (event.type !== "tool/result" && ptc?.kind !== "end") continue;
280
+ const message = event.data.message;
281
+ const id = ptc?.kind === "end" ? ptc.id : message?.source?.callId;
282
+ if (typeof id !== "string") throw new Error("review-context: incomplete durable result");
283
+ const call = pending.get(id);
284
+ pending.delete(id);
285
+ if (call === void 0) continue;
286
+ if (ptc?.kind === "end" ? ptc.failed : event.data.error !== void 0 || toolResultFailed(message ?? {})) continue;
287
+ const path = touchedPathOfToolCall(call.name, call.arguments);
288
+ if (path === void 0) continue;
289
+ if (isAbsolute(path)) paths.add(path);
290
+ else {
291
+ if (call.directories.size === 0) throw new Error("review-context: durable file activity has no cwd");
292
+ for (const directory of call.directories) paths.add(resolve(directory, path));
293
+ }
294
+ if (paths.size > 512) throw new Error("review-context: durable work index exceeded");
295
+ }
296
+ return [...paths];
297
+ }
298
+ /** fs tools whose success touches the file at their `file_path` argument. */
299
+ const FILE_TOUCH_TOOL_NAMES = {
300
+ read: true,
301
+ write: true,
302
+ edit: true
303
+ };
304
+ /** One explicit fs-tool target, shared with live touch capture. */
305
+ function touchedPathOfToolCall(name, argumentsJson) {
306
+ if (FILE_TOUCH_TOOL_NAMES[name] !== true) return void 0;
307
+ let parsed;
308
+ try {
309
+ parsed = JSON.parse(argumentsJson);
310
+ } catch {
311
+ return;
312
+ }
313
+ if (typeof parsed !== "object" || parsed === null || !("file_path" in parsed) || typeof parsed.file_path !== "string") return void 0;
314
+ return parsed.file_path.trim() || void 0;
315
+ }
316
+ //#endregion
227
317
  //#region src/compat/instruction-overlap.ts
228
318
  /** Recognize files already represented by the native instruction baseline in this step. */
229
319
  function nativeInstructionCopies(messages, cwd) {
@@ -327,12 +417,6 @@ const PLUGIN_SOURCE = "@dsh-electron/dsh-plugin-multi-root-workspace";
327
417
  const DEFAULT_MAX_BYTES = 65536;
328
418
  /** Largest instruction file this row will read; larger files are ignored. */
329
419
  const DEFAULT_MAX_SOURCE_BYTES = 32768;
330
- /** Tools whose SUCCESS makes the directories on the path to the file relevant. */
331
- const FILE_TOUCH_TOOL_NAMES = {
332
- read: true,
333
- write: true,
334
- edit: true
335
- };
336
420
  /** Cap on unanswered `tool/call` records remembered per session. */
337
421
  const MAX_PENDING_CALLS = 128;
338
422
  /** Cap on touched paths remembered per session; beyond it, touches are dropped. */
@@ -362,31 +446,6 @@ function relativeDirectoryOf(root, directory) {
362
446
  return suffix.length === 0 ? "." : suffix;
363
447
  }
364
448
  /**
365
- * The path one RAW tool call touched.
366
- *
367
- * `arguments` is the model-produced JSON string, so the path is only known after
368
- * parsing it; a call that names no `file_path`, or is not one of the tools whose
369
- * success makes a directory relevant, touched nothing this row cares about.
370
- * @param name - the tool name the model called.
371
- * @param argumentsJson - the tool call's raw arguments, exactly as produced.
372
- * @returns the trimmed absolute-or-relative path, or `undefined`.
373
- */
374
- function touchedPathOfToolCall(name, argumentsJson) {
375
- if (FILE_TOUCH_TOOL_NAMES[name] !== true) return void 0;
376
- let parsed;
377
- try {
378
- parsed = JSON.parse(argumentsJson);
379
- } catch {
380
- return;
381
- }
382
- if (typeof parsed !== "object" || parsed === null) return void 0;
383
- if (!("file_path" in parsed)) return void 0;
384
- const path = parsed.file_path;
385
- if (typeof path !== "string") return void 0;
386
- const trimmed = path.trim();
387
- return trimmed.length > 0 ? trimmed : void 0;
388
- }
389
- /**
390
449
  * The directory a touched path's instructions would live in.
391
450
  *
392
451
  * Tool arguments are the model's raw input, so a relative `file_path` is
@@ -460,7 +519,9 @@ function apply(ctx, config = {}) {
460
519
  if (existing !== void 0) return existing;
461
520
  const created = {
462
521
  scopes: /* @__PURE__ */ new Map(),
463
- touched: /* @__PURE__ */ new Set()
522
+ provenanceRoots: /* @__PURE__ */ new Set(),
523
+ touched: /* @__PURE__ */ new Set(),
524
+ worked: /* @__PURE__ */ new Set()
464
525
  };
465
526
  states.set(session, created);
466
527
  return created;
@@ -468,36 +529,88 @@ function apply(ctx, config = {}) {
468
529
  ctx.on("session/event", (session, event) => {
469
530
  const pendingCalls = callsBySession.get(session) ?? /* @__PURE__ */ new Map();
470
531
  callsBySession.set(session, pendingCalls);
471
- if (event.type === "tool/call") {
532
+ const ptc = ptcFileActivity(event);
533
+ if (event.type === "tool/call" || ptc?.kind === "start") {
472
534
  if (pendingCalls.size >= MAX_PENDING_CALLS) {
473
535
  const oldest = pendingCalls.keys().next();
474
536
  if (!oldest.done) pendingCalls.delete(oldest.value);
475
537
  }
476
- pendingCalls.set(event.data.callId, {
477
- name: event.data.name,
478
- arguments: event.data.arguments,
538
+ const call = ptc?.kind === "start" ? {
539
+ callId: ptc.id,
540
+ name: ptc.name,
541
+ arguments: ptc.arguments
542
+ } : event.type === "tool/call" ? event.data : void 0;
543
+ if (call !== void 0) pendingCalls.set(call.callId, {
544
+ name: call.name,
545
+ arguments: call.arguments,
479
546
  cwd: effectiveDirectory(ctx, session)
480
547
  });
481
548
  return;
482
549
  }
483
- if (event.type !== "tool/result") return;
484
- const callId = event.data.message.source.callId;
550
+ if (event.type !== "tool/result" && ptc?.kind !== "end") return;
551
+ const callId = ptc?.kind === "end" ? ptc.id : event.type === "tool/result" ? event.data.message.source.callId : "";
485
552
  const call = pendingCalls.get(callId);
486
553
  pendingCalls.delete(callId);
487
554
  if (call === void 0) return;
488
- if (event.data.error !== void 0 || toolResultFailed(event.data.message)) return;
555
+ if (ptc?.kind === "end" ? ptc.failed : event.type === "tool/result" && (event.data.error !== void 0 || toolResultFailed(event.data.message))) return;
489
556
  const path = call.observedPath ?? touchedPathOfToolCall(call.name, call.arguments);
490
557
  if (path === void 0) return;
491
558
  const state = stateOf(session);
559
+ const absolute = isAbsolute(path) ? path : call.cwd === void 0 ? void 0 : resolve(call.cwd, path);
560
+ if (absolute !== void 0) {
561
+ if (state.worked.size >= MAX_DELIVERED_SCOPES && !state.worked.has(absolute)) state.overflow = true;
562
+ else state.worked.add(absolute);
563
+ }
492
564
  if (state.touched.size >= MAX_TOUCHED_PATHS) return;
493
565
  if (isAbsolute(path)) state.touched.add(path);
494
566
  else if (call.cwd !== void 0) state.touched.add(resolve(call.cwd, path));
495
567
  });
496
568
  ctx.inject(["fs"], (scope) => {
569
+ scope.provide("multiRootInstructions", {
570
+ knownRoots(session) {
571
+ return [...states.get(session)?.provenanceRoots ?? []];
572
+ },
573
+ async snapshot(agent, roots, target, signal) {
574
+ signal.throwIfAborted();
575
+ if (roots.length === 0) return [];
576
+ const api = await instructionsApi();
577
+ if (api === void 0) throw new Error("review-context: instruction discovery unavailable");
578
+ const existing = states.get(agent.session);
579
+ if (existing?.overflow) throw new Error("review-context: successful work index exceeded");
580
+ const state = {
581
+ scopes: new Map([...existing?.scopes ?? []].map(([key, value]) => [key, {
582
+ ...value,
583
+ digest: void 0
584
+ }])),
585
+ provenanceRoots: new Set(existing?.provenanceRoots),
586
+ touched: /* @__PURE__ */ new Set([...existing?.touched ?? [], ...existing?.worked ?? []]),
587
+ worked: /* @__PURE__ */ new Set()
588
+ };
589
+ for (const path of replayedInstructionTargets(agent.session.snapshotEvents(), agent.session.header.cwd)) state.touched.add(path);
590
+ if (target !== void 0) state.touched.add(target);
591
+ const cwd = effectiveDirectory(scope, agent.session);
592
+ const reviewCwd = cwd === void 0 ? void 0 : canonicalPath(cwd);
593
+ const result = [];
594
+ for (const root of roots) {
595
+ const plan = await planRoot(scope, api, root, config, state, signal, true, reviewCwd);
596
+ for (const { file } of plan.deliveries) result.push({
597
+ root,
598
+ path: file.absolutePath,
599
+ relativeDirectory: relativeDirectoryOf(root, dirname(file.absolutePath)),
600
+ digest: digestOf(file.content),
601
+ text: file.content
602
+ });
603
+ }
604
+ signal.throwIfAborted();
605
+ if (Buffer.byteLength(JSON.stringify(result), "utf8") > maxBytes) throw new Error("review-context: instruction budget exceeded");
606
+ return result;
607
+ }
608
+ });
497
609
  scope.on("fs/observed", (target, _observation, actor) => {
498
610
  const exec = executions.getStore();
499
611
  if (exec?.agent === void 0 || FILE_TOUCH_TOOL_NAMES[exec.name] !== true || actor !== exec) return;
500
- const call = callsBySession.get(exec.agent.session)?.get(exec.callId);
612
+ const calls = callsBySession.get(exec.agent.session);
613
+ const call = calls?.get(exec.callId) ?? calls?.get(`ptc:${exec.callId}`);
501
614
  if (call !== void 0) call.observedPath = scope.fs.processPath(target);
502
615
  });
503
616
  scope.on("agent/pre-step", async ({ agent, messages, signal }, next) => {
@@ -531,7 +644,9 @@ async function pending(ctx, states, agent, config, maxBytes, signal, messages) {
531
644
  const session = agent.session;
532
645
  const state = states.get(session) ?? {
533
646
  scopes: /* @__PURE__ */ new Map(),
534
- touched: /* @__PURE__ */ new Set()
647
+ provenanceRoots: /* @__PURE__ */ new Set(),
648
+ touched: /* @__PURE__ */ new Set(),
649
+ worked: /* @__PURE__ */ new Set()
535
650
  };
536
651
  states.set(session, state);
537
652
  const evaluatedTouches = new Set(state.touched);
@@ -610,7 +725,10 @@ async function pending(ctx, states, agent, config, maxBytes, signal, messages) {
610
725
  if (text !== void 0 || liveUpdates.length > 0) {
611
726
  for (const root of revoked) for (const [key, entry] of state.scopes) if (entry.root === root) state.scopes.delete(key);
612
727
  for (const entry of gone) state.scopes.delete(entry.key);
613
- for (const entry of liveUpdates) state.scopes.set(entry.key, entry.scope);
728
+ for (const entry of liveUpdates) {
729
+ state.scopes.set(entry.key, entry.scope);
730
+ state.provenanceRoots.add(entry.scope.root);
731
+ }
614
732
  }
615
733
  return message;
616
734
  }
@@ -626,15 +744,19 @@ async function pending(ctx, states, agent, config, maxBytes, signal, messages) {
626
744
  * of that directory's ancestors up to the root, because discovery reports an
627
745
  * intermediate directory's file but only the exact directory's candidates
628
746
  * survive the filter below.
747
+ * A strict review can also seed its effective cwd chain without recording a
748
+ * successful touch; ordinary pre-step delivery never supplies that seed.
629
749
  * @param ctx - the row's context, for `fs` and the logger.
630
750
  * @param api - the adapted upstream instruction surface.
631
751
  * @param root - the canonical additional root.
632
752
  * @param config - the row's configuration.
633
753
  * @param state - the session's delivery state.
634
754
  * @param signal - the turn's cancellation signal.
755
+ * @param strict - fail on incomplete review discovery instead of deferring delivery.
756
+ * @param reviewCwd - review-only directory seed; never a successful touch.
635
757
  * @returns the files to send and the deliveries to withdraw.
636
758
  */
637
- async function planRoot(ctx, api, root, config, state, signal) {
759
+ async function planRoot(ctx, api, root, config, state, signal, strict = false, reviewCwd) {
638
760
  const delivered = /* @__PURE__ */ new Map();
639
761
  for (const [key, entry] of state.scopes) if (entry.root === root) delivered.set(key, entry);
640
762
  const dirs = /* @__PURE__ */ new Set([root]);
@@ -642,8 +764,9 @@ async function planRoot(ctx, api, root, config, state, signal) {
642
764
  const dir = dirname(entry.path);
643
765
  if (dir !== root) dirs.add(dir);
644
766
  }
645
- for (const touched of state.touched) {
646
- const parent = parentDirectoryOf(touched);
767
+ const parents = [...state.touched].map(parentDirectoryOf);
768
+ if (reviewCwd !== void 0) parents.push(reviewCwd);
769
+ for (const parent of parents) {
647
770
  if (parent === void 0 || !isCanonicallyUnder(parent, root)) continue;
648
771
  let dir = parent;
649
772
  for (;;) {
@@ -659,7 +782,7 @@ async function planRoot(ctx, api, root, config, state, signal) {
659
782
  signal.throwIfAborted();
660
783
  walks.push({
661
784
  relativeDirectory: relativeDirectoryOf(root, dir),
662
- read: await loadDirectory(ctx.fs, api, root, config, signal, dir)
785
+ read: await loadDirectory(ctx.fs, api, root, config, signal, dir, strict)
663
786
  });
664
787
  }
665
788
  walks.sort((left, right) => left.relativeDirectory.split(sep).length - right.relativeDirectory.split(sep).length || (left.relativeDirectory < right.relativeDirectory ? -1 : left.relativeDirectory > right.relativeDirectory ? 1 : 0));
@@ -678,7 +801,7 @@ async function planRoot(ctx, api, root, config, state, signal) {
678
801
  const previous = delivered.get(key);
679
802
  if (previous !== void 0 && previous.digest === digestOf(file.content)) continue;
680
803
  if (previous === void 0 && walk.relativeDirectory !== ".") {
681
- if (state.scopes.size >= MAX_DELIVERED_SCOPES) {
804
+ if (!strict && state.scopes.size >= MAX_DELIVERED_SCOPES) {
682
805
  if (!scopeCapWarned) {
683
806
  scopeCapWarned = true;
684
807
  ctx.logger.warn("multi-root workspace: additional-root instruction scopes capped at %d", MAX_DELIVERED_SCOPES);
@@ -708,6 +831,18 @@ async function planRoot(ctx, api, root, config, state, signal) {
708
831
  gone
709
832
  };
710
833
  }
834
+ /** Bounded provider read; an over-budget source never becomes a partial constraint. */
835
+ async function readReviewInstruction(fs, target, signal, maxBytes) {
836
+ const chunks = [];
837
+ let bytes = 0;
838
+ for await (const chunk of await fs.streamText(target, signal)) {
839
+ signal.throwIfAborted();
840
+ bytes += Buffer.byteLength(chunk, "utf8");
841
+ if (bytes > maxBytes) throw new Error("review-context: instruction source budget exceeded");
842
+ chunks.push(chunk);
843
+ }
844
+ return chunks.join("");
845
+ }
711
846
  /**
712
847
  * Walk from `cwd` up to `root` and read the instruction candidates it reports.
713
848
  *
@@ -734,9 +869,21 @@ async function planRoot(ctx, api, root, config, state, signal) {
734
869
  * @param cwd - the directory the walk starts from; always inside `root`.
735
870
  * @returns the directory read.
736
871
  */
737
- async function loadDirectory(fs, api, root, config, signal, cwd) {
738
- const maxSourceBytes = config.maxSourceBytes ?? 32768;
872
+ async function loadDirectory(fs, api, root, config, signal, cwd, strict = false) {
873
+ const configuredSourceBytes = config.maxSourceBytes ?? 32768;
874
+ if (strict && (!Number.isFinite(configuredSourceBytes) || configuredSourceBytes <= 0)) throw new Error("review-context: invalid source budget");
875
+ const maxSourceBytes = strict ? Math.min(configuredSourceBytes, DEFAULT_MAX_BYTES) : configuredSourceBytes;
739
876
  let candidates;
877
+ if (strict) for (const name of [...config.instructionFileCandidates ?? ["AGENTS.md", "CLAUDE.md"], ...config.localInstructionFileCandidates ?? ["AGENTS.local.md", "CLAUDE.local.md"]]) {
878
+ if (basename(name) !== name || name === "." || name === "..") throw new Error("review-context: invalid instruction candidate");
879
+ signal.throwIfAborted();
880
+ try {
881
+ await stat(resolve(cwd, name));
882
+ } catch (error) {
883
+ const code = error.code;
884
+ if (code !== "ENOENT" && code !== "ENOTDIR") throw new Error("review-context: instruction probe failed");
885
+ }
886
+ }
740
887
  try {
741
888
  candidates = await api.discover({
742
889
  cwd,
@@ -745,7 +892,8 @@ async function loadDirectory(fs, api, root, config, signal, cwd) {
745
892
  ...config.instructionFileCandidates === void 0 ? {} : { instructionFileCandidates: config.instructionFileCandidates },
746
893
  ...config.localInstructionFileCandidates === void 0 ? {} : { localInstructionFileCandidates: config.localInstructionFileCandidates }
747
894
  });
748
- } catch {
895
+ } catch (error) {
896
+ if (strict) throw error;
749
897
  return {
750
898
  candidates: [],
751
899
  files: [],
@@ -762,15 +910,21 @@ async function loadDirectory(fs, api, root, config, signal, cwd) {
762
910
  if (!isCanonicallyUnder(path, root)) continue;
763
911
  const info = await fs.stat(target, signal);
764
912
  if (info === void 0 || info.type !== "file") continue;
765
- if (info.size !== void 0 && info.size > maxSourceBytes) continue;
766
- const content = await fs.readText(target, signal);
913
+ if (info.size !== void 0 && info.size > maxSourceBytes) {
914
+ if (strict) throw new Error("review-context: instruction source budget exceeded");
915
+ continue;
916
+ }
917
+ const content = strict ? await readReviewInstruction(fs, target, signal, maxSourceBytes) : await fs.readText(target, signal);
918
+ if (strict && Buffer.byteLength(content, "utf8") > maxSourceBytes) throw new Error("review-context: instruction source budget exceeded");
919
+ if (strict && canonicalPath(candidate.absolutePath) !== path) throw new Error("review-context: instruction identity changed");
767
920
  if (canonicalPath(candidate.absolutePath) !== path || !isCanonicallyUnder(path, root)) continue;
768
921
  files.push({
769
922
  ...candidate,
770
923
  displayPath: candidate.absolutePath,
771
924
  content
772
925
  });
773
- } catch {
926
+ } catch (error) {
927
+ if (strict) throw error;
774
928
  continue;
775
929
  }
776
930
  }
@@ -1,2 +1,2 @@
1
- import { a as composeInstructionMessage, i as apply, n as DEFAULT_MAX_SOURCE_BYTES, o as inject, r as PLUGIN_SOURCE, s as touchedPathOfToolCall, t as DEFAULT_MAX_BYTES } from "./instructions-D19jRxN0.js";
1
+ import { a as composeInstructionMessage, i as apply, n as DEFAULT_MAX_SOURCE_BYTES, o as inject, r as PLUGIN_SOURCE, s as touchedPathOfToolCall, t as DEFAULT_MAX_BYTES } from "./instructions-CGNS3H0H.js";
2
2
  export { DEFAULT_MAX_BYTES, DEFAULT_MAX_SOURCE_BYTES, PLUGIN_SOURCE, apply, composeInstructionMessage, inject, touchedPathOfToolCall };
package/lib/lsp.js CHANGED
@@ -1,6 +1,6 @@
1
+ import { t as wrapMethod } from "./method-wrapper-CFY3p3j2.js";
1
2
  import { t as aroundTool } from "./tool-context-CpgIFcCB.js";
2
3
  import { t as routeWorkspacePath } from "./workspace-routing-YPxaQ4V3.js";
3
- import { t as wrapMethod } from "./method-wrapper-CFY3p3j2.js";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  //#region src/lsp.ts
6
6
  const inject = [
@@ -1,10 +1,10 @@
1
- import { i as isWindowsNetworkOrDevicePath } from "./windows-runner-contract-CEfmZzu0.js";
2
- import { a as classifyStoredRoots, d as reservedRootBoundary, f as resolveRootIndex, i as canonicalRoot, m as validateRootCandidate, n as additionalRootId, r as availableRoots, s as expandRootInput, t as RootValidationError, u as removeStatusAt } from "./roots-B-0l8mNG.js";
1
+ import { i as isWindowsNetworkOrDevicePath } from "./windows-runner-contract-B5R2K3mM.js";
2
+ import { a as classifyStoredRoots, d as reservedRootBoundary, f as resolveRootIndex, i as canonicalRoot, m as validateRootCandidate, n as additionalRootId, r as availableRoots, s as expandRootInput, t as RootValidationError, u as removeStatusAt } from "./roots-Ca_W1S9n.js";
3
3
  import { createRequire } from "node:module";
4
- import { Service } from "@deepseek-ai/cordis";
5
4
  import { closeSync, fstatSync, mkdirSync, openSync, statSync } from "node:fs";
6
- import { dirname, isAbsolute } from "node:path";
7
5
  import { randomUUID } from "node:crypto";
6
+ import { dirname, isAbsolute } from "node:path";
7
+ import { Service } from "@deepseek-ai/cordis";
8
8
  import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
9
9
  import { tryLockExclusive } from "@deepseek-ai/node-addon-system/flock";
10
10
  //#region src/compat/storage-zod.ts
@@ -123,7 +123,7 @@ var RegistryAuthorityLease = class RegistryAuthorityLease {
123
123
  mode: 448
124
124
  });
125
125
  if (process.platform === "win32") {
126
- const { acquireLockHandleWin32 } = await import("./registry-lease-win32-DTO6QpT9.js");
126
+ const { acquireLockHandleWin32 } = await import("./registry-lease-win32-DVTq9d4D.js");
127
127
  let handle;
128
128
  try {
129
129
  handle = await acquireLockHandleWin32(leasePath);
@@ -175,7 +175,7 @@ var RegistryAuthorityLease = class RegistryAuthorityLease {
175
175
  if (this.released) return;
176
176
  this.released = true;
177
177
  if (this.held.kind === "win32") {
178
- const { releaseLockHandleWin32 } = await import("./registry-lease-win32-DTO6QpT9.js");
178
+ const { releaseLockHandleWin32 } = await import("./registry-lease-win32-DVTq9d4D.js");
179
179
  await releaseLockHandleWin32(this.held.handle);
180
180
  return;
181
181
  }
package/lib/registry.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as MultiRootRegistry, c as multiRootDomainSpec, i as MAX_ALIAS_LENGTH, n as DOMAIN_NAME, o as REGISTRY_CONTENDED_MESSAGE, r as MAX_ADDITIONAL_ROOTS_CEILING, s as TABLE_NAME, t as DEFAULT_MAX_ADDITIONAL_ROOTS } from "./registry-DCxC5Lay.js";
1
+ import { a as MultiRootRegistry, c as multiRootDomainSpec, i as MAX_ALIAS_LENGTH, n as DOMAIN_NAME, o as REGISTRY_CONTENDED_MESSAGE, r as MAX_ADDITIONAL_ROOTS_CEILING, s as TABLE_NAME, t as DEFAULT_MAX_ADDITIONAL_ROOTS } from "./registry-BClm6wfh.js";
2
2
  export { DEFAULT_MAX_ADDITIONAL_ROOTS, DOMAIN_NAME, MAX_ADDITIONAL_ROOTS_CEILING, MAX_ALIAS_LENGTH, MultiRootRegistry, MultiRootRegistry as default, REGISTRY_CONTENDED_MESSAGE, TABLE_NAME, multiRootDomainSpec };
@@ -0,0 +1,98 @@
1
+ import { t as effectiveDirectory } from "./session-directory-BLOstut1.js";
2
+ import { s as touchedPathOfToolCall } from "./instructions-CGNS3H0H.js";
3
+ import { l as isCanonicallyUnder } from "./roots-Ca_W1S9n.js";
4
+ import { createHash } from "node:crypto";
5
+ import { resolve } from "node:path";
6
+ import { Service } from "@deepseek-ai/cordis";
7
+ import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
8
+ //#region src/compat/auto-review-contract.ts
9
+ /** The companion's joint support contract is deliberately narrower than the ordinary bundle. */
10
+ const AUTO_REVIEW_RELEASE = "0.2.1-rc.2";
11
+ function assertAutoReviewRelease(release) {
12
+ if (release !== "0.2.1-rc.2") throw new Error(`multi-root Auto Review requires exactly DSH ${AUTO_REVIEW_RELEASE}`);
13
+ }
14
+ //#endregion
15
+ //#region src/compat/review-session.ts
16
+ /** Fingerprint only the native instruction producer's visible authority data. */
17
+ function nativeInstructionFingerprint(session) {
18
+ const rows = [];
19
+ for (const seq of session.surface.nodes) {
20
+ const event = session.eventAt(seq);
21
+ if (event?.type === "user/message" && event.data.source.kind === "agent-instructions") rows.push(event.data);
22
+ }
23
+ return createHash("sha256").update(JSON.stringify(rows)).digest("hex");
24
+ }
25
+ //#endregion
26
+ //#region src/review-context.ts
27
+ /** Read-only, current host context for the optional Auto Review companion. */
28
+ /** All authority remains in the existing Registry/scope and instruction index. */
29
+ var MultiRootReviewContext = class extends Service {
30
+ static inject = [
31
+ "multiRootCompat",
32
+ "multiRootProviders",
33
+ "multiRootScope",
34
+ "multiRootRegistry",
35
+ "multiRootInstructions",
36
+ "sandboxPolicy",
37
+ "workingDirectory",
38
+ "fs"
39
+ ];
40
+ constructor(ctx) {
41
+ super(ctx, "multiRootReviewContext");
42
+ }
43
+ assertCompatible() {
44
+ assertAutoReviewRelease(this.ctx.multiRootCompat.release);
45
+ }
46
+ async build(exec, signal) {
47
+ signal.throwIfAborted();
48
+ const agent = exec.agent;
49
+ if (agent === void 0) throw new Error("review-context: missing session");
50
+ const policy = this.ctx.sandboxPolicy.resolve({ session: agent.session });
51
+ const primary = canonicalPath(policy.workspaceRoot);
52
+ if (agent.session.header.cwd !== void 0 && canonicalPath(agent.session.header.cwd) !== primary) throw new Error("review-context: inconsistent session origin");
53
+ await this.ctx.multiRootRegistry.refresh(primary);
54
+ signal.throwIfAborted();
55
+ const cwd = effectiveDirectory(this.ctx, agent.session);
56
+ if (cwd === void 0) throw new Error("review-context: missing effective cwd");
57
+ const scope = this.ctx.multiRootScope.previewForTool(policy, exec.name);
58
+ const environment = {
59
+ sessionOrigin: scope.primaryRoot,
60
+ effectiveCwd: cwd,
61
+ sandboxMode: policy.mode,
62
+ registryAuthority: scope.authority,
63
+ roots: scope.roots.map((root) => ({
64
+ path: root.path,
65
+ access: root.access,
66
+ effectiveWritable: scope.writableRoots.includes(root.path) && !scope.deniedRoots.some((deny) => isCanonicallyUnder(root.path, deny))
67
+ })),
68
+ writableRoots: scope.writableRoots,
69
+ deniedRoots: scope.deniedRoots,
70
+ ...scope.protectedHome === void 0 ? {} : { protectedHome: scope.protectedHome },
71
+ ambientWrites: policy.mode === "danger-full-access" && scope.authority === "available",
72
+ readConfinement: false,
73
+ primaryRootWritable: policy.mode !== "read-only" && !scope.deniedRoots.some((deny) => isCanonicallyUnder(scope.primaryRoot, deny)),
74
+ writeBoundary: scope.authority === "unavailable" && policy.mode !== "read-only" ? "primary-root-only; no shared temp grants; explicit denies take precedence" : policy.mode === "danger-full-access" ? "ambient host writes minus explicit deny boundaries; membership is not an access allowlist" : policy.mode === "workspace-write" ? "primary root, effective writable additional roots and upstream host temp grants; explicit denies take precedence" : "only effective writable additional roots; explicit denies take precedence"
75
+ };
76
+ const path = touchedPathOfToolCall(exec.name, JSON.stringify(exec.arguments));
77
+ const target = path === void 0 ? void 0 : this.ctx.fs.processPath(await this.ctx.fs.resolve(resolve(cwd, path), { signal }));
78
+ const constraints = await this.ctx.multiRootInstructions.snapshot(agent, scope.authority === "available" ? scope.additionalRoots : [], target, signal);
79
+ signal.throwIfAborted();
80
+ const payload = {
81
+ schemaVersion: 1,
82
+ environment,
83
+ instructionProvenanceRoots: [.../* @__PURE__ */ new Set([...scope.additionalRoots, ...this.ctx.multiRootInstructions.knownRoots(agent.session)])],
84
+ constraints,
85
+ completeness: "complete"
86
+ };
87
+ if (Buffer.byteLength(JSON.stringify(payload), "utf8") > 65536) throw new Error("review-context: context budget exceeded");
88
+ return {
89
+ ...payload,
90
+ fingerprint: createHash("sha256").update(JSON.stringify([payload, nativeInstructionFingerprint(agent.session)])).digest("hex")
91
+ };
92
+ }
93
+ async assertCurrent(exec, snapshot, signal) {
94
+ if ((await this.build(exec, signal)).fingerprint !== snapshot.fingerprint) throw new Error("review-context: snapshot changed; retry the tool call");
95
+ }
96
+ };
97
+ //#endregion
98
+ export { assertAutoReviewRelease as n, MultiRootReviewContext as t };
@@ -0,0 +1,2 @@
1
+ import { t as MultiRootReviewContext } from "./review-context-D-ohSVmA.js";
2
+ export { MultiRootReviewContext, MultiRootReviewContext as default };
@@ -1,4 +1,4 @@
1
- import { i as isWindowsNetworkOrDevicePath } from "./windows-runner-contract-CEfmZzu0.js";
1
+ import { i as isWindowsNetworkOrDevicePath } from "./windows-runner-contract-B5R2K3mM.js";
2
2
  import { statSync } from "node:fs";
3
3
  import { isAbsolute, join, sep } from "node:path";
4
4
  import { canonicalPath } from "@deepseek-ai/dsh-sandbox";