dsh-claude-slides 2.9.2 → 2.10.0
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/README.md +9 -8
- package/cordis.patch.yml +8 -2
- package/lib/index.js +28 -753
- package/package.json +4 -18
- package/skills/claude-slides/SKILL.md +3 -3
- package/skills/claude-slides/scripts/artifact.mjs +1 -1
- package/skills/claude-slides/scripts/server.mjs +11 -8
- package/lib/client.js +0 -1800
package/lib/index.js
CHANGED
|
@@ -1,484 +1,32 @@
|
|
|
1
|
-
// dsh-claude-slides 2.
|
|
1
|
+
// dsh-claude-slides 2.10.0: built from baoyu-design.
|
|
2
2
|
var define_PLUGIN_default = { name: "claude-slides", provider: "dsh-claude-slides", skills: [{ name: "claude-slides", description: "Make and revise slide decks in the Slides artifact format (project/deck.json plus one HTML file per slide) as local files, then open them in a local slide editor where the user can present, retype, drag, and export PDF, HTML or PowerPoint. Use when the user asks for an editable slide deck or presentation in this format, names claude-slides or the Slides type, or wants to revise a folder that holds project/deck.json. Not for a one-off single-file HTML deck or prototype.", comments: true }] };
|
|
3
3
|
|
|
4
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
5
|
-
import { randomUUID } from "node:crypto";
|
|
6
|
-
import { statSync as statSync2 } from "node:fs";
|
|
7
4
|
import { readFile } from "node:fs/promises";
|
|
8
|
-
import {
|
|
5
|
+
import { dirname as dirname2, join as join2, posix, sep, win32 } from "node:path";
|
|
9
6
|
import { fileURLToPath } from "node:url";
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import { basename, dirname, join, parse } from "node:path";
|
|
16
|
-
|
|
17
|
-
var base = (skill) => `/api/baoyu-design.${skill}.artifacts`;
|
|
18
|
-
var listPath = base;
|
|
19
|
-
var openPath = (skill) => `${base(skill)}.open`;
|
|
20
|
-
var previewPath = (skill) => `${base(skill)}.preview`;
|
|
21
|
-
var releasePath = (skill) => `${base(skill)}.release`;
|
|
22
|
-
var thumbnailPath = (skill) => `${base(skill)}.thumbnail`;
|
|
23
|
-
var updatePath = (skill) => `${base(skill)}.update`;
|
|
24
|
-
var deletePath = (skill) => `${base(skill)}.delete`;
|
|
25
|
-
var restorePath = (skill) => `${base(skill)}.restore`;
|
|
26
|
-
|
|
27
|
-
var TITLES = {
|
|
28
|
-
"claude-slides": ["project/deck.json", "title"],
|
|
29
|
-
"claude-design": ["project/canvas.json", "title"],
|
|
30
|
-
"claude-design-system": ["project/design-system.json", "title"],
|
|
31
|
-
"claude-dashboard": ["db/dash/meta.json", "title"],
|
|
32
|
-
"claude-docs": ["doc.json", "name"],
|
|
33
|
-
"claude-motion": ["db/film/meta.json", "title"]
|
|
34
|
-
};
|
|
35
|
-
var OPEN_TIMEOUT_MS = 3e4;
|
|
36
|
-
var TITLE_MAX = 200;
|
|
37
|
-
var EDITOR_FILES = [".artifact-server.json", ".artifact-server.log", ".sharing.json", ".gitignore", ".DS_Store", "comments.json", "history", "exports"];
|
|
38
|
-
var FOLDER = {
|
|
39
|
-
"claude-slides": ["project"],
|
|
40
|
-
"claude-design": ["project"],
|
|
41
|
-
"claude-design-system": ["project"],
|
|
42
|
-
"claude-dashboard": ["db", "_blobs", "src"],
|
|
43
|
-
"claude-docs": ["doc.json", "tabs", "_blobs", ".sessions.json"],
|
|
44
|
-
"claude-motion": ["db", "_blobs", "src"]
|
|
45
|
-
};
|
|
46
|
-
var NOT_CONTENT = /* @__PURE__ */ new Set([...EDITOR_FILES, ".sessions.json", ".git", "node_modules"]);
|
|
47
|
-
var STAMP_MAX_ENTRIES = 2e4;
|
|
48
|
-
function dshHome() {
|
|
49
|
-
const set = process.env.DSH_HOME?.trim();
|
|
50
|
-
return set ? set : join(homedir(), ".dsh");
|
|
51
|
-
}
|
|
52
|
-
function artifactsDir() {
|
|
53
|
-
return join(dshHome(), "baoyu-design", "artifacts");
|
|
54
|
-
}
|
|
55
|
-
var folderHash = (root) => createHash("sha256").update(root).digest("hex").slice(0, 24);
|
|
56
|
-
function recordFile(root) {
|
|
57
|
-
return join(artifactsDir(), `${folderHash(root)}.json`);
|
|
58
|
-
}
|
|
59
|
-
function thumbnailFile(root) {
|
|
60
|
-
return join(dshHome(), "baoyu-design", "thumbnails", `${folderHash(root)}.jpg`);
|
|
61
|
-
}
|
|
62
|
-
function trashDir() {
|
|
63
|
-
return join(dshHome(), "baoyu-design", "trash");
|
|
64
|
-
}
|
|
65
|
-
function artifactTitle(skill, root) {
|
|
66
|
-
const at = TITLES[skill];
|
|
67
|
-
if (at === void 0) return null;
|
|
68
|
-
try {
|
|
69
|
-
const value = JSON.parse(readFileSync(join(root, at[0]), "utf8"))[at[1]];
|
|
70
|
-
const title = typeof value === "string" ? value.trim() : "";
|
|
71
|
-
if (title && title !== "project") return title;
|
|
72
|
-
} catch {
|
|
73
|
-
}
|
|
74
|
-
return basename(root);
|
|
75
|
-
}
|
|
76
|
-
function isRecord(value) {
|
|
77
|
-
const r = value;
|
|
78
|
-
return typeof r === "object" && r !== null && r.v === 1 && typeof r.root === "string" && typeof r.skill === "string" && typeof r.plugin === "string" && typeof r.sessionId === "string" && typeof r.title === "string" && typeof r.openedAt === "string";
|
|
79
|
-
}
|
|
80
|
-
function readRecord(file) {
|
|
81
|
-
try {
|
|
82
|
-
const value = JSON.parse(readFileSync(file, "utf8"));
|
|
83
|
-
return isRecord(value) ? value : null;
|
|
84
|
-
} catch {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
function writeWhole(file, data) {
|
|
89
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
90
|
-
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
91
|
-
writeFileSync(tmp, data);
|
|
92
|
-
renameSync(tmp, file);
|
|
93
|
-
}
|
|
94
|
-
function writeRecord(record) {
|
|
95
|
-
writeWhole(recordFile(record.root), `${JSON.stringify(record, null, 2)}
|
|
96
|
-
`);
|
|
97
|
-
}
|
|
98
|
-
function recordOpened(skill, plugin, sessionId, root, now = /* @__PURE__ */ new Date()) {
|
|
99
|
-
const file = recordFile(root);
|
|
100
|
-
const before = readRecord(file);
|
|
101
|
-
const mine = before?.root === root ? before : null;
|
|
102
|
-
const record = {
|
|
103
|
-
v: 1,
|
|
104
|
-
root,
|
|
105
|
-
skill,
|
|
106
|
-
plugin,
|
|
107
|
-
sessionId,
|
|
108
|
-
title: artifactTitle(skill, root) ?? before?.title ?? basename(root),
|
|
109
|
-
openedAt: now.toISOString(),
|
|
110
|
-
firstOpenedAt: mine ? mine.firstOpenedAt ?? mine.openedAt : now.toISOString(),
|
|
111
|
-
...mine?.pinned ? { pinned: true } : {},
|
|
112
|
-
...mine?.thumbStamp ? { thumbStamp: mine.thumbStamp } : {}
|
|
113
|
-
};
|
|
114
|
-
writeRecord(record);
|
|
115
|
-
return record;
|
|
116
|
-
}
|
|
117
|
-
function contentStamp(root) {
|
|
118
|
-
let newest = 0;
|
|
119
|
-
let entries = 0;
|
|
120
|
-
const walk = (dir, top) => {
|
|
121
|
-
let names;
|
|
122
|
-
try {
|
|
123
|
-
names = readdirSync(dir);
|
|
124
|
-
} catch {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
for (const name2 of names) {
|
|
128
|
-
if (++entries > STAMP_MAX_ENTRIES) return;
|
|
129
|
-
if (name2.endsWith(".tmp") || top && NOT_CONTENT.has(name2)) continue;
|
|
130
|
-
const path = join(dir, name2);
|
|
131
|
-
try {
|
|
132
|
-
const stat = statSync(path);
|
|
133
|
-
if (stat.isDirectory()) walk(path, false);
|
|
134
|
-
else if (stat.mtimeMs > newest) newest = stat.mtimeMs;
|
|
135
|
-
} catch {
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
walk(root, true);
|
|
140
|
-
return newest > 0 ? new Date(newest).toISOString() : "";
|
|
141
|
-
}
|
|
142
|
-
function isShared(root) {
|
|
143
|
-
try {
|
|
144
|
-
return JSON.parse(readFileSync(join(root, ".sharing.json"), "utf8")).lan === true;
|
|
145
|
-
} catch {
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
function isDeletable(skill, root) {
|
|
150
|
-
const own = FOLDER[skill];
|
|
151
|
-
if (own === void 0) return false;
|
|
152
|
-
if (root === homedir() || root === parse(root).root) return false;
|
|
153
|
-
try {
|
|
154
|
-
return readdirSync(root).every((name2) => own.includes(name2) || EDITOR_FILES.includes(name2) || name2.endsWith(".tmp"));
|
|
155
|
-
} catch {
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
function listArtifacts() {
|
|
160
|
-
let names;
|
|
161
|
-
try {
|
|
162
|
-
names = readdirSync(artifactsDir());
|
|
163
|
-
} catch {
|
|
164
|
-
return [];
|
|
165
|
-
}
|
|
166
|
-
const views = [];
|
|
167
|
-
for (const name2 of names) {
|
|
168
|
-
if (!name2.endsWith(".json")) continue;
|
|
169
|
-
const record = readRecord(join(artifactsDir(), name2));
|
|
170
|
-
if (record === null || !existsSync(record.root)) continue;
|
|
171
|
-
views.push({
|
|
172
|
-
root: record.root,
|
|
173
|
-
skill: record.skill,
|
|
174
|
-
plugin: record.plugin,
|
|
175
|
-
sessionId: record.sessionId,
|
|
176
|
-
title: artifactTitle(record.skill, record.root) ?? record.title,
|
|
177
|
-
openedAt: record.openedAt,
|
|
178
|
-
firstOpenedAt: record.firstOpenedAt ?? record.openedAt,
|
|
179
|
-
updatedAt: contentStamp(record.root) || record.openedAt,
|
|
180
|
-
pinned: record.pinned === true,
|
|
181
|
-
shared: isShared(record.root),
|
|
182
|
-
deletable: isDeletable(record.skill, record.root),
|
|
183
|
-
thumbnail: record.thumbStamp !== void 0 && existsSync(thumbnailFile(record.root)) ? record.thumbStamp : null
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
return views.sort((a, b) => a.openedAt < b.openedAt ? 1 : a.openedAt > b.openedAt ? -1 : 0);
|
|
187
|
-
}
|
|
188
|
-
function findArtifact(root) {
|
|
189
|
-
const record = readRecord(recordFile(root));
|
|
190
|
-
return record?.root === root ? record : null;
|
|
191
|
-
}
|
|
192
|
-
function scriptEnv(node) {
|
|
193
|
-
const env = { ...process.env };
|
|
194
|
-
if (node.env) {
|
|
195
|
-
const [name2, value] = node.env.split("=");
|
|
196
|
-
if (name2) env[name2] = value ?? "";
|
|
197
|
-
}
|
|
198
|
-
return env;
|
|
199
|
-
}
|
|
200
|
-
function runScript(node, script, args, input) {
|
|
201
|
-
return new Promise((resolve, reject) => {
|
|
202
|
-
const child = spawn(node.path, [script, ...args], { env: scriptEnv(node), stdio: [input === void 0 ? "ignore" : "pipe", "pipe", "pipe"], windowsHide: true });
|
|
203
|
-
let stdout = "";
|
|
204
|
-
let stderr = "";
|
|
205
|
-
const timer = setTimeout(() => {
|
|
206
|
-
child.kill();
|
|
207
|
-
reject(new Error(`${args[0]} did not finish in time`));
|
|
208
|
-
}, OPEN_TIMEOUT_MS);
|
|
209
|
-
child.stdout?.setEncoding("utf8").on("data", (chunk) => stdout += chunk);
|
|
210
|
-
child.stderr?.setEncoding("utf8").on("data", (chunk) => stderr += chunk);
|
|
211
|
-
child.stdin?.end(input);
|
|
212
|
-
child.on("error", (error) => {
|
|
213
|
-
clearTimeout(timer);
|
|
214
|
-
reject(error);
|
|
215
|
-
});
|
|
216
|
-
child.on("close", (code) => {
|
|
217
|
-
clearTimeout(timer);
|
|
218
|
-
if (code === 0) return resolve(firstJson(stdout));
|
|
219
|
-
const failure = firstJson(stderr) ?? firstJson(stdout);
|
|
220
|
-
const message = typeof failure?.message === "string" ? failure.message : stderr.trim() || `${args[0]} exited with ${code ?? "a signal"}`;
|
|
221
|
-
reject(new Error(message));
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
async function runOpen(node, script, root) {
|
|
226
|
-
const out = await runScript(node, script, ["open", root]);
|
|
227
|
-
if (typeof out?.url !== "string") throw new Error("open printed no address");
|
|
228
|
-
return { url: out.url, reused: out.reused === true };
|
|
229
|
-
}
|
|
230
|
-
async function renameArtifact(node, script, record, title) {
|
|
231
|
-
const { root, skill } = record;
|
|
232
|
-
const at = TITLES[skill];
|
|
233
|
-
if (at === void 0) throw new Error(`no title to change for ${skill}`);
|
|
234
|
-
if (skill === "claude-dashboard" || skill === "claude-motion") {
|
|
235
|
-
const path = at[0].replace(/^db\//, "").replace(/\.json$/, "");
|
|
236
|
-
await runScript(node, script, ["write-db", root, "-"], JSON.stringify([{ op: "update", path, data: { [at[1]]: title } }]));
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
const file = join(root, at[0]);
|
|
240
|
-
const index = JSON.parse(readFileSync(file, "utf8"));
|
|
241
|
-
if (skill === "claude-docs") {
|
|
242
|
-
await runScript(node, script, ["update", root, JSON.stringify({ ref: { object: "project", id: String(index.id) }, payload: { name: title } })]);
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
writeWhole(file, `${JSON.stringify({ ...index, [at[1]]: title }, null, 2)}
|
|
246
|
-
`);
|
|
247
|
-
}
|
|
248
|
-
function cleanTitle(raw) {
|
|
249
|
-
if (typeof raw !== "string") return null;
|
|
250
|
-
const title = raw.replace(/\s+/g, " ").trim().slice(0, TITLE_MAX);
|
|
251
|
-
return title || null;
|
|
252
|
-
}
|
|
253
|
-
function moveFolder(from, to) {
|
|
254
|
-
mkdirSync(dirname(to), { recursive: true });
|
|
255
|
-
try {
|
|
256
|
-
renameSync(from, to);
|
|
257
|
-
} catch (error) {
|
|
258
|
-
if (error.code !== "EXDEV") throw error;
|
|
259
|
-
cpSync(from, to, { recursive: true, preserveTimestamps: true });
|
|
260
|
-
rmSync(from, { recursive: true, force: true });
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
var TRASH_ID = /^[0-9a-f]{24}-\d+$/;
|
|
264
|
-
function trashArtifact(record) {
|
|
265
|
-
const id = `${folderHash(record.root)}-${Date.now()}`;
|
|
266
|
-
const entry = join(trashDir(), id);
|
|
267
|
-
try {
|
|
268
|
-
for (const name2 of readdirSync(trashDir())) {
|
|
269
|
-
if (TRASH_ID.test(name2) && !existsSync(join(trashDir(), name2, "folder"))) rmSync(join(trashDir(), name2), { recursive: true, force: true });
|
|
270
|
-
}
|
|
271
|
-
} catch {
|
|
272
|
-
}
|
|
273
|
-
mkdirSync(entry, { recursive: true });
|
|
274
|
-
writeFileSync(join(entry, "record.json"), `${JSON.stringify(record, null, 2)}
|
|
275
|
-
`);
|
|
276
|
-
const thumbnail = thumbnailFile(record.root);
|
|
277
|
-
if (existsSync(thumbnail)) renameSync(thumbnail, join(entry, "thumbnail.jpg"));
|
|
278
|
-
const moved = isDeletable(record.skill, record.root);
|
|
279
|
-
const path = join(entry, "folder", basename(record.root));
|
|
280
|
-
if (moved) moveFolder(record.root, path);
|
|
281
|
-
rmSync(recordFile(record.root), { force: true });
|
|
282
|
-
return { trash: id, moved, path: moved ? path : record.root };
|
|
283
|
-
}
|
|
284
|
-
function restoreArtifact(id) {
|
|
285
|
-
if (!TRASH_ID.test(id)) throw new Error("not a trash entry");
|
|
286
|
-
const entry = join(trashDir(), id);
|
|
287
|
-
const record = readRecord(join(entry, "record.json"));
|
|
288
|
-
if (record === null) throw new Error("nothing to put back");
|
|
289
|
-
const folder = join(entry, "folder", basename(record.root));
|
|
290
|
-
if (existsSync(folder)) {
|
|
291
|
-
if (existsSync(record.root)) throw new Error(`${record.root} is taken`);
|
|
292
|
-
moveFolder(folder, record.root);
|
|
293
|
-
}
|
|
294
|
-
writeRecord(record);
|
|
295
|
-
const thumbnail = join(entry, "thumbnail.jpg");
|
|
296
|
-
if (existsSync(thumbnail)) {
|
|
297
|
-
mkdirSync(dirname(thumbnailFile(record.root)), { recursive: true });
|
|
298
|
-
renameSync(thumbnail, thumbnailFile(record.root));
|
|
299
|
-
}
|
|
300
|
-
rmSync(entry, { recursive: true, force: true });
|
|
301
|
-
return record;
|
|
302
|
-
}
|
|
303
|
-
function firstJson(text) {
|
|
304
|
-
const start = text.indexOf("{");
|
|
305
|
-
const end = text.lastIndexOf("}");
|
|
306
|
-
if (start < 0 || end <= start) return null;
|
|
307
|
-
try {
|
|
308
|
-
const value = JSON.parse(text.slice(start, end + 1));
|
|
309
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
310
|
-
} catch {
|
|
311
|
-
return null;
|
|
312
|
-
}
|
|
8
|
+
var HOST = Symbol.for("baoyu-design/dsh-artifacts-host");
|
|
9
|
+
function hostHub() {
|
|
10
|
+
const g = globalThis;
|
|
11
|
+
return g[HOST] ??= { members: [] };
|
|
313
12
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
routes.register({
|
|
317
|
-
path: listPath(skill.name),
|
|
318
|
-
methods: ["GET"],
|
|
319
|
-
requestBody: "buffered",
|
|
320
|
-
fetch: () => Promise.resolve(json({ artifacts: listArtifacts() }))
|
|
321
|
-
});
|
|
322
|
-
routes.register({
|
|
323
|
-
path: openPath(skill.name),
|
|
324
|
-
methods: ["POST"],
|
|
325
|
-
requestBody: "buffered",
|
|
326
|
-
fetch: async (request) => {
|
|
327
|
-
const root = new URL(request.url).searchParams.get("root") ?? "";
|
|
328
|
-
const record = root ? findArtifact(root) : null;
|
|
329
|
-
if (record === null || record.skill !== skill.name) return json({ error: "not a recorded artifact of this skill" }, 404);
|
|
330
|
-
if (!existsSync(root)) return json({ error: "the folder is gone" }, 404);
|
|
331
|
-
try {
|
|
332
|
-
const { url, reused } = await runOpen(skill.node, skill.script, root);
|
|
333
|
-
const again = recordOpened(skill.name, skill.plugin, record.sessionId, root);
|
|
334
|
-
opened(again);
|
|
335
|
-
return json({ url, reused, sessionId: again.sessionId, title: again.title });
|
|
336
|
-
} catch (error) {
|
|
337
|
-
return json({ error: error.message }, 500);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
const rootOf = (request) => new URL(request.url).searchParams.get("root") ?? "";
|
|
342
|
-
const mine = (root) => {
|
|
343
|
-
const record = root ? findArtifact(root) : null;
|
|
344
|
-
return record?.skill === skill.name && existsSync(root) ? record : null;
|
|
345
|
-
};
|
|
346
|
-
const notMine = () => json({ error: "not a recorded artifact of this skill" }, 404);
|
|
347
|
-
const started = /* @__PURE__ */ new Map();
|
|
348
|
-
routes.register({
|
|
349
|
-
path: previewPath(skill.name),
|
|
350
|
-
methods: ["POST"],
|
|
351
|
-
requestBody: "buffered",
|
|
352
|
-
fetch: async (request) => {
|
|
353
|
-
const root = rootOf(request);
|
|
354
|
-
const record = mine(root);
|
|
355
|
-
if (record === null) return notMine();
|
|
356
|
-
try {
|
|
357
|
-
const { url, reused } = await runOpen(skill.node, skill.script, root);
|
|
358
|
-
if (!reused) started.set(root, record.openedAt);
|
|
359
|
-
return json({ url, reused });
|
|
360
|
-
} catch (error) {
|
|
361
|
-
return json({ error: error.message }, 500);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
|
-
routes.register({
|
|
366
|
-
path: releasePath(skill.name),
|
|
367
|
-
methods: ["POST"],
|
|
368
|
-
requestBody: "buffered",
|
|
369
|
-
fetch: async (request) => {
|
|
370
|
-
const root = rootOf(request);
|
|
371
|
-
const since = started.get(root);
|
|
372
|
-
started.delete(root);
|
|
373
|
-
const record = mine(root);
|
|
374
|
-
if (record === null) return notMine();
|
|
375
|
-
if (since === void 0 || since !== record.openedAt || new URL(request.url).searchParams.get("keep") === "1") return json({ stopped: false });
|
|
376
|
-
try {
|
|
377
|
-
await runScript(skill.node, skill.script, ["stop", root]);
|
|
378
|
-
return json({ stopped: true });
|
|
379
|
-
} catch (error) {
|
|
380
|
-
return json({ error: error.message }, 500);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
routes.register({
|
|
385
|
-
path: thumbnailPath(skill.name),
|
|
386
|
-
methods: ["GET", "POST"],
|
|
387
|
-
requestBody: "buffered",
|
|
388
|
-
fetch: async (request) => {
|
|
389
|
-
const root = rootOf(request);
|
|
390
|
-
const record = root ? findArtifact(root) : null;
|
|
391
|
-
if (record === null) return json({ error: "not a recorded artifact" }, 404);
|
|
392
|
-
if (request.method === "GET") {
|
|
393
|
-
try {
|
|
394
|
-
const bytes2 = readFileSync(thumbnailFile(root));
|
|
395
|
-
return new Response(bytes2, { headers: { "content-type": "image/jpeg", "cache-control": "private, max-age=31536000, immutable" } });
|
|
396
|
-
} catch {
|
|
397
|
-
return json({ error: "no thumbnail yet" }, 404);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
const bytes = new Uint8Array(await request.arrayBuffer());
|
|
401
|
-
if (bytes.length < 3 || bytes[0] !== 255 || bytes[1] !== 216) return json({ error: "a thumbnail is a JPEG" }, 400);
|
|
402
|
-
writeWhole(thumbnailFile(root), bytes);
|
|
403
|
-
const stamp = contentStamp(root) || record.openedAt;
|
|
404
|
-
writeRecord({ ...findArtifact(root) ?? record, thumbStamp: stamp });
|
|
405
|
-
return json({ thumbnail: stamp, updatedAt: stamp });
|
|
406
|
-
}
|
|
407
|
-
});
|
|
408
|
-
routes.register({
|
|
409
|
-
path: updatePath(skill.name),
|
|
410
|
-
methods: ["POST"],
|
|
411
|
-
requestBody: "buffered",
|
|
412
|
-
fetch: async (request) => {
|
|
413
|
-
const root = rootOf(request);
|
|
414
|
-
const record = mine(root);
|
|
415
|
-
if (record === null) return notMine();
|
|
416
|
-
const patch = await request.json().catch(() => null);
|
|
417
|
-
if (patch === null || typeof patch !== "object") return json({ error: "not a JSON object" }, 400);
|
|
418
|
-
try {
|
|
419
|
-
if (patch.title !== void 0) {
|
|
420
|
-
const title = cleanTitle(patch.title);
|
|
421
|
-
if (title === null) return json({ error: "a title cannot be empty" }, 400);
|
|
422
|
-
await renameArtifact(skill.node, skill.script, record, title);
|
|
423
|
-
}
|
|
424
|
-
const now = findArtifact(root) ?? record;
|
|
425
|
-
const next = { ...now, title: artifactTitle(skill.name, root) ?? now.title };
|
|
426
|
-
if (patch.pinned === true) next.pinned = true;
|
|
427
|
-
else if (patch.pinned === false) delete next.pinned;
|
|
428
|
-
writeRecord(next);
|
|
429
|
-
return json({ title: next.title, pinned: next.pinned === true });
|
|
430
|
-
} catch (error) {
|
|
431
|
-
return json({ error: error.message }, 500);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
});
|
|
435
|
-
routes.register({
|
|
436
|
-
path: deletePath(skill.name),
|
|
437
|
-
methods: ["POST"],
|
|
438
|
-
requestBody: "buffered",
|
|
439
|
-
fetch: async (request) => {
|
|
440
|
-
const root = rootOf(request);
|
|
441
|
-
const record = mine(root);
|
|
442
|
-
if (record === null) return notMine();
|
|
443
|
-
try {
|
|
444
|
-
if (isDeletable(skill.name, root)) await runScript(skill.node, skill.script, ["stop", root]).catch(() => null);
|
|
445
|
-
started.delete(root);
|
|
446
|
-
return json(trashArtifact(record));
|
|
447
|
-
} catch (error) {
|
|
448
|
-
return json({ error: error.message }, 500);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
routes.register({
|
|
453
|
-
path: restorePath(skill.name),
|
|
454
|
-
methods: ["POST"],
|
|
455
|
-
requestBody: "buffered",
|
|
456
|
-
fetch: (request) => {
|
|
457
|
-
try {
|
|
458
|
-
return Promise.resolve(json({ root: restoreArtifact(new URL(request.url).searchParams.get("trash") ?? "").root }));
|
|
459
|
-
} catch (error) {
|
|
460
|
-
return Promise.resolve(json({ error: error.message }, 400));
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
});
|
|
13
|
+
function hostRunning() {
|
|
14
|
+
return hostHub().members.length > 0;
|
|
464
15
|
}
|
|
465
16
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
var inject = ["skills"];
|
|
469
|
-
var BUNDLED_SKILL_RANK = 600;
|
|
470
|
-
var packageRoot = fileURLToPath(new URL("../", import.meta.url));
|
|
17
|
+
import { statSync } from "node:fs";
|
|
18
|
+
import { delimiter, dirname, isAbsolute, join } from "node:path";
|
|
471
19
|
function isFile(path) {
|
|
472
20
|
try {
|
|
473
|
-
return
|
|
21
|
+
return statSync(path).isFile();
|
|
474
22
|
} catch {
|
|
475
23
|
return false;
|
|
476
24
|
}
|
|
477
25
|
}
|
|
478
|
-
function resolveNode(config = {}) {
|
|
26
|
+
function resolveNode(plugin, config = {}) {
|
|
479
27
|
if (config.node !== void 0) {
|
|
480
28
|
if (!isAbsolute(config.node) || !isFile(config.node)) {
|
|
481
|
-
throw new Error(`${
|
|
29
|
+
throw new Error(`${plugin}: config.node must be an absolute path to an executable file: ${config.node}`);
|
|
482
30
|
}
|
|
483
31
|
return { path: config.node, from: "config" };
|
|
484
32
|
}
|
|
@@ -488,24 +36,30 @@ function resolveNode(config = {}) {
|
|
|
488
36
|
const roots = [];
|
|
489
37
|
const resourcesPath = process.resourcesPath;
|
|
490
38
|
if (typeof resourcesPath === "string") roots.push(resourcesPath);
|
|
491
|
-
let dir =
|
|
39
|
+
let dir = dirname(process.execPath);
|
|
492
40
|
for (let i = 0; i < 6; i++) {
|
|
493
|
-
roots.push(
|
|
494
|
-
const parent =
|
|
41
|
+
roots.push(join(dir, "Resources"), join(dir, "resources"));
|
|
42
|
+
const parent = dirname(dir);
|
|
495
43
|
if (parent === dir) break;
|
|
496
44
|
dir = parent;
|
|
497
45
|
}
|
|
498
46
|
for (const root of roots) {
|
|
499
|
-
const candidate =
|
|
47
|
+
const candidate = join(root, ...runtime);
|
|
500
48
|
if (isFile(candidate)) return { path: candidate, from: "desktop runtime" };
|
|
501
49
|
}
|
|
502
50
|
for (const entry of (process.env.PATH ?? "").split(delimiter)) {
|
|
503
51
|
if (entry === "" || !isAbsolute(entry)) continue;
|
|
504
|
-
const candidate =
|
|
52
|
+
const candidate = join(entry, exe);
|
|
505
53
|
if (isFile(candidate)) return { path: candidate, from: "PATH" };
|
|
506
54
|
}
|
|
507
55
|
return { path: process.execPath, env: "ELECTRON_RUN_AS_NODE=1", from: "electron" };
|
|
508
56
|
}
|
|
57
|
+
|
|
58
|
+
var PLUGIN = define_PLUGIN_default;
|
|
59
|
+
var name = PLUGIN.name;
|
|
60
|
+
var inject = ["skills"];
|
|
61
|
+
var BUNDLED_SKILL_RANK = 600;
|
|
62
|
+
var packageRoot = fileURLToPath(new URL("../", import.meta.url));
|
|
509
63
|
function shellQuote(path) {
|
|
510
64
|
return `"${path.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
511
65
|
}
|
|
@@ -518,7 +72,7 @@ function scriptCommand(node, script, args, platform) {
|
|
|
518
72
|
const env = name2 ? `$env:${name2} = ${powerShellQuote(value ?? "")};` : "";
|
|
519
73
|
return [env, "&", powerShellQuote(node.path), powerShellQuote(script), ...args].filter(Boolean).join(" ");
|
|
520
74
|
}
|
|
521
|
-
function runtimeSection(skill, skillDir, node, platform = process.platform) {
|
|
75
|
+
function runtimeSection(skill, skillDir, node, platform = process.platform, watched = true) {
|
|
522
76
|
const windows = platform === "win32";
|
|
523
77
|
const script = (windows ? win32 : posix).join(skillDir, "scripts", "artifact.mjs");
|
|
524
78
|
const written = skill.scriptAlias === void 0 ? "`node <this skill's folder>/scripts/artifact.mjs <command>`" : `\`node $${skill.scriptAlias} <command>\` (\`${skill.scriptAlias}\` being \`<this skill's folder>/scripts/artifact.mjs\`)`;
|
|
@@ -542,7 +96,7 @@ function runtimeSection(skill, skillDir, node, platform = process.platform) {
|
|
|
542
96
|
"`open` prints the editor's `url` (on 127.0.0.1): give it to the user as a Markdown link. In the DeepSeek Harness",
|
|
543
97
|
"app a chat link opens in the right sidebar's Browser, next to this conversation."
|
|
544
98
|
];
|
|
545
|
-
if (skill.comments) {
|
|
99
|
+
if (skill.comments && watched) {
|
|
546
100
|
const sent = skill.commentsBy === "mention" ? "A comment, reply or edit in the viewer that says `@agent` is sent to you (the viewer has no Send to Agent box)." : "The editor shows **Send to Agent** on a comment while comments can reach you; a comment sent with it is sent to you.";
|
|
547
101
|
lines.push(
|
|
548
102
|
"",
|
|
@@ -568,266 +122,13 @@ function runtimeSection(skill, skillDir, node, platform = process.platform) {
|
|
|
568
122
|
}
|
|
569
123
|
return lines.join("\n");
|
|
570
124
|
}
|
|
571
|
-
var COMMENT_SOURCE = "baoyu-design-comment";
|
|
572
|
-
var SUMMARY_MAX = 120;
|
|
573
|
-
var SESSION_CHECK_MS = 6e4;
|
|
574
|
-
var RETRY_MS = 5e3;
|
|
575
|
-
var MAX_QUICK_FAILURES = 3;
|
|
576
|
-
var IDLE_CHECK_MS = 500;
|
|
577
|
-
var AGENT_GRACE_MS = 3e4;
|
|
578
|
-
var TURN_START_MS = 3e3;
|
|
579
|
-
var TURN_MAX_MS = 30 * 6e4;
|
|
580
|
-
function jsonObject(text) {
|
|
581
|
-
const trimmed = text.trim();
|
|
582
|
-
const starts = [0];
|
|
583
|
-
for (let i = trimmed.indexOf("\n{"); i >= 0; i = trimmed.indexOf("\n{", i + 1)) starts.push(i + 1);
|
|
584
|
-
for (const start of starts) {
|
|
585
|
-
if (trimmed[start] !== "{") continue;
|
|
586
|
-
const end = trimmed.lastIndexOf("}");
|
|
587
|
-
for (let stop = end; stop > start; stop = trimmed.lastIndexOf("}", stop - 1)) {
|
|
588
|
-
try {
|
|
589
|
-
const value = JSON.parse(trimmed.slice(start, stop + 1));
|
|
590
|
-
if (value && typeof value === "object" && !Array.isArray(value)) return value;
|
|
591
|
-
} catch {
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
return null;
|
|
596
|
-
}
|
|
597
|
-
function jsonEnd(text, start) {
|
|
598
|
-
let depth = 0;
|
|
599
|
-
let inString = false;
|
|
600
|
-
for (let i = start; i < text.length; i++) {
|
|
601
|
-
const c = text[i];
|
|
602
|
-
if (inString) {
|
|
603
|
-
if (c === "\\") i++;
|
|
604
|
-
else if (c === '"') inString = false;
|
|
605
|
-
} else if (c === '"') inString = true;
|
|
606
|
-
else if (c === "{" || c === "[") depth++;
|
|
607
|
-
else if ((c === "}" || c === "]") && --depth === 0) return i;
|
|
608
|
-
}
|
|
609
|
-
return -1;
|
|
610
|
-
}
|
|
611
|
-
function jsonObjects(text) {
|
|
612
|
-
const found = [];
|
|
613
|
-
const starts = text.startsWith("{") ? [0] : [];
|
|
614
|
-
for (let i = text.indexOf("\n{"); i >= 0; i = text.indexOf("\n{", i + 1)) starts.push(i + 1);
|
|
615
|
-
let after = 0;
|
|
616
|
-
for (const start of starts) {
|
|
617
|
-
if (start < after) continue;
|
|
618
|
-
const end = jsonEnd(text, start);
|
|
619
|
-
if (end < 0) continue;
|
|
620
|
-
try {
|
|
621
|
-
const value = JSON.parse(text.slice(start, end + 1));
|
|
622
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
623
|
-
found.push(value);
|
|
624
|
-
after = end + 1;
|
|
625
|
-
}
|
|
626
|
-
} catch {
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
return found;
|
|
630
|
-
}
|
|
631
|
-
function bashStdout(result) {
|
|
632
|
-
const value = result.value;
|
|
633
|
-
if (typeof value === "string") return value;
|
|
634
|
-
if (value && typeof value === "object") {
|
|
635
|
-
if (value.kind !== "foreground" || value.exitCode !== 0) return null;
|
|
636
|
-
return typeof value.stdout?.text === "string" ? value.stdout.text : null;
|
|
637
|
-
}
|
|
638
|
-
const text = result.content.map((block) => block.type === "text" ? block.text ?? "" : "").join("\n");
|
|
639
|
-
return text || null;
|
|
640
|
-
}
|
|
641
|
-
function openedEditor(script, exec, result) {
|
|
642
|
-
if (exec.name !== "bash" && exec.name !== "pwsh" || exec.agent === void 0 || result.isError) return null;
|
|
643
|
-
const args = exec.arguments;
|
|
644
|
-
const command = args?.command;
|
|
645
|
-
if (typeof command !== "string" || args?.run_in_background === true) return null;
|
|
646
|
-
if (!/(?:^|[\s"'])open(?:[\s"']|$)/.test(command)) return null;
|
|
647
|
-
const stdout = bashStdout(result);
|
|
648
|
-
const opened = stdout === null ? void 0 : jsonObjects(stdout).findLast((o) => typeof o.root === "string" && typeof o.url === "string");
|
|
649
|
-
if (typeof opened?.root !== "string" || typeof opened.url !== "string") return null;
|
|
650
|
-
if (!ranScript(script, command, opened.stop)) return null;
|
|
651
|
-
return { session: String(exec.agent.id), root: opened.root, url: opened.url };
|
|
652
|
-
}
|
|
653
|
-
function ranScript(script, command, stop) {
|
|
654
|
-
if (typeof stop !== "string") return command.includes(script);
|
|
655
|
-
if (stop.includes(script)) return true;
|
|
656
|
-
const folder = basename2(dirname2(dirname2(script)));
|
|
657
|
-
return new RegExp(`[/\\\\]${folder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[/\\\\]scripts[/\\\\]artifact\\.mjs`).test(stop);
|
|
658
|
-
}
|
|
659
|
-
var bound = (text) => text.length <= SUMMARY_MAX ? text : `${text.slice(0, SUMMARY_MAX - 1)}…`;
|
|
660
|
-
function commentMessage(skill, plugin, out, root) {
|
|
661
|
-
const title = typeof out.title === "string" && out.title ? out.title : root.split(/[\\/]/).pop() || root;
|
|
662
|
-
const url = typeof out.url === "string" ? out.url : "";
|
|
663
|
-
const threads = (out.threads ?? []).map((t) => {
|
|
664
|
-
const mine = (t.messages ?? []).filter((m) => m.from === "user");
|
|
665
|
-
return { id: t.id, where: t.where ?? "", text: (mine[mine.length - 1] ?? t.messages?.[t.messages.length - 1])?.text ?? "" };
|
|
666
|
-
});
|
|
667
|
-
const requests = (out.requests ?? []).filter((r) => r && typeof r.text === "string").map((r) => ({ id: String(r.id), label: typeof r.label === "string" ? r.label : "", text: r.text }));
|
|
668
|
-
const { next: _next, timedOut: _timedOut, about: _about, ...rest } = out;
|
|
669
|
-
const how = skill.commentsBy === "mention" ? "read the block, change the doc with `update`, then `comment-reply`" : "change the files, then `comment-reply`";
|
|
670
|
-
const where = `"${title}" (${url || root})`;
|
|
671
|
-
const heading = threads.length > 0 && requests.length > 0 ? `Comments, and requests from the page's buttons, sent to you from the editor of ${where}:` : threads.length > 0 ? `Comments sent to you from the editor of ${where}:` : `Requests sent to you by the buttons on ${where}:`;
|
|
672
|
-
const text = [
|
|
673
|
-
heading,
|
|
674
|
-
"",
|
|
675
|
-
"```json",
|
|
676
|
-
JSON.stringify(rest, null, 2),
|
|
677
|
-
"```",
|
|
678
|
-
"",
|
|
679
|
-
...threads.length > 0 ? [`Handle the comments as Comments says (${how}).`] : [],
|
|
680
|
-
...requests.length > 0 ? [
|
|
681
|
-
"Each request is one of the page's buttons (`label`) asking for its `text`: those are the user's words, as if",
|
|
682
|
-
"typed here. Handle each as the skill says for those words."
|
|
683
|
-
] : [],
|
|
684
|
-
'Run the script as "Running the calls in DeepSeek Harness" says.',
|
|
685
|
-
"DeepSeek Harness is already waiting for the next ones: do not run `comments --wait`."
|
|
686
|
-
].join("\n");
|
|
687
|
-
const first = threads[0];
|
|
688
|
-
const asked = requests[0];
|
|
689
|
-
const summary = bound(
|
|
690
|
-
first ? `Comment on "${title}": ${first.text.replace(/\s+/g, " ")}`.trim() : `${asked?.label || "Request"} on "${title}": ${(asked?.text ?? "").replace(/\s+/g, " ")}`.trim()
|
|
691
|
-
);
|
|
692
|
-
const message = {
|
|
693
|
-
role: "user",
|
|
694
|
-
id: randomUUID(),
|
|
695
|
-
content: [{ type: "text", text }],
|
|
696
|
-
source: {
|
|
697
|
-
kind: COMMENT_SOURCE,
|
|
698
|
-
form: "notice",
|
|
699
|
-
summary,
|
|
700
|
-
plugin,
|
|
701
|
-
title,
|
|
702
|
-
url,
|
|
703
|
-
root,
|
|
704
|
-
threads,
|
|
705
|
-
...requests.length > 0 ? { requests } : {}
|
|
706
|
-
}
|
|
707
|
-
};
|
|
708
|
-
return Object.freeze(message);
|
|
709
|
-
}
|
|
710
|
-
var CommentWatcher = class {
|
|
711
|
-
watches = /* @__PURE__ */ new Map();
|
|
712
|
-
check;
|
|
713
|
-
ctx;
|
|
714
|
-
skill;
|
|
715
|
-
script;
|
|
716
|
-
node;
|
|
717
|
-
constructor(ctx, skill, script, node) {
|
|
718
|
-
this.ctx = ctx;
|
|
719
|
-
this.skill = skill;
|
|
720
|
-
this.script = script;
|
|
721
|
-
this.node = node;
|
|
722
|
-
this.check = setInterval(() => this.dropGoneSessions(), SESSION_CHECK_MS);
|
|
723
|
-
this.check.unref?.();
|
|
724
|
-
}
|
|
725
|
-
log(level, message) {
|
|
726
|
-
this.ctx.logger?.[level]?.(`${PLUGIN.name}: ${message}`);
|
|
727
|
-
}
|
|
728
|
-
agent(session) {
|
|
729
|
-
return this.ctx.get("agents")?.get(session);
|
|
730
|
-
}
|
|
731
|
-
watch(session, root) {
|
|
732
|
-
const key = `${session}\0${root}`;
|
|
733
|
-
if (this.watches.has(key)) return;
|
|
734
|
-
const watch = { child: null, stopped: false, quickFailures: 0, graceUntil: Date.now() + AGENT_GRACE_MS };
|
|
735
|
-
this.watches.set(key, watch);
|
|
736
|
-
this.log("info", `waiting for comments on ${root}`);
|
|
737
|
-
this.run(key, session, root, watch);
|
|
738
|
-
}
|
|
739
|
-
stop(key, watch, why) {
|
|
740
|
-
if (watch.stopped) return;
|
|
741
|
-
watch.stopped = true;
|
|
742
|
-
if (watch.retry) clearTimeout(watch.retry);
|
|
743
|
-
watch.child?.kill();
|
|
744
|
-
watch.child = null;
|
|
745
|
-
if (this.watches.get(key) === watch) this.watches.delete(key);
|
|
746
|
-
this.log("info", `stopped waiting for comments (${why})`);
|
|
747
|
-
}
|
|
748
|
-
run(key, session, root, watch) {
|
|
749
|
-
watch.retry = void 0;
|
|
750
|
-
if (watch.stopped) return;
|
|
751
|
-
if (this.agent(session) === void 0) {
|
|
752
|
-
if (Date.now() >= watch.graceUntil) return this.stop(key, watch, "the session is gone");
|
|
753
|
-
watch.retry = setTimeout(() => this.run(key, session, root, watch), IDLE_CHECK_MS);
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
const started = Date.now();
|
|
757
|
-
const child = spawn2(this.node.path, [this.script, "comments", root, "--wait"], { env: scriptEnv(this.node), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
758
|
-
watch.child = child;
|
|
759
|
-
let stdout = "";
|
|
760
|
-
let stderr = "";
|
|
761
|
-
child.stdout?.setEncoding("utf8").on("data", (chunk) => stdout += chunk);
|
|
762
|
-
child.stderr?.setEncoding("utf8").on("data", (chunk) => stderr += chunk);
|
|
763
|
-
child.on("error", (error) => this.stop(key, watch, `the wait did not start: ${error.message}`));
|
|
764
|
-
child.on("close", (code) => {
|
|
765
|
-
if (watch.child === child) watch.child = null;
|
|
766
|
-
if (watch.stopped) return;
|
|
767
|
-
if (code === 0) {
|
|
768
|
-
watch.quickFailures = 0;
|
|
769
|
-
const out = jsonObject(stdout);
|
|
770
|
-
const came = (list) => Array.isArray(list) && list.length > 0;
|
|
771
|
-
if (out && (came(out.threads) || came(out.requests))) {
|
|
772
|
-
this.deliver(session, out, root);
|
|
773
|
-
return this.runWhenIdle(key, session, root, watch);
|
|
774
|
-
}
|
|
775
|
-
this.run(key, session, root, watch);
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
const failure = jsonObject(stderr);
|
|
779
|
-
const reason = typeof failure?.code === "string" ? failure.code : `exit ${code ?? "signal"}`;
|
|
780
|
-
if (failure && typeof failure.code === "string") return this.stop(key, watch, reason);
|
|
781
|
-
if (Date.now() - started < RETRY_MS && ++watch.quickFailures > MAX_QUICK_FAILURES) return this.stop(key, watch, reason);
|
|
782
|
-
watch.retry = setTimeout(() => this.run(key, session, root, watch), RETRY_MS);
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
|
-
/** Start the next wait once the agent is done with the batch it was just handed. */
|
|
786
|
-
runWhenIdle(key, session, root, watch) {
|
|
787
|
-
const handed = Date.now();
|
|
788
|
-
let working = false;
|
|
789
|
-
const look = () => {
|
|
790
|
-
watch.retry = void 0;
|
|
791
|
-
if (watch.stopped) return;
|
|
792
|
-
const agent = this.agent(session);
|
|
793
|
-
if (agent === void 0) return this.stop(key, watch, "the session is gone");
|
|
794
|
-
const idle = agent.status === "idle";
|
|
795
|
-
if (!idle) working = true;
|
|
796
|
-
const waited = Date.now() - handed;
|
|
797
|
-
if (idle && (working || waited > TURN_START_MS) || waited > TURN_MAX_MS) return this.run(key, session, root, watch);
|
|
798
|
-
watch.retry = setTimeout(look, IDLE_CHECK_MS);
|
|
799
|
-
};
|
|
800
|
-
look();
|
|
801
|
-
}
|
|
802
|
-
deliver(session, out, root) {
|
|
803
|
-
const agent = this.agent(session);
|
|
804
|
-
if (agent === void 0) return;
|
|
805
|
-
const message = commentMessage(this.skill, PLUGIN.provider, out, root);
|
|
806
|
-
try {
|
|
807
|
-
if (agent.status === "idle") agent.followup(message);
|
|
808
|
-
else agent.inject(message);
|
|
809
|
-
} catch (error) {
|
|
810
|
-
this.log("warn", `could not hand the comments or requests to the agent: ${error.message}`);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
dropGoneSessions() {
|
|
814
|
-
for (const [key, watch] of [...this.watches]) {
|
|
815
|
-
const session = key.slice(0, key.indexOf("\0"));
|
|
816
|
-
if (this.agent(session) === void 0 && Date.now() >= watch.graceUntil) this.stop(key, watch, "the session is gone");
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
dispose() {
|
|
820
|
-
clearInterval(this.check);
|
|
821
|
-
for (const [key, watch] of [...this.watches]) this.stop(key, watch, "the plugin stopped");
|
|
822
|
-
}
|
|
823
|
-
};
|
|
824
125
|
function skillBody(raw, path) {
|
|
825
126
|
const frontmatter = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/u.exec(raw);
|
|
826
127
|
if (frontmatter === null) throw new Error(`${PLUGIN.name}: ${path} has no YAML frontmatter`);
|
|
827
128
|
return raw.slice(frontmatter[0].length).trim();
|
|
828
129
|
}
|
|
829
130
|
function apply(ctx, config = {}) {
|
|
830
|
-
const node = resolveNode(config ?? {});
|
|
131
|
+
const node = resolveNode(PLUGIN.name, config ?? {});
|
|
831
132
|
const skills = new Map(PLUGIN.skills.map((skill) => [skill.name, skill]));
|
|
832
133
|
const candidates = PLUGIN.skills.map(({ name: skillName, description }) => {
|
|
833
134
|
const directory = join2(packageRoot, "skills", skillName);
|
|
@@ -853,40 +154,14 @@ function apply(ctx, config = {}) {
|
|
|
853
154
|
if (skill === void 0) throw new Error(`${PLUGIN.name}: no skill named ${candidate.name}`);
|
|
854
155
|
const raw = await readFile(candidate.path, { encoding: "utf8", signal: options?.signal });
|
|
855
156
|
const directory = dirname2(candidate.path);
|
|
856
|
-
return { ...summary, content: skillBody(raw, candidate.path) + runtimeSection(skill, directory, node) };
|
|
157
|
+
return { ...summary, content: skillBody(raw, candidate.path) + runtimeSection(skill, directory, node, process.platform, hostRunning()) };
|
|
857
158
|
}
|
|
858
159
|
};
|
|
859
160
|
ctx.skills.registerProvider(() => provider);
|
|
860
|
-
for (const skill of PLUGIN.skills) {
|
|
861
|
-
const script = join2(packageRoot, "skills", skill.name, "scripts", "artifact.mjs");
|
|
862
|
-
const watcher = skill.comments ? new CommentWatcher(ctx, skill, script, node) : null;
|
|
863
|
-
if (watcher) ctx.effect(() => () => watcher.dispose(), `${PLUGIN.name}: comment waits`);
|
|
864
|
-
ctx.on("tools/result", (exec, result) => {
|
|
865
|
-
const opened = openedEditor(script, exec, result);
|
|
866
|
-
if (opened === null) return;
|
|
867
|
-
watcher?.watch(opened.session, opened.root);
|
|
868
|
-
try {
|
|
869
|
-
recordOpened(skill.name, PLUGIN.provider, opened.session, opened.root);
|
|
870
|
-
} catch (error) {
|
|
871
|
-
ctx.logger?.warn?.(`${PLUGIN.name}: could not note ${opened.root} for the Artifacts page: ${error.message}`);
|
|
872
|
-
}
|
|
873
|
-
});
|
|
874
|
-
ctx.inject?.(["connection"], (sub) => {
|
|
875
|
-
registerRoutes(sub.connection.fetch, { name: skill.name, plugin: PLUGIN.provider, script, node }, (record) => {
|
|
876
|
-
watcher?.watch(record.sessionId, record.root);
|
|
877
|
-
});
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
161
|
}
|
|
881
162
|
export {
|
|
882
|
-
COMMENT_SOURCE,
|
|
883
163
|
apply,
|
|
884
|
-
commentMessage,
|
|
885
164
|
inject,
|
|
886
|
-
jsonObject,
|
|
887
|
-
jsonObjects,
|
|
888
165
|
name,
|
|
889
|
-
openedEditor,
|
|
890
|
-
resolveNode,
|
|
891
166
|
runtimeSection
|
|
892
167
|
};
|