@dsh-electron/dsh-plugin-multi-root-workspace 0.1.8 → 0.1.10
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.
- package/CHANGELOG.en.md +22 -1
- package/CHANGELOG.md +22 -1
- package/README.en.md +39 -16
- package/README.md +47 -24
- package/cordis.patch.yml +10 -0
- package/lib/auto-review-packages-nRjnb4pU.js +15 -0
- package/lib/auto-review.js +166 -0
- package/lib/client.js +573 -131
- package/lib/client.js.map +1 -1
- package/lib/{command-CESuwlVt.js → command-BGdz7VBH.js} +68 -8
- package/lib/command.js +1 -1
- package/lib/{compat-DR2LvmYZ.js → compat-D-MljsPV.js} +13 -2
- package/lib/compat.js +1 -1
- package/lib/{contract-DZ4zczDT.js → contract-CStSEOcK.js} +44 -1
- package/lib/fork-CzbeWxHp.js +670 -0
- package/lib/fs.js +1 -1
- package/lib/index.js +9 -8
- package/lib/{instructions-D19jRxN0.js → instructions-MePoRigy.js} +213 -263
- package/lib/instructions.js +1 -1
- package/lib/llm-message-fYORacvh.js +206 -0
- package/lib/lsp.js +1 -1
- package/lib/metadata-DgqSdVX_.js +35 -0
- package/lib/{registry-DCxC5Lay.js → registry-KSsmU393.js} +66 -13
- package/lib/registry.js +1 -1
- package/lib/review-context-AH-SXHmj.js +98 -0
- package/lib/review-context.js +2 -0
- package/lib/{roots-B-0l8mNG.js → roots-Ca_W1S9n.js} +1 -1
- package/lib/{sandbox-CVAcT_gy.js → sandbox-CXk5h-ct.js} +3 -3
- package/lib/sandbox.js +1 -1
- package/lib/scope.js +15 -6
- package/lib/skills.js +790 -0
- package/lib/types/auto-review/entry.d.ts +5 -0
- package/lib/types/client/WorkspaceFoldersAction.d.ts +1 -1
- package/lib/types/client/locales.d.ts +26 -16
- package/lib/types/client/skills-client.d.ts +23 -0
- package/lib/types/client/skills-settings.d.ts +10 -0
- package/lib/types/client/skills.d.ts +36 -0
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/command.d.ts +1 -1
- package/lib/types/compat/auto-review-contract.d.ts +3 -0
- package/lib/types/compat/auto-review-loader.d.ts +33 -0
- package/lib/types/compat/auto-review-packages.d.ts +1 -0
- package/lib/types/compat/client-session.d.ts +2 -0
- package/lib/types/compat/dsh-version.d.ts +5 -5
- package/lib/types/compat/instruction-activity.d.ts +17 -0
- package/lib/types/compat/review-session.d.ts +2 -0
- package/lib/types/compat/skill-content.d.ts +3 -0
- package/lib/types/compat/skills-client.d.ts +33 -0
- package/lib/types/compat/skills-settings.d.ts +8 -0
- package/lib/types/compat/skills.d.ts +13 -0
- package/lib/types/compat/storage-zod.d.ts +1 -0
- package/lib/types/contract.d.ts +12 -1
- package/lib/types/instructions.d.ts +18 -1
- package/lib/types/registry.d.ts +7 -0
- package/lib/types/review-context.d.ts +49 -0
- package/lib/types/roots.d.ts +4 -1
- package/lib/types/scope.d.ts +5 -0
- package/lib/types/skills/catalog.d.ts +34 -0
- package/lib/types/skills/invocation.d.ts +7 -0
- package/lib/types/skills/metadata.d.ts +13 -0
- package/lib/types/skills/pool.d.ts +19 -0
- package/lib/types/skills/reader.d.ts +3 -0
- package/lib/types/skills/routing.d.ts +2 -0
- package/lib/types/skills-contract.d.ts +39 -0
- package/lib/types/skills.d.ts +43 -0
- package/lib/windows-runner.js +2 -2
- package/lib/workspace-files.js +2 -2
- package/package.json +121 -68
- package/src/auto-review/FORK.md +26 -0
- package/src/auto-review/LICENSE +21 -0
- package/src/auto-review/README.en.md +22 -0
- package/src/auto-review/README.md +22 -0
- package/lib/{fs-Nlp2AiRX.js → fs-BTXySwSG.js} +1 -1
- package/lib/{registry-lease-win32-DTO6QpT9.js → registry-lease-win32-DVTq9d4D.js} +1 -1
- package/lib/{windows-runner-contract-CEfmZzu0.js → windows-runner-contract-B5R2K3mM.js} +1 -1
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 };
|