@lobehub/cli 0.0.43 → 0.0.44

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
@@ -15606,6 +15606,10 @@ const UserLabSchema = object$2({
15606
15606
  */
15607
15607
  enableMessageTextSelectionActions: boolean().optional(),
15608
15608
  /**
15609
+ * show OAuth app management in personal and workspace settings
15610
+ */
15611
+ enableOAuthApps: boolean().optional(),
15612
+ /**
15609
15613
  * show the "Add Platform Agent" entry in the create menu
15610
15614
  */
15611
15615
  enablePlatformAgent: boolean().optional(),
@@ -253663,7 +253667,7 @@ const ASK_USER_MCP_TOOL_NAME = "mcp__lobe_cc__ask_user_question";
253663
253667
  */
253664
253668
  const ASK_USER_API_NAME = "askUserQuestion";
253665
253669
  const CLAUDE_CODE_CLI_INSTALL_DOCS_URL = "https://docs.anthropic.com/en/docs/claude-code/setup";
253666
- const CLI_AUTH_REQUIRED_PATTERNS = [
253670
+ const CLI_AUTH_REQUIRED_PATTERNS$1 = [
253667
253671
  /failed to authenticate/i,
253668
253672
  /invalid authentication credentials/i,
253669
253673
  /authentication[_ ]error/i,
@@ -253879,7 +253883,7 @@ const getApiRetryData = (rawValue) => {
253879
253883
  };
253880
253884
  const getAuthRequiredTerminalError = (result) => {
253881
253885
  const rawMessage = getCliResultMessage(result);
253882
- if (!rawMessage || !CLI_AUTH_REQUIRED_PATTERNS.some((pattern) => pattern.test(rawMessage))) return;
253886
+ if (!rawMessage || !CLI_AUTH_REQUIRED_PATTERNS$1.some((pattern) => pattern.test(rawMessage))) return;
253883
253887
  return {
253884
253888
  agentType: "claude-code",
253885
253889
  clearEchoedContent: true,
@@ -255645,18 +255649,10 @@ const computeFileChangeDiff = async (change, cwd, snapshot) => {
255645
255649
  };
255646
255650
  return buildFileChangeDiff(filePath, previousContent, "");
255647
255651
  }
255648
- if (!snapshot) return {
255652
+ if (!snapshot?.exists || snapshot.content === void 0) return {
255649
255653
  linesAdded: change.linesAdded ?? 0,
255650
255654
  linesDeleted: change.linesDeleted ?? 0
255651
255655
  };
255652
- if (!snapshot?.exists && !current.exists) return {
255653
- linesAdded: 0,
255654
- linesDeleted: 0
255655
- };
255656
- if (snapshot?.exists && snapshot.content === void 0) return {
255657
- linesAdded: 0,
255658
- linesDeleted: 0
255659
- };
255660
255656
  if (current.exists && current.content === void 0) return {
255661
255657
  linesAdded: 0,
255662
255658
  linesDeleted: 0
@@ -255913,6 +255909,103 @@ var AgentStreamPipeline = class {
255913
255909
  }
255914
255910
  };
255915
255911
 
255912
+ //#endregion
255913
+ //#region ../../packages/heterogeneous-agents/src/spawn/classifyProcessFailure.ts
255914
+ /**
255915
+ * Process-level failure classification for `lh hetero exec` runs.
255916
+ *
255917
+ * The stream adapters (`adapters/claudeCode.ts`, `adapters/codex.ts`) classify
255918
+ * failures the CLI reports in-stream (overloaded / rate-limit / auth relayed
255919
+ * via a `result` event), but a run that dies BEFORE the agent CLI produces any
255920
+ * stream — `spawn claude ENOENT`, an auth failure printed straight to stderr —
255921
+ * never reaches an adapter. Without classification those runs land on the
255922
+ * server as a bare `{ message }` error, so the client renders the generic JSON
255923
+ * error card instead of the heterogeneous status guide (install CLI / sign in).
255924
+ *
255925
+ * This helper mirrors the desktop in-process classifier
255926
+ * (`HeterogeneousAgentCtr.getSessionErrorPayload`) for the two guide codes a
255927
+ * process-level failure can produce: `cli_not_found` and `auth_required`.
255928
+ * The returned shape is persisted verbatim as the `ChatMessageError.body`, so
255929
+ * it must carry `agentType` + `code` — that pair is what
255930
+ * `isHeterogeneousAgentStatusGuideError` gates the dedicated UI on.
255931
+ */
255932
+ /**
255933
+ * Node reports a missing executable as an `ErrnoException` with
255934
+ * `code: 'ENOENT'` and message `spawn <command> ENOENT`. When only stderr text
255935
+ * is available (the raw error object was already flattened into the stderr
255936
+ * tail), match the message shape instead. Note ENOENT is also raised for a
255937
+ * missing `cwd` — same behavior as the desktop classifier; both mean "this
255938
+ * machine can't start the CLI as configured".
255939
+ */
255940
+ const SPAWN_ENOENT_PATTERN = /\bspawn .+ ENOENT\b/;
255941
+ /** Mirrors `CLI_AUTH_REQUIRED_PATTERNS` in the desktop `HeterogeneousAgentCtr`. */
255942
+ const CLI_AUTH_REQUIRED_PATTERNS = [
255943
+ /failed to authenticate/i,
255944
+ /invalid authentication credentials/i,
255945
+ /authentication[_ ]error/i,
255946
+ /not authenticated/i,
255947
+ /\bunauthorized\b/i,
255948
+ /\b401\b/
255949
+ ];
255950
+ const CLI_NOT_FOUND_MESSAGES = {
255951
+ "claude-code": "Claude Code CLI was not found on the machine running this agent. Install it and make sure `claude` can be executed.",
255952
+ "codex": "Codex CLI was not found on the machine running this agent. Install it and make sure `codex` can be executed."
255953
+ };
255954
+ const AUTH_REQUIRED_MESSAGES = {
255955
+ "claude-code": "Claude Code could not authenticate on the machine running this agent. Sign in again or refresh its credentials, then retry.",
255956
+ "codex": "Codex could not authenticate on the machine running this agent. Sign in again or refresh its credentials, then retry."
255957
+ };
255958
+ /**
255959
+ * Codes/agent types the client renders the dedicated status-guide card for.
255960
+ * Must stay in sync with `HETEROGENEOUS_AGENT_STATUS_GUIDE_ERROR_CODES` in
255961
+ * `src/features/Conversation/Error/heterogeneous.ts` — that predicate gates the
255962
+ * guide UI on the same `agentType` + `code` pair.
255963
+ */
255964
+ const STATUS_GUIDE_ERROR_CODES = new Set([
255965
+ "auth_required",
255966
+ "cli_not_found",
255967
+ "overloaded",
255968
+ "rate_limit"
255969
+ ]);
255970
+ const STATUS_GUIDE_AGENT_TYPES = new Set([
255971
+ "amp",
255972
+ "claude-code",
255973
+ "codex"
255974
+ ]);
255975
+ /**
255976
+ * Whether a terminal error payload (an adapter's in-stream `error` event data,
255977
+ * or a persisted `ChatMessageError.body`) is a structured status-guide error —
255978
+ * i.e. carries the `agentType` + `code` pair the client's guide UI gates on.
255979
+ */
255980
+ const isHeteroStatusGuideErrorData = (value) => {
255981
+ if (!value || typeof value !== "object") return false;
255982
+ const { agentType, code } = value;
255983
+ return typeof agentType === "string" && STATUS_GUIDE_AGENT_TYPES.has(agentType) && typeof code === "string" && STATUS_GUIDE_ERROR_CODES.has(code);
255984
+ };
255985
+ /**
255986
+ * Classify a process-level run failure into a structured status-guide error,
255987
+ * or `undefined` when the failure isn't one the guide UI can act on (the
255988
+ * caller should then keep its flat `{ message }` error).
255989
+ */
255990
+ const classifyHeteroProcessFailure = (params) => {
255991
+ const { agentType, errnoCode } = params;
255992
+ const detail = params.detail?.trim();
255993
+ const cliNotFoundMessage = CLI_NOT_FOUND_MESSAGES[agentType];
255994
+ if (!cliNotFoundMessage) return;
255995
+ if (errnoCode === "ENOENT" || detail && SPAWN_ENOENT_PATTERN.test(detail)) return {
255996
+ agentType,
255997
+ code: "cli_not_found",
255998
+ message: cliNotFoundMessage,
255999
+ ...detail ? { stderr: detail } : {}
256000
+ };
256001
+ if (detail && CLI_AUTH_REQUIRED_PATTERNS.some((pattern) => pattern.test(detail))) return {
256002
+ agentType,
256003
+ code: "auth_required",
256004
+ message: AUTH_REQUIRED_MESSAGES[agentType],
256005
+ stderr: detail
256006
+ };
256007
+ };
256008
+
255916
256009
  //#endregion
255917
256010
  //#region ../../packages/heterogeneous-agents/src/spawn/cliSpawn.ts
255918
256011
  const WINDOWS_EXE_EXT_PATTERN = /\.exe$/i;
@@ -257242,6 +257335,37 @@ const exec$1 = async (options) => {
257242
257335
  })();
257243
257336
  }
257244
257337
  /**
257338
+ * Build the `finish` error payload. Process-level failures the agent CLI
257339
+ * never got to report in-stream (spawn ENOENT because the CLI isn't
257340
+ * installed, an auth failure printed straight to stderr) are classified into
257341
+ * the structured status-guide shape and attached as `body`, so the client
257342
+ * renders the dedicated install/sign-in guide instead of the generic error
257343
+ * card. Unclassifiable failures keep the flat `{ message, type }` everything
257344
+ * downstream already handles.
257345
+ *
257346
+ * A classified error is always typed `AgentRuntimeError` — matching how the
257347
+ * adapters' in-stream classified errors (overloaded / rate_limit) persist —
257348
+ * instead of leaking the transport-internal `type` the failure happened to
257349
+ * surface through (`stream_error` for a spawn ENOENT reads wrong on a
257350
+ * "CLI not installed" error).
257351
+ */
257352
+ const buildFinishError = (message, type, errnoCode) => {
257353
+ const classified = classifyHeteroProcessFailure({
257354
+ agentType,
257355
+ detail: message,
257356
+ errnoCode
257357
+ });
257358
+ if (!classified) return {
257359
+ message,
257360
+ type
257361
+ };
257362
+ return {
257363
+ body: { ...classified },
257364
+ message: classified.message,
257365
+ type: "AgentRuntimeError"
257366
+ };
257367
+ };
257368
+ /**
257245
257369
  * Spawn one agent process and stream all its events into the server ingester.
257246
257370
  *
257247
257371
  * When `interceptResumeErrors` is true, any `error`-type event whose
@@ -257261,6 +257385,12 @@ const exec$1 = async (options) => {
257261
257385
  * and still exit 0, so the exit code alone is not enough)
257262
257386
  * terminalErrorMessage — the message from that terminal `error` event, used
257263
257387
  * as the task-level error detail in the finish payload
257388
+ * terminalErrorData — the full structured payload of that terminal `error`
257389
+ * event when the adapter already classified it into a
257390
+ * status-guide error (overloaded / rate_limit / …); the
257391
+ * finish leg forwards it verbatim as the error `body` so
257392
+ * the client renders the dedicated guide instead of the
257393
+ * generic error card
257264
257394
  * stderrContent — accumulated stderr (only when interceptResumeErrors=true)
257265
257395
  */
257266
257396
  const runOneAgent = async (spawnOpts, interceptResumeErrors, runLabel) => {
@@ -257278,10 +257408,7 @@ const exec$1 = async (options) => {
257278
257408
  if (serverIngester && sink) try {
257279
257409
  await serverIngester.drain();
257280
257410
  await sink.finish({
257281
- error: {
257282
- message,
257283
- type: "AgentRuntimeError"
257284
- },
257411
+ error: buildFinishError(message, "AgentRuntimeError"),
257285
257412
  result: "error"
257286
257413
  });
257287
257414
  } catch {}
@@ -257328,6 +257455,7 @@ const exec$1 = async (options) => {
257328
257455
  let resumeNotFound = false;
257329
257456
  let sawTerminalError = false;
257330
257457
  let terminalErrorMessage;
257458
+ let terminalErrorData;
257331
257459
  const ingestError = false;
257332
257460
  try {
257333
257461
  for await (const event of handle.events) {
@@ -257343,6 +257471,7 @@ const exec$1 = async (options) => {
257343
257471
  sawTerminalError = true;
257344
257472
  const data = event.data;
257345
257473
  terminalErrorMessage = String(data?.message ?? data?.error ?? "") || void 0;
257474
+ terminalErrorData = isHeteroStatusGuideErrorData(data) ? data : void 0;
257346
257475
  }
257347
257476
  if (emitJsonl) process.stdout.write(`${JSON.stringify(event)}\n`);
257348
257477
  serverIngester?.push(event);
@@ -257352,10 +257481,7 @@ const exec$1 = async (options) => {
257352
257481
  if (serverIngester && sink) try {
257353
257482
  await serverIngester.drain();
257354
257483
  await sink.finish({
257355
- error: {
257356
- message: String(err),
257357
- type: "stream_error"
257358
- },
257484
+ error: buildFinishError(String(err), "stream_error", err?.code),
257359
257485
  result: "error"
257360
257486
  });
257361
257487
  } catch {}
@@ -257377,6 +257503,7 @@ const exec$1 = async (options) => {
257377
257503
  sessionId: handle.sessionId,
257378
257504
  signal,
257379
257505
  stderrContent,
257506
+ terminalErrorData,
257380
257507
  terminalErrorMessage
257381
257508
  };
257382
257509
  };
@@ -257423,10 +257550,11 @@ const exec$1 = async (options) => {
257423
257550
  const exitedClean = !result.ingestError && !result.sawTerminalError && (code === 0 || signal === "SIGTERM");
257424
257551
  const stderrTail = result.stderrContent.trim();
257425
257552
  const errorDetail = result.terminalErrorMessage || stderrTail;
257426
- const finishError = !exitedClean && errorDetail ? {
257427
- message: errorDetail.slice(-1024),
257553
+ const finishError = exitedClean ? void 0 : result.terminalErrorData ? {
257554
+ body: { ...result.terminalErrorData },
257555
+ message: String(result.terminalErrorData.message ?? errorDetail ?? ""),
257428
257556
  type: "AgentRuntimeError"
257429
- } : void 0;
257557
+ } : errorDetail ? buildFinishError(errorDetail.slice(-1024), "AgentRuntimeError") : void 0;
257430
257558
  try {
257431
257559
  await sink.finish({
257432
257560
  error: finishError,
@@ -263078,6 +263206,22 @@ function registerUserCommand(program) {
263078
263206
  //#endregion
263079
263207
  //#region ../../packages/const/src/verify.ts
263080
263208
  /**
263209
+ * The kind of thing a verification session checks. Orthogonal to `source` (which
263210
+ * records what *produced* the run): `scenario` drives how the report renders its
263211
+ * scope header and scenario-specific detail. Open-ended — new scenarios add a
263212
+ * value here plus their own `VerifyRunContext` shape.
263213
+ * - coding: verifying a software change (branch / commit / surfaces under test).
263214
+ * - writing: verifying a written deliverable (manuscript / chapters / documents).
263215
+ * - research: verifying a research deliverable (question / sources / claims).
263216
+ * - generic: any other delivery — no modeled scope; context is an open bag.
263217
+ */
263218
+ const verifyRunScenarios = [
263219
+ "coding",
263220
+ "writing",
263221
+ "research",
263222
+ "generic"
263223
+ ];
263224
+ /**
263081
263225
  * The product surface a check was exercised on — *where* it ran, never *what
263082
263226
  * kind* of test it was. `unit` / `backend` / `type-check` are test kinds and do
263083
263227
  * not belong here; a backend change verified through the CLI has surface `cli`.
@@ -263163,6 +263307,12 @@ const statusColor$1 = (status) => {
263163
263307
  if (status === "delivered") return import_picocolors.default.yellow(status);
263164
263308
  return import_picocolors.default.dim(status);
263165
263309
  };
263310
+ /** The user's standing verdict on one union check, for the USER column. */
263311
+ const userGlyph = (userReview) => {
263312
+ if (!userReview) return import_picocolors.default.dim("· pending");
263313
+ if (userReview.action === "accept") return import_picocolors.default.green("✓ accepted");
263314
+ return userReview.stale ? import_picocolors.default.dim("· pending") : import_picocolors.default.red("✗ rejected");
263315
+ };
263166
263316
  function registerAcceptanceCommands(verify) {
263167
263317
  const acceptance = verify.command("acceptance").description("Manage subject-level delivery acceptances (the cross-round union of verify sessions)");
263168
263318
  acceptance.command("list").description("List acceptances, newest first").option("--json [fields]", "Output JSON").action(async (options) => {
@@ -263203,20 +263353,36 @@ function registerAcceptanceCommands(verify) {
263203
263353
  if (latestReport?.summary) console.log(`${import_picocolors.default.bold("summary")}: ${latestReport.summary}`);
263204
263354
  console.log();
263205
263355
  printTable(checks.map((c) => [
263356
+ `C${c.seq}`,
263206
263357
  truncate(c.title, 46),
263207
263358
  stateGlyph(c.state),
263359
+ userGlyph(c.userReview),
263208
263360
  c.required ? "gate" : "soft",
263209
263361
  c.surface ?? "",
263210
263362
  c.fixed ? "fixed" : c.carriedFromRound !== void 0 ? `carried r${c.carriedFromRound}` : "",
263211
263363
  c.history.map((h) => `r${h.roundIndex}:${h.state}`).join(" → ")
263212
263364
  ]), [
263365
+ "C#",
263213
263366
  "CHECK",
263214
263367
  "FINAL",
263368
+ "USER",
263215
263369
  "BLOCK",
263216
263370
  "SURFACE",
263217
263371
  "NOTE",
263218
263372
  "HISTORY"
263219
263373
  ]);
263374
+ const withFeedback = checks.filter((c) => c.reviews.some((r) => r.action === "reject"));
263375
+ if (withFeedback.length > 0) {
263376
+ console.log();
263377
+ console.log(import_picocolors.default.bold("user feedback"));
263378
+ for (const c of withFeedback) for (const review of c.reviews) {
263379
+ if (review.action !== "reject") continue;
263380
+ const marker = (c.userReview && !c.userReview.stale ? c.reviews.at(-1) === review : false) ? import_picocolors.default.red("▶ actionable") : import_picocolors.default.dim("· addressed");
263381
+ console.log(` ${marker} C${c.seq} ${truncate(c.title, 60)} ${import_picocolors.default.dim(`[${c.id}] (r${review.roundIndex})`)}`);
263382
+ if (review.comment) console.log(` ${review.comment}`);
263383
+ for (const annotation of review.annotations ?? []) if (annotation.comment) console.log(` ${import_picocolors.default.dim("region:")} ${annotation.comment}`);
263384
+ }
263385
+ }
263220
263386
  console.log();
263221
263387
  printTable(rounds.map((r) => [
263222
263388
  `r${r.run.roundIndex}`,
@@ -263312,6 +263478,18 @@ function toVerdict(raw) {
263312
263478
  ].includes(s)) return "failed";
263313
263479
  return "uncertain";
263314
263480
  }
263481
+ /**
263482
+ * The report's headline verdict when the author didn't set `summary.verdict`:
263483
+ * derived from the ingested cases, so a report can never ship verdict-less and
263484
+ * render as a permanent "?" in every list surface.
263485
+ */
263486
+ function deriveReportVerdict(cases) {
263487
+ const verdicts = cases.map((c) => toVerdict(c?.result ?? c?.verdict));
263488
+ if (verdicts.length === 0) return void 0;
263489
+ if (verdicts.includes("failed")) return "failed";
263490
+ if (verdicts.includes("uncertain")) return "uncertain";
263491
+ return "passed";
263492
+ }
263315
263493
  /** Pick an evidence medium from a file extension. */
263316
263494
  function evidenceTypeForFile(file) {
263317
263495
  const ext = path.extname(file).toLowerCase().slice(1);
@@ -263466,6 +263644,35 @@ function surfacesFromResult(result) {
263466
263644
  }
263467
263645
  return canonical.length > 0 ? canonical : void 0;
263468
263646
  }
263647
+ /**
263648
+ * What kind of delivery the report verified. Defaults to `coding` — the
263649
+ * agent-testing harness's home turf — and rejects an unknown value rather than
263650
+ * storing a scenario nothing renders (mirrors the strict surface policy: the
263651
+ * author still has the context to fix it).
263652
+ */
263653
+ function scenarioFromResult(result) {
263654
+ const raw = result.scenario;
263655
+ if (raw === void 0) return "coding";
263656
+ if (typeof raw === "string" && verifyRunScenarios.includes(raw)) return raw;
263657
+ log$10.error(`result.json "scenario" must be one of: ${verifyRunScenarios.join(", ")} — rejected: ${JSON.stringify(raw)}`);
263658
+ process.exit(1);
263659
+ }
263660
+ /**
263661
+ * A non-coding scenario's scope, passed through from result.json `context` as
263662
+ * the scenario's own bag (the server stores it as-is; known shapes live in
263663
+ * `@lobechat/types`). Top-level `entry` / `createdAt` are lifted as defaults so
263664
+ * every scenario gets the shared provenance fields for free; explicit `context`
263665
+ * keys win.
263666
+ */
263667
+ function genericContextFromResult(result) {
263668
+ const bag = objectValue(result.context) ?? {};
263669
+ const entries = Object.entries({
263670
+ entry: firstString(result.entry),
263671
+ testedAt: firstString(result.createdAt),
263672
+ ...bag
263673
+ }).filter(([, v]) => v !== void 0);
263674
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
263675
+ }
263469
263676
  /** Reject an out-of-vocabulary plan value rather than storing a word nothing reads. */
263470
263677
  function assertPlanEnum(value, allowed, field, itemId) {
263471
263678
  if (allowed.includes(value)) return value;
@@ -263492,12 +263699,9 @@ function assertPlanEnum(value, allowed, field, itemId) {
263492
263699
  * no enum can carry. Everything else the frozen {@link VerifyCheckItem} needs is
263493
263700
  * filled in here, so an author writes only `{ id, title }` plus what they mean.
263494
263701
  *
263495
- * Returns `undefined` only when the report declares no `plan` field at all —
263496
- * "leave whatever is stored alone". A `plan` that IS present but empty returns
263497
- * `[]`, which CLEARS the stored one. The distinction matters on a re-ingest of a
263498
- * reused report dir: a previous round's plan would otherwise stay attached, and
263499
- * every one of its items would render as "not run" against a report that never
263500
- * planned them.
263702
+ * Returns `undefined` when the report declares no `plan` field. A `plan` that is
263703
+ * present but empty returns `[]`, recording an explicitly empty plan in this
263704
+ * immutable snapshot.
263501
263705
  */
263502
263706
  function planFromResult(result) {
263503
263707
  if (!Array.isArray(result.plan)) return void 0;
@@ -263611,28 +263815,13 @@ function subjectFromResult(result) {
263611
263815
  requirement: firstString(value.requirement)
263612
263816
  } : null;
263613
263817
  }
263614
- /**
263615
- * The report dir remembers which verification session it created, so
263616
- * re-verifying the same case updates one evolving `/verify/<id>` in place
263617
- * instead of spawning a fresh list entry every round. Kept in a sidecar (not
263618
- * result.json, which the harness regenerates each round) so it survives a
263619
- * rewrite of the report body.
263620
- */
263621
- const RUN_SIDECAR = ".verify-run.json";
263622
- function readSidecarRunId(dir) {
263623
- const p = path.join(dir, RUN_SIDECAR);
263624
- if (!existsSync(p)) return void 0;
263625
- try {
263626
- const parsed = JSON.parse(readFileSync$1(p, "utf8"));
263627
- return typeof parsed?.verifyRunId === "string" ? parsed.verifyRunId : void 0;
263628
- } catch {
263629
- return;
263630
- }
263631
- }
263632
- function writeSidecarRunId(dir, verifyRunId) {
263633
- try {
263634
- writeFileSync(path.join(dir, RUN_SIDECAR), `${JSON.stringify({ verifyRunId }, null, 2)}\n`);
263635
- } catch {}
263818
+ /** Default acceptance subject for a report authored inside a LobeHub topic. */
263819
+ function subjectFromEnv() {
263820
+ const topicId = firstString(process.env.LOBEHUB_TOPIC_ID);
263821
+ return topicId ? {
263822
+ subjectId: topicId,
263823
+ subjectType: "topic"
263824
+ } : null;
263636
263825
  }
263637
263826
  function registerVerifyCommand(program) {
263638
263827
  const verify = program.command("verify").description("Manage the Agent Run delivery checker (criteria, rubrics, plans, results)");
@@ -264111,7 +264300,7 @@ function registerVerifyCommand(program) {
264111
264300
  });
264112
264301
  console.log(`${import_picocolors.default.green("✓")} Recorded ${import_picocolors.default.bold(decision)} on result ${import_picocolors.default.bold(resultId)}`);
264113
264302
  });
264114
- verify.command("ingest-report <reportDir>").description("Ingest a local agent-testing report (result.json + report.md + assets) as a verify session").option("--source <source>", "agent | agent-testing", "agent-testing").option("--operation <id>", "Link the session to an existing Agent Run").option("--title <title>", "Override the session title").option("--goal <goal>", "The goal/task being verified").option("--run <verifyRunId>", "Update an existing session in place instead of creating one").option("--new", "Force a fresh session even if this report dir already created one").option("--subject <type:id>", "Chain this session onto the subject's acceptance as its next round (task:<id> | topic:<id> | document:<id>)").option("--requirement <text>", "Acceptance requirement recorded when the aggregate is first created").option("--open", "Print the in-app URL to open the report").option("--json [fields]", "Output JSON").action(async (reportDir, options) => {
264303
+ verify.command("ingest-report <reportDir>").description("Ingest a local agent-testing report (result.json + report.md + assets) as a verify session").option("--source <source>", "agent | agent-testing", "agent-testing").option("--operation <id>", "Link the session to an existing Agent Run").option("--title <title>", "Override the session title").option("--goal <goal>", "The goal/task being verified").option("--subject <type:id>", "Override the required acceptance subject (defaults to the current LOBEHUB_TOPIC_ID)").option("--requirement <text>", "Acceptance requirement recorded when the aggregate is first created").option("--open", "Print the in-app URL to open the report").option("--json [fields]", "Output JSON").action(async (reportDir, options) => {
264115
264304
  const dir = path.resolve(reportDir);
264116
264305
  const resultPath = path.join(dir, "result.json");
264117
264306
  if (!existsSync(resultPath)) {
@@ -264129,20 +264318,24 @@ function registerVerifyCommand(program) {
264129
264318
  const summary = result.summary ?? {};
264130
264319
  const reportMdPath = path.join(dir, "report.md");
264131
264320
  const content = existsSync(reportMdPath) ? readFileSync$1(reportMdPath, "utf8") : void 0;
264132
- const branch = typeof result.branch === "string" ? result.branch : void 0;
264133
- const surfaces = surfacesFromResult(result);
264134
- const pullRequest = pullRequestFromResult(result) ?? pullRequestFromBranch(branch);
264135
- const contextEntries = Object.entries({
264136
- branch,
264137
- commit: typeof result.commit === "string" ? result.commit : void 0,
264138
- entry: typeof result.entry === "string" ? result.entry : void 0,
264139
- pullRequest,
264140
- surfaces,
264141
- testedAt: typeof result.createdAt === "string" ? result.createdAt : void 0
264142
- }).filter(([, v]) => v !== void 0);
264143
- const context = contextEntries.length > 0 ? Object.fromEntries(contextEntries) : void 0;
264321
+ const scenario = scenarioFromResult(result);
264322
+ let context;
264323
+ let pullRequest;
264324
+ if (scenario === "coding") {
264325
+ const branch = typeof result.branch === "string" ? result.branch : void 0;
264326
+ const surfaces = surfacesFromResult(result);
264327
+ pullRequest = pullRequestFromResult(result) ?? pullRequestFromBranch(branch);
264328
+ const contextEntries = Object.entries({
264329
+ branch,
264330
+ commit: typeof result.commit === "string" ? result.commit : void 0,
264331
+ entry: typeof result.entry === "string" ? result.entry : void 0,
264332
+ pullRequest,
264333
+ surfaces,
264334
+ testedAt: typeof result.createdAt === "string" ? result.createdAt : void 0
264335
+ }).filter(([, v]) => v !== void 0);
264336
+ context = contextEntries.length > 0 ? Object.fromEntries(contextEntries) : void 0;
264337
+ } else context = genericContextFromResult(result);
264144
264338
  const plan = planFromResult(result);
264145
- const scenario = result.scenario === "coding" ? "coding" : "coding";
264146
264339
  let subject = subjectFromResult(result);
264147
264340
  if (options.subject) {
264148
264341
  const ref = parseSubjectRef(options.subject);
@@ -264154,45 +264347,25 @@ function registerVerifyCommand(program) {
264154
264347
  ref,
264155
264348
  requirement: subject?.requirement
264156
264349
  };
264157
- } else if (result.subject && !subject) log$10.warn("result.json `subject` is malformed — ignoring it (expected \"type:id\" or {type,id})");
264350
+ } else if (result.subject && !subject) {
264351
+ log$10.error("result.json `subject` is malformed (expected \"type:id\" or {type,id})");
264352
+ process.exit(1);
264353
+ } else if (!subject) {
264354
+ const ref = subjectFromEnv();
264355
+ if (ref) subject = { ref };
264356
+ }
264357
+ if (!subject) {
264358
+ log$10.error("Acceptance subject is required: run inside a LobeHub topic or pass --subject task:<id> | topic:<id> | document:<id>");
264359
+ process.exit(1);
264360
+ }
264158
264361
  const requirement = options.requirement ?? subject?.requirement;
264159
264362
  const client = await getTrpcClient();
264160
264363
  const goal = options.goal ?? (typeof result.focus === "string" ? result.focus : void 0);
264161
264364
  const title = options.title ?? result.title;
264365
+ if (!title) log$10.warn("result.json has no \"title\" — the run will list as untitled; set result.title (or pass --title)");
264162
264366
  const origin = originFromEnv();
264163
264367
  const newRunMetadata = metadataForReport(result, void 0, origin);
264164
- const rememberedRunId = options.new ? void 0 : options.run ?? readSidecarRunId(dir);
264165
- let runId;
264166
- let reused = false;
264167
- if (rememberedRunId) {
264168
- const existing = await client.verify.getRun.query({ verifyRunId: rememberedRunId });
264169
- if (existing && existing.userDecision) {
264170
- if (options.run) {
264171
- log$10.error(`Session ${options.run} already carries a user decision (${existing.userDecision}) — drop --run so a new round is created`);
264172
- process.exit(1);
264173
- }
264174
- log$10.info(`Recorded session was already decided (${existing.userDecision}) — starting a new round`);
264175
- } else if (existing) {
264176
- reused = true;
264177
- runId = existing.id;
264178
- const metadata = metadataForReport(result, existing.metadata, origin);
264179
- await client.verify.updateRun.mutate({
264180
- value: {
264181
- context,
264182
- goal,
264183
- metadata,
264184
- plan,
264185
- scenario,
264186
- title
264187
- },
264188
- verifyRunId: runId
264189
- });
264190
- } else if (options.run) {
264191
- log$10.error(`Verification session not found: ${options.run}`);
264192
- process.exit(1);
264193
- } else log$10.warn(`Recorded session ${rememberedRunId} no longer exists — creating a new one`);
264194
- }
264195
- if (!reused) runId = (await client.verify.createRun.mutate({
264368
+ const runId = (await client.verify.createRun.mutate({
264196
264369
  context,
264197
264370
  goal,
264198
264371
  metadata: newRunMetadata,
@@ -264202,19 +264375,15 @@ function registerVerifyCommand(program) {
264202
264375
  source: options.source,
264203
264376
  title
264204
264377
  })).id;
264205
- let acceptanceId;
264206
- if (subject) {
264207
- const acceptance = await client.acceptance.ensure.mutate({
264208
- requirement,
264209
- subjectId: subject.ref.subjectId,
264210
- subjectType: subject.ref.subjectType
264211
- });
264212
- acceptanceId = acceptance.id;
264213
- await client.acceptance.attachRun.mutate({
264214
- acceptanceId: acceptance.id,
264215
- verifyRunId: runId
264216
- });
264217
- }
264378
+ const acceptanceId = (await client.acceptance.ensure.mutate({
264379
+ requirement,
264380
+ subjectId: subject.ref.subjectId,
264381
+ subjectType: subject.ref.subjectType
264382
+ })).id;
264383
+ await client.acceptance.attachRun.mutate({
264384
+ acceptanceId,
264385
+ verifyRunId: runId
264386
+ });
264218
264387
  const seenCheckItemIds = /* @__PURE__ */ new Set();
264219
264388
  let evidenceCount = 0;
264220
264389
  let inlined = 0;
@@ -264234,10 +264403,6 @@ function registerVerifyCommand(program) {
264234
264403
  verifierType: "agent",
264235
264404
  verifyRunId: runId
264236
264405
  });
264237
- if (reused) {
264238
- const prior = await client.verify.listEvidence.query({ checkResultId: checkResult.id });
264239
- for (const ev of prior) await client.verify.deleteEvidence.mutate({ id: ev.id });
264240
- }
264241
264406
  for (const evidenceInput of reportEvidence(c.evidence)) {
264242
264407
  const rel = evidenceInput.path;
264243
264408
  const abs = path.isAbsolute(rel) ? rel : path.join(dir, rel);
@@ -264275,18 +264440,9 @@ function registerVerifyCommand(program) {
264275
264440
  summary: conclusion,
264276
264441
  totalChecks: summary.total ?? cases.length,
264277
264442
  uncertainChecks: (summary.blocked ?? 0) + (summary.uncertain ?? 0) || void 0,
264278
- verdict: summary.verdict ? toVerdict(summary.verdict) : void 0,
264443
+ verdict: summary.verdict ? toVerdict(summary.verdict) : deriveReportVerdict(cases),
264279
264444
  verifyRunId: runId
264280
264445
  });
264281
- let pruned = 0;
264282
- if (reused) {
264283
- const existingResults = await client.verify.listResultsByRun.query({ verifyRunId: runId });
264284
- for (const r of existingResults) if (!seenCheckItemIds.has(r.checkItemId)) {
264285
- await client.verify.deleteResult.mutate({ id: r.id });
264286
- pruned += 1;
264287
- }
264288
- }
264289
- writeSidecarRunId(dir, runId);
264290
264446
  const unplanned = plan?.length ? [...seenCheckItemIds].filter((id) => !plan.some((item) => item.id === id)) : [];
264291
264447
  if (options.json !== void 0) {
264292
264448
  outputJson({
@@ -264296,28 +264452,26 @@ function registerVerifyCommand(program) {
264296
264452
  inlined,
264297
264453
  origin,
264298
264454
  planItems: plan?.length ?? 0,
264299
- pruned,
264300
264455
  pullRequest,
264301
- reused,
264302
- subject: subject?.ref,
264456
+ scenario,
264457
+ subject: subject.ref,
264303
264458
  unplanned,
264304
264459
  verifyRunId: runId
264305
264460
  }, typeof options.json === "string" ? options.json : void 0);
264306
264461
  return;
264307
264462
  }
264308
- const verb = reused ? "Updated" : "Ingested";
264309
- console.log(`${import_picocolors.default.green("✓")} ${verb} ${import_picocolors.default.bold(String(cases.length))} case(s), ${import_picocolors.default.bold(String(evidenceCount))} evidence artifact(s)${inlined > 0 ? `, ${import_picocolors.default.bold(String(inlined))} inline` : ""}${pruned > 0 ? `, pruned ${import_picocolors.default.bold(String(pruned))} stale case(s)` : ""}`);
264463
+ console.log(`${import_picocolors.default.green("✓")} Ingested ${import_picocolors.default.bold(String(cases.length))} case(s), ${import_picocolors.default.bold(String(evidenceCount))} evidence artifact(s)${inlined > 0 ? `, ${import_picocolors.default.bold(String(inlined))} inline` : ""}`);
264310
264464
  if (plan?.length) {
264311
264465
  const unexecuted = plan.filter((item) => !seenCheckItemIds.has(item.id));
264312
264466
  console.log(`${import_picocolors.default.bold("plan")}: ${plan.length} item(s)${unexecuted.length > 0 ? import_picocolors.default.yellow(` — ${unexecuted.length} planned but not executed`) : ""}${unplanned.length > 0 ? import_picocolors.default.dim(` — ${unplanned.length} unplanned case(s)`) : ""}`);
264313
- } else if (plan && reused) console.log(`${import_picocolors.default.bold("plan")}: ${import_picocolors.default.dim("none — cleared the previously stored plan")}`);
264467
+ }
264314
264468
  if (pullRequest?.url) console.log(`${import_picocolors.default.bold("pr")}: ${pullRequest.url}`);
264315
264469
  if (origin?.topicId) console.log(`${import_picocolors.default.bold("origin topic")}: ${origin.topicId}`);
264316
- console.log(`${import_picocolors.default.bold("verifyRunId")}: ${runId}${reused ? import_picocolors.default.dim(" (updated in place)") : ""}`);
264317
- if (acceptanceId) console.log(`${import_picocolors.default.bold("acceptance")}: ${acceptanceId} ${import_picocolors.default.dim(`(${subject.ref.subjectType}:${subject.ref.subjectId})`)}`);
264470
+ console.log(`${import_picocolors.default.bold("verifyRunId")}: ${runId} ${import_picocolors.default.dim("(immutable snapshot)")}`);
264471
+ console.log(`${import_picocolors.default.bold("acceptance")}: ${acceptanceId} ${import_picocolors.default.dim(`(${subject.ref.subjectType}:${subject.ref.subjectId})`)}`);
264318
264472
  if (options.open) {
264319
264473
  console.log(`${import_picocolors.default.bold("open")}: /verify/${runId}`);
264320
- if (acceptanceId) console.log(`${import_picocolors.default.bold("open acceptance")}: /acceptance/${acceptanceId}`);
264474
+ console.log(`${import_picocolors.default.bold("open acceptance")}: /acceptance/${acceptanceId}`);
264321
264475
  }
264322
264476
  });
264323
264477
  }
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.43" "User Commands"
3
+ .TH LH 1 "" "@lobehub/cli 0.0.44" "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.43",
3
+ "version": "0.0.44",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "lh": "./dist/index.js",
@@ -35,14 +35,14 @@
35
35
  "typescript": "^6.0.3",
36
36
  "ws": "^8.21.0",
37
37
  "@lobechat/agent-gateway-client": "1.0.0",
38
- "@lobechat/device-gateway-client": "1.0.0",
39
38
  "@lobechat/device-control": "1.0.0",
39
+ "@lobechat/heterogeneous-agents": "1.0.0",
40
+ "@lobechat/device-gateway-client": "1.0.0",
41
+ "@lobechat/device-identity": "1.0.0",
40
42
  "@lobechat/const": "1.0.0",
41
43
  "@lobechat/local-file-shell": "1.0.0",
42
44
  "@lobechat/tool-runtime": "1.0.0",
43
- "@lobechat/types": "1.0.0",
44
- "@lobechat/heterogeneous-agents": "1.0.0",
45
- "@lobechat/device-identity": "1.0.0"
45
+ "@lobechat/types": "1.0.0"
46
46
  },
47
47
  "publishConfig": {
48
48
  "access": "public",