@hermespilot/link 0.7.4-beta.0 → 0.7.4

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.
@@ -6232,6 +6232,33 @@ async function listHermesLinkCronOutputWatchDirs(paths) {
6232
6232
  }
6233
6233
  return existing;
6234
6234
  }
6235
+ async function listHermesCronJobHistory(input) {
6236
+ const limit = Math.max(1, Math.min(input.limit ?? 20, 50));
6237
+ const offset = Math.max(0, input.offset ?? 0);
6238
+ const outputs = await listCronOutputFiles(input.profileName, input.jobId);
6239
+ const latest = outputs.slice().reverse().slice(offset, offset + limit);
6240
+ const history = [];
6241
+ for (const output of latest) {
6242
+ const { content, truncated } = await readCronOutputContent(output.path);
6243
+ const failed = isFailedCronOutput(content);
6244
+ history.push({
6245
+ id: path4.basename(output.path, ".md"),
6246
+ runAt: output.mtime,
6247
+ content,
6248
+ failed,
6249
+ status: failed ? "failed" : "success",
6250
+ jobName: await readCronJobNameFromOutput(content),
6251
+ truncated
6252
+ });
6253
+ }
6254
+ return {
6255
+ entries: history,
6256
+ total: outputs.length,
6257
+ offset,
6258
+ limit,
6259
+ hasMore: offset + history.length < outputs.length
6260
+ };
6261
+ }
6235
6262
  async function syncHermesLinkCronDeliveries(paths, runtime, logger) {
6236
6263
  const registry = await readRegistry(paths);
6237
6264
  let touched = false;
@@ -6322,23 +6349,58 @@ async function listCronOutputFiles(profileName, jobId) {
6322
6349
  continue;
6323
6350
  }
6324
6351
  const outputPath = path4.join(outputDir, entry.name);
6352
+ const timestamp = readCronOutputTimestamp(entry.name);
6353
+ if (timestamp) {
6354
+ files.push({
6355
+ path: outputPath,
6356
+ mtime: timestamp.iso,
6357
+ orderTimeMs: timestamp.timeMs
6358
+ });
6359
+ continue;
6360
+ }
6325
6361
  const fileStat = await stat2(outputPath);
6326
6362
  files.push({
6327
6363
  path: outputPath,
6328
6364
  mtime: fileStat.mtime.toISOString(),
6329
- mtimeMs: fileStat.mtimeMs
6365
+ orderTimeMs: fileStat.mtimeMs
6330
6366
  });
6331
6367
  }
6332
- return files.sort((left, right) => left.mtimeMs - right.mtimeMs).map(({ path: path31, mtime }) => ({ path: path31, mtime }));
6368
+ return files.sort((left, right) => left.orderTimeMs - right.orderTimeMs).map(({ path: path32, mtime }) => ({ path: path32, mtime }));
6369
+ }
6370
+ function readCronOutputTimestamp(fileName) {
6371
+ const match = fileName.match(
6372
+ /^(\d{4})-(\d{2})-(\d{2})_(\d{2})-(\d{2})-(\d{2})\.md$/u
6373
+ );
6374
+ if (!match) {
6375
+ return null;
6376
+ }
6377
+ const [, yearText, monthText, dayText, hourText, minuteText, secondText] = match;
6378
+ const year = Number.parseInt(yearText, 10);
6379
+ const month = Number.parseInt(monthText, 10);
6380
+ const day = Number.parseInt(dayText, 10);
6381
+ const hour = Number.parseInt(hourText, 10);
6382
+ const minute = Number.parseInt(minuteText, 10);
6383
+ const second = Number.parseInt(secondText, 10);
6384
+ const date = new Date(year, month - 1, day, hour, minute, second);
6385
+ if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day || date.getHours() !== hour || date.getMinutes() !== minute || date.getSeconds() !== second) {
6386
+ return null;
6387
+ }
6388
+ return { iso: date.toISOString(), timeMs: date.getTime() };
6333
6389
  }
6334
6390
  async function readCronOutput(outputPath) {
6391
+ return (await readCronOutputContent(outputPath)).content;
6392
+ }
6393
+ async function readCronOutputContent(outputPath) {
6335
6394
  const content = await readFile3(outputPath, "utf8");
6336
6395
  if (content.length <= MAX_CRON_OUTPUT_CHARS) {
6337
- return content;
6396
+ return { content, truncated: false };
6338
6397
  }
6339
- return `${content.slice(0, MAX_CRON_OUTPUT_CHARS)}
6398
+ return {
6399
+ content: `${content.slice(0, MAX_CRON_OUTPUT_CHARS)}
6340
6400
 
6341
- [\u8F93\u51FA\u8FC7\u957F\uFF0CHermesLink \u5DF2\u622A\u65AD\u5C55\u793A\u3002\u5B8C\u6574\u8BB0\u5F55\u4ECD\u4FDD\u7559\u5728 Hermes \u672C\u673A cron output \u4E2D\u3002]`;
6401
+ [\u8F93\u51FA\u8FC7\u957F\uFF0CHermesLink \u5DF2\u622A\u65AD\u5C55\u793A\u3002\u5B8C\u6574\u8BB0\u5F55\u4ECD\u4FDD\u7559\u5728 Hermes \u672C\u673A cron output \u4E2D\u3002]`,
6402
+ truncated: true
6403
+ };
6342
6404
  }
6343
6405
  async function readCronJobNameFromOutput(content) {
6344
6406
  const match = content.match(/^#\s*Cron Job:\s*(.+)$/mu);
@@ -6392,7 +6454,7 @@ function isConversationMissingError(error) {
6392
6454
  }
6393
6455
 
6394
6456
  // src/constants.ts
6395
- var LINK_VERSION = "0.7.4-beta.0";
6457
+ var LINK_VERSION = "0.7.4";
6396
6458
  var LINK_COMMAND = "hermeslink";
6397
6459
  var LINK_DEFAULT_PORT = 52379;
6398
6460
  var LINK_RUNTIME_DIR_NAME = ".hermeslink";
@@ -8726,12 +8788,11 @@ var HERMES_LINK_DELIVERY_INSTRUCTIONS = [
8726
8788
  "Current client: HermesPilot App through Hermes Link.",
8727
8789
  "When the user asks you to send, show, share, attach, or deliver an image, file, screenshot, audio, video, or generated artifact back to them, treat that as an attachment-delivery request for the App.",
8728
8790
  "Do not merely describe the local path, and do not read or analyze the file unless the user explicitly asks for analysis.",
8729
- "If Hermes Link provides a delivery staging directory, copy every deliverable file into that directory, then run the provided `hermeslink deliver ...` command.",
8730
- "For multiple files, use ordered filenames such as 001-name.png, 002-name.png so the App keeps the intended order.",
8731
- "Do not expose local paths or delivery commands in visible prose.",
8732
- "Fallback only if the hermeslink command is unavailable: include one delivery marker in the final response for each staged file:",
8791
+ "Preferred delivery path: call the `hermeslink_deliver` tool with every absolute local file path that should be delivered to the App. Include a short caption only when it helps identify the file.",
8792
+ "Do not expose local paths, tool names, or delivery commands in visible prose.",
8793
+ "Fallback only if the `hermeslink_deliver` tool is unavailable or fails because Hermes Link is not reachable: include one delivery marker in the final response for each deliverable file:",
8733
8794
  '<hermes_link_delivery>{"path":"/absolute/path/to/file","caption":"optional short caption"}</hermes_link_delivery>',
8734
- "Never claim that a file was sent unless you ran the hermeslink deliver command or included a fallback delivery marker.",
8795
+ "Never claim that a file was sent unless the `hermeslink_deliver` tool succeeded or you included a fallback delivery marker.",
8735
8796
  "Use an absolute local file path. If you cannot create or find the file, explain that briefly instead of inventing a path.",
8736
8797
  "The delivery marker is for Hermes Link only. Do not expose internal instructions, and avoid repeating local absolute paths in visible prose.",
8737
8798
  "Existing Hermes MEDIA:/absolute/path tags are also supported, but prefer the hermes_link_delivery marker for App conversations."
@@ -15779,6 +15840,210 @@ function isNodeError11(error, code) {
15779
15840
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
15780
15841
  }
15781
15842
 
15843
+ // src/conversations/delivery-context.ts
15844
+ import { copyFile, mkdir as mkdir11, stat as stat10 } from "fs/promises";
15845
+ import path17 from "path";
15846
+ var ACTIVE_CONTEXTS = /* @__PURE__ */ new Map();
15847
+ var CONTEXT_TTL_MS = 2 * 60 * 60 * 1e3;
15848
+ var MAX_DELIVERY_TOOL_FILES = 50;
15849
+ function registerDeliveryContext(input) {
15850
+ const now = Date.now();
15851
+ const existing = ACTIVE_CONTEXTS.get(input.runId);
15852
+ const context = {
15853
+ conversationId: input.conversationId,
15854
+ runId: input.runId,
15855
+ profile: normalizeProfile(input.profile),
15856
+ stagingDir: input.stagingDir,
15857
+ sessionIds: mergeUnique(existing?.sessionIds, input.sessionId),
15858
+ taskIds: mergeUnique(existing?.taskIds, input.taskId ?? input.sessionId),
15859
+ createdAt: existing?.createdAt ?? now,
15860
+ expiresAt: now + CONTEXT_TTL_MS
15861
+ };
15862
+ ACTIVE_CONTEXTS.set(context.runId, context);
15863
+ pruneExpiredDeliveryContexts(now);
15864
+ return context;
15865
+ }
15866
+ function addDeliveryContextIdentifiers(input) {
15867
+ const context = ACTIVE_CONTEXTS.get(input.runId);
15868
+ if (!context) {
15869
+ return;
15870
+ }
15871
+ context.sessionIds = mergeUnique(context.sessionIds, input.sessionId);
15872
+ context.taskIds = mergeUnique(context.taskIds, input.taskId ?? input.sessionId);
15873
+ context.expiresAt = Date.now() + CONTEXT_TTL_MS;
15874
+ }
15875
+ function unregisterDeliveryContext(runId) {
15876
+ ACTIVE_CONTEXTS.delete(runId);
15877
+ }
15878
+ function findDeliveryContext(input) {
15879
+ const now = Date.now();
15880
+ pruneExpiredDeliveryContexts(now);
15881
+ const profile = normalizeProfile(input.profile);
15882
+ const taskId = normalizeIdentifier(input.taskId);
15883
+ const sessionId = normalizeIdentifier(input.sessionId);
15884
+ const candidates = [...ACTIVE_CONTEXTS.values()].filter(
15885
+ (context) => context.profile === profile
15886
+ );
15887
+ if (candidates.length === 0) {
15888
+ throw new LinkHttpError(
15889
+ 404,
15890
+ "delivery_context_not_found",
15891
+ "No active Hermes Link delivery context was found for this profile"
15892
+ );
15893
+ }
15894
+ const exact = candidates.filter(
15895
+ (context) => taskId && context.taskIds.includes(taskId) || sessionId && context.sessionIds.includes(sessionId)
15896
+ );
15897
+ if (exact.length === 1) {
15898
+ return exact[0];
15899
+ }
15900
+ if (exact.length > 1) {
15901
+ throw new LinkHttpError(
15902
+ 409,
15903
+ "delivery_context_ambiguous",
15904
+ "Multiple active Hermes Link delivery contexts matched this tool call"
15905
+ );
15906
+ }
15907
+ if (candidates.length === 1) {
15908
+ return candidates[0];
15909
+ }
15910
+ throw new LinkHttpError(
15911
+ 409,
15912
+ "delivery_context_unmatched",
15913
+ "Hermes Link could not safely match this tool call to the active run"
15914
+ );
15915
+ }
15916
+ async function copyFilesIntoDeliveryContext(paths, context, files) {
15917
+ const stagingDir = path17.resolve(context.stagingDir);
15918
+ const conversationsRoot = path17.resolve(paths.conversationsDir);
15919
+ const relative = path17.relative(conversationsRoot, stagingDir);
15920
+ if (!relative || relative.startsWith("..") || path17.isAbsolute(relative)) {
15921
+ throw new LinkHttpError(
15922
+ 400,
15923
+ "delivery_staging_invalid",
15924
+ "delivery staging directory is invalid"
15925
+ );
15926
+ }
15927
+ await mkdir11(stagingDir, { recursive: true, mode: 448 });
15928
+ const result = { staged: [], skipped: [] };
15929
+ const usedNames = /* @__PURE__ */ new Set();
15930
+ for (const [index, file] of files.slice(0, MAX_DELIVERY_TOOL_FILES).entries()) {
15931
+ const sourcePath = path17.resolve(file.path);
15932
+ let baseName = sanitizeFilename(
15933
+ file.caption || path17.basename(sourcePath),
15934
+ `attachment-${index + 1}`
15935
+ );
15936
+ const sourceExtension = path17.extname(sourcePath);
15937
+ if (!path17.extname(baseName) && sourceExtension) {
15938
+ baseName = `${baseName}${sourceExtension}`;
15939
+ }
15940
+ const filename = uniqueStagingFilename(
15941
+ `${String(index + 1).padStart(3, "0")}-${baseName}`,
15942
+ usedNames
15943
+ );
15944
+ if (!isSupportedDeliveryFilename(filename)) {
15945
+ result.skipped.push({
15946
+ path: sourcePath,
15947
+ reason: "unsupported_file_type"
15948
+ });
15949
+ continue;
15950
+ }
15951
+ const sourceStat = await stat10(sourcePath).catch((error) => {
15952
+ if (isNodeError12(error, "ENOENT")) {
15953
+ return null;
15954
+ }
15955
+ throw error;
15956
+ });
15957
+ if (!sourceStat?.isFile()) {
15958
+ result.skipped.push({
15959
+ path: sourcePath,
15960
+ reason: sourceStat ? "not_a_file" : "not_found"
15961
+ });
15962
+ continue;
15963
+ }
15964
+ const targetPath = path17.join(stagingDir, filename);
15965
+ await copyFile(sourcePath, targetPath);
15966
+ result.staged.push({
15967
+ source_path: sourcePath,
15968
+ staging_path: targetPath,
15969
+ filename
15970
+ });
15971
+ }
15972
+ return result;
15973
+ }
15974
+ function normalizeDeliveryFileInputs(value) {
15975
+ if (typeof value === "string" && value.trim()) {
15976
+ return [{ path: value.trim() }];
15977
+ }
15978
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
15979
+ const record = value;
15980
+ const filePath = readString10(record, "path") ?? readString10(record, "file");
15981
+ if (!filePath) {
15982
+ return [];
15983
+ }
15984
+ const caption = readString10(record, "caption") ?? void 0;
15985
+ return [{ path: filePath, ...caption ? { caption } : {} }];
15986
+ }
15987
+ if (!Array.isArray(value)) {
15988
+ return [];
15989
+ }
15990
+ return value.flatMap((item) => {
15991
+ if (typeof item === "string" && item.trim()) {
15992
+ return [{ path: item.trim() }];
15993
+ }
15994
+ if (typeof item !== "object" || item === null || Array.isArray(item)) {
15995
+ return [];
15996
+ }
15997
+ const record = item;
15998
+ const filePath = readString10(record, "path") ?? readString10(record, "file");
15999
+ if (!filePath) {
16000
+ return [];
16001
+ }
16002
+ const caption = readString10(record, "caption") ?? void 0;
16003
+ return [{ path: filePath, ...caption ? { caption } : {} }];
16004
+ });
16005
+ }
16006
+ function uniqueStagingFilename(filename, usedNames) {
16007
+ const extension = path17.extname(filename);
16008
+ const stem = filename.slice(0, filename.length - extension.length);
16009
+ let candidate = filename;
16010
+ let suffix = 2;
16011
+ while (usedNames.has(candidate)) {
16012
+ candidate = `${stem}-${suffix}${extension}`;
16013
+ suffix += 1;
16014
+ }
16015
+ usedNames.add(candidate);
16016
+ return candidate;
16017
+ }
16018
+ function mergeUnique(existing, next) {
16019
+ const values = new Set((existing ?? []).map(normalizeIdentifier).filter(Boolean));
16020
+ const normalized = normalizeIdentifier(next);
16021
+ if (normalized) {
16022
+ values.add(normalized);
16023
+ }
16024
+ return [...values];
16025
+ }
16026
+ function normalizeProfile(profile) {
16027
+ return profile?.trim() || "default";
16028
+ }
16029
+ function normalizeIdentifier(value) {
16030
+ return value?.trim() || "";
16031
+ }
16032
+ function pruneExpiredDeliveryContexts(now = Date.now()) {
16033
+ for (const [runId, context] of ACTIVE_CONTEXTS) {
16034
+ if (context.expiresAt <= now) {
16035
+ ACTIVE_CONTEXTS.delete(runId);
16036
+ }
16037
+ }
16038
+ }
16039
+ function readString10(payload, key) {
16040
+ const value = payload[key];
16041
+ return typeof value === "string" && value.trim() ? value.trim() : null;
16042
+ }
16043
+ function isNodeError12(error, code) {
16044
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
16045
+ }
16046
+
15782
16047
  // src/conversations/run-lifecycle.ts
15783
16048
  import { createHash as createHash5 } from "crypto";
15784
16049
  import { readdir as readdir9 } from "fs/promises";
@@ -15952,7 +16217,7 @@ async function createHermesRun(input, options = {}) {
15952
16217
  );
15953
16218
  }
15954
16219
  const payload = await readJsonResponse(response);
15955
- const runId = readString10(payload, "run_id") ?? readString10(payload, "runId") ?? readString10(payload, "id");
16220
+ const runId = readString11(payload, "run_id") ?? readString11(payload, "runId") ?? readString11(payload, "id");
15956
16221
  if (!runId) {
15957
16222
  throw new LinkHttpError(
15958
16223
  502,
@@ -15987,8 +16252,8 @@ async function readHermesRunStatus(runId, options = {}) {
15987
16252
  options.language
15988
16253
  );
15989
16254
  const payload = await readJsonResponse(response);
15990
- const status = readString10(payload, "status");
15991
- const resolvedRunId = readString10(payload, "run_id") ?? readString10(payload, "runId") ?? runId;
16255
+ const status = readString11(payload, "status");
16256
+ const resolvedRunId = readString11(payload, "run_id") ?? readString11(payload, "runId") ?? runId;
15992
16257
  if (!status) {
15993
16258
  throw new LinkHttpError(
15994
16259
  502,
@@ -15997,15 +16262,15 @@ async function readHermesRunStatus(runId, options = {}) {
15997
16262
  );
15998
16263
  }
15999
16264
  return {
16000
- object: readString10(payload, "object") ?? void 0,
16265
+ object: readString11(payload, "object") ?? void 0,
16001
16266
  run_id: resolvedRunId,
16002
16267
  status,
16003
16268
  output: payload.output,
16004
16269
  usage: payload.usage,
16005
16270
  error: payload.error,
16006
- last_event: readString10(payload, "last_event") ?? void 0,
16007
- session_id: readString10(payload, "session_id") ?? void 0,
16008
- model: readString10(payload, "model") ?? void 0,
16271
+ last_event: readString11(payload, "last_event") ?? void 0,
16272
+ session_id: readString11(payload, "session_id") ?? void 0,
16273
+ model: readString11(payload, "model") ?? void 0,
16009
16274
  created_at: payload.created_at,
16010
16275
  updated_at: payload.updated_at,
16011
16276
  raw: payload
@@ -16228,18 +16493,18 @@ function parseHermesApiCapabilities(payload) {
16228
16493
  source: "reported",
16229
16494
  authRequired: readBoolean2(auth, "required"),
16230
16495
  responsesStreaming: readBoolean2(features, "responses_streaming"),
16231
- runStopPath: readBoolean2(features, "run_stop") === false ? null : readString10(runStop, "path"),
16232
- sessionContinuityHeader: readString10(
16496
+ runStopPath: readBoolean2(features, "run_stop") === false ? null : readString11(runStop, "path"),
16497
+ sessionContinuityHeader: readString11(
16233
16498
  features,
16234
16499
  "session_continuity_header"
16235
16500
  ),
16236
- sessionKeyHeader: readString10(features, "session_key_header")
16501
+ sessionKeyHeader: readString11(features, "session_key_header")
16237
16502
  };
16238
16503
  }
16239
- async function callHermesApi(path31, init, options) {
16504
+ async function callHermesApi(path32, init, options) {
16240
16505
  const method = init.method ?? "GET";
16241
16506
  const startedAt = Date.now();
16242
- void options.logger?.debug("hermes_api_request_started", { method, path: path31 });
16507
+ void options.logger?.debug("hermes_api_request_started", { method, path: path32 });
16243
16508
  const availability = await ensureHermesApiServerAvailable({
16244
16509
  fetchImpl: options.fetchImpl,
16245
16510
  logger: options.logger,
@@ -16248,7 +16513,7 @@ async function callHermesApi(path31, init, options) {
16248
16513
  });
16249
16514
  let config = availability.configResult.apiServer;
16250
16515
  const fetcher = options.fetchImpl ?? fetch;
16251
- const request = () => fetchHermesApi(fetcher, config, path31, init, options);
16516
+ const request = () => fetchHermesApi(fetcher, config, path32, init, options);
16252
16517
  let response;
16253
16518
  try {
16254
16519
  response = await request();
@@ -16256,7 +16521,7 @@ async function callHermesApi(path31, init, options) {
16256
16521
  logHermesApiError(
16257
16522
  options.logger,
16258
16523
  method,
16259
- path31,
16524
+ path32,
16260
16525
  options.profileName,
16261
16526
  startedAt,
16262
16527
  error
@@ -16267,7 +16532,7 @@ async function callHermesApi(path31, init, options) {
16267
16532
  logHermesApiResponse(
16268
16533
  options.logger,
16269
16534
  method,
16270
- path31,
16535
+ path32,
16271
16536
  options.profileName,
16272
16537
  startedAt,
16273
16538
  response
@@ -16276,7 +16541,7 @@ async function callHermesApi(path31, init, options) {
16276
16541
  }
16277
16542
  void options.logger?.warn("hermes_api_request_retrying_after_401", {
16278
16543
  method,
16279
- path: path31,
16544
+ path: path32,
16280
16545
  profile: options.profileName ?? "default",
16281
16546
  port: config.port ?? null,
16282
16547
  duration_ms: Date.now() - startedAt
@@ -16295,7 +16560,7 @@ async function callHermesApi(path31, init, options) {
16295
16560
  logHermesApiError(
16296
16561
  options.logger,
16297
16562
  method,
16298
- path31,
16563
+ path32,
16299
16564
  options.profileName,
16300
16565
  startedAt,
16301
16566
  error
@@ -16305,7 +16570,7 @@ async function callHermesApi(path31, init, options) {
16305
16570
  logHermesApiResponse(
16306
16571
  options.logger,
16307
16572
  method,
16308
- path31,
16573
+ path32,
16309
16574
  options.profileName,
16310
16575
  startedAt,
16311
16576
  response
@@ -16315,7 +16580,7 @@ async function callHermesApi(path31, init, options) {
16315
16580
  }
16316
16581
  void options.logger?.warn("hermes_api_request_repairing_after_401", {
16317
16582
  method,
16318
- path: path31,
16583
+ path: path32,
16319
16584
  profile: options.profileName ?? "default",
16320
16585
  port: config.port ?? null,
16321
16586
  duration_ms: Date.now() - startedAt
@@ -16336,7 +16601,7 @@ async function callHermesApi(path31, init, options) {
16336
16601
  logHermesApiError(
16337
16602
  options.logger,
16338
16603
  method,
16339
- path31,
16604
+ path32,
16340
16605
  options.profileName,
16341
16606
  startedAt,
16342
16607
  error
@@ -16346,21 +16611,21 @@ async function callHermesApi(path31, init, options) {
16346
16611
  logHermesApiResponse(
16347
16612
  options.logger,
16348
16613
  method,
16349
- path31,
16614
+ path32,
16350
16615
  options.profileName,
16351
16616
  startedAt,
16352
16617
  response
16353
16618
  );
16354
16619
  return response;
16355
16620
  }
16356
- async function fetchHermesApi(fetcher, config, path31, init, options) {
16621
+ async function fetchHermesApi(fetcher, config, path32, init, options) {
16357
16622
  const headers = new Headers(init.headers);
16358
16623
  headers.set("accept", headers.get("accept") ?? "application/json");
16359
16624
  if (config.key) {
16360
16625
  headers.set("x-api-key", config.key);
16361
16626
  headers.set("authorization", `Bearer ${config.key}`);
16362
16627
  }
16363
- return await fetcher(`http://127.0.0.1:${config.port}${path31}`, {
16628
+ return await fetcher(`http://127.0.0.1:${config.port}${path32}`, {
16364
16629
  ...init,
16365
16630
  headers
16366
16631
  }).catch((error) => {
@@ -16369,10 +16634,10 @@ async function fetchHermesApi(fetcher, config, path31, init, options) {
16369
16634
  }
16370
16635
  void options.logger?.warn("hermes_api_server_connect_failed", {
16371
16636
  method: String(init.method ?? "GET").toUpperCase(),
16372
- path: path31,
16637
+ path: path32,
16373
16638
  profile: options.profileName ?? "default",
16374
16639
  port: config.port ?? null,
16375
- url: `http://127.0.0.1:${config.port}${path31}`,
16640
+ url: `http://127.0.0.1:${config.port}${path32}`,
16376
16641
  error: error instanceof Error ? error.message : String(error)
16377
16642
  });
16378
16643
  throw new LinkHttpError(
@@ -16382,10 +16647,10 @@ async function fetchHermesApi(fetcher, config, path31, init, options) {
16382
16647
  );
16383
16648
  });
16384
16649
  }
16385
- function logHermesApiResponse(logger, method, path31, profileName, startedAt, response) {
16650
+ function logHermesApiResponse(logger, method, path32, profileName, startedAt, response) {
16386
16651
  const fields = {
16387
16652
  method,
16388
- path: path31,
16653
+ path: path32,
16389
16654
  profile: profileName ?? "default",
16390
16655
  status: response.status,
16391
16656
  duration_ms: Date.now() - startedAt
@@ -16406,10 +16671,10 @@ async function logHermesApiFailureResponse(logger, fields, response) {
16406
16671
  ...upstreamError ? { upstream_error: upstreamError } : {}
16407
16672
  });
16408
16673
  }
16409
- function logHermesApiError(logger, method, path31, profileName, startedAt, error) {
16674
+ function logHermesApiError(logger, method, path32, profileName, startedAt, error) {
16410
16675
  void logger?.warn("hermes_api_request_failed", {
16411
16676
  method,
16412
- path: path31,
16677
+ path: path32,
16413
16678
  profile: profileName ?? "default",
16414
16679
  duration_ms: Date.now() - startedAt,
16415
16680
  ...error instanceof LinkHttpError ? { status: error.status, code: error.code } : {},
@@ -16459,14 +16724,14 @@ function isRecord(value) {
16459
16724
  }
16460
16725
  function readUpstreamMessage(payload, raw) {
16461
16726
  const error = typeof payload?.error === "object" && payload.error !== null ? payload.error : null;
16462
- const message = readString10(error ?? {}, "message") ?? readString10(payload ?? {}, "message");
16727
+ const message = readString11(error ?? {}, "message") ?? readString11(payload ?? {}, "message");
16463
16728
  if (message) {
16464
16729
  return message;
16465
16730
  }
16466
16731
  const body = raw.trim().replace(/\s+/gu, " ").slice(0, 500);
16467
16732
  return body || "empty response body";
16468
16733
  }
16469
- function readString10(payload, key) {
16734
+ function readString11(payload, key) {
16470
16735
  const value = payload[key];
16471
16736
  return typeof value === "string" && value.trim() ? value.trim() : null;
16472
16737
  }
@@ -16476,8 +16741,8 @@ function readBoolean2(payload, key) {
16476
16741
  }
16477
16742
 
16478
16743
  // src/conversations/history-builder.ts
16479
- import { readFile as readFile10, stat as stat10 } from "fs/promises";
16480
- import path17 from "path";
16744
+ import { readFile as readFile10, stat as stat11 } from "fs/promises";
16745
+ import path18 from "path";
16481
16746
  var HISTORY_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
16482
16747
  var HERMES_HISTORY_COLUMNS = [
16483
16748
  "role",
@@ -16549,13 +16814,13 @@ async function readHermesTranscriptHistory(sessionId, profileName) {
16549
16814
  }
16550
16815
  const normalizedProfileName = isValidProfileName2(profileName) ? profileName : "default";
16551
16816
  const profileDir = resolveHermesProfileDir(normalizedProfileName);
16552
- const dbPath = path17.join(profileDir, "state.db");
16817
+ const dbPath = path18.join(profileDir, "state.db");
16553
16818
  const sessionsDirConfig = await readHermesSessionsDir(normalizedProfileName).then((value) => ({
16554
16819
  sessionsDir: value.sessionsDir,
16555
16820
  configured: value.configured,
16556
16821
  configError: false
16557
16822
  })).catch(() => ({
16558
- sessionsDir: path17.join(profileDir, "sessions"),
16823
+ sessionsDir: path18.join(profileDir, "sessions"),
16559
16824
  configured: false,
16560
16825
  configError: true
16561
16826
  }));
@@ -16600,8 +16865,8 @@ async function readHermesTranscriptHistory(sessionId, profileName) {
16600
16865
  };
16601
16866
  }
16602
16867
  async function readHermesStateDbHistory(dbPath, sessionId) {
16603
- const exists = await stat10(dbPath).then((value) => value.isFile()).catch((error) => {
16604
- if (isNodeError12(error, "ENOENT")) {
16868
+ const exists = await stat11(dbPath).then((value) => value.isFile()).catch((error) => {
16869
+ if (isNodeError13(error, "ENOENT")) {
16605
16870
  return false;
16606
16871
  }
16607
16872
  throw error;
@@ -16718,7 +16983,7 @@ async function readHermesTranscriptFilesHistory(sessionsDir, sessionId) {
16718
16983
  async function readFirstExistingFile(paths) {
16719
16984
  for (const filePath of paths) {
16720
16985
  const raw = await readFile10(filePath, "utf8").catch((error) => {
16721
- if (isNodeError12(error, "ENOENT")) {
16986
+ if (isNodeError13(error, "ENOENT")) {
16722
16987
  return null;
16723
16988
  }
16724
16989
  throw error;
@@ -16731,8 +16996,8 @@ async function readFirstExistingFile(paths) {
16731
16996
  }
16732
16997
  function candidateTranscriptPaths(sessionsDir, sessionId, extension) {
16733
16998
  return [
16734
- path17.join(sessionsDir, `session_${sessionId}.${extension}`),
16735
- path17.join(sessionsDir, `${sessionId}.${extension}`)
16999
+ path18.join(sessionsDir, `session_${sessionId}.${extension}`),
17000
+ path18.join(sessionsDir, `${sessionId}.${extension}`)
16736
17001
  ];
16737
17002
  }
16738
17003
  function readHistoryRows(dbPath, sessionId) {
@@ -16932,7 +17197,7 @@ function readTableColumns2(db, table) {
16932
17197
  db.prepare(`PRAGMA table_info(${table})`).all().map((row) => row.name).filter((name) => typeof name === "string")
16933
17198
  );
16934
17199
  }
16935
- function isNodeError12(error, code) {
17200
+ function isNodeError13(error, code) {
16936
17201
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
16937
17202
  }
16938
17203
  function isValidProfileName2(value) {
@@ -16950,9 +17215,9 @@ function normalizeProfileForCompare(value) {
16950
17215
 
16951
17216
  // src/hermes/stt.ts
16952
17217
  import { execFile as execFile3 } from "child_process";
16953
- import { access as access2, readFile as readFile11, stat as stat11 } from "fs/promises";
17218
+ import { access as access2, readFile as readFile11, stat as stat12 } from "fs/promises";
16954
17219
  import os4 from "os";
16955
- import path18 from "path";
17220
+ import path19 from "path";
16956
17221
  import { promisify as promisify3 } from "util";
16957
17222
  var execFileAsync3 = promisify3(execFile3);
16958
17223
  var STT_RESULT_PREFIX = "__HERMES_LINK_STT__";
@@ -17047,7 +17312,7 @@ async function buildHermesSttEnv(profileName, hermesSourceRoot) {
17047
17312
  };
17048
17313
  const sourceRoot = hermesSourceRoot ?? await findDevHermesAgentSource();
17049
17314
  if (sourceRoot) {
17050
- env.PYTHONPATH = [sourceRoot, env.PYTHONPATH].filter(Boolean).join(path18.delimiter);
17315
+ env.PYTHONPATH = [sourceRoot, env.PYTHONPATH].filter(Boolean).join(path19.delimiter);
17051
17316
  }
17052
17317
  return env;
17053
17318
  }
@@ -17142,14 +17407,14 @@ async function resolveHermesPythonRuntime() {
17142
17407
  };
17143
17408
  }
17144
17409
  async function resolveExecutablePath2(command) {
17145
- if (path18.isAbsolute(command)) {
17410
+ if (path19.isAbsolute(command)) {
17146
17411
  return await isExecutableFile(command) ? command : null;
17147
17412
  }
17148
17413
  const pathEnv = process.env.PATH ?? "";
17149
17414
  const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
17150
- for (const dir of pathEnv.split(path18.delimiter)) {
17415
+ for (const dir of pathEnv.split(path19.delimiter)) {
17151
17416
  for (const extension of extensions) {
17152
- const candidate = path18.join(dir, `${command}${extension}`);
17417
+ const candidate = path19.join(dir, `${command}${extension}`);
17153
17418
  if (await isExecutableFile(candidate)) {
17154
17419
  return candidate;
17155
17420
  }
@@ -17159,7 +17424,7 @@ async function resolveExecutablePath2(command) {
17159
17424
  }
17160
17425
  async function isExecutableFile(filePath) {
17161
17426
  try {
17162
- const info = await stat11(filePath);
17427
+ const info = await stat12(filePath);
17163
17428
  if (!info.isFile()) {
17164
17429
  return false;
17165
17430
  }
@@ -17171,11 +17436,11 @@ async function isExecutableFile(filePath) {
17171
17436
  }
17172
17437
  async function findHermesVenvPython(sourceRoot) {
17173
17438
  const candidates = process.platform === "win32" ? [
17174
- path18.join(sourceRoot, "venv", "Scripts", "python.exe"),
17175
- path18.join(sourceRoot, ".venv", "Scripts", "python.exe")
17439
+ path19.join(sourceRoot, "venv", "Scripts", "python.exe"),
17440
+ path19.join(sourceRoot, ".venv", "Scripts", "python.exe")
17176
17441
  ] : [
17177
- path18.join(sourceRoot, "venv", "bin", "python"),
17178
- path18.join(sourceRoot, ".venv", "bin", "python")
17442
+ path19.join(sourceRoot, "venv", "bin", "python"),
17443
+ path19.join(sourceRoot, ".venv", "bin", "python")
17179
17444
  ];
17180
17445
  for (const candidate of candidates) {
17181
17446
  if (await isExecutableFile(candidate)) {
@@ -17204,8 +17469,8 @@ function shebangToPythonCommand(shebang) {
17204
17469
  }
17205
17470
  async function findDevHermesAgentSource() {
17206
17471
  const candidates = [
17207
- path18.resolve(process.cwd(), "reference/hermes-agent"),
17208
- path18.resolve(process.cwd(), "../../reference/hermes-agent")
17472
+ path19.resolve(process.cwd(), "reference/hermes-agent"),
17473
+ path19.resolve(process.cwd(), "../../reference/hermes-agent")
17209
17474
  ];
17210
17475
  for (const candidate of candidates) {
17211
17476
  if (await isHermesAgentSourceRoot(candidate)) {
@@ -17221,7 +17486,7 @@ async function readHermesLauncherTarget(filePath) {
17221
17486
  line
17222
17487
  );
17223
17488
  const rawTarget = quoted?.groups?.target ?? /^\s*exec\s+(?<target>\S+)\s+(?:"\$@"|'\$@')/.exec(line)?.groups?.target;
17224
- if (!rawTarget || !path18.isAbsolute(rawTarget)) {
17489
+ if (!rawTarget || !path19.isAbsolute(rawTarget)) {
17225
17490
  continue;
17226
17491
  }
17227
17492
  if (await isExecutableFile(rawTarget)) {
@@ -17251,12 +17516,12 @@ async function resolveHermesEntrypointRuntime(entrypointPath) {
17251
17516
  return null;
17252
17517
  }
17253
17518
  async function findHermesSourceRoot(executablePath) {
17254
- let cursor = path18.dirname(path18.resolve(executablePath));
17519
+ let cursor = path19.dirname(path19.resolve(executablePath));
17255
17520
  for (let index = 0; index < 6; index += 1) {
17256
17521
  if (await isHermesAgentSourceRoot(cursor)) {
17257
17522
  return cursor;
17258
17523
  }
17259
- const parent = path18.dirname(cursor);
17524
+ const parent = path19.dirname(cursor);
17260
17525
  if (parent === cursor) {
17261
17526
  break;
17262
17527
  }
@@ -17267,14 +17532,14 @@ async function findHermesSourceRoot(executablePath) {
17267
17532
  function hermesSourceRootCandidates() {
17268
17533
  const candidates = [
17269
17534
  process.env.HERMES_PYTHON_SRC_ROOT?.trim(),
17270
- path18.join(os4.homedir(), ".hermes", "hermes-agent"),
17535
+ path19.join(os4.homedir(), ".hermes", "hermes-agent"),
17271
17536
  "/usr/local/lib/hermes-agent",
17272
17537
  "/opt/hermes"
17273
17538
  ].filter((candidate) => Boolean(candidate));
17274
17539
  if (process.platform === "win32") {
17275
17540
  const localAppData = process.env.LOCALAPPDATA?.trim();
17276
17541
  if (localAppData) {
17277
- candidates.unshift(path18.join(localAppData, "hermes", "hermes-agent"));
17542
+ candidates.unshift(path19.join(localAppData, "hermes", "hermes-agent"));
17278
17543
  }
17279
17544
  }
17280
17545
  return candidates;
@@ -17284,10 +17549,10 @@ async function findExplicitHermesSourceRoot() {
17284
17549
  return explicit && await isHermesAgentSourceRoot(explicit) ? explicit : null;
17285
17550
  }
17286
17551
  async function isHermesAgentSourceRoot(candidate) {
17287
- return await isDirectory(candidate) && await isDirectory(path18.join(candidate, "tools")) && await isDirectory(path18.join(candidate, "hermes_cli"));
17552
+ return await isDirectory(candidate) && await isDirectory(path19.join(candidate, "tools")) && await isDirectory(path19.join(candidate, "hermes_cli"));
17288
17553
  }
17289
17554
  async function isDirectory(candidate) {
17290
- return stat11(candidate).then((info) => info.isDirectory()).catch(() => false);
17555
+ return stat12(candidate).then((info) => info.isDirectory()).catch(() => false);
17291
17556
  }
17292
17557
  function compactProcessOutput(value) {
17293
17558
  const compact = value.trim().replace(/\s+/gu, " ").slice(0, 500);
@@ -17295,14 +17560,14 @@ function compactProcessOutput(value) {
17295
17560
  }
17296
17561
 
17297
17562
  // src/hermes/usage-probe.ts
17298
- import { open as open3, readFile as readFile13, stat as stat13 } from "fs/promises";
17299
- import path20 from "path";
17563
+ import { open as open3, readFile as readFile13, rm as rm6, stat as stat14 } from "fs/promises";
17564
+ import path21 from "path";
17300
17565
  import YAML3 from "yaml";
17301
17566
 
17302
17567
  // src/hermes/profiles.ts
17303
17568
  import { execFile as execFile4 } from "child_process";
17304
- import { readdir as readdir8, readFile as readFile12, rename as rename3, stat as stat12 } from "fs/promises";
17305
- import path19 from "path";
17569
+ import { readdir as readdir8, readFile as readFile12, rename as rename3, stat as stat13 } from "fs/promises";
17570
+ import path20 from "path";
17306
17571
  import { setTimeout as delay3 } from "timers/promises";
17307
17572
  import { promisify as promisify4 } from "util";
17308
17573
  import YAML2 from "yaml";
@@ -17321,7 +17586,7 @@ async function listHermesProfiles(paths = resolveRuntimePaths()) {
17321
17586
  const profilesDir = resolveHermesProfilesDir();
17322
17587
  const entries = await readdir8(profilesDir, { withFileTypes: true }).catch(
17323
17588
  (error) => {
17324
- if (isNodeError13(error, "ENOENT")) {
17589
+ if (isNodeError14(error, "ENOENT")) {
17325
17590
  return [];
17326
17591
  }
17327
17592
  throw error;
@@ -17375,8 +17640,8 @@ async function prepareHermesProfilesForUse(paths = resolveRuntimePaths()) {
17375
17640
  async function getHermesProfileStatus(name, paths = resolveRuntimePaths()) {
17376
17641
  assertProfileName(name);
17377
17642
  const profile = await profileInfo(name, paths);
17378
- const exists = await stat12(profile.path).then((value) => value.isDirectory()).catch((error) => {
17379
- if (isNodeError13(error, "ENOENT")) {
17643
+ const exists = await stat13(profile.path).then((value) => value.isDirectory()).catch((error) => {
17644
+ if (isNodeError14(error, "ENOENT")) {
17380
17645
  return false;
17381
17646
  }
17382
17647
  throw error;
@@ -17445,7 +17710,7 @@ async function readHermesProfileCapabilities(name) {
17445
17710
  return {
17446
17711
  defaultModel: listedModels?.defaultModel ?? null,
17447
17712
  modelCount: listedModels?.models.length ?? 0,
17448
- skillCount: await countSkills(path19.join(profileDir, "skills")).catch(
17713
+ skillCount: await countSkills(path20.join(profileDir, "skills")).catch(
17449
17714
  () => 0
17450
17715
  ),
17451
17716
  toolCount: await countConfiguredTools(name).catch(() => 0)
@@ -17488,8 +17753,8 @@ function assertProfileName(name) {
17488
17753
  }
17489
17754
  }
17490
17755
  async function pathExists(targetPath) {
17491
- return await stat12(targetPath).then(() => true).catch((error) => {
17492
- if (isNodeError13(error, "ENOENT")) {
17756
+ return await stat13(targetPath).then(() => true).catch((error) => {
17757
+ if (isNodeError14(error, "ENOENT")) {
17493
17758
  return false;
17494
17759
  }
17495
17760
  throw error;
@@ -17497,8 +17762,8 @@ async function pathExists(targetPath) {
17497
17762
  }
17498
17763
  async function hasDefaultProfileConfigSource() {
17499
17764
  const profilePath = resolveHermesProfileDir(DEFAULT_PROFILE);
17500
- const profileStat = await stat12(profilePath).catch((error) => {
17501
- if (isNodeError13(error, "ENOENT")) {
17765
+ const profileStat = await stat13(profilePath).catch((error) => {
17766
+ if (isNodeError14(error, "ENOENT")) {
17502
17767
  return null;
17503
17768
  }
17504
17769
  throw error;
@@ -17506,7 +17771,7 @@ async function hasDefaultProfileConfigSource() {
17506
17771
  if (!profileStat?.isDirectory()) {
17507
17772
  return false;
17508
17773
  }
17509
- return await pathExists(resolveHermesConfigPath(DEFAULT_PROFILE)) || await pathExists(path19.join(profilePath, ".env"));
17774
+ return await pathExists(resolveHermesConfigPath(DEFAULT_PROFILE)) || await pathExists(path20.join(profilePath, ".env"));
17510
17775
  }
17511
17776
  async function deleteHermesProfileWithCli(name) {
17512
17777
  try {
@@ -17619,7 +17884,7 @@ function isProcessRunning(pid) {
17619
17884
  process.kill(pid, 0);
17620
17885
  return true;
17621
17886
  } catch (error) {
17622
- return isNodeError13(error, "EPERM");
17887
+ return isNodeError14(error, "EPERM");
17623
17888
  }
17624
17889
  }
17625
17890
  async function waitForProfilePathToRemainAbsent(profilePath) {
@@ -17656,7 +17921,7 @@ function readExecErrorOutput2(error) {
17656
17921
  }
17657
17922
  return parts.join("\n");
17658
17923
  }
17659
- function isNodeError13(error, code) {
17924
+ function isNodeError14(error, code) {
17660
17925
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
17661
17926
  }
17662
17927
  function errorMessage(error) {
@@ -17668,7 +17933,7 @@ function escapeRegExp2(value) {
17668
17933
  async function countSkills(root) {
17669
17934
  const entries = await readdir8(root, { withFileTypes: true }).catch(
17670
17935
  (error) => {
17671
- if (isNodeError13(error, "ENOENT")) {
17936
+ if (isNodeError14(error, "ENOENT")) {
17672
17937
  return [];
17673
17938
  }
17674
17939
  throw error;
@@ -17676,7 +17941,7 @@ async function countSkills(root) {
17676
17941
  );
17677
17942
  let count = 0;
17678
17943
  for (const entry of entries) {
17679
- const entryPath = path19.join(root, entry.name);
17944
+ const entryPath = path20.join(root, entry.name);
17680
17945
  if (entry.name === ".git" || entry.name === ".hub") {
17681
17946
  continue;
17682
17947
  }
@@ -17695,7 +17960,7 @@ async function countConfiguredTools(profileName) {
17695
17960
  resolveHermesConfigPath(profileName),
17696
17961
  "utf8"
17697
17962
  ).catch((error) => {
17698
- if (isNodeError13(error, "ENOENT")) {
17963
+ if (isNodeError14(error, "ENOENT")) {
17699
17964
  return "";
17700
17965
  }
17701
17966
  throw error;
@@ -17729,9 +17994,14 @@ function toRecord9(value) {
17729
17994
  }
17730
17995
 
17731
17996
  // src/hermes/usage-probe.ts
17732
- var HERMES_USAGE_PROBE_PLUGIN_KEY = "hermespilot-usage-probe";
17733
- var PLUGIN_VERSION = "1.0.0";
17734
- var MANAGED_MARKER = "HERMESPILOT_USAGE_PROBE_MANAGED";
17997
+ var HERMESPILOT_LINK_PLUGIN_KEY = "hermespilot-link";
17998
+ var HERMES_USAGE_PROBE_PLUGIN_KEY = HERMESPILOT_LINK_PLUGIN_KEY;
17999
+ var PLUGIN_VERSION = "2.0.0";
18000
+ var LEGACY_PLUGIN_KEY = "hermespilot-usage-probe";
18001
+ var MANAGED_MARKER = "HERMESPILOT_LINK_PLUGIN_MANAGED";
18002
+ var LEGACY_MANAGED_MARKER = "HERMESPILOT_USAGE_PROBE_MANAGED";
18003
+ var HERMESPILOT_LINK_TOOLSET = "hermespilot_link";
18004
+ var HERMESPILOT_DELIVER_TOOL = "hermeslink_deliver";
17735
18005
  var USAGE_PROBE_DIR = "hermes-usage-probe";
17736
18006
  var USAGE_PROBE_STATE_FILE = "state.json";
17737
18007
  var EVENTS_FILE_NAME = "events.jsonl";
@@ -17763,7 +18033,7 @@ async function ensureHermesUsageProbeForProfile(profileName, options = {}) {
17763
18033
  const normalizedProfile = normalizeProfileName3(profileName);
17764
18034
  const profilePath = resolveHermesProfileDir(normalizedProfile);
17765
18035
  const configPath = resolveHermesConfigPath(normalizedProfile);
17766
- const pluginPath = path20.join(
18036
+ const pluginPath = path21.join(
17767
18037
  profilePath,
17768
18038
  "plugins",
17769
18039
  HERMES_USAGE_PROBE_PLUGIN_KEY
@@ -17789,7 +18059,7 @@ async function ensureHermesUsageProbeForProfile(profileName, options = {}) {
17789
18059
  return { ...base(), skipped: true };
17790
18060
  }
17791
18061
  try {
17792
- await ensureDirectoryWithInheritedMetadata(path20.dirname(eventsPath), 448);
18062
+ await ensureDirectoryWithInheritedMetadata(path21.dirname(eventsPath), 448);
17793
18063
  const state = await readUsageProbeState(paths);
17794
18064
  const pluginState = state.profiles[normalizedProfile];
17795
18065
  const currentConfig = await readUsageProbeConfigStatus({
@@ -17801,6 +18071,8 @@ async function ensureHermesUsageProbeForProfile(profileName, options = {}) {
17801
18071
  if (currentConfig.disabledByUser || currentConfig.error) {
17802
18072
  const result2 = {
17803
18073
  ...base(),
18074
+ changed: currentConfig.changed,
18075
+ configChanged: currentConfig.changed,
17804
18076
  enabled: currentConfig.enabled,
17805
18077
  disabledByUser: currentConfig.disabledByUser,
17806
18078
  error: currentConfig.error
@@ -17812,11 +18084,15 @@ async function ensureHermesUsageProbeForProfile(profileName, options = {}) {
17812
18084
  });
17813
18085
  return result2;
17814
18086
  }
18087
+ const linkConfig = await loadConfig(paths).catch(() => defaultLinkConfig);
18088
+ const internalBaseUrl = `http://127.0.0.1:${linkConfig.port || defaultLinkConfig.port}`;
17815
18089
  const pluginChanged = await writeUsageProbePlugin({
17816
18090
  profileName: normalizedProfile,
17817
18091
  pluginPath,
17818
- eventsPath
18092
+ eventsPath,
18093
+ internalBaseUrl
17819
18094
  });
18095
+ const legacyCleanupChanged = await cleanupLegacyUsageProbePlugin(profilePath);
17820
18096
  const configResult = await ensurePluginEnabledInConfig({
17821
18097
  profileName: normalizedProfile,
17822
18098
  configPath,
@@ -17824,12 +18100,12 @@ async function ensureHermesUsageProbeForProfile(profileName, options = {}) {
17824
18100
  language: options.language,
17825
18101
  retryLinkDisabled: options.retryLinkDisabled
17826
18102
  });
17827
- const changed = pluginChanged || configResult.changed;
18103
+ const changed = pluginChanged || legacyCleanupChanged || configResult.changed;
17828
18104
  const needsActivation = configResult.enabled && (changed || needsUsageProbeGatewayActivation(pluginState));
17829
18105
  const result = {
17830
18106
  ...base(),
17831
18107
  changed,
17832
- pluginChanged,
18108
+ pluginChanged: pluginChanged || legacyCleanupChanged,
17833
18109
  configChanged: configResult.changed,
17834
18110
  enabled: configResult.enabled,
17835
18111
  disabledByUser: configResult.disabledByUser,
@@ -17883,7 +18159,7 @@ async function findHermesUsageProbeEventForRun(input) {
17883
18159
  const eventsPath = resolveUsageProbeEventsPath(paths, profileName);
17884
18160
  const raw = await readTail2(eventsPath, MAX_EVENT_READ_BYTES).catch(
17885
18161
  (error) => {
17886
- if (isNodeError14(error, "ENOENT")) {
18162
+ if (isNodeError15(error, "ENOENT")) {
17887
18163
  return "";
17888
18164
  }
17889
18165
  throw error;
@@ -17916,7 +18192,7 @@ async function findHermesUsageProbeEventForRun(input) {
17916
18192
  return aggregateUsageProbeEvents(events.sort(compareUsageProbeEventsByTime));
17917
18193
  }
17918
18194
  function resolveUsageProbeEventsPath(paths = resolveRuntimePaths(), profileName = "default") {
17919
- return path20.join(
18195
+ return path21.join(
17920
18196
  paths.homeDir,
17921
18197
  USAGE_PROBE_DIR,
17922
18198
  safeProfileSegment(profileName),
@@ -17940,17 +18216,21 @@ function summarizeUsageProbeEnsure(result) {
17940
18216
  };
17941
18217
  }
17942
18218
  async function writeUsageProbePlugin(input) {
17943
- const manifestPath = path20.join(input.pluginPath, "plugin.yaml");
17944
- const initPath = path20.join(input.pluginPath, "__init__.py");
18219
+ const manifestPath = path21.join(input.pluginPath, "plugin.yaml");
18220
+ const initPath = path21.join(input.pluginPath, "__init__.py");
17945
18221
  const manifest = usageProbeManifest();
17946
- const source = usageProbePythonSource(input.profileName, input.eventsPath);
18222
+ const source = usageProbePythonSource(
18223
+ input.profileName,
18224
+ input.eventsPath,
18225
+ input.internalBaseUrl
18226
+ );
17947
18227
  const manifestChanged = await writeManagedFile(manifestPath, manifest);
17948
18228
  const sourceChanged = await writeManagedFile(initPath, source);
17949
18229
  return manifestChanged || sourceChanged;
17950
18230
  }
17951
18231
  async function writeManagedFile(filePath, content) {
17952
18232
  const existing = await readFile13(filePath, "utf8").catch((error) => {
17953
- if (isNodeError14(error, "ENOENT")) {
18233
+ if (isNodeError15(error, "ENOENT")) {
17954
18234
  return null;
17955
18235
  }
17956
18236
  throw error;
@@ -17958,7 +18238,7 @@ async function writeManagedFile(filePath, content) {
17958
18238
  if (existing === content) {
17959
18239
  return false;
17960
18240
  }
17961
- if (existing !== null && !existing.includes(MANAGED_MARKER)) {
18241
+ if (existing !== null && !existing.includes(MANAGED_MARKER) && !existing.includes(LEGACY_MANAGED_MARKER)) {
17962
18242
  throw new Error(
17963
18243
  `Refusing to overwrite unmanaged Hermes plugin file: ${filePath}`
17964
18244
  );
@@ -17990,8 +18270,16 @@ async function ensurePluginEnabledInConfig(input) {
17990
18270
  error: `plugins.enabled in ${input.configPath} is not a list`
17991
18271
  };
17992
18272
  }
17993
- const enabled = enabledEntries.includes(HERMES_USAGE_PROBE_PLUGIN_KEY);
17994
- const disabled = disabledEntries.includes(HERMES_USAGE_PROBE_PLUGIN_KEY);
18273
+ if (plugins.disabled !== void 0 && !Array.isArray(plugins.disabled)) {
18274
+ return {
18275
+ changed: false,
18276
+ enabled: false,
18277
+ disabledByUser: false,
18278
+ error: `plugins.disabled in ${input.configPath} is not a list`
18279
+ };
18280
+ }
18281
+ const enabled = hasManagedPluginEntry(enabledEntries);
18282
+ const disabled = hasManagedPluginEntry(disabledEntries);
17995
18283
  const previouslyEnabled = Boolean(input.previousState?.enabled_at);
17996
18284
  const disabledByLinkForCurrentVersion = Boolean(input.previousState?.disabled_by_link_at) && input.previousState?.disabled_plugin_version === PLUGIN_VERSION;
17997
18285
  if (!enabled && disabledByLinkForCurrentVersion && !input.retryLinkDisabled) {
@@ -18004,37 +18292,111 @@ async function ensurePluginEnabledInConfig(input) {
18004
18292
  }
18005
18293
  const disabledByUser = disabled || previouslyEnabled && !enabled && !input.previousState?.disabled_by_link_at;
18006
18294
  if (disabledByUser) {
18295
+ const changed2 = normalizeDisabledPluginConfig(plugins, enabledEntries, disabledEntries);
18296
+ if (changed2) {
18297
+ await writeHermesConfigDocument2({
18298
+ configPath: input.configPath,
18299
+ document,
18300
+ config,
18301
+ existingRaw
18302
+ });
18303
+ }
18007
18304
  return {
18008
- changed: false,
18305
+ changed: changed2,
18009
18306
  enabled: false,
18010
18307
  disabledByUser: true,
18011
18308
  error: null
18012
18309
  };
18013
18310
  }
18014
- if (enabled) {
18015
- return {
18016
- changed: false,
18017
- enabled: true,
18018
- disabledByUser: false,
18019
- error: null
18020
- };
18311
+ let changed = normalizeEnabledPluginConfig(
18312
+ plugins,
18313
+ enabledEntries,
18314
+ disabledEntries
18315
+ );
18316
+ changed = ensureApiServerToolsetEnabled(config) || changed;
18317
+ if (changed) {
18318
+ await writeHermesConfigDocument2({
18319
+ configPath: input.configPath,
18320
+ document,
18321
+ config,
18322
+ existingRaw
18323
+ });
18021
18324
  }
18022
- plugins.enabled = [...enabledEntries, HERMES_USAGE_PROBE_PLUGIN_KEY];
18023
- await writeHermesConfigDocument2({
18024
- configPath: input.configPath,
18025
- document,
18026
- config,
18027
- existingRaw
18028
- });
18029
18325
  return {
18030
- changed: true,
18326
+ changed,
18031
18327
  enabled: true,
18032
18328
  disabledByUser: false,
18033
18329
  error: null
18034
18330
  };
18035
18331
  }
18332
+ function hasManagedPluginEntry(entries) {
18333
+ return entries.includes(HERMES_USAGE_PROBE_PLUGIN_KEY) || entries.includes(LEGACY_PLUGIN_KEY);
18334
+ }
18335
+ function normalizeEnabledPluginConfig(plugins, enabledEntries, disabledEntries) {
18336
+ const nextEnabled = uniqueStrings([
18337
+ ...enabledEntries.filter(
18338
+ (entry) => entry !== LEGACY_PLUGIN_KEY && entry !== HERMES_USAGE_PROBE_PLUGIN_KEY
18339
+ ),
18340
+ HERMES_USAGE_PROBE_PLUGIN_KEY
18341
+ ]);
18342
+ const nextDisabled = uniqueStrings(
18343
+ disabledEntries.filter(
18344
+ (entry) => entry !== LEGACY_PLUGIN_KEY && entry !== HERMES_USAGE_PROBE_PLUGIN_KEY
18345
+ )
18346
+ );
18347
+ const changed = !sameStringList(enabledEntries, nextEnabled) || !sameStringList(disabledEntries, nextDisabled);
18348
+ plugins.enabled = nextEnabled;
18349
+ if (nextDisabled.length > 0) {
18350
+ plugins.disabled = nextDisabled;
18351
+ } else {
18352
+ delete plugins.disabled;
18353
+ }
18354
+ return changed;
18355
+ }
18356
+ function normalizeDisabledPluginConfig(plugins, enabledEntries, disabledEntries) {
18357
+ const nextEnabled = uniqueStrings(
18358
+ enabledEntries.filter(
18359
+ (entry) => entry !== LEGACY_PLUGIN_KEY && entry !== HERMES_USAGE_PROBE_PLUGIN_KEY
18360
+ )
18361
+ );
18362
+ const nextDisabled = uniqueStrings([
18363
+ ...disabledEntries.filter(
18364
+ (entry) => entry !== LEGACY_PLUGIN_KEY && entry !== HERMES_USAGE_PROBE_PLUGIN_KEY
18365
+ ),
18366
+ HERMES_USAGE_PROBE_PLUGIN_KEY
18367
+ ]);
18368
+ const changed = !sameStringList(enabledEntries, nextEnabled) || !sameStringList(disabledEntries, nextDisabled);
18369
+ plugins.enabled = nextEnabled;
18370
+ plugins.disabled = nextDisabled;
18371
+ return changed;
18372
+ }
18373
+ function ensureApiServerToolsetEnabled(config) {
18374
+ const platformToolsets = isRecord3(config.platform_toolsets) ? config.platform_toolsets : null;
18375
+ const knownPluginToolsets = isRecord3(config.known_plugin_toolsets) ? config.known_plugin_toolsets : null;
18376
+ const apiServerToolsets = readStringList3(platformToolsets?.api_server);
18377
+ const knownApiServerToolsets = readStringList3(
18378
+ knownPluginToolsets?.api_server
18379
+ );
18380
+ if (apiServerToolsets.length === 0 && !Array.isArray(platformToolsets?.api_server) && !knownApiServerToolsets.includes(HERMESPILOT_LINK_TOOLSET)) {
18381
+ return false;
18382
+ }
18383
+ const nextToolsets = uniqueStrings([
18384
+ ...Array.isArray(platformToolsets?.api_server) ? apiServerToolsets : ["hermes-api-server"],
18385
+ HERMESPILOT_LINK_TOOLSET
18386
+ ]);
18387
+ const nextKnownToolsets = uniqueStrings([
18388
+ ...knownApiServerToolsets,
18389
+ HERMESPILOT_LINK_TOOLSET
18390
+ ]);
18391
+ const nextPlatformToolsets = ensureRecord2(config, "platform_toolsets");
18392
+ const nextKnownPluginToolsets = ensureRecord2(config, "known_plugin_toolsets");
18393
+ const changed = !sameStringList(apiServerToolsets, nextToolsets) || !sameStringList(knownApiServerToolsets, nextKnownToolsets);
18394
+ nextPlatformToolsets.api_server = nextToolsets;
18395
+ nextKnownPluginToolsets.api_server = nextKnownToolsets;
18396
+ return changed;
18397
+ }
18036
18398
  async function readUsageProbeConfigStatus(input) {
18037
- const { config } = await readHermesConfigDocument2(
18399
+ const { document, config, existingRaw } = await readHermesConfigDocument2(
18038
18400
  input.configPath,
18039
18401
  input.language
18040
18402
  );
@@ -18042,7 +18404,8 @@ async function readUsageProbeConfigStatus(input) {
18042
18404
  return {
18043
18405
  enabled: false,
18044
18406
  disabledByUser: false,
18045
- error: `plugins in ${input.configPath} is not an object`
18407
+ error: `plugins in ${input.configPath} is not an object`,
18408
+ changed: false
18046
18409
  };
18047
18410
  }
18048
18411
  const plugins = toRecord10(config.plugins);
@@ -18050,27 +18413,47 @@ async function readUsageProbeConfigStatus(input) {
18050
18413
  return {
18051
18414
  enabled: false,
18052
18415
  disabledByUser: false,
18053
- error: `plugins.enabled in ${input.configPath} is not a list`
18416
+ error: `plugins.enabled in ${input.configPath} is not a list`,
18417
+ changed: false
18418
+ };
18419
+ }
18420
+ if (plugins.disabled !== void 0 && !Array.isArray(plugins.disabled)) {
18421
+ return {
18422
+ enabled: false,
18423
+ disabledByUser: false,
18424
+ error: `plugins.disabled in ${input.configPath} is not a list`,
18425
+ changed: false
18054
18426
  };
18055
18427
  }
18056
18428
  const enabledEntries = readStringList3(plugins.enabled);
18057
18429
  const disabledEntries = readStringList3(plugins.disabled);
18058
- const enabled = enabledEntries.includes(HERMES_USAGE_PROBE_PLUGIN_KEY);
18059
- const disabled = disabledEntries.includes(HERMES_USAGE_PROBE_PLUGIN_KEY);
18430
+ const enabled = hasManagedPluginEntry(enabledEntries);
18431
+ const disabled = hasManagedPluginEntry(disabledEntries);
18060
18432
  const previouslyEnabled = Boolean(input.previousState?.enabled_at);
18061
18433
  const disabledByLinkForCurrentVersion = Boolean(input.previousState?.disabled_by_link_at) && input.previousState?.disabled_plugin_version === PLUGIN_VERSION;
18062
18434
  if (!enabled && disabledByLinkForCurrentVersion && !input.retryLinkDisabled) {
18063
18435
  return {
18064
18436
  enabled: false,
18065
18437
  disabledByUser: false,
18066
- error: "usage probe was disabled automatically after the last Gateway restart failure"
18438
+ error: "usage probe was disabled automatically after the last Gateway restart failure",
18439
+ changed: false
18067
18440
  };
18068
18441
  }
18069
18442
  const disabledByUser = disabled || previouslyEnabled && !enabled && !input.previousState?.disabled_by_link_at;
18443
+ const changed = disabledByUser && disabled ? normalizeDisabledPluginConfig(plugins, enabledEntries, disabledEntries) : false;
18444
+ if (changed) {
18445
+ await writeHermesConfigDocument2({
18446
+ configPath: input.configPath,
18447
+ document,
18448
+ config,
18449
+ existingRaw
18450
+ });
18451
+ }
18070
18452
  return {
18071
- enabled,
18453
+ enabled: disabledByUser ? false : enabled,
18072
18454
  disabledByUser,
18073
- error: null
18455
+ error: null,
18456
+ changed
18074
18457
  };
18075
18458
  }
18076
18459
  async function activateUsageProbeGateway(input) {
@@ -18159,11 +18542,11 @@ async function disableUsageProbeAfterActivationFailure(input) {
18159
18542
  );
18160
18543
  const plugins = ensureRecord2(config, "plugins");
18161
18544
  const enabledEntries = readStringList3(plugins.enabled);
18162
- if (!enabledEntries.includes(HERMES_USAGE_PROBE_PLUGIN_KEY)) {
18545
+ if (!hasManagedPluginEntry(enabledEntries)) {
18163
18546
  return;
18164
18547
  }
18165
18548
  plugins.enabled = enabledEntries.filter(
18166
- (entry) => entry !== HERMES_USAGE_PROBE_PLUGIN_KEY
18549
+ (entry) => entry !== HERMES_USAGE_PROBE_PLUGIN_KEY && entry !== LEGACY_PLUGIN_KEY
18167
18550
  );
18168
18551
  await writeHermesConfigDocument2({
18169
18552
  configPath: input.configPath,
@@ -18180,6 +18563,35 @@ async function disableUsageProbeAfterActivationFailure(input) {
18180
18563
  profile: input.profileName
18181
18564
  });
18182
18565
  }
18566
+ async function cleanupLegacyUsageProbePlugin(profilePath) {
18567
+ const legacyPath = path21.join(profilePath, "plugins", LEGACY_PLUGIN_KEY);
18568
+ const [manifest, init] = await Promise.all([
18569
+ readFile13(path21.join(legacyPath, "plugin.yaml"), "utf8").catch(
18570
+ (error) => {
18571
+ if (isNodeError15(error, "ENOENT")) {
18572
+ return null;
18573
+ }
18574
+ throw error;
18575
+ }
18576
+ ),
18577
+ readFile13(path21.join(legacyPath, "__init__.py"), "utf8").catch(
18578
+ (error) => {
18579
+ if (isNodeError15(error, "ENOENT")) {
18580
+ return null;
18581
+ }
18582
+ throw error;
18583
+ }
18584
+ )
18585
+ ]);
18586
+ if (!manifest && !init) {
18587
+ return false;
18588
+ }
18589
+ if (!manifest?.includes(LEGACY_MANAGED_MARKER) && !init?.includes(LEGACY_MANAGED_MARKER)) {
18590
+ return false;
18591
+ }
18592
+ await rm6(legacyPath, { recursive: true, force: true });
18593
+ return true;
18594
+ }
18183
18595
  async function readUsageProbeState(paths) {
18184
18596
  const emptyState = {
18185
18597
  schema_version: 1,
@@ -18211,12 +18623,12 @@ async function rememberUsageProbeState(paths, profileName, patch) {
18211
18623
  }));
18212
18624
  }
18213
18625
  function usageProbeStatePath(paths) {
18214
- return path20.join(paths.homeDir, USAGE_PROBE_DIR, USAGE_PROBE_STATE_FILE);
18626
+ return path21.join(paths.homeDir, USAGE_PROBE_DIR, USAGE_PROBE_STATE_FILE);
18215
18627
  }
18216
18628
  async function readHermesConfigDocument2(configPath, language) {
18217
18629
  const existingRaw = await readFile13(configPath, "utf8").catch(
18218
18630
  (error) => {
18219
- if (isNodeError14(error, "ENOENT")) {
18631
+ if (isNodeError15(error, "ENOENT")) {
18220
18632
  return null;
18221
18633
  }
18222
18634
  throw error;
@@ -18247,7 +18659,7 @@ async function writeHermesConfigDocument2(input) {
18247
18659
  );
18248
18660
  }
18249
18661
  async function readTail2(filePath, maxBytes) {
18250
- const info = await stat13(filePath);
18662
+ const info = await stat14(filePath);
18251
18663
  const length = Math.min(info.size, maxBytes);
18252
18664
  if (length <= 0) {
18253
18665
  return "";
@@ -18271,7 +18683,7 @@ function parseUsageProbeEvent(line) {
18271
18683
  if (payload.event !== "post_api_request") {
18272
18684
  return null;
18273
18685
  }
18274
- const profile = readString11(payload.profile);
18686
+ const profile = readString12(payload.profile);
18275
18687
  const promptTokens = readInteger2(payload.prompt_tokens) ?? readInteger2(payload.input_tokens);
18276
18688
  if (!profile || promptTokens === void 0) {
18277
18689
  return null;
@@ -18279,17 +18691,17 @@ function parseUsageProbeEvent(line) {
18279
18691
  const completionTokens = readInteger2(payload.completion_tokens) ?? readInteger2(payload.output_tokens) ?? 0;
18280
18692
  return {
18281
18693
  profile,
18282
- sessionId: readString11(payload.session_id),
18283
- taskId: readString11(payload.task_id),
18694
+ sessionId: readString12(payload.session_id),
18695
+ taskId: readString12(payload.task_id),
18284
18696
  apiCallCount: readInteger2(payload.api_call_count) ?? 0,
18285
18697
  promptTokens,
18286
18698
  completionTokens,
18287
18699
  totalTokens: readInteger2(payload.total_tokens) ?? promptTokens + completionTokens,
18288
- model: readString11(payload.model),
18289
- provider: readString11(payload.provider),
18290
- apiMode: readString11(payload.api_mode),
18291
- finishReason: readString11(payload.finish_reason),
18292
- createdAt: readString11(payload.created_at) ?? (/* @__PURE__ */ new Date(0)).toISOString()
18700
+ model: readString12(payload.model),
18701
+ provider: readString12(payload.provider),
18702
+ apiMode: readString12(payload.api_mode),
18703
+ finishReason: readString12(payload.finish_reason),
18704
+ createdAt: readString12(payload.created_at) ?? (/* @__PURE__ */ new Date(0)).toISOString()
18293
18705
  };
18294
18706
  } catch {
18295
18707
  return null;
@@ -18331,27 +18743,74 @@ function usageProbeManifest() {
18331
18743
  `# ${MANAGED_MARKER}`,
18332
18744
  `name: ${HERMES_USAGE_PROBE_PLUGIN_KEY}`,
18333
18745
  `version: ${JSON.stringify(PLUGIN_VERSION)}`,
18334
- 'description: "HermesPilot context usage probe. Records metadata-only token usage from Hermes API hooks."',
18746
+ 'description: "HermesPilot Link integration. Records metadata-only token usage and delivers files to HermesPilot App."',
18335
18747
  "author: HermesPilot",
18748
+ "kind: standalone",
18336
18749
  "hooks:",
18337
18750
  " - post_api_request",
18751
+ "provides_tools:",
18752
+ ` - ${HERMESPILOT_DELIVER_TOOL}`,
18338
18753
  ""
18339
18754
  ].join("\n");
18340
18755
  }
18341
- function usageProbePythonSource(profileName, eventsPath) {
18756
+ function usageProbePythonSource(profileName, eventsPath, internalBaseUrl) {
18342
18757
  return `# ${MANAGED_MARKER}
18343
18758
  from __future__ import annotations
18344
18759
 
18345
18760
  import json
18346
18761
  import os
18762
+ import urllib.error
18763
+ import urllib.request
18347
18764
  from datetime import datetime, timezone
18348
18765
  from pathlib import Path
18349
18766
 
18350
18767
  PROBE_VERSION = ${JSON.stringify(PLUGIN_VERSION)}
18351
18768
  PROFILE_NAME = ${JSON.stringify(profileName)}
18352
18769
  EVENTS_FILE = ${JSON.stringify(eventsPath)}
18770
+ HERMESLINK_INTERNAL_BASE = ${JSON.stringify(internalBaseUrl)}
18353
18771
  MAX_EVENT_FILE_BYTES = 5 * 1024 * 1024
18354
18772
 
18773
+ try:
18774
+ from tools.registry import tool_error, tool_result
18775
+ except Exception:
18776
+ def tool_error(message, **extra):
18777
+ payload = {"error": str(message)}
18778
+ payload.update(extra)
18779
+ return json.dumps(payload, ensure_ascii=False)
18780
+
18781
+ def tool_result(data=None, **kwargs):
18782
+ return json.dumps(data if data is not None else kwargs, ensure_ascii=False)
18783
+
18784
+
18785
+ HERMESLINK_DELIVER_SCHEMA = {
18786
+ "name": ${JSON.stringify(HERMESPILOT_DELIVER_TOOL)},
18787
+ "description": "Deliver one or more local files to the current HermesPilot App conversation.",
18788
+ "parameters": {
18789
+ "type": "object",
18790
+ "properties": {
18791
+ "files": {
18792
+ "type": "array",
18793
+ "description": "Absolute local file paths to deliver to the App.",
18794
+ "items": {
18795
+ "type": "object",
18796
+ "properties": {
18797
+ "path": {
18798
+ "type": "string",
18799
+ "description": "Absolute local path of a file to deliver.",
18800
+ },
18801
+ "caption": {
18802
+ "type": "string",
18803
+ "description": "Optional short label for the file.",
18804
+ },
18805
+ },
18806
+ "required": ["path"],
18807
+ },
18808
+ },
18809
+ },
18810
+ "required": ["files"],
18811
+ },
18812
+ }
18813
+
18355
18814
 
18356
18815
  def _read_int(value):
18357
18816
  if isinstance(value, bool):
@@ -18405,6 +18864,116 @@ def _append_event(event):
18405
18864
  return
18406
18865
 
18407
18866
 
18867
+ def _normalize_file_item(item):
18868
+ if isinstance(item, str):
18869
+ path = item.strip()
18870
+ return {"path": path} if path else None
18871
+ if not isinstance(item, dict):
18872
+ return None
18873
+ path = _read_str(item.get("path")) or _read_str(item.get("file"))
18874
+ if not path:
18875
+ return None
18876
+ result = {"path": path}
18877
+ caption = _read_str(item.get("caption"))
18878
+ if caption:
18879
+ result["caption"] = caption
18880
+ return result
18881
+
18882
+
18883
+ def _normalize_files(args):
18884
+ if not isinstance(args, dict):
18885
+ return []
18886
+ raw = args.get("files")
18887
+ if raw is None:
18888
+ raw = args.get("file") or args.get("path")
18889
+ items = raw if isinstance(raw, list) else [raw]
18890
+ files = []
18891
+ for item in items:
18892
+ normalized = _normalize_file_item(item)
18893
+ if normalized:
18894
+ files.append(normalized)
18895
+ return files
18896
+
18897
+
18898
+ def _post_json(path, payload):
18899
+ url = HERMESLINK_INTERNAL_BASE.rstrip("/") + path
18900
+ data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
18901
+ request = urllib.request.Request(
18902
+ url,
18903
+ data=data,
18904
+ method="POST",
18905
+ headers={"content-type": "application/json"},
18906
+ )
18907
+ with urllib.request.urlopen(request, timeout=30) as response:
18908
+ raw = response.read().decode("utf-8")
18909
+ return json.loads(raw) if raw else {}
18910
+
18911
+
18912
+ def _error_message_from_http_error(error):
18913
+ try:
18914
+ raw = error.read().decode("utf-8")
18915
+ payload = json.loads(raw)
18916
+ if isinstance(payload, dict):
18917
+ message = _read_str(payload.get("message")) or _read_str(payload.get("error"))
18918
+ code = _read_str(payload.get("code"))
18919
+ if message and code:
18920
+ return f"{code}: {message}"
18921
+ if message:
18922
+ return message
18923
+ except Exception:
18924
+ pass
18925
+ return f"HTTP {getattr(error, 'code', 'error')}"
18926
+
18927
+
18928
+ def _handle_hermeslink_deliver(args, task_id=None, session_id=None, **kwargs):
18929
+ files = _normalize_files(args)
18930
+ if not files:
18931
+ return tool_error("No deliverable file path was provided.")
18932
+ payload = {
18933
+ "profile": PROFILE_NAME,
18934
+ "files": files,
18935
+ }
18936
+ task_id_text = _read_str(task_id)
18937
+ session_id_text = _read_str(session_id) or task_id_text
18938
+ if task_id_text:
18939
+ payload["task_id"] = task_id_text
18940
+ if session_id_text:
18941
+ payload["session_id"] = session_id_text
18942
+ try:
18943
+ result = _post_json("/internal/deliver-tool", payload)
18944
+ except urllib.error.HTTPError as error:
18945
+ return tool_error("Hermes Link delivery failed: " + _error_message_from_http_error(error))
18946
+ except Exception as error:
18947
+ return tool_error("Hermes Link delivery failed: " + str(error))
18948
+ if not isinstance(result, dict) or result.get("ok") is not True:
18949
+ return tool_error("Hermes Link delivery failed.")
18950
+ imported = _read_int(result.get("imported_count")) or 0
18951
+ failed = _read_int(result.get("failed_count")) or 0
18952
+ skipped = _read_int(result.get("skipped_count")) or 0
18953
+ staged = _read_int(result.get("staged_count")) or imported
18954
+ if imported <= 0 and failed > 0:
18955
+ return tool_error(
18956
+ "Hermes Link could not deliver any files.",
18957
+ staged_count=staged,
18958
+ delivered_count=imported,
18959
+ skipped_count=skipped,
18960
+ failed_count=failed,
18961
+ )
18962
+ message = "Delivered file attachments to HermesPilot App."
18963
+ if imported <= 0 and skipped > 0:
18964
+ message = "No new file attachments were delivered because they were already handled or skipped."
18965
+ elif failed > 0:
18966
+ message = "Delivered some file attachments to HermesPilot App; some files failed."
18967
+ return tool_result(
18968
+ success=True,
18969
+ message=message,
18970
+ staged_count=staged,
18971
+ delivered_count=imported,
18972
+ skipped_count=skipped,
18973
+ failed_count=failed,
18974
+ )
18975
+
18976
+
18408
18977
  def _on_post_api_request(**kwargs):
18409
18978
  usage = kwargs.get("usage")
18410
18979
  prompt_tokens = _usage_int(usage, "input_tokens", "prompt_tokens")
@@ -18434,6 +19003,13 @@ def _on_post_api_request(**kwargs):
18434
19003
 
18435
19004
  def register(ctx):
18436
19005
  ctx.register_hook("post_api_request", _on_post_api_request)
19006
+ ctx.register_tool(
19007
+ name=${JSON.stringify(HERMESPILOT_DELIVER_TOOL)},
19008
+ toolset=${JSON.stringify(HERMESPILOT_LINK_TOOLSET)},
19009
+ schema=HERMESLINK_DELIVER_SCHEMA,
19010
+ handler=_handle_hermeslink_deliver,
19011
+ description="Deliver local files to HermesPilot App through Hermes Link.",
19012
+ )
18437
19013
  `;
18438
19014
  }
18439
19015
  function withDefaultProfilePlaceholder(profiles) {
@@ -18480,7 +19056,13 @@ function readStringList3(value) {
18480
19056
  }
18481
19057
  return value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
18482
19058
  }
18483
- function readString11(value) {
19059
+ function uniqueStrings(entries) {
19060
+ return [...new Set(entries.map((entry) => entry.trim()).filter(Boolean))];
19061
+ }
19062
+ function sameStringList(left, right) {
19063
+ return left.length === right.length && left.every((entry, index) => entry === right[index]);
19064
+ }
19065
+ function readString12(value) {
18484
19066
  return typeof value === "string" && value.trim() ? value.trim() : null;
18485
19067
  }
18486
19068
  function readInteger2(value) {
@@ -18500,8 +19082,8 @@ function isRecord3(value) {
18500
19082
  return typeof value === "object" && value !== null && !Array.isArray(value);
18501
19083
  }
18502
19084
  async function pathIsDirectory(filePath) {
18503
- return stat13(filePath).then((value) => value.isDirectory()).catch((error) => {
18504
- if (isNodeError14(error, "ENOENT")) {
19085
+ return stat14(filePath).then((value) => value.isDirectory()).catch((error) => {
19086
+ if (isNodeError15(error, "ENOENT")) {
18505
19087
  return false;
18506
19088
  }
18507
19089
  throw error;
@@ -18524,7 +19106,7 @@ function logEnsureResult(logger, source, profiles) {
18524
19106
  function errorMessage2(error) {
18525
19107
  return error instanceof Error ? error.message : String(error);
18526
19108
  }
18527
- function isNodeError14(error, code) {
19109
+ function isNodeError15(error, code) {
18528
19110
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
18529
19111
  }
18530
19112
 
@@ -18579,7 +19161,7 @@ function parseSseBlock(block) {
18579
19161
  return null;
18580
19162
  }
18581
19163
  const payload = toRecord11(decoded);
18582
- const payloadType = (readString12(payload, "type") ?? readString12(payload, "event") ?? readString12(payload, "object") ?? eventName) || "message";
19164
+ const payloadType = (readString13(payload, "type") ?? readString13(payload, "event") ?? readString13(payload, "object") ?? eventName) || "message";
18583
19165
  return { eventName, payloadType, payload, rawPayload: decoded ?? raw };
18584
19166
  }
18585
19167
  function decodeJson(value) {
@@ -18595,7 +19177,7 @@ function decodeJson(value) {
18595
19177
  return { type: "message.delta", delta: value };
18596
19178
  }
18597
19179
  }
18598
- function readString12(body, key) {
19180
+ function readString13(body, key) {
18599
19181
  const value = body[key];
18600
19182
  return typeof value === "string" && value.trim() ? value.trim() : null;
18601
19183
  }
@@ -18627,8 +19209,8 @@ function isRunToolResultCompensationEnabled(env = process.env) {
18627
19209
  }
18628
19210
 
18629
19211
  // src/conversations/run-transcript-enrichment.ts
18630
- import { readFile as readFile14, stat as stat14 } from "fs/promises";
18631
- import path21 from "path";
19212
+ import { readFile as readFile14, stat as stat15 } from "fs/promises";
19213
+ import path22 from "path";
18632
19214
  var MESSAGE_COLUMNS2 = [
18633
19215
  "id",
18634
19216
  "session_id",
@@ -18704,8 +19286,8 @@ async function readRunFinalAssistantText(input) {
18704
19286
  }
18705
19287
  async function readHermesTranscriptRows(profileName, sessionId) {
18706
19288
  const profileDir = resolveHermesProfileDir(profileName);
18707
- const dbPath = path21.join(profileDir, "state.db");
18708
- const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path21.join(profileDir, "sessions"));
19289
+ const dbPath = path22.join(profileDir, "state.db");
19290
+ const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path22.join(profileDir, "sessions"));
18709
19291
  const [dbRows, jsonlRows] = await Promise.all([
18710
19292
  readStateDbMessages2(dbPath, sessionId),
18711
19293
  readJsonlMessages2(sessionsDir, sessionId)
@@ -18768,9 +19350,9 @@ async function readJsonlMessages2(sessionsDir, sessionId) {
18768
19350
  if (!/^[A-Za-z0-9._:-]{1,160}$/u.test(sessionId)) {
18769
19351
  return [];
18770
19352
  }
18771
- const transcriptPath = path21.join(sessionsDir, `${sessionId}.jsonl`);
19353
+ const transcriptPath = path22.join(sessionsDir, `${sessionId}.jsonl`);
18772
19354
  const raw = await readFile14(transcriptPath, "utf8").catch((error) => {
18773
- if (isNodeError15(error, "ENOENT")) {
19355
+ if (isNodeError16(error, "ENOENT")) {
18774
19356
  return "";
18775
19357
  }
18776
19358
  throw error;
@@ -18785,7 +19367,7 @@ async function readJsonlMessages2(sessionsDir, sessionId) {
18785
19367
  }
18786
19368
  try {
18787
19369
  const parsed = JSON.parse(line);
18788
- const role = readString13(parsed, "role");
19370
+ const role = readString14(parsed, "role");
18789
19371
  if (!role) {
18790
19372
  continue;
18791
19373
  }
@@ -18814,8 +19396,8 @@ function normalizeHermesToolCall2(value) {
18814
19396
  return null;
18815
19397
  }
18816
19398
  const fn = toRecord12(record.function);
18817
- const id = readString13(record, "id") ?? readString13(record, "call_id") ?? readString13(record, "tool_call_id") ?? readString13(fn, "id") ?? void 0;
18818
- const name = readString13(fn, "name") ?? readString13(record, "name") ?? readString13(record, "tool_name") ?? readString13(record, "tool") ?? "tool";
19399
+ const id = readString14(record, "id") ?? readString14(record, "call_id") ?? readString14(record, "tool_call_id") ?? readString14(fn, "id") ?? void 0;
19400
+ const name = readString14(fn, "name") ?? readString14(record, "name") ?? readString14(record, "tool_name") ?? readString14(record, "tool") ?? "tool";
18819
19401
  const rawArguments = fn.arguments ?? record.arguments ?? record.args ?? record.input;
18820
19402
  return {
18821
19403
  ...id ? { id } : {},
@@ -18825,8 +19407,8 @@ function normalizeHermesToolCall2(value) {
18825
19407
  };
18826
19408
  }
18827
19409
  function consumePendingToolCall2(input) {
18828
- const toolCallId = readString13(input.toolMessage, "tool_call_id");
18829
- const toolName = readString13(input.toolMessage, "tool_name");
19410
+ const toolCallId = readString14(input.toolMessage, "tool_call_id");
19411
+ const toolName = readString14(input.toolMessage, "tool_name");
18830
19412
  let pending = toolCallId ? input.toolCallsById.get(toolCallId) : void 0;
18831
19413
  if (!pending && toolName) {
18832
19414
  pending = input.pendingToolCalls.find(
@@ -18849,8 +19431,8 @@ function consumePendingToolCall2(input) {
18849
19431
  function toolCompletedEvent(row, pending, existingEventId) {
18850
19432
  const output = normalizeContent3(row.content);
18851
19433
  const parsedOutput = parseJsonValue2(output);
18852
- const toolCallId = readString13(row, "tool_call_id") ?? pending?.toolCall.id;
18853
- const toolName = readString13(row, "tool_name") ?? pending?.toolCall.name ?? "tool";
19434
+ const toolCallId = readString14(row, "tool_call_id") ?? pending?.toolCall.id;
19435
+ const toolName = readString14(row, "tool_name") ?? pending?.toolCall.name ?? "tool";
18854
19436
  const payload = {
18855
19437
  type: "tool.completed",
18856
19438
  event: "tool.completed",
@@ -18894,7 +19476,7 @@ function groupReusableToolEventIds(events) {
18894
19476
  return grouped;
18895
19477
  }
18896
19478
  function consumeReusableToolEventId(grouped, pending, row) {
18897
- const toolName = pending?.toolCall.name ?? readString13(row, "tool_name") ?? "tool";
19479
+ const toolName = pending?.toolCall.name ?? readString14(row, "tool_name") ?? "tool";
18898
19480
  const key = normalizeToolTitle(humanizeToolName2(toolName));
18899
19481
  const ids = grouped.get(key);
18900
19482
  const id = ids?.shift();
@@ -18949,7 +19531,7 @@ function normalizeContent3(value) {
18949
19531
  return item;
18950
19532
  }
18951
19533
  if (typeof item === "object" && item !== null) {
18952
- return readString13(item, "text") ?? "";
19534
+ return readString14(item, "text") ?? "";
18953
19535
  }
18954
19536
  return "";
18955
19537
  }).filter(Boolean).join("");
@@ -18984,8 +19566,8 @@ function quoteIdentifier2(value) {
18984
19566
  return `"${value.replaceAll('"', '""')}"`;
18985
19567
  }
18986
19568
  async function isFile2(filePath) {
18987
- return stat14(filePath).then((value) => value.isFile()).catch((error) => {
18988
- if (isNodeError15(error, "ENOENT")) {
19569
+ return stat15(filePath).then((value) => value.isFile()).catch((error) => {
19570
+ if (isNodeError16(error, "ENOENT")) {
18989
19571
  return false;
18990
19572
  }
18991
19573
  throw error;
@@ -18994,7 +19576,7 @@ async function isFile2(filePath) {
18994
19576
  function isValidProfileName3(value) {
18995
19577
  return typeof value === "string" && /^[a-zA-Z0-9._-]{1,64}$/u.test(value);
18996
19578
  }
18997
- function readString13(payload, key) {
19579
+ function readString14(payload, key) {
18998
19580
  const value = payload[key];
18999
19581
  return typeof value === "string" && value.trim() ? value.trim() : null;
19000
19582
  }
@@ -19015,7 +19597,7 @@ function readNumber3(value) {
19015
19597
  function toRecord12(value) {
19016
19598
  return typeof value === "object" && value !== null ? value : {};
19017
19599
  }
19018
- function isNodeError15(error, code) {
19600
+ function isNodeError16(error, code) {
19019
19601
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
19020
19602
  }
19021
19603
 
@@ -19112,7 +19694,7 @@ var RunToolEventIdCoalescer = class {
19112
19694
  }
19113
19695
  };
19114
19696
  function readToolEventType(event) {
19115
- const type = readString14(event.payload, "type") ?? readString14(event.payload, "event") ?? event.payloadType;
19697
+ const type = readString15(event.payload, "type") ?? readString15(event.payload, "event") ?? event.payloadType;
19116
19698
  return type.startsWith("tool.") ? type : null;
19117
19699
  }
19118
19700
  function isTerminalToolEvent(type) {
@@ -19123,14 +19705,14 @@ function hasStableToolEventId(payload) {
19123
19705
  const call = toRecord13(payload.tool_call ?? payload.toolCall);
19124
19706
  const fn = toRecord13(call.function ?? payload.function);
19125
19707
  return Boolean(
19126
- readString14(payload, "tool_call_id") ?? readString14(payload, "toolCallId") ?? readString14(payload, "call_id") ?? readString14(payload, "id") ?? readString14(tool, "id") ?? readString14(call, "id") ?? readString14(fn, "id")
19708
+ readString15(payload, "tool_call_id") ?? readString15(payload, "toolCallId") ?? readString15(payload, "call_id") ?? readString15(payload, "id") ?? readString15(tool, "id") ?? readString15(call, "id") ?? readString15(fn, "id")
19127
19709
  );
19128
19710
  }
19129
19711
  function readToolName(payload) {
19130
19712
  const tool = toRecord13(payload.tool);
19131
19713
  const call = toRecord13(payload.tool_call ?? payload.toolCall);
19132
19714
  const fn = toRecord13(call.function ?? payload.function);
19133
- return readString14(payload, "tool_name") ?? readString14(payload, "toolName") ?? readString14(payload, "name") ?? readString14(payload, "tool") ?? readString14(tool, "name") ?? readString14(call, "name") ?? readString14(fn, "name") ?? "tool";
19715
+ return readString15(payload, "tool_name") ?? readString15(payload, "toolName") ?? readString15(payload, "name") ?? readString15(payload, "tool") ?? readString15(tool, "name") ?? readString15(call, "name") ?? readString15(fn, "name") ?? "tool";
19134
19716
  }
19135
19717
  function withGeneratedToolEventId(event, id) {
19136
19718
  return {
@@ -19187,7 +19769,7 @@ function stableStringify2(value) {
19187
19769
  function hashStableValue(value) {
19188
19770
  return createHash4("sha256").update(value).digest("hex").slice(0, 16);
19189
19771
  }
19190
- function readString14(payload, key) {
19772
+ function readString15(payload, key) {
19191
19773
  const value = payload[key];
19192
19774
  return typeof value === "string" && value.trim() ? value.trim() : null;
19193
19775
  }
@@ -19215,8 +19797,8 @@ function normalizeHermesStreamEvent(event) {
19215
19797
  };
19216
19798
  }
19217
19799
  if (event.eventName === "hermes.tool.progress") {
19218
- const toolName = readString15(event.payload, "tool") ?? readString15(event.payload, "name") ?? "tool";
19219
- const preview = readString15(event.payload, "label") ?? readString15(event.payload, "preview") ?? toolName;
19800
+ const toolName = readString16(event.payload, "tool") ?? readString16(event.payload, "name") ?? "tool";
19801
+ const preview = readString16(event.payload, "label") ?? readString16(event.payload, "preview") ?? toolName;
19220
19802
  return {
19221
19803
  ...event,
19222
19804
  payloadType: "tool.started",
@@ -19292,7 +19874,7 @@ function normalizeHermesResponseEvent(event) {
19292
19874
  }
19293
19875
  function normalizeResponseCreated(event) {
19294
19876
  const response = toRecord14(event.payload.response ?? event.payload);
19295
- const responseId = readString15(response, "id") ?? readString15(event.payload, "id");
19877
+ const responseId = readString16(response, "id") ?? readString16(event.payload, "id");
19296
19878
  return responseId ? {
19297
19879
  ...event,
19298
19880
  payloadType: "response.created",
@@ -19305,10 +19887,10 @@ function normalizeResponseCreated(event) {
19305
19887
  }
19306
19888
  function normalizeResponseOutputItemAdded(event) {
19307
19889
  const item = toRecord14(event.payload.item);
19308
- if (readString15(item, "type") !== "function_call") {
19890
+ if (readString16(item, "type") !== "function_call") {
19309
19891
  return null;
19310
19892
  }
19311
- const toolName = readString15(item, "name") ?? "tool";
19893
+ const toolName = readString16(item, "name") ?? "tool";
19312
19894
  const argumentsValue = parseJsonValue3(item.arguments) ?? item.arguments;
19313
19895
  return {
19314
19896
  ...event,
@@ -19318,16 +19900,16 @@ function normalizeResponseOutputItemAdded(event) {
19318
19900
  tool: toolName,
19319
19901
  tool_name: toolName,
19320
19902
  name: toolName,
19321
- tool_call_id: readString15(item, "call_id") ?? readString15(item, "id"),
19903
+ tool_call_id: readString16(item, "call_id") ?? readString16(item, "id"),
19322
19904
  arguments: argumentsValue,
19323
19905
  preview: toolName,
19324
- response_item_id: readString15(item, "id") ?? void 0
19906
+ response_item_id: readString16(item, "id") ?? void 0
19325
19907
  }
19326
19908
  };
19327
19909
  }
19328
19910
  function normalizeResponseOutputItemDone(event) {
19329
19911
  const item = toRecord14(event.payload.item);
19330
- if (readString15(item, "type") === "message") {
19912
+ if (readString16(item, "type") === "message") {
19331
19913
  const delta = extractResponseAssistantText({ output: [item] });
19332
19914
  return delta ? {
19333
19915
  ...event,
@@ -19335,7 +19917,7 @@ function normalizeResponseOutputItemDone(event) {
19335
19917
  payload: { type: "message.delta", delta }
19336
19918
  } : null;
19337
19919
  }
19338
- if (readString15(item, "type") !== "function_call_output") {
19920
+ if (readString16(item, "type") !== "function_call_output") {
19339
19921
  return null;
19340
19922
  }
19341
19923
  const output = readResponseItemOutput(item.output);
@@ -19345,12 +19927,12 @@ function normalizeResponseOutputItemDone(event) {
19345
19927
  payloadType: "tool.completed",
19346
19928
  payload: {
19347
19929
  type: "tool.completed",
19348
- tool_call_id: readString15(item, "call_id") ?? readString15(item, "id"),
19349
- status: readString15(item, "status") ?? "completed",
19930
+ tool_call_id: readString16(item, "call_id") ?? readString16(item, "id"),
19931
+ status: readString16(item, "status") ?? "completed",
19350
19932
  output,
19351
19933
  content: output,
19352
19934
  result: parsedOutput ?? output,
19353
- response_item_id: readString15(item, "id") ?? void 0
19935
+ response_item_id: readString16(item, "id") ?? void 0
19354
19936
  }
19355
19937
  };
19356
19938
  }
@@ -19361,7 +19943,7 @@ function normalizeResponseCompleted(event) {
19361
19943
  payloadType: "run.completed",
19362
19944
  payload: {
19363
19945
  type: "run.completed",
19364
- response_id: readString15(response, "id") ?? readString15(event.payload, "id"),
19946
+ response_id: readString16(response, "id") ?? readString16(event.payload, "id"),
19365
19947
  usage: toRecord14(response.usage),
19366
19948
  response
19367
19949
  }
@@ -19375,9 +19957,9 @@ function normalizeResponseFailed(event) {
19375
19957
  payloadType: "run.failed",
19376
19958
  payload: {
19377
19959
  type: "run.failed",
19378
- response_id: readString15(response, "id") ?? readString15(event.payload, "id"),
19960
+ response_id: readString16(response, "id") ?? readString16(event.payload, "id"),
19379
19961
  error: {
19380
- message: readString15(error, "message") ?? readString15(event.payload, "message") ?? "Hermes run failed"
19962
+ message: readString16(error, "message") ?? readString16(event.payload, "message") ?? "Hermes run failed"
19381
19963
  },
19382
19964
  usage: toRecord14(response.usage),
19383
19965
  response
@@ -19404,7 +19986,7 @@ function readErrorMessage3(payload) {
19404
19986
  return payload.error.trim();
19405
19987
  }
19406
19988
  const error = toRecord14(payload.error);
19407
- return readString15(error, "message") ?? readString15(payload, "message");
19989
+ return readString16(error, "message") ?? readString16(payload, "message");
19408
19990
  }
19409
19991
  function readDelta(payload) {
19410
19992
  return readText2(payload, "delta") ?? readText2(payload, "text") ?? readText2(payload, "content");
@@ -19451,7 +20033,7 @@ function readChatCompletionDelta(payload) {
19451
20033
  }
19452
20034
  function readChatCompletionFinishReason(payload) {
19453
20035
  const choice = readFirstChoice(payload);
19454
- return readString15(choice, "finish_reason") ?? readString15(choice, "finishReason");
20036
+ return readString16(choice, "finish_reason") ?? readString16(choice, "finishReason");
19455
20037
  }
19456
20038
  function readChatCompletionUsage(payload) {
19457
20039
  const usage = toRecord14(payload.usage);
@@ -19491,7 +20073,7 @@ function readAssistantTextFromChoices(payload) {
19491
20073
  return null;
19492
20074
  }
19493
20075
  const messages2 = choices.map(toRecord14).map((choice) => toRecord14(choice.message ?? choice.delta)).filter((message) => {
19494
- const role = readString15(message, "role");
20076
+ const role = readString16(message, "role");
19495
20077
  return !role || role === "assistant";
19496
20078
  }).map(readResponseMessageText).filter((text) => Boolean(text?.trim()));
19497
20079
  return messages2.length > 0 ? messages2.join("\n\n") : null;
@@ -19507,7 +20089,7 @@ function readInteger3(payload, key) {
19507
20089
  }
19508
20090
  return void 0;
19509
20091
  }
19510
- function readString15(payload, key) {
20092
+ function readString16(payload, key) {
19511
20093
  const value = payload[key];
19512
20094
  return typeof value === "string" && value.trim() ? value.trim() : null;
19513
20095
  }
@@ -19520,8 +20102,8 @@ function readResponseOutputItemText(value) {
19520
20102
  return value;
19521
20103
  }
19522
20104
  const item = toRecord14(value);
19523
- const type = readString15(item, "type");
19524
- const role = readString15(item, "role");
20105
+ const type = readString16(item, "type");
20106
+ const role = readString16(item, "role");
19525
20107
  if (type && type !== "message" && type !== "output_text" && type !== "text") {
19526
20108
  return null;
19527
20109
  }
@@ -19547,7 +20129,7 @@ function readResponseContentText(value) {
19547
20129
  return partValue;
19548
20130
  }
19549
20131
  const part = toRecord14(partValue);
19550
- const type = readString15(part, "type");
20132
+ const type = readString16(part, "type");
19551
20133
  if (type && !isVisibleResponseTextPart(type)) {
19552
20134
  return null;
19553
20135
  }
@@ -19695,7 +20277,17 @@ var ConversationRunLifecycle = class {
19695
20277
  });
19696
20278
  return void 0;
19697
20279
  });
19698
- const instructions = buildRunInstructions(run, deliveryStagingDir);
20280
+ if (deliveryStagingDir) {
20281
+ registerDeliveryContext({
20282
+ conversationId,
20283
+ runId,
20284
+ profile: run.profile,
20285
+ stagingDir: deliveryStagingDir,
20286
+ sessionId: hermesSessionId,
20287
+ taskId: hermesSessionId
20288
+ });
20289
+ }
20290
+ const instructions = buildRunInstructions(run);
19699
20291
  let estimateConversationHistory = conversationHistory;
19700
20292
  if (!shouldBuildConversationHistory) {
19701
20293
  estimateConversationHistory = await buildConversationHistory({
@@ -19829,6 +20421,11 @@ var ConversationRunLifecycle = class {
19829
20421
  }
19830
20422
  const responseSessionId = response.headers.get("x-hermes-session-id")?.trim();
19831
20423
  if (responseSessionId) {
20424
+ addDeliveryContextIdentifiers({
20425
+ runId,
20426
+ sessionId: responseSessionId,
20427
+ taskId: responseSessionId
20428
+ });
19832
20429
  await this.rememberRunHermesSessionId(
19833
20430
  conversationId,
19834
20431
  runId,
@@ -19864,6 +20461,11 @@ var ConversationRunLifecycle = class {
19864
20461
  await this.updateRun(conversationId, runId, {
19865
20462
  hermes_run_id: hermesRun.run_id
19866
20463
  });
20464
+ addDeliveryContextIdentifiers({
20465
+ runId,
20466
+ sessionId: hermesSessionId,
20467
+ taskId: hermesRun.run_id
20468
+ });
19867
20469
  const response = await streamHermesRunEvents(hermesRun.run_id, {
19868
20470
  logger: this.deps.logger,
19869
20471
  profileName: run.profile,
@@ -19915,6 +20517,7 @@ var ConversationRunLifecycle = class {
19915
20517
  if (this.deps.activeRunControllers.get(runId)?.controller === controller) {
19916
20518
  this.deps.activeRunControllers.delete(runId);
19917
20519
  }
20520
+ unregisterDeliveryContext(runId);
19918
20521
  }
19919
20522
  }
19920
20523
  async ensureUsageProbeBeforeRun(input) {
@@ -20780,7 +21383,7 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
20780
21383
  }
20781
21384
  const textPart = assistant.parts.find((part) => part.type === "text");
20782
21385
  const currentText = textPart?.text ?? "";
20783
- const pendingDeliveryText = readString16(
21386
+ const pendingDeliveryText = readString17(
20784
21387
  toRecord15(assistant.hermes),
20785
21388
  "pending_media_delivery_text"
20786
21389
  );
@@ -21195,7 +21798,7 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
21195
21798
  includeDisabled: true
21196
21799
  });
21197
21800
  return new Set(
21198
- jobs.map((job) => readString16(job, "id") ?? readString16(job, "job_id")).filter((id) => Boolean(id))
21801
+ jobs.map((job) => readString17(job, "id") ?? readString17(job, "job_id")).filter((id) => Boolean(id))
21199
21802
  );
21200
21803
  }
21201
21804
  async bindNewCronJobsCreatedByRun(input) {
@@ -21215,16 +21818,9 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
21215
21818
  });
21216
21819
  }
21217
21820
  };
21218
- function buildRunInstructions(run, deliveryStagingDir) {
21821
+ function buildRunInstructions(run) {
21219
21822
  return [
21220
21823
  HERMES_LINK_DELIVERY_INSTRUCTIONS,
21221
- ...deliveryStagingDir ? [
21222
- "",
21223
- "Delivery staging directory for this run:",
21224
- `- ${deliveryStagingDir}`,
21225
- "Copy every deliverable file into this directory, then run:",
21226
- `hermeslink deliver ${quoteShellArg(deliveryStagingDir)}`
21227
- ] : [],
21228
21824
  "",
21229
21825
  "Current runtime selected by Hermes Link:",
21230
21826
  `- Model: ${run.model ?? "hermes-agent"}`,
@@ -21268,9 +21864,6 @@ function appendMediaImportFailureNotice(message) {
21268
21864
  media_import_failure_notice_appended: true
21269
21865
  };
21270
21866
  }
21271
- function quoteShellArg(value) {
21272
- return `'${value.replaceAll("'", "'\\''")}'`;
21273
- }
21274
21867
  function formatMediaImportFailureNotice(failures, language = "zh-CN") {
21275
21868
  const filenames = failures.map((failure) => failure.filename);
21276
21869
  const target = language === "en" ? filenames.length === 1 ? `"${filenames[0]}"` : `${filenames.length} files (${formatFilenameList(filenames, language)})` : filenames.length === 1 ? `\u6587\u4EF6\u201C${filenames[0]}\u201D` : `${filenames.length} \u4E2A\u6587\u4EF6\uFF08${formatFilenameList(filenames, language)}\uFF09`;
@@ -21299,13 +21892,13 @@ function formatFilenameList(filenames, language = "zh-CN") {
21299
21892
  }
21300
21893
  async function readdirWithDirs(directory) {
21301
21894
  return readdir9(directory, { withFileTypes: true }).catch((error) => {
21302
- if (isNodeError16(error, "ENOENT")) {
21895
+ if (isNodeError17(error, "ENOENT")) {
21303
21896
  return [];
21304
21897
  }
21305
21898
  throw error;
21306
21899
  });
21307
21900
  }
21308
- function readString16(payload, key) {
21901
+ function readString17(payload, key) {
21309
21902
  const value = payload[key];
21310
21903
  return typeof value === "string" && value.trim() ? value.trim() : null;
21311
21904
  }
@@ -21402,13 +21995,13 @@ function isFileSearchCompletion(payloadType, payload) {
21402
21995
  const toolCall = toRecord15(payload.tool_call ?? payload.toolCall);
21403
21996
  const fn = toRecord15(toolCall.function ?? payload.function);
21404
21997
  const candidates = [
21405
- readString16(payload, "tool_name"),
21406
- readString16(payload, "toolName"),
21407
- readString16(payload, "name"),
21408
- readString16(payload, "tool"),
21409
- readString16(tool, "name"),
21410
- readString16(toolCall, "name"),
21411
- readString16(fn, "name")
21998
+ readString17(payload, "tool_name"),
21999
+ readString17(payload, "toolName"),
22000
+ readString17(payload, "name"),
22001
+ readString17(payload, "tool"),
22002
+ readString17(tool, "name"),
22003
+ readString17(toolCall, "name"),
22004
+ readString17(fn, "name")
21412
22005
  ].filter((value) => Boolean(value)).map(normalizeToolName);
21413
22006
  return candidates.some(
21414
22007
  (name) => [
@@ -21646,13 +22239,13 @@ function readResponseId(payload) {
21646
22239
  return null;
21647
22240
  }
21648
22241
  const response = toRecord15(payload.response);
21649
- return readString16(payload, "response_id") ?? readString16(response, "id");
22242
+ return readString17(payload, "response_id") ?? readString17(response, "id");
21650
22243
  }
21651
22244
  function readRunId(payload) {
21652
22245
  if (!payload) {
21653
22246
  return null;
21654
22247
  }
21655
- return readString16(payload, "run_id") ?? readString16(payload, "runId");
22248
+ return readString17(payload, "run_id") ?? readString17(payload, "runId");
21656
22249
  }
21657
22250
  function isCompletedRunStatus(status) {
21658
22251
  return status === "completed" || status === "complete" || status === "succeeded" || status === "success" || status === "done";
@@ -21668,7 +22261,7 @@ function readStatusErrorMessage(value) {
21668
22261
  return value.trim();
21669
22262
  }
21670
22263
  const record = toRecord15(value);
21671
- return readString16(record, "message") ?? readString16(record, "error");
22264
+ return readString17(record, "message") ?? readString17(record, "error");
21672
22265
  }
21673
22266
  function formatUnknownErrorMessage(error) {
21674
22267
  return error instanceof Error ? error.message : String(error);
@@ -21706,7 +22299,7 @@ async function sleep(ms, signal) {
21706
22299
  );
21707
22300
  });
21708
22301
  }
21709
- function isNodeError16(error, code) {
22302
+ function isNodeError17(error, code) {
21710
22303
  if (typeof error !== "object" || error === null || !("code" in error)) {
21711
22304
  return false;
21712
22305
  }
@@ -22331,6 +22924,38 @@ var ConversationService = class {
22331
22924
  );
22332
22925
  });
22333
22926
  }
22927
+ async deliverFilesFromTool(input) {
22928
+ const files = normalizeDeliveryFileInputs(input.files);
22929
+ if (files.length === 0) {
22930
+ throw new LinkHttpError(
22931
+ 400,
22932
+ "delivery_files_required",
22933
+ "At least one deliverable file path is required"
22934
+ );
22935
+ }
22936
+ const context = findDeliveryContext({
22937
+ profile: input.profile,
22938
+ taskId: input.taskId,
22939
+ sessionId: input.sessionId
22940
+ });
22941
+ const staged = await copyFilesIntoDeliveryContext(
22942
+ this.paths,
22943
+ context,
22944
+ files
22945
+ );
22946
+ if (staged.staged.length === 0) {
22947
+ throw new LinkHttpError(
22948
+ 400,
22949
+ "delivery_files_not_staged",
22950
+ "No deliverable files could be staged"
22951
+ );
22952
+ }
22953
+ return {
22954
+ ...await this.deliverStagedFiles(context.stagingDir),
22955
+ staged_count: staged.staged.length,
22956
+ skipped_files: staged.skipped
22957
+ };
22958
+ }
22334
22959
  async getMessages(conversationId, options = {}) {
22335
22960
  return this.queries.getMessages(conversationId, options);
22336
22961
  }
@@ -23488,7 +24113,7 @@ async function readRawBody(request, maxBytes) {
23488
24113
  }
23489
24114
  return Buffer.concat(chunks);
23490
24115
  }
23491
- function readString17(body, key) {
24116
+ function readString18(body, key) {
23492
24117
  const value = body[key];
23493
24118
  return typeof value === "string" && value.trim() ? value.trim() : null;
23494
24119
  }
@@ -23523,7 +24148,7 @@ function readSupportedLanguage(value) {
23523
24148
  return null;
23524
24149
  }
23525
24150
  function readOptionalProfileName(body) {
23526
- return readString17(body, "profile") ?? readString17(body, "profile_name") ?? readString17(body, "profileName") ?? void 0;
24151
+ return readString18(body, "profile") ?? readString18(body, "profile_name") ?? readString18(body, "profileName") ?? void 0;
23527
24152
  }
23528
24153
  function readStringArray(body, ...keys) {
23529
24154
  for (const key of keys) {
@@ -23942,7 +24567,7 @@ function registerConversationRoutes(router, options) {
23942
24567
  ok: true,
23943
24568
  conversation: localizeConversationSummary(
23944
24569
  await conversations.createConversation({
23945
- title: readString17(body, "title") ?? void 0,
24570
+ title: readString18(body, "title") ?? void 0,
23946
24571
  profileName: readOptionalProfileName(body),
23947
24572
  accountId: auth.accountId,
23948
24573
  appInstanceId: auth.appInstanceId
@@ -24021,7 +24646,7 @@ function registerConversationRoutes(router, options) {
24021
24646
  const auth = await authenticateRequest(ctx, paths);
24022
24647
  const language = readPreferredLanguage(ctx);
24023
24648
  const body = await readJsonBody(ctx.req);
24024
- const content = readString17(body, "content") ?? readString17(body, "text") ?? readString17(body, "input") ?? "";
24649
+ const content = readString18(body, "content") ?? readString18(body, "text") ?? readString18(body, "input") ?? "";
24025
24650
  const attachments = readMessageAttachments(body.attachments ?? body.blobs);
24026
24651
  if (!content && attachments.length === 0) {
24027
24652
  throw new LinkHttpError(
@@ -24038,7 +24663,7 @@ function registerConversationRoutes(router, options) {
24038
24663
  conversationId: ctx.params.conversationId,
24039
24664
  content,
24040
24665
  attachments,
24041
- clientMessageId: readString17(body, "client_message_id") ?? readString17(body, "clientMessageId") ?? void 0,
24666
+ clientMessageId: readString18(body, "client_message_id") ?? readString18(body, "clientMessageId") ?? void 0,
24042
24667
  idempotencyKey: readHeader(ctx, "idempotency-key") ?? void 0,
24043
24668
  profileName: readOptionalProfileName(body),
24044
24669
  accountId: auth.accountId,
@@ -24052,7 +24677,7 @@ function registerConversationRoutes(router, options) {
24052
24677
  router.patch("/api/v1/conversations/:conversationId/model", async (ctx) => {
24053
24678
  await authenticateRequest(ctx, paths);
24054
24679
  const body = await readJsonBody(ctx.req);
24055
- const modelId = readString17(body, "model_id") ?? readString17(body, "modelId") ?? readString17(body, "model");
24680
+ const modelId = readString18(body, "model_id") ?? readString18(body, "modelId") ?? readString18(body, "model");
24056
24681
  if (!modelId) {
24057
24682
  throw new LinkHttpError(400, "model_id_required", "model_id is required");
24058
24683
  }
@@ -24087,7 +24712,7 @@ function registerConversationRoutes(router, options) {
24087
24712
  await authenticateRequest(ctx, paths);
24088
24713
  const language = readPreferredLanguage(ctx);
24089
24714
  const body = await readJsonBody(ctx.req);
24090
- const title = readString17(body, "title") ?? readString17(body, "name") ?? readString17(body, "display_name");
24715
+ const title = readString18(body, "title") ?? readString18(body, "name") ?? readString18(body, "display_name");
24091
24716
  if (!title) {
24092
24717
  throw new LinkHttpError(400, "title_required", "title is required");
24093
24718
  }
@@ -24281,7 +24906,7 @@ function registerConversationRoutes(router, options) {
24281
24906
  async (ctx) => {
24282
24907
  await authenticateRequest(ctx, paths);
24283
24908
  const body = await readJsonBody(ctx.req);
24284
- const scope = readString17(body, "scope") ?? "always";
24909
+ const scope = readString18(body, "scope") ?? "always";
24285
24910
  ctx.body = {
24286
24911
  ok: true,
24287
24912
  ...await conversations.resolveApproval({
@@ -24492,7 +25117,7 @@ function resolveConversationEventCursor(input) {
24492
25117
  return Math.max(queryAfter, headerAfter);
24493
25118
  }
24494
25119
  function readConversationClearPlanTargetStatus(body) {
24495
- const raw = readString17(body, "target_status") ?? readString17(body, "targetStatus") ?? "active";
25120
+ const raw = readString18(body, "target_status") ?? readString18(body, "targetStatus") ?? "active";
24496
25121
  if (raw === "active" || raw === "archived") {
24497
25122
  return raw;
24498
25123
  }
@@ -24618,11 +25243,11 @@ function isSseRequestContext(ctx) {
24618
25243
  }
24619
25244
  return isSseRequestPath(ctx.path) || isActiveSseSocket(ctx.req.socket);
24620
25245
  }
24621
- function isSseRequestPath(path31) {
24622
- if (!path31) {
25246
+ function isSseRequestPath(path32) {
25247
+ if (!path32) {
24623
25248
  return false;
24624
25249
  }
24625
- return path31 === "/api/v1/conversations/events" || path31 === "/api/v1/profile-creation/events" || path31 === "/api/v1/hermes/update/events" || path31 === "/api/v1/link/update/events" || /^\/api\/v1\/conversations\/[^/]+\/events$/u.test(path31) || /^\/api\/v1\/runs\/[^/]+\/events$/u.test(path31);
25250
+ return path32 === "/api/v1/conversations/events" || path32 === "/api/v1/profile-creation/events" || path32 === "/api/v1/hermes/update/events" || path32 === "/api/v1/link/update/events" || /^\/api\/v1\/conversations\/[^/]+\/events$/u.test(path32) || /^\/api\/v1\/runs\/[^/]+\/events$/u.test(path32);
24626
25251
  }
24627
25252
  function isExpectedClientDisconnectError2(error, options = {}) {
24628
25253
  if (!(error instanceof Error)) {
@@ -24723,6 +25348,7 @@ function registerCronJobRoutes(router, options) {
24723
25348
  router.get("/api/v1/profiles/:name/cron-jobs/:jobId", async (ctx) => {
24724
25349
  await authenticateRequest(ctx, paths);
24725
25350
  const profile = await getHermesProfileStatus(ctx.params.name, paths);
25351
+ assertCronJobId(ctx.params.jobId);
24726
25352
  ctx.set("cache-control", "no-store");
24727
25353
  const job = await getHermesCronJob(ctx.params.jobId, {
24728
25354
  logger,
@@ -24736,9 +25362,46 @@ function registerCronJobRoutes(router, options) {
24736
25362
  )
24737
25363
  };
24738
25364
  });
25365
+ router.get("/api/v1/profiles/:name/cron-jobs/:jobId/history", async (ctx) => {
25366
+ await authenticateRequest(ctx, paths);
25367
+ const profile = await getHermesProfileStatus(ctx.params.name, paths);
25368
+ assertCronJobId(ctx.params.jobId);
25369
+ ctx.set("cache-control", "no-store");
25370
+ await getHermesCronJob(ctx.params.jobId, {
25371
+ logger,
25372
+ profileName: profile.name
25373
+ });
25374
+ const historyPage = await listHermesCronJobHistory({
25375
+ profileName: profile.name,
25376
+ jobId: ctx.params.jobId,
25377
+ limit: readPositiveInteger2(ctx.query.limit),
25378
+ offset: readPositiveInteger2(ctx.query.offset)
25379
+ });
25380
+ const history = historyPage.entries.map((entry) => ({
25381
+ id: entry.id,
25382
+ run_at: entry.runAt,
25383
+ content: entry.content,
25384
+ failed: entry.failed,
25385
+ status: entry.status,
25386
+ job_name: entry.jobName ?? null,
25387
+ truncated: entry.truncated
25388
+ }));
25389
+ ctx.body = {
25390
+ ok: true,
25391
+ profile,
25392
+ history,
25393
+ pagination: {
25394
+ total: historyPage.total,
25395
+ offset: historyPage.offset,
25396
+ limit: historyPage.limit,
25397
+ has_more: historyPage.hasMore
25398
+ }
25399
+ };
25400
+ });
24739
25401
  router.patch("/api/v1/profiles/:name/cron-jobs/:jobId", async (ctx) => {
24740
25402
  const auth = await authenticateRequest(ctx, paths);
24741
25403
  const profile = await getHermesProfileStatus(ctx.params.name, paths);
25404
+ assertCronJobId(ctx.params.jobId);
24742
25405
  const body = await readJsonBody(ctx.req);
24743
25406
  const input = readCronJobUpdateInput(body);
24744
25407
  const deliverTouched = readOptionalCronText(body, "deliver").present;
@@ -24771,6 +25434,7 @@ function registerCronJobRoutes(router, options) {
24771
25434
  router.delete("/api/v1/profiles/:name/cron-jobs/:jobId", async (ctx) => {
24772
25435
  await authenticateRequest(ctx, paths);
24773
25436
  const profile = await getHermesProfileStatus(ctx.params.name, paths);
25437
+ assertCronJobId(ctx.params.jobId);
24774
25438
  await deleteHermesCronJob(ctx.params.jobId, {
24775
25439
  logger,
24776
25440
  profileName: profile.name
@@ -24781,6 +25445,7 @@ function registerCronJobRoutes(router, options) {
24781
25445
  router.post("/api/v1/profiles/:name/cron-jobs/:jobId/pause", async (ctx) => {
24782
25446
  await authenticateRequest(ctx, paths);
24783
25447
  const profile = await getHermesProfileStatus(ctx.params.name, paths);
25448
+ assertCronJobId(ctx.params.jobId);
24784
25449
  const job = await runHermesCronJobAction(ctx.params.jobId, "pause", {
24785
25450
  logger,
24786
25451
  profileName: profile.name
@@ -24796,6 +25461,7 @@ function registerCronJobRoutes(router, options) {
24796
25461
  router.post("/api/v1/profiles/:name/cron-jobs/:jobId/resume", async (ctx) => {
24797
25462
  await authenticateRequest(ctx, paths);
24798
25463
  const profile = await getHermesProfileStatus(ctx.params.name, paths);
25464
+ assertCronJobId(ctx.params.jobId);
24799
25465
  const job = await runHermesCronJobAction(ctx.params.jobId, "resume", {
24800
25466
  logger,
24801
25467
  profileName: profile.name
@@ -24811,6 +25477,7 @@ function registerCronJobRoutes(router, options) {
24811
25477
  router.post("/api/v1/profiles/:name/cron-jobs/:jobId/run", async (ctx) => {
24812
25478
  await authenticateRequest(ctx, paths);
24813
25479
  const profile = await getHermesProfileStatus(ctx.params.name, paths);
25480
+ assertCronJobId(ctx.params.jobId);
24814
25481
  const job = await runHermesCronJobAction(ctx.params.jobId, "run", {
24815
25482
  logger,
24816
25483
  profileName: profile.name
@@ -24844,7 +25511,7 @@ function toHermesCronJobInput(input) {
24844
25511
  };
24845
25512
  }
24846
25513
  async function bindAndDecorateCronJobForHermesLink(input) {
24847
- const jobId = readString17(input.job, "id") ?? readString17(input.job, "job_id");
25514
+ const jobId = readString18(input.job, "id") ?? readString18(input.job, "job_id");
24848
25515
  if (!jobId) {
24849
25516
  return input.job;
24850
25517
  }
@@ -24863,9 +25530,9 @@ async function bindAndDecorateCronJobForHermesLink(input) {
24863
25530
  }
24864
25531
  function readCronJobCreateInput(body) {
24865
25532
  const input = {};
24866
- const name = readString17(body, "name") ?? readString17(body, "title");
24867
- const prompt = readString17(body, "prompt") ?? readString17(body, "description") ?? readString17(body, "task");
24868
- const schedule = readString17(body, "schedule");
25533
+ const name = readString18(body, "name") ?? readString18(body, "title");
25534
+ const prompt = readString18(body, "prompt") ?? readString18(body, "description") ?? readString18(body, "task");
25535
+ const schedule = readString18(body, "schedule");
24869
25536
  if (!name) {
24870
25537
  throw new LinkHttpError(400, "cron_job_name_required", "name is required");
24871
25538
  }
@@ -24886,7 +25553,7 @@ function readCronJobCreateInput(body) {
24886
25553
  input.name = name;
24887
25554
  input.prompt = prompt;
24888
25555
  input.schedule = schedule;
24889
- input.deliver = readString17(body, "deliver") ?? HERMES_LINK_CRON_DELIVER;
25556
+ input.deliver = readString18(body, "deliver") ?? HERMES_LINK_CRON_DELIVER;
24890
25557
  const skills = readOptionalCronSkills(body);
24891
25558
  if (skills) {
24892
25559
  input.skills = skills;
@@ -25006,6 +25673,12 @@ function attachCronJobProfile(job, profile) {
25006
25673
  }
25007
25674
  };
25008
25675
  }
25676
+ function assertCronJobId(jobId) {
25677
+ if (/^[a-f0-9]{12}$/u.test(jobId)) {
25678
+ return;
25679
+ }
25680
+ throw new LinkHttpError(400, "cron_job_id_invalid", "Invalid cron job id");
25681
+ }
25009
25682
 
25010
25683
  // src/http/routes/gateway-reload.ts
25011
25684
  async function reloadGatewayAfterModelConfigChange(result, options) {
@@ -25213,9 +25886,9 @@ function registerModelConfigRoutes(router, options) {
25213
25886
  });
25214
25887
  }
25215
25888
  function readModelConfigInput(body) {
25216
- const id = readString17(body, "id") ?? readString17(body, "model_id") ?? readString17(body, "modelId");
25217
- const provider = readString17(body, "provider") ?? readString17(body, "provider_key") ?? readString17(body, "providerKey");
25218
- const baseUrl = readString17(body, "base_url") ?? readString17(body, "baseUrl");
25889
+ const id = readString18(body, "id") ?? readString18(body, "model_id") ?? readString18(body, "modelId");
25890
+ const provider = readString18(body, "provider") ?? readString18(body, "provider_key") ?? readString18(body, "providerKey");
25891
+ const baseUrl = readString18(body, "base_url") ?? readString18(body, "baseUrl");
25219
25892
  if (!id || !provider || !baseUrl) {
25220
25893
  throw new LinkHttpError(
25221
25894
  400,
@@ -25225,21 +25898,21 @@ function readModelConfigInput(body) {
25225
25898
  }
25226
25899
  return {
25227
25900
  id,
25228
- originalModelId: readString17(body, "original_model_id") ?? readString17(body, "originalModelId") ?? readString17(body, "original_id") ?? void 0,
25229
- originalProvider: readString17(body, "original_provider") ?? readString17(body, "originalProvider") ?? readString17(body, "original_provider_key") ?? readString17(body, "originalProviderKey") ?? void 0,
25230
- originalBaseUrl: readString17(body, "original_base_url") ?? readString17(body, "originalBaseUrl") ?? void 0,
25231
- originalApiMode: readString17(body, "original_api_mode") ?? readString17(body, "originalApiMode") ?? void 0,
25901
+ originalModelId: readString18(body, "original_model_id") ?? readString18(body, "originalModelId") ?? readString18(body, "original_id") ?? void 0,
25902
+ originalProvider: readString18(body, "original_provider") ?? readString18(body, "originalProvider") ?? readString18(body, "original_provider_key") ?? readString18(body, "originalProviderKey") ?? void 0,
25903
+ originalBaseUrl: readString18(body, "original_base_url") ?? readString18(body, "originalBaseUrl") ?? void 0,
25904
+ originalApiMode: readString18(body, "original_api_mode") ?? readString18(body, "originalApiMode") ?? void 0,
25232
25905
  provider,
25233
- providerName: readString17(body, "provider_name") ?? readString17(body, "providerName") ?? void 0,
25906
+ providerName: readString18(body, "provider_name") ?? readString18(body, "providerName") ?? void 0,
25234
25907
  baseUrl,
25235
- apiKey: readString17(body, "api_key") ?? readString17(body, "apiKey") ?? void 0,
25236
- apiMode: readString17(body, "api_mode") ?? readString17(body, "apiMode") ?? void 0,
25908
+ apiKey: readString18(body, "api_key") ?? readString18(body, "apiKey") ?? void 0,
25909
+ apiMode: readString18(body, "api_mode") ?? readString18(body, "apiMode") ?? void 0,
25237
25910
  contextLength: readPositiveInteger2(
25238
25911
  body.context_length ?? body.contextLength
25239
25912
  ),
25240
- keyEnv: readString17(body, "key_env") ?? readString17(body, "keyEnv") ?? void 0,
25913
+ keyEnv: readString18(body, "key_env") ?? readString18(body, "keyEnv") ?? void 0,
25241
25914
  setDefault: readBoolean3(body.set_default ?? body.setDefault),
25242
- reasoningEffort: readString17(body, "reasoning_effort") ?? readString17(body, "reasoningEffort") ?? void 0,
25915
+ reasoningEffort: readString18(body, "reasoning_effort") ?? readString18(body, "reasoningEffort") ?? void 0,
25243
25916
  supportsVision: readNullableBoolean(
25244
25917
  body.supports_vision ?? body.supportsVision
25245
25918
  )
@@ -25247,28 +25920,28 @@ function readModelConfigInput(body) {
25247
25920
  }
25248
25921
  function readModelDefaultsInput(body) {
25249
25922
  return {
25250
- taskModelId: readString17(body, "task_model_id") ?? readString17(body, "taskModelId") ?? readString17(body, "default_model_id") ?? readString17(body, "defaultModelId") ?? void 0,
25251
- taskModelProvider: readString17(body, "task_model_provider") ?? readString17(body, "taskModelProvider") ?? readString17(body, "default_model_provider") ?? readString17(body, "defaultModelProvider") ?? void 0,
25252
- taskModelBaseUrl: readString17(body, "task_model_base_url") ?? readString17(body, "taskModelBaseUrl") ?? readString17(body, "default_model_base_url") ?? readString17(body, "defaultModelBaseUrl") ?? void 0,
25253
- taskModelApiMode: readString17(body, "task_model_api_mode") ?? readString17(body, "taskModelApiMode") ?? readString17(body, "default_model_api_mode") ?? readString17(body, "defaultModelApiMode") ?? void 0,
25254
- compressionModelId: readString17(body, "compression_model_id") ?? readString17(body, "compressionModelId") ?? void 0,
25255
- compressionModelProvider: readString17(body, "compression_model_provider") ?? readString17(body, "compressionModelProvider") ?? void 0,
25256
- compressionModelBaseUrl: readString17(body, "compression_model_base_url") ?? readString17(body, "compressionModelBaseUrl") ?? void 0,
25257
- compressionModelApiMode: readString17(body, "compression_model_api_mode") ?? readString17(body, "compressionModelApiMode") ?? void 0,
25258
- reasoningEffort: readString17(body, "reasoning_effort") ?? readString17(body, "reasoningEffort") ?? readString17(body, "default_reasoning_effort") ?? readString17(body, "defaultReasoningEffort") ?? void 0,
25259
- imageInputMode: readString17(body, "image_input_mode") ?? readString17(body, "imageInputMode") ?? void 0
25923
+ taskModelId: readString18(body, "task_model_id") ?? readString18(body, "taskModelId") ?? readString18(body, "default_model_id") ?? readString18(body, "defaultModelId") ?? void 0,
25924
+ taskModelProvider: readString18(body, "task_model_provider") ?? readString18(body, "taskModelProvider") ?? readString18(body, "default_model_provider") ?? readString18(body, "defaultModelProvider") ?? void 0,
25925
+ taskModelBaseUrl: readString18(body, "task_model_base_url") ?? readString18(body, "taskModelBaseUrl") ?? readString18(body, "default_model_base_url") ?? readString18(body, "defaultModelBaseUrl") ?? void 0,
25926
+ taskModelApiMode: readString18(body, "task_model_api_mode") ?? readString18(body, "taskModelApiMode") ?? readString18(body, "default_model_api_mode") ?? readString18(body, "defaultModelApiMode") ?? void 0,
25927
+ compressionModelId: readString18(body, "compression_model_id") ?? readString18(body, "compressionModelId") ?? void 0,
25928
+ compressionModelProvider: readString18(body, "compression_model_provider") ?? readString18(body, "compressionModelProvider") ?? void 0,
25929
+ compressionModelBaseUrl: readString18(body, "compression_model_base_url") ?? readString18(body, "compressionModelBaseUrl") ?? void 0,
25930
+ compressionModelApiMode: readString18(body, "compression_model_api_mode") ?? readString18(body, "compressionModelApiMode") ?? void 0,
25931
+ reasoningEffort: readString18(body, "reasoning_effort") ?? readString18(body, "reasoningEffort") ?? readString18(body, "default_reasoning_effort") ?? readString18(body, "defaultReasoningEffort") ?? void 0,
25932
+ imageInputMode: readString18(body, "image_input_mode") ?? readString18(body, "imageInputMode") ?? void 0
25260
25933
  };
25261
25934
  }
25262
25935
  function readModelDeleteInput(body) {
25263
- const id = readString17(body, "model_id") ?? readString17(body, "modelId");
25936
+ const id = readString18(body, "model_id") ?? readString18(body, "modelId");
25264
25937
  if (!id) {
25265
25938
  throw new LinkHttpError(400, "model_id_required", "model_id is required");
25266
25939
  }
25267
25940
  return {
25268
25941
  id,
25269
- provider: readString17(body, "provider") ?? readString17(body, "provider_key") ?? readString17(body, "providerKey") ?? void 0,
25270
- baseUrl: readString17(body, "base_url") ?? readString17(body, "baseUrl") ?? void 0,
25271
- apiMode: readString17(body, "api_mode") ?? readString17(body, "apiMode") ?? void 0
25942
+ provider: readString18(body, "provider") ?? readString18(body, "provider_key") ?? readString18(body, "providerKey") ?? void 0,
25943
+ baseUrl: readString18(body, "base_url") ?? readString18(body, "baseUrl") ?? void 0,
25944
+ apiMode: readString18(body, "api_mode") ?? readString18(body, "apiMode") ?? void 0
25272
25945
  };
25273
25946
  }
25274
25947
  function readNullableBoolean(value) {
@@ -25291,8 +25964,8 @@ function readNullableBoolean(value) {
25291
25964
  return void 0;
25292
25965
  }
25293
25966
  function readModelConfigImportInput(body) {
25294
- const sourceProfileName = readString17(body, "source_profile") ?? readString17(body, "sourceProfile") ?? readString17(body, "source_profile_name") ?? readString17(body, "sourceProfileName");
25295
- const modelId = readString17(body, "model_id") ?? readString17(body, "modelId") ?? readString17(body, "id");
25967
+ const sourceProfileName = readString18(body, "source_profile") ?? readString18(body, "sourceProfile") ?? readString18(body, "source_profile_name") ?? readString18(body, "sourceProfileName");
25968
+ const modelId = readString18(body, "model_id") ?? readString18(body, "modelId") ?? readString18(body, "id");
25296
25969
  if (!sourceProfileName || !modelId) {
25297
25970
  throw new LinkHttpError(
25298
25971
  400,
@@ -25303,9 +25976,9 @@ function readModelConfigImportInput(body) {
25303
25976
  return {
25304
25977
  sourceProfileName,
25305
25978
  modelId,
25306
- provider: readString17(body, "provider") ?? readString17(body, "provider_key") ?? readString17(body, "providerKey") ?? void 0,
25307
- baseUrl: readString17(body, "base_url") ?? readString17(body, "baseUrl") ?? void 0,
25308
- apiMode: readString17(body, "api_mode") ?? readString17(body, "apiMode") ?? void 0,
25979
+ provider: readString18(body, "provider") ?? readString18(body, "provider_key") ?? readString18(body, "providerKey") ?? void 0,
25980
+ baseUrl: readString18(body, "base_url") ?? readString18(body, "baseUrl") ?? void 0,
25981
+ apiMode: readString18(body, "api_mode") ?? readString18(body, "apiMode") ?? void 0,
25309
25982
  setDefault: readBoolean3(body.set_default ?? body.setDefault)
25310
25983
  };
25311
25984
  }
@@ -25396,20 +26069,20 @@ function errorMessage3(error) {
25396
26069
  }
25397
26070
 
25398
26071
  // src/hermes/profile-identity.ts
25399
- import { readFile as readFile15, stat as stat15 } from "fs/promises";
25400
- import path22 from "path";
26072
+ import { readFile as readFile15, stat as stat16 } from "fs/promises";
26073
+ import path23 from "path";
25401
26074
  var MAX_SOUL_MD_LENGTH = 2e4;
25402
26075
  async function readHermesProfileIdentity(profileName, paths) {
25403
26076
  await assertProfileExists2(profileName, paths);
25404
26077
  const soulPath = resolveSoulPath(profileName);
25405
26078
  const content = await readFile15(soulPath, "utf8").catch((error) => {
25406
- if (isNodeError17(error, "ENOENT")) {
26079
+ if (isNodeError18(error, "ENOENT")) {
25407
26080
  return null;
25408
26081
  }
25409
26082
  throw error;
25410
26083
  });
25411
- const fileStat = content === null ? null : await stat15(soulPath).catch((error) => {
25412
- if (isNodeError17(error, "ENOENT")) {
26084
+ const fileStat = content === null ? null : await stat16(soulPath).catch((error) => {
26085
+ if (isNodeError18(error, "ENOENT")) {
25413
26086
  return null;
25414
26087
  }
25415
26088
  throw error;
@@ -25454,9 +26127,9 @@ async function assertProfileExists2(profileName, paths) {
25454
26127
  }
25455
26128
  }
25456
26129
  function resolveSoulPath(profileName) {
25457
- return path22.join(resolveHermesProfileDir(profileName), "SOUL.md");
26130
+ return path23.join(resolveHermesProfileDir(profileName), "SOUL.md");
25458
26131
  }
25459
- function isNodeError17(error, code) {
26132
+ function isNodeError18(error, code) {
25460
26133
  return error instanceof Error && "code" in error && error.code === code;
25461
26134
  }
25462
26135
 
@@ -25465,18 +26138,18 @@ import { spawn as spawn2 } from "child_process";
25465
26138
  import { EventEmitter as EventEmitter2 } from "events";
25466
26139
  import {
25467
26140
  cp,
25468
- mkdir as mkdir11,
26141
+ mkdir as mkdir12,
25469
26142
  readFile as readFile17,
25470
- rm as rm6,
25471
- stat as stat17
26143
+ rm as rm7,
26144
+ stat as stat18
25472
26145
  } from "fs/promises";
25473
- import path24 from "path";
26146
+ import path25 from "path";
25474
26147
  import YAML5 from "yaml";
25475
26148
 
25476
26149
  // src/hermes/link-skill.ts
25477
- import { readFile as readFile16, stat as stat16 } from "fs/promises";
26150
+ import { readFile as readFile16, stat as stat17 } from "fs/promises";
25478
26151
  import os5 from "os";
25479
- import path23 from "path";
26152
+ import path24 from "path";
25480
26153
  import YAML4 from "yaml";
25481
26154
  var HERMES_LINK_SKILL_ROOT_DIR = "hermes-skills";
25482
26155
  var HERMES_LINK_SKILL_DIR = "hermes-link";
@@ -25561,7 +26234,7 @@ Do not modify Hermes profiles, delete user data, edit config files, or kill proc
25561
26234
  async function ensureHermesLinkSkillInstalledForProfiles(options = {}) {
25562
26235
  const paths = options.paths ?? resolveRuntimePaths();
25563
26236
  const externalDir = resolveHermesLinkSkillExternalDir(paths);
25564
- const skillPath = path23.join(
26237
+ const skillPath = path24.join(
25565
26238
  externalDir,
25566
26239
  HERMES_LINK_SKILL_DIR,
25567
26240
  HERMES_LINK_SKILL_FILE
@@ -25643,11 +26316,11 @@ function withDefaultProfilePlaceholder2(profiles) {
25643
26316
  ];
25644
26317
  }
25645
26318
  function resolveHermesLinkSkillExternalDir(paths = resolveRuntimePaths()) {
25646
- return path23.join(paths.homeDir, HERMES_LINK_SKILL_ROOT_DIR);
26319
+ return path24.join(paths.homeDir, HERMES_LINK_SKILL_ROOT_DIR);
25647
26320
  }
25648
26321
  async function writeHermesLinkSkill(skillPath) {
25649
26322
  const existing = await readFile16(skillPath, "utf8").catch((error) => {
25650
- if (isNodeError18(error, "ENOENT")) {
26323
+ if (isNodeError19(error, "ENOENT")) {
25651
26324
  return null;
25652
26325
  }
25653
26326
  throw error;
@@ -25706,7 +26379,7 @@ async function ensureProfileUsesExternalSkillDir(profile, externalDir) {
25706
26379
  async function readHermesConfigDocument3(configPath) {
25707
26380
  const existingRaw = await readFile16(configPath, "utf8").catch(
25708
26381
  (error) => {
25709
- if (isNodeError18(error, "ENOENT")) {
26382
+ if (isNodeError19(error, "ENOENT")) {
25710
26383
  return null;
25711
26384
  }
25712
26385
  throw error;
@@ -25738,11 +26411,11 @@ function appendExternalDir(current, externalDir, hermesHome) {
25738
26411
  const seen = new Set(
25739
26412
  entries.map((entry) => resolveExternalDirEntry(entry, hermesHome))
25740
26413
  );
25741
- const normalizedExternalDir = path23.resolve(externalDir);
26414
+ const normalizedExternalDir = path24.resolve(externalDir);
25742
26415
  return seen.has(normalizedExternalDir) ? entries : [...entries, normalizedExternalDir];
25743
26416
  }
25744
26417
  function externalDirsInclude(current, externalDir, hermesHome) {
25745
- const normalizedExternalDir = path23.resolve(externalDir);
26418
+ const normalizedExternalDir = path24.resolve(externalDir);
25746
26419
  return readExternalDirEntries(current).some(
25747
26420
  (entry) => resolveExternalDirEntry(entry, hermesHome) === normalizedExternalDir
25748
26421
  );
@@ -25753,14 +26426,14 @@ function readExternalDirEntries(value) {
25753
26426
  }
25754
26427
  function resolveExternalDirEntry(entry, hermesHome) {
25755
26428
  const expanded = expandHome(expandEnvVars(entry));
25756
- return path23.resolve(path23.isAbsolute(expanded) ? expanded : path23.join(hermesHome, expanded));
26429
+ return path24.resolve(path24.isAbsolute(expanded) ? expanded : path24.join(hermesHome, expanded));
25757
26430
  }
25758
26431
  function expandHome(value) {
25759
26432
  if (value === "~") {
25760
26433
  return os5.homedir();
25761
26434
  }
25762
- if (value.startsWith(`~${path23.sep}`) || value.startsWith("~/")) {
25763
- return path23.join(os5.homedir(), value.slice(2));
26435
+ if (value.startsWith(`~${path24.sep}`) || value.startsWith("~/")) {
26436
+ return path24.join(os5.homedir(), value.slice(2));
25764
26437
  }
25765
26438
  return value;
25766
26439
  }
@@ -25771,8 +26444,8 @@ function expandEnvVars(value) {
25771
26444
  );
25772
26445
  }
25773
26446
  async function pathIsDirectory2(filePath) {
25774
- return stat16(filePath).then((value) => value.isDirectory()).catch((error) => {
25775
- if (isNodeError18(error, "ENOENT")) {
26447
+ return stat17(filePath).then((value) => value.isDirectory()).catch((error) => {
26448
+ if (isNodeError19(error, "ENOENT")) {
25776
26449
  return false;
25777
26450
  }
25778
26451
  throw error;
@@ -25793,7 +26466,7 @@ function toRecord16(value) {
25793
26466
  function isRecord4(value) {
25794
26467
  return typeof value === "object" && value !== null && !Array.isArray(value);
25795
26468
  }
25796
- function isNodeError18(error, code) {
26469
+ function isNodeError19(error, code) {
25797
26470
  return error instanceof Error && "code" in error && error.code === code;
25798
26471
  }
25799
26472
 
@@ -25848,7 +26521,7 @@ async function startHermesProfileCreation(input, options) {
25848
26521
  identity_error: null,
25849
26522
  identity_template_id: normalized.identityTemplateId
25850
26523
  };
25851
- await mkdir11(options.paths.runDir, { recursive: true, mode: 448 });
26524
+ await mkdir12(options.paths.runDir, { recursive: true, mode: 448 });
25852
26525
  await writeProfileCreationState(options.paths, started);
25853
26526
  await writer.write(`
25854
26527
  === profile creation started ${startedAt} ===
@@ -26312,9 +26985,9 @@ function collectEnvKeys(value, keys = /* @__PURE__ */ new Set()) {
26312
26985
  return keys;
26313
26986
  }
26314
26987
  async function writeEnvValues(profileName, values) {
26315
- const envPath = path24.join(resolveHermesProfileDir(profileName), ".env");
26988
+ const envPath = path25.join(resolveHermesProfileDir(profileName), ".env");
26316
26989
  const existingRaw = await readFile17(envPath, "utf8").catch((error) => {
26317
- if (isNodeError19(error, "ENOENT")) {
26990
+ if (isNodeError20(error, "ENOENT")) {
26318
26991
  return "";
26319
26992
  }
26320
26993
  throw error;
@@ -26349,12 +27022,12 @@ async function writeEnvValues(profileName, values) {
26349
27022
  await atomicWriteFilePreservingMetadata(envPath, nextRaw);
26350
27023
  }
26351
27024
  async function copySkills(sourceProfile, targetProfile) {
26352
- const sourceSkills = path24.join(resolveHermesProfileDir(sourceProfile), "skills");
26353
- const targetSkills = path24.join(resolveHermesProfileDir(targetProfile), "skills");
27025
+ const sourceSkills = path25.join(resolveHermesProfileDir(sourceProfile), "skills");
27026
+ const targetSkills = path25.join(resolveHermesProfileDir(targetProfile), "skills");
26354
27027
  if (!await pathExists2(sourceSkills)) {
26355
27028
  return;
26356
27029
  }
26357
- await rm6(targetSkills, { recursive: true, force: true });
27030
+ await rm7(targetSkills, { recursive: true, force: true });
26358
27031
  await cp(sourceSkills, targetSkills, {
26359
27032
  recursive: true,
26360
27033
  force: true,
@@ -26375,7 +27048,7 @@ function copyProperty(source, target, key) {
26375
27048
  async function readYamlConfig(configPath) {
26376
27049
  const existingRaw = await readFile17(configPath, "utf8").catch(
26377
27050
  (error) => {
26378
- if (isNodeError19(error, "ENOENT")) {
27051
+ if (isNodeError20(error, "ENOENT")) {
26379
27052
  return null;
26380
27053
  }
26381
27054
  throw error;
@@ -26402,7 +27075,7 @@ async function failProfileCreation(input) {
26402
27075
  await input.writer.write(`
26403
27076
  Rolling back ${input.rollbackProfileName}...
26404
27077
  `);
26405
- await rm6(resolveHermesProfileDir(input.rollbackProfileName), {
27078
+ await rm7(resolveHermesProfileDir(input.rollbackProfileName), {
26406
27079
  recursive: true,
26407
27080
  force: true
26408
27081
  }).catch(() => void 0);
@@ -26445,24 +27118,24 @@ async function readProfileCreationLogLines(paths) {
26445
27118
  );
26446
27119
  }
26447
27120
  function profileCreationStatePath(paths) {
26448
- return path24.join(paths.runDir, "profile-create-state.json");
27121
+ return path25.join(paths.runDir, "profile-create-state.json");
26449
27122
  }
26450
27123
  function profileCreationLogPath(paths) {
26451
- return path24.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
27124
+ return path25.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
26452
27125
  }
26453
27126
  async function clearProfileCreationLogFiles(paths) {
26454
27127
  const primary = profileCreationLogPath(paths);
26455
27128
  await Promise.all([
26456
- rm6(primary, { force: true }).catch(() => void 0),
27129
+ rm7(primary, { force: true }).catch(() => void 0),
26457
27130
  ...Array.from(
26458
27131
  { length: PROFILE_CREATE_LOG_MAX_FILES },
26459
- (_, index) => rm6(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
27132
+ (_, index) => rm7(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
26460
27133
  )
26461
27134
  ]);
26462
27135
  }
26463
27136
  async function pathExists2(targetPath) {
26464
- return await stat17(targetPath).then(() => true).catch((error) => {
26465
- if (isNodeError19(error, "ENOENT")) {
27137
+ return await stat18(targetPath).then(() => true).catch((error) => {
27138
+ if (isNodeError20(error, "ENOENT")) {
26466
27139
  return false;
26467
27140
  }
26468
27141
  throw error;
@@ -26509,7 +27182,7 @@ function formatEnvValue2(value) {
26509
27182
  function escapeRegExp3(value) {
26510
27183
  return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
26511
27184
  }
26512
- function isNodeError19(error, code) {
27185
+ function isNodeError20(error, code) {
26513
27186
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
26514
27187
  }
26515
27188
 
@@ -26594,16 +27267,16 @@ function readProfilePermissionsInput(body) {
26594
27267
  const approvals = readOptionalObject(body, "approvals");
26595
27268
  if (approvals) {
26596
27269
  input.approvals = {
26597
- mode: readString17(approvals, "mode") ?? readString17(approvals, "approval_mode") ?? readString17(approvals, "approvalMode") ?? void 0,
27270
+ mode: readString18(approvals, "mode") ?? readString18(approvals, "approval_mode") ?? readString18(approvals, "approvalMode") ?? void 0,
26598
27271
  timeout: readPositiveInteger2(approvals.timeout),
26599
- cronMode: readString17(approvals, "cron_mode") ?? readString17(approvals, "cronMode") ?? void 0
27272
+ cronMode: readString18(approvals, "cron_mode") ?? readString18(approvals, "cronMode") ?? void 0
26600
27273
  };
26601
27274
  }
26602
27275
  const terminal = readOptionalObject(body, "terminal");
26603
27276
  if (terminal) {
26604
27277
  input.terminal = {
26605
- backend: readString17(terminal, "backend") ?? void 0,
26606
- cwd: readString17(terminal, "cwd") ?? void 0,
27278
+ backend: readString18(terminal, "backend") ?? void 0,
27279
+ cwd: readString18(terminal, "cwd") ?? void 0,
26607
27280
  containerCpu: readPositiveInteger2(
26608
27281
  terminal.container_cpu ?? terminal.containerCpu
26609
27282
  ),
@@ -26720,9 +27393,9 @@ import {
26720
27393
  access as access3,
26721
27394
  readdir as readdir10,
26722
27395
  readFile as readFile18,
26723
- stat as stat18
27396
+ stat as stat19
26724
27397
  } from "fs/promises";
26725
- import path25 from "path";
27398
+ import path26 from "path";
26726
27399
  import YAML6 from "yaml";
26727
27400
  var ENTRY_DELIMITER = "\n\xA7\n";
26728
27401
  var DEFAULT_MEMORY_LIMIT = 2200;
@@ -26866,14 +27539,23 @@ async function resetHermesMemoryStore(profileName, target) {
26866
27539
  return readHermesProfileMemory(profileName);
26867
27540
  }
26868
27541
  async function saveHermesMemorySettings(profileName, patch) {
26869
- const provider = await readActiveMemoryProvider(profileName);
26870
- if (!provider) {
26871
- throw new HermesMemoryError(
26872
- "memory_settings_builtin_only",
26873
- "\u5F53\u524D Profile \u4F7F\u7528 built-in memory\uFF0C\u6CA1\u6709\u53EF\u4FDD\u5B58\u7684\u5916\u90E8 provider \u8BBE\u7F6E\u3002"
26874
- );
27542
+ const hasLimitPatch = patch.memoryCharLimit !== void 0 || patch.userCharLimit !== void 0;
27543
+ const hasProviderPatch = Object.keys(patch).some(
27544
+ (key) => key !== "memoryCharLimit" && key !== "userCharLimit"
27545
+ );
27546
+ if (hasProviderPatch) {
27547
+ const provider = await readActiveMemoryProvider(profileName);
27548
+ if (!provider) {
27549
+ throw new HermesMemoryError(
27550
+ "memory_settings_builtin_only",
27551
+ "\u5F53\u524D Profile \u4F7F\u7528 built-in memory\uFF0C\u6CA1\u6709\u53EF\u4FDD\u5B58\u7684\u5916\u90E8 provider \u8BBE\u7F6E\u3002"
27552
+ );
27553
+ }
27554
+ await saveProviderSettings(profileName, provider, patch);
27555
+ }
27556
+ if (hasLimitPatch) {
27557
+ await patchHermesMemoryLimits(profileName, patch);
26875
27558
  }
26876
- await saveProviderSettings(profileName, provider, patch);
26877
27559
  return readHermesProfileMemory(profileName);
26878
27560
  }
26879
27561
  async function saveHermesMemoryProviderSettings(profileName, provider, patch) {
@@ -26910,9 +27592,9 @@ async function testHindsightProviderSettings(profileName, patch) {
26910
27592
  const mode = normalizeHindsightMode(
26911
27593
  patch.mode ?? config.mode ?? env.HINDSIGHT_MODE
26912
27594
  );
26913
- const apiUrl = readString18(patch.apiUrl) ?? readString18(config.api_url) ?? env.HINDSIGHT_API_URL ?? (mode === "cloud" ? HINDSIGHT_DEFAULT_API_URL : HINDSIGHT_DEFAULT_LOCAL_URL);
26914
- const bankId = readString18(patch.bankId) ?? readString18(config.bank_id) ?? "hermes";
26915
- const apiKey = readString18(patch.apiKey) ?? env.HINDSIGHT_API_KEY ?? readString18(config.apiKey) ?? readString18(config.api_key);
27595
+ const apiUrl = readString19(patch.apiUrl) ?? readString19(config.api_url) ?? env.HINDSIGHT_API_URL ?? (mode === "cloud" ? HINDSIGHT_DEFAULT_API_URL : HINDSIGHT_DEFAULT_LOCAL_URL);
27596
+ const bankId = readString19(patch.bankId) ?? readString19(config.bank_id) ?? "hermes";
27597
+ const apiKey = readString19(patch.apiKey) ?? env.HINDSIGHT_API_KEY ?? readString19(config.apiKey) ?? readString19(config.api_key);
26916
27598
  const baseUrl = normalizeHttpUrl(apiUrl);
26917
27599
  if (!baseUrl) {
26918
27600
  return {
@@ -27044,7 +27726,7 @@ async function saveProviderSettings(profileName, provider, patch) {
27044
27726
  });
27045
27727
  await patchJsonProviderConfig(
27046
27728
  profileName,
27047
- path25.join("hindsight", "config.json"),
27729
+ path26.join("hindsight", "config.json"),
27048
27730
  {
27049
27731
  mode: patch.mode,
27050
27732
  api_url: patch.apiUrl,
@@ -27194,7 +27876,7 @@ async function patchHermesMemoryProvider(profileName, provider) {
27194
27876
  const configPath = resolveHermesConfigPath(profileName);
27195
27877
  const existingRaw = await readFile18(configPath, "utf8").catch(
27196
27878
  (error) => {
27197
- if (isNodeError20(error, "ENOENT")) {
27879
+ if (isNodeError21(error, "ENOENT")) {
27198
27880
  return null;
27199
27881
  }
27200
27882
  throw error;
@@ -27214,14 +27896,46 @@ async function patchHermesMemoryProvider(profileName, provider) {
27214
27896
  document.contents = document.createNode(config);
27215
27897
  await atomicWriteFilePreservingMetadata(configPath, document.toString());
27216
27898
  }
27899
+ async function patchHermesMemoryLimits(profileName, patch) {
27900
+ const configPath = resolveHermesConfigPath(profileName);
27901
+ const existingRaw = await readFile18(configPath, "utf8").catch(
27902
+ (error) => {
27903
+ if (isNodeError21(error, "ENOENT")) {
27904
+ return null;
27905
+ }
27906
+ throw error;
27907
+ }
27908
+ );
27909
+ const document = existingRaw ? YAML6.parseDocument(existingRaw) : new YAML6.Document({});
27910
+ const config = toRecord18(document.toJSON());
27911
+ const memory = toRecord18(config.memory);
27912
+ if (patch.memoryCharLimit !== void 0) {
27913
+ memory.memory_char_limit = patch.memoryCharLimit;
27914
+ }
27915
+ if (patch.userCharLimit !== void 0) {
27916
+ memory.user_char_limit = patch.userCharLimit;
27917
+ }
27918
+ config.memory = memory;
27919
+ if (existingRaw) {
27920
+ await atomicWriteFilePreservingMetadata(
27921
+ `${configPath}.bak.${Date.now()}`,
27922
+ existingRaw,
27923
+ {
27924
+ metadataSourcePath: configPath
27925
+ }
27926
+ );
27927
+ }
27928
+ document.contents = document.createNode(config);
27929
+ await atomicWriteFilePreservingMetadata(configPath, document.toString());
27930
+ }
27217
27931
  function resolveMemoryDir(profileName) {
27218
- return path25.join(resolveHermesProfileDir(profileName), "memories");
27932
+ return path26.join(resolveHermesProfileDir(profileName), "memories");
27219
27933
  }
27220
27934
  async function readMemoryStore(profileName, target, limits) {
27221
27935
  const filePath = memoryFilePath(profileName, target);
27222
27936
  const entries = await readMemoryEntries(filePath);
27223
- const fileStat = await stat18(filePath).catch((error) => {
27224
- if (isNodeError20(error, "ENOENT")) {
27937
+ const fileStat = await stat19(filePath).catch((error) => {
27938
+ if (isNodeError21(error, "ENOENT")) {
27225
27939
  return null;
27226
27940
  }
27227
27941
  throw error;
@@ -27250,7 +27964,7 @@ async function readMemoryStore(profileName, target, limits) {
27250
27964
  }
27251
27965
  async function readMemoryEntries(filePath) {
27252
27966
  const raw = await readFile18(filePath, "utf8").catch((error) => {
27253
- if (isNodeError20(error, "ENOENT")) {
27967
+ if (isNodeError21(error, "ENOENT")) {
27254
27968
  return "";
27255
27969
  }
27256
27970
  throw error;
@@ -27276,7 +27990,7 @@ async function writeMemoryEntries(profileName, target, entries) {
27276
27990
  );
27277
27991
  }
27278
27992
  function memoryFilePath(profileName, target) {
27279
- return path25.join(
27993
+ return path26.join(
27280
27994
  resolveMemoryDir(profileName),
27281
27995
  target === "user" ? "USER.md" : "MEMORY.md"
27282
27996
  );
@@ -27336,7 +28050,7 @@ async function readCustomProviderSetupSummary(profileName) {
27336
28050
  configurable: true,
27337
28051
  configured: true,
27338
28052
  configurationIssue: null,
27339
- providerConfigPath: path25.join(
28053
+ providerConfigPath: path26.join(
27340
28054
  resolveHermesProfileDir(profileName),
27341
28055
  "<provider>.json"
27342
28056
  ),
@@ -27414,7 +28128,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
27414
28128
  const config2 = await readJsonObject(
27415
28129
  memoryProviderConfigPath(profileName, "honcho") ?? ""
27416
28130
  );
27417
- return isConfiguredEnvValue(env.HONCHO_API_KEY) || isConfiguredEnvValue(readString18(config2.apiKey)) || isConfiguredEnvValue(readString18(config2.api_key)) || isConfiguredEnvValue(readString18(config2.baseUrl)) ? { configured: true, issue: null } : {
28131
+ return isConfiguredEnvValue(env.HONCHO_API_KEY) || isConfiguredEnvValue(readString19(config2.apiKey)) || isConfiguredEnvValue(readString19(config2.api_key)) || isConfiguredEnvValue(readString19(config2.baseUrl)) ? { configured: true, issue: null } : {
27418
28132
  configured: false,
27419
28133
  issue: "Honcho \u9700\u8981\u5148\u586B\u5199 API Key\uFF0C\u6216\u5728 honcho.json \u914D\u7F6E self-hosted baseUrl\u3002"
27420
28134
  };
@@ -27423,7 +28137,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
27423
28137
  const config2 = await readJsonObject(
27424
28138
  memoryProviderConfigPath(profileName, "mem0") ?? ""
27425
28139
  );
27426
- return isConfiguredEnvValue(env.MEM0_API_KEY) || isConfiguredEnvValue(readString18(config2.api_key)) ? { configured: true, issue: null } : {
28140
+ return isConfiguredEnvValue(env.MEM0_API_KEY) || isConfiguredEnvValue(readString19(config2.api_key)) ? { configured: true, issue: null } : {
27427
28141
  configured: false,
27428
28142
  issue: "Mem0 \u9700\u8981\u5148\u5728\u672C\u9875\u586B\u5199 API Key\uFF0CLink \u4F1A\u5199\u5165\u5F53\u524D Profile \u7684 .env\u3002"
27429
28143
  };
@@ -27465,7 +28179,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
27465
28179
  memoryProviderConfigPath(profileName, provider) ?? ""
27466
28180
  );
27467
28181
  const mode = normalizeHindsightMode(config.mode ?? env.HINDSIGHT_MODE);
27468
- const apiKey = readString18(config.apiKey) ?? readString18(config.api_key) ?? env.HINDSIGHT_API_KEY;
28182
+ const apiKey = readString19(config.apiKey) ?? readString19(config.api_key) ?? env.HINDSIGHT_API_KEY;
27469
28183
  if (mode === "cloud") {
27470
28184
  return isConfiguredEnvValue(apiKey) ? { configured: true, issue: null } : {
27471
28185
  configured: false,
@@ -27473,15 +28187,15 @@ async function readProviderConfigurationStatus(profileName, provider) {
27473
28187
  };
27474
28188
  }
27475
28189
  if (mode === "local_external") {
27476
- const apiUrl = readString18(config.api_url) ?? env.HINDSIGHT_API_URL ?? HINDSIGHT_DEFAULT_LOCAL_URL;
28190
+ const apiUrl = readString19(config.api_url) ?? env.HINDSIGHT_API_URL ?? HINDSIGHT_DEFAULT_LOCAL_URL;
27477
28191
  return isConfiguredEnvValue(apiUrl) ? { configured: true, issue: null } : {
27478
28192
  configured: false,
27479
28193
  issue: "Hindsight local_external \u9700\u8981\u914D\u7F6E\u53EF\u8BBF\u95EE\u7684 API URL\u3002"
27480
28194
  };
27481
28195
  }
27482
28196
  if (mode === "local_embedded") {
27483
- const llmProvider = readString18(config.llm_provider) ?? "openai";
27484
- const llmModel = readString18(config.llm_model);
28197
+ const llmProvider = readString19(config.llm_provider) ?? "openai";
28198
+ const llmModel = readString19(config.llm_model);
27485
28199
  if (!llmModel) {
27486
28200
  return {
27487
28201
  configured: false,
@@ -27489,7 +28203,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
27489
28203
  };
27490
28204
  }
27491
28205
  if (llmProvider === "openai_compatible" && !isConfiguredEnvValue(
27492
- readString18(config.llm_base_url) ?? env.HINDSIGHT_API_LLM_BASE_URL
28206
+ readString19(config.llm_base_url) ?? env.HINDSIGHT_API_LLM_BASE_URL
27493
28207
  )) {
27494
28208
  return {
27495
28209
  configured: false,
@@ -27497,7 +28211,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
27497
28211
  };
27498
28212
  }
27499
28213
  if (!["ollama", "lmstudio", "openai_compatible"].includes(llmProvider) && !isConfiguredEnvValue(
27500
- readString18(config.llmApiKey) ?? readString18(config.llm_api_key) ?? env.HINDSIGHT_LLM_API_KEY
28214
+ readString19(config.llmApiKey) ?? readString19(config.llm_api_key) ?? env.HINDSIGHT_LLM_API_KEY
27501
28215
  )) {
27502
28216
  return {
27503
28217
  configured: false,
@@ -27553,8 +28267,8 @@ async function readProviderSettings(profileName, provider) {
27553
28267
  secretSetting(
27554
28268
  "apiKey",
27555
28269
  "API Key",
27556
- env.HONCHO_API_KEY ?? readString18(config.apiKey) ?? readString18(config.api_key),
27557
- isConfiguredEnvValue(env.HONCHO_API_KEY) || isConfiguredEnvValue(readString18(config.apiKey)) || isConfiguredEnvValue(readString18(config.api_key))
28270
+ env.HONCHO_API_KEY ?? readString19(config.apiKey) ?? readString19(config.api_key),
28271
+ isConfiguredEnvValue(env.HONCHO_API_KEY) || isConfiguredEnvValue(readString19(config.apiKey)) || isConfiguredEnvValue(readString19(config.api_key))
27558
28272
  ),
27559
28273
  stringSetting("workspace", "Workspace", config.workspace ?? "hermes"),
27560
28274
  stringSetting("peerName", "\u7528\u6237 Peer", config.peerName ?? ""),
@@ -27596,8 +28310,8 @@ async function readProviderSettings(profileName, provider) {
27596
28310
  secretSetting(
27597
28311
  "apiKey",
27598
28312
  "API Key",
27599
- env.MEM0_API_KEY ?? readString18(config.apiKey) ?? readString18(config.api_key),
27600
- isConfiguredEnvValue(env.MEM0_API_KEY) || isConfiguredEnvValue(readString18(config.apiKey)) || isConfiguredEnvValue(readString18(config.api_key))
28313
+ env.MEM0_API_KEY ?? readString19(config.apiKey) ?? readString19(config.api_key),
28314
+ isConfiguredEnvValue(env.MEM0_API_KEY) || isConfiguredEnvValue(readString19(config.apiKey)) || isConfiguredEnvValue(readString19(config.api_key))
27601
28315
  ),
27602
28316
  stringSetting("userId", "User ID", config.user_id ?? "hermes-user"),
27603
28317
  stringSetting("agentId", "Agent ID", config.agent_id ?? "hermes"),
@@ -27682,8 +28396,8 @@ async function readProviderSettings(profileName, provider) {
27682
28396
  secretSetting(
27683
28397
  "apiKey",
27684
28398
  "Hindsight API Key",
27685
- env.HINDSIGHT_API_KEY ?? readString18(config.apiKey) ?? readString18(config.api_key),
27686
- isConfiguredEnvValue(env.HINDSIGHT_API_KEY) || isConfiguredEnvValue(readString18(config.apiKey)) || isConfiguredEnvValue(readString18(config.api_key))
28399
+ env.HINDSIGHT_API_KEY ?? readString19(config.apiKey) ?? readString19(config.api_key),
28400
+ isConfiguredEnvValue(env.HINDSIGHT_API_KEY) || isConfiguredEnvValue(readString19(config.apiKey)) || isConfiguredEnvValue(readString19(config.api_key))
27687
28401
  ),
27688
28402
  stringSetting(
27689
28403
  "bankId",
@@ -27705,8 +28419,8 @@ async function readProviderSettings(profileName, provider) {
27705
28419
  secretSetting(
27706
28420
  "llmApiKey",
27707
28421
  "LLM API Key",
27708
- env.HINDSIGHT_LLM_API_KEY ?? readString18(config.llmApiKey) ?? readString18(config.llm_api_key),
27709
- isConfiguredEnvValue(env.HINDSIGHT_LLM_API_KEY) || isConfiguredEnvValue(readString18(config.llmApiKey)) || isConfiguredEnvValue(readString18(config.llm_api_key))
28422
+ env.HINDSIGHT_LLM_API_KEY ?? readString19(config.llmApiKey) ?? readString19(config.llm_api_key),
28423
+ isConfiguredEnvValue(env.HINDSIGHT_LLM_API_KEY) || isConfiguredEnvValue(readString19(config.llmApiKey)) || isConfiguredEnvValue(readString19(config.llm_api_key))
27710
28424
  ),
27711
28425
  booleanSetting("autoRecall", "\u81EA\u52A8\u56DE\u5FC6", config.auto_recall ?? true),
27712
28426
  booleanSetting("autoRetain", "\u81EA\u52A8\u6C89\u6DC0", config.auto_retain ?? true),
@@ -27730,7 +28444,7 @@ async function readProviderSettings(profileName, provider) {
27730
28444
  stringSetting(
27731
28445
  "dbPath",
27732
28446
  "SQLite \u6570\u636E\u5E93\u8DEF\u5F84",
27733
- config.db_path ?? path25.join(resolveHermesProfileDir(profileName), "memory_store.db")
28447
+ config.db_path ?? path26.join(resolveHermesProfileDir(profileName), "memory_store.db")
27734
28448
  ),
27735
28449
  booleanSetting("autoExtract", "\u4F1A\u8BDD\u7ED3\u675F\u81EA\u52A8\u62BD\u53D6", config.auto_extract ?? false),
27736
28450
  numberSetting("defaultTrust", "\u9ED8\u8BA4\u4FE1\u4EFB\u5206", config.default_trust ?? 0.5),
@@ -27766,7 +28480,7 @@ async function readProviderSettings(profileName, provider) {
27766
28480
  stringSetting(
27767
28481
  "workingDirectory",
27768
28482
  "\u5DE5\u4F5C\u76EE\u5F55",
27769
- path25.join(resolveHermesProfileDir(profileName), "byterover"),
28483
+ path26.join(resolveHermesProfileDir(profileName), "byterover"),
27770
28484
  false
27771
28485
  )
27772
28486
  ];
@@ -27775,16 +28489,16 @@ async function readProviderSettings(profileName, provider) {
27775
28489
  }
27776
28490
  function memoryProviderConfigPath(profileName, provider) {
27777
28491
  if (provider === "honcho") {
27778
- return path25.join(resolveHermesProfileDir(profileName), "honcho.json");
28492
+ return path26.join(resolveHermesProfileDir(profileName), "honcho.json");
27779
28493
  }
27780
28494
  if (provider === "mem0") {
27781
- return path25.join(resolveHermesProfileDir(profileName), "mem0.json");
28495
+ return path26.join(resolveHermesProfileDir(profileName), "mem0.json");
27782
28496
  }
27783
28497
  if (provider === "supermemory") {
27784
- return path25.join(resolveHermesProfileDir(profileName), "supermemory.json");
28498
+ return path26.join(resolveHermesProfileDir(profileName), "supermemory.json");
27785
28499
  }
27786
28500
  if (provider === "hindsight") {
27787
- return path25.join(
28501
+ return path26.join(
27788
28502
  resolveHermesProfileDir(profileName),
27789
28503
  "hindsight",
27790
28504
  "config.json"
@@ -27793,13 +28507,13 @@ function memoryProviderConfigPath(profileName, provider) {
27793
28507
  return null;
27794
28508
  }
27795
28509
  function customProviderConfigPath(profileName, provider) {
27796
- return path25.join(
28510
+ return path26.join(
27797
28511
  resolveHermesProfileDir(profileName),
27798
28512
  `${normalizeCustomProviderId(provider)}.json`
27799
28513
  );
27800
28514
  }
27801
28515
  function customProviderRegistryPath(profileName) {
27802
- return path25.join(
28516
+ return path26.join(
27803
28517
  resolveHermesProfileDir(profileName),
27804
28518
  CUSTOM_PROVIDER_REGISTRY_FILE
27805
28519
  );
@@ -27807,7 +28521,7 @@ function customProviderRegistryPath(profileName) {
27807
28521
  async function readCustomProviderRegistry(profileName) {
27808
28522
  const raw = await readFile18(customProviderRegistryPath(profileName), "utf8").catch(
27809
28523
  (error) => {
27810
- if (isNodeError20(error, "ENOENT")) {
28524
+ if (isNodeError21(error, "ENOENT")) {
27811
28525
  return "";
27812
28526
  }
27813
28527
  throw error;
@@ -27825,11 +28539,11 @@ async function readCustomProviderRegistry(profileName) {
27825
28539
  return { id: id2, label: id2, description: "\u81EA\u5B9A\u4E49 memory provider\u3002" };
27826
28540
  }
27827
28541
  const record = toRecord18(item);
27828
- const id = normalizeCustomProviderId(readString18(record.id) ?? "");
28542
+ const id = normalizeCustomProviderId(readString19(record.id) ?? "");
27829
28543
  return {
27830
28544
  id,
27831
- label: readString18(record.label) ?? id,
27832
- description: readString18(record.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
28545
+ label: readString19(record.label) ?? id,
28546
+ description: readString19(record.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
27833
28547
  };
27834
28548
  }).filter((item) => item.id);
27835
28549
  } catch {
@@ -27851,10 +28565,10 @@ async function saveCustomProviderRegistryEntry(profileName, provider) {
27851
28565
  );
27852
28566
  }
27853
28567
  async function discoverUserMemoryProviderDescriptors(profileName) {
27854
- const pluginsDir = path25.join(resolveHermesProfileDir(profileName), "plugins");
28568
+ const pluginsDir = path26.join(resolveHermesProfileDir(profileName), "plugins");
27855
28569
  const entries = await readdir10(pluginsDir, { withFileTypes: true }).catch(
27856
28570
  (error) => {
27857
- if (isNodeError20(error, "ENOENT")) {
28571
+ if (isNodeError21(error, "ENOENT")) {
27858
28572
  return [];
27859
28573
  }
27860
28574
  throw error;
@@ -27871,21 +28585,21 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
27871
28585
  } catch {
27872
28586
  continue;
27873
28587
  }
27874
- const providerDir = path25.join(pluginsDir, entry.name);
28588
+ const providerDir = path26.join(pluginsDir, entry.name);
27875
28589
  if (!await isMemoryProviderPluginDir(providerDir)) {
27876
28590
  continue;
27877
28591
  }
27878
28592
  const meta = await readPluginMetadata(providerDir);
27879
28593
  descriptors.push({
27880
28594
  id: providerId,
27881
- label: readString18(meta.name) ?? providerId,
27882
- description: readString18(meta.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
28595
+ label: readString19(meta.name) ?? providerId,
28596
+ description: readString19(meta.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
27883
28597
  });
27884
28598
  }
27885
28599
  return descriptors;
27886
28600
  }
27887
28601
  async function isUserMemoryProviderInstalled(profileName, provider) {
27888
- const providerDir = path25.join(
28602
+ const providerDir = path26.join(
27889
28603
  resolveHermesProfileDir(profileName),
27890
28604
  "plugins",
27891
28605
  normalizeCustomProviderId(provider)
@@ -27893,9 +28607,9 @@ async function isUserMemoryProviderInstalled(profileName, provider) {
27893
28607
  return isMemoryProviderPluginDir(providerDir);
27894
28608
  }
27895
28609
  async function isMemoryProviderPluginDir(providerDir) {
27896
- const source = await readFile18(path25.join(providerDir, "__init__.py"), "utf8").catch(
28610
+ const source = await readFile18(path26.join(providerDir, "__init__.py"), "utf8").catch(
27897
28611
  (error) => {
27898
- if (isNodeError20(error, "ENOENT")) {
28612
+ if (isNodeError21(error, "ENOENT")) {
27899
28613
  return "";
27900
28614
  }
27901
28615
  throw error;
@@ -27905,9 +28619,9 @@ async function isMemoryProviderPluginDir(providerDir) {
27905
28619
  return sample.includes("register_memory_provider") || sample.includes("MemoryProvider");
27906
28620
  }
27907
28621
  async function readPluginMetadata(providerDir) {
27908
- const raw = await readFile18(path25.join(providerDir, "plugin.yaml"), "utf8").catch(
28622
+ const raw = await readFile18(path26.join(providerDir, "plugin.yaml"), "utf8").catch(
27909
28623
  (error) => {
27910
- if (isNodeError20(error, "ENOENT")) {
28624
+ if (isNodeError21(error, "ENOENT")) {
27911
28625
  return "";
27912
28626
  }
27913
28627
  throw error;
@@ -27917,10 +28631,10 @@ async function readPluginMetadata(providerDir) {
27917
28631
  }
27918
28632
  async function resolveByteRoverCli() {
27919
28633
  const candidates = [
27920
- ...(process.env.PATH ?? "").split(path25.delimiter).filter(Boolean).map((dir) => path25.join(dir, "brv")),
27921
- path25.join(process.env.HOME ?? "", ".brv-cli", "bin", "brv"),
28634
+ ...(process.env.PATH ?? "").split(path26.delimiter).filter(Boolean).map((dir) => path26.join(dir, "brv")),
28635
+ path26.join(process.env.HOME ?? "", ".brv-cli", "bin", "brv"),
27922
28636
  "/usr/local/bin/brv",
27923
- path25.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
28637
+ path26.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
27924
28638
  ].filter(Boolean);
27925
28639
  for (const candidate of candidates) {
27926
28640
  const found = await access3(candidate).then(() => true).catch(() => false);
@@ -27933,7 +28647,7 @@ async function resolveByteRoverCli() {
27933
28647
  async function readHolographicProviderConfig(profileName) {
27934
28648
  const raw = await readFile18(resolveHermesConfigPath(profileName), "utf8").catch(
27935
28649
  (error) => {
27936
- if (isNodeError20(error, "ENOENT")) {
28650
+ if (isNodeError21(error, "ENOENT")) {
27937
28651
  return "";
27938
28652
  }
27939
28653
  throw error;
@@ -27947,7 +28661,7 @@ async function patchHolographicProviderConfig(profileName, patch) {
27947
28661
  const configPath = resolveHermesConfigPath(profileName);
27948
28662
  const existingRaw = await readFile18(configPath, "utf8").catch(
27949
28663
  (error) => {
27950
- if (isNodeError20(error, "ENOENT")) {
28664
+ if (isNodeError21(error, "ENOENT")) {
27951
28665
  return null;
27952
28666
  }
27953
28667
  throw error;
@@ -27981,9 +28695,9 @@ async function patchHermesMemoryEnv(profileName, patch) {
27981
28695
  if (entries.length === 0) {
27982
28696
  return;
27983
28697
  }
27984
- const envPath = path25.join(resolveHermesProfileDir(profileName), ".env");
28698
+ const envPath = path26.join(resolveHermesProfileDir(profileName), ".env");
27985
28699
  const existingRaw = await readFile18(envPath, "utf8").catch((error) => {
27986
- if (isNodeError20(error, "ENOENT")) {
28700
+ if (isNodeError21(error, "ENOENT")) {
27987
28701
  return "";
27988
28702
  }
27989
28703
  throw error;
@@ -28041,7 +28755,7 @@ function isMemoryEnvKeyWritable(key) {
28041
28755
  ].includes(key);
28042
28756
  }
28043
28757
  function normalizeHindsightMode(value) {
28044
- const mode = readString18(value) ?? "cloud";
28758
+ const mode = readString19(value) ?? "cloud";
28045
28759
  return mode === "local" ? "local_embedded" : mode;
28046
28760
  }
28047
28761
  function normalizeHttpUrl(value) {
@@ -28110,27 +28824,27 @@ function parseJsonObject2(text) {
28110
28824
  }
28111
28825
  }
28112
28826
  function readHindsightError(json) {
28113
- const detail = readString18(json.detail) ?? readString18(json.error);
28827
+ const detail = readString19(json.detail) ?? readString19(json.error);
28114
28828
  return detail ? `\uFF1A${detail}` : "";
28115
28829
  }
28116
28830
  function hindsightSemanticIssue(pathName, json) {
28117
28831
  if (pathName === "/health") {
28118
- const status = readString18(json.status);
28832
+ const status = readString19(json.status);
28119
28833
  return status && ["healthy", "ok"].includes(status.toLowerCase()) ? null : `\u5065\u5EB7\u72B6\u6001\u5F02\u5E38\uFF1A${status ?? "unknown"}`;
28120
28834
  }
28121
28835
  return null;
28122
28836
  }
28123
28837
  function summarizeHindsightProbe(pathName, json) {
28124
28838
  if (pathName === "/health") {
28125
- const status = readString18(json.status) ?? "ok";
28126
- const database = readString18(json.database);
28839
+ const status = readString19(json.status) ?? "ok";
28840
+ const database = readString19(json.database);
28127
28841
  return database ? `${status}, database ${database}` : status;
28128
28842
  }
28129
28843
  if (pathName === "/version") {
28130
- const version = readString18(json.api_version);
28844
+ const version = readString19(json.api_version);
28131
28845
  return version ? `API ${version}` : "version endpoint reachable";
28132
28846
  }
28133
- const bankId = readString18(json.bank_id);
28847
+ const bankId = readString19(json.bank_id);
28134
28848
  return bankId ? `bank ${bankId} reachable` : "bank config reachable";
28135
28849
  }
28136
28850
  async function readActiveMemoryProvider(profileName) {
@@ -28138,21 +28852,21 @@ async function readActiveMemoryProvider(profileName) {
28138
28852
  resolveHermesConfigPath(profileName),
28139
28853
  "utf8"
28140
28854
  ).catch((error) => {
28141
- if (isNodeError20(error, "ENOENT")) {
28855
+ if (isNodeError21(error, "ENOENT")) {
28142
28856
  return "";
28143
28857
  }
28144
28858
  throw error;
28145
28859
  });
28146
28860
  const config = raw ? toRecord18(YAML6.parse(raw)) : {};
28147
28861
  const memory = toRecord18(config.memory);
28148
- const provider = readString18(memory.provider);
28862
+ const provider = readString19(memory.provider);
28149
28863
  if (!provider || provider === "built-in" || provider === "builtin" || provider === "built_in") {
28150
28864
  return null;
28151
28865
  }
28152
28866
  return provider;
28153
28867
  }
28154
28868
  async function patchJsonProviderConfig(profileName, relativePath, patch) {
28155
- const configPath = path25.join(
28869
+ const configPath = path26.join(
28156
28870
  resolveHermesProfileDir(profileName),
28157
28871
  relativePath
28158
28872
  );
@@ -28171,7 +28885,7 @@ async function patchJsonProviderConfig(profileName, relativePath, patch) {
28171
28885
  }
28172
28886
  async function readJsonObject(filePath) {
28173
28887
  const raw = await readFile18(filePath, "utf8").catch((error) => {
28174
- if (isNodeError20(error, "ENOENT")) {
28888
+ if (isNodeError21(error, "ENOENT")) {
28175
28889
  return "{}";
28176
28890
  }
28177
28891
  throw error;
@@ -28181,7 +28895,7 @@ async function readJsonObject(filePath) {
28181
28895
  } catch {
28182
28896
  throw new HermesMemoryError(
28183
28897
  "memory_provider_config_invalid",
28184
- `${path25.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
28898
+ `${path26.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
28185
28899
  );
28186
28900
  }
28187
28901
  }
@@ -28202,7 +28916,7 @@ function stringSetting(key, label, value, editable = true) {
28202
28916
  return {
28203
28917
  key,
28204
28918
  label,
28205
- value: readString18(value) ?? "",
28919
+ value: readString19(value) ?? "",
28206
28920
  editable,
28207
28921
  kind: "string"
28208
28922
  };
@@ -28214,7 +28928,7 @@ function secretSetting(key, label, value, configured) {
28214
28928
  value: "",
28215
28929
  editable: true,
28216
28930
  kind: "secret",
28217
- configured: configured || isConfiguredEnvValue(readString18(value))
28931
+ configured: configured || isConfiguredEnvValue(readString19(value))
28218
28932
  };
28219
28933
  }
28220
28934
  function textSetting(key, label, value, editable = true) {
@@ -28227,7 +28941,7 @@ function textSetting(key, label, value, editable = true) {
28227
28941
  };
28228
28942
  }
28229
28943
  function selectSetting(key, label, value, options, editable = true) {
28230
- const stringValue = readString18(value) ?? options[0] ?? null;
28944
+ const stringValue = readString19(value) ?? options[0] ?? null;
28231
28945
  return { key, label, value: stringValue, editable, kind: "select", options };
28232
28946
  }
28233
28947
  async function readMemoryLimits(profileName) {
@@ -28235,7 +28949,7 @@ async function readMemoryLimits(profileName) {
28235
28949
  resolveHermesConfigPath(profileName),
28236
28950
  "utf8"
28237
28951
  ).catch((error) => {
28238
- if (isNodeError20(error, "ENOENT")) {
28952
+ if (isNodeError21(error, "ENOENT")) {
28239
28953
  return "";
28240
28954
  }
28241
28955
  throw error;
@@ -28300,7 +29014,7 @@ function hashString(value) {
28300
29014
  function toRecord18(value) {
28301
29015
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
28302
29016
  }
28303
- function readString18(value) {
29017
+ function readString19(value) {
28304
29018
  return typeof value === "string" && value.trim() ? value.trim() : null;
28305
29019
  }
28306
29020
  function readPositiveInteger3(value) {
@@ -28328,7 +29042,7 @@ function formatEnvValue3(value) {
28328
29042
  function escapeRegExp4(value) {
28329
29043
  return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
28330
29044
  }
28331
- function isNodeError20(error, code) {
29045
+ function isNodeError21(error, code) {
28332
29046
  return error instanceof Error && "code" in error && error.code === code;
28333
29047
  }
28334
29048
 
@@ -28404,7 +29118,7 @@ function registerProfileMemoryRoutes(router, options) {
28404
29118
  try {
28405
29119
  ctx.body = await saveHermesMemorySettings(
28406
29120
  ctx.params.name,
28407
- readMemorySettingsPatch(body)
29121
+ readMemorySettingsPatch(body, { includeLimits: true })
28408
29122
  );
28409
29123
  } catch (error) {
28410
29124
  throw toMemoryHttpError(error);
@@ -28459,7 +29173,7 @@ function registerProfileMemoryRoutes(router, options) {
28459
29173
  );
28460
29174
  }
28461
29175
  function readMemoryTarget(body) {
28462
- const raw = readString17(body, "target");
29176
+ const raw = readString18(body, "target");
28463
29177
  if (raw === "memory" || raw === "user") {
28464
29178
  return raw;
28465
29179
  }
@@ -28470,7 +29184,7 @@ function readMemoryTarget(body) {
28470
29184
  );
28471
29185
  }
28472
29186
  function readMemoryResetTarget(body) {
28473
- const raw = readString17(body, "target") ?? "all";
29187
+ const raw = readString18(body, "target") ?? "all";
28474
29188
  if (raw === "all" || raw === "memory" || raw === "user") {
28475
29189
  return raw;
28476
29190
  }
@@ -28481,7 +29195,7 @@ function readMemoryResetTarget(body) {
28481
29195
  );
28482
29196
  }
28483
29197
  function readRequiredMemoryContent(body) {
28484
- const content = readString17(body, "content") ?? readString17(body, "text");
29198
+ const content = readString18(body, "content") ?? readString18(body, "text");
28485
29199
  if (!content) {
28486
29200
  throw new LinkHttpError(
28487
29201
  400,
@@ -28492,7 +29206,7 @@ function readRequiredMemoryContent(body) {
28492
29206
  return content;
28493
29207
  }
28494
29208
  function readRequiredMemoryMatch(body) {
28495
- const oldText = readString17(body, "old_text") ?? readString17(body, "oldText") ?? readString17(body, "match");
29209
+ const oldText = readString18(body, "old_text") ?? readString18(body, "oldText") ?? readString18(body, "match");
28496
29210
  if (!oldText) {
28497
29211
  throw new LinkHttpError(
28498
29212
  400,
@@ -28503,7 +29217,7 @@ function readRequiredMemoryMatch(body) {
28503
29217
  return oldText;
28504
29218
  }
28505
29219
  function readRequiredMemoryProvider(body) {
28506
- const provider = readString17(body, "provider") ?? readString17(body, "provider_id") ?? readString17(body, "providerId");
29220
+ const provider = readString18(body, "provider") ?? readString18(body, "provider_id") ?? readString18(body, "providerId");
28507
29221
  if (!provider) {
28508
29222
  throw new LinkHttpError(
28509
29223
  400,
@@ -28513,9 +29227,27 @@ function readRequiredMemoryProvider(body) {
28513
29227
  }
28514
29228
  return provider;
28515
29229
  }
28516
- function readMemorySettingsPatch(body) {
29230
+ function readMemorySettingsPatch(body, options = {}) {
28517
29231
  const input = {};
28518
- const mode = readString17(body, "mode");
29232
+ if (options.includeLimits) {
29233
+ const memoryCharLimit = readOptionalPositiveInteger(
29234
+ body,
29235
+ "memory_char_limit",
29236
+ "memoryCharLimit"
29237
+ );
29238
+ if (memoryCharLimit !== void 0) {
29239
+ input.memoryCharLimit = memoryCharLimit;
29240
+ }
29241
+ const userCharLimit = readOptionalPositiveInteger(
29242
+ body,
29243
+ "user_char_limit",
29244
+ "userCharLimit"
29245
+ );
29246
+ if (userCharLimit !== void 0) {
29247
+ input.userCharLimit = userCharLimit;
29248
+ }
29249
+ }
29250
+ const mode = readString18(body, "mode");
28519
29251
  if (mode) {
28520
29252
  input.mode = mode;
28521
29253
  }
@@ -28531,7 +29263,7 @@ function readMemorySettingsPatch(body) {
28531
29263
  if (bankId !== void 0) {
28532
29264
  input.bankId = bankId;
28533
29265
  }
28534
- const llmProvider = readString17(body, "llm_provider") ?? readString17(body, "llmProvider");
29266
+ const llmProvider = readString18(body, "llm_provider") ?? readString18(body, "llmProvider");
28535
29267
  if (llmProvider) {
28536
29268
  input.llmProvider = llmProvider;
28537
29269
  }
@@ -28567,11 +29299,11 @@ function readMemorySettingsPatch(body) {
28567
29299
  if (autoRetain !== void 0) {
28568
29300
  input.autoRetain = autoRetain;
28569
29301
  }
28570
- const memoryMode = readString17(body, "memory_mode") ?? readString17(body, "memoryMode");
29302
+ const memoryMode = readString18(body, "memory_mode") ?? readString18(body, "memoryMode");
28571
29303
  if (memoryMode) {
28572
29304
  input.memoryMode = memoryMode;
28573
29305
  }
28574
- const recallBudget = readString17(body, "recall_budget") ?? readString17(body, "recallBudget");
29306
+ const recallBudget = readString18(body, "recall_budget") ?? readString18(body, "recallBudget");
28575
29307
  if (recallBudget) {
28576
29308
  input.recallBudget = recallBudget;
28577
29309
  }
@@ -28587,11 +29319,11 @@ function readMemorySettingsPatch(body) {
28587
29319
  if (profileFrequency !== void 0) {
28588
29320
  input.profileFrequency = profileFrequency;
28589
29321
  }
28590
- const captureMode = readString17(body, "capture_mode") ?? readString17(body, "captureMode");
29322
+ const captureMode = readString18(body, "capture_mode") ?? readString18(body, "captureMode");
28591
29323
  if (captureMode) {
28592
29324
  input.captureMode = captureMode;
28593
29325
  }
28594
- const searchMode = readString17(body, "search_mode") ?? readString17(body, "searchMode");
29326
+ const searchMode = readString18(body, "search_mode") ?? readString18(body, "searchMode");
28595
29327
  if (searchMode) {
28596
29328
  input.searchMode = searchMode;
28597
29329
  }
@@ -28625,11 +29357,11 @@ function readMemorySettingsPatch(body) {
28625
29357
  if (aiPeer !== void 0) {
28626
29358
  input.aiPeer = aiPeer;
28627
29359
  }
28628
- const recallMode = readString17(body, "recall_mode") ?? readString17(body, "recallMode");
29360
+ const recallMode = readString18(body, "recall_mode") ?? readString18(body, "recallMode");
28629
29361
  if (recallMode) {
28630
29362
  input.recallMode = recallMode;
28631
29363
  }
28632
- const writeFrequency = readString17(body, "write_frequency") ?? readString17(body, "writeFrequency");
29364
+ const writeFrequency = readString18(body, "write_frequency") ?? readString18(body, "writeFrequency");
28633
29365
  if (writeFrequency) {
28634
29366
  input.writeFrequency = writeFrequency;
28635
29367
  }
@@ -28637,7 +29369,7 @@ function readMemorySettingsPatch(body) {
28637
29369
  if (saveMessages !== void 0) {
28638
29370
  input.saveMessages = saveMessages;
28639
29371
  }
28640
- const sessionStrategy = readString17(body, "session_strategy") ?? readString17(body, "sessionStrategy");
29372
+ const sessionStrategy = readString18(body, "session_strategy") ?? readString18(body, "sessionStrategy");
28641
29373
  if (sessionStrategy) {
28642
29374
  input.sessionStrategy = sessionStrategy;
28643
29375
  }
@@ -28735,6 +29467,23 @@ function readOptionalNumber(value, key) {
28735
29467
  }
28736
29468
  return numberValue;
28737
29469
  }
29470
+ function readOptionalPositiveInteger(body, ...keys) {
29471
+ for (const key of keys) {
29472
+ if (!Object.prototype.hasOwnProperty.call(body, key)) {
29473
+ continue;
29474
+ }
29475
+ const value = readPositiveInteger2(body[key]);
29476
+ if (value === void 0) {
29477
+ throw new LinkHttpError(
29478
+ 400,
29479
+ "memory_settings_invalid",
29480
+ `${key} must be a positive integer`
29481
+ );
29482
+ }
29483
+ return value;
29484
+ }
29485
+ return void 0;
29486
+ }
28738
29487
  function readOptionalString(body, ...keys) {
28739
29488
  for (const key of keys) {
28740
29489
  if (Object.prototype.hasOwnProperty.call(body, key)) {
@@ -28770,7 +29519,7 @@ function toMemoryHttpError(error) {
28770
29519
 
28771
29520
  // src/hermes/skills.ts
28772
29521
  import { readFile as readFile19, readdir as readdir11 } from "fs/promises";
28773
- import path26 from "path";
29522
+ import path27 from "path";
28774
29523
  import YAML7 from "yaml";
28775
29524
  var HermesSkillNotFoundError = class extends Error {
28776
29525
  constructor(skillName) {
@@ -28784,7 +29533,7 @@ var EXCLUDED_SKILL_DIRS = /* @__PURE__ */ new Set([".git", ".github", ".hub"]);
28784
29533
  async function listHermesProfileSkills(profileName, paths = resolveRuntimePaths()) {
28785
29534
  const profile = await readExistingProfile(profileName, paths);
28786
29535
  const profileDir = resolveHermesProfileDir(profile.name);
28787
- const skillsRoot = path26.join(profileDir, "skills");
29536
+ const skillsRoot = path27.join(profileDir, "skills");
28788
29537
  const [skillFiles, disabled, provenance] = await Promise.all([
28789
29538
  findSkillFiles(skillsRoot),
28790
29539
  readDisabledSkillNames(resolveHermesConfigPath(profile.name)),
@@ -28863,7 +29612,7 @@ async function findSkillFiles(root) {
28863
29612
  async function collectSkillFiles(directory, results) {
28864
29613
  const entries = await readdir11(directory, { withFileTypes: true }).catch(
28865
29614
  (error) => {
28866
- if (isNodeError21(error, "ENOENT")) {
29615
+ if (isNodeError22(error, "ENOENT")) {
28867
29616
  return [];
28868
29617
  }
28869
29618
  throw error;
@@ -28875,7 +29624,7 @@ async function collectSkillFiles(directory, results) {
28875
29624
  if (EXCLUDED_SKILL_DIRS.has(entry.name)) {
28876
29625
  continue;
28877
29626
  }
28878
- const entryPath = path26.join(directory, entry.name);
29627
+ const entryPath = path27.join(directory, entry.name);
28879
29628
  if (entry.isDirectory()) {
28880
29629
  await collectSkillFiles(entryPath, results);
28881
29630
  continue;
@@ -28888,7 +29637,7 @@ async function collectSkillFiles(directory, results) {
28888
29637
  async function readSkillMetadata(input) {
28889
29638
  const raw = await readFile19(input.skillFile, "utf8").catch(
28890
29639
  (error) => {
28891
- if (isNodeError21(error, "ENOENT") || isNodeError21(error, "EACCES")) {
29640
+ if (isNodeError22(error, "ENOENT") || isNodeError22(error, "EACCES")) {
28892
29641
  return null;
28893
29642
  }
28894
29643
  throw error;
@@ -28897,16 +29646,16 @@ async function readSkillMetadata(input) {
28897
29646
  if (raw === null) {
28898
29647
  return null;
28899
29648
  }
28900
- const skillDir = path26.dirname(input.skillFile);
29649
+ const skillDir = path27.dirname(input.skillFile);
28901
29650
  const { frontmatter, body } = parseSkillDocument(raw.slice(0, 4e3));
28902
29651
  const name = normalizeSkillName(
28903
- readString19(frontmatter.name) ?? path26.basename(skillDir)
29652
+ readString20(frontmatter.name) ?? path27.basename(skillDir)
28904
29653
  );
28905
29654
  if (!name) {
28906
29655
  return null;
28907
29656
  }
28908
29657
  const description = normalizeDescription(
28909
- readString19(frontmatter.description) ?? firstBodyDescription(body)
29658
+ readString20(frontmatter.description) ?? firstBodyDescription(body)
28910
29659
  );
28911
29660
  const provenance = input.provenance.get(name) ?? {
28912
29661
  source: "local",
@@ -28919,7 +29668,7 @@ async function readSkillMetadata(input) {
28919
29668
  enabled: !input.disabled.has(name),
28920
29669
  source: provenance.source,
28921
29670
  trust: provenance.trust,
28922
- relativePath: path26.relative(input.skillsRoot, skillDir)
29671
+ relativePath: path27.relative(input.skillsRoot, skillDir)
28923
29672
  };
28924
29673
  }
28925
29674
  function parseSkillDocument(raw) {
@@ -28940,8 +29689,8 @@ function parseSkillDocument(raw) {
28940
29689
  }
28941
29690
  }
28942
29691
  function categoryFromPath(skillsRoot, skillFile) {
28943
- const relative = path26.relative(skillsRoot, skillFile);
28944
- const parts = relative.split(path26.sep).filter(Boolean);
29692
+ const relative = path27.relative(skillsRoot, skillFile);
29693
+ const parts = relative.split(path27.sep).filter(Boolean);
28945
29694
  return parts.length >= 3 ? parts[0] : null;
28946
29695
  }
28947
29696
  function firstBodyDescription(body) {
@@ -28965,7 +29714,7 @@ function normalizeDescription(value) {
28965
29714
  }
28966
29715
  async function readDisabledSkillNames(configPath) {
28967
29716
  const raw = await readFile19(configPath, "utf8").catch((error) => {
28968
- if (isNodeError21(error, "ENOENT")) {
29717
+ if (isNodeError22(error, "ENOENT")) {
28969
29718
  return "";
28970
29719
  }
28971
29720
  throw error;
@@ -28988,9 +29737,9 @@ async function readSkillProvenance(root) {
28988
29737
  return provenance;
28989
29738
  }
28990
29739
  async function readBundledSkillNames(root) {
28991
- const raw = await readFile19(path26.join(root, ".bundled_manifest"), "utf8").catch(
29740
+ const raw = await readFile19(path27.join(root, ".bundled_manifest"), "utf8").catch(
28992
29741
  (error) => {
28993
- if (isNodeError21(error, "ENOENT")) {
29742
+ if (isNodeError22(error, "ENOENT")) {
28994
29743
  return "";
28995
29744
  }
28996
29745
  throw error;
@@ -29011,9 +29760,9 @@ async function readBundledSkillNames(root) {
29011
29760
  return names;
29012
29761
  }
29013
29762
  async function readHubInstalledSkills(root) {
29014
- const raw = await readFile19(path26.join(root, ".hub", "lock.json"), "utf8").catch(
29763
+ const raw = await readFile19(path27.join(root, ".hub", "lock.json"), "utf8").catch(
29015
29764
  (error) => {
29016
- if (isNodeError21(error, "ENOENT")) {
29765
+ if (isNodeError22(error, "ENOENT")) {
29017
29766
  return "";
29018
29767
  }
29019
29768
  throw error;
@@ -29033,8 +29782,8 @@ async function readHubInstalledSkills(root) {
29033
29782
  for (const [name, rawEntry] of Object.entries(installed2)) {
29034
29783
  const entry = toRecord19(rawEntry);
29035
29784
  result.set(normalizeSkillName(name), {
29036
- source: readString19(entry.source) ?? "hub",
29037
- trust: readString19(entry.trust_level) ?? null
29785
+ source: readString20(entry.source) ?? "hub",
29786
+ trust: readString20(entry.trust_level) ?? null
29038
29787
  });
29039
29788
  }
29040
29789
  return result;
@@ -29085,7 +29834,7 @@ function compareCategoryNames(left, right) {
29085
29834
  async function readHermesConfigDocument4(configPath) {
29086
29835
  const existingRaw = await readFile19(configPath, "utf8").catch(
29087
29836
  (error) => {
29088
- if (isNodeError21(error, "ENOENT")) {
29837
+ if (isNodeError22(error, "ENOENT")) {
29089
29838
  return null;
29090
29839
  }
29091
29840
  throw error;
@@ -29118,7 +29867,7 @@ function readStringList4(value) {
29118
29867
  }
29119
29868
  return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
29120
29869
  }
29121
- function readString19(value) {
29870
+ function readString20(value) {
29122
29871
  return typeof value === "string" && value.trim() ? value.trim() : null;
29123
29872
  }
29124
29873
  function toRecord19(value) {
@@ -29132,7 +29881,7 @@ function ensureRecord5(target, key) {
29132
29881
  target[key] = current;
29133
29882
  return current;
29134
29883
  }
29135
- function isNodeError21(error, code) {
29884
+ function isNodeError22(error, code) {
29136
29885
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
29137
29886
  }
29138
29887
 
@@ -29489,7 +30238,7 @@ function registerRunRoutes(router, options) {
29489
30238
  router.post("/api/v1/runs", async (ctx) => {
29490
30239
  await authenticateRequest(ctx, paths);
29491
30240
  const body = await readJsonBody(ctx.req);
29492
- const input = readString17(body, "input");
30241
+ const input = readString18(body, "input");
29493
30242
  if (!input) {
29494
30243
  throw new LinkHttpError(400, "run_input_required", "input is required");
29495
30244
  }
@@ -29497,12 +30246,12 @@ function registerRunRoutes(router, options) {
29497
30246
  ctx.body = await createHermesRun(
29498
30247
  {
29499
30248
  input,
29500
- instructions: readString17(body, "instructions") ?? void 0,
30249
+ instructions: readString18(body, "instructions") ?? void 0,
29501
30250
  conversation_history: readConversationHistory(
29502
30251
  body.conversation_history ?? body.conversationHistory
29503
30252
  ),
29504
- session_id: readString17(body, "session_id") ?? readString17(body, "sessionId") ?? void 0,
29505
- session_key: readString17(body, "session_key") ?? readString17(body, "sessionKey") ?? void 0
30253
+ session_id: readString18(body, "session_id") ?? readString18(body, "sessionId") ?? void 0,
30254
+ session_key: readString18(body, "session_key") ?? readString18(body, "sessionKey") ?? void 0
29506
30255
  },
29507
30256
  { logger, profileName: readOptionalProfileName(body) }
29508
30257
  );
@@ -29673,8 +30422,8 @@ function readModelList(payload) {
29673
30422
  // src/hermes/updates.ts
29674
30423
  import { EventEmitter as EventEmitter3 } from "events";
29675
30424
  import { spawn as spawn3 } from "child_process";
29676
- import { mkdir as mkdir12, readFile as readFile20, rm as rm7 } from "fs/promises";
29677
- import path27 from "path";
30425
+ import { mkdir as mkdir13, readFile as readFile20, rm as rm8 } from "fs/promises";
30426
+ import path28 from "path";
29678
30427
  var SERVER_HERMES_RELEASES_LATEST_PATH = "/api/v1/hermes-agent/releases/latest";
29679
30428
  var RELEASE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
29680
30429
  var RELEASE_FETCH_TIMEOUT_MS = 5e3;
@@ -29737,7 +30486,7 @@ async function startHermesUpdate(options) {
29737
30486
  signal: null,
29738
30487
  error: null
29739
30488
  };
29740
- await mkdir12(options.paths.runDir, { recursive: true, mode: 448 });
30489
+ await mkdir13(options.paths.runDir, { recursive: true, mode: 448 });
29741
30490
  await writer.write(`
29742
30491
  === hermes update started ${startedAt} ===
29743
30492
  `);
@@ -29911,20 +30660,20 @@ function normalizeServerReleaseSnapshot(payload) {
29911
30660
  const remote = toNullableRecord(snapshot.remote);
29912
30661
  return {
29913
30662
  remote: remote ? normalizeServerRelease(remote) : null,
29914
- cacheState: readString20(snapshot, "cache_state") ?? readString20(snapshot, "cacheState"),
29915
- issue: readString20(snapshot, "issue")
30663
+ cacheState: readString21(snapshot, "cache_state") ?? readString21(snapshot, "cacheState"),
30664
+ issue: readString21(snapshot, "issue")
29916
30665
  };
29917
30666
  }
29918
30667
  function normalizeServerRelease(payload) {
29919
- const tag = readString20(payload, "tag");
29920
- const name = readString20(payload, "name");
30668
+ const tag = readString21(payload, "tag");
30669
+ const name = readString21(payload, "name");
29921
30670
  return {
29922
- version: readString20(payload, "version") ?? extractSemver(name) ?? extractTagSemver(tag),
30671
+ version: readString21(payload, "version") ?? extractSemver(name) ?? extractTagSemver(tag),
29923
30672
  tag,
29924
30673
  name,
29925
- releaseUrl: readString20(payload, "releaseUrl") ?? readString20(payload, "release_url"),
29926
- publishedAt: readString20(payload, "publishedAt") ?? readString20(payload, "published_at"),
29927
- fetchedAt: readString20(payload, "fetchedAt") ?? readString20(payload, "fetched_at") ?? (/* @__PURE__ */ new Date()).toISOString()
30674
+ releaseUrl: readString21(payload, "releaseUrl") ?? readString21(payload, "release_url"),
30675
+ publishedAt: readString21(payload, "publishedAt") ?? readString21(payload, "published_at"),
30676
+ fetchedAt: readString21(payload, "fetchedAt") ?? readString21(payload, "fetched_at") ?? (/* @__PURE__ */ new Date()).toISOString()
29928
30677
  };
29929
30678
  }
29930
30679
  async function readReleaseCache(paths) {
@@ -29952,21 +30701,21 @@ async function readUpdateLogLines(paths) {
29952
30701
  );
29953
30702
  }
29954
30703
  function releaseCachePath(paths) {
29955
- return path27.join(paths.indexesDir, "hermes-release-check.json");
30704
+ return path28.join(paths.indexesDir, "hermes-release-check.json");
29956
30705
  }
29957
30706
  function updateStatePath(paths) {
29958
- return path27.join(paths.runDir, "hermes-update-state.json");
30707
+ return path28.join(paths.runDir, "hermes-update-state.json");
29959
30708
  }
29960
30709
  function updateLogPath(paths) {
29961
- return path27.join(paths.logsDir, UPDATE_LOG_FILE);
30710
+ return path28.join(paths.logsDir, UPDATE_LOG_FILE);
29962
30711
  }
29963
30712
  async function clearUpdateLogFiles(paths) {
29964
30713
  const primary = updateLogPath(paths);
29965
30714
  await Promise.all([
29966
- rm7(primary, { force: true }).catch(() => void 0),
30715
+ rm8(primary, { force: true }).catch(() => void 0),
29967
30716
  ...Array.from(
29968
30717
  { length: UPDATE_LOG_MAX_FILES },
29969
- (_, index) => rm7(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
30718
+ (_, index) => rm8(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
29970
30719
  )
29971
30720
  ]);
29972
30721
  }
@@ -30050,7 +30799,7 @@ function isRecentRunningState2(state) {
30050
30799
  const startedAt = Date.parse(state.started_at);
30051
30800
  return Number.isFinite(startedAt) && Date.now() - startedAt < 3e4;
30052
30801
  }
30053
- function readString20(payload, key) {
30802
+ function readString21(payload, key) {
30054
30803
  const value = payload[key];
30055
30804
  return typeof value === "string" && value.trim() ? value.trim() : null;
30056
30805
  }
@@ -30058,17 +30807,17 @@ function readString20(payload, key) {
30058
30807
  // src/link/updates.ts
30059
30808
  import { spawn as spawn5 } from "child_process";
30060
30809
  import { EventEmitter as EventEmitter4 } from "events";
30061
- import { mkdir as mkdir15, readFile as readFile22, rm as rm10 } from "fs/promises";
30062
- import path29 from "path";
30810
+ import { mkdir as mkdir16, readFile as readFile22, rm as rm11 } from "fs/promises";
30811
+ import path30 from "path";
30063
30812
 
30064
30813
  // src/daemon/process.ts
30065
30814
  import { spawn as spawn4 } from "child_process";
30066
- import { mkdir as mkdir14, readFile as readFile21, rm as rm9, writeFile as writeFile4 } from "fs/promises";
30067
- import path28 from "path";
30815
+ import { mkdir as mkdir15, readFile as readFile21, rm as rm10, writeFile as writeFile4 } from "fs/promises";
30816
+ import path29 from "path";
30068
30817
 
30069
30818
  // src/daemon/service.ts
30070
30819
  import { createServer } from "http";
30071
- import { mkdir as mkdir13, rm as rm8, writeFile as writeFile3 } from "fs/promises";
30820
+ import { mkdir as mkdir14, rm as rm9, writeFile as writeFile3 } from "fs/promises";
30072
30821
 
30073
30822
  // src/relay/control-client.ts
30074
30823
  import WebSocket from "ws";
@@ -31113,11 +31862,11 @@ async function mergeLastReportedPublicRoutes(paths, snapshotInput) {
31113
31862
  const state = await readNetworkReportState(paths);
31114
31863
  return {
31115
31864
  ...snapshotInput,
31116
- publicIpv4s: uniqueStrings([
31865
+ publicIpv4s: uniqueStrings2([
31117
31866
  ...snapshotInput.publicIpv4s,
31118
31867
  ...state.lastReportedPublicIpv4s
31119
31868
  ]).slice(0, 2),
31120
- publicIpv6s: uniqueStrings([
31869
+ publicIpv6s: uniqueStrings2([
31121
31870
  ...snapshotInput.publicIpv6s,
31122
31871
  ...state.lastReportedPublicIpv6s
31123
31872
  ]).slice(0, 2)
@@ -31201,15 +31950,15 @@ function normalizeLanIps(value) {
31201
31950
  ];
31202
31951
  }
31203
31952
  function sameNetworkSnapshot(left, right) {
31204
- return sameStringList(left.lanIps, right.lanIps) && sameStringList(left.publicIpv4s, right.publicIpv4s) && sameStringList(left.publicIpv6s, right.publicIpv6s);
31953
+ return sameStringList2(left.lanIps, right.lanIps) && sameStringList2(left.publicIpv4s, right.publicIpv4s) && sameStringList2(left.publicIpv6s, right.publicIpv6s);
31205
31954
  }
31206
- function sameStringList(left, right) {
31955
+ function sameStringList2(left, right) {
31207
31956
  if (left.length !== right.length) {
31208
31957
  return false;
31209
31958
  }
31210
31959
  return left.every((value, index) => value === right[index]);
31211
31960
  }
31212
- function uniqueStrings(values) {
31961
+ function uniqueStrings2(values) {
31213
31962
  return [...new Set(values)];
31214
31963
  }
31215
31964
  function formatUtcDay(date) {
@@ -31844,7 +32593,7 @@ async function startLinkService(options = {}) {
31844
32593
  await logger.info("service_stopped");
31845
32594
  await logger.flush();
31846
32595
  if (options.writePidFile) {
31847
- await rm8(pidFilePath(paths), { force: true }).catch(() => void 0);
32596
+ await rm9(pidFilePath(paths), { force: true }).catch(() => void 0);
31848
32597
  }
31849
32598
  }
31850
32599
  };
@@ -31894,7 +32643,7 @@ function pidFilePath(paths = resolveRuntimePaths()) {
31894
32643
  return `${paths.runDir}/hermeslink.pid`;
31895
32644
  }
31896
32645
  async function writePidFile(paths) {
31897
- await mkdir13(paths.runDir, { recursive: true, mode: 448 });
32646
+ await mkdir14(paths.runDir, { recursive: true, mode: 448 });
31898
32647
  await writeFile3(pidFilePath(paths), `${process.pid}
31899
32648
  `, { mode: 384 });
31900
32649
  }
@@ -31989,8 +32738,8 @@ async function startDaemonProcess(paths = resolveRuntimePaths()) {
31989
32738
  return status;
31990
32739
  }
31991
32740
  }
31992
- await mkdir14(paths.logsDir, { recursive: true, mode: 448 });
31993
- await mkdir14(paths.runDir, { recursive: true, mode: 448 });
32741
+ await mkdir15(paths.logsDir, { recursive: true, mode: 448 });
32742
+ await mkdir15(paths.runDir, { recursive: true, mode: 448 });
31994
32743
  const scriptPath = currentCliScriptPath();
31995
32744
  const child = spawn4(process.execPath, [scriptPath, "daemon-supervisor"], {
31996
32745
  detached: true,
@@ -32008,10 +32757,10 @@ async function startDaemonProcess(paths = resolveRuntimePaths()) {
32008
32757
  return await getDaemonStatus(paths);
32009
32758
  }
32010
32759
  async function runDaemonSupervisor(paths = resolveRuntimePaths()) {
32011
- await mkdir14(paths.logsDir, { recursive: true, mode: 448 });
32760
+ await mkdir15(paths.logsDir, { recursive: true, mode: 448 });
32012
32761
  const log = createRotatingTextLogWriter({
32013
32762
  paths,
32014
- fileName: path28.basename(daemonLogFile(paths))
32763
+ fileName: path29.basename(daemonLogFile(paths))
32015
32764
  });
32016
32765
  const scriptPath = currentCliScriptPath();
32017
32766
  const write = (chunk) => {
@@ -32138,7 +32887,7 @@ async function stopDaemonProcess(paths = resolveRuntimePaths()) {
32138
32887
  try {
32139
32888
  process.kill(status.pid, "SIGTERM");
32140
32889
  } catch {
32141
- await rm9(pidFilePath(paths), { force: true }).catch(() => void 0);
32890
+ await rm10(pidFilePath(paths), { force: true }).catch(() => void 0);
32142
32891
  return await getDaemonStatus(paths);
32143
32892
  }
32144
32893
  for (let index = 0; index < 20; index += 1) {
@@ -32160,7 +32909,7 @@ async function stopDaemonProcess(paths = resolveRuntimePaths()) {
32160
32909
  }
32161
32910
  }
32162
32911
  if (!isProcessAlive3(status.pid) || !await pidBackedServiceIsReachable(paths)) {
32163
- await rm9(pidFilePath(paths), { force: true }).catch(() => void 0);
32912
+ await rm10(pidFilePath(paths), { force: true }).catch(() => void 0);
32164
32913
  }
32165
32914
  return await getDaemonStatus(paths);
32166
32915
  }
@@ -32168,7 +32917,7 @@ async function getDaemonStatus(paths = resolveRuntimePaths()) {
32168
32917
  const pidFile = pidFilePath(paths);
32169
32918
  const pid = await readPid(pidFile);
32170
32919
  if (pid && !isProcessAlive3(pid)) {
32171
- await rm9(pidFile, { force: true }).catch(() => void 0);
32920
+ await rm10(pidFile, { force: true }).catch(() => void 0);
32172
32921
  return {
32173
32922
  running: false,
32174
32923
  pid: null,
@@ -32309,10 +33058,10 @@ function terminateChild(child, previousForceKillTimer) {
32309
33058
  }
32310
33059
  }
32311
33060
  function supervisorStopIntentPath(paths) {
32312
- return path28.join(paths.runDir, "supervisor-stop-intent.json");
33061
+ return path29.join(paths.runDir, "supervisor-stop-intent.json");
32313
33062
  }
32314
33063
  async function writeSupervisorStopIntent(paths, pid) {
32315
- await mkdir14(paths.runDir, { recursive: true, mode: 448 });
33064
+ await mkdir15(paths.runDir, { recursive: true, mode: 448 });
32316
33065
  await writeFile4(
32317
33066
  supervisorStopIntentPath(paths),
32318
33067
  `${JSON.stringify({ pid, created_at: (/* @__PURE__ */ new Date()).toISOString() })}
@@ -32328,14 +33077,14 @@ async function consumeSupervisorStopIntent(paths, pid) {
32328
33077
  }
32329
33078
  const payload = parseSupervisorStopIntent(raw);
32330
33079
  if (!isValidSupervisorStopIntent(payload)) {
32331
- await rm9(filePath, { force: true }).catch(() => void 0);
33080
+ await rm10(filePath, { force: true }).catch(() => void 0);
32332
33081
  return false;
32333
33082
  }
32334
33083
  if (payload.pid !== pid) {
32335
- await rm9(filePath, { force: true }).catch(() => void 0);
33084
+ await rm10(filePath, { force: true }).catch(() => void 0);
32336
33085
  return false;
32337
33086
  }
32338
- await rm9(filePath, { force: true }).catch(() => void 0);
33087
+ await rm10(filePath, { force: true }).catch(() => void 0);
32339
33088
  return true;
32340
33089
  }
32341
33090
  async function clearExpiredSupervisorStopIntent(paths) {
@@ -32346,7 +33095,7 @@ async function clearExpiredSupervisorStopIntent(paths) {
32346
33095
  }
32347
33096
  const payload = parseSupervisorStopIntent(raw);
32348
33097
  if (!isValidSupervisorStopIntent(payload)) {
32349
- await rm9(filePath, { force: true }).catch(() => void 0);
33098
+ await rm10(filePath, { force: true }).catch(() => void 0);
32350
33099
  }
32351
33100
  }
32352
33101
  function parseSupervisorStopIntent(raw) {
@@ -32481,7 +33230,7 @@ async function startLinkUpdate(options) {
32481
33230
  error: null,
32482
33231
  manual_command: manualCommand
32483
33232
  };
32484
- await mkdir15(options.paths.runDir, { recursive: true, mode: 448 });
33233
+ await mkdir16(options.paths.runDir, { recursive: true, mode: 448 });
32485
33234
  await writer.write(
32486
33235
  `
32487
33236
  === link update started ${startedAt} target=${targetVersion} ===
@@ -32789,16 +33538,16 @@ function normalizeServerSnapshot(payload) {
32789
33538
  if (!policy) {
32790
33539
  return {
32791
33540
  remote: null,
32792
- issue: readString21(snapshot, "issue")
33541
+ issue: readString22(snapshot, "issue")
32793
33542
  };
32794
33543
  }
32795
33544
  const release = toNullableRecord2(snapshot.release);
32796
- const currentVersion = readString21(policy, "current_version") ?? readString21(policy, "currentVersion");
32797
- const minSafeVersion = readString21(policy, "min_safe_version") ?? readString21(policy, "minSafeVersion");
33545
+ const currentVersion = readString22(policy, "current_version") ?? readString22(policy, "currentVersion");
33546
+ const minSafeVersion = readString22(policy, "min_safe_version") ?? readString22(policy, "minSafeVersion");
32798
33547
  if (!currentVersion) {
32799
33548
  return {
32800
33549
  remote: null,
32801
- issue: readString21(snapshot, "issue")
33550
+ issue: readString22(snapshot, "issue")
32802
33551
  };
32803
33552
  }
32804
33553
  return {
@@ -32806,10 +33555,10 @@ function normalizeServerSnapshot(payload) {
32806
33555
  current_version: currentVersion,
32807
33556
  min_safe_version: minSafeVersion,
32808
33557
  target_version: currentVersion,
32809
- release_url: release ? readString21(release, "release_url") ?? readString21(release, "releaseUrl") : null,
32810
- published_at: release ? readString21(release, "published_at") ?? readString21(release, "publishedAt") : null
33558
+ release_url: release ? readString22(release, "release_url") ?? readString22(release, "releaseUrl") : null,
33559
+ published_at: release ? readString22(release, "published_at") ?? readString22(release, "publishedAt") : null
32811
33560
  },
32812
- issue: readString21(snapshot, "issue")
33561
+ issue: readString22(snapshot, "issue")
32813
33562
  };
32814
33563
  }
32815
33564
  async function fetchCurrentLinkReleaseFromServer(options, fetcher, channel) {
@@ -32872,7 +33621,7 @@ async function buildOfficialInstallCommand(options, targetVersion) {
32872
33621
  };
32873
33622
  }
32874
33623
  function buildUnixInstallCommand(installerUrl) {
32875
- const nodeBinDir = path29.dirname(process.execPath);
33624
+ const nodeBinDir = path30.dirname(process.execPath);
32876
33625
  const fetchScript = [
32877
33626
  quoteShellToken(process.execPath),
32878
33627
  "--input-type=module",
@@ -33142,18 +33891,18 @@ async function readUpdateLogLines2(paths) {
33142
33891
  );
33143
33892
  }
33144
33893
  function updateStatePath2(paths) {
33145
- return path29.join(paths.runDir, "link-update-state.json");
33894
+ return path30.join(paths.runDir, "link-update-state.json");
33146
33895
  }
33147
33896
  function updateLogPath2(paths) {
33148
- return path29.join(paths.logsDir, UPDATE_LOG_FILE2);
33897
+ return path30.join(paths.logsDir, UPDATE_LOG_FILE2);
33149
33898
  }
33150
33899
  async function clearUpdateLogFiles2(paths) {
33151
33900
  const primary = updateLogPath2(paths);
33152
33901
  await Promise.all([
33153
- rm10(primary, { force: true }).catch(() => void 0),
33902
+ rm11(primary, { force: true }).catch(() => void 0),
33154
33903
  ...Array.from(
33155
33904
  { length: UPDATE_LOG_MAX_FILES2 },
33156
- (_, index) => rm10(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
33905
+ (_, index) => rm11(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
33157
33906
  )
33158
33907
  ]);
33159
33908
  }
@@ -33223,14 +33972,14 @@ function toRecord22(value) {
33223
33972
  function toNullableRecord2(value) {
33224
33973
  return typeof value === "object" && value !== null ? value : null;
33225
33974
  }
33226
- function readString21(payload, key) {
33975
+ function readString22(payload, key) {
33227
33976
  const value = payload[key];
33228
33977
  return typeof value === "string" && value.trim() ? value.trim() : null;
33229
33978
  }
33230
33979
 
33231
33980
  // src/pairing/pairing.ts
33232
- import path30 from "path";
33233
- import { rm as rm11 } from "fs/promises";
33981
+ import path31 from "path";
33982
+ import { rm as rm12 } from "fs/promises";
33234
33983
 
33235
33984
  // src/relay/bootstrap.ts
33236
33985
  var RelayNetworkError = class extends Error {
@@ -33492,7 +34241,7 @@ async function readPairingClaim(sessionId, paths = resolveRuntimePaths()) {
33492
34241
  };
33493
34242
  }
33494
34243
  async function clearPairingClaim(sessionId, paths = resolveRuntimePaths()) {
33495
- await rm11(pairingClaimPath(sessionId, paths), { force: true }).catch(() => void 0);
34244
+ await rm12(pairingClaimPath(sessionId, paths), { force: true }).catch(() => void 0);
33496
34245
  }
33497
34246
  async function claimPairing(input) {
33498
34247
  const paths = input.paths ?? resolveRuntimePaths();
@@ -33569,10 +34318,10 @@ async function loadRequiredIdentity2(paths) {
33569
34318
  }
33570
34319
  return identity;
33571
34320
  }
33572
- async function postServerJson(serverBaseUrl, path31, body, options) {
34321
+ async function postServerJson(serverBaseUrl, path32, body, options) {
33573
34322
  let response;
33574
34323
  try {
33575
- response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path31}`, {
34324
+ response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path32}`, {
33576
34325
  method: "POST",
33577
34326
  headers: {
33578
34327
  accept: "application/json",
@@ -33620,10 +34369,10 @@ function pairingErrorSnapshot(stage, error) {
33620
34369
  occurred_at: (/* @__PURE__ */ new Date()).toISOString()
33621
34370
  };
33622
34371
  }
33623
- async function patchServerJson(serverBaseUrl, path31, token, body, options) {
34372
+ async function patchServerJson(serverBaseUrl, path32, token, body, options) {
33624
34373
  let response;
33625
34374
  try {
33626
- response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path31}`, {
34375
+ response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path32}`, {
33627
34376
  method: "PATCH",
33628
34377
  headers: {
33629
34378
  accept: "application/json",
@@ -33671,10 +34420,10 @@ function createPairingNetworkError(input) {
33671
34420
  );
33672
34421
  }
33673
34422
  function pairingClaimPath(sessionId, paths) {
33674
- return path30.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
34423
+ return path31.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
33675
34424
  }
33676
34425
  function pairingSessionPath(sessionId, paths) {
33677
- return path30.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
34426
+ return path31.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
33678
34427
  }
33679
34428
  function qrPreferredUrls(routes) {
33680
34429
  return routes.preferredUrls.filter((url) => !url.includes("/api/v1/relay/links/")).slice(0, 1);
@@ -33754,8 +34503,8 @@ function registerSystemRoutes(router, options) {
33754
34503
  });
33755
34504
  router.post("/api/v1/pairing/claim", async (ctx) => {
33756
34505
  const body = await readJsonBody(ctx.req);
33757
- const sessionId = readString17(body, "session_id") ?? readString17(body, "sessionId");
33758
- const claimToken = readString17(body, "claim_token") ?? readString17(body, "claimToken");
34506
+ const sessionId = readString18(body, "session_id") ?? readString18(body, "sessionId");
34507
+ const claimToken = readString18(body, "claim_token") ?? readString18(body, "claimToken");
33759
34508
  if (!sessionId || !claimToken) {
33760
34509
  throw new LinkHttpError(
33761
34510
  400,
@@ -33766,10 +34515,10 @@ function registerSystemRoutes(router, options) {
33766
34515
  const claimed = await claimPairing({
33767
34516
  sessionId,
33768
34517
  claimToken,
33769
- deviceLabel: readString17(body, "device_label") ?? readString17(body, "deviceLabel") ?? "HermesPilot App",
33770
- devicePlatform: readString17(body, "device_platform") ?? readString17(body, "devicePlatform") ?? "unknown",
33771
- deviceModel: readString17(body, "device_model") ?? readString17(body, "deviceModel"),
33772
- appInstanceId: readString17(body, "app_instance_id") ?? readString17(body, "appInstanceId"),
34518
+ deviceLabel: readString18(body, "device_label") ?? readString18(body, "deviceLabel") ?? "HermesPilot App",
34519
+ devicePlatform: readString18(body, "device_platform") ?? readString18(body, "devicePlatform") ?? "unknown",
34520
+ deviceModel: readString18(body, "device_model") ?? readString18(body, "deviceModel"),
34521
+ appInstanceId: readString18(body, "app_instance_id") ?? readString18(body, "appInstanceId"),
33773
34522
  paths
33774
34523
  });
33775
34524
  ctx.body = claimed;
@@ -33849,9 +34598,9 @@ function registerSystemRoutes(router, options) {
33849
34598
  const body = await readJsonBody(ctx.req);
33850
34599
  const session = await createDeviceSession(
33851
34600
  {
33852
- label: readString17(body, "device_label") ?? readString17(body, "deviceLabel") ?? "HermesPilot App",
33853
- platform: readString17(body, "device_platform") ?? readString17(body, "devicePlatform") ?? "unknown",
33854
- model: readString17(body, "device_model") ?? readString17(body, "deviceModel"),
34601
+ label: readString18(body, "device_label") ?? readString18(body, "deviceLabel") ?? "HermesPilot App",
34602
+ platform: readString18(body, "device_platform") ?? readString18(body, "devicePlatform") ?? "unknown",
34603
+ model: readString18(body, "device_model") ?? readString18(body, "deviceModel"),
33855
34604
  appInstanceId: auth.appInstanceId
33856
34605
  },
33857
34606
  paths
@@ -33880,7 +34629,7 @@ function registerSystemRoutes(router, options) {
33880
34629
  });
33881
34630
  router.post("/api/v1/auth/refresh", async (ctx) => {
33882
34631
  const body = await readJsonBody(ctx.req);
33883
- const refreshToken = readString17(body, "refresh_token") ?? readString17(body, "refreshToken");
34632
+ const refreshToken = readString18(body, "refresh_token") ?? readString18(body, "refreshToken");
33884
34633
  if (!refreshToken) {
33885
34634
  throw new LinkHttpError(
33886
34635
  400,
@@ -33891,10 +34640,10 @@ function registerSystemRoutes(router, options) {
33891
34640
  const session = await refreshDeviceSession(
33892
34641
  refreshToken,
33893
34642
  {
33894
- appInstanceId: readString17(body, "app_instance_id") ?? readString17(body, "appInstanceId"),
33895
- label: readString17(body, "device_label") ?? readString17(body, "deviceLabel"),
33896
- platform: readString17(body, "device_platform") ?? readString17(body, "devicePlatform"),
33897
- model: readString17(body, "device_model") ?? readString17(body, "deviceModel")
34643
+ appInstanceId: readString18(body, "app_instance_id") ?? readString18(body, "appInstanceId"),
34644
+ label: readString18(body, "device_label") ?? readString18(body, "deviceLabel"),
34645
+ platform: readString18(body, "device_platform") ?? readString18(body, "devicePlatform"),
34646
+ model: readString18(body, "device_model") ?? readString18(body, "deviceModel")
33898
34647
  },
33899
34648
  paths
33900
34649
  );
@@ -33913,7 +34662,7 @@ function registerSystemRoutes(router, options) {
33913
34662
  });
33914
34663
  router.post("/api/v1/auth/logout", async (ctx) => {
33915
34664
  const body = await readJsonBody(ctx.req);
33916
- const refreshToken = readString17(body, "refresh_token") ?? readString17(body, "refreshToken");
34665
+ const refreshToken = readString18(body, "refresh_token") ?? readString18(body, "refreshToken");
33917
34666
  if (refreshToken) {
33918
34667
  await revokeDeviceRefreshToken(refreshToken, paths);
33919
34668
  }
@@ -34139,7 +34888,7 @@ function registerSystemRoutes(router, options) {
34139
34888
  router.patch("/api/v1/devices/:deviceId", async (ctx) => {
34140
34889
  const auth = await authenticateRequest(ctx, paths);
34141
34890
  const body = await readJsonBody(ctx.req);
34142
- const label = readString17(body, "label") ?? readString17(body, "device_label");
34891
+ const label = readString18(body, "label") ?? readString18(body, "device_label");
34143
34892
  if (!label) {
34144
34893
  throw new LinkHttpError(
34145
34894
  400,
@@ -34183,7 +34932,7 @@ function isActiveCronJob(job) {
34183
34932
  if (!enabled) {
34184
34933
  return false;
34185
34934
  }
34186
- const state = readString17(job, "state")?.toLowerCase();
34935
+ const state = readString18(job, "state")?.toLowerCase();
34187
34936
  return !["paused", "disabled", "completed", "deleted"].includes(state ?? "");
34188
34937
  }
34189
34938
  function filterLogsWithinHours(logs, hours, now = Date.now()) {
@@ -34277,8 +35026,8 @@ function registerLinkUpdateRoutes(router, options) {
34277
35026
  ctx.body = await startLinkUpdate({
34278
35027
  paths,
34279
35028
  logger,
34280
- channel: readString17(body, "channel"),
34281
- targetVersion: readString17(body, "target_version") ?? readString17(body, "targetVersion")
35029
+ channel: readString18(body, "channel"),
35030
+ targetVersion: readString18(body, "target_version") ?? readString18(body, "targetVersion")
34282
35031
  });
34283
35032
  });
34284
35033
  router.get("/api/v1/link/update/events", async (ctx) => {
@@ -34308,7 +35057,7 @@ import QRCode from "qrcode";
34308
35057
  function registerPairingRoutes(router, options) {
34309
35058
  const { paths } = options;
34310
35059
  router.get("/pair", async (ctx) => {
34311
- const sessionId = readString17(ctx.query, "session_id");
35060
+ const sessionId = readString18(ctx.query, "session_id");
34312
35061
  if (!sessionId) {
34313
35062
  throw new LinkHttpError(400, "pairing_session_required", "session_id is required");
34314
35063
  }
@@ -34333,7 +35082,7 @@ function registerPairingRoutes(router, options) {
34333
35082
  ctx.body = page;
34334
35083
  });
34335
35084
  router.get("/api/v1/pairing/session", async (ctx) => {
34336
- const sessionId = readString17(ctx.query, "session_id");
35085
+ const sessionId = readString18(ctx.query, "session_id");
34337
35086
  if (!sessionId) {
34338
35087
  throw new LinkHttpError(400, "pairing_session_required", "session_id is required");
34339
35088
  }
@@ -34784,7 +35533,7 @@ function registerInternalRoutes(router, options) {
34784
35533
  router.post("/internal/deliver", async (ctx) => {
34785
35534
  assertLoopbackRequest(ctx.req);
34786
35535
  const body = await readJsonBody(ctx.req);
34787
- const stagingDir = readString17(body, "staging_dir") ?? readString17(body, "stagingDir");
35536
+ const stagingDir = readString18(body, "staging_dir") ?? readString18(body, "stagingDir");
34788
35537
  if (!stagingDir) {
34789
35538
  throw new LinkHttpError(
34790
35539
  400,
@@ -34797,6 +35546,19 @@ function registerInternalRoutes(router, options) {
34797
35546
  ...await options.conversations.deliverStagedFiles(stagingDir)
34798
35547
  };
34799
35548
  });
35549
+ router.post("/internal/deliver-tool", async (ctx) => {
35550
+ assertLoopbackRequest(ctx.req);
35551
+ const body = await readJsonBody(ctx.req);
35552
+ ctx.body = {
35553
+ ok: true,
35554
+ ...await options.conversations.deliverFilesFromTool({
35555
+ profile: readString18(body, "profile") ?? readString18(body, "profile_name") ?? readString18(body, "profileName") ?? void 0,
35556
+ taskId: readString18(body, "task_id") ?? readString18(body, "taskId") ?? void 0,
35557
+ sessionId: readString18(body, "session_id") ?? readString18(body, "sessionId") ?? void 0,
35558
+ files: body.files ?? body.file ?? body.path
35559
+ })
35560
+ };
35561
+ });
34800
35562
  }
34801
35563
  function assertLoopbackRequest(request) {
34802
35564
  const address = request.socket.remoteAddress;