@neta-art/cohub-cli 7.1.2 → 8.0.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 +40 -2
- package/dist/auth.js +38 -5
- package/dist/client.js +5 -2
- package/dist/commands/runtime.d.ts +1 -2
- package/dist/commands/runtime.js +169 -293
- package/dist/commands/sandboxd-binary.d.ts +1 -1
- package/dist/commands/sandboxd-binary.js +13 -5
- package/dist/runtime/archive-store.d.ts +5 -1
- package/dist/runtime/archive-store.js +22 -6
- package/dist/runtime/connection.d.ts +4 -2
- package/dist/runtime/connection.js +113 -44
- package/dist/runtime/diagnostics.d.ts +3 -0
- package/dist/runtime/diagnostics.js +3 -0
- package/dist/runtime/harness.d.ts +7 -0
- package/dist/runtime/harness.js +63 -17
- package/dist/runtime/instance.d.ts +5 -0
- package/dist/runtime/instance.js +159 -0
- package/dist/runtime/json-rpc.d.ts +2 -0
- package/dist/runtime/json-rpc.js +2 -0
- package/dist/runtime/launch.d.ts +20 -0
- package/dist/runtime/launch.js +176 -0
- package/dist/runtime/native-codex-hook.d.ts +1 -0
- package/dist/runtime/native-codex-hook.js +28 -0
- package/dist/runtime/native-install.d.ts +21 -0
- package/dist/runtime/native-install.js +130 -0
- package/dist/runtime/native-ipc.d.ts +26 -0
- package/dist/runtime/native-ipc.js +101 -0
- package/dist/runtime/native-pi-extension.d.ts +20 -0
- package/dist/runtime/native-pi-extension.js +47 -0
- package/dist/runtime/native-sync-store.d.ts +97 -0
- package/dist/runtime/native-sync-store.js +365 -0
- package/dist/runtime/native-sync.d.ts +25 -0
- package/dist/runtime/native-sync.js +128 -0
- package/dist/runtime/native-transcript.d.ts +27 -0
- package/dist/runtime/native-transcript.js +281 -0
- package/dist/runtime/presentation.d.ts +21 -0
- package/dist/runtime/presentation.js +78 -0
- package/dist/runtime/process-group.d.ts +2 -0
- package/dist/runtime/process-group.js +124 -31
- package/dist/runtime/session-store.d.ts +17 -2
- package/dist/runtime/session-store.js +118 -9
- package/dist/runtime/space-binding.d.ts +3 -0
- package/dist/runtime/space-binding.js +43 -6
- package/dist/runtime/supervisor.d.ts +16 -0
- package/dist/runtime/supervisor.js +277 -0
- package/dist/runtime/worker.d.ts +1 -0
- package/dist/runtime/worker.js +20 -0
- package/package.json +3 -2
package/dist/commands/runtime.js
CHANGED
|
@@ -1,328 +1,204 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { stat } from "node:fs/promises";
|
|
4
|
-
import { basename, resolve } from "node:path";
|
|
1
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
5
2
|
import { createInterface } from "node:readline/promises";
|
|
6
|
-
import { isLocalHarness, resolveCohubEnvironment, resolveWebsocketUrl } from "@neta-art/cohub";
|
|
7
|
-
import { requireAccessToken } from "../auth.js";
|
|
8
3
|
import { createClient } from "../client.js";
|
|
9
|
-
import {
|
|
10
|
-
import { currentIdentityKey
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
4
|
+
import { json as outJson, jsonRequested } from "../output.js";
|
|
5
|
+
import { currentIdentityKey } from "../space.js";
|
|
6
|
+
import { resolveRuntimeTarget, runtimeUp, parseRuntimeHarnesses } from "../runtime/launch.js";
|
|
7
|
+
import { canonicalRuntimeRoot, getRuntimeSpaceBinding } from "../runtime/space-binding.js";
|
|
8
|
+
import { installNativeSync } from "../runtime/native-install.js";
|
|
9
|
+
import { listNativeSyncStores } from "../runtime/native-sync-store.js";
|
|
10
|
+
import { requestRuntimeInstance, runtimeInstanceDirectory } from "../runtime/instance.js";
|
|
11
|
+
import { atLeastLevel, diagnosticLevels, formatDiagnostic, printRuntimeSummary } from "../runtime/presentation.js";
|
|
14
12
|
import { RuntimeSessionStore } from "../runtime/session-store.js";
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return "error";
|
|
22
|
-
if (level.includes("warn"))
|
|
23
|
-
return "warn";
|
|
24
|
-
return stream === "stderr" ? "error" : "debug";
|
|
25
|
-
}
|
|
26
|
-
function captureSandboxOutput(stream, streamName, diagnostics) {
|
|
27
|
-
if (!stream)
|
|
28
|
-
return;
|
|
29
|
-
let pending = "";
|
|
30
|
-
const consume = (chunk) => {
|
|
31
|
-
pending += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
32
|
-
let newline = pending.indexOf("\n");
|
|
33
|
-
while (newline >= 0) {
|
|
34
|
-
const line = pending.slice(0, newline).trim();
|
|
35
|
-
pending = pending.slice(newline + 1);
|
|
36
|
-
if (line)
|
|
37
|
-
recordSandboxOutput(line, streamName, diagnostics);
|
|
38
|
-
newline = pending.indexOf("\n");
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
stream.on("data", consume);
|
|
42
|
-
stream.on("end", () => {
|
|
43
|
-
if (pending.trim())
|
|
44
|
-
recordSandboxOutput(pending.trim(), streamName, diagnostics);
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
function recordSandboxOutput(line, streamName, diagnostics) {
|
|
48
|
-
let parsed = null;
|
|
49
|
-
try {
|
|
50
|
-
const value = JSON.parse(line);
|
|
51
|
-
if (value && typeof value === "object" && !Array.isArray(value))
|
|
52
|
-
parsed = value;
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
// Older or third-party binaries may still emit text logs.
|
|
56
|
-
}
|
|
57
|
-
const level = sandboxOutputLevel(parsed?.level, streamName);
|
|
58
|
-
const message = typeof parsed?.msg === "string" ? parsed.msg : line;
|
|
59
|
-
const data = parsed
|
|
60
|
-
? Object.fromEntries(Object.entries(parsed).filter(([key]) => !["msg", "level", "time"].includes(key)))
|
|
61
|
-
: { message: line };
|
|
62
|
-
diagnostics.log(level, "sandboxd.log", {
|
|
63
|
-
stream: streamName,
|
|
64
|
-
message,
|
|
65
|
-
...(level === "error" ? { error: { message } } : {}),
|
|
66
|
-
...data,
|
|
67
|
-
}, { component: "sandboxd" });
|
|
68
|
-
}
|
|
69
|
-
function printDiagnostic(event) {
|
|
70
|
-
const scope = [event.component, event.event].filter(Boolean).join(".");
|
|
71
|
-
const context = [
|
|
72
|
-
event.connectionId && `connection=${event.connectionId}`,
|
|
73
|
-
event.sessionId && `session=${event.sessionId}`,
|
|
74
|
-
event.turnId && `turn=${event.turnId}`,
|
|
75
|
-
event.traceContext?.requestId && `request=${event.traceContext.requestId}`,
|
|
76
|
-
event.traceContext?.traceId && `trace=${event.traceContext.traceId}`,
|
|
77
|
-
].filter(Boolean).join(" ");
|
|
78
|
-
const data = event.data && Object.keys(event.data).length > 0 ? ` ${JSON.stringify(event.data)}` : "";
|
|
79
|
-
process.stdout.write(`${event.timestamp} ${event.level.toUpperCase().padEnd(5)} ${scope}${context ? ` ${context}` : ""}${data}${event.error ? ` ${JSON.stringify(event.error)}` : ""}\n`);
|
|
80
|
-
}
|
|
81
|
-
export function parseRuntimeHarnesses(values) {
|
|
82
|
-
const names = values.flatMap((value) => value.split(",")).map((name) => name.trim()).filter(Boolean);
|
|
83
|
-
if (names.some((name) => !isLocalHarness(name)))
|
|
84
|
-
throw new Error("Harness must be pi or codex");
|
|
85
|
-
return [...new Set(names.length ? names : ["pi"])];
|
|
86
|
-
}
|
|
13
|
+
import { readRuntimeDiagnosticEvents, RuntimeDiagnosticReader, runtimeDiagnosticsDirectory, serializeDiagnosticError } from "../runtime/diagnostics.js";
|
|
14
|
+
export { resolveLocalSpaceName, parseRuntimeHarnesses } from "../runtime/launch.js";
|
|
15
|
+
const reportFailure = (cause) => {
|
|
16
|
+
process.stderr.write(`Runtime failed: ${serializeDiagnosticError(cause).message}\n`);
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
};
|
|
87
19
|
export function registerRuntime(program) {
|
|
88
20
|
const runtime = program.command("runtime").description("Connect a local workspace");
|
|
89
21
|
runtime.command("up [dir]")
|
|
90
22
|
.description("Connect local Harnesses and files")
|
|
91
23
|
.option("-s, --space <id>", "Target Space")
|
|
92
|
-
.option("-n, --
|
|
24
|
+
.option("-n, --new", "Create a new Space")
|
|
25
|
+
.option("--name <name>", "New Space name")
|
|
26
|
+
.option("-d, --detach", "Run in the background")
|
|
93
27
|
.option("--harness <name>", "Pi or Codex; repeatable", (value, previous) => [...previous, value], [])
|
|
94
28
|
.option("--pi <path>", "Pi executable")
|
|
95
29
|
.option("--codex <path>", "Codex executable")
|
|
96
|
-
.option("-y, --yes", "Accept local execution
|
|
30
|
+
.option("-y, --yes", "Accept defaults and authorize local execution")
|
|
31
|
+
.option("--verbose", "Show diagnostic details")
|
|
97
32
|
.option("--json", "JSON output")
|
|
98
33
|
.action(async (dir, options) => {
|
|
99
|
-
const controller = new AbortController();
|
|
100
|
-
const stop = () => controller.abort();
|
|
101
|
-
process.once("SIGINT", stop);
|
|
102
|
-
process.once("SIGTERM", stop);
|
|
103
34
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
const capabilities = await discoverHarnesses(harnesses, options, root);
|
|
123
|
-
const client = createClient();
|
|
124
|
-
const requested = options.space?.trim() || explicitSpace(program);
|
|
125
|
-
const validateLocalRuntime = async (spaceId) => {
|
|
126
|
-
const sandbox = (await client.space(spaceId).sandbox.get()).sandbox;
|
|
127
|
-
if (sandbox?.provider !== "local")
|
|
128
|
-
throw new Error("Space does not have a local Runtime");
|
|
129
|
-
};
|
|
130
|
-
const { spaceId } = await resolveRuntimeSpace({
|
|
131
|
-
root,
|
|
132
|
-
identityKey: currentIdentityKey(),
|
|
133
|
-
explicitSpaceId: requested,
|
|
134
|
-
createSpace: async () => (await client.spaces.create({
|
|
135
|
-
name: resolveLocalSpaceName(root, options.name),
|
|
136
|
-
config: { sandbox: { provider: "local" } },
|
|
137
|
-
})).space.id,
|
|
138
|
-
validateSpace: validateLocalRuntime,
|
|
139
|
-
});
|
|
140
|
-
const spaceClient = client.space(spaceId);
|
|
141
|
-
const store = new RuntimeSessionStore(spaceId, { projectionSource: spaceClient });
|
|
142
|
-
const runtimeId = randomUUID();
|
|
143
|
-
const diagnostics = new RuntimeDiagnostics({ root: store.root, spaceId, runtimeId });
|
|
144
|
-
store.setDiagnostics(diagnostics);
|
|
145
|
-
diagnostics.log("info", "runtime.cli_started", {
|
|
146
|
-
platform: process.platform,
|
|
147
|
-
arch: process.arch,
|
|
148
|
-
node: process.versions.node,
|
|
149
|
-
harnesses,
|
|
150
|
-
proxyConfigured: ["HTTPS_PROXY", "HTTP_PROXY", "ALL_PROXY"].some((key) => Boolean(process.env[key]?.trim())),
|
|
151
|
-
});
|
|
35
|
+
await runtimeUp(program, dir, { ...options, json: jsonRequested(options) });
|
|
36
|
+
}
|
|
37
|
+
catch (cause) {
|
|
38
|
+
reportFailure(cause);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
for (const action of ["attach", "detach"])
|
|
42
|
+
runtime.command(action)
|
|
43
|
+
.description(action === "attach" ? "Sync native Pi / Codex Turns" : "Pause native sync; retain all receipts")
|
|
44
|
+
.option("-s, --space <id>", "Target Space")
|
|
45
|
+
.option("--harness <name>", "Pi or Codex; repeatable", (value, previous) => [...previous, value], [])
|
|
46
|
+
.option("--pi <path>", "Pi executable for capability checks")
|
|
47
|
+
.option("--codex <path>", "Codex executable for capability checks")
|
|
48
|
+
.option("-y, --yes", "Authorize project conversation and native archive uploads")
|
|
49
|
+
.option("--json", "JSON output")
|
|
50
|
+
.action(async (options) => {
|
|
152
51
|
try {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
bridge = spawn(binary, ["--local", "--space", spaceId, "--root", root, "--relay", process.env.COHUB_RELAY_URL?.trim() || relay.toString()], {
|
|
180
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
181
|
-
env: {
|
|
182
|
-
...process.env,
|
|
183
|
-
COHUB_RELAY_TOKEN: token,
|
|
184
|
-
COHUB_RUNTIME_ID: runtimeId,
|
|
185
|
-
COHUB_LOG_FORMAT: "json",
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
captureSandboxOutput(bridge.stdout, "stdout", diagnostics);
|
|
189
|
-
captureSandboxOutput(bridge.stderr, "stderr", diagnostics);
|
|
190
|
-
bridgeClosed = new Promise((resolveClosed) => bridge?.once("close", () => resolveClosed()));
|
|
191
|
-
bridge.on("error", (cause) => {
|
|
192
|
-
diagnostics.log("error", "sandboxd.process_error", { error: serializeDiagnosticError(cause) }, { component: "sandboxd" });
|
|
193
|
-
console.error(cause);
|
|
194
|
-
controller.abort();
|
|
195
|
-
});
|
|
196
|
-
bridge.once("exit", (code, signal) => {
|
|
197
|
-
diagnostics.log(code === 0 ? "info" : "error", "sandboxd.process_exit", { code, signal }, { component: "sandboxd" });
|
|
198
|
-
controller.abort();
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
if (announced)
|
|
202
|
-
return;
|
|
203
|
-
announced = true;
|
|
204
|
-
const webUrl = `${resolveCohubEnvironment() === "prod" ? "https://cohub.live" : "https://dev.cohub.live"}/spaces/${spaceId}`;
|
|
205
|
-
if (jsonRequested(options))
|
|
206
|
-
outJson({ spaceId, root, harnesses, runtimeId, diagnosticsPath: diagnostics.logPath, url: webUrl });
|
|
207
|
-
else
|
|
208
|
-
console.error(`Runtime connected: ${webUrl} (runtimeId=${runtimeId}, logs=${diagnostics.logPath})`);
|
|
209
|
-
},
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
finally {
|
|
213
|
-
if (bridge) {
|
|
214
|
-
const child = bridge;
|
|
215
|
-
child.kill("SIGTERM");
|
|
216
|
-
const timeout = setTimeout(() => child.kill("SIGKILL"), 3000);
|
|
217
|
-
await bridgeClosed;
|
|
218
|
-
clearTimeout(timeout);
|
|
52
|
+
const spaceId = await resolveRuntimeTarget(program, options.space);
|
|
53
|
+
const identity = currentIdentityKey();
|
|
54
|
+
if (!identity)
|
|
55
|
+
throw new Error("Sign in first");
|
|
56
|
+
const root = await canonicalRuntimeRoot(process.cwd());
|
|
57
|
+
if (action === "attach" && (await getRuntimeSpaceBinding(root, identity))?.spaceId !== spaceId)
|
|
58
|
+
throw new Error("Bind this directory with runtime up --space first");
|
|
59
|
+
const instance = await requestRuntimeInstance(runtimeInstanceDirectory(identity, spaceId));
|
|
60
|
+
if (action === "attach" && (!instance || instance.root !== root))
|
|
61
|
+
throw new Error("Start this directory's Runtime first: cohub runtime up -d");
|
|
62
|
+
if (action === "attach" && !instance?.nativeSync)
|
|
63
|
+
throw new Error("Restart the Runtime with this CLI before attaching");
|
|
64
|
+
const harnesses = parseRuntimeHarnesses(options.harness.length ? options.harness : instance?.harnesses ?? ["pi", "codex"]);
|
|
65
|
+
if (action === "attach" && harnesses.some((harness) => !instance?.harnesses.includes(harness)))
|
|
66
|
+
throw new Error("Enable these Harnesses with runtime up first");
|
|
67
|
+
if (action === "attach" && !options.yes) {
|
|
68
|
+
if (!process.stdin.isTTY)
|
|
69
|
+
throw new Error("Use --yes to authorize native sync");
|
|
70
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
71
|
+
try {
|
|
72
|
+
const answer = await rl.question(`Install user-level ${harnesses.join(" / ")} integration and upload this project's opened conversations, tool output and raw archives to this Space? History may contain secrets. [y/N] `);
|
|
73
|
+
if (!/^y(es)?$/i.test(answer.trim()))
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
rl.close();
|
|
219
78
|
}
|
|
220
79
|
}
|
|
80
|
+
const result = await installNativeSync({ root, spaceId, identity, harnesses, disabled: action === "detach", executables: { pi: options.pi, codex: options.codex } });
|
|
81
|
+
if (jsonRequested(options))
|
|
82
|
+
outJson(result);
|
|
83
|
+
else
|
|
84
|
+
process.stdout.write(action === "attach"
|
|
85
|
+
? `Native sync enabled. Reload Pi or restart Codex and review its hook trust prompt.\n${result.configPath}\n`
|
|
86
|
+
: "Native sync paused; all local records retained\n");
|
|
221
87
|
}
|
|
222
88
|
catch (cause) {
|
|
223
|
-
|
|
224
|
-
throw cause;
|
|
89
|
+
reportFailure(cause);
|
|
225
90
|
}
|
|
226
|
-
|
|
227
|
-
|
|
91
|
+
});
|
|
92
|
+
runtime.command("status").description("Local and server status")
|
|
93
|
+
.option("-s, --space <id>", "Target Space")
|
|
94
|
+
.option("--json", "JSON output")
|
|
95
|
+
.action(async (options) => {
|
|
96
|
+
try {
|
|
97
|
+
const spaceId = await resolveRuntimeTarget(program, options.space);
|
|
98
|
+
const identity = currentIdentityKey();
|
|
99
|
+
const local = identity ? await requestRuntimeInstance(runtimeInstanceDirectory(identity, spaceId)) : null;
|
|
100
|
+
const space = createClient().space(spaceId);
|
|
101
|
+
const store = new RuntimeSessionStore(spaceId, { projectionSource: space, archiveTransport: null });
|
|
102
|
+
const [remote, pendingLocalArchives, failedLocalArchives, nativeStores] = await Promise.all([
|
|
103
|
+
space.getRuntime(undefined, { signal: AbortSignal.timeout(5000) }).then((value) => ({ value, error: null })).catch((error) => ({ value: null, error: serializeDiagnosticError(error).message })),
|
|
104
|
+
store.archives.pendingCount(), store.archives.failedCaptureCount(),
|
|
105
|
+
identity ? listNativeSyncStores(store.root, spaceId, identity) : [],
|
|
106
|
+
]);
|
|
107
|
+
const nativeSessions = await Promise.all(nativeStores.map((native) => native.status()));
|
|
108
|
+
const result = { ...remote.value, spaceId, local, remote: remote.value, remoteError: remote.error, diagnosticsPath: runtimeDiagnosticsDirectory(store.root), pendingLocalArchives, failedLocalArchives, nativeSessions };
|
|
109
|
+
if (jsonRequested(options))
|
|
110
|
+
outJson(result);
|
|
111
|
+
else {
|
|
112
|
+
if (local)
|
|
113
|
+
printRuntimeSummary(local);
|
|
114
|
+
else
|
|
115
|
+
process.stdout.write(`Local process Not running\nSpace ${spaceId}\nLogs ${result.diagnosticsPath}\n`);
|
|
116
|
+
process.stdout.write(`Server ${remote.error ? `Unknown — ${remote.error}` : remote.value?.online ? "Harness connected" : "Offline"}\nArchives ${pendingLocalArchives} pending · ${failedLocalArchives} failed\n`);
|
|
117
|
+
if (nativeSessions.length)
|
|
118
|
+
process.stdout.write(`Native chats ${nativeSessions.length} · ${nativeSessions.reduce((sum, session) => sum + session.pendingTurns, 0)} Turns pending · ${nativeSessions.reduce((sum, session) => sum + session.pendingArchives, 0)} archives pending\n`);
|
|
228
119
|
}
|
|
229
120
|
}
|
|
230
121
|
catch (cause) {
|
|
231
|
-
|
|
232
|
-
error("Runtime failed", cause instanceof Error ? cause.message : String(cause));
|
|
233
|
-
}
|
|
234
|
-
finally {
|
|
235
|
-
process.removeListener("SIGINT", stop);
|
|
236
|
-
process.removeListener("SIGTERM", stop);
|
|
122
|
+
reportFailure(cause);
|
|
237
123
|
}
|
|
238
124
|
});
|
|
239
|
-
runtime.command("
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
125
|
+
runtime.command("down").description("Stop this local Runtime; retain all data")
|
|
126
|
+
.option("-s, --space <id>", "Target Space")
|
|
127
|
+
.option("-y, --yes", "Stop even with unconfirmed executions")
|
|
128
|
+
.option("--json", "JSON output")
|
|
129
|
+
.action(async (options) => {
|
|
130
|
+
try {
|
|
131
|
+
const spaceId = await resolveRuntimeTarget(program, options.space);
|
|
132
|
+
const identity = currentIdentityKey();
|
|
133
|
+
if (!identity)
|
|
134
|
+
throw new Error("Sign in to the Runtime account");
|
|
135
|
+
const directory = runtimeInstanceDirectory(identity, spaceId);
|
|
136
|
+
const local = await requestRuntimeInstance(directory, "stop", Boolean(options.yes));
|
|
137
|
+
const until = Date.now() + 15_000;
|
|
138
|
+
let running = Boolean(local);
|
|
139
|
+
while (running && Date.now() < until) {
|
|
140
|
+
await delay(250);
|
|
141
|
+
try {
|
|
142
|
+
running = Boolean(await requestRuntimeInstance(directory));
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
running = true;
|
|
146
|
+
} // An unreachable control socket does not prove the process stopped.
|
|
147
|
+
}
|
|
148
|
+
if (running)
|
|
149
|
+
throw new Error("Runtime is still stopping; inspect logs");
|
|
150
|
+
if (jsonRequested(options))
|
|
151
|
+
outJson({ spaceId, stopped: true });
|
|
152
|
+
else
|
|
153
|
+
process.stdout.write("Runtime stopped; data retained\n");
|
|
154
|
+
}
|
|
155
|
+
catch (cause) {
|
|
156
|
+
reportFailure(cause);
|
|
157
|
+
}
|
|
255
158
|
});
|
|
256
|
-
runtime.command("logs")
|
|
257
|
-
.description("Read local Runtime diagnostics")
|
|
159
|
+
runtime.command("logs").description("Read local Runtime diagnostics")
|
|
258
160
|
.option("-s, --space <id>", "Target Space")
|
|
259
161
|
.option("-l, --limit <count>", "Number of events", "100")
|
|
260
|
-
.option("--
|
|
261
|
-
.option("--
|
|
162
|
+
.option("--level <level>", "Minimum level: debug, info, warn, error", "info")
|
|
163
|
+
.option("-f, --follow", "Keep watching")
|
|
164
|
+
.option("--json", "Raw diagnostic events")
|
|
262
165
|
.action(async (options) => {
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
const limit = Number(options.limit ?? "100");
|
|
266
|
-
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 10_000)
|
|
267
|
-
return error("Invalid diagnostic limit", "Use an integer between 1 and 10000 / 使用 1 到 10000 之间的整数");
|
|
268
|
-
const asJson = jsonRequested(options);
|
|
269
|
-
const reader = options.follow ? new RuntimeDiagnosticReader(store.root) : null;
|
|
270
|
-
const render = async () => {
|
|
271
|
-
const fresh = options.follow
|
|
272
|
-
? await reader?.read({ limit }) ?? []
|
|
273
|
-
: await readRuntimeDiagnosticEvents(store.root, { limit });
|
|
274
|
-
if (options.follow && fresh.length === 0)
|
|
275
|
-
return;
|
|
276
|
-
if (asJson && options.follow) {
|
|
277
|
-
for (const event of fresh)
|
|
278
|
-
process.stdout.write(`${JSON.stringify(event)}\n`);
|
|
279
|
-
}
|
|
280
|
-
else if (asJson) {
|
|
281
|
-
outJson(fresh);
|
|
282
|
-
}
|
|
283
|
-
else if (fresh.length === 0) {
|
|
284
|
-
process.stdout.write("No Runtime diagnostics / 未找到 Runtime 诊断记录\n");
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
for (const event of fresh)
|
|
288
|
-
printDiagnostic(event);
|
|
289
|
-
}
|
|
290
|
-
};
|
|
166
|
+
const controller = new AbortController();
|
|
167
|
+
const stop = () => controller.abort();
|
|
291
168
|
try {
|
|
292
|
-
await
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
};
|
|
319
|
-
timer = setTimeout(() => void poll(), 2_000);
|
|
320
|
-
process.once("SIGINT", stop);
|
|
321
|
-
process.once("SIGTERM", stop);
|
|
322
|
-
});
|
|
169
|
+
const spaceId = await resolveRuntimeTarget(program, options.space);
|
|
170
|
+
const store = new RuntimeSessionStore(spaceId, { projectionSource: createClient().space(spaceId), archiveTransport: null });
|
|
171
|
+
const limit = Number(options.limit);
|
|
172
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 10_000)
|
|
173
|
+
throw new Error("Use a limit from 1 to 10000");
|
|
174
|
+
if (!diagnosticLevels.includes(options.level))
|
|
175
|
+
throw new Error("Use debug, info, warn or error");
|
|
176
|
+
const asJson = jsonRequested(options);
|
|
177
|
+
const reader = new RuntimeDiagnosticReader(store.root);
|
|
178
|
+
process.once("SIGINT", stop);
|
|
179
|
+
process.once("SIGTERM", stop);
|
|
180
|
+
do {
|
|
181
|
+
const events = (options.follow ? await reader.read({ limit }) : await readRuntimeDiagnosticEvents(store.root, { limit }))
|
|
182
|
+
.filter((event) => atLeastLevel(event.level, options.level));
|
|
183
|
+
if (asJson && !options.follow)
|
|
184
|
+
outJson(events);
|
|
185
|
+
else
|
|
186
|
+
for (const event of events)
|
|
187
|
+
process.stdout.write(asJson ? `${JSON.stringify(event)}\n` : formatDiagnostic(event, true));
|
|
188
|
+
if (!options.follow) {
|
|
189
|
+
if (!asJson && !events.length)
|
|
190
|
+
process.stdout.write("No matching diagnostics\n");
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
await delay(1000, undefined, { signal: controller.signal }).catch(() => undefined);
|
|
194
|
+
} while (!controller.signal.aborted);
|
|
323
195
|
}
|
|
324
196
|
catch (cause) {
|
|
325
|
-
|
|
197
|
+
reportFailure(cause);
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
process.removeListener("SIGINT", stop);
|
|
201
|
+
process.removeListener("SIGTERM", stop);
|
|
326
202
|
}
|
|
327
203
|
});
|
|
328
204
|
}
|
|
@@ -15,11 +15,19 @@ import { Readable } from "node:stream";
|
|
|
15
15
|
// that tag's publish-cdn job has succeeded, otherwise `runtime up` 404s on the
|
|
16
16
|
// default download.
|
|
17
17
|
//
|
|
18
|
-
// v2.
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
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. No new capability is required, so this
|
|
29
|
+
// pin is a diagnostics upgrade only.
|
|
30
|
+
export const SANDBOXD_VERSION = "v2.54.1";
|
|
23
31
|
const BINARY_NAME = "cohub-sandboxd";
|
|
24
32
|
// Public CDN prefix hosting the release archives (the repo is private, so the
|
|
25
33
|
// GitHub Release assets are not publicly downloadable). Overridable for staging
|
|
@@ -24,7 +24,9 @@ export declare class RuntimeArchiveStore {
|
|
|
24
24
|
private flushing;
|
|
25
25
|
private readonly capturing;
|
|
26
26
|
private errorReporter;
|
|
27
|
-
constructor(root: string, transport?: ArchiveTransport | undefined);
|
|
27
|
+
constructor(root: string, transport?: ArchiveTransport | null | undefined);
|
|
28
|
+
/** Distinguishes "no transport configured" from transient upload failures. */
|
|
29
|
+
get hasTransport(): boolean;
|
|
28
30
|
setErrorReporter(reporter: ((error: unknown, index?: HarnessArchiveIndex) => void) | null): void;
|
|
29
31
|
pendingCount(): Promise<number>;
|
|
30
32
|
failedCaptureCount(): Promise<number>;
|
|
@@ -38,6 +40,8 @@ export declare class RuntimeArchiveStore {
|
|
|
38
40
|
harness: "pi" | "codex";
|
|
39
41
|
nativeSessionId: string;
|
|
40
42
|
path: string;
|
|
43
|
+
sizeBytes?: number;
|
|
44
|
+
expectedChecksum?: string;
|
|
41
45
|
}, turnId: string): Promise<HarnessArchive>;
|
|
42
46
|
private capture;
|
|
43
47
|
flush(signal: AbortSignal): Promise<void>;
|