@lobehub/cli 0.0.26 → 0.0.28

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 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", enqueuePush);
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(() => {
@@ -212147,7 +212152,7 @@ var SerialServerIngester = class {
212147
212152
  }
212148
212153
  push(event) {
212149
212154
  if (this.fatalError) return;
212150
- if (event.type === "stream_chunk" && event.data?.chunkType === "text" && typeof event.data?.content === "string") {
212155
+ if (event.type === "stream_chunk" && event.data?.chunkType === "text" && typeof event.data?.content === "string" && !event.data?.subagent) {
212151
212156
  this.accumulatedText += event.data.content;
212152
212157
  this.pendingTextEvent = event;
212153
212158
  if (this.timer) clearTimeout(this.timer);
@@ -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(spawnOpts);
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.26" "User Commands"
3
+ .TH LH 1 "" "@lobehub/cli 0.0.28" "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.26",
3
+ "version": "0.0.28",
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/device-gateway-client": "1.0.0",
35
- "@lobechat/heterogeneous-agents": "1.0.0",
36
- "@lobechat/local-file-shell": "1.0.0",
37
34
  "@lobechat/device-identity": "1.0.0",
38
35
  "@lobechat/agent-gateway-client": "1.0.0",
39
- "@lobechat/tool-runtime": "1.0.0"
36
+ "@lobechat/heterogeneous-agents": "1.0.0",
37
+ "@lobechat/tool-runtime": "1.0.0",
38
+ "@lobechat/local-file-shell": "1.0.0",
39
+ "@lobechat/device-gateway-client": "1.0.0"
40
40
  },
41
41
  "publishConfig": {
42
42
  "access": "public",