@neta-art/cohub-cli 9.1.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 +2 -2
- package/dist/board-kernel.js +583 -382
- 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/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
|
@@ -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.
|
|
@@ -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.1.
|
|
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",
|