@lobehub/cli 0.0.63 → 0.0.65
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 +616 -60
- package/man/man1/lh.1 +1 -1
- package/package.json +28 -27
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { finished, pipeline } from "node:stream/promises";
|
|
|
14
14
|
import { Duplex, PassThrough, Readable, Transform, Writable, getDefaultHighWaterMark, pipeline as pipeline$1 } from "node:stream";
|
|
15
15
|
import readline, { createInterface } from "node:readline";
|
|
16
16
|
import { EventEmitter, addAbortListener, on, once, setMaxListeners } from "node:events";
|
|
17
|
-
import fs$1, { access, chmod, copyFile, cp, lstat, mkdir, open, readFile, readdir, readlink, realpath, rename, rm, stat, unlink as unlink$1, writeFile } from "node:fs/promises";
|
|
17
|
+
import fs$1, { access, chmod, copyFile, cp, lstat, mkdir, mkdtemp, open, readFile, readdir, readlink, realpath, rename, rm, stat, unlink as unlink$1, writeFile } from "node:fs/promises";
|
|
18
18
|
import crypto$1, { X509Certificate, createHash, randomBytes, randomUUID, sign } from "node:crypto";
|
|
19
19
|
import { text } from "node:stream/consumers";
|
|
20
20
|
import { ReadableStream as ReadableStream$1 } from "node:stream/web";
|
|
@@ -23324,6 +23324,7 @@ const TRPC_NAMESPACE_API_KEY_RULES = {
|
|
|
23324
23324
|
user: rw("user:read", "user:write"),
|
|
23325
23325
|
userMemories: rw("user:read", "user:write"),
|
|
23326
23326
|
userMemory: rw("user:read", "user:write"),
|
|
23327
|
+
userSkill: rw("agent:read", "agent:write"),
|
|
23327
23328
|
verify: "blocked",
|
|
23328
23329
|
video: { any: "model:invoke" },
|
|
23329
23330
|
waitlist: "blocked",
|
|
@@ -292724,21 +292725,56 @@ var init_src = __esmMin((() => {
|
|
|
292724
292725
|
//#endregion
|
|
292725
292726
|
//#region ../../packages/local-file-shell/src/shell/runner.ts
|
|
292726
292727
|
/**
|
|
292727
|
-
*
|
|
292728
|
-
*
|
|
292729
|
-
*
|
|
292730
|
-
*
|
|
292728
|
+
* `true` = resolves to a directory; `false` = resolves to a non-directory
|
|
292729
|
+
* (a regular file); `undefined` = cannot be resolved at all (missing, an
|
|
292730
|
+
* unreadable parent, a symlink loop). Keeping the three cases apart lets the
|
|
292731
|
+
* report name what actually happened instead of guessing.
|
|
292731
292732
|
*/
|
|
292732
|
-
const
|
|
292733
|
+
const statIsDirectory = async (dir) => {
|
|
292733
292734
|
try {
|
|
292734
|
-
|
|
292735
|
-
|
|
292736
|
-
|
|
292737
|
-
|
|
292738
|
-
|
|
292735
|
+
return (await stat(dir)).isDirectory();
|
|
292736
|
+
} catch {
|
|
292737
|
+
return;
|
|
292738
|
+
}
|
|
292739
|
+
};
|
|
292740
|
+
/**
|
|
292741
|
+
* Resolve a requested cwd to a directory the shell can actually start in.
|
|
292742
|
+
*
|
|
292743
|
+
* A cwd pinned on another machine (a stale repo path, a moved worktree) would
|
|
292744
|
+
* otherwise kill every command in the run: the run cannot change its own pinned
|
|
292745
|
+
* cwd, callers strip a model-supplied cwd before dispatch, and re-running
|
|
292746
|
+
* `cd` inside the command does not help because the check happens before the
|
|
292747
|
+
* shell exists — so the same failure repeats forever. Un-sandboxed runs fall
|
|
292748
|
+
* back to a fresh isolated directory created under the OS temp root, and the
|
|
292749
|
+
* fallback is reported so nothing downstream mistakes it for the requested
|
|
292750
|
+
* directory.
|
|
292751
|
+
*
|
|
292752
|
+
* Sandboxed runs do NOT fall back: the sandbox fence (`createLocalSandboxPolicy`)
|
|
292753
|
+
* is built on the requested cwd, so silently spawning in another directory
|
|
292754
|
+
* would grant writes somewhere the user never approved. Those fail loudly.
|
|
292755
|
+
*/
|
|
292756
|
+
const resolveWorkingDirectory = async (requestedCwd, sandboxRequested, fallbackRoot) => {
|
|
292757
|
+
const usable = await statIsDirectory(requestedCwd);
|
|
292758
|
+
if (usable) return { cwd: requestedCwd };
|
|
292759
|
+
const reason = usable === false ? `Working directory is not a directory on ${os.hostname()}: ${requestedCwd}` : `Working directory does not exist on ${os.hostname()}: ${requestedCwd}`;
|
|
292760
|
+
if (sandboxRequested) return { error: `${reason}. A sandboxed run must start in its requested directory — the sandbox fence is built on it — so the command was not run.` };
|
|
292761
|
+
try {
|
|
292762
|
+
const fallback = await mkdtemp(path.join(fallbackRoot ?? os.tmpdir(), "lobehub-cwd-fallback-"));
|
|
292763
|
+
return {
|
|
292764
|
+
cwd: fallback,
|
|
292765
|
+
notice: `${reason}. Falling back to ${fallback}.`,
|
|
292766
|
+
remove: async () => {
|
|
292767
|
+
await rm(fallback, {
|
|
292768
|
+
force: true,
|
|
292769
|
+
recursive: true
|
|
292770
|
+
});
|
|
292771
|
+
}
|
|
292772
|
+
};
|
|
292773
|
+
} catch {
|
|
292774
|
+
return { error: `${reason}. The shell is fine — run the command from a directory that exists on this device.` };
|
|
292739
292775
|
}
|
|
292740
292776
|
};
|
|
292741
|
-
async function runCommand$1({ command, cwd, description, env: extraEnv, run_in_background, timeout = DEFAULT_OBSERVATION_TIMEOUT_MS }, { backend: backendOverride, processManager, logger, onSandboxUnavailable, sandboxPolicy, spawnProcess }) {
|
|
292777
|
+
async function runCommand$1({ command, cwd, description, env: extraEnv, run_in_background, timeout = DEFAULT_OBSERVATION_TIMEOUT_MS }, { backend: backendOverride, processManager, logger, onSandboxUnavailable, sandboxPolicy, spawnProcess, tmpdirOverride }) {
|
|
292742
292778
|
if (!command) return {
|
|
292743
292779
|
error: "command is required",
|
|
292744
292780
|
success: false
|
|
@@ -292750,12 +292786,18 @@ async function runCommand$1({ command, cwd, description, env: extraEnv, run_in_b
|
|
|
292750
292786
|
cwd,
|
|
292751
292787
|
timeout
|
|
292752
292788
|
});
|
|
292789
|
+
let effectiveCwd = cwd;
|
|
292790
|
+
let cwdNotice;
|
|
292791
|
+
let removeCwdFallback;
|
|
292753
292792
|
if (cwd) {
|
|
292754
|
-
const
|
|
292755
|
-
if (
|
|
292756
|
-
error:
|
|
292793
|
+
const resolution = await resolveWorkingDirectory(cwd, !!sandboxPolicy, tmpdirOverride);
|
|
292794
|
+
if (resolution.error) return {
|
|
292795
|
+
error: resolution.error,
|
|
292757
292796
|
success: false
|
|
292758
292797
|
};
|
|
292798
|
+
effectiveCwd = resolution.cwd;
|
|
292799
|
+
cwdNotice = resolution.notice;
|
|
292800
|
+
removeCwdFallback = resolution.remove;
|
|
292759
292801
|
}
|
|
292760
292802
|
const requestedEnv = extraEnv ? {
|
|
292761
292803
|
...process.env,
|
|
@@ -292763,6 +292805,7 @@ async function runCommand$1({ command, cwd, description, env: extraEnv, run_in_b
|
|
|
292763
292805
|
} : process.env;
|
|
292764
292806
|
const shellConfig = await getShellConfig(process.platform === "win32" ? normalizeEnvVarRefs(command, requestedEnv, (await detectWindowsShell()).type) : command);
|
|
292765
292807
|
let outputFiles;
|
|
292808
|
+
let registered = false;
|
|
292766
292809
|
let releaseSandbox;
|
|
292767
292810
|
let sandboxed;
|
|
292768
292811
|
try {
|
|
@@ -292774,7 +292817,7 @@ async function runCommand$1({ command, cwd, description, env: extraEnv, run_in_b
|
|
|
292774
292817
|
try {
|
|
292775
292818
|
launchPlan = await createSandboxLaunchPlan({
|
|
292776
292819
|
command: shellConfig,
|
|
292777
|
-
cwd,
|
|
292820
|
+
cwd: effectiveCwd,
|
|
292778
292821
|
env: requestedEnv,
|
|
292779
292822
|
policy: sandboxPolicy
|
|
292780
292823
|
});
|
|
@@ -292794,7 +292837,7 @@ async function runCommand$1({ command, cwd, description, env: extraEnv, run_in_b
|
|
|
292794
292837
|
args: launchCommand.args,
|
|
292795
292838
|
cmd: launchCommand.cmd
|
|
292796
292839
|
}, {
|
|
292797
|
-
cwd,
|
|
292840
|
+
cwd: effectiveCwd,
|
|
292798
292841
|
env: launchEnv,
|
|
292799
292842
|
outputFiles: shellOutputFiles,
|
|
292800
292843
|
shellId
|
|
@@ -292818,21 +292861,27 @@ async function runCommand$1({ command, cwd, description, env: extraEnv, run_in_b
|
|
|
292818
292861
|
shellProcess.spawnError = new Error(`Failed to start command${cwdContext}: ${error.message}`, { cause: error });
|
|
292819
292862
|
shellProcess.exitCode = 1;
|
|
292820
292863
|
});
|
|
292821
|
-
handle.once("close", () =>
|
|
292864
|
+
handle.once("close", () => {
|
|
292865
|
+
releaseSandbox?.();
|
|
292866
|
+
if (removeCwdFallback) removeCwdFallback().catch((error) => {
|
|
292867
|
+
logger?.debug(`${logPrefix} Fallback directory cleanup failed`, { error });
|
|
292868
|
+
});
|
|
292869
|
+
});
|
|
292822
292870
|
processManager.register(shellId, shellProcess);
|
|
292871
|
+
registered = true;
|
|
292823
292872
|
processManager.closeOutputFiles(shellOutputFiles);
|
|
292824
292873
|
logger?.info?.(`${logPrefix} Started session`, {
|
|
292825
292874
|
background: run_in_background,
|
|
292826
292875
|
shellId
|
|
292827
292876
|
});
|
|
292828
292877
|
if (run_in_background) return {
|
|
292829
|
-
output: "",
|
|
292878
|
+
output: cwdNotice ?? "",
|
|
292830
292879
|
output_files: processManager.getOutputFilesInfo(shellOutputFiles),
|
|
292831
292880
|
sandboxed,
|
|
292832
292881
|
shell_id: shellId,
|
|
292833
292882
|
success: true
|
|
292834
292883
|
};
|
|
292835
|
-
|
|
292884
|
+
const result = {
|
|
292836
292885
|
...await processManager.getRunCommandOutput({
|
|
292837
292886
|
shell_id: shellId,
|
|
292838
292887
|
timeout
|
|
@@ -292840,9 +292889,17 @@ async function runCommand$1({ command, cwd, description, env: extraEnv, run_in_b
|
|
|
292840
292889
|
sandboxed,
|
|
292841
292890
|
shell_id: shellId
|
|
292842
292891
|
};
|
|
292892
|
+
if (!cwdNotice) return result;
|
|
292893
|
+
const prefix = `${cwdNotice}\n\n`;
|
|
292894
|
+
return {
|
|
292895
|
+
...result,
|
|
292896
|
+
output: `${prefix}${result.output ?? ""}`,
|
|
292897
|
+
stdout: `${prefix}${result.stdout ?? ""}`
|
|
292898
|
+
};
|
|
292843
292899
|
} catch (error) {
|
|
292844
292900
|
releaseSandbox?.();
|
|
292845
292901
|
if (outputFiles) processManager.closeOutputFiles(outputFiles);
|
|
292902
|
+
if (removeCwdFallback && !registered) await removeCwdFallback().catch(() => {});
|
|
292846
292903
|
return {
|
|
292847
292904
|
error: error.message,
|
|
292848
292905
|
success: false
|
|
@@ -292852,18 +292909,35 @@ async function runCommand$1({ command, cwd, description, env: extraEnv, run_in_b
|
|
|
292852
292909
|
|
|
292853
292910
|
//#endregion
|
|
292854
292911
|
//#region src/auth/apiKey.ts
|
|
292912
|
+
var ApiKeyValidationError = class extends Error {
|
|
292913
|
+
constructor(message, retryable) {
|
|
292914
|
+
super(message);
|
|
292915
|
+
this.retryable = retryable;
|
|
292916
|
+
this.name = "ApiKeyValidationError";
|
|
292917
|
+
}
|
|
292918
|
+
};
|
|
292855
292919
|
async function getUserIdFromApiKey(apiKey, serverUrl) {
|
|
292856
292920
|
const normalizedServerUrl = normalizeUrl(serverUrl) || resolveServerUrl();
|
|
292857
|
-
|
|
292921
|
+
let response;
|
|
292922
|
+
try {
|
|
292923
|
+
response = await fetch(`${normalizedServerUrl}/api/v1/users/me?includeCount=0`, { headers: { Authorization: `Bearer ${apiKey}` } });
|
|
292924
|
+
} catch (error) {
|
|
292925
|
+
throw new ApiKeyValidationError(error instanceof Error ? error.message : String(error), true);
|
|
292926
|
+
}
|
|
292927
|
+
const retryableStatus = response.status >= 500 || [
|
|
292928
|
+
408,
|
|
292929
|
+
425,
|
|
292930
|
+
429
|
|
292931
|
+
].includes(response.status);
|
|
292858
292932
|
let body;
|
|
292859
292933
|
try {
|
|
292860
292934
|
body = await response.json();
|
|
292861
292935
|
} catch {
|
|
292862
|
-
throw new
|
|
292936
|
+
throw new ApiKeyValidationError(`Failed to parse response from ${normalizedServerUrl}/api/v1/users/me?includeCount=0.`, response.ok || retryableStatus);
|
|
292863
292937
|
}
|
|
292864
|
-
if (!response.ok || body?.success === false) throw new
|
|
292938
|
+
if (!response.ok || body?.success === false) throw new ApiKeyValidationError(body?.error || body?.message || `Request failed with status ${response.status}.`, retryableStatus);
|
|
292865
292939
|
const userId = body?.data?.id || body?.data?.userId;
|
|
292866
|
-
if (!userId) throw new
|
|
292940
|
+
if (!userId) throw new ApiKeyValidationError("Current user response did not include a user id.", true);
|
|
292867
292941
|
return userId;
|
|
292868
292942
|
}
|
|
292869
292943
|
|
|
@@ -292941,7 +293015,7 @@ async function resolveToken(options) {
|
|
|
292941
293015
|
} catch (error) {
|
|
292942
293016
|
const message = error instanceof Error ? error.message : String(error);
|
|
292943
293017
|
log$13.error(`Failed to validate ${source.origin}: ${message}`);
|
|
292944
|
-
process.exit(1);
|
|
293018
|
+
process.exit(error instanceof ApiKeyValidationError && error.retryable ? options.unavailableExitCode ?? 1 : 1);
|
|
292945
293019
|
}
|
|
292946
293020
|
}
|
|
292947
293021
|
const result = await getValidToken();
|
|
@@ -292963,7 +293037,7 @@ async function resolveToken(options) {
|
|
|
292963
293037
|
}
|
|
292964
293038
|
const report = describeTokenLookup(result);
|
|
292965
293039
|
log$13.error(report ? `${report.detail} ${report.fix}` : `No authentication found. Run '${"lh"} login' first, or set ${CLI_API_KEY_ENV}, or provide --token.`);
|
|
292966
|
-
process.exit(1);
|
|
293040
|
+
process.exit(result.status === "unavailable" ? options.unavailableExitCode ?? 1 : 1);
|
|
292967
293041
|
}
|
|
292968
293042
|
|
|
292969
293043
|
//#endregion
|
|
@@ -296321,6 +296395,23 @@ var TerminalSessionManager = class {
|
|
|
296321
296395
|
const SERVICE_NAME = CLI_CONNECT_SERVICE_NAME;
|
|
296322
296396
|
const ENV_NAME_PATTERN = /^[A-Z_]\w*$/i;
|
|
296323
296397
|
const SERVICE_ENV_FILE_NAME = "connect-service.env";
|
|
296398
|
+
/**
|
|
296399
|
+
* Exit code for workspace failures worth retrying (EX_TEMPFAIL). It sits outside the
|
|
296400
|
+
* unit's `SuccessExitStatus`, so `Restart=on-failure` restarts the service child.
|
|
296401
|
+
*/
|
|
296402
|
+
const CONNECT_RETRYABLE_EXIT_CODE = 75;
|
|
296403
|
+
const PERMANENT_TRPC_ERROR_CODES = new Set([
|
|
296404
|
+
"BAD_REQUEST",
|
|
296405
|
+
"CONFLICT",
|
|
296406
|
+
"FORBIDDEN",
|
|
296407
|
+
"NOT_FOUND",
|
|
296408
|
+
"UNAUTHORIZED"
|
|
296409
|
+
]);
|
|
296410
|
+
/** Exit code for a failed workspace operation (token mint, refresh, device registration): 1 for refusals, 75 otherwise. */
|
|
296411
|
+
function getWorkspaceFailureExitCode(error) {
|
|
296412
|
+
const code = error?.data?.code;
|
|
296413
|
+
return code && PERMANENT_TRPC_ERROR_CODES.has(code) ? 1 : 75;
|
|
296414
|
+
}
|
|
296324
296415
|
function systemctl(args, stdio = "pipe") {
|
|
296325
296416
|
const output = execFileSync("systemctl", ["--user", ...args], {
|
|
296326
296417
|
encoding: "utf8",
|
|
@@ -296346,7 +296437,13 @@ function getExecErrorMessage(error) {
|
|
|
296346
296437
|
if (stderr) return stderr;
|
|
296347
296438
|
return error instanceof Error ? error.message : String(error);
|
|
296348
296439
|
}
|
|
296349
|
-
function
|
|
296440
|
+
function buildServiceConnectArgs(options) {
|
|
296441
|
+
const args = ["connect", "--service-child"];
|
|
296442
|
+
if (options.workspace) args.push("--workspace", options.workspace);
|
|
296443
|
+
if (options.public) args.push("--public");
|
|
296444
|
+
return args;
|
|
296445
|
+
}
|
|
296446
|
+
function renderUnit(options) {
|
|
296350
296447
|
const quoteSystemdValue = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
|
|
296351
296448
|
const escapeSystemdEnvValue = (value) => value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
|
|
296352
296449
|
const homeDir = os.homedir();
|
|
@@ -296357,16 +296454,14 @@ function renderUnit() {
|
|
|
296357
296454
|
const execStart = [
|
|
296358
296455
|
process.execPath,
|
|
296359
296456
|
scriptPath,
|
|
296360
|
-
|
|
296361
|
-
"--service-child"
|
|
296457
|
+
...buildServiceConnectArgs(options)
|
|
296362
296458
|
].map(quoteSystemdValue).join(" ");
|
|
296363
296459
|
return [
|
|
296364
296460
|
"[Unit]",
|
|
296365
296461
|
`Description=${CLI_DISPLAY_NAME} connect service`,
|
|
296366
296462
|
"After=network-online.target",
|
|
296367
296463
|
"Wants=network-online.target",
|
|
296368
|
-
"StartLimitIntervalSec=
|
|
296369
|
-
"StartLimitBurst=10",
|
|
296464
|
+
"StartLimitIntervalSec=0",
|
|
296370
296465
|
"",
|
|
296371
296466
|
"[Service]",
|
|
296372
296467
|
"Type=simple",
|
|
@@ -296379,7 +296474,7 @@ function renderUnit() {
|
|
|
296379
296474
|
`StandardError=append:${logPath}`,
|
|
296380
296475
|
"Restart=on-failure",
|
|
296381
296476
|
"SuccessExitStatus=0 1 2",
|
|
296382
|
-
"RestartSec=
|
|
296477
|
+
"RestartSec=5",
|
|
296383
296478
|
"TimeoutStopSec=20",
|
|
296384
296479
|
"",
|
|
296385
296480
|
"[Install]",
|
|
@@ -296436,7 +296531,8 @@ function assertConnectServiceAuthAvailable() {
|
|
|
296436
296531
|
if (process.env.LOBEHUB_JWT || readCliApiKeyEnv() || loadCredentials()) return;
|
|
296437
296532
|
throw new Error(`No authentication found. Run '${"lh"} login' first, or set ${CLI_API_KEY_ENV} before starting the connect service.`);
|
|
296438
296533
|
}
|
|
296439
|
-
function installConnectService() {
|
|
296534
|
+
function installConnectService(options = {}) {
|
|
296535
|
+
if (options.public && !options.workspace) throw new Error("--public requires --workspace.");
|
|
296440
296536
|
requireLinuxSystemd();
|
|
296441
296537
|
assertNoConnectDaemonRunning();
|
|
296442
296538
|
assertConnectServiceAuthAvailable();
|
|
@@ -296445,13 +296541,10 @@ function installConnectService() {
|
|
|
296445
296541
|
mode: 448,
|
|
296446
296542
|
recursive: true
|
|
296447
296543
|
});
|
|
296448
|
-
fs.writeFileSync(path.join(getUserServiceDir(), SERVICE_NAME), renderUnit(), { mode: 420 });
|
|
296544
|
+
fs.writeFileSync(path.join(getUserServiceDir(), SERVICE_NAME), renderUnit(options), { mode: 420 });
|
|
296449
296545
|
systemctl(["daemon-reload"], "inherit");
|
|
296450
|
-
systemctl([
|
|
296451
|
-
|
|
296452
|
-
"--now",
|
|
296453
|
-
SERVICE_NAME
|
|
296454
|
-
], "inherit");
|
|
296546
|
+
systemctl(["enable", SERVICE_NAME], "inherit");
|
|
296547
|
+
systemctl(["restart", SERVICE_NAME], "inherit");
|
|
296455
296548
|
}
|
|
296456
296549
|
function uninstallConnectService() {
|
|
296457
296550
|
const unitPath = path.join(getUserServiceDir(), SERVICE_NAME);
|
|
@@ -507960,7 +508053,7 @@ const sweepLocalTraces = async () => {
|
|
|
507960
508053
|
const SHUTDOWN_METRICS_FLUSH_MS = 3e3;
|
|
507961
508054
|
const CONNECT_SERVICE_NAME = CLI_CONNECT_SERVICE_NAME;
|
|
507962
508055
|
function registerConnectCommand(program) {
|
|
507963
|
-
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 (
|
|
508056
|
+
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 (workspace members)").option("--public", "With --workspace: enroll into the shared pool visible to every member (default: private, visible only to you)").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").option("--service-child", "Internal: runs as the system service child process").action(async (options) => {
|
|
507964
508057
|
if (options.verbose) setVerbose(true);
|
|
507965
508058
|
if (options.daemon) return handleDaemonStart(options);
|
|
507966
508059
|
const isServiceChild = options.serviceChild || process.env.LOBEHUB_CONNECT_SERVICE === "1";
|
|
@@ -508006,9 +508099,12 @@ function registerConnectCommand(program) {
|
|
|
508006
508099
|
});
|
|
508007
508100
|
});
|
|
508008
508101
|
const serviceCmd = connectCmd.command("service").description("Manage the Linux user systemd connect service");
|
|
508009
|
-
serviceCmd.command("install").description("Install and start the Linux user systemd connect service").action(() => {
|
|
508010
|
-
installConnectService(
|
|
508011
|
-
|
|
508102
|
+
serviceCmd.command("install").description("Install and start the Linux user systemd connect service").option("--workspace <id>", "Enroll as a device of this workspace (workspace members)").option("--public", "With --workspace: enroll into the shared pool visible to every member (default: private, visible only to you)").action((options) => {
|
|
508103
|
+
installConnectService({
|
|
508104
|
+
public: options.public,
|
|
508105
|
+
workspace: options.workspace
|
|
508106
|
+
});
|
|
508107
|
+
log$13.info(options.workspace ? `Installed and started ${CONNECT_SERVICE_NAME} for workspace ${options.workspace}.` : `Installed and started ${CONNECT_SERVICE_NAME}.`);
|
|
508012
508108
|
log$13.info("Run 'lh connect service status' to inspect the service.");
|
|
508013
508109
|
});
|
|
508014
508110
|
serviceCmd.command("uninstall").description("Remove the Linux user systemd connect service").action(() => {
|
|
@@ -508079,7 +508175,11 @@ function buildDaemonArgs(options) {
|
|
|
508079
508175
|
return args;
|
|
508080
508176
|
}
|
|
508081
508177
|
async function runConnect(options, isDaemonChild) {
|
|
508082
|
-
|
|
508178
|
+
const unavailableExitCode = options.workspace ? 75 : void 0;
|
|
508179
|
+
let auth = await resolveToken({
|
|
508180
|
+
...options,
|
|
508181
|
+
unavailableExitCode
|
|
508182
|
+
});
|
|
508083
508183
|
const settings = loadSettings();
|
|
508084
508184
|
const gatewayUrl = normalizeUrl(options.gateway) || settings?.gatewayUrl;
|
|
508085
508185
|
if (!gatewayUrl && settings?.serverUrl) {
|
|
@@ -508097,11 +508197,17 @@ async function runConnect(options, isDaemonChild) {
|
|
|
508097
508197
|
let connectToken = auth.token;
|
|
508098
508198
|
let connectTokenType = auth.tokenType;
|
|
508099
508199
|
if (workspaceId) {
|
|
508100
|
-
|
|
508200
|
+
try {
|
|
508201
|
+
connectToken = (await mintWorkspaceConnectToken(auth, workspaceId)).token;
|
|
508202
|
+
} catch (err) {
|
|
508203
|
+
log$13.error(`Workspace connect token mint failed: ${err.message}`);
|
|
508204
|
+
process.exit(getWorkspaceFailureExitCode(err));
|
|
508205
|
+
throw new Error("process.exit", { cause: err });
|
|
508206
|
+
}
|
|
508101
508207
|
connectTokenType = "jwt";
|
|
508102
508208
|
}
|
|
508103
508209
|
const refreshConnectToken = async () => {
|
|
508104
|
-
const refreshed = await resolveToken({});
|
|
508210
|
+
const refreshed = await resolveToken({ unavailableExitCode });
|
|
508105
508211
|
if (!refreshed) return void 0;
|
|
508106
508212
|
auth = refreshed;
|
|
508107
508213
|
if (workspaceId) {
|
|
@@ -508340,6 +508446,7 @@ async function runConnect(options, isDaemonChild) {
|
|
|
508340
508446
|
let cancelRefreshTimer = startProactiveRefresh();
|
|
508341
508447
|
let authFailedRefreshAttempted = false;
|
|
508342
508448
|
client.on("auth_failed", async (reason) => {
|
|
508449
|
+
let exitCode = 1;
|
|
508343
508450
|
if (connectTokenType === "jwt" && !authFailedRefreshAttempted) {
|
|
508344
508451
|
authFailedRefreshAttempted = true;
|
|
508345
508452
|
info(`Authentication failed (${reason}). Attempting token refresh...`);
|
|
@@ -508354,16 +508461,20 @@ async function runConnect(options, isDaemonChild) {
|
|
|
508354
508461
|
await client.reconnect();
|
|
508355
508462
|
return;
|
|
508356
508463
|
}
|
|
508357
|
-
} catch {
|
|
508464
|
+
} catch (err) {
|
|
508465
|
+
error(`Token refresh failed: ${err.message}`);
|
|
508466
|
+
if (workspaceId) exitCode = getWorkspaceFailureExitCode(err);
|
|
508467
|
+
}
|
|
508358
508468
|
}
|
|
508359
508469
|
error(`Authentication failed: ${reason}`);
|
|
508360
508470
|
error(`Run '${"lh"} login', or set ${CLI_API_KEY_ENV} and run '${"lh"} login --server <url>' to configure API key authentication.`);
|
|
508361
508471
|
cleanup();
|
|
508362
|
-
process.exit(
|
|
508472
|
+
process.exit(exitCode);
|
|
508363
508473
|
});
|
|
508364
508474
|
client.on("auth_expired", async () => {
|
|
508365
508475
|
if (connectTokenType === "apiKey") return;
|
|
508366
508476
|
info("Authentication expired. Attempting to refresh token...");
|
|
508477
|
+
let exitCode = 1;
|
|
508367
508478
|
try {
|
|
508368
508479
|
const newToken = await refreshConnectToken();
|
|
508369
508480
|
if (newToken) {
|
|
@@ -508373,10 +508484,13 @@ async function runConnect(options, isDaemonChild) {
|
|
|
508373
508484
|
await client.reconnect();
|
|
508374
508485
|
return;
|
|
508375
508486
|
}
|
|
508376
|
-
} catch {
|
|
508487
|
+
} catch (err) {
|
|
508488
|
+
error(`Token refresh failed: ${err.message}`);
|
|
508489
|
+
if (workspaceId) exitCode = getWorkspaceFailureExitCode(err);
|
|
508490
|
+
}
|
|
508377
508491
|
error("Could not refresh token. Run 'lh login' to re-authenticate.");
|
|
508378
508492
|
cleanup();
|
|
508379
|
-
process.exit(
|
|
508493
|
+
process.exit(exitCode);
|
|
508380
508494
|
});
|
|
508381
508495
|
client.on("replaced", () => {
|
|
508382
508496
|
error(`Another '${"lh"} connect' on this machine took over this device's gateway connection. Only one can be connected at a time; stopping this one.`);
|
|
@@ -508411,7 +508525,7 @@ async function runConnect(options, isDaemonChild) {
|
|
|
508411
508525
|
if (workspaceId) {
|
|
508412
508526
|
error(`Workspace device registration failed: ${err.message}`);
|
|
508413
508527
|
cleanup();
|
|
508414
|
-
process.exit(
|
|
508528
|
+
process.exit(getWorkspaceFailureExitCode(err));
|
|
508415
508529
|
}
|
|
508416
508530
|
error(`Device registration failed (non-fatal): ${err.message}`);
|
|
508417
508531
|
}
|
|
@@ -512440,6 +512554,7 @@ const admissionHint = {
|
|
|
512440
512554
|
pending_decision: "a decision gate is still waiting on an answer",
|
|
512441
512555
|
review_changed: "task review feedback changed after this turn started",
|
|
512442
512556
|
stale_input: "the graph moved after this turn started, so the plan would apply to stale input",
|
|
512557
|
+
started_from_draft: "the Goal was started from its draft, which settled this turn; the next turn plans further work",
|
|
512443
512558
|
turn_owner: "this Goal has no matching planning turn",
|
|
512444
512559
|
turn_settled: "this turn's run is no longer running",
|
|
512445
512560
|
wait_unsettled: "unfinished work must be settled before waiting",
|
|
@@ -512695,10 +512810,12 @@ function registerGoalCommand$1(program) {
|
|
|
512695
512810
|
};
|
|
512696
512811
|
const result = fromTopic ? hasOperationToken() ? await client.goal.createConversationGoal.mutate({
|
|
512697
512812
|
...goalInput,
|
|
512813
|
+
draft: true,
|
|
512698
512814
|
operationId
|
|
512699
512815
|
}) : await client.goal.create.mutate({
|
|
512700
512816
|
...goalInput,
|
|
512701
512817
|
agentId: process.env.LOBEHUB_AGENT_ID,
|
|
512818
|
+
draft: true,
|
|
512702
512819
|
conversationOperationId: operationId,
|
|
512703
512820
|
conversationTopicId: process.env.LOBEHUB_TOPIC_ID
|
|
512704
512821
|
}) : await client.goal.create.mutate({
|
|
@@ -512726,7 +512843,11 @@ function registerGoalCommand$1(program) {
|
|
|
512726
512843
|
"first; every other seed task (or only --units) leaves the graph as a roster unit, its description",
|
|
512727
512844
|
"kept as that unit's note, and is released wave by wave once the probes ran through without a person.",
|
|
512728
512845
|
"A rejection revises the recipe and re-probes. Tasks that differ only in names, files or details are",
|
|
512729
|
-
"one recipe; tasks whose steps differ are not, so start those without --rollout."
|
|
512846
|
+
"one recipe; tasks whose steps differ are not, so start those without --rollout.",
|
|
512847
|
+
"Inside a batch a seed may depend only on a probe, and folded seeds need distinct titles.",
|
|
512848
|
+
"",
|
|
512849
|
+
"Starting a draft that holds tasks settles the planning turn that created it: submit any plan with",
|
|
512850
|
+
"lh goal plan before starting, not after."
|
|
512730
512851
|
].join("\n")).option("--rollout <mode>", "Fold the seed tasks into a batch: canary or full").option("--probes <nodeIds...>", "Seed task node ids that run first (2-5 for canary)").option("--units <nodeIds...>", "Seed task node ids that become batch units (default: every other seed)").option("--recipe <text>", "The procedure every unit follows").option("--recipe-file <path>", "Read the recipe from a markdown file").option("--variant <axis=values...>", "An axis the units differ along, e.g. mechanism=replica,swr (repeatable); the gate holds until the probes cover each value").option("--operation <id>", "Defaults to LOBEHUB_OPERATION_ID").option("--json", "Output JSON").action(async (id, options) => {
|
|
512731
512852
|
let rollout;
|
|
512732
512853
|
if (options.rollout) {
|
|
@@ -522792,7 +522913,7 @@ const EMPTY_COMPLETION_RESULT = { completion: {
|
|
|
522792
522913
|
} };
|
|
522793
522914
|
|
|
522794
522915
|
//#endregion
|
|
522795
|
-
//#region node_modules/.pnpm/@hono+node-server@2.1.
|
|
522916
|
+
//#region node_modules/.pnpm/@hono+node-server@2.1.5_hono@4.13.13/node_modules/@hono/node-server/dist/constants-BLSFu_RU.mjs
|
|
522796
522917
|
const X_ALREADY_SENT = "x-hono-already-sent";
|
|
522797
522918
|
|
|
522798
522919
|
//#endregion
|
|
@@ -522821,7 +522942,7 @@ const defineWebSocketHelper = (handler) => {
|
|
|
522821
522942
|
};
|
|
522822
522943
|
|
|
522823
522944
|
//#endregion
|
|
522824
|
-
//#region node_modules/.pnpm/@hono+node-server@2.1.
|
|
522945
|
+
//#region node_modules/.pnpm/@hono+node-server@2.1.5_hono@4.13.13/node_modules/@hono/node-server/dist/index.mjs
|
|
522825
522946
|
var RequestError = class extends Error {
|
|
522826
522947
|
constructor(message, options) {
|
|
522827
522948
|
super(message, options);
|
|
@@ -523643,7 +523764,7 @@ const makeCloseHandler = (req, incoming, outgoing, needsBodyCleanup) => () => {
|
|
|
523643
523764
|
if (incoming.errored) {
|
|
523644
523765
|
recordBodyBufferedBeforeDisconnect(incoming);
|
|
523645
523766
|
req[abortRequest](incoming.errored.toString());
|
|
523646
|
-
} else if (!outgoing.writableFinished) {
|
|
523767
|
+
} else if (!outgoing.writableFinished || incoming instanceof Http2ServerRequest && incoming.aborted) {
|
|
523647
523768
|
recordBodyBufferedBeforeDisconnect(incoming);
|
|
523648
523769
|
req[abortRequest]("Client connection prematurely closed.");
|
|
523649
523770
|
}
|
|
@@ -535089,6 +535210,345 @@ async function uploadReportEvidence(client, params) {
|
|
|
535089
535210
|
};
|
|
535090
535211
|
}
|
|
535091
535212
|
|
|
535213
|
+
//#endregion
|
|
535214
|
+
//#region src/commands/acceptanceLanguage.ts
|
|
535215
|
+
const HAN = {
|
|
535216
|
+
languageName: "Chinese",
|
|
535217
|
+
pattern: /\p{Script=Han}/u
|
|
535218
|
+
};
|
|
535219
|
+
const CYRILLIC = {
|
|
535220
|
+
languageName: "Cyrillic script",
|
|
535221
|
+
pattern: /\p{Script=Cyrillic}/u
|
|
535222
|
+
};
|
|
535223
|
+
const ARABIC = {
|
|
535224
|
+
languageName: "Arabic script",
|
|
535225
|
+
pattern: /\p{Script=Arabic}/u
|
|
535226
|
+
};
|
|
535227
|
+
/** Keyed by the primary language subtag of `responseLanguage` (`zh-CN` → `zh`). */
|
|
535228
|
+
const SCRIPT_RULES = {
|
|
535229
|
+
ar: {
|
|
535230
|
+
...ARABIC,
|
|
535231
|
+
languageName: "Arabic"
|
|
535232
|
+
},
|
|
535233
|
+
be: CYRILLIC,
|
|
535234
|
+
bg: {
|
|
535235
|
+
...CYRILLIC,
|
|
535236
|
+
languageName: "Bulgarian"
|
|
535237
|
+
},
|
|
535238
|
+
el: {
|
|
535239
|
+
languageName: "Greek",
|
|
535240
|
+
pattern: /\p{Script=Greek}/u
|
|
535241
|
+
},
|
|
535242
|
+
fa: {
|
|
535243
|
+
...ARABIC,
|
|
535244
|
+
languageName: "Persian"
|
|
535245
|
+
},
|
|
535246
|
+
he: {
|
|
535247
|
+
languageName: "Hebrew",
|
|
535248
|
+
pattern: /\p{Script=Hebrew}/u
|
|
535249
|
+
},
|
|
535250
|
+
hi: {
|
|
535251
|
+
languageName: "Hindi",
|
|
535252
|
+
pattern: /\p{Script=Devanagari}/u
|
|
535253
|
+
},
|
|
535254
|
+
ja: {
|
|
535255
|
+
languageName: "Japanese",
|
|
535256
|
+
pattern: /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}]/u
|
|
535257
|
+
},
|
|
535258
|
+
kk: CYRILLIC,
|
|
535259
|
+
ko: {
|
|
535260
|
+
languageName: "Korean",
|
|
535261
|
+
pattern: /\p{Script=Hangul}/u
|
|
535262
|
+
},
|
|
535263
|
+
mn: CYRILLIC,
|
|
535264
|
+
ru: {
|
|
535265
|
+
...CYRILLIC,
|
|
535266
|
+
languageName: "Russian"
|
|
535267
|
+
},
|
|
535268
|
+
sr: CYRILLIC,
|
|
535269
|
+
th: {
|
|
535270
|
+
languageName: "Thai",
|
|
535271
|
+
pattern: /\p{Script=Thai}/u
|
|
535272
|
+
},
|
|
535273
|
+
uk: {
|
|
535274
|
+
...CYRILLIC,
|
|
535275
|
+
languageName: "Ukrainian"
|
|
535276
|
+
},
|
|
535277
|
+
zh: HAN
|
|
535278
|
+
};
|
|
535279
|
+
const responseLanguageSettingsUrl = () => {
|
|
535280
|
+
const url = new URL("/settings/appearance", resolveServerUrl());
|
|
535281
|
+
url.username = "";
|
|
535282
|
+
url.password = "";
|
|
535283
|
+
return url.toString();
|
|
535284
|
+
};
|
|
535285
|
+
function reportLanguageFor(responseLanguage) {
|
|
535286
|
+
const locale = responseLanguage?.trim();
|
|
535287
|
+
if (!locale) return { status: "unset" };
|
|
535288
|
+
const rule = SCRIPT_RULES[locale.split(/[-_]/)[0].toLowerCase()];
|
|
535289
|
+
return rule ? {
|
|
535290
|
+
locale,
|
|
535291
|
+
rule,
|
|
535292
|
+
status: "enforced"
|
|
535293
|
+
} : {
|
|
535294
|
+
locale,
|
|
535295
|
+
status: "unchecked"
|
|
535296
|
+
};
|
|
535297
|
+
}
|
|
535298
|
+
async function resolveReportLanguage(client) {
|
|
535299
|
+
const value = (await client.user.getUserState.query())?.settings?.general?.responseLanguage;
|
|
535300
|
+
return reportLanguageFor(typeof value === "string" ? value : void 0);
|
|
535301
|
+
}
|
|
535302
|
+
/**
|
|
535303
|
+
* Code, links and paths are legitimately English inside any report; strip them
|
|
535304
|
+
* so "修复了 `useFoo` 的问题" passes and a bare `npm test` field is judged on
|
|
535305
|
+
* nothing rather than on the identifier.
|
|
535306
|
+
*/
|
|
535307
|
+
const stripNonProse = (text) => text.replaceAll(/(`{3,}|~{3,})[\s\S]*?\1/g, " ").replaceAll(/`[^`\n]*`/g, " ").replaceAll(/!?\[[^\]]*\]\([^)]*\)/g, (match) => match.replace(/\([^)]*\)$/, "")).replaceAll(/https?:\/\/[^\s<>"'`,。!?、;:()【】「」《》]+/g, " ");
|
|
535308
|
+
/**
|
|
535309
|
+
* A field fails when it contains letters but none in the target script — the
|
|
535310
|
+
* whole field is in another language. Mixed text with identifiers passes.
|
|
535311
|
+
*/
|
|
535312
|
+
function findLanguageViolations(fields, rule) {
|
|
535313
|
+
return fields.flatMap((field) => {
|
|
535314
|
+
if (typeof field.text !== "string") return [];
|
|
535315
|
+
const prose = stripNonProse(field.text);
|
|
535316
|
+
if (!/\p{L}/u.test(prose) || rule.pattern.test(prose)) return [];
|
|
535317
|
+
return [{
|
|
535318
|
+
path: field.path,
|
|
535319
|
+
text: field.text
|
|
535320
|
+
}];
|
|
535321
|
+
});
|
|
535322
|
+
}
|
|
535323
|
+
/** Prose in a check definition: its outcome, preconditions and step wording. */
|
|
535324
|
+
function checkDefinitionFields(at, definition) {
|
|
535325
|
+
const value = definition ?? {};
|
|
535326
|
+
const preconditions = Array.isArray(value.preconditions) ? value.preconditions : [];
|
|
535327
|
+
const steps = Array.isArray(value.steps) ? value.steps : [];
|
|
535328
|
+
return [
|
|
535329
|
+
{
|
|
535330
|
+
path: `${at}.expected`,
|
|
535331
|
+
text: value.expected
|
|
535332
|
+
},
|
|
535333
|
+
...preconditions.map((text, index) => ({
|
|
535334
|
+
path: `${at}.preconditions[${index}]`,
|
|
535335
|
+
text
|
|
535336
|
+
})),
|
|
535337
|
+
...steps.flatMap((step) => [{
|
|
535338
|
+
path: `${at}.steps[${String(step?.id)}].instruction`,
|
|
535339
|
+
text: step?.instruction
|
|
535340
|
+
}, {
|
|
535341
|
+
path: `${at}.steps[${String(step?.id)}].expected`,
|
|
535342
|
+
text: step?.expected
|
|
535343
|
+
}])
|
|
535344
|
+
];
|
|
535345
|
+
}
|
|
535346
|
+
/**
|
|
535347
|
+
* Video step/claim captions and the before/after caption band. Shared by
|
|
535348
|
+
* `ingest` and `evidence upload --metadata`, which persist the same shape.
|
|
535349
|
+
*/
|
|
535350
|
+
function evidenceCaptionFields(at, metadata) {
|
|
535351
|
+
const value = metadata ?? {};
|
|
535352
|
+
return [...(Array.isArray(value.chapters) ? value.chapters : []).flatMap((chapter, i) => [{
|
|
535353
|
+
path: `${at}.chapters[${i}].label`,
|
|
535354
|
+
text: chapter?.label
|
|
535355
|
+
}, {
|
|
535356
|
+
path: `${at}.chapters[${i}].note`,
|
|
535357
|
+
text: chapter?.note
|
|
535358
|
+
}]), {
|
|
535359
|
+
path: `${at}.comparison.label`,
|
|
535360
|
+
text: value.comparison?.label
|
|
535361
|
+
}];
|
|
535362
|
+
}
|
|
535363
|
+
/** Every `ingest` field that renders as prose on the acceptance page. */
|
|
535364
|
+
function ingestLanguageFields(input) {
|
|
535365
|
+
const fields = [
|
|
535366
|
+
{
|
|
535367
|
+
path: "title",
|
|
535368
|
+
text: input.title
|
|
535369
|
+
},
|
|
535370
|
+
{
|
|
535371
|
+
path: "focus",
|
|
535372
|
+
text: input.goal
|
|
535373
|
+
},
|
|
535374
|
+
{
|
|
535375
|
+
path: "requirement",
|
|
535376
|
+
text: input.requirement
|
|
535377
|
+
},
|
|
535378
|
+
{
|
|
535379
|
+
path: "summary.conclusion",
|
|
535380
|
+
text: input.conclusion
|
|
535381
|
+
},
|
|
535382
|
+
{
|
|
535383
|
+
path: "report.md",
|
|
535384
|
+
text: input.report
|
|
535385
|
+
},
|
|
535386
|
+
{
|
|
535387
|
+
path: "proposal.md",
|
|
535388
|
+
text: input.proposal
|
|
535389
|
+
}
|
|
535390
|
+
];
|
|
535391
|
+
for (const item of input.plan ?? []) {
|
|
535392
|
+
const config = item.verifierConfig ?? {};
|
|
535393
|
+
const evidenceSpecs = Array.isArray(config.requiredEvidence) ? config.requiredEvidence : [];
|
|
535394
|
+
fields.push({
|
|
535395
|
+
path: `plan[${item.id}].title`,
|
|
535396
|
+
text: item.title
|
|
535397
|
+
}, {
|
|
535398
|
+
path: `plan[${item.id}].category`,
|
|
535399
|
+
text: item.category
|
|
535400
|
+
}, {
|
|
535401
|
+
path: `plan[${item.id}].method`,
|
|
535402
|
+
text: config.method
|
|
535403
|
+
}, {
|
|
535404
|
+
path: `plan[${item.id}].expected`,
|
|
535405
|
+
text: config.expected
|
|
535406
|
+
}, {
|
|
535407
|
+
path: `plan[${item.id}].description`,
|
|
535408
|
+
text: item.description
|
|
535409
|
+
}, ...checkDefinitionFields(`plan[${item.id}].definition`, item.definition), ...evidenceSpecs.map((spec, index) => ({
|
|
535410
|
+
path: `plan[${item.id}].requiredEvidence[${index}].hint`,
|
|
535411
|
+
text: spec?.hint
|
|
535412
|
+
})));
|
|
535413
|
+
}
|
|
535414
|
+
for (const { case: c, checkItemId } of input.cases) {
|
|
535415
|
+
fields.push({
|
|
535416
|
+
path: `cases[${checkItemId}].name`,
|
|
535417
|
+
text: c.name ?? c.case ?? c.title
|
|
535418
|
+
}, {
|
|
535419
|
+
path: `cases[${checkItemId}].observation`,
|
|
535420
|
+
text: c.keyObservation ?? c.observation ?? c.note
|
|
535421
|
+
}, {
|
|
535422
|
+
path: `cases[${checkItemId}].suggestion`,
|
|
535423
|
+
text: c.suggestion
|
|
535424
|
+
});
|
|
535425
|
+
(Array.isArray(c.evidence) ? c.evidence : [c.evidence]).forEach((entry, index) => {
|
|
535426
|
+
if (!entry || typeof entry !== "object") return;
|
|
535427
|
+
const at = `cases[${checkItemId}].evidence[${index}]`;
|
|
535428
|
+
fields.push({
|
|
535429
|
+
path: `${at}.description`,
|
|
535430
|
+
text: entry.description ?? entry.desc
|
|
535431
|
+
}, ...evidenceCaptionFields(at, entry));
|
|
535432
|
+
});
|
|
535433
|
+
fields.push(...visualizationFields(`cases[${checkItemId}]`, c));
|
|
535434
|
+
}
|
|
535435
|
+
return fields;
|
|
535436
|
+
}
|
|
535437
|
+
/**
|
|
535438
|
+
* Visualization prose: titles, context and axis/series labels. Categories and
|
|
535439
|
+
* table cells are data (model names, file names, ids) and legitimately Latin,
|
|
535440
|
+
* so they are not checked — a false rejection has no bypass.
|
|
535441
|
+
*/
|
|
535442
|
+
function visualizationFields(at, c) {
|
|
535443
|
+
const datasets = Array.isArray(c.datasets) ? c.datasets : [];
|
|
535444
|
+
const views = Array.isArray(c.visualizations) ? c.visualizations : [];
|
|
535445
|
+
return [...datasets.flatMap((dataset, d) => (Array.isArray(dataset?.fields) ? dataset.fields : []).map((field, f) => ({
|
|
535446
|
+
path: `${at}.datasets[${d}].fields[${f}].label`,
|
|
535447
|
+
text: field?.label
|
|
535448
|
+
}))), ...views.flatMap((view, v) => {
|
|
535449
|
+
const series = Array.isArray(view?.encoding?.series) ? view.encoding.series : [];
|
|
535450
|
+
return [
|
|
535451
|
+
{
|
|
535452
|
+
path: `${at}.visualizations[${v}].title`,
|
|
535453
|
+
text: view?.title
|
|
535454
|
+
},
|
|
535455
|
+
{
|
|
535456
|
+
path: `${at}.visualizations[${v}].context`,
|
|
535457
|
+
text: view?.context
|
|
535458
|
+
},
|
|
535459
|
+
...[
|
|
535460
|
+
"xLabel",
|
|
535461
|
+
"yLabel",
|
|
535462
|
+
"valueLabel"
|
|
535463
|
+
].map((key) => ({
|
|
535464
|
+
path: `${at}.visualizations[${v}].encoding.${key}`,
|
|
535465
|
+
text: view?.encoding?.[key]
|
|
535466
|
+
})),
|
|
535467
|
+
...series.map((entry, i) => ({
|
|
535468
|
+
path: `${at}.visualizations[${v}].encoding.series[${i}].label`,
|
|
535469
|
+
text: entry?.label
|
|
535470
|
+
}))
|
|
535471
|
+
];
|
|
535472
|
+
})];
|
|
535473
|
+
}
|
|
535474
|
+
const preview = (text) => {
|
|
535475
|
+
const line = text.replaceAll(/\s+/g, " ").trim();
|
|
535476
|
+
return line.length > 80 ? `${line.slice(0, 77)}...` : line;
|
|
535477
|
+
};
|
|
535478
|
+
var ReportLanguageError = class extends Error {
|
|
535479
|
+
constructor(locale, violations, languageName) {
|
|
535480
|
+
super([
|
|
535481
|
+
`Report language check failed: your account's response language (settings.general.responseLanguage) is "${locale}", so every human-readable field must be written in ${languageName}.`,
|
|
535482
|
+
`These ${violations.length} field(s) contain no ${languageName} text:`,
|
|
535483
|
+
...violations.map((v) => ` - ${v.path}: "${preview(v.text)}"`),
|
|
535484
|
+
`Rewrite them in ${languageName} and retry. If "${locale}" is not the language you read, change Response Language at ${responseLanguageSettingsUrl()} first.`
|
|
535485
|
+
].join("\n"));
|
|
535486
|
+
this.locale = locale;
|
|
535487
|
+
this.violations = violations;
|
|
535488
|
+
this.name = "ReportLanguageError";
|
|
535489
|
+
}
|
|
535490
|
+
};
|
|
535491
|
+
const unsetLanguageWarning = () => `Report language not checked: your account has no response language set (Response Language is "Follow System" at ${responseLanguageSettingsUrl()}). Set it so acceptance text is checked against the language you read.`;
|
|
535492
|
+
/**
|
|
535493
|
+
* Throws `ReportLanguageError` before any remote write when a field is in the
|
|
535494
|
+
* wrong script. There is intentionally no bypass flag: an agent that hits the
|
|
535495
|
+
* error would just add it.
|
|
535496
|
+
*/
|
|
535497
|
+
async function assertReportLanguage(client, fields, language) {
|
|
535498
|
+
const resolved = language ?? await resolveReportLanguage(client);
|
|
535499
|
+
if (resolved.status === "unset") {
|
|
535500
|
+
log$13.warn(unsetLanguageWarning());
|
|
535501
|
+
return resolved;
|
|
535502
|
+
}
|
|
535503
|
+
if (resolved.status !== "enforced") return resolved;
|
|
535504
|
+
const violations = findLanguageViolations(fields, resolved.rule);
|
|
535505
|
+
if (violations.length > 0) throw new ReportLanguageError(resolved.locale, violations, resolved.rule.languageName);
|
|
535506
|
+
return resolved;
|
|
535507
|
+
}
|
|
535508
|
+
/** JSON-safe view of the resolved language for `--json` output. */
|
|
535509
|
+
const languageSummary = (language) => ({
|
|
535510
|
+
enforced: language.status === "enforced",
|
|
535511
|
+
locale: language.status === "unset" ? null : language.locale,
|
|
535512
|
+
...language.status === "enforced" ? { languageName: language.rule.languageName } : {},
|
|
535513
|
+
status: language.status
|
|
535514
|
+
});
|
|
535515
|
+
/**
|
|
535516
|
+
* Prose in a flow definition: its title, every inline check's wording, and
|
|
535517
|
+
* edge triggers/conditions, which render as graph labels.
|
|
535518
|
+
*/
|
|
535519
|
+
function flowLanguageFields(definition) {
|
|
535520
|
+
const flow = definition ?? {};
|
|
535521
|
+
const fields = [{
|
|
535522
|
+
path: "definition.title",
|
|
535523
|
+
text: flow.title
|
|
535524
|
+
}];
|
|
535525
|
+
const nodes = Array.isArray(flow.nodes) ? flow.nodes : [];
|
|
535526
|
+
for (const node of nodes) {
|
|
535527
|
+
const check = node?.check;
|
|
535528
|
+
if (!check || typeof check !== "object") continue;
|
|
535529
|
+
const at = `nodes[${String(check.id ?? node.id)}].check`;
|
|
535530
|
+
fields.push({
|
|
535531
|
+
path: `${at}.title`,
|
|
535532
|
+
text: check.title
|
|
535533
|
+
}, {
|
|
535534
|
+
path: `${at}.description`,
|
|
535535
|
+
text: check.description
|
|
535536
|
+
}, ...checkDefinitionFields(`${at}.definition`, check.definition));
|
|
535537
|
+
}
|
|
535538
|
+
const edges = Array.isArray(flow.edges) ? flow.edges : [];
|
|
535539
|
+
for (const edge of edges) {
|
|
535540
|
+
const value = edge ?? {};
|
|
535541
|
+
fields.push({
|
|
535542
|
+
path: `edges[${String(value.id)}].trigger`,
|
|
535543
|
+
text: value.trigger
|
|
535544
|
+
}, {
|
|
535545
|
+
path: `edges[${String(value.id)}].condition`,
|
|
535546
|
+
text: value.condition
|
|
535547
|
+
});
|
|
535548
|
+
}
|
|
535549
|
+
return fields;
|
|
535550
|
+
}
|
|
535551
|
+
|
|
535092
535552
|
//#endregion
|
|
535093
535553
|
//#region src/commands/acceptanceRun.ts
|
|
535094
535554
|
const listMaterializedFiles = (directory) => {
|
|
@@ -535173,7 +535633,15 @@ function printWiring(links) {
|
|
|
535173
535633
|
}
|
|
535174
535634
|
}
|
|
535175
535635
|
async function runCreateAction(options) {
|
|
535176
|
-
const
|
|
535636
|
+
const client = await getTrpcClient();
|
|
535637
|
+
await assertReportLanguage(client, [{
|
|
535638
|
+
path: "--title",
|
|
535639
|
+
text: options.title
|
|
535640
|
+
}, {
|
|
535641
|
+
path: "--goal",
|
|
535642
|
+
text: options.goal
|
|
535643
|
+
}]);
|
|
535644
|
+
const created = await client.verify.createRun.mutate({
|
|
535177
535645
|
goal: options.goal,
|
|
535178
535646
|
operationId: options.operation,
|
|
535179
535647
|
source: options.source,
|
|
@@ -535230,7 +535698,22 @@ async function runDeleteAction(runId, options) {
|
|
|
535230
535698
|
console.log(`${import_picocolors.default.green("✓")} Deleted run ${import_picocolors.default.bold(result.id)}`);
|
|
535231
535699
|
}
|
|
535232
535700
|
async function resultIngestAction(options) {
|
|
535233
|
-
const
|
|
535701
|
+
const client = await getTrpcClient();
|
|
535702
|
+
await assertReportLanguage(client, [
|
|
535703
|
+
{
|
|
535704
|
+
path: "--title",
|
|
535705
|
+
text: options.title
|
|
535706
|
+
},
|
|
535707
|
+
{
|
|
535708
|
+
path: "--evidence",
|
|
535709
|
+
text: options.evidence
|
|
535710
|
+
},
|
|
535711
|
+
{
|
|
535712
|
+
path: "--suggestion",
|
|
535713
|
+
text: options.suggestion
|
|
535714
|
+
}
|
|
535715
|
+
]);
|
|
535716
|
+
const created = await client.verify.ingestResult.mutate({
|
|
535234
535717
|
checkItemId: options.check,
|
|
535235
535718
|
checkItemIndex: options.index ? Number.parseInt(options.index, 10) : void 0,
|
|
535236
535719
|
checkItemTitle: options.title,
|
|
@@ -535281,6 +535764,13 @@ async function submitAction(options) {
|
|
|
535281
535764
|
process.exit(1);
|
|
535282
535765
|
}
|
|
535283
535766
|
const client = await getTrpcClient();
|
|
535767
|
+
await assertReportLanguage(client, [{
|
|
535768
|
+
path: "--title",
|
|
535769
|
+
text: options.title
|
|
535770
|
+
}, {
|
|
535771
|
+
path: "--desc",
|
|
535772
|
+
text: options.desc
|
|
535773
|
+
}]);
|
|
535284
535774
|
let fileId;
|
|
535285
535775
|
let inlineContent = options.content;
|
|
535286
535776
|
if (options.file) {
|
|
@@ -535344,6 +535834,10 @@ async function evidenceUploadAction(options) {
|
|
|
535344
535834
|
}
|
|
535345
535835
|
const metadata = options.metadata ? JSON.parse(options.metadata) : void 0;
|
|
535346
535836
|
const client = await getTrpcClient();
|
|
535837
|
+
await assertReportLanguage(client, [{
|
|
535838
|
+
path: "--desc",
|
|
535839
|
+
text: options.desc
|
|
535840
|
+
}, ...evidenceCaptionFields("--metadata", metadata)]);
|
|
535347
535841
|
let fileId = options.fileId;
|
|
535348
535842
|
let inlineContent = options.content;
|
|
535349
535843
|
if (options.file) {
|
|
@@ -535400,7 +535894,15 @@ async function evidenceDeleteAction(evidenceId, options) {
|
|
|
535400
535894
|
}
|
|
535401
535895
|
async function reportUpsertAction(options) {
|
|
535402
535896
|
const num = (s) => s === void 0 ? void 0 : Number.parseInt(s, 10);
|
|
535403
|
-
const
|
|
535897
|
+
const client = await getTrpcClient();
|
|
535898
|
+
await assertReportLanguage(client, [{
|
|
535899
|
+
path: "--summary",
|
|
535900
|
+
text: options.summary
|
|
535901
|
+
}, {
|
|
535902
|
+
path: "--content",
|
|
535903
|
+
text: options.content
|
|
535904
|
+
}]);
|
|
535905
|
+
const created = await client.verify.upsertReport.mutate({
|
|
535404
535906
|
content: options.content,
|
|
535405
535907
|
failedChecks: num(options.failed),
|
|
535406
535908
|
passedChecks: num(options.passed),
|
|
@@ -535518,6 +536020,30 @@ async function ingestReportAction(reportDir, options) {
|
|
|
535518
536020
|
const requirement = options.requirement ?? subject?.requirement;
|
|
535519
536021
|
const conclusion = typeof summary.conclusion === "string" ? summary.conclusion : typeof summary.note === "string" ? summary.note : void 0;
|
|
535520
536022
|
const client = await getTrpcClient();
|
|
536023
|
+
const language = await assertReportLanguage(client, ingestLanguageFields({
|
|
536024
|
+
cases,
|
|
536025
|
+
conclusion,
|
|
536026
|
+
goal,
|
|
536027
|
+
plan,
|
|
536028
|
+
proposal,
|
|
536029
|
+
report: content,
|
|
536030
|
+
requirement,
|
|
536031
|
+
title
|
|
536032
|
+
}));
|
|
536033
|
+
if (options.dryRun) {
|
|
536034
|
+
const checked = language.status === "enforced" ? `report language ${language.locale} ✓` : language.status === "unchecked" ? `report language ${language.locale} (not script-checked)` : "report language not set";
|
|
536035
|
+
if (options.json !== void 0) {
|
|
536036
|
+
outputJson({
|
|
536037
|
+
cases: cases.length,
|
|
536038
|
+
dryRun: true,
|
|
536039
|
+
language: languageSummary(language),
|
|
536040
|
+
planItems: plan?.length ?? 0
|
|
536041
|
+
}, typeof options.json === "string" ? options.json : void 0);
|
|
536042
|
+
return;
|
|
536043
|
+
}
|
|
536044
|
+
console.log(`${import_picocolors.default.green("✓ Dry run passed")} — ${cases.length} case(s), ${plan?.length ?? 0} plan item(s), ${checked}. Nothing was published.`);
|
|
536045
|
+
return;
|
|
536046
|
+
}
|
|
535521
536047
|
let acceptance;
|
|
535522
536048
|
let bundle;
|
|
535523
536049
|
if (requestedAcceptanceId) {
|
|
@@ -535782,7 +536308,7 @@ function withReportUpsertOptions(cmd) {
|
|
|
535782
536308
|
return cmd.requiredOption("--run <verifyRunId>", "Target session id").option("--verdict <verdict>", "passed|failed|uncertain").option("--summary <text>", "Short summary").option("--content <markdown>", "Full markdown body").option("--total <n>", "Total checks").option("--passed <n>", "Passed checks").option("--failed <n>", "Failed checks").option("--uncertain <n>", "Uncertain checks").option("--json [fields]", "Output JSON");
|
|
535783
536309
|
}
|
|
535784
536310
|
function withIngestReportOptions(cmd) {
|
|
535785
|
-
return cmd.option("--source <source>", "agent | agent-testing", "agent-testing").option("--operation <id>", "Link the session to an existing Agent Run").option("--acceptance <id>", "Append this round to an existing acceptance").option("--title <title>", "Override the session title").option("--goal <goal>", "The goal/task being verified").option("--subject <type:id>", "Attach to a task/topic/document (defaults to the current topic; otherwise standalone)").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");
|
|
536311
|
+
return cmd.option("--source <source>", "agent | agent-testing", "agent-testing").option("--operation <id>", "Link the session to an existing Agent Run").option("--acceptance <id>", "Append this round to an existing acceptance").option("--title <title>", "Override the session title").option("--goal <goal>", "The goal/task being verified").option("--subject <type:id>", "Attach to a task/topic/document (defaults to the current topic; otherwise standalone)").option("--requirement <text>", "Acceptance requirement recorded when the aggregate is first created").option("--open", "Print the in-app URL to open the report").option("--dry-run", "Validate the report (including its language) without publishing; works on a plan-only result.json").option("--json [fields]", "Output JSON");
|
|
535786
536312
|
}
|
|
535787
536313
|
/**
|
|
535788
536314
|
* The run-scoped acceptance commands. A run is one immutable round of a
|
|
@@ -535896,6 +536422,7 @@ function attachAcceptanceFlowCommands(acceptance) {
|
|
|
535896
536422
|
flow.command("publish <acceptanceId>").requiredOption("--file <path>", "JSON: definition and optional flowId").action(async (id, options) => {
|
|
535897
536423
|
const client = await getTrpcClient();
|
|
535898
536424
|
const input = JSON.parse(await readFile(options.file, "utf8"));
|
|
536425
|
+
await assertReportLanguage(client, flowLanguageFields(input.definition));
|
|
535899
536426
|
outputJson(await client.acceptance.publishFlow.mutate({
|
|
535900
536427
|
...input,
|
|
535901
536428
|
id
|
|
@@ -535930,6 +536457,10 @@ function attachAcceptanceFlowCommands(acceptance) {
|
|
|
535930
536457
|
flow.command("record <acceptanceId>").requiredOption("--file <path>", "JSON: verifyRunId, checkItemId, verdict, observation").action(async (id, options) => {
|
|
535931
536458
|
const client = await getTrpcClient();
|
|
535932
536459
|
const input = JSON.parse(await readFile(options.file, "utf8"));
|
|
536460
|
+
await assertReportLanguage(client, [{
|
|
536461
|
+
path: "observation",
|
|
536462
|
+
text: input.observation
|
|
536463
|
+
}]);
|
|
535933
536464
|
outputJson(await client.acceptance.recordFlowStep.mutate({
|
|
535934
536465
|
...input,
|
|
535935
536466
|
id
|
|
@@ -535991,6 +536522,23 @@ const userGlyph = (userReview) => {
|
|
|
535991
536522
|
function registerAcceptanceCommands(parent, options) {
|
|
535992
536523
|
const acceptance = parent.command("acceptance").description(options?.deprecated ? "Deprecated alias — use `lh acceptance`" : "Delivery acceptances: the cross-round review loop (checks, feedback, decision)");
|
|
535993
536524
|
attachAcceptanceFlowCommands(acceptance);
|
|
536525
|
+
acceptance.command("preflight").description("Check that the CLI is authenticated and print the language every acceptance text must be written in. Run it before authoring anything, and pass the language to any subagent you delegate to.").option("--json [fields]", "Output JSON").action(async (options) => {
|
|
536526
|
+
const language = await resolveReportLanguage(await getTrpcClient());
|
|
536527
|
+
const instruction = language.status === "unset" ? "No response language is set on this account, so the language is not checked. Write in the language the user converses in." : `Write every human-readable acceptance field (title, plan, case names and observations, evidence descriptions, conclusion, report.md, proposal.md) in ${language.locale}${language.status === "enforced" ? `, i.e. ${language.rule.languageName}` : ""}. Pass this language explicitly to any subagent you delegate to.`;
|
|
536528
|
+
if (options.json !== void 0) {
|
|
536529
|
+
outputJson({
|
|
536530
|
+
authenticated: true,
|
|
536531
|
+
instruction,
|
|
536532
|
+
language: languageSummary(language),
|
|
536533
|
+
settingsUrl: responseLanguageSettingsUrl()
|
|
536534
|
+
}, typeof options.json === "string" ? options.json : void 0);
|
|
536535
|
+
return;
|
|
536536
|
+
}
|
|
536537
|
+
if (language.status === "unset") log$13.warn(unsetLanguageWarning());
|
|
536538
|
+
console.log(`${import_picocolors.default.green("✓")} Authenticated`);
|
|
536539
|
+
console.log(`${import_picocolors.default.bold("report language")}: ${language.status === "unset" ? import_picocolors.default.yellow("not set") : language.locale}${language.status === "unchecked" ? import_picocolors.default.dim(" (not script-checked)") : ""}`);
|
|
536540
|
+
console.log(instruction);
|
|
536541
|
+
});
|
|
535994
536542
|
acceptance.command("create").summary("Create or reuse an acceptance without creating a verification round or results").description("Create or reuse an acceptance record. This does not create checks, verification rounds, results, reports, or evidence.\n\nBefore creating, agents should load and read the acceptance skill (.agents/skills/acceptance/SKILL.md) and follow its workflow. If the skill is missing, run `lh acceptance install`, then read it.\n\nUse create for flow-first planning. If a completed local report already exists, use `lh acceptance run ingest <reportDir>` to publish it; a separate create call is unnecessary. An acceptance URL alone does not mean verification is complete: publish results and required evidence, then read back coverage before handing off a completed acceptance.").requiredOption("--requirement <text>", "Durable business goal (preserved when reusing a subject)").option("-t, --title <text>", "Display title for a standalone acceptance").option("--subject <type:id>", "Reuse or create for task/topic/document/standalone; omit for a new standalone acceptance").option("--json [fields]", "Output JSON, optionally select fields (acceptanceId, acceptanceUrl, requirement, status, subject)").action(async (options) => {
|
|
535995
536543
|
const requirement = options.requirement.trim();
|
|
535996
536544
|
const title = options.title?.trim();
|
|
@@ -536001,7 +536549,15 @@ function registerAcceptanceCommands(parent, options) {
|
|
|
536001
536549
|
subjectType: "standalone"
|
|
536002
536550
|
} : parseSubjectRef(options.subject);
|
|
536003
536551
|
if (!subject) throw new InvalidArgumentError(`--subject must be one of ${acceptanceSubjectTypes.map((type) => `${type}:<id>`).join(" | ")}`);
|
|
536004
|
-
const
|
|
536552
|
+
const client = await getTrpcClient();
|
|
536553
|
+
await assertReportLanguage(client, [{
|
|
536554
|
+
path: "--requirement",
|
|
536555
|
+
text: requirement
|
|
536556
|
+
}, {
|
|
536557
|
+
path: "--title",
|
|
536558
|
+
text: title
|
|
536559
|
+
}]);
|
|
536560
|
+
const result = await client.acceptance.ensure.mutate({
|
|
536005
536561
|
...subject,
|
|
536006
536562
|
requirement,
|
|
536007
536563
|
title
|
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.65" "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.65",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"lh": "dist/index.js",
|
|
@@ -16,11 +16,36 @@
|
|
|
16
16
|
"dist",
|
|
17
17
|
"man"
|
|
18
18
|
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "bun scripts/ensureWorkspaceLinks.ts && tsdown && tsdown src/daemon/maintenanceWorker.ts --no-config --no-clean --format esm --target node22",
|
|
21
|
+
"cli:link": "bun link",
|
|
22
|
+
"cli:unlink": "bun unlink",
|
|
23
|
+
"dev": "LOBEHUB_CLI_HOME=.lobehub-dev bun src/index.ts",
|
|
24
|
+
"man:generate": "bun src/man/generate.ts",
|
|
25
|
+
"prepublishOnly": "npm run build && npm run man:generate",
|
|
26
|
+
"test": "bunx vitest run --config vitest.config.mts --silent='passed-only'",
|
|
27
|
+
"test:coverage": "bunx vitest run --config vitest.config.mts --coverage",
|
|
28
|
+
"test:e2e": "bunx vitest run --config vitest.e2e.config.mts --silent='passed-only'",
|
|
29
|
+
"type-check": "tsc --noEmit"
|
|
30
|
+
},
|
|
19
31
|
"dependencies": {
|
|
20
32
|
"@lydell/node-pty": "1.2.0-beta.15",
|
|
21
33
|
"ws": "^8.21.0"
|
|
22
34
|
},
|
|
23
35
|
"devDependencies": {
|
|
36
|
+
"@lobechat/agent-gateway-client": "workspace:*",
|
|
37
|
+
"@lobechat/agent-tracing": "workspace:*",
|
|
38
|
+
"@lobechat/const": "workspace:*",
|
|
39
|
+
"@lobechat/device-control": "workspace:*",
|
|
40
|
+
"@lobechat/device-gateway-client": "workspace:*",
|
|
41
|
+
"@lobechat/device-identity": "workspace:*",
|
|
42
|
+
"@lobechat/eval-rubric": "workspace:*",
|
|
43
|
+
"@lobechat/heterogeneous-agents": "workspace:*",
|
|
44
|
+
"@lobechat/html-artifact": "workspace:*",
|
|
45
|
+
"@lobechat/local-file-shell": "workspace:*",
|
|
46
|
+
"@lobechat/tool-runtime": "workspace:*",
|
|
47
|
+
"@lobechat/types": "workspace:*",
|
|
48
|
+
"@lobechat/utils": "workspace:*",
|
|
24
49
|
"@trpc/client": "^11.18.0",
|
|
25
50
|
"@types/node": "^24.13.2",
|
|
26
51
|
"@types/semver": "^7.7.1",
|
|
@@ -39,20 +64,7 @@
|
|
|
39
64
|
"tsdown": "^0.21.10",
|
|
40
65
|
"typescript": "^6.0.3",
|
|
41
66
|
"url-join": "^5.0.0",
|
|
42
|
-
"vitest": "5.0.0"
|
|
43
|
-
"@lobechat/const": "1.0.0",
|
|
44
|
-
"@lobechat/device-control": "1.0.0",
|
|
45
|
-
"@lobechat/agent-gateway-client": "1.0.0",
|
|
46
|
-
"@lobechat/agent-tracing": "1.0.0",
|
|
47
|
-
"@lobechat/eval-rubric": "1.0.0",
|
|
48
|
-
"@lobechat/device-gateway-client": "1.0.0",
|
|
49
|
-
"@lobechat/device-identity": "1.0.0",
|
|
50
|
-
"@lobechat/html-artifact": "1.0.0",
|
|
51
|
-
"@lobechat/local-file-shell": "1.0.0",
|
|
52
|
-
"@lobechat/heterogeneous-agents": "1.0.0",
|
|
53
|
-
"@lobechat/tool-runtime": "1.0.0",
|
|
54
|
-
"@lobechat/types": "1.0.0",
|
|
55
|
-
"@lobechat/utils": "1.0.0"
|
|
67
|
+
"vitest": "5.0.0"
|
|
56
68
|
},
|
|
57
69
|
"engines": {
|
|
58
70
|
"node": ">=22.15"
|
|
@@ -60,16 +72,5 @@
|
|
|
60
72
|
"publishConfig": {
|
|
61
73
|
"access": "public",
|
|
62
74
|
"registry": "https://registry.npmjs.org"
|
|
63
|
-
},
|
|
64
|
-
"scripts": {
|
|
65
|
-
"build": "bun scripts/ensureWorkspaceLinks.ts && tsdown && tsdown src/daemon/maintenanceWorker.ts --no-config --no-clean --format esm --target node22",
|
|
66
|
-
"cli:link": "bun link",
|
|
67
|
-
"cli:unlink": "bun unlink",
|
|
68
|
-
"dev": "LOBEHUB_CLI_HOME=.lobehub-dev bun src/index.ts",
|
|
69
|
-
"man:generate": "bun src/man/generate.ts",
|
|
70
|
-
"test": "bunx vitest run --config vitest.config.mts --silent='passed-only'",
|
|
71
|
-
"test:coverage": "bunx vitest run --config vitest.config.mts --coverage",
|
|
72
|
-
"test:e2e": "bunx vitest run --config vitest.e2e.config.mts --silent='passed-only'",
|
|
73
|
-
"type-check": "tsc --noEmit"
|
|
74
75
|
}
|
|
75
|
-
}
|
|
76
|
+
}
|