@lobehub/cli 0.0.54 → 0.0.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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, mkdir, open, readFile, readdir, realpath, rename, rm, stat, unlink as unlink$1, writeFile } from "node:fs/promises";
17
+ import fs$1, { access, copyFile, mkdir, open, readFile, readdir, 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 crypto$2 from "crypto";
@@ -18857,7 +18857,7 @@ const INTEREST_AREA_KEYS = [
18857
18857
  const interestAreaKeySet = new Set(INTEREST_AREA_KEYS);
18858
18858
 
18859
18859
  //#endregion
18860
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/compat/predicate/isPlainObject.mjs
18860
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/compat/predicate/isPlainObject.mjs
18861
18861
  /**
18862
18862
  * Checks if a given value is a plain object.
18863
18863
  *
@@ -18896,13 +18896,13 @@ function isPlainObject$2(object) {
18896
18896
  }
18897
18897
 
18898
18898
  //#endregion
18899
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/compat/_internal/getSymbols.mjs
18899
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/compat/_internal/getSymbols.mjs
18900
18900
  function getSymbols(object) {
18901
18901
  return Object.getOwnPropertySymbols(object).filter((symbol) => Object.prototype.propertyIsEnumerable.call(object, symbol));
18902
18902
  }
18903
18903
 
18904
18904
  //#endregion
18905
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/compat/_internal/getTag.mjs
18905
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/compat/_internal/getTag.mjs
18906
18906
  /**
18907
18907
  * Gets the `toStringTag` of `value`.
18908
18908
  *
@@ -18916,7 +18916,7 @@ function getTag(value) {
18916
18916
  }
18917
18917
 
18918
18918
  //#endregion
18919
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/compat/_internal/tags.mjs
18919
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/compat/_internal/tags.mjs
18920
18920
  const regexpTag = "[object RegExp]";
18921
18921
  const stringTag = "[object String]";
18922
18922
  const numberTag = "[object Number]";
@@ -18941,13 +18941,13 @@ const float32ArrayTag = "[object Float32Array]";
18941
18941
  const float64ArrayTag = "[object Float64Array]";
18942
18942
 
18943
18943
  //#endregion
18944
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/_internal/globalThis.mjs
18944
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/_internal/globalThis.mjs
18945
18945
  const globalThis_ = typeof globalThis === "object" && globalThis || typeof window === "object" && window || typeof self === "object" && self || typeof global === "object" && global || (function() {
18946
18946
  return this;
18947
18947
  })();
18948
18948
 
18949
18949
  //#endregion
18950
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/predicate/isBuffer.mjs
18950
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/predicate/isBuffer.mjs
18951
18951
  /**
18952
18952
  * Checks if the given value is a Buffer instance.
18953
18953
  *
@@ -18971,7 +18971,7 @@ function isBuffer(x) {
18971
18971
  }
18972
18972
 
18973
18973
  //#endregion
18974
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/predicate/isLength.mjs
18974
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/predicate/isLength.mjs
18975
18975
  /**
18976
18976
  * Checks if a given value is a valid length.
18977
18977
  *
@@ -18998,7 +18998,7 @@ function isLength(value) {
18998
18998
  }
18999
18999
 
19000
19000
  //#endregion
19001
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/compat/predicate/isArrayLike.mjs
19001
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/compat/predicate/isArrayLike.mjs
19002
19002
  /**
19003
19003
  * Checks if `value` is array-like.
19004
19004
  *
@@ -19018,15 +19018,18 @@ function isArrayLike(value) {
19018
19018
  }
19019
19019
 
19020
19020
  //#endregion
19021
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/_internal/isUnsafeProperty.mjs
19021
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/_internal/isUnsafeProperty.mjs
19022
19022
  /**
19023
- * Checks if a property key is unsafe to modify directly.
19023
+ * Checks if a property key is unsafe to access or copy directly.
19024
19024
  *
19025
- * This function is used in functions like `merge` to prevent prototype pollution attacks
19026
- * by identifying property keys that could modify the object's prototype chain or constructor.
19025
+ * This function is used in functions like `get`, `unset`, and `merge` to prevent
19026
+ * prototype pollution attacks by identifying `__proto__`, which modifies the object's
19027
+ * prototype chain. `constructor` and `prototype` stay accessible here because lodash
19028
+ * keeps reads of them unrestricted; write paths use `isUnsafeToWriteProperty` instead,
19029
+ * which also blocks those keys.
19027
19030
  *
19028
19031
  * @param key - The property key to check
19029
- * @returns `true` if the property is unsafe to modify directly, `false` otherwise
19032
+ * @returns `true` if the property is unsafe to access directly, `false` otherwise
19030
19033
  * @internal
19031
19034
  */
19032
19035
  function isUnsafeProperty(key) {
@@ -19034,7 +19037,7 @@ function isUnsafeProperty(key) {
19034
19037
  }
19035
19038
 
19036
19039
  //#endregion
19037
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/predicate/isPrimitive.mjs
19040
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/predicate/isPrimitive.mjs
19038
19041
  /**
19039
19042
  * Checks whether a value is a JavaScript primitive.
19040
19043
  * JavaScript primitives include null, undefined, strings, numbers, booleans, symbols, and bigints.
@@ -19061,7 +19064,7 @@ function isPrimitive(value) {
19061
19064
  }
19062
19065
 
19063
19066
  //#endregion
19064
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/predicate/isTypedArray.mjs
19067
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/predicate/isTypedArray.mjs
19065
19068
  /**
19066
19069
  * Checks if a value is a TypedArray.
19067
19070
  * @param x The value to check.
@@ -19082,7 +19085,7 @@ function isTypedArray$2(x) {
19082
19085
  }
19083
19086
 
19084
19087
  //#endregion
19085
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/object/cloneDeepWith.mjs
19088
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/object/cloneDeepWith.mjs
19086
19089
  /**
19087
19090
  * Deeply clones the given object.
19088
19091
  *
@@ -19118,7 +19121,9 @@ function isTypedArray$2(x) {
19118
19121
  * // Clone an array with a customizer
19119
19122
  * const arr = [1, 2, 3];
19120
19123
  * const clonedArr = cloneDeepWith(arr, (value) => {
19121
- * return value + 1; // Increment each value
19124
+ * if (typeof value === 'number') {
19125
+ * return value + 1; // Increment each number
19126
+ * }
19122
19127
  * });
19123
19128
  * console.log(clonedArr); // [2, 3, 4]
19124
19129
  * console.log(clonedArr === arr); // false
@@ -19257,7 +19262,7 @@ function isCloneableObject(object) {
19257
19262
  }
19258
19263
 
19259
19264
  //#endregion
19260
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/compat/object/cloneDeepWith.mjs
19265
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/compat/object/cloneDeepWith.mjs
19261
19266
  /**
19262
19267
  * Creates a deep clone of the given object using a customizer function.
19263
19268
  *
@@ -19288,7 +19293,9 @@ function isCloneableObject(object) {
19288
19293
  * // Clone an array with a customizer
19289
19294
  * const arr = [1, 2, 3];
19290
19295
  * const clonedArr = cloneDeepWith(arr, (value) => {
19291
- * return value + 1; // Increment each value
19296
+ * if (typeof value === 'number') {
19297
+ * return value + 1; // Increment each number
19298
+ * }
19292
19299
  * });
19293
19300
  * console.log(clonedArr); // [2, 3, 4]
19294
19301
  * console.log(clonedArr === arr); // false
@@ -19325,7 +19332,7 @@ function cloneDeepWith(obj, customizer) {
19325
19332
  }
19326
19333
 
19327
19334
  //#endregion
19328
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/compat/object/cloneDeep.mjs
19335
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/compat/object/cloneDeep.mjs
19329
19336
  /**
19330
19337
  * Creates a deep clone of the given object.
19331
19338
  *
@@ -19343,30 +19350,30 @@ function cloneDeepWith(obj, customizer) {
19343
19350
  * @example
19344
19351
  * // Clone an array
19345
19352
  * const arr = [1, 2, 3];
19346
- * const clonedArr = clone(arr);
19353
+ * const clonedArr = cloneDeep(arr);
19347
19354
  * console.log(clonedArr); // [1, 2, 3]
19348
19355
  * console.log(clonedArr === arr); // false
19349
19356
  *
19350
19357
  * @example
19351
19358
  * // Clone an array with nested objects
19352
19359
  * const arr = [1, { a: 1 }, [1, 2, 3]];
19353
- * const clonedArr = clone(arr);
19360
+ * const clonedArr = cloneDeep(arr);
19354
19361
  * arr[1].a = 2;
19355
- * console.log(arr); // [2, { a: 2 }, [1, 2, 3]]
19362
+ * console.log(arr); // [1, { a: 2 }, [1, 2, 3]]
19356
19363
  * console.log(clonedArr); // [1, { a: 1 }, [1, 2, 3]]
19357
19364
  * console.log(clonedArr === arr); // false
19358
19365
  *
19359
19366
  * @example
19360
19367
  * // Clone an object
19361
19368
  * const obj = { a: 1, b: 'es-toolkit', c: [1, 2, 3] };
19362
- * const clonedObj = clone(obj);
19369
+ * const clonedObj = cloneDeep(obj);
19363
19370
  * console.log(clonedObj); // { a: 1, b: 'es-toolkit', c: [1, 2, 3] }
19364
19371
  * console.log(clonedObj === obj); // false
19365
19372
  *
19366
19373
  * @example
19367
19374
  * // Clone an object with nested objects
19368
19375
  * const obj = { a: 1, b: { c: 1 } };
19369
- * const clonedObj = clone(obj);
19376
+ * const clonedObj = cloneDeep(obj);
19370
19377
  * obj.b.c = 2;
19371
19378
  * console.log(obj); // { a: 1, b: { c: 2 } }
19372
19379
  * console.log(clonedObj); // { a: 1, b: { c: 1 } }
@@ -19377,7 +19384,7 @@ function cloneDeep(obj) {
19377
19384
  }
19378
19385
 
19379
19386
  //#endregion
19380
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/compat/predicate/isArguments.mjs
19387
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/compat/predicate/isArguments.mjs
19381
19388
  /**
19382
19389
  * Checks if the given value is an arguments object.
19383
19390
  *
@@ -19403,7 +19410,7 @@ function isArguments$1(value) {
19403
19410
  }
19404
19411
 
19405
19412
  //#endregion
19406
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/compat/predicate/isObjectLike.mjs
19413
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/compat/predicate/isObjectLike.mjs
19407
19414
  /**
19408
19415
  * Checks if the given value is object-like.
19409
19416
  *
@@ -19432,7 +19439,7 @@ function isObjectLike$1(value) {
19432
19439
  }
19433
19440
 
19434
19441
  //#endregion
19435
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/compat/predicate/isArrayLikeObject.mjs
19442
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/compat/predicate/isArrayLikeObject.mjs
19436
19443
  /**
19437
19444
  * Checks if the given value is a non-primitive, array-like object.
19438
19445
  *
@@ -19450,7 +19457,7 @@ function isArrayLikeObject(value) {
19450
19457
  }
19451
19458
 
19452
19459
  //#endregion
19453
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/compat/predicate/isTypedArray.mjs
19460
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/compat/predicate/isTypedArray.mjs
19454
19461
  /**
19455
19462
  * Checks if a value is a TypedArray.
19456
19463
  * @param x The value to check.
@@ -19471,7 +19478,7 @@ function isTypedArray$1(x) {
19471
19478
  }
19472
19479
 
19473
19480
  //#endregion
19474
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/object/clone.mjs
19481
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/object/clone.mjs
19475
19482
  /**
19476
19483
  * Creates a shallow clone of the given object.
19477
19484
  *
@@ -19530,7 +19537,7 @@ function clone$1(obj) {
19530
19537
  }
19531
19538
 
19532
19539
  //#endregion
19533
- //#region ../../node_modules/.pnpm/es-toolkit@1.50.0/node_modules/es-toolkit/dist/compat/object/mergeWith.mjs
19540
+ //#region ../../node_modules/.pnpm/es-toolkit@1.52.0/node_modules/es-toolkit/dist/compat/object/mergeWith.mjs
19534
19541
  /**
19535
19542
  * Merges the properties of one or more source objects into the target object using a customizer function.
19536
19543
  *
@@ -19594,7 +19601,7 @@ function mergeWithDeep(target, source, merge, stack) {
19594
19601
  stack.set(source, target);
19595
19602
  if (Array.isArray(source)) {
19596
19603
  source = source.slice();
19597
- for (let i = 0; i < source.length; i++) source[i] = source[i] ?? void 0;
19604
+ for (let i = 0; i < source.length; i++) if (!(i in source)) source[i] = void 0;
19598
19605
  }
19599
19606
  const sourceKeys = [...Object.keys(source), ...getSymbols(source)];
19600
19607
  for (let i = 0; i < sourceKeys.length; i++) {
@@ -19683,7 +19690,7 @@ const MEMORY_SEARCH_TOP_K_LIMITS = {
19683
19690
 
19684
19691
  //#endregion
19685
19692
  //#region ../../package.json
19686
- var version$2 = "2.2.16";
19693
+ var version$2 = "2.2.17";
19687
19694
 
19688
19695
  //#endregion
19689
19696
  //#region ../../packages/const/src/version.ts
@@ -20119,29 +20126,24 @@ const _clients = /* @__PURE__ */ new Map();
20119
20126
  const _toolsClients = /* @__PURE__ */ new Map();
20120
20127
  async function getAuthAndServer() {
20121
20128
  const envJwt = process.env.LOBEHUB_JWT;
20122
- if (envJwt) {
20123
- const serverUrl = resolveServerUrl();
20124
- return {
20125
- headers: { "Oidc-Auth": envJwt },
20126
- serverUrl
20127
- };
20128
- }
20129
+ if (envJwt) return {
20130
+ headers: () => ({ "Oidc-Auth": process.env.LOBEHUB_JWT || envJwt }),
20131
+ serverUrl: resolveServerUrl()
20132
+ };
20129
20133
  const envApiKey = readCliApiKeyEnv();
20130
- if (envApiKey) {
20131
- const serverUrl = resolveServerUrl();
20132
- return {
20133
- headers: { "X-API-Key": envApiKey },
20134
- serverUrl
20135
- };
20136
- }
20134
+ if (envApiKey) return {
20135
+ headers: () => ({ "X-API-Key": envApiKey }),
20136
+ serverUrl: resolveServerUrl()
20137
+ };
20137
20138
  const result = await getValidToken();
20138
20139
  if (!result) {
20139
20140
  log$11.error(`No authentication found. Run '${"lh"} login' (or 'npx -y ${cliPackageName} login') first, or set ${CLI_API_KEY_ENV}.`);
20140
20141
  process.exit(1);
20141
20142
  }
20142
20143
  const serverUrl = resolveServerUrl();
20144
+ const { accessToken } = result.credentials;
20143
20145
  return {
20144
- headers: { "Oidc-Auth": result.credentials.accessToken },
20146
+ headers: () => ({ "Oidc-Auth": accessToken }),
20145
20147
  serverUrl
20146
20148
  };
20147
20149
  }
@@ -20152,7 +20154,7 @@ async function getTrpcClient(workspaceId) {
20152
20154
  if (cached) return cached;
20153
20155
  const { headers, serverUrl } = await getAuthAndServer();
20154
20156
  const client = createTRPCClient({ links: [httpLink({
20155
- headers: withWorkspaceHeader(headers, wsId),
20157
+ headers: () => withWorkspaceHeader(headers(), wsId),
20156
20158
  transformer: SuperJSON,
20157
20159
  url: `${serverUrl}/trpc/lambda`
20158
20160
  })] });
@@ -20190,7 +20192,7 @@ async function getToolsTrpcClient(workspaceId) {
20190
20192
  if (cached) return cached;
20191
20193
  const { headers, serverUrl } = await getAuthAndServer();
20192
20194
  const client = createTRPCClient({ links: [httpLink({
20193
- headers: withWorkspaceHeader(headers, wsId),
20195
+ headers: () => withWorkspaceHeader(headers(), wsId),
20194
20196
  transformer: SuperJSON,
20195
20197
  url: `${serverUrl}/trpc/tools`
20196
20198
  })] });
@@ -21608,6 +21610,80 @@ function registerAgentSpaceFsCommand(agentCommand) {
21608
21610
  registerFsCommands(agentCommand.command("space").description("Manage agent document space").command("fs").description("Operate on the agent document VFS"));
21609
21611
  }
21610
21612
 
21613
+ //#endregion
21614
+ //#region ../../packages/utils/src/taskSlug.ts
21615
+ /**
21616
+ * Human-readable tail of a task detail URL, e.g. `/task/T-501/飞书适配器支持-post-图文消息`.
21617
+ *
21618
+ * The slug is cosmetic: `taskId` stays the only resolution key, so a stale or
21619
+ * hand-edited slug never breaks a link (see `useCanonicalTaskSlug`).
21620
+ */
21621
+ /** Long enough to read the task at a glance, short enough to stay pasteable. */
21622
+ const TASK_SLUG_MAX_LENGTH = 60;
21623
+ /**
21624
+ * Ceiling on the code points a slug may carry, independent of how many
21625
+ * graphemes that is.
21626
+ *
21627
+ * Grapheme counting alone is not a size bound: a single cluster accepts an
21628
+ * unlimited run of combining marks, so `a` plus 10k accents is one grapheme and
21629
+ * a ~20KB path segment — past what browsers and proxies accept in a request
21630
+ * line. Titles arrive from imported and generated content, so the bound has to
21631
+ * hold for input nobody typed by hand.
21632
+ */
21633
+ const TASK_SLUG_MAX_CODE_POINTS = 4 * 60;
21634
+ const SEPARATOR_RUN = /^-+|-+$/g;
21635
+ /**
21636
+ * Everything that isn't a Unicode letter, digit or combining mark collapses
21637
+ * into one `-`.
21638
+ *
21639
+ * `\p{M}` is not decoration: Devanagari matras, Thai vowel signs and Arabic
21640
+ * harakat are separate code points that NFKC does not fold into their base
21641
+ * letter, so dropping marks shreds those scripts one character at a time
21642
+ * (`कार्य पूरा करें` → `क-र-य-प-र-कर`). CJK happened to be unaffected, which is
21643
+ * why the original character class read as correct.
21644
+ */
21645
+ const NON_SLUG_RUN = /[^\p{L}\p{N}\p{M}]+/gu;
21646
+ const trimSeparators = (value) => value.replaceAll(SEPARATOR_RUN, "");
21647
+ /**
21648
+ * Grapheme segmenter, built once — constructing one per call shows up on list
21649
+ * renders that build a link per row. `undefined` locale is deliberate: grapheme
21650
+ * boundaries are locale-independent for the scripts this has to protect.
21651
+ */
21652
+ const graphemeSegmenter = typeof Intl !== "undefined" && "Segmenter" in Intl ? new Intl.Segmenter(void 0, { granularity: "grapheme" }) : void 0;
21653
+ /**
21654
+ * Split into user-perceived characters. Falls back to code points where
21655
+ * `Intl.Segmenter` is missing (Firefox < 125, older runtimes) — that still
21656
+ * keeps surrogate pairs intact, it just can't protect combining marks.
21657
+ */
21658
+ const toGraphemes = (value) => graphemeSegmenter ? [...graphemeSegmenter.segment(value)].map((entry) => entry.segment) : [...value];
21659
+ /**
21660
+ * Build the slug segment for a task title.
21661
+ *
21662
+ * The output is restricted to Unicode letters, digits, combining marks and `-`,
21663
+ * which are all legal in a path segment — so the link needs no percent-encoding
21664
+ * and a copied URL stays readable. CJK titles keep their own characters rather
21665
+ * than being transliterated: losing them would defeat the point of the slug.
21666
+ *
21667
+ * Returns `''` for an empty or punctuation-only title, which keeps the URL at
21668
+ * its bare `/task/:taskId` form instead of appending a dangling segment.
21669
+ */
21670
+ const taskTitleSlug = (title) => {
21671
+ if (!title) return "";
21672
+ const normalized = trimSeparators(title.normalize("NFKC").toLowerCase().replaceAll(NON_SLUG_RUN, "-"));
21673
+ const graphemes = toGraphemes(normalized);
21674
+ const kept = [];
21675
+ let codePoints = 0;
21676
+ for (const grapheme of graphemes) {
21677
+ if (kept.length >= 60) break;
21678
+ const size = [...grapheme].length;
21679
+ if (codePoints + size > TASK_SLUG_MAX_CODE_POINTS) break;
21680
+ kept.push(grapheme);
21681
+ codePoints += size;
21682
+ }
21683
+ if (kept.length === graphemes.length) return normalized;
21684
+ return trimSeparators(kept.join(""));
21685
+ };
21686
+
21611
21687
  //#endregion
21612
21688
  //#region src/commands/task/url.ts
21613
21689
  const buildAppUrl = ({ pathname, serverUrl, workspaceSlug }) => {
@@ -25759,6 +25835,10 @@ var init_displayName = __esmMin((() => {}));
25759
25835
  //#region ../../packages/types/src/agent/document.ts
25760
25836
  var init_document$2 = __esmMin((() => {}));
25761
25837
 
25838
+ //#endregion
25839
+ //#region ../../packages/types/src/agent/executionTargetRules.ts
25840
+ var init_executionTargetRules = __esmMin((() => {}));
25841
+
25762
25842
  //#endregion
25763
25843
  //#region ../../packages/types/src/agent/heterogeneousIntervention.ts
25764
25844
  var init_heterogeneousIntervention = __esmMin((() => {}));
@@ -25846,6 +25926,7 @@ var init_agent = __esmMin((() => {
25846
25926
  init_chatConfig();
25847
25927
  init_displayName();
25848
25928
  init_document$2();
25929
+ init_executionTargetRules();
25849
25930
  init_heteroCliArgs();
25850
25931
  init_heterogeneousAgent();
25851
25932
  init_heterogeneousIntervention();
@@ -27073,6 +27154,10 @@ var init_documentComment = __esmMin((() => {}));
27073
27154
  //#region ../../packages/types/src/documentLike.ts
27074
27155
  var init_documentLike = __esmMin((() => {}));
27075
27156
 
27157
+ //#endregion
27158
+ //#region ../../packages/types/src/environment/index.ts
27159
+ var init_environment = __esmMin((() => {}));
27160
+
27076
27161
  //#endregion
27077
27162
  //#region ../../packages/types/src/eval/agentEval.ts
27078
27163
  var init_agentEval = __esmMin((() => {}));
@@ -28044,26 +28129,12 @@ var init_aiModel = __esmMin((() => {
28044
28129
 
28045
28130
  //#endregion
28046
28131
  //#region ../../packages/types/src/topic/topic.ts
28047
- var topicExecutionConfigSchema, topicScheduledRunClaimSchema, topicScheduledRunBaseSchema, resumeAfterRateLimitRunSchema, delayedStartRunSchema, topicScheduledRunSchema, legacyRateLimitRunSchema, chatTopicMetadataUpdateSchema, chatTopicCreateMetadataSchema, TOPIC_STATUSES, chatTopicStatusSchema;
28132
+ var topicScheduledRunClaimSchema, topicScheduledRunBaseSchema, resumeAfterRateLimitRunSchema, delayedStartRunSchema, topicScheduledRunSchema, legacyRateLimitRunSchema, chatTopicMetadataUpdateSchema, chatTopicCreateMetadataSchema, TOPIC_STATUSES, chatTopicStatusSchema;
28048
28133
  var init_topic$1 = __esmMin((() => {
28049
28134
  init_aiModel();
28050
28135
  init_zod$1();
28051
28136
  init_agentHook();
28052
28137
  init_device();
28053
- topicExecutionConfigSchema = object$2({
28054
- /** Automatic snapshots retain the workspace default's execution restrictions. */
28055
- inheritWorkspaceScope: boolean().optional(),
28056
- boundDeviceId: string().optional(),
28057
- executionTarget: _enum([
28058
- "auto",
28059
- "device",
28060
- "local",
28061
- "none",
28062
- "sandbox"
28063
- ]).optional(),
28064
- localSandbox: boolean().optional(),
28065
- localSandboxNetwork: boolean().optional()
28066
- });
28067
28138
  topicScheduledRunClaimSchema = object$2({
28068
28139
  claimedAt: string(),
28069
28140
  expiresAt: string(),
@@ -28139,7 +28210,6 @@ var init_topic$1 = __esmMin((() => {
28139
28210
  userMessageId: string()
28140
28211
  });
28141
28212
  chatTopicMetadataUpdateSchema = object$2({
28142
- executionConfig: topicExecutionConfigSchema.optional(),
28143
28213
  boundDeviceId: string().optional(),
28144
28214
  heteroEffort: custom$1((value) => typeof value === "string").optional(),
28145
28215
  heteroSessionBindingKey: string().optional(),
@@ -28219,7 +28289,6 @@ var init_topic$1 = __esmMin((() => {
28219
28289
  workingDirectoryConfig: workingDirConfigSchema.optional()
28220
28290
  });
28221
28291
  chatTopicCreateMetadataSchema = chatTopicMetadataUpdateSchema.pick({
28222
- executionConfig: true,
28223
28292
  heteroEffort: true,
28224
28293
  reasoningConfig: true
28225
28294
  });
@@ -28390,6 +28459,11 @@ var init_preference = __esmMin((() => {
28390
28459
  /** Capture a conversation turn as an eval test case (developer-facing). */
28391
28460
  enableEvalCapture: boolean().optional(),
28392
28461
  /**
28462
+ * route every agent run in this tab over one shared gateway WebSocket
28463
+ * (protocol v2 mux) instead of one socket per run
28464
+ */
28465
+ enableGatewayMux: boolean().optional(),
28466
+ /**
28393
28467
  * enable the per-topic acceptance tray above the composer (author a topic's
28394
28468
  * delivery checklist inline)
28395
28469
  */
@@ -28885,6 +28959,10 @@ var init_acceptanceFlow = __esmMin((() => {
28885
28959
  });
28886
28960
  }));
28887
28961
 
28962
+ //#endregion
28963
+ //#region ../../packages/types/src/oauthApp.ts
28964
+ var init_oauthApp = __esmMin((() => {}));
28965
+
28888
28966
  //#endregion
28889
28967
  //#region ../../packages/types/src/openai/chat.ts
28890
28968
  var init_chat = __esmMin((() => {}));
@@ -28945,6 +29023,7 @@ var init_src$1 = __esmMin((() => {
28945
29023
  init_documentComment();
28946
29024
  init_documentLike();
28947
29025
  init_entityId();
29026
+ init_environment();
28948
29027
  init_eval();
28949
29028
  init_expertise();
28950
29029
  init_export();
@@ -28989,6 +29068,7 @@ var init_src$1 = __esmMin((() => {
28989
29068
  init_acceptanceComment();
28990
29069
  init_acceptanceFlow();
28991
29070
  init_agentRuntime();
29071
+ init_oauthApp();
28992
29072
  init_chat();
28993
29073
  init_plugin();
28994
29074
  init_subscription();
@@ -37453,7 +37533,7 @@ const isRecord$5 = (value) => isObjectLike(value) && !Array.isArray(value);
37453
37533
  const toRecord = (value) => isRecord$5(value) ? value : void 0;
37454
37534
 
37455
37535
  //#endregion
37456
- //#region ../../node_modules/.pnpm/partial-json@0.1.7/node_modules/partial-json/dist/options.js
37536
+ //#region node_modules/.pnpm/partial-json@0.1.7/node_modules/partial-json/dist/options.js
37457
37537
  var require_options = /* @__PURE__ */ __commonJSMin(((exports) => {
37458
37538
  /**
37459
37539
  * Sometimes you don't allow every type to be partially parsed.
@@ -37544,7 +37624,7 @@ var require_options = /* @__PURE__ */ __commonJSMin(((exports) => {
37544
37624
  }));
37545
37625
 
37546
37626
  //#endregion
37547
- //#region ../../node_modules/.pnpm/partial-json@0.1.7/node_modules/partial-json/dist/index.js
37627
+ //#region node_modules/.pnpm/partial-json@0.1.7/node_modules/partial-json/dist/index.js
37548
37628
  var require_dist$4 = /* @__PURE__ */ __commonJSMin(((exports) => {
37549
37629
  var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
37550
37630
  if (k2 === void 0) k2 = k;
@@ -38156,6 +38236,7 @@ var init_formatCommandResult = __esmMin((() => {
38156
38236
  }
38157
38237
  if (stdout) parts.push(`Stdout:\n${stdout}`);
38158
38238
  if (stderr) parts.push(`Stderr:\n${stderr}`);
38239
+ if (!(!!stdout || !!stderr || !!outputFiles?.stdout?.path || !!outputFiles?.stderr?.path) && exitCode !== void 0) parts.push("(no output)");
38159
38240
  return parts.join("\n\n");
38160
38241
  };
38161
38242
  }));
@@ -38358,6 +38439,11 @@ var init_fileSystem = __esmMin((() => {
38358
38439
  init_formatWriteResult();
38359
38440
  }));
38360
38441
 
38442
+ //#endregion
38443
+ //#region ../../packages/utils/src/artifactShare.ts
38444
+ const artifactSharePath = (deploymentId) => `/share/artifact/${deploymentId}`;
38445
+ const artifactShareUrl = (origin, deploymentId) => `${origin.replace(/\/+$/, "")}${artifactSharePath(deploymentId)}`;
38446
+
38361
38447
  //#endregion
38362
38448
  //#region src/utils/artifactManifest.ts
38363
38449
  const MANIFEST_DIR = ".lobehub";
@@ -38571,13 +38657,17 @@ function registerArtifactCommand(program) {
38571
38657
  }
38572
38658
  const result = await deployments.commitWorkspaceHtmlUpload.mutate({ uploadId: prepared.data.uploadId });
38573
38659
  if (persistsBinding && result.data.id) writeBinding(manifest, { deploymentId: result.data.id });
38660
+ const publicUrl = result.data.id ? artifactShareUrl(resolveServerUrl(), result.data.id) : result.data.publicUrl;
38661
+ const payload = {
38662
+ ...result.data,
38663
+ publicUrl
38664
+ };
38574
38665
  if (options.json !== void 0) {
38575
- const fields = typeof options.json === "string" ? options.json : void 0;
38576
- outputJson(result.data, fields);
38666
+ outputJson(payload, typeof options.json === "string" ? options.json : void 0);
38577
38667
  return;
38578
38668
  }
38579
38669
  console.log(`${import_picocolors.default.green("✓")} Published ${import_picocolors.default.bold(title)} (${entries.length} file(s))`);
38580
- if (result.data.publicUrl) console.log(` URL: ${import_picocolors.default.dim(result.data.publicUrl)}`);
38670
+ if (publicUrl) console.log(` URL: ${import_picocolors.default.dim(publicUrl)}`);
38581
38671
  console.log(` Deployment: ${import_picocolors.default.dim(result.data.id ?? "")}`);
38582
38672
  });
38583
38673
  }
@@ -232618,6 +232708,54 @@ const defaultReadExternalAssetForPublish = async ({ path: filePath, workingDirec
232618
232708
  };
232619
232709
  }
232620
232710
  };
232711
+ const resolvePublishPath = (target, workingDirectory) => {
232712
+ const expanded = expandHomePath(target);
232713
+ return path.isAbsolute(expanded) ? expanded : path.resolve(expandHomePath(workingDirectory), expanded);
232714
+ };
232715
+ const realpathForCreate = async (target) => {
232716
+ const missing = [];
232717
+ let current = path.resolve(target);
232718
+ for (;;) try {
232719
+ return path.join(await realpath(current), ...missing);
232720
+ } catch {
232721
+ const parent = path.dirname(current);
232722
+ if (parent === current) return path.join(current, ...missing);
232723
+ missing.unshift(path.basename(current));
232724
+ current = parent;
232725
+ }
232726
+ };
232727
+ const defaultCopyAssetForPublish = async ({ from, to, workingDirectory }) => {
232728
+ try {
232729
+ if (!workingDirectory) return {
232730
+ error: "Missing working directory",
232731
+ success: false
232732
+ };
232733
+ const realRoot = await safeRealpath(expandHomePath(workingDirectory));
232734
+ const target = await realpathForCreate(resolvePublishPath(to, workingDirectory));
232735
+ if (!target.startsWith(`${realRoot}${path.sep}`)) return {
232736
+ error: "Destination is outside the approved workspace",
232737
+ success: false
232738
+ };
232739
+ const realSource = await realpath(resolvePublishPath(from, workingDirectory));
232740
+ const stats = await stat(realSource);
232741
+ if (!stats.isFile()) return {
232742
+ error: "Path is not a file",
232743
+ success: false
232744
+ };
232745
+ if (stats.size > EXTERNAL_PUBLISH_ASSET_MAX_BYTES) return {
232746
+ error: "File is too large to publish",
232747
+ success: false
232748
+ };
232749
+ await mkdir(path.dirname(target), { recursive: true });
232750
+ await copyFile(realSource, target);
232751
+ return { success: true };
232752
+ } catch (error) {
232753
+ return {
232754
+ error: error.message,
232755
+ success: false
232756
+ };
232757
+ }
232758
+ };
232621
232759
 
232622
232760
  //#endregion
232623
232761
  //#region ../../packages/device-control/src/projectFileSearchManager.ts
@@ -242201,6 +242339,7 @@ const executeDeviceRpc = async (method, params, deps) => {
242201
242339
  case "searchProjectFiles": return deps.searchProjectFiles(params);
242202
242340
  case "getLocalFilePreview": return deps.getLocalFilePreview(params);
242203
242341
  case "readExternalAssetForPublish": return (deps.readExternalAssetForPublish ?? defaultReadExternalAssetForPublish)(params);
242342
+ case "copyAssetForPublish": return (deps.copyAssetForPublish ?? defaultCopyAssetForPublish)(params);
242204
242343
  case "moveLocalFiles": return moveLocalFiles(params);
242205
242344
  case "renameLocalFile": return renameLocalFile(params);
242206
242345
  case "writeLocalFile": return writeLocalFile(params);
@@ -297195,7 +297334,7 @@ var ComputerRuntime = class {
297195
297334
  if (!result.success) return this.errorOutput(result, {
297196
297335
  files: [],
297197
297336
  totalCount: 0
297198
- });
297337
+ }, true);
297199
297338
  const files = result.result?.files || [];
297200
297339
  const totalCount = result.result?.totalCount;
297201
297340
  const state = {
@@ -297217,7 +297356,7 @@ var ComputerRuntime = class {
297217
297356
  success: true
297218
297357
  };
297219
297358
  } catch (error) {
297220
- return this.handleError(error);
297359
+ return this.handleError(error, true);
297221
297360
  }
297222
297361
  }
297223
297362
  async readFile(args) {
@@ -297228,7 +297367,7 @@ var ComputerRuntime = class {
297228
297367
  endLine: args.endLine,
297229
297368
  path: args.path,
297230
297369
  startLine: args.startLine
297231
- });
297370
+ }, true);
297232
297371
  const r = result.result || {};
297233
297372
  if (r.isImage && r.imageUrl) {
297234
297373
  const filename = r.filename || args.path;
@@ -297272,7 +297411,7 @@ var ComputerRuntime = class {
297272
297411
  success: true
297273
297412
  };
297274
297413
  } catch (error) {
297275
- return this.handleError(error);
297414
+ return this.handleError(error, true);
297276
297415
  }
297277
297416
  }
297278
297417
  async writeFile(args) {
@@ -297333,7 +297472,7 @@ var ComputerRuntime = class {
297333
297472
  if (!result.success) return this.errorOutput(result, {
297334
297473
  results: [],
297335
297474
  totalCount: 0
297336
- });
297475
+ }, true);
297337
297476
  const rawResults = result.result?.results || result.result;
297338
297477
  const results = Array.isArray(rawResults) ? rawResults : [];
297339
297478
  const state = {
@@ -297346,7 +297485,7 @@ var ComputerRuntime = class {
297346
297485
  success: true
297347
297486
  };
297348
297487
  } catch (error) {
297349
- return this.handleError(error);
297488
+ return this.handleError(error, true);
297350
297489
  }
297351
297490
  }
297352
297491
  async moveFiles(args) {
@@ -297475,7 +297614,7 @@ var ComputerRuntime = class {
297475
297614
  ...sessionState,
297476
297615
  error: result.error?.message,
297477
297616
  success: false
297478
- });
297617
+ }, true);
297479
297618
  return {
297480
297619
  ...output,
297481
297620
  content: formatSandboxRecreation(output.content, result.sessionExpiredAndRecreated)
@@ -297510,7 +297649,7 @@ var ComputerRuntime = class {
297510
297649
  success: true
297511
297650
  };
297512
297651
  } catch (error) {
297513
- return this.handleError(error);
297652
+ return this.handleError(error, true);
297514
297653
  }
297515
297654
  }
297516
297655
  async killCommand(args) {
@@ -297547,7 +297686,7 @@ var ComputerRuntime = class {
297547
297686
  matches: [],
297548
297687
  pattern: args.pattern,
297549
297688
  totalMatches: 0
297550
- });
297689
+ }, true);
297551
297690
  const r = result.result || {};
297552
297691
  const matches = r.matches || [];
297553
297692
  const totalMatches = r.totalMatches ?? r.total_matches ?? 0;
@@ -297565,7 +297704,7 @@ var ComputerRuntime = class {
297565
297704
  success: true
297566
297705
  };
297567
297706
  } catch (error) {
297568
- return this.handleError(error);
297707
+ return this.handleError(error, true);
297569
297708
  }
297570
297709
  }
297571
297710
  async globFiles(args) {
@@ -297579,7 +297718,7 @@ var ComputerRuntime = class {
297579
297718
  files: [],
297580
297719
  pattern: args.pattern,
297581
297720
  totalCount: 0
297582
- });
297721
+ }, true);
297583
297722
  const files = result.result?.files || [];
297584
297723
  const totalCount = result.result?.totalCount ?? result.result?.total_files ?? files.length;
297585
297724
  const state = {
@@ -297596,21 +297735,39 @@ var ComputerRuntime = class {
297596
297735
  success: true
297597
297736
  };
297598
297737
  } catch (error) {
297599
- return this.handleError(error);
297738
+ return this.handleError(error, true);
297600
297739
  }
297601
297740
  }
297602
- handleError(error) {
297741
+ handleError(error, retrySafe = false) {
297742
+ const diagnostics = error && typeof error === "object" ? error : void 0;
297743
+ const errorMessage = error instanceof Error ? error.message : String(error);
297603
297744
  return {
297604
- content: error instanceof Error ? error.message : String(error),
297605
- error,
297745
+ content: errorMessage,
297746
+ error: {
297747
+ code: diagnostics?.code,
297748
+ doc_url: diagnostics?.doc_url,
297749
+ hint: diagnostics?.hint,
297750
+ kind: retrySafe ? void 0 : "stop",
297751
+ message: errorMessage,
297752
+ name: diagnostics?.name,
297753
+ status: diagnostics?.status
297754
+ },
297606
297755
  success: false
297607
297756
  };
297608
297757
  }
297609
- errorOutput(result, state) {
297758
+ errorOutput(result, state, retrySafe = false) {
297610
297759
  const errorText = result.error?.message || (result.error !== void 0 ? JSON.stringify(result.error) : void 0) || (typeof state?.stderr === "string" ? state.stderr : void 0) || (typeof state?.error === "string" ? state.error : void 0) || "[UNKNOWN_EXEC_ERROR] Tool execution failed";
297611
297760
  return {
297612
297761
  content: errorText,
297613
- error: { message: errorText },
297762
+ error: {
297763
+ code: result.error?.code,
297764
+ doc_url: result.error?.doc_url,
297765
+ hint: result.error?.hint,
297766
+ kind: retrySafe ? void 0 : "stop",
297767
+ message: errorText,
297768
+ name: result.error?.name,
297769
+ status: result.error?.status
297770
+ },
297614
297771
  state,
297615
297772
  success: false
297616
297773
  };
@@ -510095,14 +510252,28 @@ function printTick(result) {
510095
510252
  if (result.taskId) console.log(` ${import_picocolors.default.dim(`task: ${result.taskId}`)}`);
510096
510253
  if (result.nodeId) console.log(` ${import_picocolors.default.dim(`node: ${result.nodeId}`)}`);
510097
510254
  }
510255
+ /**
510256
+ * Whether this process runs with a server-minted operation token (device and
510257
+ * gateway runs) rather than the user's own login. Operation tokens are refused
510258
+ * on ordinary routes, so commands that act for the running conversation pick
510259
+ * the operation-authenticated endpoint instead.
510260
+ */
510261
+ const hasOperationToken = () => {
510262
+ const injectedJwt = process.env.LOBEHUB_JWT;
510263
+ if (!injectedJwt) return false;
510264
+ try {
510265
+ return JSON.parse(Buffer.from(injectedJwt.split(".")[1], "base64url").toString()).purpose === "hetero-operation";
510266
+ } catch {
510267
+ return false;
510268
+ }
510269
+ };
510098
510270
  function registerGoalCommand$1(program) {
510099
510271
  const goal = program.command("goal").description("Run long-horizon Goal Graphs");
510100
510272
  goal.command("plan <id>").description("Atomically submit the current main Agent plan").requiredOption("--file <path>", "JSON plan: action tasks/verify/retry/escalate, reason and action fields").requiredOption("--token <token>", "Current server-issued planning turn token").option("--operation <id>", "Defaults to LOBEHUB_OPERATION_ID").option("--json", "Output JSON").action(async (id, options) => {
510101
510273
  const operationId = options.operation ?? process.env.LOBEHUB_OPERATION_ID;
510102
510274
  if (!operationId) throw new Error("Current manager operation ID required");
510103
510275
  const client = await getTrpcClient();
510104
- const injectedJwt = process.env.LOBEHUB_JWT;
510105
- const result = await (injectedJwt && JSON.parse(Buffer.from(injectedJwt.split(".")[1], "base64url").toString()).purpose === "hetero-operation" ? client.goal.submitOperationPlan : client.goal.submitPlan).mutate({
510276
+ const result = await (hasOperationToken() ? client.goal.submitOperationPlan : client.goal.submitPlan).mutate({
510106
510277
  id,
510107
510278
  token: options.token,
510108
510279
  operationId,
@@ -510111,14 +510282,15 @@ function registerGoalCommand$1(program) {
510111
510282
  if (options.json) outputJson(result.data);
510112
510283
  else console.log("Plan recorded; the coordinator will continue after this Agent turn exits.");
510113
510284
  });
510114
- goal.command("create <title>").option("--max-manager-turns <n>", "Enable CLI manager planning with this turn limit").description("Create a standalone goal and seed its graph").option("-r, --requirement <text>", "Acceptance requirement").option("-i, --instruction <text>", "The ask in the user's own words, shown on the problem node").option("-t, --task <title...>", "Initial task node titles (omit to let the planner decompose)").option("--agent <id>", "Responsible agent ID").option("--project <id>", "Project ID").option("--explore <instruction>", "Explore alternatives using completed experiment results").option("--max-experiments <n>", "Maximum experiment nodes (requires --explore, default 10)").option("--supervise", "Enable bounded recovery supervision in an independent topic").option("--max-supervision-incidents <n>", "Maximum supervised interruptions (default 10, maximum 100)").option("--max-rounds <n>", "Maximum goal rounds").option("--max-cost <usd>", "Maximum total cost in USD").option("--max-attempts-per-task <n>", "Attempts per Task before opening a decision gate").option("--max-concurrent-tasks <n>", "How many of this goal's tasks may run at once (default 3)").option("--max-steps-per-run <n>", "Optional agent step cap per Task run (for example 500)").option("--operation-lease-timeout-ms <n>", "Reclaim a Task operation after this idle time (minimum: 60000)").option("--json [fields]", "Output JSON").action(async (title, options) => {
510285
+ goal.command("create <title>").option("--max-manager-turns <n>", "Add a main Agent with this turn limit; it takes over problems the system planner cannot route").description("Create a standalone goal and seed its graph").option("-r, --requirement <text>", "Acceptance requirement").option("-i, --instruction <text>", "The ask in the user's own words, shown on the problem node").option("-t, --task <title...>", "Initial task node titles (omit to let the planner decompose)").option("--agent <id>", "Goal agent: supervises and plans the goal (defaults to the current agent)").option("--task-agent <id>", "Dedicated executor for the goal's tasks (defaults to the goal agent)").option("--project <id>", "Project ID").option("--explore <instruction>", "Explore alternatives using completed experiment results").option("--max-experiments <n>", "Maximum experiment nodes (requires --explore, default 10)").option("--supervise", "Enable bounded recovery supervision in an independent topic").option("--max-supervision-incidents <n>", "Maximum supervised interruptions (default 10, maximum 100)").option("--max-rounds <n>", "Maximum goal rounds").option("--max-cost <usd>", "Maximum total cost in USD").option("--max-attempts-per-task <n>", "Attempts per Task before opening a decision gate").option("--max-concurrent-tasks <n>", "How many of this goal's tasks may run at once (default 3)").option("--max-steps-per-run <n>", "Optional agent step cap per Task run (for example 500)").option("--operation-lease-timeout-ms <n>", "Reclaim a Task operation after this idle time (minimum: 60000)").option("--conversation", "Create the goal from the current conversation run (/goal): this agent supervises it from this conversation, and the output carries a turnToken for the first plan").option("--criterion <text...>", "Acceptance criterion (repeatable)").option("--json [fields]", "Output JSON").action(async (title, options) => {
510115
510286
  if (options.maxExperiments && !options.explore) throw new Error("--max-experiments requires --explore");
510287
+ const operationId = process.env.LOBEHUB_OPERATION_ID;
510288
+ if (options.conversation && !operationId) throw new Error("--conversation must run inside an agent conversation (LOBEHUB_OPERATION_ID)");
510116
510289
  const client = await getTrpcClient();
510117
510290
  const buildUrl = await resolveAppUrlBuilder(client);
510118
- const result = await client.goal.create.mutate({
510119
- agentId: options.agent ?? process.env.LOBEHUB_AGENT_ID,
510120
- createdByAgentId: process.env.LOBEHUB_AGENT_ID,
510121
- config: options.maxManagerTurns || options.explore || options.supervise || options.maxAttemptsPerTask || options.maxStepsPerRun || options.operationLeaseTimeoutMs || options.maxConcurrentTasks ? {
510291
+ const goalInput = {
510292
+ config: options.maxManagerTurns || options.explore || options.supervise || options.maxAttemptsPerTask || options.maxStepsPerRun || options.operationLeaseTimeoutMs || options.maxConcurrentTasks || options.taskAgent ? {
510293
+ taskAgentId: options.taskAgent,
510122
510294
  manager: options.maxManagerTurns ? { maxTurns: Number(options.maxManagerTurns) } : void 0,
510123
510295
  exploration: options.explore ? {
510124
510296
  instruction: options.explore,
@@ -510135,6 +510307,7 @@ function registerGoalCommand$1(program) {
510135
510307
  operationLeaseTimeoutMs: options.operationLeaseTimeoutMs ? Number.parseInt(options.operationLeaseTimeoutMs, 10) : void 0
510136
510308
  }
510137
510309
  } : void 0,
510310
+ criteria: options.criterion?.map((criterion) => ({ title: criterion })),
510138
510311
  maxRounds: options.maxRounds ? Number.parseInt(options.maxRounds, 10) : void 0,
510139
510312
  maxTotalCost: options.maxCost ? Number.parseFloat(options.maxCost) : void 0,
510140
510313
  problemDescription: options.instruction,
@@ -510142,14 +510315,30 @@ function registerGoalCommand$1(program) {
510142
510315
  requirement: options.requirement,
510143
510316
  title,
510144
510317
  tasks: options.task
510318
+ };
510319
+ const result = options.conversation ? hasOperationToken() ? await client.goal.createConversationGoal.mutate({
510320
+ ...goalInput,
510321
+ operationId
510322
+ }) : await client.goal.create.mutate({
510323
+ ...goalInput,
510324
+ agentId: process.env.LOBEHUB_AGENT_ID,
510325
+ conversationOperationId: operationId,
510326
+ conversationTopicId: process.env.LOBEHUB_TOPIC_ID
510327
+ }) : await client.goal.create.mutate({
510328
+ ...goalInput,
510329
+ agentId: options.agent ?? process.env.LOBEHUB_AGENT_ID,
510330
+ createdByAgentId: process.env.LOBEHUB_AGENT_ID
510145
510331
  });
510332
+ const turnToken = "turnToken" in result ? result.turnToken : void 0;
510146
510333
  const url = buildUrl(`/goal/${encodeURIComponent(result.data.goal.id)}`);
510147
510334
  if (options.json !== void 0) return outputJson({
510148
510335
  ...result.data,
510336
+ turnToken,
510149
510337
  url
510150
510338
  }, options.json);
510151
510339
  printGraph(result.data);
510152
510340
  console.log(`${import_picocolors.default.bold("goal")}: ${url}`);
510341
+ if (turnToken) console.log(`${import_picocolors.default.bold("planning turn")}: lh goal plan ${result.data.goal.id} --token ${turnToken} --file <plan.json>`);
510153
510342
  });
510154
510343
  goal.command("list").description("List goals with their graph roll-up").option("--agent <id>", "Filter by responsible agent").option("--project <id>", "Filter by project").option("--status <status...>", "Filter by lifecycle status").option("--limit <n>", "Maximum rows", "50").option("--json [fields]", "Output JSON").action(async (options) => {
510155
510344
  const result = await (await getTrpcClient()).goal.list.query({
@@ -510248,7 +510437,7 @@ function registerGoalCommand$1(program) {
510248
510437
  });
510249
510438
  log$11.info(result.message);
510250
510439
  });
510251
- goal.command("set-agent <id> <agent>").description("Hand the goal to a different responsible agent (unfinished tasks follow)").option("--goal-only", "Only change the goal-level agent; leave existing tasks as assigned").action(async (id, agentId, options) => {
510440
+ goal.command("set-agent <id> <agent>").description("Hand the goal to a different supervising agent (its unfinished tasks follow unless the goal has a task agent)").option("--goal-only", "Only change the goal-level agent; leave existing tasks as assigned").action(async (id, agentId, options) => {
510252
510441
  const result = await (await getTrpcClient()).goal.setAgent.mutate({
510253
510442
  agentId,
510254
510443
  goalOnly: options.goalOnly,
@@ -510256,7 +510445,15 @@ function registerGoalCommand$1(program) {
510256
510445
  });
510257
510446
  log$11.info(result.message);
510258
510447
  });
510259
- goal.command("restart <id>").description("Start every unfinished task over (cancel stale runs, reset to backlog)").option("--agent <id>", "Also hand the goal and restarted tasks to this agent").action(async (id, options) => {
510448
+ goal.command("set-task-agent <id> <agent>").description("Route the goal's tasks to a dedicated executor; \"none\" hands them back to the goal agent").option("--goal-only", "Only change future tasks; leave existing tasks as assigned").action(async (id, agent, options) => {
510449
+ const result = await (await getTrpcClient()).goal.setTaskAgent.mutate({
510450
+ agentId: agent === "none" ? null : agent,
510451
+ goalOnly: options.goalOnly,
510452
+ id
510453
+ });
510454
+ log$11.info(result.message);
510455
+ });
510456
+ goal.command("restart <id>").description("Start every unfinished task over (cancel stale runs, reset to backlog)").option("--agent <id>", "Also hand the restarted tasks (and the goal's executor slot) to this agent").action(async (id, options) => {
510260
510457
  const result = await (await getTrpcClient()).goal.restart.mutate({
510261
510458
  agentId: options.agent,
510262
510459
  id
@@ -523402,6 +523599,85 @@ const createOperationHeartbeat = ({ intervalMs = DEFAULT_OPERATION_HEARTBEAT_INT
523402
523599
  };
523403
523600
  };
523404
523601
 
523602
+ //#endregion
523603
+ //#region src/utils/OperationTokenRenewal.ts
523604
+ /** Renew this long before expiry, so a slow or failed attempt still has room to retry. */
523605
+ const RENEW_BEFORE_EXPIRY_MS = 3600 * 1e3;
523606
+ const RETRY_DELAY_MS = 60 * 1e3;
523607
+ const MIN_DELAY_MS = 30 * 1e3;
523608
+ /**
523609
+ * The server will never renew after these: the token is gone, out of scope, the
523610
+ * run ended, or the server predates the renewal endpoint.
523611
+ */
523612
+ const TERMINAL_CODES = new Set([
523613
+ "CONFLICT",
523614
+ "FORBIDDEN",
523615
+ "NOT_FOUND",
523616
+ "UNAUTHORIZED"
523617
+ ]);
523618
+ /** Expiry of a server-minted operation token; undefined for any other credential. */
523619
+ const readOperationTokenExpiry = (jwt) => {
523620
+ try {
523621
+ const payload = JSON.parse(Buffer.from(jwt.split(".")[1] ?? "", "base64url").toString("utf8"));
523622
+ return payload?.purpose === "hetero-operation" && typeof payload.exp === "number" ? payload.exp * 1e3 : void 0;
523623
+ } catch {
523624
+ return;
523625
+ }
523626
+ };
523627
+ /**
523628
+ * Keep the operation token of a long `hetero exec` run valid.
523629
+ *
523630
+ * The server signs it for four hours, and a Goal Task can run far longer. Once
523631
+ * it expires every ingest is rejected — heartbeats included — so the lease stops
523632
+ * renewing and the server reclaims the operation as abandoned while the agent is
523633
+ * still working. Renewing writes the new token back to `LOBEHUB_JWT`, which the
523634
+ * tRPC clients read on every request.
523635
+ *
523636
+ * Only a `hetero-operation` token is renewed. A desktop run authenticates with
523637
+ * its own session token, which has its own refresh flow.
523638
+ */
523639
+ const createOperationTokenRenewal = ({ operationId, renew }) => {
523640
+ let timer;
523641
+ let stopped = false;
523642
+ const schedule = (delayMs) => {
523643
+ if (stopped) return;
523644
+ timer = setTimeout(() => void attempt(), Math.max(delayMs, MIN_DELAY_MS));
523645
+ timer.unref?.();
523646
+ };
523647
+ const scheduleFromCurrentToken = () => {
523648
+ const jwt = process.env.LOBEHUB_JWT;
523649
+ const expiresAt = jwt ? readOperationTokenExpiry(jwt) : void 0;
523650
+ if (expiresAt === void 0) return false;
523651
+ schedule(expiresAt - RENEW_BEFORE_EXPIRY_MS - Date.now());
523652
+ return true;
523653
+ };
523654
+ const attempt = async () => {
523655
+ try {
523656
+ const { jwt } = await renew(operationId);
523657
+ if (stopped) return;
523658
+ process.env.LOBEHUB_JWT = jwt;
523659
+ scheduleFromCurrentToken();
523660
+ } catch (error) {
523661
+ const code = error?.data?.code;
523662
+ const message = error instanceof Error ? error.message : String(error);
523663
+ if (code && TERMINAL_CODES.has(code)) {
523664
+ log$11.warn(`Operation token can no longer be renewed (${code}): ${message}`);
523665
+ return;
523666
+ }
523667
+ log$11.warn(`Could not renew the operation token, retrying: ${message}`);
523668
+ schedule(RETRY_DELAY_MS);
523669
+ }
523670
+ };
523671
+ return {
523672
+ /** Whether this run holds a token that gets renewed at all. */
523673
+ active: scheduleFromCurrentToken(),
523674
+ stop: () => {
523675
+ stopped = true;
523676
+ clearTimeout(timer);
523677
+ }
523678
+ };
523679
+ };
523680
+
523405
523681
  //#endregion
523406
523682
  //#region src/utils/TrpcIngestSink.ts
523407
523683
  /**
@@ -523455,6 +523731,24 @@ var TrpcIngestSink = class {
523455
523731
  //#region src/commands/hetero.ts
523456
523732
  init_resolveCliCommand();
523457
523733
  const SUPPORTED_AGENT_TYPES = new Set(LOCAL_HETEROGENEOUS_AGENT_TYPES);
523734
+ /**
523735
+ * Extra env for the spawned agent process.
523736
+ *
523737
+ * In server-ingest mode the run's operation and conversation arrive as CLI
523738
+ * arguments, not env, so the agent's own shell could not name them. Commands it
523739
+ * runs on behalf of this conversation — `lh goal create --conversation` for
523740
+ * `/goal`, evidence ingests — read `LOBEHUB_OPERATION_ID` / `LOBEHUB_TOPIC_ID`,
523741
+ * so they are echoed into the child. A standalone run has no server identity
523742
+ * and passes none.
523743
+ */
523744
+ const buildAgentProcessEnv = ({ operationId, pathEnv, topicId }) => {
523745
+ const env = {
523746
+ ...pathEnv ? { PATH: pathEnv } : {},
523747
+ ...operationId ? { LOBEHUB_OPERATION_ID: operationId } : {},
523748
+ ...topicId ? { LOBEHUB_TOPIC_ID: topicId } : {}
523749
+ };
523750
+ return Object.keys(env).length > 0 ? env : void 0;
523751
+ };
523458
523752
  const SUPPORTED_AGENT_TITLES = HETEROGENEOUS_AGENT_CONFIGS.map(({ title }) => title).join(" / ");
523459
523753
  const SUPPORTED_AGENT_COMMANDS = HETEROGENEOUS_AGENT_CONFIGS.map(({ defaultCommand }) => `\`${defaultCommand}\``).join(", ");
523460
523754
  const CODEX_REASONING_EFFORT_CONFIG_KEY = "model_reasoning_effort";
@@ -523725,6 +524019,10 @@ const exec = async (options) => {
523725
524019
  operationId,
523726
524020
  push: (event) => serverIngester.push(event)
523727
524021
  }) : void 0;
524022
+ const operationTokenRenewal = serverIngester && operationId ? createOperationTokenRenewal({
524023
+ operationId,
524024
+ renew: async (id) => (await getTrpcClient()).aiAgent.refreshHeteroOperationToken.mutate({ operationId: id })
524025
+ }) : void 0;
523728
524026
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
523729
524027
  let askServer;
523730
524028
  let askBridge;
@@ -523974,7 +524272,11 @@ const exec = async (options) => {
523974
524272
  const interceptResume = !!options.resume;
523975
524273
  const extraArgs = [...buildExtraArgs(options) ?? [], ...askMcpConfigPath ? ["--mcp-config", askMcpConfigPath] : []];
523976
524274
  const resolvedCommand = await resolveHeteroSpawnCommand(agentType, options.command);
523977
- const commandEnv = resolvedCommand.pathEnv ? { PATH: resolvedCommand.pathEnv } : void 0;
524275
+ const commandEnv = buildAgentProcessEnv({
524276
+ operationId: serverIngest ? operationId : void 0,
524277
+ pathEnv: resolvedCommand.pathEnv,
524278
+ topicId: options.topic
524279
+ });
523978
524280
  const permissionMode = options.type === "devin" ? "bypass" : void 0;
523979
524281
  const first = await runOneAgent({
523980
524282
  agentType: options.type,
@@ -524049,6 +524351,7 @@ const exec = async (options) => {
524049
524351
  finishDeliveryFailed = true;
524050
524352
  log$11.error("Failed to send heteroFinish:", err instanceof Error ? err.message : String(err));
524051
524353
  }
524354
+ operationTokenRenewal?.stop();
524052
524355
  askPollAbort.abort();
524053
524356
  if (askServer) {
524054
524357
  askServer.unregisterOperation(operationId);
@@ -526030,7 +526333,8 @@ function registerProjectCommand(program) {
526030
526333
  parentTaskId: options.parent,
526031
526334
  projectId
526032
526335
  });
526033
- const url = buildUrl(`/task/${encodeURIComponent(result.data.identifier)}`);
526336
+ const slug = taskTitleSlug(result.data.name);
526337
+ const url = buildUrl(`/task/${encodeURIComponent(result.data.identifier)}${slug ? `/${slug}` : ""}`);
526034
526338
  console.log(`${import_picocolors.default.green("✓")} Created task ${import_picocolors.default.bold(result.data.identifier)}`);
526035
526339
  console.log(`${import_picocolors.default.bold("task")}: ${url}`);
526036
526340
  });
@@ -527650,7 +527954,8 @@ function registerTaskCommand(program) {
527650
527954
  if (options.priority) input.priority = Number.parseInt(options.priority, 10);
527651
527955
  if (options.prefix) input.identifierPrefix = options.prefix;
527652
527956
  const result = await client.task.create.mutate(input);
527653
- const url = buildUrl(`/task/${encodeURIComponent(result.data.identifier)}`);
527957
+ const slug = taskTitleSlug(result.data.name);
527958
+ const url = buildUrl(`/task/${encodeURIComponent(result.data.identifier)}${slug ? `/${slug}` : ""}`);
527654
527959
  if (options.json !== void 0) {
527655
527960
  outputJson({
527656
527961
  ...result.data,
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.54" "User Commands"
3
+ .TH LH 1 "" "@lobehub/cli 0.0.55" "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.54",
3
+ "version": "0.0.55",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "lh": "./dist/index.js",
@@ -39,19 +39,19 @@
39
39
  "typescript": "^6.0.3",
40
40
  "url-join": "^5.0.0",
41
41
  "vitest": "5.0.0",
42
- "@lobechat/agent-gateway-client": "1.0.0",
43
- "@lobechat/agent-tracing": "1.0.0",
44
42
  "@lobechat/device-control": "1.0.0",
43
+ "@lobechat/agent-tracing": "1.0.0",
45
44
  "@lobechat/const": "1.0.0",
45
+ "@lobechat/agent-gateway-client": "1.0.0",
46
46
  "@lobechat/device-gateway-client": "1.0.0",
47
47
  "@lobechat/device-identity": "1.0.0",
48
48
  "@lobechat/eval-rubric": "1.0.0",
49
49
  "@lobechat/heterogeneous-agents": "1.0.0",
50
- "@lobechat/utils": "1.0.0",
51
- "@lobechat/tool-runtime": "1.0.0",
52
50
  "@lobechat/html-artifact": "1.0.0",
51
+ "@lobechat/tool-runtime": "1.0.0",
53
52
  "@lobechat/local-file-shell": "1.0.0",
54
- "@lobechat/types": "1.0.0"
53
+ "@lobechat/types": "1.0.0",
54
+ "@lobechat/utils": "1.0.0"
55
55
  },
56
56
  "engines": {
57
57
  "node": ">=22.15"