@neta-art/cohub-cli 9.0.0 → 9.1.1
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 +41 -28
- package/dist/board-kernel.js +583 -382
- package/dist/commands/boards.js +31 -12
- package/dist/commands/runtime.js +3 -2
- package/dist/commands/sandboxd-binary.d.ts +1 -1
- package/dist/commands/sandboxd-binary.js +2 -23
- package/dist/index.js +1 -1
- package/dist/network-error.d.ts +3 -0
- package/dist/network-error.js +22 -0
- package/dist/output.js +4 -9
- package/dist/runtime/connection.js +2 -1
- package/dist/runtime/launch.d.ts +1 -1
- package/dist/runtime/launch.js +12 -9
- package/dist/runtime/native/execution.js +3 -2
- package/dist/safe-remote-image.d.ts +5 -1
- package/dist/safe-remote-image.js +16 -8
- package/package.json +4 -4
package/dist/commands/boards.js
CHANGED
|
@@ -145,7 +145,7 @@ function registerExportCommand(boards) {
|
|
|
145
145
|
.option("--scale <factor>", "Output pixels per board unit", "2")
|
|
146
146
|
.option("--padding <units>", "Padding around the content in board units")
|
|
147
147
|
.option("--theme <mode>", "dark or light", "dark")
|
|
148
|
-
.option("--
|
|
148
|
+
.option("--paper <mode>", "Output paper: paper or transparent", "paper")
|
|
149
149
|
.option("--format <format>", `Override the format (${BOARD_EXPORT_FORMATS.join(", ")}, ${BOARD_VIDEO_FORMATS.join(", ")})`)
|
|
150
150
|
.option("--quality <q>", "JPEG/WebP quality from 0 to 1", "0.92")
|
|
151
151
|
.option("--fps <rate>", "Video frame rate; defaults to the --at step")
|
|
@@ -189,7 +189,7 @@ Examples:
|
|
|
189
189
|
scale: parseNumber(options.scale ?? "2", "--scale", { min: 0.01, max: 16 }),
|
|
190
190
|
...(options.padding === undefined ? {} : { padding: parseNumber(options.padding, "--padding", { min: 0 }) }),
|
|
191
191
|
colorScheme: parseChoice(options.theme, "--theme", ["dark", "light"]),
|
|
192
|
-
background: parseChoice(options.
|
|
192
|
+
background: parseChoice(options.paper, "--paper", ["paper", "transparent"]),
|
|
193
193
|
format: videoFormat,
|
|
194
194
|
fps,
|
|
195
195
|
output: out,
|
|
@@ -213,7 +213,7 @@ Examples:
|
|
|
213
213
|
scale: parseNumber(options.scale ?? "2", "--scale", { min: 0.01, max: 16 }),
|
|
214
214
|
...(options.padding === undefined ? {} : { padding: parseNumber(options.padding, "--padding", { min: 0 }) }),
|
|
215
215
|
colorScheme: parseChoice(options.theme, "--theme", ["dark", "light"]),
|
|
216
|
-
background: parseChoice(options.
|
|
216
|
+
background: parseChoice(options.paper, "--paper", ["paper", "transparent"]),
|
|
217
217
|
format: parseExportFormat(options, out),
|
|
218
218
|
quality: parseNumber(options.quality ?? "0.92", "--quality", { min: 0, max: 1 }),
|
|
219
219
|
withImages: options.images !== false,
|
|
@@ -252,6 +252,9 @@ A Board is one JSON document: { board, items, animations }.
|
|
|
252
252
|
get read it
|
|
253
253
|
apply write it with a JSON Merge Patch; null deletes
|
|
254
254
|
schema the schema of any part, and what tracks can animate
|
|
255
|
+
preset tracks for a preset motion, ready to apply
|
|
256
|
+
history list versions, or restore one
|
|
257
|
+
export render an image, a frame sequence or a video
|
|
255
258
|
examples starter documents
|
|
256
259
|
Times are milliseconds; 500ms, 12.5s and 2m also work on the command line.`);
|
|
257
260
|
withJson(boards.command("create <path>")
|
|
@@ -288,7 +291,7 @@ Times are milliseconds; 500ms, 12.5s and 2m also work on the command line.`);
|
|
|
288
291
|
});
|
|
289
292
|
boards.command("get <board>")
|
|
290
293
|
.description("Print the Board document as JSON")
|
|
291
|
-
.option("--only <sections>", "items, animations
|
|
294
|
+
.option("--only <sections>", "board, items, animations (comma-separated)")
|
|
292
295
|
.option("--items <ids>", "Only these items")
|
|
293
296
|
.option("--within <frame-id>", "A frame and everything inside it")
|
|
294
297
|
.option("--rect <rect>", "Items intersecting x,y,width,height")
|
|
@@ -298,14 +301,15 @@ Times are milliseconds; 500ms, 12.5s and 2m also work on the command line.`);
|
|
|
298
301
|
.addHelpText("after", `
|
|
299
302
|
cohub boards get plan.board
|
|
300
303
|
cohub boards get plan.board --within s2
|
|
301
|
-
cohub boards get plan.board --only
|
|
304
|
+
cohub boards get plan.board --only board`)
|
|
302
305
|
.action(async (target, options) => {
|
|
303
306
|
try {
|
|
304
307
|
const only = list(options.only);
|
|
305
|
-
if (only?.some((section) => section !== "items" && section !== "animations"))
|
|
306
|
-
throw new Error("--only takes items, animations, or
|
|
308
|
+
if (only?.some((section) => section !== "board" && section !== "items" && section !== "animations"))
|
|
309
|
+
throw new Error("--only takes board, items, animations, or a combination.");
|
|
310
|
+
const sections = only ? new Set(only) : null;
|
|
307
311
|
const input = {
|
|
308
|
-
...(only ? { only: only } : {}),
|
|
312
|
+
...(only ? { only: only.filter((section) => section !== "board") } : {}),
|
|
309
313
|
...(list(options.items) ? { items: list(options.items) } : {}),
|
|
310
314
|
...(options.within ? { within: options.within } : {}),
|
|
311
315
|
...(options.rect ? { rect: parseRect(options.rect) } : {}),
|
|
@@ -313,7 +317,20 @@ Times are milliseconds; 500ms, 12.5s and 2m also work on the command line.`);
|
|
|
313
317
|
...(options.limit ? { limit: parseNumber(options.limit, "--limit", { min: 1, integer: true }) } : {}),
|
|
314
318
|
...(options.cursor ? { cursor: options.cursor } : {}),
|
|
315
319
|
};
|
|
316
|
-
|
|
320
|
+
const result = await (await boardClient(boards, target)).get(input);
|
|
321
|
+
if (!sections)
|
|
322
|
+
return outJson(result);
|
|
323
|
+
outJson({
|
|
324
|
+
id: result.id,
|
|
325
|
+
title: result.title,
|
|
326
|
+
version: result.version,
|
|
327
|
+
updatedAt: result.updatedAt,
|
|
328
|
+
playback: result.playback,
|
|
329
|
+
...(sections.has("board") ? { board: result.board } : {}),
|
|
330
|
+
...(sections.has("items") && result.items ? { items: result.items } : {}),
|
|
331
|
+
...(sections.has("animations") && result.animations ? { animations: result.animations } : {}),
|
|
332
|
+
...(result.next ? { next: result.next } : {}),
|
|
333
|
+
});
|
|
317
334
|
}
|
|
318
335
|
catch (cause) {
|
|
319
336
|
handleHttp(cause);
|
|
@@ -393,6 +410,7 @@ Times are milliseconds; 500ms, 12.5s and 2m also work on the command line.`);
|
|
|
393
410
|
boards.command("schema [target]")
|
|
394
411
|
.description("Print JSON Schema for a part of a Board")
|
|
395
412
|
.addHelpText("after", `
|
|
413
|
+
With a target: one item type, or animation, track, or camera.
|
|
396
414
|
Targets: ${BOARD_SCHEMA_TARGETS.join(", ")}
|
|
397
415
|
Without a target: units, item types, colors and every animatable property.`)
|
|
398
416
|
.action((target) => {
|
|
@@ -411,14 +429,15 @@ Without a target: units, item types, colors and every animatable property.`)
|
|
|
411
429
|
boards.command("preset <name>")
|
|
412
430
|
.description("Print tracks for a preset motion as a patch for apply")
|
|
413
431
|
.requiredOption("--targets <ids>", "Comma-separated item ids")
|
|
414
|
-
.
|
|
432
|
+
.option("--animation <id>", "Wrap the tracks in this animation; omit to get bare tracks")
|
|
415
433
|
.option("--at <time>", "Start of the first target", "0")
|
|
416
434
|
.option("--stagger <time>", "Delay added per target", "0")
|
|
417
435
|
.option("--duration <time>", "Length of each target's motion")
|
|
418
436
|
.option("--ease <ease>", "CSS easing, e.g. ease-out or cubic-bezier(.2,.8,.2,1)")
|
|
419
437
|
.addHelpText("after", `
|
|
420
438
|
Presets: ${BOARD_PRESET_NAMES.join(", ")}
|
|
421
|
-
cohub boards preset rise --targets a,b,c --animation intro --stagger 120ms | cohub boards apply plan.board -i
|
|
439
|
+
cohub boards preset rise --targets a,b,c --animation intro --stagger 120ms | cohub boards apply plan.board -i -
|
|
440
|
+
cohub boards preset float --targets ship | jq -c '{animations:{idle:.}}' | cohub boards apply plan.board -i -`)
|
|
422
441
|
.action((name, options) => {
|
|
423
442
|
try {
|
|
424
443
|
if (!isBoardPresetName(name))
|
|
@@ -433,7 +452,7 @@ Presets: ${BOARD_PRESET_NAMES.join(", ")}
|
|
|
433
452
|
...(options.duration ? { duration: parseBoardTime(options.duration, "--duration") } : {}),
|
|
434
453
|
...(options.ease ? { ease: options.ease } : {}),
|
|
435
454
|
});
|
|
436
|
-
outJson({ animations: { [options.animation]: { tracks } } });
|
|
455
|
+
outJson(options.animation ? { animations: { [options.animation]: { tracks } } } : { tracks });
|
|
437
456
|
}
|
|
438
457
|
catch (cause) {
|
|
439
458
|
handleHttp(cause);
|
package/dist/commands/runtime.js
CHANGED
|
@@ -41,8 +41,9 @@ export function registerRuntime(program) {
|
|
|
41
41
|
.option("--harness <name>", "Pi or Codex; repeatable", (value, previous) => [...previous, value], [])
|
|
42
42
|
.option("--pi <path>", "Pi executable")
|
|
43
43
|
.option("--codex <path>", "Codex executable")
|
|
44
|
-
.option("--display [screen]", "Share a screen: auto
|
|
45
|
-
.option("-
|
|
44
|
+
.option("--display [screen]", "Share a screen (default: auto when available), or xvfb[:WIDTHxHEIGHT] for a virtual one")
|
|
45
|
+
.option("--no-display", "Disable screen sharing and skip its confirmation")
|
|
46
|
+
.option("-y, --yes", "Accept defaults and authorize local execution, native sync, and screen sharing")
|
|
46
47
|
.option("--verbose", "Show diagnostic details")
|
|
47
48
|
.option("--json", "JSON output")
|
|
48
49
|
.action(async (dir, options) => {
|
|
@@ -6,29 +6,8 @@ import { homedir, tmpdir } from "node:os";
|
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { pipeline } from "node:stream/promises";
|
|
8
8
|
import { Readable } from "node:stream";
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
// protocol ("1") guarantees backward compatibility.
|
|
12
|
-
//
|
|
13
|
-
// IMPORTANT: this must point at a tag whose CDN artifacts have already been
|
|
14
|
-
// published by .github/workflows/sandbox-binaries-build.yml. Only bump it AFTER
|
|
15
|
-
// that tag's publish-cdn job has succeeded, otherwise `runtime up` 404s on the
|
|
16
|
-
// default download.
|
|
17
|
-
//
|
|
18
|
-
// v2.54.0 is the first published tag with the private managed Runtime control
|
|
19
|
-
// pipe (`COHUB_RUNTIME_MANAGED` over fd 3), so `runtime up` reads connection
|
|
20
|
-
// state from the daemon instead of polling the API every five seconds. It also
|
|
21
|
-
// carries the optional workspace-search runner download. v2.53.1 already has
|
|
22
|
-
// the native FSEvents backends and the `runtimeId` control frame, and older
|
|
23
|
-
// releases stay usable through the compatibility readiness/restart path.
|
|
24
|
-
//
|
|
25
|
-
// v2.54.1 keeps the same wire protocol and adds relay diagnostics: data-channel
|
|
26
|
-
// pairing outlives the runner's dial timeout, dial failures distinguish a
|
|
27
|
-
// timeout from an explicit rejection, and teardown-time websocket write
|
|
28
|
-
// failures log at debug instead of warn. v2.55.0 republishes that compatible
|
|
29
|
-
// runner alongside the matching platform release; no new runner capability is
|
|
30
|
-
// required.
|
|
31
|
-
export const SANDBOXD_VERSION = "v2.55.0";
|
|
9
|
+
// Pin only after all platform artifacts are published to the public CDN.
|
|
10
|
+
export const SANDBOXD_VERSION = "v2.61.1";
|
|
32
11
|
const BINARY_NAME = "cohub-sandboxd";
|
|
33
12
|
// Public CDN prefix hosting the release archives (the repo is private, so the
|
|
34
13
|
// GitHub Release assets are not publicly downloadable). Overridable for staging
|
package/dist/index.js
CHANGED
|
@@ -56,7 +56,7 @@ Common commands:
|
|
|
56
56
|
cohub runtime up ./my-project
|
|
57
57
|
cohub runtime logs --follow
|
|
58
58
|
cohub search "release notes"
|
|
59
|
-
cohub -s <space-id> boards
|
|
59
|
+
cohub -s <space-id> boards get <board-id>
|
|
60
60
|
cohub -s <space-id> spaces turns ls --author others
|
|
61
61
|
cohub -s <space-id> spaces sessions turns ls <session-id>
|
|
62
62
|
cohub -s home spaces files ls
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const text = (value) => (typeof value === "string" && value ? value : null);
|
|
2
|
+
export const isFetchFailure = (error) => error instanceof Error && error.message === "fetch failed";
|
|
3
|
+
export function fetchFailureReason(error) {
|
|
4
|
+
if (!isFetchFailure(error) || typeof error.cause !== "object" || !error.cause)
|
|
5
|
+
return null;
|
|
6
|
+
const cause = error.cause;
|
|
7
|
+
const message = text(cause.message);
|
|
8
|
+
const code = text(cause.code);
|
|
9
|
+
const hostname = text(cause.hostname);
|
|
10
|
+
const parts = [
|
|
11
|
+
code && !message?.includes(code) ? code : null,
|
|
12
|
+
hostname && !message?.includes(hostname) ? `host: ${hostname}` : null,
|
|
13
|
+
message,
|
|
14
|
+
].filter(Boolean);
|
|
15
|
+
return parts.length > 0 ? parts.join(" · ") : null;
|
|
16
|
+
}
|
|
17
|
+
export function describeError(error) {
|
|
18
|
+
if (!(error instanceof Error))
|
|
19
|
+
return String(error);
|
|
20
|
+
const reason = fetchFailureReason(error);
|
|
21
|
+
return reason ? `${error.message} (${reason})` : error.message;
|
|
22
|
+
}
|
package/dist/output.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
import { extractBillingPayload } from "@neta-art/cohub";
|
|
3
|
+
import { fetchFailureReason, isFetchFailure } from "./network-error.js";
|
|
3
4
|
/**
|
|
4
5
|
* Timestamp → local ISO-style date and time with the system's UTC offset.
|
|
5
6
|
*
|
|
@@ -146,16 +147,10 @@ function debugErrorMetaFromBody(body) {
|
|
|
146
147
|
return items;
|
|
147
148
|
}
|
|
148
149
|
function fetchFailureDetail(e) {
|
|
149
|
-
if (!(e
|
|
150
|
+
if (!isFetchFailure(e))
|
|
150
151
|
return null;
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
const hostname = typeof cause?.hostname === "string" ? cause.hostname : null;
|
|
154
|
-
const message = typeof cause?.message === "string" ? cause.message : null;
|
|
155
|
-
const parts = [code, hostname && `host: ${hostname}`, message].filter(Boolean);
|
|
156
|
-
return parts.length > 0
|
|
157
|
-
? `Network request failed (${parts.join(" · ")}). Check DNS/proxy/firewall settings and try again.`
|
|
158
|
-
: "Network request failed. Check DNS/proxy/firewall settings and try again.";
|
|
152
|
+
const reason = fetchFailureReason(e);
|
|
153
|
+
return `Network request failed${reason ? ` (${reason})` : ""}. Check DNS/proxy/firewall settings and try again.`;
|
|
159
154
|
}
|
|
160
155
|
function errorPresentationFromHttpError(e) {
|
|
161
156
|
const httpError = e;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { setTimeout as delay } from "node:timers/promises";
|
|
3
3
|
import { RUNTIME_MAX_FRAME_BYTES, RUNTIME_PROTOCOL_VERSION, runtimeCommandSchema, runtimeReadySchema, runtimeNativeStopSchema, } from "@neta-art/cohub";
|
|
4
|
+
import { describeError } from "../network-error.js";
|
|
4
5
|
import { executeTurn } from "./native/execution.js";
|
|
5
6
|
import { ContextRequiredError } from "./native/results.js";
|
|
6
7
|
import { ProcessCleanupUncertainError } from "./process-group.js";
|
|
@@ -579,7 +580,7 @@ async function connect(options) {
|
|
|
579
580
|
error: serializeDiagnosticError(error),
|
|
580
581
|
}, context);
|
|
581
582
|
try {
|
|
582
|
-
emit({ type: "turn.error", message:
|
|
583
|
+
emit({ type: "turn.error", message: describeError(error), uncertain: !!execution.result || frame.resumeOnly === true || error instanceof ProcessCleanupUncertainError });
|
|
583
584
|
}
|
|
584
585
|
catch {
|
|
585
586
|
// Native files remain for recovery.
|
package/dist/runtime/launch.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export type RuntimeUpOptions = {
|
|
|
16
16
|
};
|
|
17
17
|
export declare const resolveLocalSpaceName: (root: string, name?: string) => string;
|
|
18
18
|
export declare function parseRuntimeHarnesses(values: string[]): ("pi" | "codex")[];
|
|
19
|
-
export declare function parseRuntimeDisplay(value: string | boolean | undefined): string | undefined;
|
|
19
|
+
export declare function parseRuntimeDisplay(value: string | boolean | undefined, platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): string | undefined;
|
|
20
20
|
export declare function resolveRuntimeTarget(program: Command, target?: string): Promise<string>;
|
|
21
21
|
export declare function startBackgroundRuntime(config: RuntimeLaunch): Promise<RuntimeSummary>;
|
|
22
22
|
export declare function runtimeUp(program: Command, dir: string | undefined, options: RuntimeUpOptions): Promise<void>;
|
package/dist/runtime/launch.js
CHANGED
|
@@ -20,15 +20,17 @@ export function parseRuntimeHarnesses(values) {
|
|
|
20
20
|
throw new Error("Harness must be pi or codex");
|
|
21
21
|
return [...new Set(names.length ? names : ["pi"])];
|
|
22
22
|
}
|
|
23
|
-
export function parseRuntimeDisplay(value) {
|
|
24
|
-
if (value ===
|
|
23
|
+
export function parseRuntimeDisplay(value, platform = process.platform, env = process.env) {
|
|
24
|
+
if (value === false)
|
|
25
25
|
return undefined;
|
|
26
|
-
|
|
26
|
+
if (value === undefined && platform !== "darwin" && (platform !== "linux" || !env.DISPLAY))
|
|
27
|
+
return undefined;
|
|
28
|
+
const spec = value === undefined || value === true ? "auto" : value.trim();
|
|
27
29
|
if (!/^(auto|macos|x11(:[\w.:-]+)?|xvfb(:\d{3,4}x\d{3,4})?)$/.test(spec)) {
|
|
28
30
|
throw new Error("Display must be auto, macos, x11[:display] or xvfb[:WIDTHxHEIGHT]");
|
|
29
31
|
}
|
|
30
|
-
if (
|
|
31
|
-
throw new Error(
|
|
32
|
+
if (platform === "linux" && spec === "auto" && !env.DISPLAY) {
|
|
33
|
+
throw new Error(env.WAYLAND_DISPLAY
|
|
32
34
|
? "Wayland sessions cannot be shared yet: log in with an X11 session, or use --display xvfb for a virtual screen"
|
|
33
35
|
: "No X11 session to share (DISPLAY is unset): use --display xvfb for a virtual screen");
|
|
34
36
|
}
|
|
@@ -106,11 +108,11 @@ export async function runtimeUp(program, dir, options) {
|
|
|
106
108
|
const binding = await getRuntimeSpaceBinding(root, identity);
|
|
107
109
|
const existingId = requested || binding?.spaceId;
|
|
108
110
|
let harnesses = parseRuntimeHarnesses(options.harness);
|
|
109
|
-
let display = parseRuntimeDisplay(options.display);
|
|
111
|
+
let display = options.display === undefined ? undefined : parseRuntimeDisplay(options.display);
|
|
110
112
|
if (existingId && !options.new) {
|
|
111
113
|
const existing = await requestRuntimeInstance(runtimeInstanceDirectory(identity, existingId));
|
|
112
114
|
if (existing) {
|
|
113
|
-
if (existing.root !== root || options.harness.length && [...existing.harnesses].sort().join() !== [...harnesses].sort().join() || options.pi || options.codex || display && display !== existing.display) {
|
|
115
|
+
if (existing.root !== root || options.harness.length && [...existing.harnesses].sort().join() !== [...harnesses].sort().join() || options.pi || options.codex || options.display !== undefined && display !== existing.display) {
|
|
114
116
|
throw new Error("Runtime is running with a different configuration. Use down first");
|
|
115
117
|
}
|
|
116
118
|
// `up` is the single idempotent entry: a reused instance adopts its running
|
|
@@ -125,6 +127,7 @@ export async function runtimeUp(program, dir, options) {
|
|
|
125
127
|
return;
|
|
126
128
|
}
|
|
127
129
|
}
|
|
130
|
+
display = parseRuntimeDisplay(options.display);
|
|
128
131
|
if (!options.harness.length)
|
|
129
132
|
harnesses = await installedHarnesses(root, options);
|
|
130
133
|
if (!harnesses.length)
|
|
@@ -157,8 +160,8 @@ export async function runtimeUp(program, dir, options) {
|
|
|
157
160
|
if (/^n(o)?$/i.test(answer.trim()))
|
|
158
161
|
return;
|
|
159
162
|
if (display && !isVirtualDisplay(display)) {
|
|
160
|
-
const share = await rl.question("Collaborators and agents can see and control this screen. Share it? [
|
|
161
|
-
if (!/^y(es)?$/i.test(share.trim()))
|
|
163
|
+
const share = await rl.question("Collaborators and agents can see and control this screen. Share it? [Y/n] ");
|
|
164
|
+
if (!/^(y(es)?)?$/i.test(share.trim()))
|
|
162
165
|
display = undefined;
|
|
163
166
|
}
|
|
164
167
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { imageForPi } from "../harness.js";
|
|
2
2
|
import { JsonRpcProcess, record, RpcProcessClosedError } from "../json-rpc.js";
|
|
3
|
+
import { describeError } from "../../network-error.js";
|
|
3
4
|
import { ProcessCleanupUncertainError } from "../process-group.js";
|
|
4
5
|
import { serializeDiagnosticError } from "../diagnostics.js";
|
|
5
6
|
import { openPrivateServer } from "./codex-link.js";
|
|
@@ -164,7 +165,7 @@ async function runPi(executor, turn, context) {
|
|
|
164
165
|
await closeHost(host, null).catch(() => undefined);
|
|
165
166
|
throw error;
|
|
166
167
|
}
|
|
167
|
-
message = { ...message, stopReason: signal.aborted ? "aborted" : "error", errorMessage: signal.aborted ? null :
|
|
168
|
+
message = { ...message, stopReason: signal.aborted ? "aborted" : "error", errorMessage: signal.aborted ? null : describeError(error) };
|
|
168
169
|
}
|
|
169
170
|
return { session, resume: prepared.resume, message, host };
|
|
170
171
|
}
|
|
@@ -306,7 +307,7 @@ async function runCodex(executor, turn, context) {
|
|
|
306
307
|
await closeHost(host, null).catch(() => undefined);
|
|
307
308
|
throw error;
|
|
308
309
|
}
|
|
309
|
-
message = { ...message, stopReason: signal.aborted ? "aborted" : "error", errorMessage: signal.aborted ? null :
|
|
310
|
+
message = { ...message, stopReason: signal.aborted ? "aborted" : "error", errorMessage: signal.aborted ? null : describeError(error) };
|
|
310
311
|
}
|
|
311
312
|
return { session, resume: prepared.resume, message, host };
|
|
312
313
|
}
|
|
@@ -1,22 +1,26 @@
|
|
|
1
|
+
import { type LookupFunction } from "node:net";
|
|
1
2
|
export declare const REMOTE_IMAGE_MAX_BYTES: number;
|
|
2
3
|
export declare const REMOTE_IMAGE_TIMEOUT_MS = 15000;
|
|
3
4
|
type ResolvedAddress = {
|
|
4
5
|
address: string;
|
|
5
6
|
family: 4 | 6;
|
|
6
7
|
};
|
|
8
|
+
type PinnedAddresses = readonly [ResolvedAddress, ...ResolvedAddress[]];
|
|
7
9
|
type Lookup = (hostname: string) => Promise<readonly ResolvedAddress[]>;
|
|
8
10
|
type RemoteResponse = {
|
|
9
11
|
status: number;
|
|
10
12
|
headers: Headers;
|
|
11
13
|
bytes: Uint8Array;
|
|
12
14
|
};
|
|
13
|
-
type Requester = (url: URL,
|
|
15
|
+
type Requester = (url: URL, addresses: PinnedAddresses, timeoutMs: number, maxBytes: number) => Promise<RemoteResponse>;
|
|
14
16
|
export type RemoteImageDownloadOptions = {
|
|
15
17
|
lookup?: Lookup;
|
|
16
18
|
requester?: Requester;
|
|
17
19
|
maxBytes?: number;
|
|
18
20
|
timeoutMs?: number;
|
|
19
21
|
};
|
|
22
|
+
/** Async like `dns.lookup`: a synchronous answer crashes TLS on an immediate connect failure. */
|
|
23
|
+
export declare function pinnedLookup(addresses: PinnedAddresses): LookupFunction;
|
|
20
24
|
export declare function downloadPublicImage(input: string, options?: RemoteImageDownloadOptions): Promise<{
|
|
21
25
|
bytes: Uint8Array;
|
|
22
26
|
mimeType: string;
|
|
@@ -34,15 +34,22 @@ function responseHeaders(input) {
|
|
|
34
34
|
}
|
|
35
35
|
return headers;
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
/** Async like `dns.lookup`: a synchronous answer crashes TLS on an immediate connect failure. */
|
|
38
|
+
export function pinnedLookup(addresses) {
|
|
39
|
+
const [first] = addresses;
|
|
40
|
+
return (_hostname, options, callback) => {
|
|
41
|
+
process.nextTick(() => options.all
|
|
42
|
+
? callback(null, [...addresses])
|
|
43
|
+
: callback(null, first.address, first.family));
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function requestPinned(url, addresses, timeoutMs, maxBytes) {
|
|
38
47
|
return new Promise((resolve, reject) => {
|
|
39
48
|
const client = url.protocol === "https:" ? https : http;
|
|
40
49
|
const request = client.request(url, {
|
|
41
50
|
agent: false,
|
|
42
51
|
headers: { Accept: "image/avif,image/webp,image/png,image/jpeg,image/gif" },
|
|
43
|
-
lookup: (
|
|
44
|
-
callback(null, address.address, address.family);
|
|
45
|
-
},
|
|
52
|
+
lookup: pinnedLookup(addresses),
|
|
46
53
|
}, (response) => {
|
|
47
54
|
const status = response.statusCode ?? 0;
|
|
48
55
|
const headers = responseHeaders(response.headers);
|
|
@@ -116,11 +123,12 @@ async function resolvePublicUrl(value, lookup, deadline) {
|
|
|
116
123
|
const addresses = isIP(hostname)
|
|
117
124
|
? [{ address: hostname, family: isIP(hostname) }]
|
|
118
125
|
: await withDeadline(lookup(hostname), deadline);
|
|
119
|
-
|
|
126
|
+
const [first, ...rest] = addresses;
|
|
127
|
+
if (!first ||
|
|
120
128
|
addresses.some((entry) => !isPublicBoardRemoteAddress(entry.address))) {
|
|
121
129
|
throw new Error("Image URL resolves to a private address");
|
|
122
130
|
}
|
|
123
|
-
return { url,
|
|
131
|
+
return { url, addresses: [first, ...rest] };
|
|
124
132
|
}
|
|
125
133
|
export async function downloadPublicImage(input, options = {}) {
|
|
126
134
|
const lookup = options.lookup ?? defaultLookup;
|
|
@@ -129,8 +137,8 @@ export async function downloadPublicImage(input, options = {}) {
|
|
|
129
137
|
const deadline = Date.now() + (options.timeoutMs ?? REMOTE_IMAGE_TIMEOUT_MS);
|
|
130
138
|
let current = input;
|
|
131
139
|
for (let redirect = 0; redirect <= MAX_REDIRECTS; redirect += 1) {
|
|
132
|
-
const { url,
|
|
133
|
-
const response = await withDeadline(requester(url,
|
|
140
|
+
const { url, addresses } = await resolvePublicUrl(current, lookup, deadline);
|
|
141
|
+
const response = await withDeadline(requester(url, addresses, remainingMs(deadline), maxBytes), deadline);
|
|
134
142
|
if (REDIRECT_STATUSES.has(response.status)) {
|
|
135
143
|
const location = response.headers.get("location");
|
|
136
144
|
if (!location)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.1.1",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"NOTICE"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
+
"@neta-art/cohub": "9.0.1",
|
|
21
22
|
"@neta-art/generation": "^0.1.30",
|
|
22
23
|
"commander": "^15.0.0",
|
|
23
24
|
"sharp": "^0.35.4",
|
|
24
|
-
"ws": "^8.21.3"
|
|
25
|
-
"@neta-art/cohub": "9.0.0"
|
|
25
|
+
"ws": "^8.21.3"
|
|
26
26
|
},
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@fontsource/geist": "^5.3.0",
|
|
32
32
|
"@fontsource/geist-mono": "^5.3.0",
|
|
33
|
-
"@types/node": "
|
|
33
|
+
"@types/node": "26.6.4",
|
|
34
34
|
"@types/ws": "^8.18.1",
|
|
35
35
|
"pixi.js": "^8.20.1",
|
|
36
36
|
"tsdown": "^0.22.14",
|