@lobehub/cli 0.0.34 → 0.0.36
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 +285 -77
- 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, { constants, createWriteStream, existsSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
6
|
+
import fs, { constants, createWriteStream, existsSync, mkdirSync, readFileSync, realpathSync, statSync, 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";
|
|
@@ -5217,7 +5217,8 @@ const CLI_API_KEY_ENV = "LOBEHUB_CLI_API_KEY";
|
|
|
5217
5217
|
|
|
5218
5218
|
//#endregion
|
|
5219
5219
|
//#region src/api/client.ts
|
|
5220
|
-
|
|
5220
|
+
const PERSONAL_KEY = "__personal__";
|
|
5221
|
+
const _clients = /* @__PURE__ */ new Map();
|
|
5221
5222
|
let _toolsClient;
|
|
5222
5223
|
async function getAuthAndServer() {
|
|
5223
5224
|
const envJwt = process.env.LOBEHUB_JWT;
|
|
@@ -5247,15 +5248,36 @@ async function getAuthAndServer() {
|
|
|
5247
5248
|
serverUrl
|
|
5248
5249
|
};
|
|
5249
5250
|
}
|
|
5250
|
-
|
|
5251
|
-
|
|
5251
|
+
/**
|
|
5252
|
+
* Resolve the workspace scope for outbound tRPC calls.
|
|
5253
|
+
*
|
|
5254
|
+
* Precedence: explicit caller arg → `LOBEHUB_WORKSPACE_ID` env (inherited
|
|
5255
|
+
* from a workspace-dispatched parent process, e.g. openclaw spawned by the
|
|
5256
|
+
* device's `runHeteroTask`) → personal mode. Without this, agentNotify
|
|
5257
|
+
* callbacks on workspace topics would resolve through personal-mode
|
|
5258
|
+
* TopicModel and 404.
|
|
5259
|
+
*/
|
|
5260
|
+
function resolveWorkspaceId(explicit) {
|
|
5261
|
+
if (explicit) return explicit;
|
|
5262
|
+
const fromEnv = process.env.LOBEHUB_WORKSPACE_ID;
|
|
5263
|
+
return fromEnv && fromEnv.length > 0 ? fromEnv : void 0;
|
|
5264
|
+
}
|
|
5265
|
+
async function getTrpcClient(workspaceId) {
|
|
5266
|
+
const wsId = resolveWorkspaceId(workspaceId);
|
|
5267
|
+
const cacheKey = wsId ?? PERSONAL_KEY;
|
|
5268
|
+
const cached = _clients.get(cacheKey);
|
|
5269
|
+
if (cached) return cached;
|
|
5252
5270
|
const { headers, serverUrl } = await getAuthAndServer();
|
|
5253
|
-
|
|
5254
|
-
headers
|
|
5271
|
+
const client = createTRPCClient({ links: [httpLink({
|
|
5272
|
+
headers: wsId ? {
|
|
5273
|
+
...headers,
|
|
5274
|
+
"X-Workspace-Id": wsId
|
|
5275
|
+
} : headers,
|
|
5255
5276
|
transformer: SuperJSON,
|
|
5256
5277
|
url: `${serverUrl}/trpc/lambda`
|
|
5257
5278
|
})] });
|
|
5258
|
-
|
|
5279
|
+
_clients.set(cacheKey, client);
|
|
5280
|
+
return client;
|
|
5259
5281
|
}
|
|
5260
5282
|
/**
|
|
5261
5283
|
* Build a Lambda tRPC client from an already-resolved auth context, without
|
|
@@ -5264,9 +5286,12 @@ async function getTrpcClient() {
|
|
|
5264
5286
|
* via env/stored creds and `process.exit(1)` when none exist, which would
|
|
5265
5287
|
* abort an otherwise-valid explicit-token session.
|
|
5266
5288
|
*/
|
|
5267
|
-
function createLambdaClient(auth) {
|
|
5289
|
+
function createLambdaClient(auth, workspaceId) {
|
|
5268
5290
|
return createTRPCClient({ links: [httpLink({
|
|
5269
|
-
headers:
|
|
5291
|
+
headers: {
|
|
5292
|
+
...auth.tokenType === "apiKey" ? { "X-API-Key": auth.token } : { "Oidc-Auth": auth.token },
|
|
5293
|
+
...workspaceId ? { "X-Workspace-Id": workspaceId } : {}
|
|
5294
|
+
},
|
|
5270
5295
|
transformer: SuperJSON,
|
|
5271
5296
|
url: `${auth.serverUrl}/trpc/lambda`
|
|
5272
5297
|
})] });
|
|
@@ -7642,7 +7667,9 @@ const HotkeyEnum = {
|
|
|
7642
7667
|
DeleteLastMessage: "deleteLastMessage",
|
|
7643
7668
|
EditMessage: "editMessage",
|
|
7644
7669
|
NavigateToChat: "navigateToChat",
|
|
7670
|
+
NextTab: "nextTab",
|
|
7645
7671
|
OpenChatSettings: "openChatSettings",
|
|
7672
|
+
PrevTab: "prevTab",
|
|
7646
7673
|
OpenHotkeyHelper: "openHotkeyHelper",
|
|
7647
7674
|
RegenerateMessage: "regenerateMessage",
|
|
7648
7675
|
SaveDocument: "saveDocument",
|
|
@@ -7650,6 +7677,7 @@ const HotkeyEnum = {
|
|
|
7650
7677
|
Search: "search",
|
|
7651
7678
|
ShowApp: "showApp",
|
|
7652
7679
|
SwitchAgent: "switchAgent",
|
|
7680
|
+
SwitchTab: "switchTab",
|
|
7653
7681
|
ToggleLeftPanel: "toggleLeftPanel",
|
|
7654
7682
|
ToggleRightPanel: "toggleRightPanel"
|
|
7655
7683
|
};
|
|
@@ -7683,6 +7711,31 @@ const HOTKEYS_REGISTRATION = [
|
|
|
7683
7711
|
nonEditable: true,
|
|
7684
7712
|
scopes: [HotkeyScopeEnum.Global]
|
|
7685
7713
|
},
|
|
7714
|
+
{
|
|
7715
|
+
group: HotkeyGroupEnum.Essential,
|
|
7716
|
+
id: HotkeyEnum.SwitchTab,
|
|
7717
|
+
keys: combineKeys([KeyEnum.Mod, KeyEnum.Number]),
|
|
7718
|
+
nonEditable: true,
|
|
7719
|
+
scopes: [HotkeyScopeEnum.Global]
|
|
7720
|
+
},
|
|
7721
|
+
{
|
|
7722
|
+
group: HotkeyGroupEnum.Essential,
|
|
7723
|
+
id: HotkeyEnum.NextTab,
|
|
7724
|
+
keys: combineKeys([KeyEnum.Ctrl, KeyEnum.Tab]),
|
|
7725
|
+
nonEditable: true,
|
|
7726
|
+
scopes: [HotkeyScopeEnum.Global]
|
|
7727
|
+
},
|
|
7728
|
+
{
|
|
7729
|
+
group: HotkeyGroupEnum.Essential,
|
|
7730
|
+
id: HotkeyEnum.PrevTab,
|
|
7731
|
+
keys: combineKeys([
|
|
7732
|
+
KeyEnum.Ctrl,
|
|
7733
|
+
KeyEnum.Shift,
|
|
7734
|
+
KeyEnum.Tab
|
|
7735
|
+
]),
|
|
7736
|
+
nonEditable: true,
|
|
7737
|
+
scopes: [HotkeyScopeEnum.Global]
|
|
7738
|
+
},
|
|
7686
7739
|
{
|
|
7687
7740
|
group: HotkeyGroupEnum.Essential,
|
|
7688
7741
|
id: HotkeyEnum.NavigateToChat,
|
|
@@ -8755,7 +8808,7 @@ const MEMORY_SEARCH_TOP_K_LIMITS = {
|
|
|
8755
8808
|
|
|
8756
8809
|
//#endregion
|
|
8757
8810
|
//#region ../../package.json
|
|
8758
|
-
var version$2 = "2.2.
|
|
8811
|
+
var version$2 = "2.2.7";
|
|
8759
8812
|
|
|
8760
8813
|
//#endregion
|
|
8761
8814
|
//#region ../../packages/const/src/version.ts
|
|
@@ -204262,6 +204315,7 @@ var GatewayClient = class extends EventEmitter {
|
|
|
204262
204315
|
token;
|
|
204263
204316
|
tokenType;
|
|
204264
204317
|
userId;
|
|
204318
|
+
workspaceId;
|
|
204265
204319
|
serverUrl;
|
|
204266
204320
|
logger;
|
|
204267
204321
|
autoReconnect;
|
|
@@ -204275,6 +204329,7 @@ var GatewayClient = class extends EventEmitter {
|
|
|
204275
204329
|
this.channel = options.channel;
|
|
204276
204330
|
this.serverUrl = options.serverUrl;
|
|
204277
204331
|
this.userId = options.userId;
|
|
204332
|
+
this.workspaceId = options.workspaceId;
|
|
204278
204333
|
this.logger = options.logger || noopLogger;
|
|
204279
204334
|
this.autoReconnect = options.autoReconnect ?? true;
|
|
204280
204335
|
}
|
|
@@ -204380,7 +204435,8 @@ var GatewayClient = class extends EventEmitter {
|
|
|
204380
204435
|
platform: process.platform
|
|
204381
204436
|
});
|
|
204382
204437
|
if (this.channel) params.set("channel", this.channel);
|
|
204383
|
-
if (this.
|
|
204438
|
+
if (this.workspaceId) params.set("workspaceId", this.workspaceId);
|
|
204439
|
+
else if (this.userId) params.set("userId", this.userId);
|
|
204384
204440
|
return `${wsProtocol}://${host}/ws?${params.toString()}`;
|
|
204385
204441
|
}
|
|
204386
204442
|
handleOpen = () => {
|
|
@@ -204708,7 +204764,7 @@ const buildHeteroExecStdinPayload = (params) => {
|
|
|
204708
204764
|
* instead flows back as a dispatch failure the user can see.
|
|
204709
204765
|
*/
|
|
204710
204766
|
function spawnHeteroAgentRun(params, logger) {
|
|
204711
|
-
const { agentType, cwd, imageList, jwt, operationId, prompt, resumeSessionId, serverUrl, systemContext, topicId } = params;
|
|
204767
|
+
const { agentType, args: extraArgs, cwd, imageList, jwt, operationId, prompt, resumeSessionId, serverUrl, systemContext, topicId } = params;
|
|
204712
204768
|
const workDir = cwd ?? process.cwd();
|
|
204713
204769
|
const cliArgs = [
|
|
204714
204770
|
process.argv[1],
|
|
@@ -204726,7 +204782,8 @@ function spawnHeteroAgentRun(params, logger) {
|
|
|
204726
204782
|
"-",
|
|
204727
204783
|
"--cwd",
|
|
204728
204784
|
workDir,
|
|
204729
|
-
...resumeSessionId ? ["--resume", resumeSessionId] : []
|
|
204785
|
+
...resumeSessionId ? ["--resume", resumeSessionId] : [],
|
|
204786
|
+
...extraArgs ?? []
|
|
204730
204787
|
];
|
|
204731
204788
|
const stdinPayload = buildHeteroExecStdinPayload({
|
|
204732
204789
|
imageList,
|
|
@@ -205682,6 +205739,34 @@ async function registerDevice(auth, identity) {
|
|
|
205682
205739
|
platform: process.platform
|
|
205683
205740
|
});
|
|
205684
205741
|
}
|
|
205742
|
+
/**
|
|
205743
|
+
* Identity for a WORKSPACE device: derived from the workspaceId (namespaced) so
|
|
205744
|
+
* the same physical machine enrolled into a workspace is a distinct device from
|
|
205745
|
+
* its personal identity, and stable across reconnects.
|
|
205746
|
+
*/
|
|
205747
|
+
function resolveWorkspaceDeviceIdentity(workspaceId, explicitDeviceId) {
|
|
205748
|
+
if (explicitDeviceId) return {
|
|
205749
|
+
deviceId: explicitDeviceId,
|
|
205750
|
+
identitySource: "fallback"
|
|
205751
|
+
};
|
|
205752
|
+
return deriveDeviceId(`workspace:${workspaceId}`);
|
|
205753
|
+
}
|
|
205754
|
+
/**
|
|
205755
|
+
* Mint a workspace-device connect token (owner-only on the server). The returned
|
|
205756
|
+
* token carries the `workspace_id` claim the gateway routes by.
|
|
205757
|
+
*/
|
|
205758
|
+
async function mintWorkspaceConnectToken(auth, workspaceId) {
|
|
205759
|
+
return createLambdaClient(auth, workspaceId).device.mintWorkspaceConnectToken.mutate();
|
|
205760
|
+
}
|
|
205761
|
+
/** Register this machine as a device of the given workspace (owner-only). */
|
|
205762
|
+
async function registerWorkspaceDevice(auth, identity, workspaceId) {
|
|
205763
|
+
await createLambdaClient(auth, workspaceId).device.registerWorkspaceDevice.mutate({
|
|
205764
|
+
deviceId: identity.deviceId,
|
|
205765
|
+
hostname: os.hostname(),
|
|
205766
|
+
identitySource: identity.identitySource,
|
|
205767
|
+
platform: process.platform
|
|
205768
|
+
});
|
|
205769
|
+
}
|
|
205685
205770
|
|
|
205686
205771
|
//#endregion
|
|
205687
205772
|
//#region src/tools/checkPlatformCapability.ts
|
|
@@ -205916,9 +206001,9 @@ function resolveLhPath() {
|
|
|
205916
206001
|
return "lh";
|
|
205917
206002
|
}
|
|
205918
206003
|
}
|
|
205919
|
-
async function sendAutoNotify(topicId, taskId, text, agentId) {
|
|
206004
|
+
async function sendAutoNotify(topicId, taskId, text, agentId, workspaceId) {
|
|
205920
206005
|
try {
|
|
205921
|
-
await (await getTrpcClient()).agentNotify.notify.mutate({
|
|
206006
|
+
await (await getTrpcClient(workspaceId)).agentNotify.notify.mutate({
|
|
205922
206007
|
agentId,
|
|
205923
206008
|
content: text,
|
|
205924
206009
|
role: "assistant",
|
|
@@ -205935,9 +206020,9 @@ async function sendAutoNotify(topicId, taskId, text, agentId) {
|
|
|
205935
206020
|
* `sendAutoNotify` which writes an error message AND triggers completion via
|
|
205936
206021
|
* the `done` flag.
|
|
205937
206022
|
*/
|
|
205938
|
-
async function sendDoneSignal(topicId, agentId) {
|
|
206023
|
+
async function sendDoneSignal(topicId, agentId, workspaceId) {
|
|
205939
206024
|
try {
|
|
205940
|
-
await (await getTrpcClient()).agentNotify.notify.mutate({
|
|
206025
|
+
await (await getTrpcClient(workspaceId)).agentNotify.notify.mutate({
|
|
205941
206026
|
agentId,
|
|
205942
206027
|
content: "",
|
|
205943
206028
|
done: true,
|
|
@@ -205966,9 +206051,13 @@ This conversation / task was sent to you by the **LobeHub platform** on behalf o
|
|
|
205966
206051
|
Use the \`${lhPath} notify\` command. All your updates appear as a **single message bubble** in the UI — create it once and update it in place.\n\n**Step 1 — Open the bubble on your first meaningful update** (captures the messageId):\n\`\`\`\nMSG_ID=$(${lhPath} notify --topic ${topicId} --role assistant --content "Starting..." --json | grep -o '"messageId":"[^"]*"' | cut -d'"' -f4)\n\`\`\`\n\n**Step 2 — Update the same bubble as you make progress**:\n\`\`\`\n${lhPath} notify --topic ${topicId} --role assistant --message-id "$MSG_ID" --content "Still working..."\n\`\`\`\n\n**Step 3 — Replace with your complete, final response when done**:\n\`\`\`\n${lhPath} notify --topic ${topicId} --role assistant --message-id "$MSG_ID" --content "<your full response here>"\n\`\`\`\n\nRules:\n- Always use \`--json\` on the first call and capture \`messageId\` from the output.\n- Always pass \`--message-id\` on every subsequent call so updates overwrite the same bubble.\n- Write what matters to the user — not implementation steps or internal tool calls.\n- Call notify at least once when the task is done, even if there were no intermediate updates.`;
|
|
205967
206052
|
}
|
|
205968
206053
|
async function runHeteroTask(params) {
|
|
205969
|
-
const { agentId, agentType, cwd, operationId, prompt, taskId, topicId } = params;
|
|
206054
|
+
const { agentId, agentType, cwd, operationId, prompt, taskId, topicId, workspaceId } = params;
|
|
205970
206055
|
const workDir = cwd || process.cwd();
|
|
205971
206056
|
const lhPath = resolveLhPath();
|
|
206057
|
+
const childEnv = workspaceId ? {
|
|
206058
|
+
...process.env,
|
|
206059
|
+
LOBEHUB_WORKSPACE_ID: workspaceId
|
|
206060
|
+
} : { ...process.env };
|
|
205972
206061
|
if (agentType === "openclaw") {
|
|
205973
206062
|
const openclawAgent = process.env.OPENCLAW_AGENT_ID ?? "main";
|
|
205974
206063
|
const enrichedPrompt = `${prompt}\n\n${buildNotifyProtocol(lhPath, topicId)}`;
|
|
@@ -205990,7 +206079,7 @@ async function runHeteroTask(params) {
|
|
|
205990
206079
|
], {
|
|
205991
206080
|
cwd: workDir,
|
|
205992
206081
|
detached: true,
|
|
205993
|
-
env:
|
|
206082
|
+
env: childEnv,
|
|
205994
206083
|
stdio: "ignore"
|
|
205995
206084
|
});
|
|
205996
206085
|
const pid = child.pid;
|
|
@@ -206003,13 +206092,14 @@ async function runHeteroTask(params) {
|
|
|
206003
206092
|
pid,
|
|
206004
206093
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
206005
206094
|
taskId,
|
|
206006
|
-
topicId
|
|
206095
|
+
topicId,
|
|
206096
|
+
workspaceId
|
|
206007
206097
|
});
|
|
206008
206098
|
log$10.info(`OpenClaw task started: taskId=${taskId} pid=${pid} agent=${openclawAgent}`);
|
|
206009
206099
|
child.on("close", (code, signal) => {
|
|
206010
206100
|
removeTask(taskId);
|
|
206011
|
-
if (code !== 0 || signal !== null) sendAutoNotify(topicId, taskId, signal ? `Task cancelled (signal: ${signal})` : `Task failed (exit code: ${code})`, agentId).finally(() => sendDoneSignal(topicId, agentId));
|
|
206012
|
-
else sendDoneSignal(topicId, agentId);
|
|
206101
|
+
if (code !== 0 || signal !== null) sendAutoNotify(topicId, taskId, signal ? `Task cancelled (signal: ${signal})` : `Task failed (exit code: ${code})`, agentId, workspaceId).finally(() => sendDoneSignal(topicId, agentId, workspaceId));
|
|
206102
|
+
else sendDoneSignal(topicId, agentId, workspaceId);
|
|
206013
206103
|
});
|
|
206014
206104
|
return JSON.stringify({
|
|
206015
206105
|
pid,
|
|
@@ -206035,7 +206125,7 @@ async function runHeteroTask(params) {
|
|
|
206035
206125
|
const child = spawn("hermes", hermesArgs, {
|
|
206036
206126
|
cwd: workDir,
|
|
206037
206127
|
detached: true,
|
|
206038
|
-
env:
|
|
206128
|
+
env: childEnv,
|
|
206039
206129
|
stdio: [
|
|
206040
206130
|
"ignore",
|
|
206041
206131
|
"pipe",
|
|
@@ -206052,7 +206142,8 @@ async function runHeteroTask(params) {
|
|
|
206052
206142
|
pid,
|
|
206053
206143
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
206054
206144
|
taskId,
|
|
206055
|
-
topicId
|
|
206145
|
+
topicId,
|
|
206146
|
+
workspaceId
|
|
206056
206147
|
});
|
|
206057
206148
|
log$10.info(`Hermes task started: taskId=${taskId} pid=${pid}`);
|
|
206058
206149
|
let stdout = "";
|
|
@@ -206062,14 +206153,14 @@ async function runHeteroTask(params) {
|
|
|
206062
206153
|
child.on("close", (code, signal) => {
|
|
206063
206154
|
removeTask(taskId);
|
|
206064
206155
|
if (code !== 0 || signal !== null) {
|
|
206065
|
-
sendAutoNotify(topicId, taskId, signal ? `Task cancelled (signal: ${signal})` : `Task failed (exit code: ${code})`, agentId).finally(() => sendDoneSignal(topicId, agentId));
|
|
206156
|
+
sendAutoNotify(topicId, taskId, signal ? `Task cancelled (signal: ${signal})` : `Task failed (exit code: ${code})`, agentId, workspaceId).finally(() => sendDoneSignal(topicId, agentId, workspaceId));
|
|
206066
206157
|
return;
|
|
206067
206158
|
}
|
|
206068
206159
|
const sessionId = stdout.match(/^session_id:\s*(\S+)/m)?.[1];
|
|
206069
206160
|
const response = stdout.replace(/^session_id:[^\n]*\n?/, "").trim();
|
|
206070
206161
|
if (sessionId) saveHermesSessionId(topicId, sessionId);
|
|
206071
|
-
if (response) sendAutoNotify(topicId, taskId, response, agentId).finally(() => sendDoneSignal(topicId, agentId));
|
|
206072
|
-
else sendDoneSignal(topicId, agentId);
|
|
206162
|
+
if (response) sendAutoNotify(topicId, taskId, response, agentId, workspaceId).finally(() => sendDoneSignal(topicId, agentId, workspaceId));
|
|
206163
|
+
else sendDoneSignal(topicId, agentId, workspaceId);
|
|
206073
206164
|
});
|
|
206074
206165
|
return JSON.stringify({
|
|
206075
206166
|
pid,
|
|
@@ -206090,7 +206181,7 @@ async function cancelHeteroTask(params) {
|
|
|
206090
206181
|
} catch (err) {
|
|
206091
206182
|
log$10.warn(`Failed to send ${signal} to pid ${entry.pid}: ${err instanceof Error ? err.message : String(err)}`);
|
|
206092
206183
|
removeTask(taskId);
|
|
206093
|
-
await sendAutoNotify(entry.topicId, taskId, "Task already completed or cancelled", entry.agentId);
|
|
206184
|
+
await sendAutoNotify(entry.topicId, taskId, "Task already completed or cancelled", entry.agentId, entry.workspaceId);
|
|
206094
206185
|
}
|
|
206095
206186
|
return JSON.stringify({
|
|
206096
206187
|
pid: entry.pid,
|
|
@@ -207160,7 +207251,7 @@ async function executeToolCall(apiName, argsStr, timeout) {
|
|
|
207160
207251
|
//#endregion
|
|
207161
207252
|
//#region src/commands/connect.ts
|
|
207162
207253
|
function registerConnectCommand(program) {
|
|
207163
|
-
const connectCmd = program.command("connect").description("Connect to the device gateway and listen for tool calls").option("--token <jwt>", "JWT access token").option("--gateway <url>", "Device gateway URL").option("--device-id <id>", "Device ID (auto-generated if not provided)").option("-v, --verbose", "Enable verbose logging").option("-d, --daemon", "Run as a background daemon process").option("--daemon-child", "Internal: runs as the daemon child process").action(async (options) => {
|
|
207254
|
+
const connectCmd = program.command("connect").description("Connect to the device gateway and listen for tool calls").option("--token <jwt>", "JWT access token").option("--gateway <url>", "Device gateway URL").option("--device-id <id>", "Device ID (auto-generated if not provided)").option("--workspace <id>", "Enroll as a device of this workspace (admin only)").option("-v, --verbose", "Enable verbose logging").option("-d, --daemon", "Run as a background daemon process").option("--daemon-child", "Internal: runs as the daemon child process").action(async (options) => {
|
|
207164
207255
|
if (options.verbose) setVerbose(true);
|
|
207165
207256
|
if (options.daemon) return handleDaemonStart(options);
|
|
207166
207257
|
await runConnect(options, options.daemonChild || process.env.LOBEHUB_DAEMON === "1");
|
|
@@ -207228,6 +207319,7 @@ function buildDaemonArgs(options) {
|
|
|
207228
207319
|
if (options.token) args.push("--token", options.token);
|
|
207229
207320
|
if (options.gateway) args.push("--gateway", options.gateway);
|
|
207230
207321
|
if (options.deviceId) args.push("--device-id", options.deviceId);
|
|
207322
|
+
if (options.workspace) args.push("--workspace", options.workspace);
|
|
207231
207323
|
if (options.verbose) args.push("--verbose");
|
|
207232
207324
|
return args;
|
|
207233
207325
|
}
|
|
@@ -207245,7 +207337,25 @@ async function runConnect(options, isDaemonChild) {
|
|
|
207245
207337
|
gatewayUrl
|
|
207246
207338
|
});
|
|
207247
207339
|
const resolvedGatewayUrl = gatewayUrl || "https://device-gateway.lobehub.com";
|
|
207248
|
-
const
|
|
207340
|
+
const workspaceId = options.workspace;
|
|
207341
|
+
const identity = workspaceId ? resolveWorkspaceDeviceIdentity(workspaceId, options.deviceId) : resolveDeviceIdentity(auth.userId, options.deviceId);
|
|
207342
|
+
let connectToken = auth.token;
|
|
207343
|
+
let connectTokenType = auth.tokenType;
|
|
207344
|
+
if (workspaceId) {
|
|
207345
|
+
connectToken = (await mintWorkspaceConnectToken(auth, workspaceId)).token;
|
|
207346
|
+
connectTokenType = "jwt";
|
|
207347
|
+
}
|
|
207348
|
+
const refreshConnectToken = async () => {
|
|
207349
|
+
const refreshed = await resolveToken({});
|
|
207350
|
+
if (!refreshed) return void 0;
|
|
207351
|
+
auth = refreshed;
|
|
207352
|
+
if (workspaceId) {
|
|
207353
|
+
connectToken = (await mintWorkspaceConnectToken(auth, workspaceId)).token;
|
|
207354
|
+
return connectToken;
|
|
207355
|
+
}
|
|
207356
|
+
connectToken = refreshed.token;
|
|
207357
|
+
return connectToken;
|
|
207358
|
+
};
|
|
207249
207359
|
const client = new GatewayClient({
|
|
207250
207360
|
channel: process.env.LOBEHUB_CLI_CHANNEL || "cli",
|
|
207251
207361
|
connectionId: loadOrCreateConnectionId(),
|
|
@@ -207253,9 +207363,10 @@ async function runConnect(options, isDaemonChild) {
|
|
|
207253
207363
|
gatewayUrl: resolvedGatewayUrl,
|
|
207254
207364
|
logger: isDaemonChild ? createDaemonLogger() : log$10,
|
|
207255
207365
|
serverUrl: auth.serverUrl,
|
|
207256
|
-
token:
|
|
207257
|
-
tokenType:
|
|
207258
|
-
userId: auth.userId
|
|
207366
|
+
token: connectToken,
|
|
207367
|
+
tokenType: connectTokenType,
|
|
207368
|
+
userId: workspaceId ? void 0 : auth.userId,
|
|
207369
|
+
workspaceId
|
|
207259
207370
|
});
|
|
207260
207371
|
const info = (msg) => {
|
|
207261
207372
|
if (isDaemonChild) appendLog(msg);
|
|
@@ -207347,6 +207458,7 @@ async function runConnect(options, isDaemonChild) {
|
|
|
207347
207458
|
try {
|
|
207348
207459
|
const ack = await spawnHeteroAgentRun({
|
|
207349
207460
|
agentType: request.agentType,
|
|
207461
|
+
args: request.args,
|
|
207350
207462
|
cwd: request.cwd,
|
|
207351
207463
|
imageList: request.imageList,
|
|
207352
207464
|
jwt: request.jwt,
|
|
@@ -207383,23 +207495,26 @@ async function runConnect(options, isDaemonChild) {
|
|
|
207383
207495
|
client.on("reconnecting", () => {
|
|
207384
207496
|
updateStatus("reconnecting");
|
|
207385
207497
|
});
|
|
207386
|
-
const startProactiveRefresh = () => scheduleProactiveRefresh(
|
|
207387
|
-
|
|
207388
|
-
|
|
207389
|
-
|
|
207498
|
+
const startProactiveRefresh = () => scheduleProactiveRefresh(connectToken, connectTokenType, async () => {
|
|
207499
|
+
const newToken = await refreshConnectToken();
|
|
207500
|
+
if (newToken) {
|
|
207501
|
+
client.updateToken(newToken);
|
|
207502
|
+
cancelRefreshTimer = startProactiveRefresh();
|
|
207503
|
+
}
|
|
207504
|
+
return newToken;
|
|
207390
207505
|
}, info, error);
|
|
207391
207506
|
let cancelRefreshTimer = startProactiveRefresh();
|
|
207392
207507
|
let authFailedRefreshAttempted = false;
|
|
207393
207508
|
client.on("auth_failed", async (reason) => {
|
|
207394
|
-
if (
|
|
207509
|
+
if (connectTokenType === "jwt" && !authFailedRefreshAttempted) {
|
|
207395
207510
|
authFailedRefreshAttempted = true;
|
|
207396
207511
|
info(`Authentication failed (${reason}). Attempting token refresh...`);
|
|
207397
207512
|
try {
|
|
207398
|
-
const
|
|
207399
|
-
|
|
207513
|
+
const prev = connectToken;
|
|
207514
|
+
const newToken = await refreshConnectToken();
|
|
207515
|
+
if (newToken && newToken !== prev) {
|
|
207400
207516
|
info("Token refreshed successfully. Reconnecting...");
|
|
207401
|
-
client.updateToken(
|
|
207402
|
-
auth = refreshed;
|
|
207517
|
+
client.updateToken(newToken);
|
|
207403
207518
|
authFailedRefreshAttempted = false;
|
|
207404
207519
|
cancelRefreshTimer = startProactiveRefresh();
|
|
207405
207520
|
await client.reconnect();
|
|
@@ -207413,14 +207528,13 @@ async function runConnect(options, isDaemonChild) {
|
|
|
207413
207528
|
process.exit(1);
|
|
207414
207529
|
});
|
|
207415
207530
|
client.on("auth_expired", async () => {
|
|
207416
|
-
if (
|
|
207531
|
+
if (connectTokenType === "apiKey") return;
|
|
207417
207532
|
info("Authentication expired. Attempting to refresh token...");
|
|
207418
207533
|
try {
|
|
207419
|
-
const
|
|
207420
|
-
if (
|
|
207534
|
+
const newToken = await refreshConnectToken();
|
|
207535
|
+
if (newToken) {
|
|
207421
207536
|
info("Token refreshed successfully. Reconnecting...");
|
|
207422
|
-
client.updateToken(
|
|
207423
|
-
auth = refreshed;
|
|
207537
|
+
client.updateToken(newToken);
|
|
207424
207538
|
cancelRefreshTimer = startProactiveRefresh();
|
|
207425
207539
|
await client.reconnect();
|
|
207426
207540
|
return;
|
|
@@ -207450,7 +207564,8 @@ async function runConnect(options, isDaemonChild) {
|
|
|
207450
207564
|
process.exit(0);
|
|
207451
207565
|
});
|
|
207452
207566
|
if (identity) try {
|
|
207453
|
-
await
|
|
207567
|
+
if (workspaceId) await registerWorkspaceDevice(auth, identity, workspaceId);
|
|
207568
|
+
else await registerDevice(auth, identity);
|
|
207454
207569
|
} catch (err) {
|
|
207455
207570
|
error(`Device registration failed (non-fatal): ${err.message}`);
|
|
207456
207571
|
}
|
|
@@ -207488,31 +207603,31 @@ function parseJwtExp(token) {
|
|
|
207488
207603
|
}
|
|
207489
207604
|
}
|
|
207490
207605
|
/**
|
|
207491
|
-
* Schedule a proactive token refresh before the
|
|
207492
|
-
*
|
|
207606
|
+
* Schedule a proactive token refresh before the (connect) token expires.
|
|
207607
|
+
* `refresh` performs the actual refresh — re-minting a workspace token or
|
|
207608
|
+
* refreshing the user token — and returns the new token. Returns a cleanup
|
|
207609
|
+
* function that cancels the scheduled timer.
|
|
207493
207610
|
*/
|
|
207494
|
-
function scheduleProactiveRefresh(
|
|
207495
|
-
if (
|
|
207496
|
-
const exp = parseJwtExp(
|
|
207611
|
+
function scheduleProactiveRefresh(token, tokenType, refresh, info, error) {
|
|
207612
|
+
if (tokenType !== "jwt") return null;
|
|
207613
|
+
const exp = parseJwtExp(token);
|
|
207497
207614
|
if (!exp) return null;
|
|
207498
|
-
const
|
|
207499
|
-
if (
|
|
207615
|
+
const lifetimeMs = exp * 1e3 - Date.now();
|
|
207616
|
+
if (lifetimeMs <= 0) {
|
|
207500
207617
|
doRefresh();
|
|
207501
207618
|
return null;
|
|
207502
207619
|
}
|
|
207620
|
+
const delay = lifetimeMs - Math.min(PROACTIVE_REFRESH_BUFFER * 1e3, lifetimeMs / 2);
|
|
207503
207621
|
const timer = setTimeout(() => void doRefresh(), delay);
|
|
207504
207622
|
return () => clearTimeout(timer);
|
|
207505
207623
|
async function doRefresh() {
|
|
207506
207624
|
try {
|
|
207507
|
-
|
|
207625
|
+
const newToken = await refresh();
|
|
207626
|
+
if (!newToken) {
|
|
207508
207627
|
error("Proactive token refresh failed — no valid credentials.");
|
|
207509
207628
|
return;
|
|
207510
207629
|
}
|
|
207511
|
-
|
|
207512
|
-
if (refreshed.token !== auth.token) {
|
|
207513
|
-
info("Proactively refreshed token.");
|
|
207514
|
-
onRefreshed(refreshed);
|
|
207515
|
-
}
|
|
207630
|
+
if (newToken !== token) info("Proactively refreshed token.");
|
|
207516
207631
|
} catch {
|
|
207517
207632
|
error("Proactive token refresh failed.");
|
|
207518
207633
|
}
|
|
@@ -209109,6 +209224,17 @@ var ClaudeCodeAdapter = class {
|
|
|
209109
209224
|
sawStreamEvent = false;
|
|
209110
209225
|
/** Track current message.id to detect step boundaries */
|
|
209111
209226
|
currentMessageId;
|
|
209227
|
+
/**
|
|
209228
|
+
* Whether the current turn (the in-flight `currentMessageId`) has already
|
|
209229
|
+
* emitted a `tool_use`. When CC reuses the SAME `message.id` to stream the
|
|
209230
|
+
* model's post-tool answer (it continues after the `tool_result` without
|
|
209231
|
+
* minting a fresh id — seen on device/batch `lh hetero exec` runs), that
|
|
209232
|
+
* trailing text must NOT coalesce onto the tool-issuing assistant. We force a
|
|
209233
|
+
* step boundary so the answer anchors to its own assistant, chained after the
|
|
209234
|
+
* tool results — otherwise text + `tool_use` share one message and the
|
|
209235
|
+
* renderer drops the tool block below the answer.
|
|
209236
|
+
*/
|
|
209237
|
+
currentTurnHadToolUse = false;
|
|
209112
209238
|
/** message.id of the stream_event delta flow currently in flight */
|
|
209113
209239
|
currentStreamEventMessageId;
|
|
209114
209240
|
/**
|
|
@@ -209299,7 +209425,10 @@ var ClaudeCodeAdapter = class {
|
|
|
209299
209425
|
if (parentToolUseId) return this.handleSubagentAssistant(raw, parentToolUseId);
|
|
209300
209426
|
const events = [];
|
|
209301
209427
|
const messageId = raw.message?.id;
|
|
209302
|
-
|
|
209428
|
+
const hasTextBlock = content.some((b) => b?.type === "text" && b.text);
|
|
209429
|
+
const hasToolUseBlock = content.some((b) => b?.type === "tool_use");
|
|
209430
|
+
const isPostToolTextReusingId = hasTextBlock && !hasToolUseBlock && messageId !== void 0 && messageId === this.currentMessageId && this.currentTurnHadToolUse;
|
|
209431
|
+
events.push(...this.openMainMessage(messageId, raw.message?.model, isPostToolTextReusingId));
|
|
209303
209432
|
if (raw.message?.model) this.currentStreamEventModel = raw.message.model;
|
|
209304
209433
|
const textParts = [];
|
|
209305
209434
|
const reasoningParts = [];
|
|
@@ -209330,7 +209459,10 @@ var ClaudeCodeAdapter = class {
|
|
|
209330
209459
|
break;
|
|
209331
209460
|
}
|
|
209332
209461
|
}
|
|
209333
|
-
if (newToolCalls.length > 0)
|
|
209462
|
+
if (newToolCalls.length > 0) {
|
|
209463
|
+
this.pendingExternalSignal = void 0;
|
|
209464
|
+
this.currentTurnHadToolUse = true;
|
|
209465
|
+
}
|
|
209334
209466
|
const textCompletion = this.getTrailingCompletion(messageId, textParts.join(""), this.streamedTextByMessageId);
|
|
209335
209467
|
const thinkingCompletion = this.getTrailingCompletion(messageId, reasoningParts.join(""), this.streamedThinkingByMessageId);
|
|
209336
209468
|
if (thinkingCompletion) events.push(this.makeChunkEvent({
|
|
@@ -209699,22 +209831,37 @@ var ClaudeCodeAdapter = class {
|
|
|
209699
209831
|
* record the id (init already primed the executor).
|
|
209700
209832
|
* - Otherwise: advance stepIndex and emit stream_end + stream_start(newStep).
|
|
209701
209833
|
*/
|
|
209702
|
-
openMainMessage(messageId, model) {
|
|
209834
|
+
openMainMessage(messageId, model, forcePostToolBoundary = false) {
|
|
209703
209835
|
if (!messageId) return [];
|
|
209704
209836
|
if (!this.started) {
|
|
209705
209837
|
this.started = true;
|
|
209706
209838
|
this.currentMessageId = messageId;
|
|
209839
|
+
this.currentTurnHadToolUse = false;
|
|
209707
209840
|
return [this.makeEvent("stream_start", {
|
|
209708
209841
|
model,
|
|
209709
209842
|
provider: "claude-code"
|
|
209710
209843
|
})];
|
|
209711
209844
|
}
|
|
209712
|
-
if (messageId === this.currentMessageId)
|
|
209845
|
+
if (messageId === this.currentMessageId) {
|
|
209846
|
+
if (!forcePostToolBoundary) return [];
|
|
209847
|
+
this.stepIndex++;
|
|
209848
|
+
this.currentTurnHadToolUse = false;
|
|
209849
|
+
this.hasUnhandledUserInput = false;
|
|
209850
|
+
this.pendingExternalSignal = void 0;
|
|
209851
|
+
return [this.makeEvent("stream_end", {}), this.makeEvent("stream_start", {
|
|
209852
|
+
messageId: `${messageId}:s${this.stepIndex}`,
|
|
209853
|
+
model,
|
|
209854
|
+
newStep: true,
|
|
209855
|
+
provider: "claude-code"
|
|
209856
|
+
})];
|
|
209857
|
+
}
|
|
209713
209858
|
if (this.currentMessageId === void 0) {
|
|
209714
209859
|
this.currentMessageId = messageId;
|
|
209860
|
+
this.currentTurnHadToolUse = false;
|
|
209715
209861
|
return [];
|
|
209716
209862
|
}
|
|
209717
209863
|
this.currentMessageId = messageId;
|
|
209864
|
+
this.currentTurnHadToolUse = false;
|
|
209718
209865
|
this.stepIndex++;
|
|
209719
209866
|
if (!this.hasUnhandledUserInput && this.activeTasks.size > 0) {
|
|
209720
209867
|
const lastTaskKey = [...this.activeTasks.keys()].at(-1);
|
|
@@ -211530,6 +211677,7 @@ var TrpcIngestSink = class {
|
|
|
211530
211677
|
//#endregion
|
|
211531
211678
|
//#region src/commands/hetero.ts
|
|
211532
211679
|
const SUPPORTED_AGENT_TYPES = new Set(["claude-code", "codex"]);
|
|
211680
|
+
const CODEX_REASONING_EFFORT_CONFIG_KEY = "model_reasoning_effort";
|
|
211533
211681
|
/**
|
|
211534
211682
|
* Patterns that indicate a `--resume <sessionId>` run should be retried
|
|
211535
211683
|
* without `--resume`. Two classes of failure:
|
|
@@ -211560,6 +211708,12 @@ const RESUME_RETRY_PATTERNS = [
|
|
|
211560
211708
|
];
|
|
211561
211709
|
const looksLikeNeedsRetryWithoutResume = (text) => RESUME_RETRY_PATTERNS.some((p) => p.test(text));
|
|
211562
211710
|
const collectImage = (value, previous = []) => [...previous, value];
|
|
211711
|
+
const collectAgentArg = (value, previous = []) => [...previous, value];
|
|
211712
|
+
const buildExtraArgs = (options) => {
|
|
211713
|
+
const selectorArgs = options.type === "codex" ? [...options.model ? ["--model", options.model] : [], ...options.effort ? ["-c", `${CODEX_REASONING_EFFORT_CONFIG_KEY}="${options.effort}"`] : []] : [...options.model ? ["--model", options.model] : [], ...options.effort ? ["--effort", options.effort] : []];
|
|
211714
|
+
const extraArgs = [...options.agentArg ?? [], ...selectorArgs];
|
|
211715
|
+
return extraArgs.length > 0 ? extraArgs : void 0;
|
|
211716
|
+
};
|
|
211563
211717
|
const readStdin = async () => {
|
|
211564
211718
|
const chunks = [];
|
|
211565
211719
|
for await (const chunk of process.stdin) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
@@ -211940,10 +212094,12 @@ const exec = async (options) => {
|
|
|
211940
212094
|
};
|
|
211941
212095
|
};
|
|
211942
212096
|
const interceptResume = !!options.resume;
|
|
212097
|
+
const extraArgs = buildExtraArgs(options);
|
|
211943
212098
|
const first = await runOneAgent({
|
|
211944
212099
|
agentType: options.type,
|
|
211945
212100
|
command: options.command,
|
|
211946
212101
|
cwd: options.cwd || process.cwd(),
|
|
212102
|
+
extraArgs,
|
|
211947
212103
|
operationId,
|
|
211948
212104
|
prompt: resolved.prompt,
|
|
211949
212105
|
resumeSessionId: options.resume
|
|
@@ -211955,6 +212111,7 @@ const exec = async (options) => {
|
|
|
211955
212111
|
agentType: options.type,
|
|
211956
212112
|
command: options.command,
|
|
211957
212113
|
cwd: options.cwd || process.cwd(),
|
|
212114
|
+
extraArgs,
|
|
211958
212115
|
operationId,
|
|
211959
212116
|
prompt: resolved.prompt
|
|
211960
212117
|
}, false, "attempt-2-noresume");
|
|
@@ -211994,7 +212151,7 @@ const exec = async (options) => {
|
|
|
211994
212151
|
process.exit(1);
|
|
211995
212152
|
};
|
|
211996
212153
|
function registerHeteroCommand(program) {
|
|
211997
|
-
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);
|
|
212154
|
+
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("--model <model>", "Forward a resolved model selection to the agent CLI").option("--effort <level>", "Forward a resolved reasoning effort selection to the agent CLI").option("--agent-arg <arg>", "Forward one native agent CLI argument after wrapper parsing (repeatable)", collectAgentArg).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);
|
|
211998
212155
|
}
|
|
211999
212156
|
|
|
212000
212157
|
//#endregion
|
|
@@ -217544,6 +217701,37 @@ function evidencePaths(evidence) {
|
|
|
217544
217701
|
}
|
|
217545
217702
|
function registerVerifyCommand(program) {
|
|
217546
217703
|
const verify = program.command("verify").description("Manage the Agent Run delivery checker (criteria, rubrics, plans, results)");
|
|
217704
|
+
verify.command("init").description("Write the portable verify skill into a working dir (.claude/skills/verify)").option("--dir <path>", "Target working directory (default: current dir)").option("--skill <id>", "Skill identifier to pull", "verify").option("--force", "Overwrite existing skill files").option("--json [fields]", "Output JSON").action(async (options) => {
|
|
217705
|
+
const bundle = await (await getTrpcClient()).verify.getSkillBundle.query({ identifier: options.skill });
|
|
217706
|
+
const baseDir = options.dir ? path.resolve(options.dir) : process.cwd();
|
|
217707
|
+
const skillDir = path.join(baseDir, ".claude", "skills", bundle.identifier);
|
|
217708
|
+
const entries = [["SKILL.md", bundle.content], ...Object.entries(bundle.files)];
|
|
217709
|
+
const written = [];
|
|
217710
|
+
const skipped = [];
|
|
217711
|
+
for (const [rel, content] of entries) {
|
|
217712
|
+
const dest = path.join(skillDir, rel);
|
|
217713
|
+
if (existsSync(dest) && !options.force) {
|
|
217714
|
+
skipped.push(rel);
|
|
217715
|
+
continue;
|
|
217716
|
+
}
|
|
217717
|
+
mkdirSync(path.dirname(dest), { recursive: true });
|
|
217718
|
+
writeFileSync(dest, content, "utf8");
|
|
217719
|
+
written.push(rel);
|
|
217720
|
+
}
|
|
217721
|
+
const result = {
|
|
217722
|
+
dir: skillDir,
|
|
217723
|
+
skill: bundle.identifier,
|
|
217724
|
+
skipped,
|
|
217725
|
+
written
|
|
217726
|
+
};
|
|
217727
|
+
if (options.json !== void 0) {
|
|
217728
|
+
outputJson(result, typeof options.json === "string" ? options.json : void 0);
|
|
217729
|
+
return;
|
|
217730
|
+
}
|
|
217731
|
+
console.log(`${import_picocolors.default.green("✓")} ${import_picocolors.default.bold(bundle.name)} skill → ${import_picocolors.default.dim(path.relative(process.cwd(), skillDir) || skillDir)}`);
|
|
217732
|
+
console.log(` ${written.length} written${skipped.length ? `, ${skipped.length} skipped` : ""}`);
|
|
217733
|
+
if (skipped.length > 0) console.log(import_picocolors.default.dim(` (skipped existing — pass --force to overwrite)`));
|
|
217734
|
+
});
|
|
217547
217735
|
const criterion = verify.command("criterion").description("Reusable pass/fail standards");
|
|
217548
217736
|
criterion.command("list").description("List criteria").option("--json [fields]", "Output JSON, optionally specify fields (comma-separated)").action(async (options) => {
|
|
217549
217737
|
const items = await (await getTrpcClient()).verify.listCriteria.query();
|
|
@@ -217935,10 +218123,23 @@ function registerVerifyCommand(program) {
|
|
|
217935
218123
|
const summary = result.summary ?? {};
|
|
217936
218124
|
const reportMdPath = path.join(dir, "report.md");
|
|
217937
218125
|
const content = existsSync(reportMdPath) ? readFileSync(reportMdPath, "utf8") : void 0;
|
|
218126
|
+
const surfaces = Array.isArray(result.surfaces) ? result.surfaces.filter((s) => typeof s === "string") : void 0;
|
|
218127
|
+
const contextEntries = Object.entries({
|
|
218128
|
+
branch: typeof result.branch === "string" ? result.branch : void 0,
|
|
218129
|
+
commit: typeof result.commit === "string" ? result.commit : void 0,
|
|
218130
|
+
entry: typeof result.entry === "string" ? result.entry : void 0,
|
|
218131
|
+
focus: typeof result.focus === "string" ? result.focus : options.goal,
|
|
218132
|
+
surfaces: surfaces && surfaces.length > 0 ? surfaces : void 0,
|
|
218133
|
+
testedAt: typeof result.createdAt === "string" ? result.createdAt : void 0
|
|
218134
|
+
}).filter(([, v]) => v !== void 0);
|
|
218135
|
+
const context = contextEntries.length > 0 ? Object.fromEntries(contextEntries) : void 0;
|
|
218136
|
+
const scenario = result.scenario === "coding" ? "coding" : "coding";
|
|
217938
218137
|
const client = await getTrpcClient();
|
|
217939
218138
|
const run = await client.verify.createRun.mutate({
|
|
217940
|
-
|
|
218139
|
+
context,
|
|
218140
|
+
goal: options.goal ?? (typeof result.focus === "string" ? result.focus : void 0),
|
|
217941
218141
|
operationId: options.operation,
|
|
218142
|
+
scenario,
|
|
217942
218143
|
source: options.source,
|
|
217943
218144
|
title: options.title ?? result.title
|
|
217944
218145
|
});
|
|
@@ -217964,22 +218165,29 @@ function registerVerifyCommand(program) {
|
|
|
217964
218165
|
log$10.warn(`evidence not found, skipping: ${rel}`);
|
|
217965
218166
|
continue;
|
|
217966
218167
|
}
|
|
217967
|
-
|
|
217968
|
-
|
|
217969
|
-
|
|
217970
|
-
|
|
217971
|
-
|
|
217972
|
-
|
|
217973
|
-
|
|
217974
|
-
|
|
217975
|
-
|
|
218168
|
+
try {
|
|
218169
|
+
const file = await uploadLocalFile(client, abs);
|
|
218170
|
+
await client.verify.uploadEvidence.mutate({
|
|
218171
|
+
capturedBy: "cli",
|
|
218172
|
+
checkResultId: checkResult.id,
|
|
218173
|
+
description: path.basename(abs),
|
|
218174
|
+
fileId: file.id,
|
|
218175
|
+
type: evidenceTypeForFile(abs)
|
|
218176
|
+
});
|
|
218177
|
+
uploaded += 1;
|
|
218178
|
+
} catch (e) {
|
|
218179
|
+
log$10.warn(`evidence upload failed, skipping ${path.basename(abs)}: ${String(e)}`);
|
|
218180
|
+
}
|
|
217976
218181
|
}
|
|
217977
218182
|
}
|
|
218183
|
+
const conclusion = typeof summary.conclusion === "string" ? summary.conclusion : typeof summary.note === "string" ? summary.note : void 0;
|
|
218184
|
+
const score = typeof summary.score === "number" ? Math.max(0, Math.min(1, summary.score / 100)) : void 0;
|
|
217978
218185
|
await client.verify.upsertReport.mutate({
|
|
217979
218186
|
content,
|
|
217980
218187
|
failedChecks: summary.failed,
|
|
218188
|
+
overallConfidence: score,
|
|
217981
218189
|
passedChecks: summary.passed,
|
|
217982
|
-
summary:
|
|
218190
|
+
summary: conclusion,
|
|
217983
218191
|
totalChecks: summary.total ?? cases.length,
|
|
217984
218192
|
uncertainChecks: (summary.blocked ?? 0) + (summary.uncertain ?? 0) || void 0,
|
|
217985
218193
|
verdict: summary.verdict ? toVerdict(summary.verdict) : void 0,
|
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.36" "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.36",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"lh": "./dist/index.js",
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"tsdown": "^0.21.4",
|
|
34
34
|
"typescript": "^6.0.3",
|
|
35
35
|
"ws": "^8.18.1",
|
|
36
|
-
"@lobechat/agent-gateway-client": "1.0.0",
|
|
37
|
-
"@lobechat/device-gateway-client": "1.0.0",
|
|
38
|
-
"@lobechat/device-control": "1.0.0",
|
|
39
36
|
"@lobechat/device-identity": "1.0.0",
|
|
40
|
-
"@lobechat/local-file-shell": "1.0.0",
|
|
41
37
|
"@lobechat/heterogeneous-agents": "1.0.0",
|
|
38
|
+
"@lobechat/device-control": "1.0.0",
|
|
39
|
+
"@lobechat/device-gateway-client": "1.0.0",
|
|
40
|
+
"@lobechat/agent-gateway-client": "1.0.0",
|
|
41
|
+
"@lobechat/local-file-shell": "1.0.0",
|
|
42
42
|
"@lobechat/tool-runtime": "1.0.0"
|
|
43
43
|
},
|
|
44
44
|
"publishConfig": {
|