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

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 (62) hide show
  1. package/CHANGELOG.en.md +26 -1
  2. package/CHANGELOG.md +26 -1
  3. package/README.en.md +24 -8
  4. package/README.md +24 -8
  5. package/cordis.patch.yml +4 -0
  6. package/lib/auto-review.js +1 -1
  7. package/lib/client.js +727 -209
  8. package/lib/client.js.map +1 -1
  9. package/lib/{command-Bwd6EGNV.js → command-BGdz7VBH.js} +65 -5
  10. package/lib/command.js +1 -1
  11. package/lib/{compat-CagUNzRB.js → compat-BiW-m_q0.js} +14 -1
  12. package/lib/compat.js +1 -1
  13. package/lib/{contract-DZ4zczDT.js → contract-CStSEOcK.js} +44 -1
  14. package/lib/index.js +6 -5
  15. package/lib/{instructions-CGNS3H0H.js → instructions-MePoRigy.js} +2 -206
  16. package/lib/instructions.js +1 -1
  17. package/lib/llm-message-fYORacvh.js +206 -0
  18. package/lib/metadata-DgqSdVX_.js +35 -0
  19. package/lib/{registry-BClm6wfh.js → registry-KSsmU393.js} +60 -7
  20. package/lib/registry.js +1 -1
  21. package/lib/{review-context-D-ohSVmA.js → review-context-AH-SXHmj.js} +1 -1
  22. package/lib/review-context.js +1 -1
  23. package/lib/scope.js +6 -2
  24. package/lib/skills.js +790 -0
  25. package/lib/types/client/WorkspaceFoldersAction.d.ts +1 -1
  26. package/lib/types/client/locales.d.ts +26 -16
  27. package/lib/types/client/open-in-app/OpenInAppAction.d.ts +5 -3
  28. package/lib/types/client/open-in-app/OpenTargetButton.d.ts +16 -10
  29. package/lib/types/client/open-in-app/adapter.d.ts +5 -1
  30. package/lib/types/client/open-in-app/controller.d.ts +5 -2
  31. package/lib/types/client/open-in-app/device.d.ts +11 -0
  32. package/lib/types/client/open-in-app/locales.d.ts +1 -1
  33. package/lib/types/client/open-in-app/open-path.d.ts +6 -5
  34. package/lib/types/client/open-in-app/open-toast.d.ts +39 -0
  35. package/lib/types/client/open-in-app/outcome.d.ts +23 -0
  36. package/lib/types/client/skills-client.d.ts +23 -0
  37. package/lib/types/client/skills-settings.d.ts +10 -0
  38. package/lib/types/client/skills.d.ts +36 -0
  39. package/lib/types/client/styles.d.ts +1 -1
  40. package/lib/types/command.d.ts +1 -1
  41. package/lib/types/compat/client-session.d.ts +2 -0
  42. package/lib/types/compat/dsh-version.d.ts +5 -5
  43. package/lib/types/compat/open-in-app-device.d.ts +23 -0
  44. package/lib/types/compat/skill-content.d.ts +3 -0
  45. package/lib/types/compat/skills-client.d.ts +33 -0
  46. package/lib/types/compat/skills-settings.d.ts +8 -0
  47. package/lib/types/compat/skills.d.ts +13 -0
  48. package/lib/types/compat/storage-zod.d.ts +1 -0
  49. package/lib/types/contract.d.ts +12 -1
  50. package/lib/types/registry.d.ts +7 -0
  51. package/lib/types/roots.d.ts +4 -1
  52. package/lib/types/scope.d.ts +2 -0
  53. package/lib/types/skills/catalog.d.ts +34 -0
  54. package/lib/types/skills/invocation.d.ts +7 -0
  55. package/lib/types/skills/metadata.d.ts +13 -0
  56. package/lib/types/skills/pool.d.ts +19 -0
  57. package/lib/types/skills/reader.d.ts +3 -0
  58. package/lib/types/skills/routing.d.ts +2 -0
  59. package/lib/types/skills-contract.d.ts +39 -0
  60. package/lib/types/skills.d.ts +43 -0
  61. package/package.json +98 -85
  62. package/lib/types/client/open-in-app/open-failure-toast.d.ts +0 -11
package/lib/skills.js ADDED
@@ -0,0 +1,790 @@
1
+ import { t as wrapMethod } from "./method-wrapper-CFY3p3j2.js";
2
+ import { r as isPackageInstalled, t as createInstructionMessage } from "./llm-message-fYORacvh.js";
3
+ import { n as skillsMetadataValid } from "./metadata-DgqSdVX_.js";
4
+ import { constants, statSync } from "node:fs";
5
+ import { createHash } from "node:crypto";
6
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
7
+ import { Service } from "@deepseek-ai/cordis";
8
+ import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
9
+ import { lstat, open, readdir, stat } from "node:fs/promises";
10
+ import { AsyncLocalStorage } from "node:async_hooks";
11
+ import z from "@deepseek-ai/schemastery";
12
+ //#region \0@oxc-project+runtime@0.149.0/helpers/esm/usingCtx.js
13
+ function _usingCtx() {
14
+ var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
15
+ var n = Error();
16
+ return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
17
+ }, e = {}, n = [];
18
+ function using(r, e) {
19
+ if (null != e) {
20
+ if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
21
+ if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
22
+ if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
23
+ if ("function" != typeof o) throw new TypeError("Object is not disposable.");
24
+ t && (o = function o() {
25
+ try {
26
+ t.call(e);
27
+ } catch (r) {
28
+ return Promise.reject(r);
29
+ }
30
+ }), n.push({
31
+ v: e,
32
+ d: o,
33
+ a: r
34
+ });
35
+ } else r && n.push({
36
+ d: e,
37
+ a: r
38
+ });
39
+ return e;
40
+ }
41
+ return {
42
+ e,
43
+ u: using.bind(null, !1),
44
+ a: using.bind(null, !0),
45
+ d: function d() {
46
+ var o, t = this.e, s = 0;
47
+ function next() {
48
+ for (; o = n.pop();) try {
49
+ if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
50
+ if (o.d) {
51
+ var r = o.d.call(o.v);
52
+ if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
53
+ } else s |= 1;
54
+ } catch (r) {
55
+ return err(r);
56
+ }
57
+ if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
58
+ if (t !== e) throw t;
59
+ }
60
+ function err(n) {
61
+ return t = t !== e ? new r(n, t) : n, next();
62
+ }
63
+ return next();
64
+ }
65
+ };
66
+ }
67
+ //#endregion
68
+ //#region src/compat/skills.ts
69
+ /** Never infer authority from cwd, a preset key, or a lookalike Agent. */
70
+ function skillAgent(ctx, options) {
71
+ const scope = options.scope;
72
+ if (typeof scope !== "object" || scope === null || !("id" in scope) || typeof scope.id !== "string") return void 0;
73
+ const agent = ctx.get("agents")?.get(scope.id);
74
+ return agent === scope ? agent : void 0;
75
+ }
76
+ function skillOrigin(ctx, options) {
77
+ const cwd = skillAgent(ctx, options)?.session.header.cwd;
78
+ return typeof cwd === "string" && cwd !== "" ? canonicalPath(cwd) : void 0;
79
+ }
80
+ /** Absence is optional; a present but broken module must fail visibly. */
81
+ async function filesystemSkillsApi() {
82
+ if (!isPackageInstalled("@deepseek-ai/dsh-skill-filesystem")) return void 0;
83
+ const api = await import("@deepseek-ai/dsh-skill-filesystem");
84
+ if (typeof api.FileSystemSkillProvider !== "function") throw new Error("multi-root skills: FileSystemSkillProvider is unavailable");
85
+ return api;
86
+ }
87
+ function volatileField(schema) {
88
+ const method = schema.volatile;
89
+ return typeof method === "function" ? method.call(schema) : schema;
90
+ }
91
+ function configValue(value) {
92
+ if (typeof value === "object" && value !== null && Symbol.for("cosmokit.volatile.write") in value) {
93
+ const get = value.get;
94
+ if (typeof get === "function") return get.call(value);
95
+ }
96
+ return value;
97
+ }
98
+ /** Cold Browser Catalog supplies a Session header, never a fabricated Agent. */
99
+ async function observedSkillOrigin(ctx, sessionId) {
100
+ try {
101
+ var _usingCtx$1 = _usingCtx();
102
+ const query = ctx.get("sessionQuery");
103
+ if (query === void 0) throw new Error("multi-root skills: SessionQuery is unavailable");
104
+ const cwd = _usingCtx$1.u(await query.observeSession(sessionId)).header.cwd;
105
+ if (typeof cwd !== "string" || cwd === "") throw new Error("multi-root skills: Session has no origin");
106
+ return canonicalPath(cwd);
107
+ } catch (_) {
108
+ _usingCtx$1.e = _;
109
+ } finally {
110
+ _usingCtx$1.d();
111
+ }
112
+ }
113
+ //#endregion
114
+ //#region src/skills/reader.ts
115
+ /** Constrain the native parser before discovery can read a candidate's body. */
116
+ function containsPath(root, target) {
117
+ const rel = relative(root, target);
118
+ return rel === "" || !isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
119
+ }
120
+ async function rootReader(ctx, root, signal, valid) {
121
+ const api = await filesystemSkillsApi();
122
+ if (api === void 0) return void 0;
123
+ const fs = ctx.fs;
124
+ const guard = (value) => {
125
+ signal.throwIfAborted();
126
+ const path = typeof value === "string" ? value : fs.processPath(value);
127
+ const canonical = canonicalPath(path);
128
+ if (!valid() || !containsPath(root, resolve(path)) || !containsPath(root, canonical)) throw Object.assign(/* @__PURE__ */ new Error("multi-root skills: path is outside the authorized root"), { code: "ENOENT" });
129
+ return canonical;
130
+ };
131
+ const guarded = new Proxy(fs, { get(target, key) {
132
+ if (key === "readText") return async (path, requestSignal) => {
133
+ requestSignal?.throwIfAborted();
134
+ const canonical = guard(path);
135
+ const handle = await open(canonical, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
136
+ try {
137
+ const before = await handle.stat();
138
+ if (!before.isFile()) throw new Error("multi-root skills: candidate is not a regular file");
139
+ const text = await handle.readFile("utf8");
140
+ requestSignal?.throwIfAborted();
141
+ if (guard(path) !== canonical) throw new Error("multi-root skills: candidate changed during read");
142
+ const after = await handle.stat(), current = await stat(canonical);
143
+ if (before.dev !== current.dev || before.ino !== current.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs) throw new Error("multi-root skills: candidate changed during read");
144
+ return text;
145
+ } finally {
146
+ await handle.close();
147
+ }
148
+ };
149
+ if (key === "resolve" || key === "stat" || key === "listDir") return async (...args) => {
150
+ guard(args[0]);
151
+ const method = Reflect.get(target, key);
152
+ return await Reflect.apply(method, target, args);
153
+ };
154
+ const value = Reflect.get(target, key);
155
+ return typeof value === "function" ? value.bind(target) : value;
156
+ } });
157
+ const delegateContext = {
158
+ get: (name) => name === "fs" ? guarded : void 0,
159
+ logger: ctx.logger
160
+ };
161
+ return new api.FileSystemSkillProvider(delegateContext, {
162
+ signal,
163
+ invalidate() {}
164
+ }, {
165
+ includeDefaultRoots: false,
166
+ watch: false,
167
+ providerName: "multi-root-skills-filesystem",
168
+ customSkillDirs: [resolve(root, ".dsh/skills"), resolve(root, ".agents/skills")]
169
+ });
170
+ }
171
+ //#endregion
172
+ //#region src/skills/pool.ts
173
+ /** Bounded shared native delegates and one polling scheduler, never per-delegate watchers. */
174
+ var SkillReaderPool = class {
175
+ invalidate;
176
+ capacity;
177
+ entries = /* @__PURE__ */ new Map();
178
+ waiters = /* @__PURE__ */ new Set();
179
+ closing = false;
180
+ polling;
181
+ timer;
182
+ constructor(invalidate, capacity = 128, intervalMs = 2e3) {
183
+ this.invalidate = invalidate;
184
+ this.capacity = capacity;
185
+ this.timer = setInterval(() => {
186
+ this.poll().catch(() => this.invalidate());
187
+ }, intervalMs);
188
+ this.timer.unref();
189
+ }
190
+ get size() {
191
+ return this.entries.size;
192
+ }
193
+ get roots() {
194
+ return [...this.entries.keys()];
195
+ }
196
+ async use(key, create, run, signal) {
197
+ for (;;) {
198
+ signal?.throwIfAborted();
199
+ if (this.closing) throw new Error("multi-root skills: reader pool is closing");
200
+ let entry = this.entries.get(key);
201
+ if (entry === void 0 && this.entries.size >= this.capacity) {
202
+ const idle = [...this.entries.values()].find((entry) => entry.busy === 0);
203
+ if (idle === void 0) {
204
+ await new Promise((resolve, reject) => {
205
+ const wake = () => {
206
+ signal?.removeEventListener("abort", abort);
207
+ this.waiters.delete(wake);
208
+ resolve();
209
+ };
210
+ const abort = () => {
211
+ this.waiters.delete(wake);
212
+ reject(signal?.reason);
213
+ };
214
+ this.waiters.add(wake);
215
+ signal?.addEventListener("abort", abort, { once: true });
216
+ if (signal?.aborted) abort();
217
+ });
218
+ continue;
219
+ }
220
+ this.entries.delete(idle.key);
221
+ await this.closeEntry(idle);
222
+ continue;
223
+ }
224
+ if (entry === void 0) {
225
+ const controller = new AbortController();
226
+ entry = {
227
+ key,
228
+ controller,
229
+ reader: create(controller.signal),
230
+ busy: 0
231
+ };
232
+ this.entries.set(key, entry);
233
+ }
234
+ this.entries.delete(key);
235
+ this.entries.set(key, entry);
236
+ entry.busy++;
237
+ try {
238
+ if (entry.stamp === void 0) entry.stamp = await skillTreeStamp(key);
239
+ signal?.throwIfAborted();
240
+ return await run(await entry.reader);
241
+ } finally {
242
+ entry.busy--;
243
+ for (const wake of [...this.waiters]) wake();
244
+ }
245
+ }
246
+ }
247
+ async poll() {
248
+ if (this.closing) return;
249
+ this.polling ??= (async () => {
250
+ let changed = false;
251
+ for (const entry of [...this.entries.values()]) {
252
+ const stamp = await skillTreeStamp(entry.key);
253
+ if (entry.stamp !== void 0 && entry.stamp !== stamp) changed = true;
254
+ entry.stamp = stamp;
255
+ }
256
+ if (changed && !this.closing) this.invalidate();
257
+ })().finally(() => {
258
+ this.polling = void 0;
259
+ });
260
+ await this.polling;
261
+ }
262
+ async dispose() {
263
+ this.closing = true;
264
+ clearInterval(this.timer);
265
+ for (const wake of [...this.waiters]) wake();
266
+ for (const entry of this.entries.values()) entry.controller.abort();
267
+ await this.polling?.catch(() => {});
268
+ while ([...this.entries.values()].some((entry) => entry.busy > 0)) await new Promise((resolve) => {
269
+ const wake = () => {
270
+ this.waiters.delete(wake);
271
+ resolve();
272
+ };
273
+ this.waiters.add(wake);
274
+ });
275
+ await Promise.allSettled([...this.entries.values()].map((entry) => this.closeEntry(entry)));
276
+ this.entries.clear();
277
+ }
278
+ async closeEntry(entry) {
279
+ entry.controller.abort();
280
+ try {
281
+ await (await entry.reader)?.dispose();
282
+ } catch {}
283
+ }
284
+ };
285
+ /** No symlink target outside the Root is read or enumerated. Content reads stay on demand. */
286
+ async function skillTreeStamp(root) {
287
+ const rows = [canonicalPath(root)];
288
+ const stamp = async (path) => {
289
+ try {
290
+ const link = await lstat(path, { bigint: true });
291
+ rows.push(`${path}:${link.dev}:${link.ino}:${link.size}:${link.mtimeNs}:${link.ctimeNs}:${canonicalPath(path)}`);
292
+ if (!containsPath(root, canonicalPath(path))) return void 0;
293
+ return await stat(path);
294
+ } catch {
295
+ rows.push(`${path}:missing`);
296
+ return;
297
+ }
298
+ };
299
+ await stamp(root);
300
+ for (const directory of [".dsh/skills", ".agents/skills"]) {
301
+ const path = join(root, directory);
302
+ if (!(await stamp(path))?.isDirectory()) continue;
303
+ const entries = await readdir(path).catch(() => []);
304
+ for (const name of entries.sort()) {
305
+ const candidate = join(path, name);
306
+ if ((await stamp(candidate))?.isDirectory()) await stamp(join(candidate, "SKILL.md"));
307
+ }
308
+ }
309
+ return rows.join("\n");
310
+ }
311
+ //#endregion
312
+ //#region src/skills/catalog.ts
313
+ /** Origin-scoped metadata discovery; Registry remains the only registration source. */
314
+ const SKILLS_PROVIDER = "multi-root-workspace";
315
+ function internalSkillName(primary, rootId, skillName) {
316
+ return `mr-${createHash("sha256").update(`${primary}\0${rootId}`).digest("hex")}-${skillName}`;
317
+ }
318
+ var AdditionalSkillCatalog = class {
319
+ ctx;
320
+ signal;
321
+ enabled;
322
+ generation;
323
+ loadedReceipts = /* @__PURE__ */ new WeakMap();
324
+ locators = /* @__PURE__ */ new WeakSet();
325
+ requests = new AsyncLocalStorage();
326
+ pool;
327
+ constructor(ctx, signal, enabled, generation = () => 0, invalidate = () => {}) {
328
+ this.ctx = ctx;
329
+ this.signal = signal;
330
+ this.enabled = enabled;
331
+ this.generation = generation;
332
+ this.pool = new SkillReaderPool(invalidate);
333
+ }
334
+ /** Read synchronously after awaited receipt checks, immediately before batch delivery. */
335
+ get revision() {
336
+ return this.generation();
337
+ }
338
+ current(primary, id) {
339
+ if (this.signal.aborted || !this.enabled() || this.ctx.multiRootRegistry.authority.kind !== "active" || this.ctx.multiRootScope.resolve({
340
+ mode: "read-only",
341
+ workspaceRoot: primary
342
+ }).authority !== "available") return void 0;
343
+ const rows = this.ctx.multiRootRegistry.list(primary);
344
+ const root = rows.find((row) => row.id === id);
345
+ if (root === void 0 || root.state !== "available" || root.skillsEnabled !== true || !skillsMetadataValid(root, rows)) return void 0;
346
+ const recorded = root.recordedPath ?? root.readOnlyRecordedPath ?? root.readWriteRecordedPath;
347
+ try {
348
+ return recorded === canonicalPath(root.path) && statSync(root.path).isDirectory() ? root : void 0;
349
+ } catch {
350
+ return;
351
+ }
352
+ }
353
+ async withReader(primary, id, options, run) {
354
+ const root = this.current(primary, id);
355
+ if (root === void 0) return await run(void 0);
356
+ return await this.requests.run({
357
+ primary,
358
+ id
359
+ }, () => this.pool.use(root.path, (signal) => rootReader(this.ctx, root.path, AbortSignal.any([signal, this.signal]), () => {
360
+ const request = this.requests.getStore();
361
+ return request !== void 0 && this.current(request.primary, request.id)?.path === root.path;
362
+ }), run, options.signal));
363
+ }
364
+ async list(primary, options) {
365
+ options.signal?.throwIfAborted();
366
+ if (!this.enabled()) return [];
367
+ const roots = await this.ctx.multiRootRegistry.refresh(primary);
368
+ const revision = this.generation();
369
+ const candidates = [];
370
+ for (const root of roots) {
371
+ if (this.current(primary, root.id) === void 0) continue;
372
+ try {
373
+ const observed = await this.withReader(primary, root.id, options, async (reader) => await reader?.list(options));
374
+ if (observed === void 0) continue;
375
+ options.signal?.throwIfAborted();
376
+ const fresh = this.current(primary, root.id);
377
+ if (fresh === void 0 || fresh.aliasId !== root.aliasId) continue;
378
+ const seen = /* @__PURE__ */ new Set();
379
+ for (const candidate of Array.isArray(observed) ? observed : observed.candidates) {
380
+ if (seen.has(candidate.name)) continue;
381
+ seen.add(candidate.name);
382
+ const locator = {
383
+ primary,
384
+ rootId: root.id,
385
+ aliasId: root.aliasId,
386
+ candidate
387
+ };
388
+ this.locators.add(locator);
389
+ candidates.push({
390
+ ...candidate,
391
+ provider: SKILLS_PROVIDER,
392
+ description: `/${locator.aliasId}:${candidate.name} — ${candidate.description}`,
393
+ name: internalSkillName(primary, root.id, candidate.name),
394
+ rank: Number.MAX_SAFE_INTEGER,
395
+ invocation: {
396
+ modelInvocable: candidate.invocation.modelInvocable,
397
+ userInvocable: false
398
+ },
399
+ locator
400
+ });
401
+ }
402
+ } catch (error) {
403
+ options.signal?.throwIfAborted();
404
+ this.ctx.logger.warn(`multi-root skills: discovery withheld for ${root.path}: ${String(error)}`);
405
+ }
406
+ }
407
+ return revision === this.generation() ? candidates : [];
408
+ }
409
+ /** Locators are created here and are not accepted from a client path or object. */
410
+ async load(primary, entry, options, user) {
411
+ const locator = entry.locator;
412
+ if (typeof locator !== "object" || locator === null || !this.locators.has(locator) || locator.primary !== primary) return void 0;
413
+ const root = this.current(primary, locator.rootId);
414
+ if (root === void 0 || root.aliasId !== locator.aliasId || entry.name !== internalSkillName(primary, root.id, locator.candidate.name)) return void 0;
415
+ const revision = this.generation();
416
+ let loaded;
417
+ let stamp;
418
+ try {
419
+ stamp = await fileStamp(locator.candidate.path);
420
+ loaded = await this.withReader(primary, root.id, options, async (reader) => await reader?.get(locator.candidate, options));
421
+ if (stamp !== await fileStamp(locator.candidate.path)) return void 0;
422
+ } catch (error) {
423
+ options.signal?.throwIfAborted();
424
+ this.ctx.logger.warn(`multi-root skills: load withheld: ${String(error)}`);
425
+ return;
426
+ }
427
+ options.signal?.throwIfAborted();
428
+ const current = this.current(primary, root.id);
429
+ if (loaded === void 0 || revision !== this.generation() || current === void 0 || current.aliasId !== locator.aliasId || loaded.name !== locator.candidate.name) return void 0;
430
+ if (!(user ? loaded.invocation.userInvocable : loaded.invocation.modelInvocable)) return void 0;
431
+ const definition = {
432
+ ...loaded,
433
+ name: user ? `${locator.aliasId}:${loaded.name}` : entry.name,
434
+ provider: SKILLS_PROVIDER,
435
+ invocation: user ? loaded.invocation : {
436
+ modelInvocable: loaded.invocation.modelInvocable,
437
+ userInvocable: false
438
+ }
439
+ };
440
+ this.loadedReceipts.set(definition, {
441
+ revision,
442
+ locator,
443
+ stamp
444
+ });
445
+ return definition;
446
+ }
447
+ async admissionValid(definition) {
448
+ 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
+ }
457
+ }
458
+ async dispose() {
459
+ await this.pool.dispose();
460
+ }
461
+ };
462
+ async function fileStamp(path) {
463
+ if (path === void 0) throw new Error("multi-root skills: missing file identity");
464
+ const value = await stat(path, { bigint: true });
465
+ return [
466
+ value.dev,
467
+ value.ino,
468
+ value.size,
469
+ value.mtimeNs,
470
+ value.ctimeNs
471
+ ].join(":");
472
+ }
473
+ //#endregion
474
+ //#region src/compat/skill-content.ts
475
+ async function renderAdditionalSkill(skill) {
476
+ const { renderSkillContent } = await import("@deepseek-ai/dsh-skill");
477
+ return renderSkillContent(skill);
478
+ }
479
+ //#endregion
480
+ //#region src/skills/invocation.ts
481
+ function installSkillInvocations(ctx, catalog, known) {
482
+ ctx.inject(["agents"], (scope) => {
483
+ scope.on("agent/pre-step", async ({ agent, messages, signal }, next) => {
484
+ const decision = await next();
485
+ if (decision.kind === "reject") return decision;
486
+ const primary = skillOrigin(scope, { scope: agent });
487
+ if (primary === void 0) return decision;
488
+ const invoked = /* @__PURE__ */ new Set();
489
+ for (const message of messages) {
490
+ if (message.source.kind !== "user") continue;
491
+ for (const block of message.content) {
492
+ if (block.type !== "text") continue;
493
+ for (const match of block.text.matchAll(/(?<!\S)\/([a-z0-9]+(?:-[a-z0-9]+)*):([a-z0-9]+(?:-[a-z0-9]+)*)(?!\S)/g)) invoked.add(`${match[1]}:${match[2]}`);
494
+ }
495
+ }
496
+ const previous = known.get(agent);
497
+ if (invoked.size === 0 && (previous === void 0 || previous.size === 0)) return decision;
498
+ const options = {
499
+ scope: agent,
500
+ signal
501
+ };
502
+ const entries = await catalog.list(primary, options);
503
+ const revision = catalog.revision;
504
+ const injections = [], pending = [];
505
+ if (previous !== void 0) {
506
+ const remaining = new Set(entries.map((entry) => entry.name));
507
+ const revoked = [];
508
+ for (const [name, locator] of previous) {
509
+ if (remaining.has(name) && catalog.current(primary, locator.rootId)?.aliasId === locator.aliasId) continue;
510
+ revoked.push(`${locator.aliasId}:${locator.candidate.name}`);
511
+ previous.delete(name);
512
+ }
513
+ if (revoked.length > 0) injections.push(await createInstructionMessage({
514
+ plugin: "multi-root-skills",
515
+ text: `The following additional-root Skills are no longer available for new invocations: ${revoked.join(", ")}. Previously delivered content remains in the conversation history.`
516
+ }));
517
+ }
518
+ for (const name of invoked) {
519
+ signal.throwIfAborted();
520
+ const entry = entries.find((entry) => {
521
+ const locator = entry.locator;
522
+ return `${locator.aliasId}:${locator.candidate.name}` === name;
523
+ });
524
+ if (entry === void 0) continue;
525
+ const loaded = await catalog.load(primary, entry, options, true);
526
+ if (loaded === void 0) continue;
527
+ const rendered = await renderAdditionalSkill(loaded);
528
+ signal.throwIfAborted();
529
+ const message = await createInstructionMessage({
530
+ plugin: "multi-root-skills",
531
+ text: rendered
532
+ });
533
+ pending.push({
534
+ entry,
535
+ loaded,
536
+ message
537
+ });
538
+ }
539
+ const admitted = await Promise.all(pending.map(({ loaded }) => catalog.admissionValid(loaded)));
540
+ signal.throwIfAborted();
541
+ if (revision === catalog.revision && admitted.every(Boolean)) for (const { entry, message } of pending) {
542
+ let tracked = known.get(agent);
543
+ if (tracked === void 0) {
544
+ tracked = /* @__PURE__ */ new Map();
545
+ known.set(agent, tracked);
546
+ }
547
+ tracked.set(entry.name, entry.locator);
548
+ injections.push(message);
549
+ }
550
+ return {
551
+ ...decision,
552
+ messages: [...decision.messages, ...injections]
553
+ };
554
+ }, { prepend: true });
555
+ });
556
+ }
557
+ //#endregion
558
+ //#region src/skills/routing.ts
559
+ /** Public native reads must not discover an additional Root as an unqualified project. */
560
+ function installSkillRouting(ctx) {
561
+ const fibers = [];
562
+ const cold = new AsyncLocalStorage();
563
+ const optionsFor = (options) => {
564
+ const primary = skillOrigin(ctx, options) ?? cold.getStore();
565
+ if (primary === void 0 || options.cwd === void 0) return options;
566
+ const rows = ctx.multiRootRegistry.list(primary);
567
+ if (rows.length === 0) return options;
568
+ const lexical = resolve(options.cwd), canonical = canonicalPath(options.cwd);
569
+ return rows.some((root) => containsPath(root.path, lexical) || containsPath(root.path, canonical)) ? {
570
+ ...options,
571
+ cwd: primary
572
+ } : options;
573
+ };
574
+ fibers.push(ctx.inject(["skills"], (scope) => {
575
+ const registry = scope.skills;
576
+ 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;
584
+ }));
585
+ scope.effect(() => wrapMethod(registry, "get", (original) => function(name, options = {}) {
586
+ return original.call(this, name, optionsFor(options));
587
+ }));
588
+ }));
589
+ fibers.push(ctx.inject(["sessionSkillCatalog", "sessionQuery"], (scope) => {
590
+ scope.effect(() => wrapMethod(scope.sessionSkillCatalog, "list", (original) => async function(request, signal) {
591
+ const primary = await observedSkillOrigin(scope, request.sessionId);
592
+ return await cold.run(primary, () => original.call(this, request, signal));
593
+ }));
594
+ }));
595
+ return fibers;
596
+ }
597
+ //#endregion
598
+ //#region src/compat/skills-settings.ts
599
+ const SKILLS_SETTINGS_ID = "multi-root-skills";
600
+ function installSkillsSettings(ctx, initial, changed) {
601
+ let source;
602
+ return {
603
+ fiber: ctx.inject(["settings"], (scope) => {
604
+ const settings = scope.get("settings");
605
+ if (typeof settings.installSection === "function") {
606
+ settings.installSection(scope, SKILLS_SETTINGS_ID, z.object({ enabled: z.boolean().default(true) }), { enabled: configValue(initial()) !== false }, {
607
+ setSource: (current) => {
608
+ source = current;
609
+ },
610
+ onChange: changed,
611
+ validate: (value) => {
612
+ if (typeof value.enabled !== "boolean") throw new Error("multi-root skills: enabled must be boolean");
613
+ }
614
+ });
615
+ return () => {
616
+ source = void 0;
617
+ changed();
618
+ };
619
+ }
620
+ if (typeof settings.configure !== "function") throw new Error("multi-root skills: unsupported Settings service");
621
+ const stop = settings.configure({ auto: false }, scope.fiber);
622
+ const stopEvent = scope.on("settings/document-updated", (id) => {
623
+ if (id === "multi-root-skills") changed();
624
+ });
625
+ return () => {
626
+ stopEvent();
627
+ stop();
628
+ };
629
+ }),
630
+ read: () => source === void 0 ? initial() : source().enabled
631
+ };
632
+ }
633
+ //#endregion
634
+ //#region src/skills.ts
635
+ /** Optional Skills host entry. Kept outside the carrier's module evaluation graph. */
636
+ const Config = z.object({ enabled: volatileField(z.boolean().default(true)) });
637
+ /** Assembly owner; discovery is installed only after native services arrive. */
638
+ var MultiRootSkills = class extends Service {
639
+ settingsConfig;
640
+ static Config = Config;
641
+ static provide = "multiRootSkills";
642
+ static inject = [
643
+ "multiRootCompat",
644
+ "multiRootProviders",
645
+ "multiRootRegistry",
646
+ "multiRootScope"
647
+ ];
648
+ constructor(ctx, settingsConfig = {}) {
649
+ super(ctx, "multiRootSkills");
650
+ this.settingsConfig = settingsConfig;
651
+ }
652
+ settingsRead;
653
+ revision = 0;
654
+ catalog;
655
+ changed = /* @__PURE__ */ new Set();
656
+ closing = false;
657
+ get generation() {
658
+ return this.revision;
659
+ }
660
+ invalidate() {
661
+ this.revision++;
662
+ for (const notify of [...this.changed]) notify();
663
+ }
664
+ status(sessionId) {
665
+ return {
666
+ sessionId,
667
+ generation: this.revision,
668
+ enabled: this.enabled && !this.closing
669
+ };
670
+ }
671
+ async listSession(sessionId, signal) {
672
+ signal?.throwIfAborted();
673
+ const primary = await observedSkillOrigin(this.ctx, sessionId), revision = this.revision;
674
+ const entries = await this.catalog?.list(primary, { signal }) ?? [];
675
+ signal?.throwIfAborted();
676
+ if (this.closing || primary !== await observedSkillOrigin(this.ctx, sessionId) || revision !== this.revision) throw new Error("multi-root skills: stale Session catalog; retry");
677
+ return {
678
+ ...this.status(sessionId),
679
+ skills: entries.flatMap((entry) => {
680
+ const locator = entry.locator;
681
+ return locator.candidate.invocation.userInvocable ? [{
682
+ name: `${locator.aliasId}:${locator.candidate.name}`,
683
+ description: locator.candidate.description,
684
+ modelInvocable: entry.invocation.modelInvocable
685
+ }] : [];
686
+ })
687
+ };
688
+ }
689
+ async previewSession(sessionId, name, signal) {
690
+ signal?.throwIfAborted();
691
+ const primary = await observedSkillOrigin(this.ctx, sessionId), catalog = this.catalog;
692
+ const entry = (await catalog?.list(primary, { signal }) ?? []).find((entry) => {
693
+ const locator = entry.locator;
694
+ return `${locator.aliasId}:${locator.candidate.name}` === name;
695
+ });
696
+ const loaded = entry === void 0 ? void 0 : await catalog?.load(primary, entry, { signal }, true);
697
+ signal?.throwIfAborted();
698
+ if (loaded === void 0 || primary !== await observedSkillOrigin(this.ctx, sessionId) || !await catalog.admissionValid(loaded) || this.closing) throw new Error("multi-root skills: Skill is unavailable");
699
+ return {
700
+ ...this.status(sessionId),
701
+ name,
702
+ content: loaded.content
703
+ };
704
+ }
705
+ async changes(sessionId, generation, signal) {
706
+ await observedSkillOrigin(this.ctx, sessionId);
707
+ signal?.throwIfAborted();
708
+ if (this.closing) throw new Error("multi-root skills: closing");
709
+ if (generation === this.revision) await new Promise((resolve, reject) => {
710
+ const finish = () => {
711
+ clearTimeout(timer);
712
+ this.changed.delete(finish);
713
+ signal?.removeEventListener("abort", abort);
714
+ resolve();
715
+ };
716
+ const abort = () => {
717
+ finish();
718
+ reject(signal?.reason);
719
+ };
720
+ const timer = setTimeout(finish, 2e4);
721
+ timer.unref();
722
+ this.changed.add(finish);
723
+ signal?.addEventListener("abort", abort, { once: true });
724
+ if (signal?.aborted) abort();
725
+ });
726
+ signal?.throwIfAborted();
727
+ await observedSkillOrigin(this.ctx, sessionId);
728
+ return this.status(sessionId);
729
+ }
730
+ async [Service.init]() {
731
+ const owner = this, ctx = this.ctx;
732
+ await ctx.effect(async function* () {
733
+ const settings = installSkillsSettings(ctx, () => owner.settingsConfig.enabled, () => owner.invalidate());
734
+ owner.settingsRead = settings.read;
735
+ yield settings.fiber.dispose;
736
+ yield ctx.multiRootRegistry.onChange(() => owner.invalidate());
737
+ yield ctx.multiRootScope.subscribe(() => owner.invalidate());
738
+ for (const fiber of installSkillRouting(ctx)) yield fiber.dispose;
739
+ yield ctx.inject(["skills", "fs"], (scope) => {
740
+ const controller = new AbortController();
741
+ const catalog = new AdditionalSkillCatalog(scope, controller.signal, () => owner.enabled, () => owner.revision, () => owner.invalidate());
742
+ owner.catalog = catalog;
743
+ scope.effect(() => () => {
744
+ controller.abort();
745
+ if (owner.catalog === catalog) owner.catalog = void 0;
746
+ return catalog.dispose();
747
+ });
748
+ const known = /* @__PURE__ */ new WeakMap();
749
+ installSkillInvocations(scope, catalog, known);
750
+ scope.skills.registerProvider((control) => {
751
+ scope.effect(() => {
752
+ owner.changed.add(control.invalidate);
753
+ return () => {
754
+ owner.changed.delete(control.invalidate);
755
+ };
756
+ });
757
+ return {
758
+ name: SKILLS_PROVIDER,
759
+ list: async (options) => {
760
+ const primary = skillOrigin(scope, options);
761
+ if (primary === void 0) return [];
762
+ const entries = await catalog.list(primary, options);
763
+ const agent = skillAgent(scope, options);
764
+ let tracked = known.get(agent);
765
+ if (tracked === void 0) {
766
+ tracked = /* @__PURE__ */ new Map();
767
+ known.set(agent, tracked);
768
+ }
769
+ for (const entry of entries) tracked.set(entry.name, entry.locator);
770
+ return entries;
771
+ },
772
+ get: async (entry, options) => {
773
+ const primary = skillOrigin(scope, options);
774
+ return primary === void 0 ? void 0 : await catalog.load(primary, entry, options, false);
775
+ }
776
+ };
777
+ });
778
+ }).dispose;
779
+ yield () => {
780
+ owner.closing = true;
781
+ owner.invalidate();
782
+ };
783
+ }, "multi-root skills: ordered owner");
784
+ }
785
+ get enabled() {
786
+ return !this.closing && configValue(this.settingsRead?.() ?? this.settingsConfig.enabled) !== false;
787
+ }
788
+ };
789
+ //#endregion
790
+ export { Config, MultiRootSkills, MultiRootSkills as default };