@neta-art/cohub-cli 10.10.0 → 10.11.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.
|
@@ -3,5 +3,4 @@ export declare const BOARD_CREATE_INPUT_MAX_BYTES: number;
|
|
|
3
3
|
export declare const BOARD_DOMAIN_INPUT_MAX_BYTES: number;
|
|
4
4
|
export declare function parseBoardJsonObject(text: string, source?: string): Record<string, unknown>;
|
|
5
5
|
export declare function readBoardJsonObject(source: string, maxBytes?: number): Promise<Record<string, unknown>>;
|
|
6
|
-
export declare function resolveBoardId(spaceId: string, target: string): Promise<string>;
|
|
7
6
|
export declare function writeBoardOutput(path: string, bytes: Uint8Array, force?: boolean): Promise<void>;
|
|
@@ -2,8 +2,6 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import { access, link, open, rename, unlink } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, join } from "node:path";
|
|
5
|
-
import { isBoardPath, parseBoardManifest, } from "@neta-art/cohub/board";
|
|
6
|
-
import { createClient } from "./client.js";
|
|
7
5
|
export const BOARD_TRANSACTION_INPUT_MAX_BYTES = 16 * 1024 * 1024;
|
|
8
6
|
export const BOARD_CREATE_INPUT_MAX_BYTES = 32 * 1024 * 1024;
|
|
9
7
|
export const BOARD_DOMAIN_INPUT_MAX_BYTES = 1024 * 1024;
|
|
@@ -62,15 +60,6 @@ export async function readBoardJsonObject(source, maxBytes = BOARD_DOMAIN_INPUT_
|
|
|
62
60
|
await handle.close();
|
|
63
61
|
}
|
|
64
62
|
}
|
|
65
|
-
export async function resolveBoardId(spaceId, target) {
|
|
66
|
-
if (!isBoardPath(target))
|
|
67
|
-
return target;
|
|
68
|
-
const file = await createClient().space(spaceId).files.read(target);
|
|
69
|
-
if (!("content" in file) || typeof file.content !== "string") {
|
|
70
|
-
throw new Error(`${target} is not a readable Board manifest`);
|
|
71
|
-
}
|
|
72
|
-
return parseBoardManifest(file.content).boardId;
|
|
73
|
-
}
|
|
74
63
|
export async function writeBoardOutput(path, bytes, force = false) {
|
|
75
64
|
if (!force) {
|
|
76
65
|
await access(path).then(() => { throw new Error(`Output already exists: ${path}; use --force to replace it`); }, () => undefined);
|
package/dist/board-export.js
CHANGED
|
@@ -12,7 +12,6 @@ const execFile = promisify(execFileCallback);
|
|
|
12
12
|
import { parseBoardMediaSource } from "@neta-art/cohub/board";
|
|
13
13
|
import { imageVariantUrl, MEDIA_VARIANT_MAX, mediaVariantSize, videoFrameUrl } from "@neta-art/cohub/media";
|
|
14
14
|
import { hasBoardScripts, boardAssetKey, boardAssetKeySource, boardDocumentAt, boardRenderTheme, boardTextureEdge, boardTexturePixels, createBoardRenderSampler, buildBoardScene, createNodeBoardRenderer, createNodeBoardScriptHost, exportBoardImageBytes, parseBoardDocument, planBoardExport, prepareBoardExport, selectBoardExportAssets, } from "./board-kernel.js";
|
|
15
|
-
import { resolveBoardId } from "./board-command-support.js";
|
|
16
15
|
import { settleBoardDocument } from "./board-media.js";
|
|
17
16
|
import { BOARD_EXPORT_FONTS } from "./board-fonts.js";
|
|
18
17
|
import { createClient } from "./client.js";
|
|
@@ -40,7 +39,7 @@ export function resolveBundledFonts() {
|
|
|
40
39
|
});
|
|
41
40
|
}
|
|
42
41
|
export async function loadBoardDocument(spaceId, target) {
|
|
43
|
-
const board = createClient().space(spaceId).
|
|
42
|
+
const board = await createClient().space(spaceId).boards.open(target);
|
|
44
43
|
let result = await board.get();
|
|
45
44
|
const snapshot = result;
|
|
46
45
|
const items = { ...result.items };
|
package/dist/board-kernel.js
CHANGED
|
@@ -6127,10 +6127,17 @@ const BoardCustomItemSchema$1 = object({
|
|
|
6127
6127
|
props: object({}).strict().default({})
|
|
6128
6128
|
}).strict();
|
|
6129
6129
|
const BOARD_EXTENSION_TYPE_PATTERN$1 = /^[a-z][a-z0-9]*(?:\.[a-z0-9-]+)+$/;
|
|
6130
|
+
const BoardItemPreviewSchema$1 = object({
|
|
6131
|
+
title: string().max(500).optional(),
|
|
6132
|
+
excerpt: string().max(2e3).optional(),
|
|
6133
|
+
cover: BoardMediaSourceSchema$1.optional().describe("An image for the card."),
|
|
6134
|
+
src: BoardMediaSourceSchema$1.optional().describe("The file or page the card stands for.")
|
|
6135
|
+
}).strict().describe("How a client without this type's renderer shows the card, and what it refers to. The type's own client keeps it current.");
|
|
6130
6136
|
const BoardExtensionItemSchema$1 = object({
|
|
6131
6137
|
...boxed$1(BOARD_DEFAULT_ITEM_SIZE$1),
|
|
6132
6138
|
type: string().regex(BOARD_EXTENSION_TYPE_PATTERN$1),
|
|
6133
|
-
props: record(string(), unknown()).default({})
|
|
6139
|
+
props: record(string(), unknown()).default({}),
|
|
6140
|
+
preview: BoardItemPreviewSchema$1.optional()
|
|
6134
6141
|
}).strict();
|
|
6135
6142
|
const BOARD_ITEM_SCHEMAS$1 = {
|
|
6136
6143
|
frame: BoardFrameItemSchema$1,
|
|
@@ -6150,6 +6157,12 @@ const BOARD_ITEM_TYPES$1 = Object.keys(BOARD_ITEM_SCHEMAS$1);
|
|
|
6150
6157
|
function isBuiltinItemType(type) {
|
|
6151
6158
|
return Object.hasOwn(BOARD_ITEM_SCHEMAS$1, type);
|
|
6152
6159
|
}
|
|
6160
|
+
function isBuiltinItem(item) {
|
|
6161
|
+
return isBuiltinItemType(item.type);
|
|
6162
|
+
}
|
|
6163
|
+
function boardItemPreview(item) {
|
|
6164
|
+
return isBuiltinItem(item) ? null : item.preview ?? null;
|
|
6165
|
+
}
|
|
6153
6166
|
function boardItemSchema(type) {
|
|
6154
6167
|
if (isBuiltinItemType(type)) return BOARD_ITEM_SCHEMAS$1[type];
|
|
6155
6168
|
return BOARD_EXTENSION_TYPE_PATTERN$1.test(type) ? BoardExtensionItemSchema$1 : null;
|
|
@@ -7687,6 +7700,16 @@ function unionRects(rects) {
|
|
|
7687
7700
|
height: maxY - minY
|
|
7688
7701
|
};
|
|
7689
7702
|
}
|
|
7703
|
+
function rotatePointAround(point, center, angleRad) {
|
|
7704
|
+
const cos = Math.cos(angleRad);
|
|
7705
|
+
const sin = Math.sin(angleRad);
|
|
7706
|
+
const dx = point.x - center.x;
|
|
7707
|
+
const dy = point.y - center.y;
|
|
7708
|
+
return {
|
|
7709
|
+
x: center.x + dx * cos - dy * sin,
|
|
7710
|
+
y: center.y + dx * sin + dy * cos
|
|
7711
|
+
};
|
|
7712
|
+
}
|
|
7690
7713
|
function itemBounds(frame) {
|
|
7691
7714
|
const rotation = frame.rotation || 0;
|
|
7692
7715
|
if (rotation === 0) return frameRect(frame);
|
|
@@ -9287,8 +9310,12 @@ const MEDIA_HOSTS = /* @__PURE__ */ new Map([
|
|
|
9287
9310
|
transform: "oss",
|
|
9288
9311
|
metaHeaders: true
|
|
9289
9312
|
}],
|
|
9313
|
+
["oss.talesofai.com", { transform: "oss" }],
|
|
9290
9314
|
["public.cohub.live", { transform: "oss" }],
|
|
9315
|
+
["public.cohub.run", { transform: "oss" }],
|
|
9291
9316
|
["sessions.cohub.live", { transform: "oss" }],
|
|
9317
|
+
["works.cohub.live", { transform: "oss" }],
|
|
9318
|
+
["works.cohub.run", { transform: "oss" }],
|
|
9292
9319
|
["cca.neta.art", { transform: "cloudflare" }]
|
|
9293
9320
|
]);
|
|
9294
9321
|
const parseHttpsUrl = (url) => {
|
|
@@ -9389,7 +9416,9 @@ function imageAssetKey(item) {
|
|
|
9389
9416
|
const url = taskArtifactPreviewUrl(featuredTaskArtifact(item.props.snapshot.artifacts));
|
|
9390
9417
|
return url ? `url:${url}` : null;
|
|
9391
9418
|
}
|
|
9392
|
-
|
|
9419
|
+
const cover = boardItemPreview(item)?.cover;
|
|
9420
|
+
const source = cover ? parseBoardMediaSource$1(cover) : null;
|
|
9421
|
+
return source ? `${source.kind}:${source.value}` : null;
|
|
9393
9422
|
}
|
|
9394
9423
|
const STILL_MEDIA = ["video", "model"];
|
|
9395
9424
|
function stillKey(media, src, mtimeMs) {
|
|
@@ -9447,6 +9476,12 @@ function boardAssetKeySource(key) {
|
|
|
9447
9476
|
media: "image",
|
|
9448
9477
|
value: base.slice(4)
|
|
9449
9478
|
};
|
|
9479
|
+
if (base.startsWith("cover-url:")) return {
|
|
9480
|
+
kind: "url",
|
|
9481
|
+
media: "image",
|
|
9482
|
+
value: base.slice(10),
|
|
9483
|
+
fit: "cover"
|
|
9484
|
+
};
|
|
9450
9485
|
return null;
|
|
9451
9486
|
}
|
|
9452
9487
|
function boardTexturePixels(item, scale) {
|
|
@@ -10074,7 +10109,7 @@ const BOARD_THEME_DATA = {
|
|
|
10074
10109
|
surface: 2039072,
|
|
10075
10110
|
hover: 1842202,
|
|
10076
10111
|
border: 1842202,
|
|
10077
|
-
brand:
|
|
10112
|
+
brand: 3900150,
|
|
10078
10113
|
text: 15920612,
|
|
10079
10114
|
muted: 9999759,
|
|
10080
10115
|
rare: 9235170,
|
|
@@ -10128,7 +10163,10 @@ const BOARD_THEME_DATA = {
|
|
|
10128
10163
|
label: 15920612
|
|
10129
10164
|
}
|
|
10130
10165
|
},
|
|
10131
|
-
style: {
|
|
10166
|
+
style: {
|
|
10167
|
+
selection: { outset: 0 },
|
|
10168
|
+
frameLabelSize: 16
|
|
10169
|
+
}
|
|
10132
10170
|
},
|
|
10133
10171
|
"neta-pixel": {
|
|
10134
10172
|
colorScheme: "light",
|
|
@@ -10200,9 +10238,9 @@ const BOARD_THEME_DATA = {
|
|
|
10200
10238
|
y: 0
|
|
10201
10239
|
},
|
|
10202
10240
|
selection: {
|
|
10203
|
-
width:
|
|
10204
|
-
outset:
|
|
10205
|
-
dash:
|
|
10241
|
+
width: 1.5,
|
|
10242
|
+
outset: 0,
|
|
10243
|
+
dash: 0,
|
|
10206
10244
|
handle: "square",
|
|
10207
10245
|
handleSize: 8
|
|
10208
10246
|
},
|
|
@@ -10210,7 +10248,8 @@ const BOARD_THEME_DATA = {
|
|
|
10210
10248
|
progress: "mosaic",
|
|
10211
10249
|
icons: "pixel",
|
|
10212
10250
|
motion: "stepped",
|
|
10213
|
-
font: "\"Figtree Variable\", \"PingFang SC\", \"Noto Sans CJK SC\", \"Microsoft YaHei\", system-ui, sans-serif"
|
|
10251
|
+
font: "\"Figtree Variable\", \"PingFang SC\", \"Noto Sans CJK SC\", \"Microsoft YaHei\", system-ui, sans-serif",
|
|
10252
|
+
frameLabelSize: 16
|
|
10214
10253
|
}
|
|
10215
10254
|
},
|
|
10216
10255
|
"neta-pixel-dark": {
|
|
@@ -10283,9 +10322,9 @@ const BOARD_THEME_DATA = {
|
|
|
10283
10322
|
y: 0
|
|
10284
10323
|
},
|
|
10285
10324
|
selection: {
|
|
10286
|
-
width:
|
|
10287
|
-
outset:
|
|
10288
|
-
dash:
|
|
10325
|
+
width: 1.5,
|
|
10326
|
+
outset: 0,
|
|
10327
|
+
dash: 0,
|
|
10289
10328
|
handle: "square",
|
|
10290
10329
|
handleSize: 8
|
|
10291
10330
|
},
|
|
@@ -10293,7 +10332,8 @@ const BOARD_THEME_DATA = {
|
|
|
10293
10332
|
progress: "mosaic",
|
|
10294
10333
|
icons: "pixel",
|
|
10295
10334
|
motion: "stepped",
|
|
10296
|
-
font: "\"Figtree Variable\", \"PingFang SC\", \"Noto Sans CJK SC\", \"Microsoft YaHei\", system-ui, sans-serif"
|
|
10335
|
+
font: "\"Figtree Variable\", \"PingFang SC\", \"Noto Sans CJK SC\", \"Microsoft YaHei\", system-ui, sans-serif",
|
|
10336
|
+
frameLabelSize: 16
|
|
10297
10337
|
}
|
|
10298
10338
|
}
|
|
10299
10339
|
};
|
|
@@ -10358,6 +10398,13 @@ function layoutShapeLabel(props, width) {
|
|
|
10358
10398
|
width: Math.max(1, width - 16)
|
|
10359
10399
|
});
|
|
10360
10400
|
}
|
|
10401
|
+
const BOARD_FRAME_LABEL_LINE_HEIGHT = 1.25;
|
|
10402
|
+
function frameLabelOffset(size) {
|
|
10403
|
+
return {
|
|
10404
|
+
x: 4,
|
|
10405
|
+
y: -(size + 6)
|
|
10406
|
+
};
|
|
10407
|
+
}
|
|
10361
10408
|
//#endregion
|
|
10362
10409
|
//#region ../../node_modules/.pnpm/pixi.js@8.20.1/node_modules/pixi.js/lib/environment-browser/browserExt.mjs
|
|
10363
10410
|
const browserExt = {
|
|
@@ -32769,8 +32816,8 @@ const measuredFonts = /* @__PURE__ */ new Set();
|
|
|
32769
32816
|
const measuredFamilies = /* @__PURE__ */ new Set();
|
|
32770
32817
|
const TRAILING_SPACE = /[ \t]+$/u;
|
|
32771
32818
|
const unquote = (name) => name.trim().replace(/^["']|["']$/gu, "");
|
|
32772
|
-
function prepareText(text, font) {
|
|
32773
|
-
const key = `${font}\n${text}`;
|
|
32819
|
+
function prepareText(text, font, whiteSpace) {
|
|
32820
|
+
const key = `${whiteSpace}\n${font}\n${text}`;
|
|
32774
32821
|
let value = prepared.get(key);
|
|
32775
32822
|
if (value) {
|
|
32776
32823
|
prepared.delete(key);
|
|
@@ -32781,30 +32828,75 @@ function prepareText(text, font) {
|
|
|
32781
32828
|
measuredFonts.add(font);
|
|
32782
32829
|
for (const name of /px\s+(.+)$/u.exec(font)?.[1]?.split(",") ?? []) measuredFamilies.add(unquote(name));
|
|
32783
32830
|
}
|
|
32784
|
-
value = prepareWithSegments(text, font, { whiteSpace
|
|
32831
|
+
value = prepareWithSegments(text, font, { whiteSpace });
|
|
32785
32832
|
if (prepared.size >= PREPARED_LIMIT) prepared.delete(prepared.keys().next().value);
|
|
32786
32833
|
prepared.set(key, value);
|
|
32787
32834
|
return value;
|
|
32788
32835
|
}
|
|
32789
|
-
|
|
32836
|
+
let measureContext;
|
|
32837
|
+
function measureTextWidth(text, font) {
|
|
32838
|
+
measureContext ??= DOMAdapter.get().createCanvas(1, 1).getContext("2d");
|
|
32839
|
+
if (!measureContext) return 0;
|
|
32840
|
+
measureContext.font = font;
|
|
32841
|
+
return measureContext.measureText(text).width;
|
|
32842
|
+
}
|
|
32843
|
+
function layoutTextLines(text, font, maxWidth = Number.POSITIVE_INFINITY, { whiteSpace = "pre-wrap", maxLines = Number.POSITIVE_INFINITY } = {}) {
|
|
32790
32844
|
const lines = [];
|
|
32791
|
-
|
|
32792
|
-
|
|
32845
|
+
const widths = [];
|
|
32846
|
+
if (maxLines <= 0) return {
|
|
32847
|
+
lines,
|
|
32848
|
+
width: 0,
|
|
32849
|
+
truncated: text.length > 0
|
|
32850
|
+
};
|
|
32851
|
+
let source = whiteSpace === "normal" ? text.replace(/\s+/gu, " ").trim() : text.replace(/\r\n?/gu, "\n").replaceAll(" ", " ");
|
|
32852
|
+
const cap = Number.isFinite(maxLines) && Number.isFinite(maxWidth) ? maxLines * (Math.ceil(maxWidth) + 1) : Number.POSITIVE_INFINITY;
|
|
32853
|
+
let truncated = source.length > cap;
|
|
32854
|
+
if (truncated) source = source.slice(0, cap);
|
|
32855
|
+
const paragraphs = whiteSpace === "normal" ? source ? [source] : [] : source.split("\n");
|
|
32856
|
+
for (const paragraph of paragraphs) {
|
|
32857
|
+
if (lines.length > maxLines) break;
|
|
32793
32858
|
if (!paragraph) {
|
|
32794
32859
|
lines.push("");
|
|
32860
|
+
widths.push(0);
|
|
32795
32861
|
continue;
|
|
32796
32862
|
}
|
|
32797
|
-
const wrapped = layoutWithLines(prepareText(paragraph, font), maxWidth, 1).lines;
|
|
32863
|
+
const wrapped = layoutWithLines(prepareText(paragraph, font, whiteSpace), maxWidth, 1).lines;
|
|
32798
32864
|
for (const [index, line] of wrapped.entries()) {
|
|
32799
32865
|
lines.push(index === wrapped.length - 1 ? line.text : line.text.replace(TRAILING_SPACE, ""));
|
|
32800
|
-
|
|
32866
|
+
widths.push(line.width);
|
|
32801
32867
|
}
|
|
32802
32868
|
}
|
|
32869
|
+
if (lines.length > maxLines) {
|
|
32870
|
+
truncated = true;
|
|
32871
|
+
lines.length = maxLines;
|
|
32872
|
+
widths.length = maxLines;
|
|
32873
|
+
}
|
|
32874
|
+
if (truncated && lines.length > 0) {
|
|
32875
|
+
const last = lines.length - 1;
|
|
32876
|
+
lines[last] = fitLineWithEllipsis(lines[last], maxWidth, (value) => measureTextWidth(value, font));
|
|
32877
|
+
widths[last] = measureTextWidth(lines[last], font);
|
|
32878
|
+
}
|
|
32803
32879
|
return {
|
|
32804
32880
|
lines,
|
|
32805
|
-
width
|
|
32881
|
+
width: widths.reduce((max, width) => Math.max(max, width), 0),
|
|
32882
|
+
truncated
|
|
32806
32883
|
};
|
|
32807
32884
|
}
|
|
32885
|
+
const graphemes = typeof Intl.Segmenter === "function" ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : null;
|
|
32886
|
+
function fitLineWithEllipsis(line, width, measureWidth) {
|
|
32887
|
+
const trimmed = line.replace(/\s+$/u, "");
|
|
32888
|
+
if (!trimmed) return "…";
|
|
32889
|
+
if (measureWidth(`${trimmed}…`) <= width) return `${trimmed}…`;
|
|
32890
|
+
const parts = graphemes ? Array.from(graphemes.segment(trimmed), ({ segment }) => segment) : Array.from(trimmed);
|
|
32891
|
+
let lo = 0;
|
|
32892
|
+
let hi = parts.length - 1;
|
|
32893
|
+
while (lo < hi) {
|
|
32894
|
+
const mid = lo + hi + 1 >> 1;
|
|
32895
|
+
if (measureWidth(`${parts.slice(0, mid).join("")}…`) <= width) lo = mid;
|
|
32896
|
+
else hi = mid - 1;
|
|
32897
|
+
}
|
|
32898
|
+
return `${parts.slice(0, lo).join("")}…`;
|
|
32899
|
+
}
|
|
32808
32900
|
const baselineShifts = /* @__PURE__ */ new Map();
|
|
32809
32901
|
function cssBaselineShift(style) {
|
|
32810
32902
|
const font = fontStringFromTextStyle(style);
|
|
@@ -32996,40 +33088,10 @@ function boardTextSize(item) {
|
|
|
32996
33088
|
}
|
|
32997
33089
|
//#endregion
|
|
32998
33090
|
//#region ../sdk/src/board/render/text-fit.ts
|
|
32999
|
-
function fitLineWithEllipsis(line, wrapWidth, measureWidth) {
|
|
33000
|
-
const trimmed = line.replace(/\s+$/u, "");
|
|
33001
|
-
if (!trimmed) return "…";
|
|
33002
|
-
const full = `${trimmed}…`;
|
|
33003
|
-
if (measureWidth(full) <= wrapWidth) return full;
|
|
33004
|
-
let lo = 0;
|
|
33005
|
-
let hi = trimmed.length;
|
|
33006
|
-
let best = "…";
|
|
33007
|
-
while (lo <= hi) {
|
|
33008
|
-
const mid = lo + hi >> 1;
|
|
33009
|
-
const candidate = mid > 0 ? `${trimmed.slice(0, mid)}…` : "…";
|
|
33010
|
-
if (measureWidth(candidate) <= wrapWidth) {
|
|
33011
|
-
best = candidate;
|
|
33012
|
-
lo = mid + 1;
|
|
33013
|
-
} else hi = mid - 1;
|
|
33014
|
-
}
|
|
33015
|
-
return best;
|
|
33016
|
-
}
|
|
33017
33091
|
function fitTextToLines(text, value, maxLines, wrapWidth) {
|
|
33018
|
-
const width = Math.max(1, wrapWidth);
|
|
33019
33092
|
text.style.wordWrap = false;
|
|
33020
|
-
|
|
33021
|
-
|
|
33022
|
-
return;
|
|
33023
|
-
}
|
|
33024
|
-
const charCap = Math.max(maxLines * Math.ceil(width), maxLines * 4);
|
|
33025
|
-
const sample = value.length > charCap ? value.slice(0, charCap) : value;
|
|
33026
|
-
const { lines } = layoutTextLines(sample, fontStringFromTextStyle(text.style), width);
|
|
33027
|
-
const kept = lines.slice(0, maxLines);
|
|
33028
|
-
if (lines.length > maxLines || sample !== value) {
|
|
33029
|
-
const lastIndex = kept.length - 1;
|
|
33030
|
-
kept[lastIndex] = fitLineWithEllipsis(kept[lastIndex] ?? "", width, (candidate) => CanvasTextMetrics.measureText(candidate, text.style).width);
|
|
33031
|
-
}
|
|
33032
|
-
const fitted = kept.join("\n");
|
|
33093
|
+
const { lines } = layoutTextLines(value, fontStringFromTextStyle(text.style), Math.max(1, wrapWidth), { maxLines });
|
|
33094
|
+
const fitted = lines.join("\n");
|
|
33033
33095
|
if (text.text !== fitted) text.text = fitted;
|
|
33034
33096
|
}
|
|
33035
33097
|
function fitTextToWidth(text, value, width) {
|
|
@@ -33644,6 +33706,10 @@ function traceLocal(graphics, frame, rect) {
|
|
|
33644
33706
|
const d = corner(x, y + height);
|
|
33645
33707
|
graphics.moveTo(a.x, a.y).lineTo(b.x, b.y).lineTo(c.x, c.y).lineTo(d.x, d.y).closePath();
|
|
33646
33708
|
}
|
|
33709
|
+
function farPictureTone(item, context) {
|
|
33710
|
+
const key = context.assetKey(item);
|
|
33711
|
+
return (key ? context.assetTone?.(key) : null) ?? void 0;
|
|
33712
|
+
}
|
|
33647
33713
|
function drawFarPlate(graphics, frame, style) {
|
|
33648
33714
|
if (frame.width <= 0 || frame.height <= 0) return;
|
|
33649
33715
|
traceLocal(graphics, frame, {
|
|
@@ -33983,7 +34049,7 @@ const modelCardRenderer = {
|
|
|
33983
34049
|
},
|
|
33984
34050
|
renderFar: (graphics, item, context) => {
|
|
33985
34051
|
drawFarPlate(graphics, item.frame, {
|
|
33986
|
-
fill: context.palette.surface,
|
|
34052
|
+
fill: farPictureTone(item, context) ?? context.palette.surface,
|
|
33987
34053
|
fillAlpha: .96,
|
|
33988
34054
|
accent: context.palette.legendary,
|
|
33989
34055
|
accentAlpha: .45
|
|
@@ -33995,6 +34061,64 @@ const modelCardRenderer = {
|
|
|
33995
34061
|
}
|
|
33996
34062
|
};
|
|
33997
34063
|
//#endregion
|
|
34064
|
+
//#region ../sdk/src/board/render/media-interaction.ts
|
|
34065
|
+
const BOARD_PLAY_MEDIA = "play-media";
|
|
34066
|
+
function mediaPlayBadgeVisible(item, zoom, options) {
|
|
34067
|
+
if (!options.materialized) return false;
|
|
34068
|
+
if (item.type === "video" || item.type === "audio") return true;
|
|
34069
|
+
if (isBoardModelItem(item)) return boardCardDetailed(zoom);
|
|
34070
|
+
if (item.type !== "task" || !boardCardDetailed(zoom)) return false;
|
|
34071
|
+
const artifact = featuredTaskArtifact(item.props.snapshot.artifacts);
|
|
34072
|
+
if (artifact?.type === "audio") return true;
|
|
34073
|
+
return artifact?.type === "video" && (Boolean(artifact.previewUrl) || options.hasVideoPreview);
|
|
34074
|
+
}
|
|
34075
|
+
function cornerKey(item) {
|
|
34076
|
+
const { width, height } = item.frame;
|
|
34077
|
+
if (item.type === "audio") return audioKeyCenter(width, height);
|
|
34078
|
+
return isBoardModelItem(item) ? modelKeyCenter(width, height) : null;
|
|
34079
|
+
}
|
|
34080
|
+
function mediaPlayKeyCenter(item) {
|
|
34081
|
+
const { x, y, width, height, rotation } = item.frame;
|
|
34082
|
+
const center = worldPoint(x + width / 2, y + height / 2);
|
|
34083
|
+
const key = cornerKey(item);
|
|
34084
|
+
if (!key) return center;
|
|
34085
|
+
return rotatePointAround(worldPoint(x + key.x, y + key.y), center, degToRad(rotation || 0));
|
|
34086
|
+
}
|
|
34087
|
+
function mediaPlayBadgeHit(item, point, zoom) {
|
|
34088
|
+
const scale = Math.max(zoom, .05);
|
|
34089
|
+
const radius = isBoardModelItem(item) ? Math.max(cornerKey(item)?.radius ?? 0, 16 / scale) : 28 / scale;
|
|
34090
|
+
const center = mediaPlayKeyCenter(item);
|
|
34091
|
+
return Math.hypot(point.x - center.x, point.y - center.y) <= radius;
|
|
34092
|
+
}
|
|
34093
|
+
function boardMediaActionAt(item, point, zoom, options) {
|
|
34094
|
+
return mediaPlayBadgeVisible(item, zoom, options) && mediaPlayBadgeHit(item, point, zoom) ? {
|
|
34095
|
+
action: BOARD_PLAY_MEDIA,
|
|
34096
|
+
itemId: item.id
|
|
34097
|
+
} : null;
|
|
34098
|
+
}
|
|
34099
|
+
const PLAY_HIT = {
|
|
34100
|
+
action: BOARD_PLAY_MEDIA,
|
|
34101
|
+
cursor: "pointer"
|
|
34102
|
+
};
|
|
34103
|
+
function mediaPlayHit(item, point, context) {
|
|
34104
|
+
const key = context.assetKey(item);
|
|
34105
|
+
return boardMediaActionAt(item, point, context.zoom, {
|
|
34106
|
+
materialized: true,
|
|
34107
|
+
hasVideoPreview: Boolean(key && context.getTexture(key))
|
|
34108
|
+
}) ? PLAY_HIT : null;
|
|
34109
|
+
}
|
|
34110
|
+
//#endregion
|
|
34111
|
+
//#region ../sdk/src/board/render/labels.ts
|
|
34112
|
+
const ARROW_LABEL = {
|
|
34113
|
+
fontWeight: "500",
|
|
34114
|
+
lineHeight: 1.25,
|
|
34115
|
+
padX: .5,
|
|
34116
|
+
padY: .25
|
|
34117
|
+
};
|
|
34118
|
+
function arrowLabelAnchor(arrow, style) {
|
|
34119
|
+
return style.trim === void 0 ? arrow.mid : pathPointAt(arrow.path, .5);
|
|
34120
|
+
}
|
|
34121
|
+
//#endregion
|
|
33998
34122
|
//#region ../sdk/src/board/render/renderers/arrow-card-renderer.ts
|
|
33999
34123
|
const partsByContainer$11 = /* @__PURE__ */ new WeakMap();
|
|
34000
34124
|
function sync$11(container, item, context) {
|
|
@@ -34056,8 +34180,9 @@ function sync$11(container, item, context) {
|
|
|
34056
34180
|
parts.label.text = props.label;
|
|
34057
34181
|
parts.label.style.fill = context.palette.text;
|
|
34058
34182
|
parts.label.style.fontSize = props.fontSize;
|
|
34059
|
-
|
|
34060
|
-
const
|
|
34183
|
+
parts.label.style.lineHeight = props.fontSize * ARROW_LABEL.lineHeight;
|
|
34184
|
+
const padX = props.fontSize * ARROW_LABEL.padX;
|
|
34185
|
+
const padY = props.fontSize * ARROW_LABEL.padY;
|
|
34061
34186
|
const { width, height } = parts.label;
|
|
34062
34187
|
const plate = {
|
|
34063
34188
|
x: -width / 2 - padX,
|
|
@@ -34077,7 +34202,7 @@ function sync$11(container, item, context) {
|
|
|
34077
34202
|
const visible = props.label.length > 0 && (style.trim === void 0 || style.trim >= .5);
|
|
34078
34203
|
parts.label.visible = visible;
|
|
34079
34204
|
parts.backdrop.visible = visible;
|
|
34080
|
-
const mid =
|
|
34205
|
+
const mid = arrowLabelAnchor(resolved, style);
|
|
34081
34206
|
parts.label.position.set(mid.x, mid.y);
|
|
34082
34207
|
parts.backdrop.position.set(mid.x, mid.y);
|
|
34083
34208
|
}
|
|
@@ -34093,7 +34218,7 @@ const arrowCardRenderer = {
|
|
|
34093
34218
|
fill: 16777215,
|
|
34094
34219
|
fontFamily: BOARD_FONT_STACK,
|
|
34095
34220
|
fontSize: 14,
|
|
34096
|
-
fontWeight:
|
|
34221
|
+
fontWeight: ARROW_LABEL.fontWeight
|
|
34097
34222
|
},
|
|
34098
34223
|
resolution: context.textResolution,
|
|
34099
34224
|
roundPixels: true
|
|
@@ -34481,8 +34606,34 @@ const partsByContainer$8 = /* @__PURE__ */ new WeakMap();
|
|
|
34481
34606
|
function detailFor(zoom) {
|
|
34482
34607
|
return boardCardDetailed(zoom) ? "full" : "title";
|
|
34483
34608
|
}
|
|
34484
|
-
function
|
|
34485
|
-
if (
|
|
34609
|
+
function viewOf(item) {
|
|
34610
|
+
if (item.type === "file") {
|
|
34611
|
+
const { src, snapshot } = item.props;
|
|
34612
|
+
return {
|
|
34613
|
+
src,
|
|
34614
|
+
title: snapshot?.title || fileStem(src),
|
|
34615
|
+
excerpt: snapshot?.excerpt ?? "",
|
|
34616
|
+
kind: filePreviewKind(snapshot),
|
|
34617
|
+
meta: fileMetaLine(src, snapshot?.size),
|
|
34618
|
+
mark: fileTypeLabel(src),
|
|
34619
|
+
category: fileCategory(src, snapshot?.mimeType)
|
|
34620
|
+
};
|
|
34621
|
+
}
|
|
34622
|
+
const preview = boardItemPreview(item);
|
|
34623
|
+
if (!preview) return null;
|
|
34624
|
+
const label = (item.type.split(".").pop() ?? item.type).toUpperCase();
|
|
34625
|
+
return {
|
|
34626
|
+
src: preview.src ?? null,
|
|
34627
|
+
title: preview.title || item.type,
|
|
34628
|
+
excerpt: preview.excerpt ?? "",
|
|
34629
|
+
kind: preview.cover ? "cover" : preview.excerpt ? "text" : "blank",
|
|
34630
|
+
meta: label,
|
|
34631
|
+
mark: label,
|
|
34632
|
+
category: preview.src ? fileCategory(preview.src) : "other"
|
|
34633
|
+
};
|
|
34634
|
+
}
|
|
34635
|
+
function coverHeight(view, height) {
|
|
34636
|
+
if (view.kind !== "cover") return 0;
|
|
34486
34637
|
const ideal = Math.round(height * COVER_RATIO);
|
|
34487
34638
|
return Math.max(0, Math.min(ideal, height - MIN_BODY_FOR_COVER));
|
|
34488
34639
|
}
|
|
@@ -34501,31 +34652,31 @@ function syncClip(parts, rect, style) {
|
|
|
34501
34652
|
}
|
|
34502
34653
|
function sync$8(container, item, context) {
|
|
34503
34654
|
const parts = partsByContainer$8.get(container);
|
|
34504
|
-
|
|
34505
|
-
|
|
34655
|
+
const view = viewOf(item);
|
|
34656
|
+
if (!parts || !view) return;
|
|
34657
|
+
syncContent(parts, item, view, context);
|
|
34506
34658
|
for (const text of [
|
|
34507
34659
|
parts.title,
|
|
34508
34660
|
parts.excerpt,
|
|
34509
34661
|
parts.typeMark
|
|
34510
34662
|
]) syncTextResolution(text, context.textResolution);
|
|
34511
34663
|
}
|
|
34512
|
-
function syncContent(parts, item, context) {
|
|
34664
|
+
function syncContent(parts, item, view, context) {
|
|
34513
34665
|
positionShell(parts.root, item);
|
|
34514
34666
|
const { width, height } = item.frame;
|
|
34515
34667
|
const detail = detailFor(context.zoom);
|
|
34516
34668
|
const elevation = cardElevation(context, item.id);
|
|
34517
34669
|
syncCardTag(parts.tag, {
|
|
34518
34670
|
glyph: "file",
|
|
34519
|
-
text:
|
|
34671
|
+
text: view.meta,
|
|
34520
34672
|
width
|
|
34521
34673
|
}, context);
|
|
34522
34674
|
const key = context.assetKey(item);
|
|
34523
34675
|
const texture = key ? context.getTexture(key) : null;
|
|
34524
34676
|
const coverFailed = Boolean(key && !texture && context.hasError(key));
|
|
34525
|
-
const fileState = context.fileState(
|
|
34526
|
-
const band = coverHeight(
|
|
34527
|
-
const kind =
|
|
34528
|
-
const category = fileCategory(item.props.src, item.props.snapshot?.mimeType);
|
|
34677
|
+
const fileState = view.src ? context.fileState(view.src) : "ok";
|
|
34678
|
+
const band = coverHeight(view, height);
|
|
34679
|
+
const { kind, category, title, excerpt, mark } = view;
|
|
34529
34680
|
const accent = fileCategoryAccent(category, context.palette);
|
|
34530
34681
|
const visualSig = [
|
|
34531
34682
|
key ?? "",
|
|
@@ -34584,9 +34735,6 @@ function syncContent(parts, item, context) {
|
|
|
34584
34735
|
parts.excerpt.visible = detail === "full";
|
|
34585
34736
|
parts.typeMark.visible = kind === "blank";
|
|
34586
34737
|
}
|
|
34587
|
-
const title = item.props.snapshot?.title || fileStem(item.props.src);
|
|
34588
|
-
const excerpt = item.props.snapshot?.excerpt ?? "";
|
|
34589
|
-
const mark = fileTypeLabel(item.props.src);
|
|
34590
34738
|
const innerWidth = Math.max(1, width - 20);
|
|
34591
34739
|
const showTypeMark = kind === "blank";
|
|
34592
34740
|
const textSig = [
|
|
@@ -34642,7 +34790,7 @@ function syncContent(parts, item, context) {
|
|
|
34642
34790
|
}
|
|
34643
34791
|
const fileCardRenderer = {
|
|
34644
34792
|
id: "file-card",
|
|
34645
|
-
canRender: (item) => item.type === "file",
|
|
34793
|
+
canRender: (item) => item.type === "file" || boardItemPreview(item) !== null,
|
|
34646
34794
|
create: (item, context) => {
|
|
34647
34795
|
const root = new Container();
|
|
34648
34796
|
const plate = new Graphics();
|
|
@@ -34706,23 +34854,19 @@ const fileCardRenderer = {
|
|
|
34706
34854
|
visualSig: "",
|
|
34707
34855
|
textSig: ""
|
|
34708
34856
|
});
|
|
34709
|
-
|
|
34857
|
+
sync$8(root, item, context);
|
|
34710
34858
|
return root;
|
|
34711
34859
|
},
|
|
34712
|
-
update:
|
|
34713
|
-
|
|
34714
|
-
},
|
|
34715
|
-
resolution: (container, item, context) => {
|
|
34716
|
-
if (item.type === "file") sync$8(container, item, context);
|
|
34717
|
-
},
|
|
34860
|
+
update: sync$8,
|
|
34861
|
+
resolution: sync$8,
|
|
34718
34862
|
zoom: (container, zoom) => {
|
|
34719
34863
|
const tag = partsByContainer$8.get(container)?.tag;
|
|
34720
34864
|
if (tag) zoomCardTag(tag, zoom);
|
|
34721
34865
|
},
|
|
34722
34866
|
renderFar: (graphics, item, context) => {
|
|
34723
|
-
const category =
|
|
34867
|
+
const category = viewOf(item)?.category ?? "other";
|
|
34724
34868
|
drawFarPlate(graphics, item.frame, {
|
|
34725
|
-
fill: context.palette.surface,
|
|
34869
|
+
fill: farPictureTone(item, context) ?? context.palette.surface,
|
|
34726
34870
|
fillAlpha: .96,
|
|
34727
34871
|
accent: fileCategoryAccent(category, context.palette),
|
|
34728
34872
|
accentAlpha: .45
|
|
@@ -34786,19 +34930,24 @@ function sync$7(container, item, context) {
|
|
|
34786
34930
|
parts.label.visible = decorated;
|
|
34787
34931
|
const title = locked ? `🔒 ${props.label}`.trim() : props.label;
|
|
34788
34932
|
const labelColor = style.stroke ? baseStroke : context.palette.muted;
|
|
34933
|
+
const size = context.style.frameLabelSize;
|
|
34789
34934
|
const textSig = [
|
|
34790
34935
|
title,
|
|
34791
34936
|
labelColor,
|
|
34792
|
-
context.style.font
|
|
34937
|
+
context.style.font,
|
|
34938
|
+
size
|
|
34793
34939
|
].join("|");
|
|
34794
34940
|
if (textSig !== parts.textSig) {
|
|
34795
34941
|
parts.textSig = textSig;
|
|
34796
34942
|
parts.label.text = title;
|
|
34797
34943
|
parts.label.style.fill = labelColor;
|
|
34798
34944
|
parts.label.style.fontFamily = context.style.font;
|
|
34945
|
+
parts.label.style.fontSize = size;
|
|
34946
|
+
parts.label.style.lineHeight = size * BOARD_FRAME_LABEL_LINE_HEIGHT;
|
|
34947
|
+
const offset = frameLabelOffset(size);
|
|
34948
|
+
parts.label.position.set(offset.x, offset.y);
|
|
34799
34949
|
}
|
|
34800
34950
|
syncTextResolution(parts.label, context.textResolution);
|
|
34801
|
-
parts.label.position.set(4, -18);
|
|
34802
34951
|
}
|
|
34803
34952
|
const frameCardRenderer = {
|
|
34804
34953
|
id: "frame-card",
|
|
@@ -34809,7 +34958,6 @@ const frameCardRenderer = {
|
|
|
34809
34958
|
const label = createLabel("", {
|
|
34810
34959
|
fill: context.palette.muted,
|
|
34811
34960
|
fontFamily: BOARD_FONT_STACK,
|
|
34812
|
-
fontSize: 12,
|
|
34813
34961
|
fontWeight: "600"
|
|
34814
34962
|
}, context.textResolution);
|
|
34815
34963
|
root.addChild(box, label);
|
|
@@ -35284,7 +35432,7 @@ const imageCardRenderer = {
|
|
|
35284
35432
|
lod: { kind: "media" },
|
|
35285
35433
|
renderFar: (graphics, item, context) => {
|
|
35286
35434
|
drawFarPlate(graphics, item.frame, {
|
|
35287
|
-
fill: context.palette.hover,
|
|
35435
|
+
fill: farPictureTone(item, context) ?? context.palette.hover,
|
|
35288
35436
|
fillAlpha: FAR_ALPHA
|
|
35289
35437
|
});
|
|
35290
35438
|
},
|
|
@@ -35797,7 +35945,7 @@ const taskCardRenderer = {
|
|
|
35797
35945
|
const { status } = liveStatus(item, context);
|
|
35798
35946
|
const active = status === "failed" || status === "running" || status === "pending";
|
|
35799
35947
|
drawFarPlate(graphics, item.frame, {
|
|
35800
|
-
fill: context.palette.surface,
|
|
35948
|
+
fill: farPictureTone(item, context) ?? context.palette.surface,
|
|
35801
35949
|
fillAlpha: .82,
|
|
35802
35950
|
...active ? {
|
|
35803
35951
|
accent: statusColor(status, context),
|
|
@@ -36170,7 +36318,7 @@ const videoCardRenderer = {
|
|
|
36170
36318
|
lod: { kind: "media" },
|
|
36171
36319
|
renderFar: (graphics, item, context) => {
|
|
36172
36320
|
drawFarPlate(graphics, item.frame, {
|
|
36173
|
-
fill: context.palette.surface,
|
|
36321
|
+
fill: farPictureTone(item, context) ?? context.palette.surface,
|
|
36174
36322
|
fillAlpha: .96,
|
|
36175
36323
|
accent: context.palette.brand,
|
|
36176
36324
|
accentAlpha: .75
|
|
@@ -36190,14 +36338,18 @@ const DEFAULT_BOARD_RENDER_LABELS = {
|
|
|
36190
36338
|
previewUnavailable: "Preview unavailable",
|
|
36191
36339
|
noPreview: "No preview"
|
|
36192
36340
|
};
|
|
36341
|
+
const playable = (renderer) => ({
|
|
36342
|
+
...renderer,
|
|
36343
|
+
hitAt: mediaPlayHit
|
|
36344
|
+
});
|
|
36193
36345
|
const builtinCardRenderers = [
|
|
36194
36346
|
textCardRenderer,
|
|
36195
36347
|
imageCardRenderer,
|
|
36196
|
-
videoCardRenderer,
|
|
36197
|
-
audioCardRenderer,
|
|
36198
|
-
modelCardRenderer,
|
|
36348
|
+
playable(videoCardRenderer),
|
|
36349
|
+
playable(audioCardRenderer),
|
|
36350
|
+
playable(modelCardRenderer),
|
|
36199
36351
|
fileCardRenderer,
|
|
36200
|
-
taskCardRenderer,
|
|
36352
|
+
playable(taskCardRenderer),
|
|
36201
36353
|
shapeCardRenderer,
|
|
36202
36354
|
drawCardRenderer,
|
|
36203
36355
|
arrowCardRenderer,
|
|
@@ -36690,10 +36842,17 @@ object({
|
|
|
36690
36842
|
props: object({}).strict().default({})
|
|
36691
36843
|
}).strict();
|
|
36692
36844
|
const BOARD_EXTENSION_TYPE_PATTERN = /^[a-z][a-z0-9]*(?:\.[a-z0-9-]+)+$/;
|
|
36845
|
+
const BoardItemPreviewSchema = object({
|
|
36846
|
+
title: string().max(500).optional(),
|
|
36847
|
+
excerpt: string().max(2e3).optional(),
|
|
36848
|
+
cover: BoardMediaSourceSchema.optional().describe("An image for the card."),
|
|
36849
|
+
src: BoardMediaSourceSchema.optional().describe("The file or page the card stands for.")
|
|
36850
|
+
}).strict().describe("How a client without this type's renderer shows the card, and what it refers to. The type's own client keeps it current.");
|
|
36693
36851
|
object({
|
|
36694
36852
|
...boxed(BOARD_DEFAULT_ITEM_SIZE),
|
|
36695
36853
|
type: string().regex(BOARD_EXTENSION_TYPE_PATTERN),
|
|
36696
|
-
props: record(string(), unknown()).default({})
|
|
36854
|
+
props: record(string(), unknown()).default({}),
|
|
36855
|
+
preview: BoardItemPreviewSchema.optional()
|
|
36697
36856
|
}).strict();
|
|
36698
36857
|
const CSS_EASE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
36699
36858
|
"linear",
|
|
@@ -37143,7 +37302,8 @@ const BASE = {
|
|
|
37143
37302
|
progress: "shimmer",
|
|
37144
37303
|
icons: "line",
|
|
37145
37304
|
motion: "smooth",
|
|
37146
|
-
font: BOARD_FONT_STACK
|
|
37305
|
+
font: BOARD_FONT_STACK,
|
|
37306
|
+
frameLabelSize: 12
|
|
37147
37307
|
};
|
|
37148
37308
|
const SHADOW = {
|
|
37149
37309
|
light: {
|
package/dist/commands/boards.js
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { mkdir } from "node:fs/promises";
|
|
4
4
|
import { dirname } from "node:path";
|
|
5
5
|
import { BOARD_PRESET_NAMES, BOARD_SCHEMA_TARGETS, BOARD_SCRIPT_API, BOARD_THEME_IDS, boardJsonSchema, boardPresetTracks, boardSchemaOverview, isBoardPresetName, listBoardPresets, } from "@neta-art/cohub/board";
|
|
6
|
-
import { BOARD_CREATE_INPUT_MAX_BYTES, BOARD_TRANSACTION_INPUT_MAX_BYTES, parseBoardJsonObject, readBoardJsonObject,
|
|
6
|
+
import { BOARD_CREATE_INPUT_MAX_BYTES, BOARD_TRANSACTION_INPUT_MAX_BYTES, parseBoardJsonObject, readBoardJsonObject, writeBoardOutput, } from "../board-command-support.js";
|
|
7
7
|
import { BOARD_EXPORT_IMAGE_MEMORY_MIB } from "../board-export-resources.js";
|
|
8
8
|
import { settleBoardPatch } from "../board-media.js";
|
|
9
9
|
import { formatBoardTime, parseBoardTime, parseBoardTimes } from "../board-time.js";
|
|
@@ -55,7 +55,7 @@ function list(value) {
|
|
|
55
55
|
}
|
|
56
56
|
async function boardClient(boards, target, spaceId) {
|
|
57
57
|
spaceId ??= await resolveSpace(boards);
|
|
58
|
-
return createClient().space(spaceId).
|
|
58
|
+
return createClient().space(spaceId).boards.open(target);
|
|
59
59
|
}
|
|
60
60
|
/** A patch from an inline argument, a file, or stdin (`-`). */
|
|
61
61
|
async function readPatch(inline, input, maxBytes) {
|
|
@@ -538,9 +538,9 @@ Presets: ${BOARD_PRESET_NAMES.join(", ")}
|
|
|
538
538
|
.action(async (target, options) => {
|
|
539
539
|
try {
|
|
540
540
|
const spaceId = await resolveSpace(boards);
|
|
541
|
-
const boardId = await resolveBoardId(spaceId, target);
|
|
542
541
|
const client = createRealtimeClient();
|
|
543
|
-
const board = client.space(spaceId).
|
|
542
|
+
const board = await client.space(spaceId).boards.open(target);
|
|
543
|
+
const boardId = board.id;
|
|
544
544
|
if (!jsonRequested(options))
|
|
545
545
|
process.stderr.write(`Watching Board ${boardId}…\n`);
|
|
546
546
|
const offConnection = client.onConnection((state) => {
|
|
@@ -90,6 +90,7 @@ export async function runRuntime(config, onState, externalSignal, onDiagnostic)
|
|
|
90
90
|
};
|
|
91
91
|
let closeInstance;
|
|
92
92
|
let bridgeTask;
|
|
93
|
+
let retryDisconnectedBridge;
|
|
93
94
|
let nativeStarted = false;
|
|
94
95
|
let tokenInFlight = null;
|
|
95
96
|
const token = (forceRefresh = false) => {
|
|
@@ -139,6 +140,9 @@ export async function runRuntime(config, onState, externalSignal, onDiagnostic)
|
|
|
139
140
|
let probeTimer;
|
|
140
141
|
let managed = false;
|
|
141
142
|
let childStopped = false;
|
|
143
|
+
let bridgeDisconnected = false;
|
|
144
|
+
let terminating = false;
|
|
145
|
+
let recoveryTimer;
|
|
142
146
|
let controlPipe = null;
|
|
143
147
|
try {
|
|
144
148
|
const initialToken = await token();
|
|
@@ -156,6 +160,8 @@ export async function runRuntime(config, onState, externalSignal, onDiagnostic)
|
|
|
156
160
|
bridge.once("error", (error) => diagnostics.log("error", "sandboxd.process_error", { error: serializeDiagnosticError(error) }));
|
|
157
161
|
bridge.once("exit", (code, exitSignal) => {
|
|
158
162
|
childStopped = true;
|
|
163
|
+
clearTimeout(recoveryTimer);
|
|
164
|
+
recoveryTimer = undefined;
|
|
159
165
|
if (!signal.aborted)
|
|
160
166
|
diagnostics.log("warn", "sandboxd.process_exit", { code, signal: exitSignal });
|
|
161
167
|
// Descendants may still hold inherited pipes. After draining buffered
|
|
@@ -198,8 +204,10 @@ export async function runRuntime(config, onState, externalSignal, onDiagnostic)
|
|
|
198
204
|
managed = true;
|
|
199
205
|
if (event.type === "connected" || event.type === "disconnected") {
|
|
200
206
|
const connected = event.type === "connected";
|
|
207
|
+
bridgeDisconnected = !connected;
|
|
201
208
|
update({ workspaceConnected: connected });
|
|
202
209
|
diagnostics.log(connected ? "info" : "warn", `sandboxd.${event.type}`);
|
|
210
|
+
retryDisconnectedBridge?.();
|
|
203
211
|
}
|
|
204
212
|
}
|
|
205
213
|
catch {
|
|
@@ -238,6 +246,11 @@ export async function runRuntime(config, onState, externalSignal, onDiagnostic)
|
|
|
238
246
|
};
|
|
239
247
|
probeTimer = setTimeout(() => void probe(), 1000);
|
|
240
248
|
const terminate = () => {
|
|
249
|
+
if (terminating || childStopped)
|
|
250
|
+
return;
|
|
251
|
+
terminating = true;
|
|
252
|
+
clearTimeout(recoveryTimer);
|
|
253
|
+
recoveryTimer = undefined;
|
|
241
254
|
// Windows has no SIGTERM; closing the managed stdin lets sandboxd stop gracefully.
|
|
242
255
|
if (process.platform === "win32")
|
|
243
256
|
bridge.stdin?.end();
|
|
@@ -246,6 +259,26 @@ export async function runRuntime(config, onState, externalSignal, onDiagnostic)
|
|
|
246
259
|
const timeout = setTimeout(() => bridge.kill("SIGKILL"), 3000);
|
|
247
260
|
void closed.finally(() => clearTimeout(timeout));
|
|
248
261
|
};
|
|
262
|
+
// A managed bridge may be sleeping for five minutes after a lease
|
|
263
|
+
// rejection. Retry only after it reports a lost connection and the
|
|
264
|
+
// Harness has regained its lease; a connected bridge stays untouched.
|
|
265
|
+
retryDisconnectedBridge = () => {
|
|
266
|
+
if (signal.aborted || !status.harnessConnected || !bridgeDisconnected || terminating || childStopped) {
|
|
267
|
+
clearTimeout(recoveryTimer);
|
|
268
|
+
recoveryTimer = undefined;
|
|
269
|
+
}
|
|
270
|
+
else if (!recoveryTimer) {
|
|
271
|
+
// Give the bridge its normal retry and channel reattachment
|
|
272
|
+
// window before replacing a process stuck in config backoff.
|
|
273
|
+
recoveryTimer = setTimeout(() => {
|
|
274
|
+
recoveryTimer = undefined;
|
|
275
|
+
if (!signal.aborted && status.harnessConnected && bridgeDisconnected && !terminating && !childStopped) {
|
|
276
|
+
diagnostics.log("info", "sandboxd.lease_recovery");
|
|
277
|
+
terminate();
|
|
278
|
+
}
|
|
279
|
+
}, 30_000);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
249
282
|
signal.addEventListener("abort", terminate, { once: true });
|
|
250
283
|
if (signal.aborted)
|
|
251
284
|
terminate();
|
|
@@ -257,10 +290,12 @@ export async function runRuntime(config, onState, externalSignal, onDiagnostic)
|
|
|
257
290
|
diagnostics.log("warn", "sandboxd.restart_failed", { error: serializeDiagnosticError(error) });
|
|
258
291
|
}
|
|
259
292
|
finally {
|
|
293
|
+
retryDisconnectedBridge = undefined;
|
|
260
294
|
childStopped = true;
|
|
261
295
|
controlPipe?.close();
|
|
262
296
|
clearTimeout(refreshTimer);
|
|
263
297
|
clearTimeout(probeTimer);
|
|
298
|
+
clearTimeout(recoveryTimer);
|
|
264
299
|
update({ workspaceConnected: false });
|
|
265
300
|
}
|
|
266
301
|
if (Date.now() - startedAt > 60_000)
|
|
@@ -276,8 +311,8 @@ export async function runRuntime(config, onState, externalSignal, onDiagnostic)
|
|
|
276
311
|
await serveRuntime({
|
|
277
312
|
spaceId: config.spaceId, cwd: config.root, url: url.toString(), capabilities,
|
|
278
313
|
runtimeId: status.runtimeId, diagnostics, token, signal, executor: native.executor,
|
|
279
|
-
onReady: () => update({ harnessConnected: true }),
|
|
280
|
-
onDisconnected: () => { native.connect(null); update({ harnessConnected: false }); },
|
|
314
|
+
onReady: () => { update({ harnessConnected: true }); retryDisconnectedBridge?.(); },
|
|
315
|
+
onDisconnected: () => { native.connect(null); update({ harnessConnected: false }); retryDisconnectedBridge?.(); },
|
|
281
316
|
onNativeStop: (stop) => { native.stop(stop.sessionId, stop.turnId); },
|
|
282
317
|
onNativeChannel: (send) => {
|
|
283
318
|
native.connect(send);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.11.0",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"NOTICE"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@neta-art/cohub": "12.
|
|
21
|
+
"@neta-art/cohub": "12.3.0",
|
|
22
22
|
"@neta-art/generation": "^0.1.40",
|
|
23
23
|
"commander": "^15.0.0",
|
|
24
24
|
"sharp": "^0.35.4",
|