@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.
@@ -0,0 +1,3 @@
1
+ export declare const isFetchFailure: (error: unknown) => error is Error;
2
+ export declare function fetchFailureReason(error: unknown): string | null;
3
+ export declare function describeError(error: unknown): string;
@@ -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 instanceof Error) || e.message !== "fetch failed")
150
+ if (!isFetchFailure(e))
150
151
  return null;
151
- const cause = e.cause;
152
- const code = typeof cause?.code === "string" ? cause.code : null;
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: error instanceof Error ? error.message : String(error), uncertain: !!execution.result || frame.resumeOnly === true || error instanceof ProcessCleanupUncertainError });
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 : error instanceof Error ? error.message : String(error) };
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 : error instanceof Error ? error.message : String(error) };
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, address: ResolvedAddress, timeoutMs: number, maxBytes: number) => Promise<RemoteResponse>;
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
- function requestPinned(url, address, timeoutMs, maxBytes) {
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: (_hostname, _options, callback) => {
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
- if (addresses.length === 0 ||
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, address: addresses[0] };
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, address } = await resolvePublicUrl(current, lookup, deadline);
133
- const response = await withDeadline(requester(url, address, remainingMs(deadline), maxBytes), deadline);
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.0",
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": "^26.4.0",
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",