@dsh-electron/dsh-plugin-multi-root-workspace 0.1.15 → 0.1.16

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 (39) hide show
  1. package/CHANGELOG.en.md +13 -1
  2. package/CHANGELOG.md +13 -1
  3. package/README.en.md +6 -6
  4. package/README.md +6 -6
  5. package/lib/auto-review.js +1 -1
  6. package/lib/client.js +761 -74
  7. package/lib/client.js.map +1 -1
  8. package/lib/{command-y5tfcAeC.js → command-DaVJ-mK3.js} +55 -1
  9. package/lib/command.js +1 -1
  10. package/lib/index.js +4 -4
  11. package/lib/{instructions-Ck9wp4J4.js → instructions-Ca80SkZq.js} +1 -1
  12. package/lib/instructions.js +1 -1
  13. package/lib/lsp.js +1 -1
  14. package/lib/process-fence.js +95 -12
  15. package/lib/{registry-DyDNDSJZ.js → registry-BibiukWk.js} +1 -1
  16. package/lib/{registry-operation-C2ZV3Jhw.js → registry-operation-CIlmt6_c.js} +5 -1
  17. package/lib/registry.js +1 -1
  18. package/lib/{review-context-B-oHkU6a.js → review-context-OxO-fMWR.js} +1 -1
  19. package/lib/review-context.js +1 -1
  20. package/lib/{sandbox-CZElTNPz.js → sandbox-aobdyQGi.js} +6 -3
  21. package/lib/sandbox.js +1 -1
  22. package/lib/scope.js +91 -4
  23. package/lib/skills.js +1 -1
  24. package/lib/types/client/files-tree/FilesTree.d.ts +49 -6
  25. package/lib/types/client/files-tree/adapter.d.ts +69 -16
  26. package/lib/types/client/files-tree/face.d.ts +31 -2
  27. package/lib/types/client/files-tree/locate.d.ts +74 -0
  28. package/lib/types/client/files-tree/navigator.d.ts +98 -0
  29. package/lib/types/client/index.d.ts +7 -0
  30. package/lib/types/client/locales.d.ts +2 -0
  31. package/lib/types/client/styles.d.ts +1 -1
  32. package/lib/types/compat/files-tree.d.ts +8 -1
  33. package/lib/types/compat/forge-entrypoints.d.ts +44 -0
  34. package/lib/types/compat/session-existence.d.ts +41 -0
  35. package/lib/types/registry-operation.d.ts +7 -0
  36. package/lib/types/roots.d.ts +3 -1
  37. package/lib/types/scope.d.ts +67 -1
  38. package/lib/workspace-files.js +1 -1
  39. package/package.json +1 -1
@@ -28,6 +28,24 @@ function connectionRpcFor(ctx, connection) {
28
28
  return connection.rpc;
29
29
  }
30
30
  //#endregion
31
+ //#region src/compat/session-existence.ts
32
+ /**
33
+ * Whether `sessionId` exists in the mounted persistence backend.
34
+ * @param ctx - the plugin fiber context; optional services read via `ctx.get`.
35
+ * @param sessionId - the panel request's session id.
36
+ * @param signal - the request's cancellation, forwarded when the backend takes options.
37
+ * @returns `true` only when a snapshot proves the session exists.
38
+ */
39
+ async function persistedSessionExists(ctx, sessionId, signal) {
40
+ try {
41
+ const persistence = ctx.get("sessionPersistence");
42
+ if (typeof persistence?.stat !== "function") return false;
43
+ return await persistence.stat(sessionId, signal === void 0 ? void 0 : { signal }) !== void 0;
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+ //#endregion
31
49
  //#region src/profile-isolation.ts
32
50
  /**
33
51
  * Profile pnpm import isolation — the host-side maintenance behind
@@ -1113,6 +1131,32 @@ function resolvePanelPrimaryRoot(ctx, sessionId) {
1113
1131
  if (session === void 0 || cwd === void 0 || cwd === "" || !isAbsolute(cwd)) throw new RootValidationError("session-not-found", `session "${sessionId}" is not an active host session`, { reference: sessionId });
1114
1132
  return cwd;
1115
1133
  }
1134
+ /**
1135
+ * Whether the panel's resolver would now name a primary root for the session —
1136
+ * the exact live-and-usable-cwd predicate, re-run so a resume that landed
1137
+ * between a rejection and its classification is recognized instead of
1138
+ * duplicating the cwd rule.
1139
+ */
1140
+ function panelSessionResolvable(ctx, sessionId) {
1141
+ try {
1142
+ resolvePanelPrimaryRoot(ctx, sessionId);
1143
+ return true;
1144
+ } catch {
1145
+ return false;
1146
+ }
1147
+ }
1148
+ /**
1149
+ * Whether the sessions store names no live session for `sessionId` — or
1150
+ * cannot even answer, in which case "not live" stays unproven and the
1151
+ * caller keeps the code the flow already had.
1152
+ */
1153
+ function liveSessionAbsent(ctx, sessionId) {
1154
+ try {
1155
+ return ctx.get("sessions")?.get(sessionId) === void 0;
1156
+ } catch {
1157
+ return false;
1158
+ }
1159
+ }
1116
1160
  /** Project one status into the panel's view. */
1117
1161
  function toRootView(status, index) {
1118
1162
  return {
@@ -1194,6 +1238,7 @@ async function rootsViewOf(ctx, registry, primaryRoot, request) {
1194
1238
  * through the channel.
1195
1239
  */
1196
1240
  async function dispatchPanelRequest(ctx, endpoint, payload, signal) {
1241
+ let panelSessionId;
1197
1242
  try {
1198
1243
  if (SKILLS_ENDPOINTS.includes(endpoint)) {
1199
1244
  const parsedSkills = skillsRequestSchema.safeParse(payload);
@@ -1231,6 +1276,7 @@ async function dispatchPanelRequest(ctx, endpoint, payload, signal) {
1231
1276
  }
1232
1277
  };
1233
1278
  const request = parsed.value;
1279
+ panelSessionId = request.sessionId;
1234
1280
  const registry = ctx.multiRootRegistry;
1235
1281
  const primaryRoot = resolvePanelPrimaryRoot(ctx, request.sessionId);
1236
1282
  switch (request.endpoint) {
@@ -1302,7 +1348,15 @@ async function dispatchPanelRequest(ctx, endpoint, payload, signal) {
1302
1348
  }
1303
1349
  }
1304
1350
  } catch (error) {
1305
- if (error instanceof RootValidationError || error instanceof AuthorityRpcError) return {
1351
+ if (error instanceof RootValidationError) return {
1352
+ ok: false,
1353
+ error: {
1354
+ code: error.code === "session-not-found" && panelSessionId !== void 0 && (panelSessionResolvable(ctx, panelSessionId) || liveSessionAbsent(ctx, panelSessionId) && await persistedSessionExists(ctx, panelSessionId, signal)) ? "session-not-live" : error.code,
1355
+ message: error.message,
1356
+ details: {}
1357
+ }
1358
+ };
1359
+ if (error instanceof AuthorityRpcError) return {
1306
1360
  ok: false,
1307
1361
  error: {
1308
1362
  code: error.code,
package/lib/command.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as renderRootsReport, c as revealArgv, i as parseFoldersCommand, n as apply, o as resolvePanelPrimaryRoot, r as inject, s as revealAccepted, t as COMMAND_NAME } from "./command-y5tfcAeC.js";
1
+ import { a as renderRootsReport, c as revealArgv, i as parseFoldersCommand, n as apply, o as resolvePanelPrimaryRoot, r as inject, s as revealAccepted, t as COMMAND_NAME } from "./command-DaVJ-mK3.js";
2
2
  export { COMMAND_NAME, apply, inject, parseFoldersCommand, renderRootsReport, resolvePanelPrimaryRoot, revealAccepted, revealArgv };
package/lib/index.js CHANGED
@@ -1,17 +1,17 @@
1
1
  import { r as renderInstructions, t as instructionsApi } from "./agent-instructions-BXuKhOzr.js";
2
2
  import { t as createInstructionMessage } from "./llm-message-CepyzEBQ.js";
3
- import { a as composeInstructionMessage, r as PLUGIN_SOURCE, t as DEFAULT_MAX_BYTES } from "./instructions-Ck9wp4J4.js";
3
+ import { a as composeInstructionMessage, r as PLUGIN_SOURCE, t as DEFAULT_MAX_BYTES } from "./instructions-Ca80SkZq.js";
4
4
  import { a as REQUIRED_CORE_PACKAGES, c as SUPPORTED_DSH_RELEASES, d as readInstalledVersion, i as OPTIONAL_CORE_PACKAGES, l as classifyInstallation, n as CORE_PACKAGES, o as RUNNER_ANCHORED_PACKAGES, r as HOST_SERVICE_PACKAGES, s as RUNTIME_OPTIONAL_PACKAGES, u as compatEnforcement } from "./dsh-version-DCwF7REk.js";
5
5
  import { a as classifyStoredRoots, c as indexedStatuses, g as resolveRootRef, i as canonicalRoot, l as isCanonicallyUnder, n as additionalRootId, p as removeStatusAt, r as availableRoots, s as expandRootInput, t as RootValidationError, y as validateRootCandidate } from "./roots-CmaWxRVy.js";
6
6
  import { c as agentPtyMarkerVerdict, i as assertSupportedInstallation, l as assertAgentPtyMarker, n as MultiRootCompatService, o as AGENT_PTY_MARKER, s as PtyMarkerContractError, t as DshCompatUnsupportedError } from "./compat-CTJbpCxu.js";
7
7
  import { l as widenConfined } from "./dialects-CdlG3RF9.js";
8
8
  import { MULTI_ROOT_PROVIDER_BRAND, MultiRootProviders, brandMultiRootProvider, isMultiRootProvider } from "./providers.js";
9
9
  import { t as MultiRootFileSystem } from "./fs-Uws0ZpmV.js";
10
- import { t as MultiRootSandboxProvider } from "./sandbox-CZElTNPz.js";
10
+ import { t as MultiRootSandboxProvider } from "./sandbox-aobdyQGi.js";
11
11
  import { a as parseRevealedView, i as parsePanelCall, n as PANEL_ENDPOINTS, o as parseRootsView, t as PANEL_CHANNEL } from "./contract-CStSEOcK.js";
12
12
  import { MultiRootScopeService, renderWorkspaceRootsContext, sanitizeAdditionalRoots } from "./scope.js";
13
- 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-DyDNDSJZ.js";
14
- import { a as renderRootsReport, c as revealArgv, i as parseFoldersCommand, o as resolvePanelPrimaryRoot, t as COMMAND_NAME } from "./command-y5tfcAeC.js";
13
+ 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-BibiukWk.js";
14
+ import { a as renderRootsReport, c as revealArgv, i as parseFoldersCommand, o as resolvePanelPrimaryRoot, t as COMMAND_NAME } from "./command-DaVJ-mK3.js";
15
15
  //#region src/index.ts
16
16
  /**
17
17
  * Carrier plugin face for the bare-package-name loader row (see the module
@@ -1,5 +1,5 @@
1
1
  import { t as effectiveDirectory } from "./session-directory-BLOstut1.js";
2
- import { c as withRegistryRead, s as withRegistryAdmission } from "./registry-operation-C2ZV3Jhw.js";
2
+ import { c as withRegistryAdmission, l as withRegistryRead } from "./registry-operation-CIlmt6_c.js";
3
3
  import { t as instructionsApi } from "./agent-instructions-BXuKhOzr.js";
4
4
  import { t as createInstructionMessage } from "./llm-message-CepyzEBQ.js";
5
5
  import { t as aroundTool } from "./tool-context-CCeY3mrK.js";
@@ -1,2 +1,2 @@
1
- import { a as composeInstructionMessage, i as apply, l as touchedPathOfToolCall, n as DEFAULT_MAX_SOURCE_BYTES, o as inject, r as PLUGIN_SOURCE, t as DEFAULT_MAX_BYTES } from "./instructions-Ck9wp4J4.js";
1
+ import { a as composeInstructionMessage, i as apply, l as touchedPathOfToolCall, n as DEFAULT_MAX_SOURCE_BYTES, o as inject, r as PLUGIN_SOURCE, t as DEFAULT_MAX_BYTES } from "./instructions-Ca80SkZq.js";
2
2
  export { DEFAULT_MAX_BYTES, DEFAULT_MAX_SOURCE_BYTES, PLUGIN_SOURCE, apply, composeInstructionMessage, inject, touchedPathOfToolCall };
package/lib/lsp.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as wrapMethod } from "./method-wrapper-D4oUCsEF.js";
2
- import { c as withRegistryRead } from "./registry-operation-C2ZV3Jhw.js";
2
+ import { l as withRegistryRead } from "./registry-operation-CIlmt6_c.js";
3
3
  import { t as aroundTool } from "./tool-context-CCeY3mrK.js";
4
4
  import { t as routeWorkspacePath } from "./workspace-routing-D_qwMUh-.js";
5
5
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -1,5 +1,5 @@
1
1
  import { t as wrapMethod } from "./method-wrapper-D4oUCsEF.js";
2
- import { s as withRegistryAdmission } from "./registry-operation-C2ZV3Jhw.js";
2
+ import { c as withRegistryAdmission } from "./registry-operation-CIlmt6_c.js";
3
3
  import { t as aroundTool } from "./tool-context-CCeY3mrK.js";
4
4
  import { r as opsCompatibilityToken } from "./ops-settings-UN2oOKEp.js";
5
5
  import { n as detectDialect, s as confineInstalled } from "./dialects-CdlG3RF9.js";
@@ -20,11 +20,11 @@ function originalProcessArgv(argv) {
20
20
  }
21
21
  //#endregion
22
22
  //#region src/compat/process-result.ts
23
- const object$1 = (value) => typeof value === "object" && value !== null;
23
+ const object$2 = (value) => typeof value === "object" && value !== null;
24
24
  const thenable = (value) => typeof value?.then === "function";
25
- const textOf = (value) => typeof value === "string" ? value : object$1(value) && typeof value.text === "string" ? value.text : "";
26
- const stderrOf = (value) => textOf(value.stderr) + (object$1(value.error) ? textOf(value.error.message) : "") + (object$1(value.failure) ? textOf(value.failure.message) : "");
27
- const exitCodeOf = (value) => typeof value.exitCode === "number" || value.exitCode === null ? value.exitCode : value.ok === false || object$1(value.error) || object$1(value.failure) ? 1 : 0;
25
+ const textOf = (value) => typeof value === "string" ? value : object$2(value) && typeof value.text === "string" ? value.text : "";
26
+ const stderrOf = (value) => textOf(value.stderr) + (object$2(value.error) ? textOf(value.error.message) : "") + (object$2(value.failure) ? textOf(value.failure.message) : "");
27
+ const exitCodeOf = (value) => typeof value.exitCode === "number" || value.exitCode === null ? value.exitCode : value.ok === false || object$2(value.error) || object$2(value.failure) ? 1 : 0;
28
28
  /** Denial and runner-failure verdicts for one settled process — the same
29
29
  * non-zero-exit + stderr-signature rules the sandbox executors apply: a fatal
30
30
  * runner line is an infrastructure failure, never a policy denial. */
@@ -42,7 +42,7 @@ const classifyOutcome = (exitCode, stderr, facts) => {
42
42
  const sandboxStamp = (value, facts, policy, stderr) => {
43
43
  const { denied, runnerFailed } = classifyOutcome(exitCodeOf(value), stderr ?? stderrOf(value), facts);
44
44
  return {
45
- ...object$1(value.sandbox) ? value.sandbox : {},
45
+ ...object$2(value.sandbox) ? value.sandbox : {},
46
46
  mode: policy.mode,
47
47
  enforcement: facts.enforcement,
48
48
  denied,
@@ -50,7 +50,7 @@ const sandboxStamp = (value, facts, policy, stderr) => {
50
50
  };
51
51
  };
52
52
  function stampProcessResult(value, facts, policy) {
53
- if (!object$1(value)) return value;
53
+ if (!object$2(value)) return value;
54
54
  return {
55
55
  ...value,
56
56
  sandbox: sandboxStamp(value, facts, policy)
@@ -65,8 +65,8 @@ function stampProcessResult(value, facts, policy) {
65
65
  */
66
66
  const liveHandle = (value) => thenable(value.done) || typeof value.done === "function" || Object.values(value).some((member) => typeof member === "function") || Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null;
67
67
  function decorateProcessOutcome(value, facts, policy, diagnostics) {
68
- if (object$1(value) && typeof value.then === "function") return value.then((result) => decorateProcessOutcome(result, facts, policy, diagnostics));
69
- if (object$1(value) && typeof value.result === "function") {
68
+ if (object$2(value) && typeof value.then === "function") return value.then((result) => decorateProcessOutcome(result, facts, policy, diagnostics));
69
+ if (object$2(value) && typeof value.result === "function") {
70
70
  let cached;
71
71
  let settled = false;
72
72
  wrapMethod(value, "result", (original) => function(...args) {
@@ -78,7 +78,7 @@ function decorateProcessOutcome(value, facts, policy, diagnostics) {
78
78
  });
79
79
  return value;
80
80
  }
81
- if (object$1(value) && liveHandle(value)) {
81
+ if (object$2(value) && liveHandle(value)) {
82
82
  const settle = () => {
83
83
  try {
84
84
  value.sandbox = sandboxStamp(value, facts, policy, diagnostics?.stderr?.());
@@ -142,6 +142,58 @@ function isAsyncProcessEntry(entry) {
142
142
  return typeof entry === "function" && entry.constructor.name === "AsyncFunction";
143
143
  }
144
144
  //#endregion
145
+ //#region src/compat/forge-entrypoints.ts
146
+ /**
147
+ * The forge-bundle service entries an admission can wrap, in this order.
148
+ * `worktrees.create`/`restore` take an `Agent` (the session rides on
149
+ * `agent.session`); every listed `forgePanel` entry takes the `Session` as
150
+ * its first argument. Entries not listed — background inspections, the
151
+ * isolated `forge-worktree` instance inside the panel's mount group — stay
152
+ * ledger-covered.
153
+ */
154
+ const FORGE_PROCESS_SERVICES = [{
155
+ service: "worktrees",
156
+ keys: ["create", "restore"]
157
+ }, {
158
+ service: "forgePanel",
159
+ keys: [
160
+ "branches",
161
+ "track",
162
+ "dismiss",
163
+ "refresh",
164
+ "localDiff",
165
+ "localText",
166
+ "changeRequestChecks",
167
+ "changeRequestDiff",
168
+ "changeRequestText",
169
+ "submitReview",
170
+ "markReady",
171
+ "merge",
172
+ "requestWorktree",
173
+ "cancelWorktree",
174
+ "stopCreation",
175
+ "retryWorktree",
176
+ "restoreWorktree",
177
+ "continueInProject"
178
+ ]
179
+ }];
180
+ const object$1 = (value) => typeof value === "object" && value !== null;
181
+ /**
182
+ * Read the session a foreign-service entry resolves its policy from — the
183
+ * first argument alone: a `Session`, or an `Agent`-shaped value carrying one.
184
+ * The probe is deliberately structural and narrow (`id` + `header`): it is a
185
+ * trust boundary over the upstream type, not a full validation — anything
186
+ * else runs unadmitted rather than let `sandboxPolicy.resolve` read fields a
187
+ * foreign argument lacks.
188
+ * @param first - the wrapped call's first argument.
189
+ * @returns the session, or `undefined` for a call that does not carry one.
190
+ */
191
+ function sessionOf(first) {
192
+ const candidate = object$1(first) && object$1(first.session) ? first.session : first;
193
+ const isSession = (value) => object$1(value) && typeof value.id === "string" && object$1(value.header);
194
+ return isSession(candidate) ? candidate : void 0;
195
+ }
196
+ //#endregion
145
197
  //#region src/process-fence.ts
146
198
  /**
147
199
  * `multi-root-process-fence`: the B9 seam for the one cell `ctx.sandbox.confine`
@@ -305,7 +357,7 @@ function apply(ctx) {
305
357
  if (hasConfinedProfile(spec.argv)) {
306
358
  if (!(spec.argv.length > prefix.length && prefix.every((segment, index) => spec.argv[index] === segment))) throw new Error("multi-root workspace: a confined spawn inside an OPS execution domain must carry this domain's own profile");
307
359
  } else argv = [...prefix, ...spec.argv];
308
- if (opsScope.admissionRequired && (hasConfinedProfile(spec.argv) || prefix.length > 0) && !opsScope.permitsProfile(argv)) throw new Error("multi-root workspace: a confined spawn requires a profile from this live admission");
360
+ if (opsScope.admissionRequired && (hasConfinedProfile(spec.argv) || prefix.length > 0) && !opsScope.permitsOperationProfile(argv)) throw new Error("multi-root workspace: a confined spawn requires a profile from this live admission");
309
361
  } else {
310
362
  argv = store === void 0 ? spec.argv : [...store.argvPrefix, ...spec.argv];
311
363
  if (scope.multiRootScope.admissionRequired && (store !== void 0 || hasConfinedProfile(argv)) && !scope.multiRootScope.permitsProfile(argv)) throw new Error("multi-root workspace: a confined spawn requires a profile from this live admission");
@@ -314,7 +366,7 @@ function apply(ctx) {
314
366
  ...spec,
315
367
  argv
316
368
  });
317
- const handle = store !== void 0 || admissionScope.admissionRequired && admissionScope.permitsProfile(argv) ? withOpsSpawnPermit(opsState, launch) : launch();
369
+ const handle = store !== void 0 || admissionScope.admissionRequired && admissionScope.permitsOperationProfile(argv) ? withOpsSpawnPermit(opsState, launch) : launch();
318
370
  if (store === void 0) return handle;
319
371
  const stderr = object(handle) && object(handle.collected) && object(handle.collected.stderr) && typeof handle.collected.stderr.readFrom === "function" ? handle.collected.stderr : void 0;
320
372
  if (stderr !== void 0) store.stderr = () => {
@@ -401,6 +453,37 @@ function apply(ctx) {
401
453
  if (runtime === void 0) return;
402
454
  for (const key of ptcEntrypoints(runtime)) deniedEntry(scope, runtime, key);
403
455
  });
456
+ const admitCalls = (service, keys) => {
457
+ ctx.inject([service], (scope) => {
458
+ const target = scope.get(service);
459
+ if (target === void 0) return;
460
+ for (const key of keys) try {
461
+ if (!isAsyncProcessEntry(target[key])) {
462
+ if (key in target) ctx.logger.warn(`multi-root workspace: foreign process entry ${service}.${key} exists but is not wrappable and stays outside the admission; any confined spawn it performs relies on the ledger`);
463
+ continue;
464
+ }
465
+ scope.effect(() => wrapMethod(target, key, (original) => async function(first, ...rest) {
466
+ const session = sessionOf(first);
467
+ if (session === void 0) return original.call(this, first, ...rest);
468
+ const policy = ctx.sandboxPolicy.resolve({ session });
469
+ let ran = false;
470
+ try {
471
+ return await withRegistryAdmission(ctx, policy, "process", () => {
472
+ ran = true;
473
+ return original.call(this, first, ...rest);
474
+ });
475
+ } catch (error) {
476
+ if (ran) throw error;
477
+ ctx.logger.warn(`multi-root workspace: admission for ${service}.${key} is unavailable; running unadmitted: ${error instanceof Error ? error.message : String(error)}`);
478
+ return original.call(this, first, ...rest);
479
+ }
480
+ }));
481
+ } catch (error) {
482
+ ctx.logger.warn(`multi-root workspace: foreign process entry ${service}.${key} is not wrappable: ${error instanceof Error ? error.message : String(error)}`);
483
+ }
484
+ });
485
+ };
486
+ for (const { service, keys } of FORGE_PROCESS_SERVICES) admitCalls(service, keys);
404
487
  }
405
488
  //#endregion
406
489
  export { apply, inject };
@@ -1,4 +1,4 @@
1
- import { a as runPrimaryOperation, n as freezeOperationScope, o as runRegistryOperation } from "./registry-operation-C2ZV3Jhw.js";
1
+ import { n as freezeOperationScope, o as runPrimaryOperation, s as runRegistryOperation } from "./registry-operation-CIlmt6_c.js";
2
2
  import { i as isWindowsNetworkOrDevicePath } from "./windows-runner-contract-B5R2K3mM.js";
3
3
  import { _ as samePath, a as classifyStoredRoots, d as primaryKey, f as protectedOverlap, h as resolveRootIndex, i as canonicalRoot, m as reservedRootBoundary, n as additionalRootId, p as removeStatusAt, s as expandRootInput, t as RootValidationError, u as markProtectedPaths, v as singleGrantOf, y as validateRootCandidate } from "./roots-CmaWxRVy.js";
4
4
  import { r as validateAliasId, t as migrateRootSkills } from "./metadata-99jgViHQ.js";
@@ -10,6 +10,10 @@ function currentRegistryOperation(proof) {
10
10
  const frame = operations.getStore();
11
11
  return frame?.active === true && frame.proof === proof ? frame.operation : void 0;
12
12
  }
13
+ function operationFrameState(proof) {
14
+ const frame = operations.getStore();
15
+ return frame === void 0 || frame.proof !== proof ? "absent" : frame.active === true ? "active" : "expired";
16
+ }
13
17
  /** Capture by value: neither later publication nor a caller can mutate admission. */
14
18
  function freezeOperationScope(scope) {
15
19
  return Object.freeze({
@@ -113,4 +117,4 @@ function operationPermitsProfile(proof, argv, capped) {
113
117
  return frame?.active === true && frame.proof === proof && frame.profiles.some((profile) => profile.capped === capped && profile.argv.length <= argv.length && profile.argv.every((arg, i) => argv[i] === arg));
114
118
  }
115
119
  //#endregion
116
- export { runPrimaryOperation as a, withRegistryRead as c, rememberOperationProfile as i, freezeOperationScope as n, runRegistryOperation as o, operationPermitsProfile as r, withRegistryAdmission as s, currentRegistryOperation as t };
120
+ export { rememberOperationProfile as a, withRegistryAdmission as c, operationPermitsProfile as i, withRegistryRead as l, freezeOperationScope as n, runPrimaryOperation as o, operationFrameState as r, runRegistryOperation as s, currentRegistryOperation as t };
package/lib/registry.js CHANGED
@@ -1,3 +1,3 @@
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-DyDNDSJZ.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-BibiukWk.js";
2
2
  import { o as nativeHostAvailability } from "./protocol-CwDnFEyK.js";
3
3
  export { DEFAULT_MAX_ADDITIONAL_ROOTS, DOMAIN_NAME, MAX_ADDITIONAL_ROOTS_CEILING, MAX_ALIAS_LENGTH, MultiRootRegistry, MultiRootRegistry as default, REGISTRY_CONTENDED_MESSAGE, TABLE_NAME, multiRootDomainSpec, nativeHostAvailability };
@@ -1,5 +1,5 @@
1
1
  import { t as effectiveDirectory } from "./session-directory-BLOstut1.js";
2
- import { c as stableOpsJson, l as touchedPathOfToolCall, s as pendingInstructionTargets } from "./instructions-Ck9wp4J4.js";
2
+ import { c as stableOpsJson, l as touchedPathOfToolCall, s as pendingInstructionTargets } from "./instructions-Ca80SkZq.js";
3
3
  import { r as opsCompatibilityToken } from "./ops-settings-UN2oOKEp.js";
4
4
  import { l as isCanonicallyUnder } from "./roots-CmaWxRVy.js";
5
5
  import { createHash } from "node:crypto";
@@ -1,2 +1,2 @@
1
- import { t as MultiRootReviewContext } from "./review-context-B-oHkU6a.js";
1
+ import { t as MultiRootReviewContext } from "./review-context-OxO-fMWR.js";
2
2
  export { MultiRootReviewContext, MultiRootReviewContext as default };
@@ -105,8 +105,9 @@ var MultiRootSandboxProvider = class extends LocalSandboxProvider {
105
105
  */
106
106
  confine(argv, policy, signal) {
107
107
  const scope = this.ctx.multiRootScope.resolve(policy);
108
+ const mint = this.ctx.multiRootScope.captureMint(scope.authority !== "unavailable" ? "ambient" : policy.mode === "read-only" ? "read-only" : policy.mode === "workspace-write" ? "clamped" : "ambient");
108
109
  const remember = (facts) => {
109
- this.ctx.multiRootScope.rememberProfile(facts.argv);
110
+ this.ctx.multiRootScope.rememberProfile(facts.argv, mint);
110
111
  return facts;
111
112
  };
112
113
  if (policy.mode === "workspace-write" && scope.authority === "unavailable") {
@@ -211,6 +212,7 @@ var MultiRootSandboxProvider = class extends LocalSandboxProvider {
211
212
  workspaceRoot: policy.workspaceRoot,
212
213
  ...policy.sessionId === void 0 ? {} : { sessionId: policy.sessionId }
213
214
  };
215
+ const mint = this.ctx.multiRootScope.captureMint("ambient");
214
216
  const probe = super.confine.call(this, ["true"], probePolicy, signal);
215
217
  const restrict = (confined) => {
216
218
  try {
@@ -232,7 +234,7 @@ var MultiRootSandboxProvider = class extends LocalSandboxProvider {
232
234
  };
233
235
  return widenConfined(() => probe, (facts) => {
234
236
  const result = restrict(facts);
235
- this.ctx.multiRootScope.rememberProfile(result.argv);
237
+ this.ctx.multiRootScope.rememberProfile(result.argv, mint);
236
238
  return result;
237
239
  }, ["true"], probePolicy, signal);
238
240
  }
@@ -253,6 +255,7 @@ var MultiRootSandboxProvider = class extends LocalSandboxProvider {
253
255
  workspaceRoot: policy.workspaceRoot,
254
256
  ...policy.sessionId === void 0 ? {} : { sessionId: policy.sessionId }
255
257
  };
258
+ const mint = this.ctx.multiRootScope.captureMint("clamped");
256
259
  const probe = super.confine.call(this, ["true"], probePolicy, signal);
257
260
  const clamp = (confined) => {
258
261
  try {
@@ -269,7 +272,7 @@ var MultiRootSandboxProvider = class extends LocalSandboxProvider {
269
272
  };
270
273
  return widenConfined(() => probe, (facts) => {
271
274
  const result = clamp(facts);
272
- this.ctx.multiRootScope.rememberProfile(result.argv);
275
+ this.ctx.multiRootScope.rememberProfile(result.argv, mint);
273
276
  return result;
274
277
  }, ["true"], probePolicy, signal);
275
278
  }
package/lib/sandbox.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import "./providers.js";
2
- import { t as MultiRootSandboxProvider } from "./sandbox-CZElTNPz.js";
2
+ import { t as MultiRootSandboxProvider } from "./sandbox-aobdyQGi.js";
3
3
  export { MultiRootSandboxProvider, MultiRootSandboxProvider as default };
package/lib/scope.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as wrapMethod } from "./method-wrapper-D4oUCsEF.js";
2
- import { i as rememberOperationProfile, r as operationPermitsProfile, t as currentRegistryOperation } from "./registry-operation-C2ZV3Jhw.js";
2
+ import { a as rememberOperationProfile, i as operationPermitsProfile, r as operationFrameState, t as currentRegistryOperation } from "./registry-operation-CIlmt6_c.js";
3
3
  import { t as aroundTool } from "./tool-context-CCeY3mrK.js";
4
4
  import { r as opsCompatibilityToken } from "./ops-settings-UN2oOKEp.js";
5
5
  import { d as primaryKey } from "./roots-CmaWxRVy.js";
@@ -149,6 +149,8 @@ function renderWorkspaceRootsContext(options) {
149
149
  }
150
150
  /** The tool-dispatch cap carried across async boundaries by the waterfall. */
151
151
  const toolCapStore = new AsyncLocalStorage();
152
+ /** Bounded process-wide ledger of unadmitted mints; a burst cannot grow it without bound. */
153
+ const MAX_MINTED_PROFILES = 256;
152
154
  /**
153
155
  * The `ctx.multiRootScope` service. Providers ask it — never the filesystem —
154
156
  * what the current scope is, which is what keeps the fs fence and every kernel
@@ -165,6 +167,14 @@ var MultiRootScopeService = class extends Service {
165
167
  batchDirty = false;
166
168
  admissionProof;
167
169
  /**
170
+ * The publication generation the mint ledger is keyed on: bumped when a
171
+ * root-table write or an authority transition changes the authorization
172
+ * basis (a repeat publish of identical content is not a publication), so a
173
+ * minted profile dies with the state it was computed from.
174
+ */
175
+ publicationVersion = 0;
176
+ mintedProfiles = [];
177
+ /**
168
178
  * The registry-authority fact the registry row publishes. Defaults to
169
179
  * `available`: a composition without a registry row can never have
170
180
  * registered roots, so nothing is unknown there. The registry sets it false
@@ -228,6 +238,11 @@ var MultiRootScopeService = class extends Service {
228
238
  }
229
239
  for (const listener of this.listeners) listener();
230
240
  }
241
+ /** A publication superseded the authorization basis: every outstanding minted profile dies. */
242
+ bumpPublication() {
243
+ this.publicationVersion += 1;
244
+ this.mintedProfiles.length = 0;
245
+ }
231
246
  /**
232
247
  * The sorted names of the tools `toolAccess` caps — read-only for the
233
248
  * prompt contribution. An invalid config is not enumerated (it caps every
@@ -298,7 +313,7 @@ var MultiRootScopeService = class extends Service {
298
313
  const restore = wrapMethod(scoped.get("subprocess"), "spawn", (original) => {
299
314
  const scope = this;
300
315
  return function(spec) {
301
- if (active && opsCompatibilityToken(scoped) !== void 0 && scope.hasActiveDenyRoots() && !opsSpawnPermitted(scope.opsProtection) && !(scope.admissionRequired && scope.admissionPathsValid() && scope.permitsProfile(spec.argv))) throw new Error("multi-root workspace: an unattributed subprocess has no protected execution or live profile admission; refusing launch while OPS compatibility write boundaries apply");
316
+ if (active && opsCompatibilityToken(scoped) !== void 0 && scope.hasActiveDenyRoots() && !opsSpawnPermitted(scope.opsProtection) && !(scope.admissionRequired && scope.admissionPathsValid() && scope.permitsOperationProfile(spec.argv))) throw new Error("multi-root workspace: an unattributed subprocess has no protected execution or live profile admission; refusing launch while OPS compatibility write boundaries apply");
302
317
  return original.call(this, spec);
303
318
  };
304
319
  });
@@ -340,12 +355,74 @@ var MultiRootScopeService = class extends Service {
340
355
  requireAdmission(proof) {
341
356
  this.admissionProof = proof;
342
357
  }
343
- rememberProfile(argv) {
344
- if (this.admissionProof !== void 0) rememberOperationProfile(this.admissionProof, argv, this.capped());
358
+ /**
359
+ * The stamp an asynchronously completing mint must carry: the publication
360
+ * generation and tool-cap basis at the mint's synchronous entry, before any
361
+ * provider probe could run. An in-flight mint that crosses a real
362
+ * publication keeps its entry generation, so the ledger refuses it rather
363
+ * than re-stamping stale authorization under the current publication.
364
+ * @param form - the profile form the mint will produce (`clamped` and
365
+ * `read-only` are the only ledger-eligible forms).
366
+ */
367
+ captureMint(form) {
368
+ return {
369
+ version: this.publicationVersion,
370
+ capped: this.capped(),
371
+ form
372
+ };
373
+ }
374
+ /**
375
+ * Record a confined profile this host's provider just minted. Only
376
+ * meaningful under the shared Registry (`admissionProof` set; without it
377
+ * `permitsProfile` never refuses): a live admission binds the profile to
378
+ * the operation and it dies with it; an expired admission never bequeaths
379
+ * one — a mint that outlived its operation is dropped, not demoted into
380
+ * the ledger; only with NO admission frame at all does the profile join
381
+ * the publication-keyed ledger stamped with `mint`, and only when the
382
+ * mint's form is primary-bounded — an `ambient` mint that arrives without
383
+ * a frame is dropped the same way.
384
+ * @param argv - the confined argv just minted.
385
+ * @param mint - the {@link captureMint} stamp taken at the mint's
386
+ * synchronous entry.
387
+ */
388
+ rememberProfile(argv, mint) {
389
+ if (this.admissionProof === void 0) return;
390
+ const frame = operationFrameState(this.admissionProof);
391
+ if (frame === "expired") return;
392
+ if (frame === "active") {
393
+ rememberOperationProfile(this.admissionProof, argv, mint.capped);
394
+ return;
395
+ }
396
+ if (mint.form === "ambient") return;
397
+ this.mintedProfiles.push({
398
+ argv: [...argv],
399
+ capped: mint.capped,
400
+ version: mint.version
401
+ });
402
+ if (this.mintedProfiles.length > MAX_MINTED_PROFILES) this.mintedProfiles.shift();
345
403
  }
404
+ /**
405
+ * Whether a confined argv may launch now: a profile minted inside this live
406
+ * admission, or — for a `confine`→`spawn` composition with no wrapped entry
407
+ * to admit it — a profile this host minted while its authorization
408
+ * publication is still current. Cached argv, argv minted under a superseded
409
+ * publication, and argv minted elsewhere stay refused.
410
+ */
346
411
  permitsProfile(argv) {
412
+ return this.admissionProof === void 0 || this.permitsOperationProfile(argv) || this.permitsMintedProfile(argv);
413
+ }
414
+ /**
415
+ * The strict form: only profiles minted inside this live admission. The OPS
416
+ * execution domain owns its profile exclusively and never honors the
417
+ * unadmitted mint ledger.
418
+ */
419
+ permitsOperationProfile(argv) {
347
420
  return this.admissionProof === void 0 || operationPermitsProfile(this.admissionProof, argv, this.capped());
348
421
  }
422
+ permitsMintedProfile(argv) {
423
+ const capped = this.capped(), version = this.publicationVersion;
424
+ return this.mintedProfiles.some((entry) => entry.version === version && entry.capped === capped && entry.argv.length > 0 && entry.argv.length <= argv.length && entry.argv.every((arg, index) => argv[index] === arg));
425
+ }
349
426
  get admissionRequired() {
350
427
  return this.admissionProof !== void 0;
351
428
  }
@@ -498,6 +575,7 @@ var MultiRootScopeService = class extends Service {
498
575
  */
499
576
  setAdditionalRoots(primaryRoot, roots) {
500
577
  const key = primaryKey(primaryRoot);
578
+ if (!sameAdditionalRoots(this.rootsByPrimary.get(key) ?? [], roots)) this.bumpPublication();
501
579
  if (roots.length === 0) this.rootsByPrimary.delete(key);
502
580
  else this.rootsByPrimary.set(key, [...roots]);
503
581
  this.notifySubscribers();
@@ -517,8 +595,17 @@ var MultiRootScopeService = class extends Service {
517
595
  const next = available ? "available" : "unavailable";
518
596
  if (this.registryAuthority === next) return;
519
597
  this.registryAuthority = next;
598
+ this.bumpPublication();
520
599
  this.notifySubscribers();
521
600
  }
522
601
  };
602
+ /** Whether two persisted member lists carry the same authorization basis, in registry order. */
603
+ function sameAdditionalRoots(a, b) {
604
+ if (a.length !== b.length) return false;
605
+ return a.every((root, index) => {
606
+ const next = b[index];
607
+ return next !== void 0 && root.id === next.id && root.path === next.path && root.recordedPath === next.recordedPath && root.access === next.access && root.withheld === next.withheld && root.alias === next.alias;
608
+ });
609
+ }
523
610
  //#endregion
524
611
  export { MULTI_ROOT_CONTEXT_NAME, MultiRootScopeService, MultiRootScopeService as default, REGISTRY_UNAVAILABLE_NOTE, renderWorkspaceRootsContext, sanitizeAdditionalRoots };
package/lib/skills.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as wrapMethod } from "./method-wrapper-D4oUCsEF.js";
2
- import { c as withRegistryRead } from "./registry-operation-C2ZV3Jhw.js";
2
+ import { l as withRegistryRead } from "./registry-operation-CIlmt6_c.js";
3
3
  import { t as createInstructionMessage } from "./llm-message-CepyzEBQ.js";
4
4
  import { n as skillsMetadataValid } from "./metadata-99jgViHQ.js";
5
5
  import { a as skillOrigin, i as skillAgent, n as filesystemSkillsApi, o as volatileField, r as observedSkillOrigin, t as configValue } from "./skills-utyuvmwr.js";