ryugi-artifacts 1.16.2 → 1.17.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/dist/cli.cjs
CHANGED
|
@@ -281,10 +281,16 @@ async function comment(options, emit) {
|
|
|
281
281
|
throw new CliError(`The running artifacts server is older than this CLI. Run \`${invocation()} start --project-dir ${server.project}\` to replace it, then run this again.`);
|
|
282
282
|
}
|
|
283
283
|
if (status < 200 || status >= 300) throw new CliError(json.error || `HTTP ${status}`);
|
|
284
|
-
if (action === "list") emit({ type: "comments", artifact: options.artifact, threads: json.threads, ...options.all ? { all: true } : {} });
|
|
284
|
+
if (action === "list") emit({ type: "comments", artifact: options.artifact, threads: json.threads, ...json.groups ? { groups: json.groups } : {}, ...options.all ? { all: true } : {} });
|
|
285
285
|
else emit({ type: WRITTEN[action], artifact: options.artifact, threadId: json.threadId, ...json.messageId ? { messageId: json.messageId } : {} });
|
|
286
286
|
return 0;
|
|
287
287
|
}
|
|
288
|
+
var VERDICT_TEXT = { pass: "passed", reject: "rejected", pending: "waiting for a verdict" };
|
|
289
|
+
var plural = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
290
|
+
function groupText(group) {
|
|
291
|
+
const parts = [VERDICT_TEXT[group.verdict], group.evidenceUpdated ? "evidence replaced since the last verdict" : "", group.rejectThreads ? `${plural(group.rejectThreads, "open reject thread")}` : ""];
|
|
292
|
+
return ` ${group.id}${group.title ? ` (${group.title})` : ""}: ${parts.filter(Boolean).join(" \xB7 ")}`;
|
|
293
|
+
}
|
|
288
294
|
var clip = (text2, max) => text2.length > max ? `${text2.slice(0, max)}\u2026` : text2;
|
|
289
295
|
var percent = (value) => `${Math.round(value * 100)}%`;
|
|
290
296
|
function anchorLines(thread) {
|
|
@@ -312,8 +318,12 @@ function threadText(thread) {
|
|
|
312
318
|
function commentText(value) {
|
|
313
319
|
if (value.type === "comments") {
|
|
314
320
|
const threads = value.threads ?? [];
|
|
315
|
-
|
|
316
|
-
|
|
321
|
+
const groups = value.groups ?? [];
|
|
322
|
+
const head = threads.length ? threads.map(threadText).join("\n\n") : value.all ? `No comments on ${value.artifact}.` : `No new comments on ${value.artifact}.`;
|
|
323
|
+
return groups.length ? `${head}
|
|
324
|
+
|
|
325
|
+
Verify groups:
|
|
326
|
+
${groups.map(groupText).join("\n")}` : head;
|
|
317
327
|
}
|
|
318
328
|
if (value.type === "comment-added") return `Added thread ${value.threadId} (message ${value.messageId})`;
|
|
319
329
|
if (value.type === "comment-replied") return `Replied in thread ${value.threadId} (message ${value.messageId})`;
|
|
@@ -339,6 +349,7 @@ var import_node_path2 = __toESM(require("node:path"), 1);
|
|
|
339
349
|
// src/shared/validate.ts
|
|
340
350
|
var BOARD_NUMBERS = ["x", "y", "w", "h"];
|
|
341
351
|
var BOARD_REQUIRED = ["file", "page", ...BOARD_NUMBERS];
|
|
352
|
+
var MEDIA_REQUIRED = ["src", "page", ...BOARD_NUMBERS];
|
|
342
353
|
|
|
343
354
|
// src/server/state.ts
|
|
344
355
|
function writeJsonAtomic(file, value) {
|
|
@@ -911,7 +922,7 @@ async function main(argv) {
|
|
|
911
922
|
}, emit);
|
|
912
923
|
}
|
|
913
924
|
if (command === "--version" || command === "-v") {
|
|
914
|
-
console.log("1.
|
|
925
|
+
console.log("1.17.0");
|
|
915
926
|
return 0;
|
|
916
927
|
}
|
|
917
928
|
if (command === "--help" || command === "-h") {
|
package/dist/server.cjs
CHANGED
|
@@ -5182,6 +5182,8 @@ var import_node_path20 = __toESM(require("node:path"), 1);
|
|
|
5182
5182
|
|
|
5183
5183
|
// src/shared/types.ts
|
|
5184
5184
|
var ARTIFACT_TYPES = ["artboard", "document", "html"];
|
|
5185
|
+
var BOARD_TYPES = ["html", "image", "video"];
|
|
5186
|
+
var isMediaBoard = (board) => board.type === "image" || board.type === "video";
|
|
5185
5187
|
|
|
5186
5188
|
// src/server/artifacts.ts
|
|
5187
5189
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
@@ -17961,6 +17963,14 @@ function readBoardAnchor(value) {
|
|
|
17961
17963
|
if (!isName(value.board)) return null;
|
|
17962
17964
|
anchor.board = value.board;
|
|
17963
17965
|
}
|
|
17966
|
+
if (value.group !== void 0) {
|
|
17967
|
+
if (!isName(value.group)) return null;
|
|
17968
|
+
anchor.group = value.group;
|
|
17969
|
+
}
|
|
17970
|
+
if (value.verdict !== void 0) {
|
|
17971
|
+
if (!anchor.group || value.verdict !== "pass" && value.verdict !== "reject") return null;
|
|
17972
|
+
anchor.verdict = value.verdict;
|
|
17973
|
+
}
|
|
17964
17974
|
if (value.nodes !== void 0) {
|
|
17965
17975
|
const nodes = readNodes(value.nodes);
|
|
17966
17976
|
if (!nodes) return null;
|
|
@@ -17980,17 +17990,24 @@ function readBoardAnchor(value) {
|
|
|
17980
17990
|
}
|
|
17981
17991
|
var boardAnchors = {
|
|
17982
17992
|
read: readBoardAnchor,
|
|
17983
|
-
same: (a5, b6) => a5.page === b6.page && a5.board === b6.board && a5.x === b6.x && a5.y === b6.y,
|
|
17993
|
+
same: (a5, b6) => a5.page === b6.page && a5.board === b6.board && a5.group === b6.group && a5.verdict === b6.verdict && a5.x === b6.x && a5.y === b6.y,
|
|
17984
17994
|
missing: "a new thread needs a board anchor (page, x, y); dropped it"
|
|
17985
17995
|
};
|
|
17986
17996
|
var emptyBoardComments = () => ({ version: 1, threads: [] });
|
|
17987
17997
|
var savedBoardComments = (incoming) => savedComments(incoming, boardAnchors);
|
|
17988
17998
|
var mergeBoardComments = (base, incoming, origin, now, newId, name) => mergeComments(base, incoming, origin, now, newId, name, boardAnchors);
|
|
17989
17999
|
function applyBoardHumanOp(file, op, now, newId, who) {
|
|
17990
|
-
if (op.op
|
|
17991
|
-
|
|
18000
|
+
if (op.op !== "create") return applyThreadOp(file, op, now, newId, who);
|
|
18001
|
+
const thread = openThread(op.anchor, "human", op.text, now, newId, who);
|
|
18002
|
+
if (op.anchor.verdict !== "pass") return { ...file, threads: [...file.threads, thread] };
|
|
18003
|
+
const closed = { status: "resolved", statusAt: now, statusBy: "human" };
|
|
18004
|
+
const rejected = (t7) => t7.anchor.group === op.anchor.group && t7.anchor.verdict === "reject" && t7.status === "open";
|
|
18005
|
+
return { ...file, threads: [...file.threads.map((t7) => rejected(t7) ? { ...t7, ...closed } : t7), { ...thread, ...closed }] };
|
|
17992
18006
|
}
|
|
17993
18007
|
function applyBoardAgentOp(file, op, now, newId, name) {
|
|
18008
|
+
if (op.op === "resolve" && file.threads.find((t7) => t7.id === op.threadId)?.anchor.verdict === "reject") {
|
|
18009
|
+
throw new Error("a reject closes only when your human partner passes the group");
|
|
18010
|
+
}
|
|
17994
18011
|
if (op.op === "add") {
|
|
17995
18012
|
const thread = openThread(op.anchor, "agent", op.text, now, newId, { name });
|
|
17996
18013
|
return { file: { ...file, threads: [...file.threads, thread] }, threadId: thread.id, messageId: thread.messages[0].id };
|
|
@@ -17998,6 +18015,19 @@ function applyBoardAgentOp(file, op, now, newId, name) {
|
|
|
17998
18015
|
return applyAgentThreadOp(file, op, now, newId, name);
|
|
17999
18016
|
}
|
|
18000
18017
|
|
|
18018
|
+
// src/shared/verdicts.ts
|
|
18019
|
+
var openedAt = (thread) => thread.messages[0]?.at ?? thread.statusAt;
|
|
18020
|
+
function groupVerdict(threads, group, stale) {
|
|
18021
|
+
const own = threads.filter((thread) => thread.anchor.group === group && thread.anchor.verdict && thread.messages.some((m11) => !m11.deleted)).sort((a5, b6) => openedAt(a5) < openedAt(b6) ? -1 : openedAt(a5) > openedAt(b6) ? 1 : 0);
|
|
18022
|
+
const rejectThreads = own.filter((thread) => thread.anchor.verdict === "reject" && thread.status === "open").length;
|
|
18023
|
+
const latest = own.at(-1);
|
|
18024
|
+
if (!latest) return { state: "pending", evidenceUpdated: false, rejectThreads };
|
|
18025
|
+
const at7 = openedAt(latest);
|
|
18026
|
+
if (stale.has(latest.id)) return { state: "pending", at: at7, evidenceUpdated: true, rejectThreads };
|
|
18027
|
+
if (latest.anchor.verdict === "pass") return { state: "pass", at: at7, evidenceUpdated: false, rejectThreads };
|
|
18028
|
+
return { state: latest.status === "open" ? "reject" : "pending", at: at7, evidenceUpdated: false, rejectThreads };
|
|
18029
|
+
}
|
|
18030
|
+
|
|
18001
18031
|
// src/server/comment-cursors.ts
|
|
18002
18032
|
var import_node_fs6 = __toESM(require("node:fs"), 1);
|
|
18003
18033
|
var import_node_path6 = __toESM(require("node:path"), 1);
|
|
@@ -18093,6 +18123,9 @@ var intersects = (a5, b6) => a5.x < b6.x + b6.w && b6.x < a5.x + a5.w && a5.y <
|
|
|
18093
18123
|
// src/shared/validate.ts
|
|
18094
18124
|
var BOARD_NUMBERS = ["x", "y", "w", "h"];
|
|
18095
18125
|
var BOARD_REQUIRED = ["file", "page", ...BOARD_NUMBERS];
|
|
18126
|
+
var MEDIA_REQUIRED = ["src", "page", ...BOARD_NUMBERS];
|
|
18127
|
+
var fileOf = (board) => isMediaBoard(board) ? board.src ?? "" : board.file;
|
|
18128
|
+
var fileField = (board) => isMediaBoard(board) ? "src" : "file";
|
|
18096
18129
|
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18097
18130
|
function structuralWarnings(raw) {
|
|
18098
18131
|
if (!isObject2(raw)) return ["canvas.json must be a JSON object"];
|
|
@@ -18122,6 +18155,16 @@ function structuralWarnings(raw) {
|
|
|
18122
18155
|
if (isObject2(board) && board.interactive !== void 0 && typeof board.interactive !== "boolean") {
|
|
18123
18156
|
warnings.push(`board "${id2}" field "interactive" must be a boolean`);
|
|
18124
18157
|
}
|
|
18158
|
+
if (isObject2(board) && board.type !== void 0 && !BOARD_TYPES.includes(board.type)) {
|
|
18159
|
+
warnings.push(`board "${id2}" field "type" must be "html", "image" or "video"`);
|
|
18160
|
+
}
|
|
18161
|
+
}
|
|
18162
|
+
}
|
|
18163
|
+
if (isObject2(raw.groups)) {
|
|
18164
|
+
for (const [id2, group] of Object.entries(raw.groups)) {
|
|
18165
|
+
if (isObject2(group) && group.verify !== void 0 && typeof group.verify !== "boolean") {
|
|
18166
|
+
warnings.push(`group "${id2}" field "verify" must be a boolean`);
|
|
18167
|
+
}
|
|
18125
18168
|
}
|
|
18126
18169
|
}
|
|
18127
18170
|
if (warnings.length > 0) return warnings;
|
|
@@ -18135,7 +18178,7 @@ function structuralWarnings(raw) {
|
|
|
18135
18178
|
warnings.push(`board "${id2}" must be an object`);
|
|
18136
18179
|
continue;
|
|
18137
18180
|
}
|
|
18138
|
-
for (const key2 of BOARD_REQUIRED) {
|
|
18181
|
+
for (const key2 of isMediaBoard(board) ? MEDIA_REQUIRED : BOARD_REQUIRED) {
|
|
18139
18182
|
if (board[key2] === void 0) warnings.push(`board "${id2}" is missing "${key2}"`);
|
|
18140
18183
|
}
|
|
18141
18184
|
for (const key2 of BOARD_NUMBERS) {
|
|
@@ -18143,7 +18186,7 @@ function structuralWarnings(raw) {
|
|
|
18143
18186
|
warnings.push(`board "${id2}" field "${key2}" must be a number`);
|
|
18144
18187
|
}
|
|
18145
18188
|
}
|
|
18146
|
-
for (const key2 of ["file", "page"]) {
|
|
18189
|
+
for (const key2 of ["file", "src", "page"]) {
|
|
18147
18190
|
if (board[key2] !== void 0 && typeof board[key2] !== "string") {
|
|
18148
18191
|
warnings.push(`board "${id2}" field "${key2}" must be a string`);
|
|
18149
18192
|
}
|
|
@@ -18174,10 +18217,11 @@ function validateCanvas(raw, fileExists, options = {}) {
|
|
|
18174
18217
|
const notes = Object.entries(canvas.notes ?? {});
|
|
18175
18218
|
for (const [id2, board] of boards) {
|
|
18176
18219
|
if (!pages.has(board.page)) warnings.push(`board "${id2}" uses unknown page "${board.page}"`);
|
|
18177
|
-
|
|
18178
|
-
|
|
18220
|
+
const file = fileOf(board);
|
|
18221
|
+
if (!file.startsWith("pages/")) warnings.push(`board "${id2}" ${fileField(board)} must start with "pages/": ${file}`);
|
|
18222
|
+
else if (!fileExists(file)) {
|
|
18179
18223
|
options.pending?.add(id2);
|
|
18180
|
-
if (!options.allowPending) warnings.push(`board "${id2}" file not found: ${
|
|
18224
|
+
if (!options.allowPending) warnings.push(`board "${id2}" file not found: ${file}`);
|
|
18181
18225
|
}
|
|
18182
18226
|
}
|
|
18183
18227
|
for (let i6 = 0; i6 < boards.length; i6++) {
|
|
@@ -18288,6 +18332,10 @@ function boardVersions(boardDir, canvas) {
|
|
|
18288
18332
|
}
|
|
18289
18333
|
try {
|
|
18290
18334
|
const stat = import_node_fs5.default.statSync(target);
|
|
18335
|
+
if (isMediaBoard(board)) {
|
|
18336
|
+
versions[board.file] = stat.size > 0 ? stat.mtimeMs : 0;
|
|
18337
|
+
continue;
|
|
18338
|
+
}
|
|
18291
18339
|
const html = import_node_fs5.default.readFileSync(target, "utf8");
|
|
18292
18340
|
const document2 = /<(?:!doctype|html)\b/i.test(html);
|
|
18293
18341
|
const ready = html.trim().length > 0 && (!document2 || /<\/html>\s*(?:<!--[\s\S]*?-->\s*)*$/i.test(html));
|
|
@@ -18309,6 +18357,7 @@ function readCanvas(boardDir) {
|
|
|
18309
18357
|
const structural = structuralWarnings(raw);
|
|
18310
18358
|
if (structural.length > 0) return { canvas: null, warnings: structural, pending: [], layout: null, versions: {} };
|
|
18311
18359
|
const canvas = { ...raw };
|
|
18360
|
+
canvas.boards = Object.fromEntries(Object.entries(canvas.boards).map(([id2, board]) => [id2, isMediaBoard(board) ? { ...board, file: board.src ?? "" } : board]));
|
|
18312
18361
|
const { versions, writing } = boardVersions(boardDir, canvas);
|
|
18313
18362
|
const pending = /* @__PURE__ */ new Set();
|
|
18314
18363
|
for (const [id2, board] of Object.entries(canvas.boards)) {
|
|
@@ -18487,6 +18536,7 @@ var boardRules = {
|
|
|
18487
18536
|
agent: applyBoardAgentOp
|
|
18488
18537
|
};
|
|
18489
18538
|
var withoutHashes = ({ hashes: _hashes, ...anchor }) => anchor;
|
|
18539
|
+
var withoutVerdict = ({ hashes: _hashes, group: _group, verdict: _verdict, ...anchor }) => anchor;
|
|
18490
18540
|
function asBoardHumanOp(value) {
|
|
18491
18541
|
if (typeof value !== "object" || value === null) return null;
|
|
18492
18542
|
const v6 = value;
|
|
@@ -18502,22 +18552,30 @@ function asBoardAgentOp(value) {
|
|
|
18502
18552
|
const v6 = value;
|
|
18503
18553
|
if (v6.op === "add") {
|
|
18504
18554
|
const anchor = readBoardAnchor(v6.anchor);
|
|
18505
|
-
return anchor && isText3(v6.text) && v6.text.length > 0 ? { op: "add", anchor:
|
|
18555
|
+
return anchor && isText3(v6.text) && v6.text.length > 0 ? { op: "add", anchor: withoutVerdict(anchor), text: v6.text } : null;
|
|
18506
18556
|
}
|
|
18507
18557
|
if (v6.op === "reply") return isId(v6.threadId) && isText3(v6.text) && v6.text.length > 0 ? { op: "reply", threadId: v6.threadId, text: v6.text } : null;
|
|
18508
18558
|
if (v6.op === "resolve") return isId(v6.threadId) ? { op: "resolve", threadId: v6.threadId } : null;
|
|
18509
18559
|
return null;
|
|
18510
18560
|
}
|
|
18511
|
-
function
|
|
18561
|
+
function readCanvasJson(dir) {
|
|
18512
18562
|
try {
|
|
18513
|
-
|
|
18514
|
-
return Object.fromEntries(Object.entries(canvas.boards ?? {}).flatMap(
|
|
18515
|
-
([id2, board]) => typeof board?.file === "string" ? [[id2, { file: board.file, page: typeof board.page === "string" ? board.page : "", w: typeof board.w === "number" ? board.w : 0 }]] : []
|
|
18516
|
-
));
|
|
18563
|
+
return JSON.parse(import_node_fs8.default.readFileSync(import_node_path7.default.join(dir, "canvas.json"), "utf8"));
|
|
18517
18564
|
} catch {
|
|
18518
18565
|
return {};
|
|
18519
18566
|
}
|
|
18520
18567
|
}
|
|
18568
|
+
function boardSpecs(dir) {
|
|
18569
|
+
return Object.fromEntries(Object.entries(readCanvasJson(dir).boards ?? {}).flatMap(([id2, board]) => {
|
|
18570
|
+
const file = board?.type === "image" || board?.type === "video" ? board.src : board?.file;
|
|
18571
|
+
return typeof file === "string" ? [[id2, { file, page: typeof board.page === "string" ? board.page : "", w: typeof board.w === "number" ? board.w : 0 }]] : [];
|
|
18572
|
+
}));
|
|
18573
|
+
}
|
|
18574
|
+
function groupSpecs(dir) {
|
|
18575
|
+
return Object.fromEntries(Object.entries(readCanvasJson(dir).groups ?? {}).flatMap(
|
|
18576
|
+
([id2, group]) => Array.isArray(group?.boards) ? [[id2, { ...typeof group.title === "string" ? { title: group.title } : {}, boards: group.boards.filter((b6) => typeof b6 === "string"), verify: group.verify === true }]] : []
|
|
18577
|
+
));
|
|
18578
|
+
}
|
|
18521
18579
|
var boardFiles = (dir) => Object.fromEntries(Object.entries(boardSpecs(dir)).map(([id2, spec]) => [id2, spec.file]));
|
|
18522
18580
|
function agentAddOnBoard(value, specs) {
|
|
18523
18581
|
if (typeof value !== "object" || value === null) return null;
|
|
@@ -18527,11 +18585,18 @@ function agentAddOnBoard(value, specs) {
|
|
|
18527
18585
|
if (!spec) return { error: `no board ${v6.board}` };
|
|
18528
18586
|
return asBoardAgentOp({ op: "add", anchor: { page: spec.page, board: v6.board, x: v6.x ?? spec.w, y: v6.y ?? 0 }, text: v6.text });
|
|
18529
18587
|
}
|
|
18588
|
+
var hashCache = /* @__PURE__ */ new Map();
|
|
18530
18589
|
function fileHash(dir, relative) {
|
|
18531
18590
|
const file = resolveBoardFile(dir, relative);
|
|
18532
18591
|
if (!file) return null;
|
|
18533
18592
|
try {
|
|
18534
|
-
|
|
18593
|
+
const stat = import_node_fs8.default.statSync(file);
|
|
18594
|
+
const stamp2 = `${stat.size}:${stat.mtimeMs}`;
|
|
18595
|
+
const cached = hashCache.get(file);
|
|
18596
|
+
if (cached?.stamp === stamp2) return cached.hash;
|
|
18597
|
+
const hash = import_node_crypto2.default.createHash("sha1").update(import_node_fs8.default.readFileSync(file)).digest("hex").slice(0, 12);
|
|
18598
|
+
hashCache.set(file, { stamp: stamp2, hash });
|
|
18599
|
+
return hash;
|
|
18535
18600
|
} catch {
|
|
18536
18601
|
return null;
|
|
18537
18602
|
}
|
|
@@ -18542,13 +18607,20 @@ function boardHashes(dir) {
|
|
|
18542
18607
|
return hash ? [[id2, hash]] : [];
|
|
18543
18608
|
}));
|
|
18544
18609
|
}
|
|
18545
|
-
var boardsOf = (anchor
|
|
18546
|
-
|
|
18547
|
-
|
|
18610
|
+
var boardsOf = (anchor, groups = {}) => [
|
|
18611
|
+
.../* @__PURE__ */ new Set([...anchor.board ? [anchor.board] : [], ...(anchor.nodes ?? []).map((node) => node.board), ...anchor.group ? groups[anchor.group]?.boards ?? [] : []])
|
|
18612
|
+
];
|
|
18613
|
+
function stamp(anchor, hashes, groups = {}) {
|
|
18614
|
+
const boards = boardsOf(anchor, groups);
|
|
18548
18615
|
if (!boards.length) return withoutHashes(anchor);
|
|
18549
18616
|
return { ...withoutHashes(anchor), hashes: Object.fromEntries(boards.map((board) => [board, hashes[board] ?? "missing"])) };
|
|
18550
18617
|
}
|
|
18551
|
-
var
|
|
18618
|
+
var sameMembers = (thread, groups) => {
|
|
18619
|
+
const group = thread.anchor.group;
|
|
18620
|
+
if (!group) return true;
|
|
18621
|
+
return Object.keys(thread.anchor.hashes ?? {}).sort().join("\n") === [...groups[group]?.boards ?? []].sort().join("\n");
|
|
18622
|
+
};
|
|
18623
|
+
var isStale = (thread, hashes, groups) => !sameMembers(thread, groups) || Object.entries(thread.anchor.hashes ?? {}).some(([board, hash]) => hashes[board] !== hash);
|
|
18552
18624
|
function createBoardCommentRegistry(options) {
|
|
18553
18625
|
const stores = /* @__PURE__ */ new Map();
|
|
18554
18626
|
const hashes = /* @__PURE__ */ new Map();
|
|
@@ -18573,7 +18645,8 @@ function createBoardCommentRegistry(options) {
|
|
|
18573
18645
|
};
|
|
18574
18646
|
const stale = (id2) => {
|
|
18575
18647
|
const now = currentHashes(id2);
|
|
18576
|
-
|
|
18648
|
+
const groups = groupSpecs(dirOf(id2));
|
|
18649
|
+
return store(id2).current().threads.filter((thread) => isStale(thread, now, groups)).map((thread) => thread.id);
|
|
18577
18650
|
};
|
|
18578
18651
|
const push2 = (id2) => {
|
|
18579
18652
|
const ids = stale(id2);
|
|
@@ -18586,11 +18659,12 @@ function createBoardCommentRegistry(options) {
|
|
|
18586
18659
|
};
|
|
18587
18660
|
const forAgent2 = (id2, staleIds) => {
|
|
18588
18661
|
const files = boardFiles(dirOf(id2));
|
|
18662
|
+
const groups = groupSpecs(dirOf(id2));
|
|
18589
18663
|
return (thread) => ({
|
|
18590
18664
|
...thread,
|
|
18591
18665
|
stale: staleIds.has(thread.id),
|
|
18592
18666
|
waitingOn: waitingOn(thread),
|
|
18593
|
-
files: Object.fromEntries(boardsOf(thread.anchor).flatMap((board) => files[board] ? [[board, files[board]]] : [])),
|
|
18667
|
+
files: Object.fromEntries(boardsOf(thread.anchor, groups).flatMap((board) => files[board] ? [[board, files[board]]] : [])),
|
|
18594
18668
|
messages: thread.messages.map(({ avatar: _avatar, ...message }) => message)
|
|
18595
18669
|
});
|
|
18596
18670
|
};
|
|
@@ -18599,7 +18673,9 @@ function createBoardCommentRegistry(options) {
|
|
|
18599
18673
|
stale,
|
|
18600
18674
|
warnings: (id2) => store(id2).warnings(),
|
|
18601
18675
|
apply: (id2, op) => {
|
|
18602
|
-
const
|
|
18676
|
+
const groups = groupSpecs(dirOf(id2));
|
|
18677
|
+
if (op.op === "create" && op.anchor.verdict && !groups[op.anchor.group]?.verify) throw new Error(`no verify group ${op.anchor.group} in artboard/${id2}`);
|
|
18678
|
+
const stamped = op.op === "create" ? { ...op, anchor: stamp(op.anchor, fresh(id2), groups) } : op;
|
|
18603
18679
|
store(id2).apply(stamped);
|
|
18604
18680
|
push2(id2);
|
|
18605
18681
|
return { comments: store(id2).current(), stale: stale(id2) };
|
|
@@ -18620,6 +18696,16 @@ function createBoardCommentRegistry(options) {
|
|
|
18620
18696
|
if (newest) cursors.set(id2, agent, newest);
|
|
18621
18697
|
return threads.map(format);
|
|
18622
18698
|
},
|
|
18699
|
+
groups: (id2) => {
|
|
18700
|
+
const specs = boardSpecs(dirOf(id2));
|
|
18701
|
+
const threads = store(id2).current().threads;
|
|
18702
|
+
const staleIds = new Set(stale(id2));
|
|
18703
|
+
return Object.entries(groupSpecs(dirOf(id2))).filter(([, group]) => group.verify).map(([groupId, group]) => {
|
|
18704
|
+
const { state, ...verdict } = groupVerdict(threads, groupId, staleIds);
|
|
18705
|
+
const page = group.boards.map((board) => specs[board]?.page).find(Boolean) ?? "";
|
|
18706
|
+
return { id: groupId, page, ...group.title ? { title: group.title } : {}, verdict: state, ...verdict, boards: group.boards };
|
|
18707
|
+
});
|
|
18708
|
+
},
|
|
18623
18709
|
sync: (id2) => {
|
|
18624
18710
|
if (store(id2).sync()) push2(id2);
|
|
18625
18711
|
},
|
|
@@ -88587,10 +88673,29 @@ function send(res, status, body, headers = {}) {
|
|
|
88587
88673
|
function sendJson(res, status, value) {
|
|
88588
88674
|
send(res, status, JSON.stringify(value), { "Content-Type": MIME2[".json"] });
|
|
88589
88675
|
}
|
|
88590
|
-
function
|
|
88591
|
-
|
|
88592
|
-
|
|
88593
|
-
|
|
88676
|
+
function byteRange(range, size) {
|
|
88677
|
+
const match = range && /^bytes=(\d*)-(\d*)$/.exec(range.trim());
|
|
88678
|
+
if (!match || !match[1] && !match[2]) return null;
|
|
88679
|
+
const start = match[1] ? Number(match[1]) : Math.max(0, size - Number(match[2]));
|
|
88680
|
+
const end = match[1] && match[2] ? Math.min(Number(match[2]), size - 1) : size - 1;
|
|
88681
|
+
return start > end || start >= size ? "unsatisfiable" : { start, end };
|
|
88682
|
+
}
|
|
88683
|
+
function sendFile(res, file, headers = {}, range) {
|
|
88684
|
+
import_node_fs10.default.stat(file, (err, stat) => {
|
|
88685
|
+
if (err || !stat.isFile()) {
|
|
88686
|
+
send(res, 404, "Not found");
|
|
88687
|
+
return;
|
|
88688
|
+
}
|
|
88689
|
+
const base = { "Referrer-Policy": "no-referrer", "Cache-Control": "no-store", "Content-Type": mimeOf(file), "Accept-Ranges": "bytes", ...headers };
|
|
88690
|
+
const part = byteRange(range, stat.size);
|
|
88691
|
+
if (part === "unsatisfiable") {
|
|
88692
|
+
res.writeHead(416, { ...base, "Content-Range": `bytes */${stat.size}` });
|
|
88693
|
+
res.end();
|
|
88694
|
+
return;
|
|
88695
|
+
}
|
|
88696
|
+
if (part) res.writeHead(206, { ...base, "Content-Range": `bytes ${part.start}-${part.end}/${stat.size}`, "Content-Length": String(part.end - part.start + 1) });
|
|
88697
|
+
else res.writeHead(200, { ...base, "Content-Length": String(stat.size) });
|
|
88698
|
+
import_node_fs10.default.createReadStream(file, part ?? {}).on("error", () => res.destroy()).pipe(res);
|
|
88594
88699
|
});
|
|
88595
88700
|
}
|
|
88596
88701
|
|
|
@@ -88750,15 +88855,15 @@ function readAnySelection(value) {
|
|
|
88750
88855
|
function createSelectionStore(stateDir, exists) {
|
|
88751
88856
|
const tabs = /* @__PURE__ */ new Map();
|
|
88752
88857
|
let clock = 0;
|
|
88753
|
-
const
|
|
88858
|
+
const fileOf2 = (kind, id2) => import_node_path12.default.join(stateDir, kind, id2, "selection.json");
|
|
88754
88859
|
const write = (kind, id2) => {
|
|
88755
88860
|
let winner;
|
|
88756
88861
|
for (const tab of tabs.values()) {
|
|
88757
88862
|
if (tab.kind !== kind || tab.id !== id2) continue;
|
|
88758
88863
|
if (!winner || tab.humanAt > winner.humanAt || tab.humanAt === winner.humanAt && tab.seenAt > winner.seenAt) winner = tab;
|
|
88759
88864
|
}
|
|
88760
|
-
if (!winner?.state) import_node_fs12.default.rmSync(
|
|
88761
|
-
else writeJsonAtomic(
|
|
88865
|
+
if (!winner?.state) import_node_fs12.default.rmSync(fileOf2(kind, id2), { force: true });
|
|
88866
|
+
else writeJsonAtomic(fileOf2(kind, id2), { artifact: `${kind}/${id2}`, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...winner.state });
|
|
88762
88867
|
};
|
|
88763
88868
|
return {
|
|
88764
88869
|
accept(socket, value) {
|
|
@@ -88792,7 +88897,7 @@ function createSelectionStore(stateDir, exists) {
|
|
|
88792
88897
|
} catch {
|
|
88793
88898
|
continue;
|
|
88794
88899
|
}
|
|
88795
|
-
for (const id2 of ids) import_node_fs12.default.rmSync(
|
|
88900
|
+
for (const id2 of ids) import_node_fs12.default.rmSync(fileOf2(kind, id2), { force: true });
|
|
88796
88901
|
}
|
|
88797
88902
|
}
|
|
88798
88903
|
};
|
|
@@ -89998,14 +90103,16 @@ function createAutoPublisher(options) {
|
|
|
89998
90103
|
var import_node_fs21 = __toESM(require("node:fs"), 1);
|
|
89999
90104
|
var RESOLVE = { artboard: resolveBoardFile, document: resolveDocumentAsset, html: resolveHtmlFile };
|
|
90000
90105
|
var isArtifactPage = (type, relative) => type === "html" ? relative === "index.html" : relative.endsWith(".html");
|
|
90001
|
-
function readArtifactFile(dir, type, relative, uiRuntimeUrl) {
|
|
90106
|
+
function readArtifactFile(dir, type, relative, uiRuntimeUrl, maxBytes = Infinity) {
|
|
90002
90107
|
if (!resolveInside(dir, relative)) return null;
|
|
90003
90108
|
const target = RESOLVE[type](dir, relative);
|
|
90004
90109
|
if (!target) return null;
|
|
90005
90110
|
let data;
|
|
90006
90111
|
try {
|
|
90112
|
+
if (import_node_fs21.default.statSync(target).size > maxBytes) throw new RangeError("too large");
|
|
90007
90113
|
data = import_node_fs21.default.readFileSync(target);
|
|
90008
|
-
} catch {
|
|
90114
|
+
} catch (err) {
|
|
90115
|
+
if (err instanceof RangeError) throw err;
|
|
90009
90116
|
return null;
|
|
90010
90117
|
}
|
|
90011
90118
|
if (!isArtifactPage(type, relative)) return { contentType: mimeOf(target), data };
|
|
@@ -90348,7 +90455,11 @@ async function startArtifactsServer(options) {
|
|
|
90348
90455
|
sendJson(res, 400, { error: "not a comment op" });
|
|
90349
90456
|
return;
|
|
90350
90457
|
}
|
|
90351
|
-
|
|
90458
|
+
try {
|
|
90459
|
+
sendJson(res, 200, boardComments.apply(id2, op));
|
|
90460
|
+
} catch (err) {
|
|
90461
|
+
sendJson(res, 400, { error: err.message });
|
|
90462
|
+
}
|
|
90352
90463
|
};
|
|
90353
90464
|
const serveAgentBoardComments = async (req, res, id2) => {
|
|
90354
90465
|
const dir = resolveArtifactDir(root, "artboard", id2);
|
|
@@ -90360,7 +90471,8 @@ async function startArtifactsServer(options) {
|
|
|
90360
90471
|
const fields = typeof body === "object" && body !== null ? body : {};
|
|
90361
90472
|
const agent = typeof fields.agent === "string" && fields.agent.length > 0 && fields.agent.length <= 100 ? fields.agent : void 0;
|
|
90362
90473
|
if (fields.op === "list") {
|
|
90363
|
-
|
|
90474
|
+
const threads = boardComments.list(id2, agent ?? "Agent", fields.all === true);
|
|
90475
|
+
sendJson(res, 200, { type: "comments", artifact: `artboard/${id2}`, threads, groups: boardComments.groups(id2) });
|
|
90364
90476
|
return;
|
|
90365
90477
|
}
|
|
90366
90478
|
const shorthand = agentAddOnBoard(body, boardSpecs(dir));
|
|
@@ -90384,7 +90496,7 @@ async function startArtifactsServer(options) {
|
|
|
90384
90496
|
}
|
|
90385
90497
|
};
|
|
90386
90498
|
const isAuthorized = (req, keyFromQuery) => keyFromQuery !== null && safeEqual(keyFromQuery, token) || safeEqual(parseCookies(req.headers.cookie)[cookieName] ?? "", token);
|
|
90387
|
-
const serveContent = (res, rest) => {
|
|
90499
|
+
const serveContent = (res, rest, range) => {
|
|
90388
90500
|
if (rest.startsWith("runtime/")) {
|
|
90389
90501
|
if (!runtimeDir) {
|
|
90390
90502
|
send(res, 404, "Not found");
|
|
@@ -90420,7 +90532,7 @@ async function startArtifactsServer(options) {
|
|
|
90420
90532
|
const page = ref.type === "html" ? relative === "index.html" : relative.endsWith(".html");
|
|
90421
90533
|
const headers = { "Content-Security-Policy": CONTENT_CSP[page && ref.type === "document" ? "html" : ref.type], "Access-Control-Allow-Origin": "*" };
|
|
90422
90534
|
if (!page) {
|
|
90423
|
-
sendFile(res, target, headers);
|
|
90535
|
+
sendFile(res, target, headers, range);
|
|
90424
90536
|
return;
|
|
90425
90537
|
}
|
|
90426
90538
|
import_node_fs23.default.readFile(target, "utf8", (err, source) => {
|
|
@@ -90520,7 +90632,7 @@ async function startArtifactsServer(options) {
|
|
|
90520
90632
|
}
|
|
90521
90633
|
if (pathname.startsWith("/c/")) {
|
|
90522
90634
|
if (!pathname.startsWith(contentPrefix)) send(res, 404, "Not found");
|
|
90523
|
-
else serveContent(res, pathname.slice(contentPrefix.length));
|
|
90635
|
+
else serveContent(res, pathname.slice(contentPrefix.length), req.headers.range);
|
|
90524
90636
|
return;
|
|
90525
90637
|
}
|
|
90526
90638
|
if (cloud && pathname === "/api/cloud/callback") {
|
|
@@ -90748,7 +90860,7 @@ async function startArtifactsServer(options) {
|
|
|
90748
90860
|
const request = typeof body === "object" && body !== null ? body : {};
|
|
90749
90861
|
if (request.op === "meta") return artifactMeta(ref, dir, contentOrigin);
|
|
90750
90862
|
if (request.op === "file") {
|
|
90751
|
-
const file = typeof request.path === "string" ? readArtifactFile(dir, ref.type, request.path, CLOUD_RUNTIME_URL) : null;
|
|
90863
|
+
const file = typeof request.path === "string" ? readArtifactFile(dir, ref.type, request.path, CLOUD_RUNTIME_URL, MAX_RELAY_FILE) : null;
|
|
90752
90864
|
if (!file) throw new Error("not found");
|
|
90753
90865
|
if (file.data.length > MAX_RELAY_FILE) throw new Error("too large");
|
|
90754
90866
|
return { contentType: file.contentType, data: file.data.toString("base64") };
|
|
@@ -90967,7 +91079,7 @@ function openReady(payload) {
|
|
|
90967
91079
|
}
|
|
90968
91080
|
|
|
90969
91081
|
// package.json
|
|
90970
|
-
var version = "1.
|
|
91082
|
+
var version = "1.17.0";
|
|
90971
91083
|
|
|
90972
91084
|
// src/server/main.ts
|
|
90973
91085
|
async function main() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{i as e}from"./rolldown-runtime-COiEfhsA.js";import{n as t,t as n}from"./jsx-runtime-CldxteV5.js";import{a as r}from"./useSandboxFrame-N5uRz7Cc.js";import{t as i}from"./index.js";import a from"./CloudDocument-DQ8BLzr7.js";import{DocumentView as o,t as s}from"./DocumentPage-CgCHoFVv.js";var c=e(t(),1),l=`1.
|
|
1
|
+
import{i as e}from"./rolldown-runtime-COiEfhsA.js";import{n as t,t as n}from"./jsx-runtime-CldxteV5.js";import{a as r}from"./useSandboxFrame-N5uRz7Cc.js";import{t as i}from"./index.js";import a from"./CloudDocument-DQ8BLzr7.js";import{DocumentView as o,t as s}from"./DocumentPage-CgCHoFVv.js";var c=e(t(),1),l=`1.17.0`,u=e=>/^(\d+)\.(\d+)\./.exec(e??``)?.slice(1,3).join(`.`)??null;function d(e,t){let n=u(e);return n!==null&&n===u(t)}var f=(e,t)=>`电脑上的 ryugi 是 ${t??`未知版本`},网页是 ${e},版本对上之前只能评论`,p=n();function m({id:e,state:t,contentBase:n,liveContentBase:u,comments:m}){let{transport:h,presence:g}=s(e,u??n??``),_=g?.online??!1;r(e,{online:g?.online??null});let[v,y]=(0,c.useState)(!1);if((0,c.useEffect)(()=>{_&&y(!0)},[_]),!h||!(v||_))return!t||!n?(0,p.jsx)(i,{children:g===null?`正在连接…`:`还没发布。电脑在线时,这里会实时显示。`}):(0,p.jsx)(a,{id:e,state:t,contentBase:n,comments:m,offline:g!==null&&!_});let b=_&&d(l,g?.version??null),x=_&&!b?f(l,g?.version??null):null;return(0,p.jsx)(o,{id:e,transport:h,live:_,cloud:{online:_,canEdit:b,reason:x}})}export{m as default};
|