@lobehub/cli 0.0.26 → 0.0.27
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/index.js +80 -7
- package/man/man1/lh.1 +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { EventEmitter, once } from "node:events";
|
|
4
4
|
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
5
5
|
import path, { basename, extname } from "node:path";
|
|
6
|
-
import fs, { createReadStream, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import fs, { createReadStream, createWriteStream, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
7
|
import process$1 from "node:process";
|
|
8
8
|
import crypto$1, { createHash, randomUUID } from "node:crypto";
|
|
9
9
|
import os, { platform, tmpdir } from "node:os";
|
|
@@ -211922,7 +211922,12 @@ const spawnAgent = async (options) => {
|
|
|
211922
211922
|
}
|
|
211923
211923
|
});
|
|
211924
211924
|
};
|
|
211925
|
-
stdout.on("data",
|
|
211925
|
+
stdout.on("data", (chunk) => {
|
|
211926
|
+
if (options.onRawStdout) try {
|
|
211927
|
+
options.onRawStdout(chunk);
|
|
211928
|
+
} catch {}
|
|
211929
|
+
enqueuePush(chunk);
|
|
211930
|
+
});
|
|
211926
211931
|
stdout.on("end", enqueueFlush);
|
|
211927
211932
|
stdout.on("error", (err) => {
|
|
211928
211933
|
pipelineQueue = pipelineQueue.then(() => {
|
|
@@ -212198,6 +212203,59 @@ var SerialServerIngester = class {
|
|
|
212198
212203
|
});
|
|
212199
212204
|
}
|
|
212200
212205
|
};
|
|
212206
|
+
/**
|
|
212207
|
+
* Persists the agent process's RAW stdout/stderr — the untouched stream-json,
|
|
212208
|
+
* BEFORE the adapter — to disk for post-hoc debugging. The adapted/ingested
|
|
212209
|
+
* view can't tell a CC-side empty `tool_result` apart from an adapter
|
|
212210
|
+
* extraction bug; the raw dump can.
|
|
212211
|
+
*
|
|
212212
|
+
* Enabled via `lh hetero exec --raw-dump <dir>`. Each exec gets its own
|
|
212213
|
+
* `<dir>/<timestamp>-<operationId>/` session folder; each spawn attempt (the
|
|
212214
|
+
* resume retry is a second attempt) writes `<label>.stdout.jsonl` /
|
|
212215
|
+
* `<label>.stderr.log`. Fully best-effort: any dump failure is logged and
|
|
212216
|
+
* swallowed so it never affects the run or its exit code.
|
|
212217
|
+
*
|
|
212218
|
+
* Future: the server-side sandbox runner (`spawnHeteroSandbox`) and the
|
|
212219
|
+
* desktop device path (`spawnLhHeteroExec`) can pass `--raw-dump` pointing at
|
|
212220
|
+
* a collectable location to capture remote runs the same way.
|
|
212221
|
+
*/
|
|
212222
|
+
var RawStreamDump = class RawStreamDump {
|
|
212223
|
+
constructor(dir) {
|
|
212224
|
+
this.dir = dir;
|
|
212225
|
+
}
|
|
212226
|
+
static async create(root, operationId, meta) {
|
|
212227
|
+
try {
|
|
212228
|
+
const safeTs = (/* @__PURE__ */ new Date()).toISOString().replaceAll(/[.:]/g, "-");
|
|
212229
|
+
const dir = path.join(path.resolve(root), `${safeTs}-${operationId}`);
|
|
212230
|
+
await mkdir(dir, { recursive: true });
|
|
212231
|
+
await writeFile(path.join(dir, "meta.json"), `${JSON.stringify({
|
|
212232
|
+
...meta,
|
|
212233
|
+
operationId,
|
|
212234
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
212235
|
+
}, null, 2)}\n`);
|
|
212236
|
+
log$7.info(`Raw stream dump enabled → ${dir}`);
|
|
212237
|
+
return new RawStreamDump(dir);
|
|
212238
|
+
} catch (err) {
|
|
212239
|
+
log$7.warn(`Failed to initialize raw stream dump: ${err instanceof Error ? err.message : String(err)}`);
|
|
212240
|
+
return;
|
|
212241
|
+
}
|
|
212242
|
+
}
|
|
212243
|
+
openAttempt(label) {
|
|
212244
|
+
const stdout = createWriteStream(path.join(this.dir, `${label}.stdout.jsonl`));
|
|
212245
|
+
const stderr = createWriteStream(path.join(this.dir, `${label}.stderr.log`));
|
|
212246
|
+
stdout.on("error", () => {});
|
|
212247
|
+
stderr.on("error", () => {});
|
|
212248
|
+
return {
|
|
212249
|
+
close: () => Promise.all([new Promise((resolve) => stdout.end(() => resolve())), new Promise((resolve) => stderr.end(() => resolve()))]).then(() => void 0),
|
|
212250
|
+
writeStderr: (chunk) => {
|
|
212251
|
+
stderr.write(chunk);
|
|
212252
|
+
},
|
|
212253
|
+
writeStdout: (chunk) => {
|
|
212254
|
+
stdout.write(chunk);
|
|
212255
|
+
}
|
|
212256
|
+
};
|
|
212257
|
+
}
|
|
212258
|
+
};
|
|
212201
212259
|
const exec = async (options) => {
|
|
212202
212260
|
if (!SUPPORTED_AGENT_TYPES.has(options.type)) {
|
|
212203
212261
|
log$7.error(`Unsupported --type "${options.type}". Supported: ${[...SUPPORTED_AGENT_TYPES].join(", ")}`);
|
|
@@ -212220,6 +212278,13 @@ const exec = async (options) => {
|
|
|
212220
212278
|
process.exit(2);
|
|
212221
212279
|
}
|
|
212222
212280
|
const operationId = options.operationId || randomUUID();
|
|
212281
|
+
let rawDump;
|
|
212282
|
+
if (options.rawDump) rawDump = await RawStreamDump.create(options.rawDump, operationId, {
|
|
212283
|
+
agentType: options.type,
|
|
212284
|
+
cwd: options.cwd || process.cwd(),
|
|
212285
|
+
resume: options.resume ?? null,
|
|
212286
|
+
topicId: options.topic ?? null
|
|
212287
|
+
});
|
|
212223
212288
|
const emitJsonl = options.render === "jsonl" || options.render === void 0 && !serverIngest;
|
|
212224
212289
|
const agentType = options.type;
|
|
212225
212290
|
let sink;
|
|
@@ -212245,11 +212310,16 @@ const exec = async (options) => {
|
|
|
212245
212310
|
* resumeNotFound — true when a resume-not-found error was intercepted
|
|
212246
212311
|
* stderrContent — accumulated stderr (only when interceptResumeErrors=true)
|
|
212247
212312
|
*/
|
|
212248
|
-
const runOneAgent = async (spawnOpts, interceptResumeErrors) => {
|
|
212313
|
+
const runOneAgent = async (spawnOpts, interceptResumeErrors, runLabel) => {
|
|
212314
|
+
const dumpAttempt = rawDump?.openAttempt(runLabel);
|
|
212249
212315
|
let handle;
|
|
212250
212316
|
try {
|
|
212251
|
-
handle = await spawnAgent(
|
|
212317
|
+
handle = await spawnAgent({
|
|
212318
|
+
...spawnOpts,
|
|
212319
|
+
onRawStdout: dumpAttempt?.writeStdout
|
|
212320
|
+
});
|
|
212252
212321
|
} catch (err) {
|
|
212322
|
+
await dumpAttempt?.close();
|
|
212253
212323
|
log$7.error("Failed to start agent:", err instanceof Error ? err.message : String(err));
|
|
212254
212324
|
process.exit(1);
|
|
212255
212325
|
}
|
|
@@ -212258,6 +212328,7 @@ const exec = async (options) => {
|
|
|
212258
212328
|
const stderrEnded = once(handle.stderr, "end").then(() => void 0);
|
|
212259
212329
|
handle.stderr.on("data", (chunk) => {
|
|
212260
212330
|
if (stderrContent.length < STDERR_CAP) stderrContent += chunk.toString();
|
|
212331
|
+
dumpAttempt?.writeStderr(chunk);
|
|
212261
212332
|
});
|
|
212262
212333
|
handle.stderr.pipe(process.stderr);
|
|
212263
212334
|
let interrupted = false;
|
|
@@ -212309,6 +212380,7 @@ const exec = async (options) => {
|
|
|
212309
212380
|
result: "error"
|
|
212310
212381
|
});
|
|
212311
212382
|
} catch {}
|
|
212383
|
+
await dumpAttempt?.close();
|
|
212312
212384
|
process.exit(1);
|
|
212313
212385
|
} finally {
|
|
212314
212386
|
process.off("SIGINT", onSigint);
|
|
@@ -212316,6 +212388,7 @@ const exec = async (options) => {
|
|
|
212316
212388
|
}
|
|
212317
212389
|
const { code, signal } = await handle.exit;
|
|
212318
212390
|
await stderrEnded;
|
|
212391
|
+
await dumpAttempt?.close();
|
|
212319
212392
|
if (interceptResumeErrors && !resumeNotFound && code !== 0 && looksLikeNeedsRetryWithoutResume(stderrContent)) resumeNotFound = true;
|
|
212320
212393
|
return {
|
|
212321
212394
|
code,
|
|
@@ -212334,7 +212407,7 @@ const exec = async (options) => {
|
|
|
212334
212407
|
operationId,
|
|
212335
212408
|
prompt: resolved.prompt,
|
|
212336
212409
|
resumeSessionId: options.resume
|
|
212337
|
-
}, interceptResume);
|
|
212410
|
+
}, interceptResume, "attempt-1");
|
|
212338
212411
|
let result = first;
|
|
212339
212412
|
if (first.resumeNotFound) {
|
|
212340
212413
|
log$7.info("Resume failed (session not found or context overflow) — retrying without --resume");
|
|
@@ -212344,7 +212417,7 @@ const exec = async (options) => {
|
|
|
212344
212417
|
cwd: options.cwd || process.cwd(),
|
|
212345
212418
|
operationId,
|
|
212346
212419
|
prompt: resolved.prompt
|
|
212347
|
-
}, false);
|
|
212420
|
+
}, false, "attempt-2-noresume");
|
|
212348
212421
|
}
|
|
212349
212422
|
const { code, signal, sessionId } = result;
|
|
212350
212423
|
if (serverIngester && sink) {
|
|
@@ -212380,7 +212453,7 @@ const exec = async (options) => {
|
|
|
212380
212453
|
process.exit(1);
|
|
212381
212454
|
};
|
|
212382
212455
|
function registerHeteroCommand(program) {
|
|
212383
|
-
program.command("hetero").description("Run heterogeneous agent CLIs (Claude Code / Codex) and stream their output").command("exec").description("Spawn a heterogeneous agent CLI and stream its events as JSONL on stdout. Standalone mode (no server ingest).").requiredOption("-t, --type <type>", `Agent type: ${[...SUPPORTED_AGENT_TYPES].join(" | ")}`).option("-p, --prompt [text]", "Prompt text. Pass `-` (or omit the value) to read from stdin.").option("-i, --image <path|url>", "Attach an image (repeatable). Accepts a local path, http(s) URL, or data: URL.", collectImage).option("--input-json <path>", "Read full multimodal prompt as JSON content blocks from a file. Use `-` for stdin.").option("-r, --resume <sessionId>", "Resume an existing agent session by its native id").option("-d, --cwd <path>", "Working directory for the spawned agent (default: process.cwd())").option("-c, --command <bin>", "Override the agent CLI binary name (default: `claude` or `codex`)").option("--operation-id <id>", "Operation id stamped onto every emitted event. Required in server-ingest mode (--topic). Generated as a UUID if omitted (standalone).").option("--topic <topicId>", "Server topic id. Enables server-ingest mode: events are batch-POSTed to aiAgent.heteroIngest. Requires --operation-id.").option("--render <mode>", "Output mode: jsonl (emit events as JSONL on stdout) | none (suppress stdout). Defaults to jsonl in standalone, none in server-ingest mode.").action(exec);
|
|
212456
|
+
program.command("hetero").description("Run heterogeneous agent CLIs (Claude Code / Codex) and stream their output").command("exec").description("Spawn a heterogeneous agent CLI and stream its events as JSONL on stdout. Standalone mode (no server ingest).").requiredOption("-t, --type <type>", `Agent type: ${[...SUPPORTED_AGENT_TYPES].join(" | ")}`).option("-p, --prompt [text]", "Prompt text. Pass `-` (or omit the value) to read from stdin.").option("-i, --image <path|url>", "Attach an image (repeatable). Accepts a local path, http(s) URL, or data: URL.", collectImage).option("--input-json <path>", "Read full multimodal prompt as JSON content blocks from a file. Use `-` for stdin.").option("-r, --resume <sessionId>", "Resume an existing agent session by its native id").option("-d, --cwd <path>", "Working directory for the spawned agent (default: process.cwd())").option("-c, --command <bin>", "Override the agent CLI binary name (default: `claude` or `codex`)").option("--operation-id <id>", "Operation id stamped onto every emitted event. Required in server-ingest mode (--topic). Generated as a UUID if omitted (standalone).").option("--topic <topicId>", "Server topic id. Enables server-ingest mode: events are batch-POSTed to aiAgent.heteroIngest. Requires --operation-id.").option("--render <mode>", "Output mode: jsonl (emit events as JSONL on stdout) | none (suppress stdout). Defaults to jsonl in standalone, none in server-ingest mode.").option("--raw-dump <dir>", "Persist the agent process RAW stdout/stderr (pre-adapter stream-json) under <dir>/<timestamp>-<operationId>/ for debugging. Each spawn attempt writes its own .stdout.jsonl / .stderr.log. Best-effort; never affects the run.").action(exec);
|
|
212384
212457
|
}
|
|
212385
212458
|
|
|
212386
212459
|
//#endregion
|
package/man/man1/lh.1
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.\" Code generated by `npm run man:generate`; DO NOT EDIT.
|
|
2
2
|
.\" Manual command details come from the Commander command tree.
|
|
3
|
-
.TH LH 1 "" "@lobehub/cli 0.0.
|
|
3
|
+
.TH LH 1 "" "@lobehub/cli 0.0.27" "User Commands"
|
|
4
4
|
.SH NAME
|
|
5
5
|
lh \- LobeHub CLI \- manage and connect to LobeHub services
|
|
6
6
|
.SH SYNOPSIS
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.27",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"lh": "./dist/index.js",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"tsdown": "^0.21.4",
|
|
32
32
|
"typescript": "^6.0.3",
|
|
33
33
|
"ws": "^8.18.1",
|
|
34
|
-
"@lobechat/
|
|
34
|
+
"@lobechat/agent-gateway-client": "1.0.0",
|
|
35
35
|
"@lobechat/heterogeneous-agents": "1.0.0",
|
|
36
|
+
"@lobechat/tool-runtime": "1.0.0",
|
|
36
37
|
"@lobechat/local-file-shell": "1.0.0",
|
|
37
|
-
"@lobechat/device-
|
|
38
|
-
"@lobechat/
|
|
39
|
-
"@lobechat/tool-runtime": "1.0.0"
|
|
38
|
+
"@lobechat/device-gateway-client": "1.0.0",
|
|
39
|
+
"@lobechat/device-identity": "1.0.0"
|
|
40
40
|
},
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"access": "public",
|