@neta-art/cohub-cli 8.2.0 → 8.3.0
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/dist/commands/runtime.d.ts +0 -12
- package/dist/commands/runtime.js +129 -144
- package/dist/commands/search.js +3 -9
- package/dist/commands/spaces.js +5 -2
- package/dist/output.d.ts +6 -0
- package/dist/output.js +11 -0
- package/dist/runtime/codex-usage.d.ts +0 -2
- package/dist/runtime/codex-usage.js +0 -12
- package/dist/runtime/connection.d.ts +5 -5
- package/dist/runtime/connection.js +27 -16
- package/dist/runtime/harness.d.ts +8 -16
- package/dist/runtime/harness.js +1 -366
- package/dist/runtime/instance.d.ts +8 -1
- package/dist/runtime/instance.js +15 -8
- package/dist/runtime/launch.js +10 -5
- package/dist/runtime/native/adapters.d.ts +32 -0
- package/dist/runtime/native/adapters.js +79 -0
- package/dist/runtime/{native-archive.d.ts → native/archive.d.ts} +0 -3
- package/dist/runtime/{native-archive.js → native/archive.js} +2 -17
- package/dist/runtime/native/attach.d.ts +33 -0
- package/dist/runtime/native/attach.js +103 -0
- package/dist/runtime/native/codex-link.d.ts +40 -0
- package/dist/runtime/native/codex-link.js +249 -0
- package/dist/runtime/native/config.d.ts +16 -0
- package/dist/runtime/native/config.js +24 -0
- package/dist/runtime/native/daemon.d.ts +88 -0
- package/dist/runtime/native/daemon.js +367 -0
- package/dist/runtime/native/execution.d.ts +45 -0
- package/dist/runtime/native/execution.js +337 -0
- package/dist/runtime/native/identity.d.ts +9 -0
- package/dist/runtime/native/identity.js +15 -0
- package/dist/runtime/native/ingest.d.ts +241 -0
- package/dist/runtime/native/ingest.js +823 -0
- package/dist/runtime/native/install.d.ts +9 -0
- package/dist/runtime/native/install.js +105 -0
- package/dist/runtime/native/pi-extension.d.ts +35 -0
- package/dist/runtime/native/pi-extension.js +160 -0
- package/dist/runtime/native/pi-link.d.ts +67 -0
- package/dist/runtime/native/pi-link.js +238 -0
- package/dist/runtime/native/results.d.ts +41 -0
- package/dist/runtime/native/results.js +128 -0
- package/dist/runtime/native/sessions.d.ts +45 -0
- package/dist/runtime/native/sessions.js +92 -0
- package/dist/runtime/{native-transcript.d.ts → native/transcript.d.ts} +5 -5
- package/dist/runtime/{native-transcript.js → native/transcript.js} +37 -11
- package/dist/runtime/native/translate.d.ts +44 -0
- package/dist/runtime/native/translate.js +263 -0
- package/dist/runtime/native/transport.d.ts +7 -0
- package/dist/runtime/native/transport.js +16 -0
- package/dist/runtime/native/version.d.ts +5 -0
- package/dist/runtime/native/version.js +18 -0
- package/dist/runtime/presentation.d.ts +7 -10
- package/dist/runtime/presentation.js +35 -10
- package/dist/runtime/projection-store.d.ts +6 -13
- package/dist/runtime/projection-store.js +11 -74
- package/dist/runtime/supervisor.d.ts +2 -0
- package/dist/runtime/supervisor.js +36 -110
- package/dist/runtime/turn-projection.d.ts +1 -3
- package/dist/runtime/turn-projection.js +3 -10
- package/package.json +4 -5
- package/dist/runtime/native-attach.d.ts +0 -28
- package/dist/runtime/native-attach.js +0 -47
- package/dist/runtime/native-codex-hook.d.ts +0 -1
- package/dist/runtime/native-codex-hook.js +0 -28
- package/dist/runtime/native-install.d.ts +0 -21
- package/dist/runtime/native-install.js +0 -130
- package/dist/runtime/native-ipc.d.ts +0 -37
- package/dist/runtime/native-ipc.js +0 -118
- package/dist/runtime/native-pi-extension.d.ts +0 -20
- package/dist/runtime/native-pi-extension.js +0 -47
- package/dist/runtime/native-sync-store.d.ts +0 -116
- package/dist/runtime/native-sync-store.js +0 -495
- package/dist/runtime/native-sync.d.ts +0 -65
- package/dist/runtime/native-sync.js +0 -326
- package/dist/runtime/session-store.d.ts +0 -84
- package/dist/runtime/session-store.js +0 -601
|
@@ -1,15 +1,3 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
-
import { type NativeIpcResponse } from "../runtime/native-ipc.js";
|
|
3
2
|
export { resolveLocalSpaceName, parseRuntimeHarnesses } from "../runtime/launch.js";
|
|
4
|
-
export declare function applyNativeImportResponse(result: {
|
|
5
|
-
imported: number;
|
|
6
|
-
failed: Array<{
|
|
7
|
-
path: string;
|
|
8
|
-
message: string;
|
|
9
|
-
}>;
|
|
10
|
-
skipped: Array<{
|
|
11
|
-
path: string;
|
|
12
|
-
message: string;
|
|
13
|
-
}>;
|
|
14
|
-
}, path: string, response: NativeIpcResponse): void;
|
|
15
3
|
export declare function registerRuntime(program: Command): void;
|
package/dist/commands/runtime.js
CHANGED
|
@@ -4,28 +4,31 @@ import { json as outJson, jsonRequested } from "../output.js";
|
|
|
4
4
|
import { currentIdentityKey } from "../space.js";
|
|
5
5
|
import { resolveRuntimeTarget, runtimeUp, parseRuntimeHarnesses } from "../runtime/launch.js";
|
|
6
6
|
import { canonicalRuntimeRoot, getRuntimeSpaceBinding } from "../runtime/space-binding.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { RuntimeArchiveStore } from "../runtime/archive-store.js";
|
|
9
|
+
import { readNativeConfig, runtimeStateRoot, writeNativeConfig } from "../runtime/native/config.js";
|
|
10
|
+
import { MAX_IMPORT_CONCURRENCY, DEFAULT_IMPORT_CONCURRENCY } from "../runtime/native/ingest.js";
|
|
11
|
+
import { installPiExtension, piExtensionState } from "../runtime/native/install.js";
|
|
12
|
+
import { controlRuntimeInstance, requestRuntimeInstance, runtimeInstanceDirectory } from "../runtime/instance.js";
|
|
13
|
+
import { atLeastLevel, diagnosticLevels, formatDiagnostic, formatImportProgress, formatNativeSync, printRuntimeSummary } from "../runtime/presentation.js";
|
|
14
14
|
import { readRuntimeDiagnosticEvents, RuntimeDiagnosticReader, runtimeDiagnosticsDirectory, serializeDiagnosticError } from "../runtime/diagnostics.js";
|
|
15
15
|
export { resolveLocalSpaceName, parseRuntimeHarnesses } from "../runtime/launch.js";
|
|
16
16
|
const reportFailure = (cause) => {
|
|
17
17
|
process.stderr.write(`Runtime failed: ${serializeDiagnosticError(cause).message}\n`);
|
|
18
18
|
process.exitCode = 1;
|
|
19
19
|
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
const megabytes = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
21
|
+
/** The bound Space of a directory, with its running Runtime; import and attach act through it. */
|
|
22
|
+
async function boundRuntime(program, dir, space) {
|
|
23
|
+
const identity = currentIdentityKey();
|
|
24
|
+
if (!identity)
|
|
25
|
+
throw new Error("Sign in first");
|
|
26
|
+
const root = await canonicalRuntimeRoot(dir ?? process.cwd());
|
|
27
|
+
const spaceId = await resolveRuntimeTarget(program, space);
|
|
28
|
+
const binding = await getRuntimeSpaceBinding(root, identity);
|
|
29
|
+
if (!binding || binding.spaceId !== spaceId)
|
|
30
|
+
throw new Error("Bind this directory with runtime up first");
|
|
31
|
+
return { identity, root, spaceId, directory: runtimeInstanceDirectory(identity, spaceId) };
|
|
29
32
|
}
|
|
30
33
|
export function registerRuntime(program) {
|
|
31
34
|
const runtime = program.command("runtime").description("Connect a local workspace");
|
|
@@ -49,37 +52,68 @@ export function registerRuntime(program) {
|
|
|
49
52
|
reportFailure(cause);
|
|
50
53
|
}
|
|
51
54
|
});
|
|
52
|
-
runtime.command("
|
|
53
|
-
.description("
|
|
55
|
+
runtime.command("attach")
|
|
56
|
+
.description("Connect native harnesses to Cohub: install the Pi extension")
|
|
54
57
|
.option("-s, --space <id>", "Target Space")
|
|
55
|
-
.option("--harness <name>", "Pi
|
|
58
|
+
.option("--harness <name>", "Pi; repeatable", (value, previous) => [...previous, value], [])
|
|
56
59
|
.option("--json", "JSON output")
|
|
57
60
|
.action(async (options) => {
|
|
58
61
|
try {
|
|
59
|
-
const
|
|
62
|
+
const harnesses = parseRuntimeHarnesses(options.harness.length ? options.harness : ["pi"]);
|
|
63
|
+
if (!harnesses.includes("pi"))
|
|
64
|
+
throw new Error("Codex needs no extension; runtime up offers its shared app-server");
|
|
65
|
+
const before = await piExtensionState();
|
|
66
|
+
if (before === "foreign")
|
|
67
|
+
throw new Error("A Pi extension named cohub exists and is not managed by Cohub; move it aside, then retry");
|
|
68
|
+
await installPiExtension();
|
|
69
|
+
const spaceId = await resolveRuntimeTarget(program, options.space).catch(() => null);
|
|
60
70
|
const identity = currentIdentityKey();
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
const root = await canonicalRuntimeRoot(process.cwd());
|
|
64
|
-
if ((await getRuntimeSpaceBinding(root, identity))?.spaceId !== spaceId)
|
|
65
|
-
throw new Error("Bind this directory with runtime up first");
|
|
66
|
-
const instance = await requestRuntimeInstance(runtimeInstanceDirectory(identity, spaceId));
|
|
67
|
-
const harnesses = parseRuntimeHarnesses(options.harness.length ? options.harness : instance?.harnesses ?? ["pi", "codex"]);
|
|
68
|
-
const result = await installNativeSync({ root, spaceId, identity, harnesses, disabled: true });
|
|
71
|
+
if (spaceId && identity)
|
|
72
|
+
await controlRuntimeInstance(runtimeInstanceDirectory(identity, spaceId), "reload").catch(() => undefined);
|
|
69
73
|
if (jsonRequested(options))
|
|
70
|
-
outJson({
|
|
74
|
+
outJson({ harness: "pi", extension: "installed", updated: before !== "installed" });
|
|
71
75
|
else
|
|
72
|
-
process.stdout.write("
|
|
76
|
+
process.stdout.write(before === "installed" ? "Pi extension is up to date\n" : "Pi extension installed. Run /reload in open Pi sessions to connect them\n");
|
|
77
|
+
}
|
|
78
|
+
catch (cause) {
|
|
79
|
+
reportFailure(cause);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
runtime.command("detach")
|
|
83
|
+
.description("Pause native sync; keep all data")
|
|
84
|
+
.option("-s, --space <id>", "Target Space")
|
|
85
|
+
.option("--harness <name>", "Pi or Codex; repeatable", (value, previous) => [...previous, value], [])
|
|
86
|
+
.option("--json", "JSON output")
|
|
87
|
+
.action(async (options) => {
|
|
88
|
+
try {
|
|
89
|
+
const { identity, spaceId, directory } = await boundRuntime(program, undefined, options.space);
|
|
90
|
+
const stateRoot = runtimeStateRoot(spaceId);
|
|
91
|
+
const config = await readNativeConfig(stateRoot, identity);
|
|
92
|
+
const paused = parseRuntimeHarnesses(options.harness.length ? options.harness : config?.harnesses ?? ["pi", "codex"]);
|
|
93
|
+
const harnesses = (config?.harnesses ?? []).filter((harness) => !paused.includes(harness));
|
|
94
|
+
if (config)
|
|
95
|
+
await writeNativeConfig(stateRoot, { ...config, harnesses, codexShared: harnesses.includes("codex") && config.codexShared });
|
|
96
|
+
await controlRuntimeInstance(directory, "reload").catch(() => undefined);
|
|
97
|
+
const codexWasShared = Boolean(config?.codexShared) && paused.includes("codex");
|
|
98
|
+
if (jsonRequested(options))
|
|
99
|
+
outJson({ spaceId, harnesses, paused, enabled: harnesses.length > 0 });
|
|
100
|
+
else {
|
|
101
|
+
process.stdout.write(harnesses.length ? `Native sync paused for ${paused.join(", ")}; still syncing ${harnesses.join(", ")}\n` : "Native sync paused; all data retained\n");
|
|
102
|
+
// Cohub never stops a server that terminal clients may be attached to.
|
|
103
|
+
if (codexWasShared)
|
|
104
|
+
process.stdout.write("Codex's shared app-server keeps running; stop it with codex app-server daemon stop\n");
|
|
105
|
+
}
|
|
73
106
|
}
|
|
74
107
|
catch (cause) {
|
|
75
108
|
reportFailure(cause);
|
|
76
109
|
}
|
|
77
110
|
});
|
|
78
111
|
runtime.command("import [dir]")
|
|
79
|
-
.description("Import local conversations")
|
|
112
|
+
.description("Import earlier local conversations, newest first")
|
|
80
113
|
.option("-s, --space <id>", "Target Space")
|
|
81
114
|
.option("--harness <name>", "Filter by harness; repeatable", (value, previous) => [...previous, value], [])
|
|
82
115
|
.option("--session <id>", "Filter by native session ID")
|
|
116
|
+
.option("--concurrency <count>", `Conversations read in parallel, 1 to ${MAX_IMPORT_CONCURRENCY}`, String(DEFAULT_IMPORT_CONCURRENCY))
|
|
83
117
|
.option("--dry-run", "Preview without importing")
|
|
84
118
|
.option("-y, --yes", "Skip confirmation")
|
|
85
119
|
.option("--json", "Output as JSON")
|
|
@@ -88,86 +122,46 @@ export function registerRuntime(program) {
|
|
|
88
122
|
const stop = () => controller.abort();
|
|
89
123
|
process.once("SIGINT", stop);
|
|
90
124
|
process.once("SIGTERM", stop);
|
|
125
|
+
const asJson = jsonRequested(options);
|
|
91
126
|
try {
|
|
92
|
-
const
|
|
93
|
-
if (!
|
|
94
|
-
throw new Error(
|
|
95
|
-
const root = await
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const config = await readNativeSyncConfig(nativeRuntimeRoot(spaceId), identity);
|
|
104
|
-
if (!config || config.root !== root)
|
|
105
|
-
throw new Error("Native sync is not enabled for this directory");
|
|
106
|
-
const harnesses = parseRuntimeHarnesses(options.harness.length ? options.harness : config.harnesses);
|
|
107
|
-
let lastProgress = 0;
|
|
108
|
-
const discovered = await discoverNativeImportCandidates(root, harnesses, {
|
|
109
|
-
signal: controller.signal,
|
|
110
|
-
onProgress: ({ harness, scanned, candidates: found }) => {
|
|
111
|
-
if (jsonRequested(options) || scanned - lastProgress < 25)
|
|
112
|
-
return;
|
|
113
|
-
lastProgress = scanned;
|
|
114
|
-
process.stderr.write(`Scanned ${scanned} ${harness} transcript${scanned === 1 ? "" : "s"}; found ${found}\n`);
|
|
115
|
-
},
|
|
116
|
-
});
|
|
117
|
-
const candidates = discovered.candidates.filter((candidate) => !options.session || candidate.nativeSessionId === options.session);
|
|
118
|
-
const superseded = discovered.superseded.filter((skip) => !options.session || skip.nativeSessionId === options.session);
|
|
119
|
-
const result = {
|
|
120
|
-
spaceId, root, harnesses, candidates, errors: discovered.errors,
|
|
121
|
-
imported: 0, pendingTurns: 0, failed: [], skipped: [], superseded, dryRun: Boolean(options.dryRun),
|
|
122
|
-
complete: discovered.errors.length === 0 && superseded.length === 0,
|
|
127
|
+
const concurrency = Number(options.concurrency);
|
|
128
|
+
if (!Number.isSafeInteger(concurrency) || concurrency < 1 || concurrency > MAX_IMPORT_CONCURRENCY)
|
|
129
|
+
throw new Error(`Use a concurrency from 1 to ${MAX_IMPORT_CONCURRENCY}`);
|
|
130
|
+
const { spaceId, root, directory } = await boundRuntime(program, dir, options.space);
|
|
131
|
+
// The Runtime does the work, so an import survives this command: Ctrl-C only pauses it.
|
|
132
|
+
const filter = { harnesses: options.harness.length ? parseRuntimeHarnesses(options.harness) : [], ...(options.session ? { nativeSessionId: options.session } : {}) };
|
|
133
|
+
const control = async (command, timeoutMs = 60_000) => {
|
|
134
|
+
const response = await controlRuntimeInstance(directory, "import", { ...filter, command, concurrency }, timeoutMs);
|
|
135
|
+
if (!response)
|
|
136
|
+
throw new Error("Local Runtime is not running; start it with cohub runtime up");
|
|
137
|
+
return response.result;
|
|
123
138
|
};
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (!candidates.length) {
|
|
132
|
-
if (!result.complete)
|
|
133
|
-
process.exitCode = 1;
|
|
134
|
-
if (jsonRequested(options))
|
|
135
|
-
outJson(result);
|
|
139
|
+
const plan = await control("plan");
|
|
140
|
+
const summary = { spaceId, root, ...plan, dryRun: Boolean(options.dryRun) };
|
|
141
|
+
if (options.dryRun || !plan.files) {
|
|
142
|
+
if (asJson)
|
|
143
|
+
outJson(summary);
|
|
144
|
+
else if (!plan.files)
|
|
145
|
+
process.stdout.write("No earlier conversations left to import\n");
|
|
136
146
|
else {
|
|
137
|
-
process.stdout.write("
|
|
138
|
-
for (const
|
|
139
|
-
process.stdout.write(`
|
|
140
|
-
|
|
141
|
-
process.stdout.write(`
|
|
147
|
+
process.stdout.write(`Found ${plan.files} conversation${plan.files === 1 ? "" : "s"} · ${megabytes(plan.bytes)}\n`);
|
|
148
|
+
for (const item of plan.sample)
|
|
149
|
+
process.stdout.write(` ${item.harness.padEnd(5)} ${new Date(item.mtimeMs).toISOString().slice(0, 16).replace("T", " ")} ${item.nativeSessionId ?? ""} ${item.path}\n`);
|
|
150
|
+
if (plan.files > plan.sample.length)
|
|
151
|
+
process.stdout.write(` … and ${plan.files - plan.sample.length} more\n`);
|
|
142
152
|
}
|
|
143
153
|
return;
|
|
144
154
|
}
|
|
145
|
-
if (options.dryRun) {
|
|
146
|
-
if (jsonRequested(options))
|
|
147
|
-
outJson(result);
|
|
148
|
-
else {
|
|
149
|
-
process.stdout.write(`Found ${candidates.length} native conversation${candidates.length === 1 ? "" : "s"}\n`);
|
|
150
|
-
for (const candidate of candidates)
|
|
151
|
-
process.stdout.write(` ${candidate.harness} ${candidate.nativeSessionId} ${candidate.turnCount} Turn${candidate.turnCount === 1 ? "" : "s"} ${candidate.path}\n`);
|
|
152
|
-
for (const error of discovered.errors)
|
|
153
|
-
process.stdout.write(`Skipped ${error.path}: ${error.message}\n`);
|
|
154
|
-
for (const skip of superseded)
|
|
155
|
-
process.stdout.write(`Superseded ${skip.path}: ${skip.message}\n`);
|
|
156
|
-
}
|
|
157
|
-
if (!result.complete)
|
|
158
|
-
process.exitCode = 1;
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
155
|
if (!options.yes) {
|
|
162
156
|
if (!process.stdin.isTTY)
|
|
163
157
|
throw new Error("Use --yes in non-interactive mode");
|
|
164
158
|
const { createInterface } = await import("node:readline/promises");
|
|
165
159
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
166
160
|
try {
|
|
167
|
-
const answer = await rl.question(`Import ${
|
|
161
|
+
const answer = await rl.question(`Import ${plan.files} conversation${plan.files === 1 ? "" : "s"} (${megabytes(plan.bytes)}) to Space ${spaceId}? [Y/n] `);
|
|
168
162
|
if (!/^(|y(es)?)$/i.test(answer.trim())) {
|
|
169
|
-
if (
|
|
170
|
-
outJson({ ...
|
|
163
|
+
if (asJson)
|
|
164
|
+
outJson({ ...summary, cancelled: true });
|
|
171
165
|
else
|
|
172
166
|
process.stdout.write("Import cancelled\n");
|
|
173
167
|
return;
|
|
@@ -177,43 +171,31 @@ export function registerRuntime(program) {
|
|
|
177
171
|
rl.close();
|
|
178
172
|
}
|
|
179
173
|
}
|
|
180
|
-
let
|
|
181
|
-
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
result.pendingTurns += response.pendingTurns;
|
|
190
|
-
}
|
|
191
|
-
catch (error) {
|
|
192
|
-
if (!controller.signal.aborted)
|
|
193
|
-
result.failed.push({ path: candidate.path, message: error instanceof Error ? error.message : String(error) });
|
|
194
|
-
}
|
|
195
|
-
finally {
|
|
196
|
-
processed += 1;
|
|
197
|
-
if (!jsonRequested(options) && !controller.signal.aborted) {
|
|
198
|
-
process.stderr.write(`\rSubmitted ${result.imported}/${candidates.length} · pending ${result.pendingTurns} Turns`);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
174
|
+
let job = await control("start");
|
|
175
|
+
while (!controller.signal.aborted && job.state === "running") {
|
|
176
|
+
if (!asJson && process.stderr.isTTY)
|
|
177
|
+
process.stderr.write(`\r\x1b[2KImporting ${formatImportProgress(job)}`);
|
|
178
|
+
await delay(500, undefined, { signal: controller.signal }).catch(() => undefined);
|
|
179
|
+
const status = await requestRuntimeInstance(directory);
|
|
180
|
+
if (!status?.native)
|
|
181
|
+
throw new Error("Local Runtime stopped; the import resumes when it starts again");
|
|
182
|
+
job = status.native.import;
|
|
201
183
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
184
|
+
if (controller.signal.aborted)
|
|
185
|
+
job = await control("pause", 5_000);
|
|
186
|
+
const paused = job.state === "paused";
|
|
187
|
+
if (asJson)
|
|
188
|
+
outJson({ ...summary, ...job, paused });
|
|
206
189
|
else {
|
|
207
|
-
if (
|
|
208
|
-
process.stderr.write("\
|
|
209
|
-
process.stdout.write(
|
|
210
|
-
for (const
|
|
211
|
-
process.stdout.write(`Skipped ${
|
|
212
|
-
|
|
213
|
-
process.stdout.write(
|
|
214
|
-
process.stdout.write("Uploads continue in the local Runtime background; use runtime status to check confirmation\n");
|
|
190
|
+
if (process.stderr.isTTY)
|
|
191
|
+
process.stderr.write("\r\x1b[2K");
|
|
192
|
+
process.stdout.write(`${paused ? "Paused" : "Imported"} ${formatImportProgress(job)}\n`);
|
|
193
|
+
for (const failure of job.failed)
|
|
194
|
+
process.stdout.write(`Skipped ${failure.path}: ${failure.message}\n`);
|
|
195
|
+
if (paused)
|
|
196
|
+
process.stdout.write("Run cohub runtime import again to continue\n");
|
|
215
197
|
}
|
|
216
|
-
if (
|
|
198
|
+
if (job.failed.length)
|
|
217
199
|
process.exitCode = 1;
|
|
218
200
|
}
|
|
219
201
|
catch (cause) {
|
|
@@ -233,16 +215,15 @@ export function registerRuntime(program) {
|
|
|
233
215
|
const identity = currentIdentityKey();
|
|
234
216
|
const local = identity ? await requestRuntimeInstance(runtimeInstanceDirectory(identity, spaceId)) : null;
|
|
235
217
|
const space = createClient().space(spaceId);
|
|
236
|
-
const
|
|
237
|
-
const
|
|
218
|
+
const stateRoot = runtimeStateRoot(spaceId);
|
|
219
|
+
const archives = new RuntimeArchiveStore(join(stateRoot, "archives"));
|
|
220
|
+
const [remote, pendingLocalArchives, failedLocalArchives] = await Promise.all([
|
|
238
221
|
space.getRuntime(undefined, { signal: AbortSignal.timeout(5000) }).then((value) => ({ value, error: null })).catch((error) => ({ value: null, error: serializeDiagnosticError(error).message })),
|
|
239
|
-
|
|
240
|
-
identity ? listNativeSyncStores(store.root, spaceId, identity) : [],
|
|
222
|
+
archives.pendingCount(), archives.failedCaptureCount(),
|
|
241
223
|
]);
|
|
242
|
-
const nativeSessions = await Promise.all(nativeStores.map((native) => native.status()));
|
|
243
224
|
// A corrupt native sync config must never take down the whole status report.
|
|
244
|
-
const nativeSync = identity ? await
|
|
245
|
-
const result = { ...remote.value, spaceId, local, remote: remote.value, remoteError: remote.error, diagnosticsPath: runtimeDiagnosticsDirectory(
|
|
225
|
+
const nativeSync = identity ? await readNativeConfig(stateRoot, identity).then((config) => ({ config, error: null }), (error) => ({ config: null, error: serializeDiagnosticError(error).message })) : { config: null, error: null };
|
|
226
|
+
const result = { ...remote.value, spaceId, local, remote: remote.value, remoteError: remote.error, diagnosticsPath: runtimeDiagnosticsDirectory(stateRoot), pendingLocalArchives, failedLocalArchives, nativeSync: nativeSync.config, nativeSyncError: nativeSync.error, native: local?.native ?? null };
|
|
246
227
|
if (jsonRequested(options))
|
|
247
228
|
outJson(result);
|
|
248
229
|
else {
|
|
@@ -251,7 +232,7 @@ export function registerRuntime(program) {
|
|
|
251
232
|
else
|
|
252
233
|
process.stdout.write(`Local process Not running\nSpace ${spaceId}\nLogs ${result.diagnosticsPath}\n`);
|
|
253
234
|
process.stdout.write(`Server ${remote.error ? `Unknown — ${remote.error}` : remote.value?.online ? "Harness connected" : "Offline"}\nArchives ${pendingLocalArchives} pending · ${failedLocalArchives} failed\n`);
|
|
254
|
-
process.stdout.write(formatNativeSync(nativeSync.config,
|
|
235
|
+
process.stdout.write(formatNativeSync(nativeSync.config, local?.native, nativeSync.error));
|
|
255
236
|
}
|
|
256
237
|
}
|
|
257
238
|
catch (cause) {
|
|
@@ -285,8 +266,12 @@ export function registerRuntime(program) {
|
|
|
285
266
|
throw new Error("Runtime is still stopping; inspect logs");
|
|
286
267
|
if (jsonRequested(options))
|
|
287
268
|
outJson({ spaceId, stopped: true });
|
|
288
|
-
else
|
|
269
|
+
else {
|
|
289
270
|
process.stdout.write("Runtime stopped; data retained\n");
|
|
271
|
+
// Terminal clients may still be attached to it, so it is never stopped for them.
|
|
272
|
+
if (local?.native?.codex?.control === "shared")
|
|
273
|
+
process.stdout.write("Codex's shared app-server keeps running; stop it with codex app-server daemon stop\n");
|
|
274
|
+
}
|
|
290
275
|
}
|
|
291
276
|
catch (cause) {
|
|
292
277
|
reportFailure(cause);
|
|
@@ -303,18 +288,18 @@ export function registerRuntime(program) {
|
|
|
303
288
|
const stop = () => controller.abort();
|
|
304
289
|
try {
|
|
305
290
|
const spaceId = await resolveRuntimeTarget(program, options.space);
|
|
306
|
-
const
|
|
291
|
+
const stateRoot = runtimeStateRoot(spaceId);
|
|
307
292
|
const limit = Number(options.limit);
|
|
308
293
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 10_000)
|
|
309
294
|
throw new Error("Use a limit from 1 to 10000");
|
|
310
295
|
if (!diagnosticLevels.includes(options.level))
|
|
311
296
|
throw new Error("Use debug, info, warn or error");
|
|
312
297
|
const asJson = jsonRequested(options);
|
|
313
|
-
const reader = new RuntimeDiagnosticReader(
|
|
298
|
+
const reader = new RuntimeDiagnosticReader(stateRoot);
|
|
314
299
|
process.once("SIGINT", stop);
|
|
315
300
|
process.once("SIGTERM", stop);
|
|
316
301
|
do {
|
|
317
|
-
const events = (options.follow ? await reader.read({ limit }) : await readRuntimeDiagnosticEvents(
|
|
302
|
+
const events = (options.follow ? await reader.read({ limit }) : await readRuntimeDiagnosticEvents(stateRoot, { limit }))
|
|
318
303
|
.filter((event) => atLeastLevel(event.level, options.level));
|
|
319
304
|
if (asJson && !options.follow)
|
|
320
305
|
outJson(events);
|
package/dist/commands/search.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isUuid } from "@neta-art/cohub";
|
|
2
2
|
import { createClient } from "../client.js";
|
|
3
|
-
import { formatLocalDateTime, table, json as outJson, jsonRequested, error, handleHttp } from "../output.js";
|
|
3
|
+
import { formatLocalDateTime, table, json as outJson, jsonRequested, error, handleHttp, truncateText } from "../output.js";
|
|
4
4
|
const DEFAULT_LIMIT = 20;
|
|
5
5
|
const MAX_TITLE_LENGTH = 72;
|
|
6
6
|
const MAX_CONTEXT_LENGTH = 42;
|
|
@@ -11,12 +11,6 @@ function clampLimit(value) {
|
|
|
11
11
|
return DEFAULT_LIMIT;
|
|
12
12
|
return Math.min(Math.max(Math.floor(parsed), 1), 50);
|
|
13
13
|
}
|
|
14
|
-
function truncate(value, maxLength) {
|
|
15
|
-
const text = (value ?? "").replace(/\s+/g, " ").trim();
|
|
16
|
-
if (text.length <= maxLength)
|
|
17
|
-
return text;
|
|
18
|
-
return `${text.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;
|
|
19
|
-
}
|
|
20
14
|
function contextFor(item) {
|
|
21
15
|
const owner = item.ownerProfile;
|
|
22
16
|
if (owner?.username)
|
|
@@ -47,8 +41,8 @@ function parseSearchInput(opts) {
|
|
|
47
41
|
function rowsFor(items) {
|
|
48
42
|
return items.map((item) => ({
|
|
49
43
|
type: item.type,
|
|
50
|
-
title:
|
|
51
|
-
context:
|
|
44
|
+
title: truncateText(item.title, MAX_TITLE_LENGTH),
|
|
45
|
+
context: truncateText(contextFor(item), MAX_CONTEXT_LENGTH),
|
|
52
46
|
match: item.matchedField,
|
|
53
47
|
updated: item.updatedAt ? formatLocalDateTime(item.updatedAt).slice(0, 10) : "",
|
|
54
48
|
href: item.href,
|
package/dist/commands/spaces.js
CHANGED
|
@@ -5,7 +5,7 @@ import { resolveCohubEnvironment } from "@neta-art/cohub";
|
|
|
5
5
|
import { uploadAvatarAsset, uploadChatImageAsset } from "../avatar.js";
|
|
6
6
|
import { createClient } from "../client.js";
|
|
7
7
|
import { putLocalFile } from "../http-put.js";
|
|
8
|
-
import { table, json as outJson, jsonRequested, ok, error, handleHttp, formatEpochMs } from "../output.js";
|
|
8
|
+
import { table, json as outJson, jsonRequested, ok, error, handleHttp, formatEpochMs, truncateText } from "../output.js";
|
|
9
9
|
import { resolveSpace } from "../space.js";
|
|
10
10
|
import { registerSpaceCommerce } from "./space-commerce.js";
|
|
11
11
|
import { registerSpaceActivity } from "./space-activity.js";
|
|
@@ -17,6 +17,8 @@ const defaultIdleTtlSeconds = cliEnv === "prod" ? 12 * 60 * 60 : 10 * 60;
|
|
|
17
17
|
const SPACE_ROLES = ["host", "builder", "guest"];
|
|
18
18
|
const SANDBOX_SPEC_IDS = ["standard", "boost", "ultra"];
|
|
19
19
|
const LABEL_RESOURCE_TYPES = ["session", "checkpoint", "file"];
|
|
20
|
+
/** Table cell width for space descriptions; `--json` keeps the full value. */
|
|
21
|
+
const SPACE_DESCRIPTION_COLUMN_WIDTH = 40;
|
|
20
22
|
function parseInteger(value, name, options = {}) {
|
|
21
23
|
if (!/^-?\d+$/.test(value.trim()))
|
|
22
24
|
return error(`Invalid ${name}`, `${name} must be an integer`);
|
|
@@ -476,6 +478,7 @@ export function registerSpaces(program) {
|
|
|
476
478
|
table(filtered, [
|
|
477
479
|
{ key: "id", label: "ID" },
|
|
478
480
|
{ key: "name", label: "Name" },
|
|
481
|
+
{ key: "description", label: "Description", format: (value) => truncateText(value, SPACE_DESCRIPTION_COLUMN_WIDTH) },
|
|
479
482
|
{ key: "isPinned", label: "Pinned" },
|
|
480
483
|
{ key: "createdAt", label: "Created" },
|
|
481
484
|
]);
|
|
@@ -500,7 +503,7 @@ export function registerSpaces(program) {
|
|
|
500
503
|
{ key: "id", label: "ID" },
|
|
501
504
|
{ key: "name", label: "Name" },
|
|
502
505
|
{ key: "slug", label: "Slug" },
|
|
503
|
-
{ key: "description", label: "Description" },
|
|
506
|
+
{ key: "description", label: "Description", format: (value) => truncateText(value, SPACE_DESCRIPTION_COLUMN_WIDTH) },
|
|
504
507
|
{ key: "status", label: "Status" },
|
|
505
508
|
{ key: "createdAt", label: "Created" },
|
|
506
509
|
]);
|
package/dist/output.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ export declare function formatLocalDateTime(value: unknown): string;
|
|
|
24
24
|
* Unix epoch for a timestamp the server never sent.
|
|
25
25
|
*/
|
|
26
26
|
export declare function formatEpochMs(value: unknown): string;
|
|
27
|
+
/**
|
|
28
|
+
* Collapse whitespace and clip long text for a single-line table cell, so
|
|
29
|
+
* multi-line values cannot break the row layout. `--json` output keeps the
|
|
30
|
+
* raw value; only the human-facing table is shortened.
|
|
31
|
+
*/
|
|
32
|
+
export declare function truncateText(value: unknown, maxLength: number): string;
|
|
27
33
|
export declare function table(rows: Row[], columns: Column[]): void;
|
|
28
34
|
export declare function json(data: unknown): void;
|
|
29
35
|
export declare function jsonRequested(opts?: {
|
package/dist/output.js
CHANGED
|
@@ -40,6 +40,17 @@ export function formatEpochMs(value) {
|
|
|
40
40
|
return "";
|
|
41
41
|
return formatLocalDateTime(ms);
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Collapse whitespace and clip long text for a single-line table cell, so
|
|
45
|
+
* multi-line values cannot break the row layout. `--json` output keeps the
|
|
46
|
+
* raw value; only the human-facing table is shortened.
|
|
47
|
+
*/
|
|
48
|
+
export function truncateText(value, maxLength) {
|
|
49
|
+
const text = typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
50
|
+
if (text.length <= maxLength)
|
|
51
|
+
return text;
|
|
52
|
+
return `${text.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;
|
|
53
|
+
}
|
|
43
54
|
function cellText(column, row) {
|
|
44
55
|
const raw = row[column.key];
|
|
45
56
|
if (column.format)
|
|
@@ -4,6 +4,4 @@ export type CodexTokenTotals = Record<typeof fields[number], number>;
|
|
|
4
4
|
export declare const codexTokenTotals: (value: unknown) => CodexTokenTotals;
|
|
5
5
|
export declare const subtractCodexTokens: (total: CodexTokenTotals, base: CodexTokenTotals) => CodexTokenTotals;
|
|
6
6
|
export declare function codexUsage(total: CodexTokenTotals): NonNullable<RuntimeMessage["usage"]>;
|
|
7
|
-
/** Seed portable imports from the original native counters, not a prior turn's `last`. */
|
|
8
|
-
export declare function codexArchiveTotals(records: Record<string, unknown>[]): CodexTokenTotals | undefined;
|
|
9
7
|
export {};
|
|
@@ -9,15 +9,3 @@ export const subtractCodexTokens = (total, base) => Object.fromEntries(fields.ma
|
|
|
9
9
|
export function codexUsage(total) {
|
|
10
10
|
return { input: Math.max(0, total.inputTokens - total.cachedInputTokens - total.cacheWriteInputTokens), output: total.outputTokens, cacheRead: total.cachedInputTokens, cacheWrite: total.cacheWriteInputTokens, totalTokens: total.totalTokens };
|
|
11
11
|
}
|
|
12
|
-
/** Seed portable imports from the original native counters, not a prior turn's `last`. */
|
|
13
|
-
export function codexArchiveTotals(records) {
|
|
14
|
-
for (let i = records.length - 1; i >= 0; i--) {
|
|
15
|
-
const entry = records[i];
|
|
16
|
-
if (entry?.type !== "token_usage_record")
|
|
17
|
-
continue;
|
|
18
|
-
const value = record(record(entry.payload).thread_token_usage);
|
|
19
|
-
if (typeof value.total_tokens !== "number")
|
|
20
|
-
continue;
|
|
21
|
-
return codexTokenTotals({ inputTokens: value.input_tokens, outputTokens: value.output_tokens, cachedInputTokens: value.cached_input_tokens, cacheWriteInputTokens: value.cache_write_input_tokens, totalTokens: value.total_tokens });
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { type RuntimeCapabilities, type NativeRuntimeEvent } from "@neta-art/cohub";
|
|
2
|
-
import { type
|
|
3
|
-
import { type RuntimeSessionStore } from "./session-store.js";
|
|
1
|
+
import { type RuntimeNativeStop, type RuntimeCapabilities, type NativeRuntimeEvent } from "@neta-art/cohub";
|
|
2
|
+
import { type Executor } from "./native/execution.js";
|
|
4
3
|
import { type RuntimeDiagnostics } from "./diagnostics.js";
|
|
5
4
|
export type RuntimeConnectionOptions = {
|
|
6
5
|
spaceId: string;
|
|
7
6
|
cwd: string;
|
|
8
7
|
url: string;
|
|
9
8
|
capabilities: RuntimeCapabilities;
|
|
10
|
-
harnesses: HarnessOptions;
|
|
11
9
|
token: (forceRefresh?: boolean) => Promise<string>;
|
|
12
10
|
signal: AbortSignal;
|
|
13
|
-
|
|
11
|
+
executor: Executor;
|
|
14
12
|
onReady: () => void;
|
|
15
13
|
onDisconnected?: () => void;
|
|
16
14
|
runtimeId?: string;
|
|
17
15
|
diagnostics?: RuntimeDiagnostics;
|
|
18
16
|
leaseConflictTimeoutMs?: number;
|
|
19
17
|
onNativeChannel?: (send: (event: NativeRuntimeEvent) => Promise<unknown>) => void;
|
|
18
|
+
/** The web asked to stop a Turn; one running in a native client is stopped there. */
|
|
19
|
+
onNativeStop?: (stop: RuntimeNativeStop) => void;
|
|
20
20
|
};
|
|
21
21
|
export declare function serveRuntime(options: RuntimeConnectionOptions): Promise<void>;
|