@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 +287 -133
- package/man/man1/lh.1 +1 -1
- package/package.json +5 -5
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 =
|
|
257427
|
-
|
|
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`
|
|
263496
|
-
*
|
|
263497
|
-
*
|
|
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
|
-
|
|
263616
|
-
|
|
263617
|
-
|
|
263618
|
-
|
|
263619
|
-
|
|
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("--
|
|
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
|
|
264133
|
-
|
|
264134
|
-
|
|
264135
|
-
|
|
264136
|
-
branch
|
|
264137
|
-
|
|
264138
|
-
|
|
264139
|
-
|
|
264140
|
-
|
|
264141
|
-
|
|
264142
|
-
|
|
264143
|
-
|
|
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)
|
|
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
|
|
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
|
-
|
|
264206
|
-
|
|
264207
|
-
|
|
264208
|
-
|
|
264209
|
-
|
|
264210
|
-
|
|
264211
|
-
|
|
264212
|
-
|
|
264213
|
-
|
|
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) :
|
|
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
|
-
|
|
264302
|
-
subject: subject
|
|
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
|
-
|
|
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
|
-
}
|
|
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}${
|
|
264317
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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",
|