@neta-art/cohub-cli 10.3.0 → 10.4.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.
@@ -1,7 +1,9 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { randomUUID } from "node:crypto";
3
+ import { createServer } from "node:net";
3
4
  import { createInterface } from "node:readline";
4
5
  import { setTimeout as delay } from "node:timers/promises";
6
+ import { PassThrough } from "node:stream";
5
7
  import { resolveWebsocketUrl } from "@neta-art/cohub";
6
8
  import { AuthRequiredError, resolveAccessToken } from "../auth.js";
7
9
  import { createClient } from "../client.js";
@@ -22,6 +24,24 @@ export function sandboxOutputLevel(value, stream) {
22
24
  return level;
23
25
  return stream === "stderr" ? "warn" : "debug";
24
26
  }
27
+ /**
28
+ * Windows cannot hand sandboxd an extra inherited pipe, so control frames use a
29
+ * named pipe instead. Only the first client is accepted; the name is unguessable.
30
+ */
31
+ async function listenWindowsControlPipe() {
32
+ const path = `\\\\.\\pipe\\cohub-sandboxd-${randomUUID()}`;
33
+ const stream = new PassThrough();
34
+ const server = createServer((socket) => {
35
+ server.close();
36
+ socket.on("error", () => stream.end());
37
+ socket.pipe(stream);
38
+ });
39
+ await new Promise((resolve, reject) => {
40
+ server.once("error", reject);
41
+ server.listen(path, () => { server.removeListener("error", reject); resolve(); });
42
+ });
43
+ return { path, stream, close: () => { server.close(); stream.end(); } };
44
+ }
25
45
  export async function runRuntime(config, onState, externalSignal, onDiagnostic) {
26
46
  const controller = new AbortController();
27
47
  const signal = externalSignal ? AbortSignal.any([externalSignal, controller.signal]) : controller.signal;
@@ -119,12 +139,16 @@ export async function runRuntime(config, onState, externalSignal, onDiagnostic)
119
139
  let probeTimer;
120
140
  let managed = false;
121
141
  let childStopped = false;
142
+ let controlPipe = null;
122
143
  try {
123
144
  const initialToken = await token();
124
145
  signal.throwIfAborted();
146
+ if (process.platform === "win32")
147
+ controlPipe = await listenWindowsControlPipe();
125
148
  const bridge = spawn(binary, ["--local", "--space", config.spaceId, "--root", config.root, "--relay", process.env.COHUB_RELAY_URL?.trim() || relay.toString()], {
126
- stdio: ["pipe", "pipe", "pipe", "pipe"],
127
- env: { ...process.env, COHUB_RELAY_TOKEN: initialToken, COHUB_RUNTIME_ID: status.runtimeId, COHUB_LOG_FORMAT: "json", COHUB_RUNTIME_MANAGED: "1", COHUB_DISPLAY: config.display ?? "" },
149
+ stdio: controlPipe ? ["pipe", "pipe", "pipe"] : ["pipe", "pipe", "pipe", "pipe"],
150
+ windowsHide: true,
151
+ env: { ...process.env, COHUB_RELAY_TOKEN: initialToken, COHUB_RUNTIME_ID: status.runtimeId, COHUB_LOG_FORMAT: "json", COHUB_RUNTIME_MANAGED: "1", COHUB_DISPLAY: config.display ?? "", ...(controlPipe ? { COHUB_RUNTIME_CONTROL_PIPE: controlPipe.path } : {}) },
128
152
  });
129
153
  bridge.stdin?.on("error", () => undefined);
130
154
  const closed = new Promise((resolve) => {
@@ -153,7 +177,7 @@ export async function runRuntime(config, onState, externalSignal, onDiagnostic)
153
177
  diagnostics.log(sandboxOutputLevel(parsed?.level, streamName), "sandboxd.log", { ...data, message: parsed?.msg ?? line }, { component: "sandboxd" });
154
178
  });
155
179
  }
156
- const control = bridge.stdio[3];
180
+ const control = controlPipe ? controlPipe.stream : bridge.stdio[3];
157
181
  const events = createInterface({ input: control });
158
182
  events.on("line", (line) => {
159
183
  try {
@@ -202,7 +226,11 @@ export async function runRuntime(config, onState, externalSignal, onDiagnostic)
202
226
  };
203
227
  probeTimer = setTimeout(() => void probe(), 1000);
204
228
  const terminate = () => {
205
- bridge.kill("SIGTERM");
229
+ // Windows has no SIGTERM; closing the managed stdin lets sandboxd stop gracefully.
230
+ if (process.platform === "win32")
231
+ bridge.stdin?.end();
232
+ else
233
+ bridge.kill("SIGTERM");
206
234
  const timeout = setTimeout(() => bridge.kill("SIGKILL"), 3000);
207
235
  void closed.finally(() => clearTimeout(timeout));
208
236
  };
@@ -218,6 +246,7 @@ export async function runRuntime(config, onState, externalSignal, onDiagnostic)
218
246
  }
219
247
  finally {
220
248
  childStopped = true;
249
+ controlPipe?.close();
221
250
  clearTimeout(refreshTimer);
222
251
  clearTimeout(probeTimer);
223
252
  update({ workspaceConnected: false });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/cohub-cli",
3
- "version": "10.3.0",
3
+ "version": "10.4.1",
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": "11.3.0",
21
+ "@neta-art/cohub": "11.3.1",
22
22
  "@neta-art/generation": "^0.1.35",
23
23
  "commander": "^15.0.0",
24
24
  "sharp": "^0.35.4",