@neta-art/cohub-cli 10.4.4 → 10.6.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/README.md +18 -2
- package/dist/commands/runtime.js +21 -8
- package/dist/commands/space-wallet.d.ts +2 -0
- package/dist/commands/space-wallet.js +64 -0
- package/dist/commands/spaces.d.ts +5 -0
- package/dist/commands/spaces.js +55 -8
- package/dist/runtime/archive-store.d.ts +2 -2
- package/dist/runtime/archive-store.js +2 -2
- package/dist/runtime/diagnostics.d.ts +3 -3
- package/dist/runtime/harnesses/chained-projection.d.ts +40 -0
- package/dist/runtime/harnesses/chained-projection.js +95 -0
- package/dist/runtime/harnesses/claude.d.ts +8 -0
- package/dist/runtime/harnesses/claude.js +94 -0
- package/dist/runtime/harnesses/codex.d.ts +4 -0
- package/dist/runtime/harnesses/codex.js +229 -0
- package/dist/runtime/harnesses/index.d.ts +16 -0
- package/dist/runtime/harnesses/index.js +57 -0
- package/dist/runtime/harnesses/pi.d.ts +4 -0
- package/dist/runtime/harnesses/pi.js +247 -0
- package/dist/runtime/harnesses/process.d.ts +31 -0
- package/dist/runtime/harnesses/process.js +75 -0
- package/dist/runtime/harnesses/stream-json.d.ts +28 -0
- package/dist/runtime/harnesses/stream-json.js +291 -0
- package/dist/runtime/harnesses/types.d.ts +108 -0
- package/dist/runtime/harnesses/types.js +1 -0
- package/dist/runtime/harnesses/workbuddy.d.ts +15 -0
- package/dist/runtime/harnesses/workbuddy.js +99 -0
- package/dist/runtime/json-rpc.d.ts +7 -1
- package/dist/runtime/json-rpc.js +23 -2
- package/dist/runtime/launch.d.ts +6 -4
- package/dist/runtime/launch.js +21 -11
- package/dist/runtime/native/adapters.d.ts +11 -28
- package/dist/runtime/native/adapters.js +7 -75
- package/dist/runtime/native/archive.d.ts +9 -7
- package/dist/runtime/native/archive.js +99 -38
- package/dist/runtime/native/attach.d.ts +3 -5
- package/dist/runtime/native/attach.js +8 -2
- package/dist/runtime/native/daemon.d.ts +2 -2
- package/dist/runtime/native/daemon.js +5 -4
- package/dist/runtime/native/execution.d.ts +9 -30
- package/dist/runtime/native/execution.js +7 -325
- package/dist/runtime/native/identity.d.ts +4 -4
- package/dist/runtime/native/identity.js +4 -4
- package/dist/runtime/native/ingest.d.ts +2 -2
- package/dist/runtime/native/ingest.js +20 -25
- package/dist/runtime/native/sessions.d.ts +4 -4
- package/dist/runtime/native/sessions.js +12 -22
- package/dist/runtime/native/transcript.d.ts +3 -0
- package/dist/runtime/native/transcript.js +6 -5
- package/dist/runtime/native/translate.d.ts +2 -0
- package/dist/runtime/native/translate.js +34 -1
- package/dist/runtime/native/version.js +1 -1
- package/dist/runtime/presentation.js +2 -1
- package/dist/runtime/projection-store.js +2 -12
- package/dist/runtime/supervisor.d.ts +4 -4
- package/dist/runtime/supervisor.js +1 -1
- package/dist/runtime/turn-projection.d.ts +1 -1
- package/dist/runtime/turn-projection.js +2 -2
- package/package.json +2 -2
- package/dist/runtime/harness.d.ts +0 -26
- package/dist/runtime/harness.js +0 -142
package/README.md
CHANGED
|
@@ -137,7 +137,9 @@ cohub -s home spaces files ls
|
|
|
137
137
|
|
|
138
138
|
## Local Runtime
|
|
139
139
|
|
|
140
|
-
Connect one local workspace to a Space and select Pi or
|
|
140
|
+
Connect one local workspace to a Space and select Pi, Codex, Claude Code or WorkBuddy per turn.
|
|
141
|
+
Claude Code (`claude`) and WorkBuddy (`codebuddy`) run Cohub Turns only; terminal sync covers Pi and Codex.
|
|
142
|
+
Without `--harness`, every installed harness is tried; one that cannot start is skipped with a warning.
|
|
141
143
|
|
|
142
144
|
To keep using native terminals with ordinary Cohub Chats / Turns:
|
|
143
145
|
|
|
@@ -179,7 +181,7 @@ are never uploaded automatically. Use `runtime logs --json` to export a failure
|
|
|
179
181
|
events carry the local `runtimeId` plus server `requestId` / `traceparent` when a turn has
|
|
180
182
|
reached the Agent trace.
|
|
181
183
|
|
|
182
|
-
The Runtime uses WebSockets, native Pi RPC / Codex app-server, and existing cloud
|
|
184
|
+
The Runtime uses WebSockets, native Pi RPC / Codex app-server / Claude Code stream-json, and existing cloud
|
|
183
185
|
streaming. Local executables and credentials are required. `sandbox up` has been
|
|
184
186
|
removed. Reconnection and result reconciliation are automatic; no recovery command is needed.
|
|
185
187
|
When a result genuinely cannot be determined, the affected Chat offers an explicit stop
|
|
@@ -282,6 +284,20 @@ cohub -s <spaceId> spaces activity 7
|
|
|
282
284
|
cohub -s <spaceId> spaces activity 365 --json
|
|
283
285
|
```
|
|
284
286
|
|
|
287
|
+
## Space wallet
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
cohub -s <spaceId> spaces wallet balance
|
|
291
|
+
cohub -s <spaceId> spaces wallet catalog
|
|
292
|
+
cohub -s <spaceId> spaces wallet recharge <productKey> --request-id <uuid>
|
|
293
|
+
cohub -s <spaceId> spaces wallet confirm <orderId>
|
|
294
|
+
cohub -s <spaceId> spaces wallet activity --page 1 --json
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Recharge prints a payment URL. Reuse the request UUID when retrying the same
|
|
298
|
+
recharge. Any signed-in user with access to the Space may contribute; payment
|
|
299
|
+
does not grant additional permissions. Transfers and withdrawals are not supported.
|
|
300
|
+
|
|
285
301
|
## Boards
|
|
286
302
|
|
|
287
303
|
A Board is one JSON document with three parts — `board` settings, an `items` map,
|
package/dist/commands/runtime.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
import { LOCAL_HARNESS_SPECS, LOCAL_HARNESSES } from "@neta-art/cohub";
|
|
2
3
|
import { createClient } from "../client.js";
|
|
3
4
|
import { json as outJson, jsonRequested } from "../output.js";
|
|
4
5
|
import { currentIdentityKey } from "../space.js";
|
|
@@ -9,6 +10,7 @@ import { RuntimeArchiveStore } from "../runtime/archive-store.js";
|
|
|
9
10
|
import { readNativeConfig, runtimeStateRoot, writeNativeConfig } from "../runtime/native/config.js";
|
|
10
11
|
import { MAX_IMPORT_CONCURRENCY, DEFAULT_IMPORT_CONCURRENCY } from "../runtime/native/ingest.js";
|
|
11
12
|
import { installPiExtension, piExtensionState } from "../runtime/native/install.js";
|
|
13
|
+
import { nativeSyncHarnesses } from "../runtime/harnesses/index.js";
|
|
12
14
|
import { controlRuntimeInstance, requestRuntimeInstance, runtimeInstanceDirectory } from "../runtime/instance.js";
|
|
13
15
|
import { atLeastLevel, diagnosticLevels, formatDiagnostic, formatImportProgress, formatNativeSync, printRuntimeSummary } from "../runtime/presentation.js";
|
|
14
16
|
import { readRuntimeDiagnosticEvents, RuntimeDiagnosticReader, runtimeDiagnosticsDirectory, serializeDiagnosticError } from "../runtime/diagnostics.js";
|
|
@@ -17,6 +19,16 @@ const reportFailure = (cause) => {
|
|
|
17
19
|
process.stderr.write(`Runtime failed: ${serializeDiagnosticError(cause).message}\n`);
|
|
18
20
|
process.exitCode = 1;
|
|
19
21
|
};
|
|
22
|
+
const SYNCED = nativeSyncHarnesses(LOCAL_HARNESSES);
|
|
23
|
+
const SYNCED_LABELS = SYNCED.map((harness) => LOCAL_HARNESS_SPECS[harness].label).join(" or ");
|
|
24
|
+
/** Native sync and import cover only harnesses whose terminal conversations sync. */
|
|
25
|
+
function parseSyncedHarnesses(values) {
|
|
26
|
+
const harnesses = parseRuntimeHarnesses(values);
|
|
27
|
+
const unsynced = harnesses.filter((harness) => !SYNCED.includes(harness));
|
|
28
|
+
if (unsynced.length)
|
|
29
|
+
throw new Error(`${unsynced.map((harness) => LOCAL_HARNESS_SPECS[harness].label).join(", ")} conversations do not sync; native sync covers ${SYNCED_LABELS}`);
|
|
30
|
+
return harnesses;
|
|
31
|
+
}
|
|
20
32
|
const megabytes = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
21
33
|
/** The bound Space of a directory, with its running Runtime; import and attach act through it. */
|
|
22
34
|
async function boundRuntime(program, dir, space) {
|
|
@@ -32,15 +44,16 @@ async function boundRuntime(program, dir, space) {
|
|
|
32
44
|
}
|
|
33
45
|
export function registerRuntime(program) {
|
|
34
46
|
const runtime = program.command("runtime").description("Connect a local workspace");
|
|
35
|
-
runtime.command("up [dir]")
|
|
47
|
+
const up = runtime.command("up [dir]")
|
|
36
48
|
.description("Connect local Harnesses and files")
|
|
37
49
|
.option("-s, --space <space>", "Target Space: ID, slug, or username/slug")
|
|
38
50
|
.option("-n, --new", "Create a new Space")
|
|
39
51
|
.option("--name <name>", "New Space name")
|
|
40
52
|
.option("-d, --detach", "Run in the background")
|
|
41
|
-
.option("--harness <name>", "
|
|
42
|
-
|
|
43
|
-
.option(
|
|
53
|
+
.option("--harness <name>", `${LOCAL_HARNESSES.join(", ")}; repeatable`, (value, previous) => [...previous, value], []);
|
|
54
|
+
for (const harness of LOCAL_HARNESSES)
|
|
55
|
+
up.option(`--${harness} <path>`, `${LOCAL_HARNESS_SPECS[harness].label} executable`);
|
|
56
|
+
up
|
|
44
57
|
.option("--display [screen]", "Share a screen (default: auto when available), or xvfb[:WIDTHxHEIGHT] for a virtual one")
|
|
45
58
|
.option("--no-display", "Disable screen sharing and skip its confirmation")
|
|
46
59
|
.option("-y, --yes", "Accept defaults and authorize local execution, native sync, and screen sharing")
|
|
@@ -63,7 +76,7 @@ export function registerRuntime(program) {
|
|
|
63
76
|
try {
|
|
64
77
|
const harnesses = parseRuntimeHarnesses(options.harness.length ? options.harness : ["pi"]);
|
|
65
78
|
if (!harnesses.includes("pi"))
|
|
66
|
-
throw new Error("
|
|
79
|
+
throw new Error("Only Pi uses an extension; Codex syncs through runtime up's shared app-server");
|
|
67
80
|
const before = await piExtensionState();
|
|
68
81
|
if (before === "foreign")
|
|
69
82
|
throw new Error("A Pi extension named cohub exists and is not managed by Cohub; move it aside, then retry");
|
|
@@ -84,14 +97,14 @@ export function registerRuntime(program) {
|
|
|
84
97
|
runtime.command("detach")
|
|
85
98
|
.description("Pause native sync; keep all data")
|
|
86
99
|
.option("-s, --space <space>", "Target Space: ID, slug, or username/slug")
|
|
87
|
-
.option("--harness <name>",
|
|
100
|
+
.option("--harness <name>", `${SYNCED_LABELS}; repeatable`, (value, previous) => [...previous, value], [])
|
|
88
101
|
.option("--json", "JSON output")
|
|
89
102
|
.action(async (options) => {
|
|
90
103
|
try {
|
|
91
104
|
const { identity, spaceId, directory } = await boundRuntime(program, undefined, options.space);
|
|
92
105
|
const stateRoot = runtimeStateRoot(spaceId);
|
|
93
106
|
const config = await readNativeConfig(stateRoot, identity);
|
|
94
|
-
const paused =
|
|
107
|
+
const paused = options.harness.length ? parseSyncedHarnesses(options.harness) : config?.harnesses ?? SYNCED;
|
|
95
108
|
const harnesses = (config?.harnesses ?? []).filter((harness) => !paused.includes(harness));
|
|
96
109
|
if (config)
|
|
97
110
|
await writeNativeConfig(stateRoot, { ...config, harnesses, codexShared: harnesses.includes("codex") && config.codexShared });
|
|
@@ -131,7 +144,7 @@ export function registerRuntime(program) {
|
|
|
131
144
|
throw new Error(`Use a concurrency from 1 to ${MAX_IMPORT_CONCURRENCY}`);
|
|
132
145
|
const { spaceId, root, directory } = await boundRuntime(program, dir, options.space);
|
|
133
146
|
// The Runtime does the work, so an import survives this command: Ctrl-C only pauses it.
|
|
134
|
-
const filter = { harnesses: options.harness.length ?
|
|
147
|
+
const filter = { harnesses: options.harness.length ? parseSyncedHarnesses(options.harness) : [], ...(options.session ? { nativeSessionId: options.session } : {}) };
|
|
135
148
|
const control = async (command, timeoutMs = 60_000) => {
|
|
136
149
|
const response = await controlRuntimeInstance(directory, "import", { ...filter, command, concurrency }, timeoutMs);
|
|
137
150
|
if (!response)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createClient } from "../client.js";
|
|
2
|
+
import { handleHttp, json as outJson, jsonRequested, table } from "../output.js";
|
|
3
|
+
import { resolveSpace } from "../space.js";
|
|
4
|
+
export function registerSpaceWallet(spaces) {
|
|
5
|
+
const wallet = spaces.command("wallet").description("Space wallet balance, recharge, and activity");
|
|
6
|
+
wallet.command("balance").option("--json", "Output as JSON").action(async (opts) => {
|
|
7
|
+
try {
|
|
8
|
+
const value = await createClient().space(await resolveSpace(spaces)).wallet.get();
|
|
9
|
+
if (jsonRequested(opts))
|
|
10
|
+
return outJson(value);
|
|
11
|
+
table([{ balance: `$${value.balanceUsd.toFixed(8)}`, usable: value.usable }], [{ key: "balance", label: "Balance" }, { key: "usable", label: "Usable" }]);
|
|
12
|
+
}
|
|
13
|
+
catch (cause) {
|
|
14
|
+
handleHttp(cause);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
wallet.command("catalog").option("--json", "Output as JSON").action(async (opts) => {
|
|
18
|
+
try {
|
|
19
|
+
const value = await createClient().space(await resolveSpace(spaces)).wallet.catalog();
|
|
20
|
+
if (jsonRequested(opts))
|
|
21
|
+
return outJson(value);
|
|
22
|
+
table(value.products.map((product) => ({ key: product.key, name: product.name, price: product.pricing.amountUsd })), [{ key: "key", label: "Product" }, { key: "name", label: "Name" }, { key: "price", label: "Price (USD)" }]);
|
|
23
|
+
}
|
|
24
|
+
catch (cause) {
|
|
25
|
+
handleHttp(cause);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
wallet.command("activity").option("--page <page>", "Page number", "1").option("--json", "Output as JSON").action(async (opts) => {
|
|
29
|
+
try {
|
|
30
|
+
const page = Number(opts.page);
|
|
31
|
+
if (!Number.isInteger(page) || page < 1 || page > 10_000)
|
|
32
|
+
throw new Error("Page must be an integer between 1 and 10000");
|
|
33
|
+
const value = await createClient().space(await resolveSpace(spaces)).wallet.activities(page);
|
|
34
|
+
if (jsonRequested(opts))
|
|
35
|
+
return outJson(value);
|
|
36
|
+
table(value.items, [{ key: "kind", label: "Activity" }, { key: "amountUsd", label: "Amount (USD)" }, { key: "actorUserId", label: "User" }, { key: "createdAt", label: "Date" }]);
|
|
37
|
+
}
|
|
38
|
+
catch (cause) {
|
|
39
|
+
handleHttp(cause);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
wallet.command("recharge <product>").requiredOption("--request-id <uuid>", "Stable UUID; reuse when retrying this recharge").option("--json", "Output as JSON").action(async (productKey, opts) => {
|
|
43
|
+
try {
|
|
44
|
+
const value = await createClient().space(await resolveSpace(spaces)).wallet.createOrder({ id: opts.requestId, productKey });
|
|
45
|
+
if (jsonRequested(opts))
|
|
46
|
+
return outJson(value);
|
|
47
|
+
if (!value.checkout.checkoutUsable || !value.checkout.checkoutUrl)
|
|
48
|
+
throw new Error(value.checkout.message ?? "Checkout is unavailable");
|
|
49
|
+
console.log(value.checkout.checkoutUrl);
|
|
50
|
+
console.log(`Order: ${value.orderId}`);
|
|
51
|
+
}
|
|
52
|
+
catch (cause) {
|
|
53
|
+
handleHttp(cause);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
wallet.command("confirm <order>").action(async (orderId) => {
|
|
57
|
+
try {
|
|
58
|
+
outJson(await createClient().space(await resolveSpace(spaces)).wallet.confirmOrder(orderId));
|
|
59
|
+
}
|
|
60
|
+
catch (cause) {
|
|
61
|
+
handleHttp(cause);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { CohubHttpClient, CreateSpaceInput } from "@neta-art/cohub";
|
|
2
2
|
import type { Command } from "commander";
|
|
3
|
+
import { createClient } from "../client.js";
|
|
3
4
|
import { type LocalUploadEntry } from "../space-files/upload.js";
|
|
4
5
|
export type SpaceCreateOptions = {
|
|
5
6
|
name: string;
|
|
6
7
|
description?: string;
|
|
7
8
|
checkpoint?: string;
|
|
9
|
+
image?: string;
|
|
8
10
|
autoDestroy?: string;
|
|
9
11
|
idleTtl?: string;
|
|
10
12
|
spec?: string;
|
|
@@ -17,4 +19,7 @@ export declare function registerPrompt(program: Command): void;
|
|
|
17
19
|
export declare function registerSpaceCreate(spacesCmd: Command, dependencies?: {
|
|
18
20
|
createClient?: () => CohubHttpClient;
|
|
19
21
|
}): Command;
|
|
22
|
+
export declare function registerSpaceSetup(spacesCmd: Command, deps?: {
|
|
23
|
+
createClient?: typeof createClient;
|
|
24
|
+
}): void;
|
|
20
25
|
export declare function registerSpaces(program: Command): void;
|
package/dist/commands/spaces.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readdir, stat } from "node:fs/promises";
|
|
2
2
|
import { basename, dirname, relative, resolve, sep } from "node:path";
|
|
3
|
-
import { readSentTurns, readSessionTurnOrigin, resolveCohubEnvironment } from "@neta-art/cohub";
|
|
3
|
+
import { harnessSchema, readSentTurns, readSessionTurnOrigin, resolveCohubEnvironment } from "@neta-art/cohub";
|
|
4
4
|
import { uploadAvatarAsset, uploadChatImageAsset } from "../avatar.js";
|
|
5
5
|
import { createClient } from "../client.js";
|
|
6
6
|
import { collectOption, readPromptContent } from "../command-input.js";
|
|
@@ -11,6 +11,7 @@ import { uploadLocalFiles } from "../space-files/upload.js";
|
|
|
11
11
|
import { registerDecisionCommand } from "./decision.js";
|
|
12
12
|
import { registerSpaceCommerce } from "./space-commerce.js";
|
|
13
13
|
import { registerSpaceFileTransfer } from "./space-files.js";
|
|
14
|
+
import { registerSpaceWallet } from "./space-wallet.js";
|
|
14
15
|
import { registerSpaceActivity } from "./space-activity.js";
|
|
15
16
|
import { registerSpaceDisplays } from "./space-displays.js";
|
|
16
17
|
import { registerSpaceInvitations } from "./space-invitations.js";
|
|
@@ -75,6 +76,7 @@ const parseSandboxSpec = (value) => value ? parseChoice(value, "sandbox spec", S
|
|
|
75
76
|
export function buildSpaceCreateInput(opts) {
|
|
76
77
|
const autoDestroy = parseAutoDestroy(opts);
|
|
77
78
|
const spec = parseSandboxSpec(opts.spec);
|
|
79
|
+
const image = opts.image ? parseChoice(opts.image, "sandbox image", ["default", "game"]) : undefined;
|
|
78
80
|
const checkpointId = opts.checkpoint?.trim();
|
|
79
81
|
if (opts.checkpoint !== undefined && !checkpointId) {
|
|
80
82
|
return error("Invalid checkpoint", "Checkpoint ID is required");
|
|
@@ -85,12 +87,13 @@ export function buildSpaceCreateInput(opts) {
|
|
|
85
87
|
...(checkpointId
|
|
86
88
|
? { bootstrapSource: { type: "checkpoint", checkpointId } }
|
|
87
89
|
: {}),
|
|
88
|
-
...((autoDestroy || spec)
|
|
90
|
+
...((autoDestroy || spec || image)
|
|
89
91
|
? {
|
|
90
92
|
config: {
|
|
91
93
|
sandbox: {
|
|
92
94
|
...(autoDestroy ? { autoDestroy } : {}),
|
|
93
95
|
...(spec ? { spec } : {}),
|
|
96
|
+
...(image ? { image } : {}),
|
|
94
97
|
},
|
|
95
98
|
},
|
|
96
99
|
}
|
|
@@ -341,7 +344,7 @@ export function registerPrompt(program) {
|
|
|
341
344
|
.option("--title <title>", "Title for a newly created session or schedule")
|
|
342
345
|
.option("-m, --model <model>", "Model name")
|
|
343
346
|
.option("-p, --provider <provider>", "Provider name")
|
|
344
|
-
.option("--harness <harness>",
|
|
347
|
+
.option("--harness <harness>", `Harness: ${harnessSchema.options.join(", ")}`, "cohub")
|
|
345
348
|
.option("--thinking-level <level>", "Thinking level: off|minimal|low|medium|high|xhigh|max")
|
|
346
349
|
.option("--read-only", "Use read-only tools")
|
|
347
350
|
.option("--steer", "Interrupt the current turn and run immediately")
|
|
@@ -376,6 +379,7 @@ export function registerSpaceCreate(spacesCmd, dependencies = {}) {
|
|
|
376
379
|
.requiredOption("-n, --name <name>", "Space name")
|
|
377
380
|
.option("-d, --description <desc>", "Space description")
|
|
378
381
|
.option("--checkpoint <id>", "Create from a checkpoint")
|
|
382
|
+
.option("--image <image>", "Sandbox image: default or game")
|
|
379
383
|
.option("--auto-destroy <mode>", "Sandbox auto destroy mode: idle or never")
|
|
380
384
|
.option("--idle-ttl <seconds>", "Idle auto destroy TTL in seconds, max 2592000 (30d)")
|
|
381
385
|
.option("--spec <spec>", "Sandbox spec: standard, boost, or ultra")
|
|
@@ -454,6 +458,40 @@ function registerUserLabels(program) {
|
|
|
454
458
|
}
|
|
455
459
|
});
|
|
456
460
|
}
|
|
461
|
+
export function registerSpaceSetup(spacesCmd, deps = {}) {
|
|
462
|
+
spacesCmd
|
|
463
|
+
.command("setup [id]")
|
|
464
|
+
.description("Show cloud sandbox startup scripts, results, and log paths")
|
|
465
|
+
.option("--json", "Output as JSON")
|
|
466
|
+
.action(async (id, opts) => {
|
|
467
|
+
try {
|
|
468
|
+
const spaceId = id ?? await resolveSpace(spacesCmd);
|
|
469
|
+
const { sandbox } = await (deps.createClient ?? createClient)().space(spaceId).sandbox.get();
|
|
470
|
+
const setup = sandbox?.meta?.setup ?? null;
|
|
471
|
+
if (jsonRequested(opts))
|
|
472
|
+
return outJson({ provider: sandbox?.provider ?? null, setup });
|
|
473
|
+
if (sandbox?.provider === "local")
|
|
474
|
+
return ok("Setup is disabled for local spaces.");
|
|
475
|
+
if (!setup)
|
|
476
|
+
return ok("No setup report is available.");
|
|
477
|
+
table([
|
|
478
|
+
{ key: "Status", value: setup.status ?? (setup.error || setup.exitCode ? "failed" : "succeeded") },
|
|
479
|
+
{ key: "Duration", value: setup.duration || "—" },
|
|
480
|
+
...(setup.error ? [{ key: "Error", value: setup.error }] : []),
|
|
481
|
+
], [{ key: "key", label: "Key" }, { key: "value", label: "Value" }]);
|
|
482
|
+
if (setup.steps?.length)
|
|
483
|
+
table(setup.steps.map((step) => ({
|
|
484
|
+
script: step.path, status: step.status, duration: step.duration || "—", logs: step.logDir,
|
|
485
|
+
})), [
|
|
486
|
+
{ key: "script", label: "Script" }, { key: "status", label: "Status" },
|
|
487
|
+
{ key: "duration", label: "Duration" }, { key: "logs", label: "Logs" },
|
|
488
|
+
]);
|
|
489
|
+
}
|
|
490
|
+
catch (e) {
|
|
491
|
+
handleHttp(e);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
457
495
|
export function registerSpaces(program) {
|
|
458
496
|
const spacesCmd = program.command("spaces").description("Space management");
|
|
459
497
|
registerSpaceInvitations(spacesCmd);
|
|
@@ -615,21 +653,24 @@ export function registerSpaces(program) {
|
|
|
615
653
|
handleHttp(e);
|
|
616
654
|
}
|
|
617
655
|
});
|
|
656
|
+
registerSpaceSetup(spacesCmd);
|
|
618
657
|
// ── spaces config ──
|
|
619
658
|
spacesCmd
|
|
620
659
|
.command("config <id>")
|
|
621
660
|
.description("Show or update space configuration")
|
|
622
661
|
.option("--auto-destroy <mode>", "Sandbox auto destroy mode: idle or never")
|
|
623
662
|
.option("--idle-ttl <seconds>", "Idle auto destroy TTL in seconds, max 2592000 (30d)")
|
|
624
|
-
.option("--spec <spec>", "Sandbox spec: standard, boost, or
|
|
663
|
+
.option("--spec <spec>", "Sandbox spec: standard, boost, ultra, or auto")
|
|
664
|
+
.option("--image <image>", "Sandbox image: default, game, or auto")
|
|
625
665
|
.option("--json", "Output as JSON")
|
|
626
666
|
.action(async (id, opts) => {
|
|
627
667
|
const client = createClient();
|
|
628
668
|
try {
|
|
629
669
|
const autoDestroy = parseAutoDestroy(opts);
|
|
630
|
-
const spec = parseSandboxSpec(opts.spec);
|
|
631
|
-
|
|
632
|
-
|
|
670
|
+
const spec = opts.spec === "auto" ? null : parseSandboxSpec(opts.spec);
|
|
671
|
+
const image = opts.image === "auto" ? null : opts.image ? parseChoice(opts.image, "sandbox image", ["default", "game"]) : undefined;
|
|
672
|
+
if (autoDestroy || spec !== undefined || image !== undefined) {
|
|
673
|
+
const result = await client.space(id).updateConfig({ sandbox: { ...(autoDestroy ? { autoDestroy } : {}), ...(spec !== undefined ? { spec } : {}), ...(image !== undefined ? { image } : {}) } });
|
|
633
674
|
if (jsonRequested(opts))
|
|
634
675
|
return outJson(result);
|
|
635
676
|
ok(`Space config updated${autoDestroy ? ` — sandbox auto destroy: ${formatAutoDestroy(autoDestroy)}` : ""}${spec ? ` — sandbox spec: ${spec}` : ""}${result.sandbox?.pendingRestart ? " — restart the sandbox to apply" : ""}`);
|
|
@@ -640,6 +681,11 @@ export function registerSpaces(program) {
|
|
|
640
681
|
return outJson(result);
|
|
641
682
|
table([
|
|
642
683
|
{ key: "sandbox.autoDestroy", value: formatAutoDestroy(result.config.sandbox.autoDestroy) },
|
|
684
|
+
{ key: "sandbox.image", value: result.config.sandbox.image ?? "default" },
|
|
685
|
+
{ key: "sandbox.imageSource", value: result.config.sandbox.preferences?.origins.image ?? "platform" },
|
|
686
|
+
{ key: "sandbox.specSource", value: result.config.sandbox.preferences?.origins.spec ?? "platform" },
|
|
687
|
+
{ key: "sandbox.warnings", value: result.config.sandbox.preferences?.warnings.join(", ") || "—" },
|
|
688
|
+
{ key: "sandbox.pendingRestart", value: String(result.config.sandbox.preferences?.pendingRestart ?? false) },
|
|
643
689
|
{ key: "sandbox.spec", value: result.config.sandbox.spec ?? "standard" },
|
|
644
690
|
{ key: "sandbox.appliedSpec", value: result.config.sandbox.appliedSpec ?? "—" },
|
|
645
691
|
{ key: "sandbox.allowedSpec", value: result.config.sandbox.allowedSpec ?? "standard" },
|
|
@@ -657,7 +703,7 @@ export function registerSpaces(program) {
|
|
|
657
703
|
.command("prompt [content...]", { hidden: true })
|
|
658
704
|
.alias("send")
|
|
659
705
|
.description("Send or schedule a prompt in the target space")
|
|
660
|
-
.option("--harness <harness>",
|
|
706
|
+
.option("--harness <harness>", `Harness: ${harnessSchema.options.join(", ")}`, "cohub")
|
|
661
707
|
.option("--session <id>", "Target session ID")
|
|
662
708
|
.option("--title <title>", "Title for a newly created session or schedule")
|
|
663
709
|
.option("-m, --model <model>", "Model name")
|
|
@@ -737,6 +783,7 @@ export function registerSpaces(program) {
|
|
|
737
783
|
// ── spaces commerce ──
|
|
738
784
|
registerSpaceCommerce(spacesCmd);
|
|
739
785
|
// ── spaces activity ──
|
|
786
|
+
registerSpaceWallet(spacesCmd);
|
|
740
787
|
registerSpaceActivity(spacesCmd);
|
|
741
788
|
// ── spaces usage ──
|
|
742
789
|
spacesCmd
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type HarnessArchive, type HarnessArchiveIndex, type RuntimeArchivePage, type RuntimeArchiveUpload } from "@neta-art/cohub";
|
|
1
|
+
import { type LocalHarness, type HarnessArchive, type HarnessArchiveIndex, type RuntimeArchivePage, type RuntimeArchiveUpload } from "@neta-art/cohub";
|
|
2
2
|
export type ArchiveTransport = {
|
|
3
3
|
fetchObject?: typeof fetch;
|
|
4
4
|
prepareRuntimeArchive(index: HarnessArchiveIndex, options?: {
|
|
@@ -41,7 +41,7 @@ export declare class RuntimeArchiveStore {
|
|
|
41
41
|
private readIndex;
|
|
42
42
|
stage(state: {
|
|
43
43
|
sessionId: string;
|
|
44
|
-
harness:
|
|
44
|
+
harness: LocalHarness;
|
|
45
45
|
nativeSessionId: string;
|
|
46
46
|
path: string;
|
|
47
47
|
sizeBytes?: number;
|
|
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { createReadStream } from "node:fs";
|
|
3
3
|
import { mkdir, open, readFile, readdir, rename, rm, stat, link } from "node:fs/promises";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
|
-
import { RUNTIME_ARCHIVE_SEGMENT_BYTES, harnessArchiveIndexSchema, validateArchiveBoundary, } from "@neta-art/cohub";
|
|
5
|
+
import { LOCAL_HARNESS_SPECS, RUNTIME_ARCHIVE_SEGMENT_BYTES, harnessArchiveIndexSchema, validateArchiveBoundary, } from "@neta-art/cohub";
|
|
6
6
|
const missing = (error) => error?.code === "ENOENT";
|
|
7
7
|
/** Raised when local capture bytes fail integrity checks (checksum, truncation, boundary); retryable via cache rebuild. */
|
|
8
8
|
export class ArchiveCaptureIntegrityError extends Error {
|
|
@@ -195,7 +195,7 @@ export class RuntimeArchiveStore {
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
index = harnessArchiveIndexSchema.parse({ ...identity, version: 1, nativeSessionId: state.nativeSessionId,
|
|
198
|
-
nativeFormat: state.harness
|
|
198
|
+
nativeFormat: LOCAL_HARNESS_SPECS[state.harness].nativeFormat, parentTurnId: parent?.turnId ?? null,
|
|
199
199
|
sizeBytes, sha256: digest.digest("hex"), segments });
|
|
200
200
|
validateArchiveBoundary(index, parent);
|
|
201
201
|
if (state.expectedChecksum && index.sha256 !== state.expectedChecksum)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RuntimeTraceContext } from "@neta-art/cohub";
|
|
1
|
+
import type { LocalHarness, RuntimeTraceContext } from "@neta-art/cohub";
|
|
2
2
|
export type RuntimeDiagnosticLevel = "debug" | "info" | "warn" | "error";
|
|
3
3
|
export type RuntimeDiagnosticError = {
|
|
4
4
|
name?: string;
|
|
@@ -27,7 +27,7 @@ export type RuntimeDiagnostic = {
|
|
|
27
27
|
spaceId: string;
|
|
28
28
|
sessionId?: string | null;
|
|
29
29
|
turnId?: string | null;
|
|
30
|
-
harness?:
|
|
30
|
+
harness?: LocalHarness | null;
|
|
31
31
|
traceContext?: RuntimeTraceContext;
|
|
32
32
|
data?: Record<string, unknown>;
|
|
33
33
|
error?: RuntimeDiagnosticError;
|
|
@@ -39,7 +39,7 @@ export type RuntimeDiagnosticContext = {
|
|
|
39
39
|
spaceId?: string;
|
|
40
40
|
sessionId?: string | null;
|
|
41
41
|
turnId?: string | null;
|
|
42
|
-
harness?:
|
|
42
|
+
harness?: LocalHarness | null;
|
|
43
43
|
traceContext?: RuntimeTraceContext;
|
|
44
44
|
};
|
|
45
45
|
export type RuntimeDiagnosticsOptions = {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type CanonicalProjectionMessage, type ContentBlock, type LocalHarness, type NativeProjection, type ProjectionInput, type ProjectionRecord } from "@neta-art/cohub";
|
|
2
|
+
type ToolResult = Extract<ContentBlock, {
|
|
3
|
+
type: "tool_result";
|
|
4
|
+
}>;
|
|
5
|
+
/** Deterministic, UUID-shaped record ids keep re-projection stable without local state. */
|
|
6
|
+
export declare function projectionUuid(key: string): string;
|
|
7
|
+
/** Provider APIs restrict tool ids; hashing keeps call/result pairs linked. */
|
|
8
|
+
export declare function projectionToolId(id: string): string;
|
|
9
|
+
export type ProjectedSegment = {
|
|
10
|
+
role: "user" | "assistant";
|
|
11
|
+
blocks: ContentBlock[];
|
|
12
|
+
message: CanonicalProjectionMessage;
|
|
13
|
+
compactSummary: boolean;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Shared walk for transcript formats whose records form a parent-linked chain. `write` maps one
|
|
17
|
+
* user/assistant segment to native records; ids and parent links are assigned here.
|
|
18
|
+
*/
|
|
19
|
+
export declare function projectChainedSession(input: ProjectionInput, target: LocalHarness, fields: {
|
|
20
|
+
id: string;
|
|
21
|
+
parent: string;
|
|
22
|
+
}, write: (segment: ProjectedSegment) => Record<string, unknown>[]): NativeProjection;
|
|
23
|
+
export declare const projectionMeta: (input: ProjectionInput, message: CanonicalProjectionMessage) => {
|
|
24
|
+
messageId: string;
|
|
25
|
+
turnId: string;
|
|
26
|
+
schema: string;
|
|
27
|
+
spaceId: string;
|
|
28
|
+
sessionId: string;
|
|
29
|
+
};
|
|
30
|
+
export declare const toolResultText: (block: ToolResult) => string;
|
|
31
|
+
/** Session id is stored on every record of these formats. */
|
|
32
|
+
export declare const rebindSessionId: (records: ProjectionRecord[], nativeSessionId: string) => {
|
|
33
|
+
key: string;
|
|
34
|
+
sourceTurnId: string | null;
|
|
35
|
+
sourceMessageId: string | null;
|
|
36
|
+
record: {
|
|
37
|
+
sessionId: string;
|
|
38
|
+
};
|
|
39
|
+
}[];
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { fingerprintProjectionTurns, isProjectionCompaction, isProjectionPendingGeneration, projectNativeMessageMeta, trimProjectionTurnsToCompaction, } from "@neta-art/cohub";
|
|
3
|
+
/** Deterministic, UUID-shaped record ids keep re-projection stable without local state. */
|
|
4
|
+
export function projectionUuid(key) {
|
|
5
|
+
const hex = createHash("sha256").update(key).digest("hex");
|
|
6
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-${((Number.parseInt(hex[16] ?? "0", 16) & 3) | 8).toString(16)}${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
|
7
|
+
}
|
|
8
|
+
/** Provider APIs restrict tool ids; hashing keeps call/result pairs linked. */
|
|
9
|
+
export function projectionToolId(id) {
|
|
10
|
+
return /^[A-Za-z0-9_-]{1,64}$/.test(id) ? id : `call_${createHash("sha256").update(id).digest("hex").slice(0, 32)}`;
|
|
11
|
+
}
|
|
12
|
+
const nonEmpty = (block) => block.type !== "text" || block.text.length > 0;
|
|
13
|
+
/**
|
|
14
|
+
* Splits Cohub messages into alternating native records. A tool call is kept only with its result,
|
|
15
|
+
* because provider APIs reject unpaired calls; dropped content is reported as a projection warning.
|
|
16
|
+
*/
|
|
17
|
+
function segments(message, warnings) {
|
|
18
|
+
const warn = (reason) => warnings.push({ sourceMessageId: message.id, sourceTurnId: message.turnId, reason });
|
|
19
|
+
if (message.content.some((block) => block.type === "image" && block.source.type === "url"))
|
|
20
|
+
warn("external image URL omitted from native projection");
|
|
21
|
+
const visible = message.content.filter((block) => nonEmpty(block) && !(block.type === "image" && block.source.type === "url"));
|
|
22
|
+
if (message.role === "user") {
|
|
23
|
+
const blocks = visible.filter((block) => block.type === "text" || block.type === "image");
|
|
24
|
+
return blocks.length ? [{ role: "user", blocks }] : [];
|
|
25
|
+
}
|
|
26
|
+
const raw = [];
|
|
27
|
+
for (const block of visible) {
|
|
28
|
+
const role = block.type === "tool_result" ? "user" : "assistant";
|
|
29
|
+
if (role === "assistant" && !["text", "tool_use"].includes(block.type))
|
|
30
|
+
continue;
|
|
31
|
+
const last = raw.at(-1);
|
|
32
|
+
if (last?.role === role)
|
|
33
|
+
last.blocks.push(block);
|
|
34
|
+
else
|
|
35
|
+
raw.push({ role, blocks: [block] });
|
|
36
|
+
}
|
|
37
|
+
const paired = [];
|
|
38
|
+
raw.forEach((segment, index) => {
|
|
39
|
+
if (segment.role === "assistant") {
|
|
40
|
+
const results = new Set(raw[index + 1]?.role === "user" ? raw[index + 1]?.blocks.flatMap((block) => block.type === "tool_result" ? [block.tool_use_id] : []) : []);
|
|
41
|
+
const blocks = segment.blocks.filter((block) => block.type !== "tool_use" || results.has(block.id));
|
|
42
|
+
if (blocks.length !== segment.blocks.length)
|
|
43
|
+
warn("tool call without a result omitted from native projection");
|
|
44
|
+
if (blocks.length)
|
|
45
|
+
paired.push({ role: "assistant", blocks });
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const calls = new Set(raw[index - 1]?.role === "assistant" ? raw[index - 1]?.blocks.flatMap((block) => block.type === "tool_use" ? [block.id] : []) : []);
|
|
49
|
+
const blocks = segment.blocks.filter((block) => block.type === "tool_result" && calls.has(block.tool_use_id));
|
|
50
|
+
if (blocks.length !== segment.blocks.length)
|
|
51
|
+
warn("tool result without a call omitted from native projection");
|
|
52
|
+
if (blocks.length)
|
|
53
|
+
paired.push({ role: "user", blocks });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return paired;
|
|
57
|
+
}
|
|
58
|
+
const compactionText = (message) => message.content.flatMap((block) => block.type === "system_note" ? [block.text] : []).join("\n");
|
|
59
|
+
/**
|
|
60
|
+
* Shared walk for transcript formats whose records form a parent-linked chain. `write` maps one
|
|
61
|
+
* user/assistant segment to native records; ids and parent links are assigned here.
|
|
62
|
+
*/
|
|
63
|
+
export function projectChainedSession(input, target, fields, write) {
|
|
64
|
+
const warnings = [];
|
|
65
|
+
const records = [];
|
|
66
|
+
let parentId = null;
|
|
67
|
+
for (const turn of trimProjectionTurnsToCompaction(input.turns)) {
|
|
68
|
+
for (const message of turn.messages) {
|
|
69
|
+
if (isProjectionPendingGeneration(message))
|
|
70
|
+
continue;
|
|
71
|
+
const parts = isProjectionCompaction(message)
|
|
72
|
+
? compactionText(message) ? [{ role: "user", blocks: [{ type: "text", text: compactionText(message) }], compactSummary: true }] : []
|
|
73
|
+
: message.role === "system" ? [] : segments(message, warnings).map((segment) => ({ ...segment, compactSummary: false }));
|
|
74
|
+
let ordinal = 0;
|
|
75
|
+
for (const part of parts) {
|
|
76
|
+
for (const native of write({ ...part, message })) {
|
|
77
|
+
const key = `${message.id}:${message.turnId}:${ordinal++}`;
|
|
78
|
+
const id = projectionUuid(`${input.sessionId}:${key}`);
|
|
79
|
+
records.push({ key, sourceTurnId: message.turnId, sourceMessageId: message.id, record: { [fields.parent]: parentId, [fields.id]: id, ...native } });
|
|
80
|
+
parentId = id;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const last = input.turns.at(-1);
|
|
86
|
+
return { target, records, cursor: { sourceSequence: last?.sequence ?? null, sourceTurnId: last?.sourceTurnId ?? null, sourceFingerprint: fingerprintProjectionTurns(input.turns) }, warnings };
|
|
87
|
+
}
|
|
88
|
+
export const projectionMeta = (input, message) => ({
|
|
89
|
+
schema: "cohub.projection.v2", spaceId: input.spaceId, sessionId: input.sessionId, ...projectNativeMessageMeta(message),
|
|
90
|
+
});
|
|
91
|
+
export const toolResultText = (block) => typeof block.content === "string"
|
|
92
|
+
? block.content
|
|
93
|
+
: block.content.flatMap((item) => item.type === "text" ? [item.text] : []).join("\n");
|
|
94
|
+
/** Session id is stored on every record of these formats. */
|
|
95
|
+
export const rebindSessionId = (records, nativeSessionId) => records.map((entry) => ({ ...entry, record: { ...entry.record, sessionId: nativeSessionId } }));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LocalHarnessAdapter } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Claude Code names a project directory by replacing every non-alphanumeric UTF-16 unit; a longer
|
|
4
|
+
* name keeps its first 200 units and appends the base-36 Java string hash of the path.
|
|
5
|
+
*/
|
|
6
|
+
export declare function claudeProjectName(cwd: string): string;
|
|
7
|
+
export declare const claudeProjectDirectory: (cwd: string) => string;
|
|
8
|
+
export declare const claudeHarness: LocalHarnessAdapter;
|