@neta-art/cohub-cli 10.3.0 → 10.4.2
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 +30 -0
- package/dist/board-export-resources.d.ts +8 -0
- package/dist/board-export-resources.js +39 -0
- package/dist/board-export.d.ts +6 -0
- package/dist/board-export.js +58 -8
- package/dist/board-kernel.d.ts +1 -1
- package/dist/board-kernel.js +490 -51
- package/dist/commands/boards/authoring.js +14 -16
- package/dist/commands/boards.d.ts +1 -0
- package/dist/commands/boards.js +27 -1
- package/dist/commands/generations.js +7 -18
- package/dist/commands/public.d.ts +1 -1
- package/dist/commands/public.js +9 -39
- package/dist/commands/rooms-rtc-binary.js +1 -1
- package/dist/commands/sandboxd-binary.d.ts +3 -3
- package/dist/commands/sandboxd-binary.js +27 -17
- package/dist/runtime/harness.d.ts +5 -0
- package/dist/runtime/harness.js +29 -3
- package/dist/runtime/json-rpc.js +1 -1
- package/dist/runtime/launch.js +21 -6
- package/dist/runtime/native/attach.js +5 -2
- package/dist/runtime/native/codex-link.d.ts +2 -0
- package/dist/runtime/native/codex-link.js +3 -1
- package/dist/runtime/native/execution.js +3 -3
- package/dist/runtime/native/ingest.js +4 -4
- package/dist/runtime/native/paths.d.ts +2 -0
- package/dist/runtime/native/paths.js +7 -0
- package/dist/runtime/native/pi-extension.js +12 -2
- package/dist/runtime/native/pi-link.js +21 -4
- package/dist/runtime/native/version.js +4 -1
- package/dist/runtime/supervisor.js +33 -4
- package/dist/space-files/upload.js +2 -19
- package/package.json +3 -3
|
@@ -4,6 +4,7 @@ import { NATIVE_INGEST_MAX_TURNS, SETTLED_TURN_STATUSES } from "@neta-art/cohub"
|
|
|
4
4
|
import { acceptsDirectory, acceptsTranscript, readSessionHeader, readTranscript, transcriptRoots } from "./adapters.js";
|
|
5
5
|
import { nativeTurnId } from "./identity.js";
|
|
6
6
|
import { ensurePlainCodexRollout } from "./transcript.js";
|
|
7
|
+
import { containsNativePath } from "./paths.js";
|
|
7
8
|
const missing = (error) => error?.code === "ENOENT";
|
|
8
9
|
/** Transcripts read in parallel per lane. The live lane never waits behind a backfill. */
|
|
9
10
|
const LIVE_CONCURRENCY = 2;
|
|
@@ -39,9 +40,8 @@ const BOUNDARY_OVERLAP = Math.max(...Object.values(BOUNDARIES).flat().map((token
|
|
|
39
40
|
const IMPORT_FAILURES_KEPT = 100;
|
|
40
41
|
/** Headers are read this many at a time; each costs one small read. */
|
|
41
42
|
const HEADER_CONCURRENCY = 16;
|
|
42
|
-
const within = (root, cwd) => cwd === root || cwd.startsWith(`${root}/`);
|
|
43
43
|
/** Whether a session in `cwd` belongs to the directory bound at `root`. */
|
|
44
|
-
export const owns = (root, cwd, nested = []) => Boolean(cwd) &&
|
|
44
|
+
export const owns = (root, cwd, nested = []) => Boolean(cwd) && containsNativePath(root, cwd) && !nested.some((inner) => containsNativePath(inner, cwd));
|
|
45
45
|
/** Every transcript of the bound directory and its subdirectories, newest first. */
|
|
46
46
|
export async function discoverTranscripts(cwd, harnesses, options = {}) {
|
|
47
47
|
const found = [];
|
|
@@ -144,7 +144,7 @@ export class NativeIngest {
|
|
|
144
144
|
return owns(this.options.cwd, cwd, this.nested);
|
|
145
145
|
}
|
|
146
146
|
exclude(nested) {
|
|
147
|
-
this.nested = nested.filter((root) => root
|
|
147
|
+
this.nested = nested.filter((root) => !containsNativePath(root, this.options.cwd) && containsNativePath(this.options.cwd, root));
|
|
148
148
|
}
|
|
149
149
|
configure(harnesses, enabledAt) {
|
|
150
150
|
const added = harnesses.filter((harness) => !this.harnesses.includes(harness));
|
|
@@ -560,7 +560,7 @@ export class NativeIngest {
|
|
|
560
560
|
}
|
|
561
561
|
harnessFor(path) {
|
|
562
562
|
for (const harness of this.harnesses) {
|
|
563
|
-
if (transcriptRoots(harness).some((root) =>
|
|
563
|
+
if (transcriptRoots(harness).some((root) => containsNativePath(root, path)) && acceptsTranscript(path, harness, this.options.cwd))
|
|
564
564
|
return harness;
|
|
565
565
|
}
|
|
566
566
|
return null;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { posix, win32 } from "node:path";
|
|
2
|
+
/** Whether a native path is the root itself or one of its descendants. */
|
|
3
|
+
export function containsNativePath(root, path, platform = process.platform) {
|
|
4
|
+
const paths = platform === "win32" ? win32 : posix;
|
|
5
|
+
const relative = paths.relative(root, path);
|
|
6
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${paths.sep}`) && !paths.isAbsolute(relative);
|
|
7
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Cohub control bridge for Pi. Self-contained: Node built-ins only, so it installs as one file.
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { existsSync, realpathSync } from "node:fs";
|
|
3
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
4
4
|
import { connect } from "node:net";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
@@ -103,7 +103,17 @@ export default function cohub(pi) {
|
|
|
103
103
|
const path = locateSocket(ctx.cwd);
|
|
104
104
|
if (!path)
|
|
105
105
|
return;
|
|
106
|
-
|
|
106
|
+
// On Windows the socket file names the Runtime's private named pipe.
|
|
107
|
+
let endpoint = path;
|
|
108
|
+
if (process.platform === "win32") {
|
|
109
|
+
try {
|
|
110
|
+
endpoint = readFileSync(path, "utf8").trim();
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const current = connect(endpoint);
|
|
107
117
|
socket = current;
|
|
108
118
|
buffer = "";
|
|
109
119
|
current.on("connect", () => send({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { createServer } from "node:net";
|
|
2
|
-
import { chmod, mkdir, rm } from "node:fs/promises";
|
|
3
|
+
import { chmod, mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
4
|
import { dirname } from "node:path";
|
|
4
5
|
import { COHUB_PI_PROTOCOL } from "./pi-extension.js";
|
|
5
6
|
const MAX_LINE_BYTES = 8 * 1024 * 1024;
|
|
@@ -68,17 +69,33 @@ export class PiLink {
|
|
|
68
69
|
}
|
|
69
70
|
async listen(signal) {
|
|
70
71
|
const { socketPath } = this.options;
|
|
72
|
+
const windows = process.platform === "win32";
|
|
73
|
+
// Windows named pipes share one global namespace: listen on a random name and
|
|
74
|
+
// publish it at socketPath, so another account cannot pre-claim the endpoint.
|
|
75
|
+
const endpoint = windows ? `\\\\.\\pipe\\cohub-pi-${randomUUID()}` : socketPath;
|
|
71
76
|
// The socket accepts commands that drive the user's agent: only this user may reach it.
|
|
72
77
|
await mkdir(dirname(socketPath), { recursive: true, mode: 0o700 });
|
|
73
|
-
if (
|
|
78
|
+
if (!windows)
|
|
74
79
|
await chmod(dirname(socketPath), 0o700);
|
|
75
80
|
await rm(socketPath, { force: true });
|
|
76
81
|
const server = createServer((socket) => this.accept(socket));
|
|
77
82
|
await new Promise((resolve, reject) => {
|
|
78
83
|
server.once("error", reject);
|
|
79
|
-
server.listen(
|
|
84
|
+
server.listen(endpoint, () => { server.removeListener("error", reject); resolve(); });
|
|
80
85
|
});
|
|
81
|
-
if (
|
|
86
|
+
if (windows) {
|
|
87
|
+
const temporary = `${socketPath}.${randomUUID()}.tmp`;
|
|
88
|
+
try {
|
|
89
|
+
await writeFile(temporary, endpoint, { mode: 0o600 });
|
|
90
|
+
await rename(temporary, socketPath);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
server.close();
|
|
94
|
+
await rm(temporary, { force: true });
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else
|
|
82
99
|
await chmod(socketPath, 0o600);
|
|
83
100
|
this.server = server;
|
|
84
101
|
signal.addEventListener("abort", () => {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
|
+
import { harnessCommand } from "../harness.js";
|
|
3
4
|
/** A harness binary's own version, or `null` when it cannot say. */
|
|
4
5
|
export async function harnessVersion(binary) {
|
|
5
|
-
const { stdout } = await
|
|
6
|
+
const { stdout } = await Promise.resolve()
|
|
7
|
+
.then(() => promisify(execFile)(...harnessCommand(binary, ["--version"], process.cwd()), { encoding: "utf8", timeout: 15_000, windowsHide: true }))
|
|
8
|
+
.catch(() => ({ stdout: "" }));
|
|
6
9
|
const match = /(\d+)\.(\d+)\.(\d+)/.exec(stdout);
|
|
7
10
|
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
8
11
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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 });
|
|
@@ -1,30 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { batchUploadEntries } from "@neta-art/cohub";
|
|
3
3
|
import { putLocalFile } from "../http-put.js";
|
|
4
4
|
import { createLimiter } from "./limit.js";
|
|
5
5
|
const PUT_CONCURRENCY = 4;
|
|
6
|
-
function batches(entries) {
|
|
7
|
-
const result = [];
|
|
8
|
-
let batch = [];
|
|
9
|
-
let bytes = 0;
|
|
10
|
-
for (const entry of entries) {
|
|
11
|
-
if (batch.length > 0 && (batch.length >= UPLOAD_MAX_BATCH_FILES || bytes + entry.size > UPLOAD_MAX_BATCH_BYTES)) {
|
|
12
|
-
result.push(batch);
|
|
13
|
-
batch = [];
|
|
14
|
-
bytes = 0;
|
|
15
|
-
}
|
|
16
|
-
batch.push(entry);
|
|
17
|
-
bytes += entry.size;
|
|
18
|
-
}
|
|
19
|
-
if (batch.length > 0)
|
|
20
|
-
result.push(batch);
|
|
21
|
-
return result;
|
|
22
|
-
}
|
|
23
6
|
export async function uploadLocalFiles(files, entries, options = {}) {
|
|
24
7
|
const uploaded = [];
|
|
25
8
|
const uploadIds = [];
|
|
26
9
|
const limit = createLimiter(PUT_CONCURRENCY);
|
|
27
|
-
for (const batch of
|
|
10
|
+
for (const batch of batchUploadEntries(entries, (entry) => entry.size)) {
|
|
28
11
|
const byId = new Map(batch.map((entry) => [randomUUID(), entry]));
|
|
29
12
|
const plan = await files.createUpload({
|
|
30
13
|
destination: { kind: "workspace", targetDir: options.targetDir },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.4.2",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"NOTICE"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@neta-art/cohub": "11.3.
|
|
22
|
-
"@neta-art/generation": "^0.1.
|
|
21
|
+
"@neta-art/cohub": "11.3.2",
|
|
22
|
+
"@neta-art/generation": "^0.1.38",
|
|
23
23
|
"commander": "^15.0.0",
|
|
24
24
|
"sharp": "^0.35.4",
|
|
25
25
|
"ws": "^8.21.3"
|