@hermespilot/link 0.7.4-beta.0 → 0.7.5-beta.0

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.5-beta.0";
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) {
@@ -23872,11 +24497,15 @@ function registerConversationRoutes(router, options) {
23872
24497
  router.get("/api/v1/conversations", async (ctx) => {
23873
24498
  const auth = await authenticateRequest(ctx, paths);
23874
24499
  const language = readPreferredLanguage(ctx);
23875
- await prepareConversationListRead(conversations, logger, auth);
24500
+ const cursor = readConversationListCursor(ctx.query);
24501
+ const forceSync = readConversationListForce(ctx.query);
24502
+ await prepareConversationListRead(conversations, logger, auth, {
24503
+ syncHermesSessions: forceSync || !cursor
24504
+ });
23876
24505
  ctx.set("cache-control", "no-store");
23877
24506
  const result = await conversations.listConversationPage({
23878
24507
  limit: readLimit(ctx.query.limit),
23879
- cursor: readQueryString(ctx.query.cursor) ?? readQueryString(ctx.query.after) ?? readQueryString(ctx.query.page_cursor)
24508
+ cursor
23880
24509
  });
23881
24510
  const localized = localizeConversationListPage(result, language);
23882
24511
  ctx.body = {
@@ -23942,7 +24571,7 @@ function registerConversationRoutes(router, options) {
23942
24571
  ok: true,
23943
24572
  conversation: localizeConversationSummary(
23944
24573
  await conversations.createConversation({
23945
- title: readString17(body, "title") ?? void 0,
24574
+ title: readString18(body, "title") ?? void 0,
23946
24575
  profileName: readOptionalProfileName(body),
23947
24576
  accountId: auth.accountId,
23948
24577
  appInstanceId: auth.appInstanceId
@@ -24021,7 +24650,7 @@ function registerConversationRoutes(router, options) {
24021
24650
  const auth = await authenticateRequest(ctx, paths);
24022
24651
  const language = readPreferredLanguage(ctx);
24023
24652
  const body = await readJsonBody(ctx.req);
24024
- const content = readString17(body, "content") ?? readString17(body, "text") ?? readString17(body, "input") ?? "";
24653
+ const content = readString18(body, "content") ?? readString18(body, "text") ?? readString18(body, "input") ?? "";
24025
24654
  const attachments = readMessageAttachments(body.attachments ?? body.blobs);
24026
24655
  if (!content && attachments.length === 0) {
24027
24656
  throw new LinkHttpError(
@@ -24038,7 +24667,7 @@ function registerConversationRoutes(router, options) {
24038
24667
  conversationId: ctx.params.conversationId,
24039
24668
  content,
24040
24669
  attachments,
24041
- clientMessageId: readString17(body, "client_message_id") ?? readString17(body, "clientMessageId") ?? void 0,
24670
+ clientMessageId: readString18(body, "client_message_id") ?? readString18(body, "clientMessageId") ?? void 0,
24042
24671
  idempotencyKey: readHeader(ctx, "idempotency-key") ?? void 0,
24043
24672
  profileName: readOptionalProfileName(body),
24044
24673
  accountId: auth.accountId,
@@ -24052,7 +24681,7 @@ function registerConversationRoutes(router, options) {
24052
24681
  router.patch("/api/v1/conversations/:conversationId/model", async (ctx) => {
24053
24682
  await authenticateRequest(ctx, paths);
24054
24683
  const body = await readJsonBody(ctx.req);
24055
- const modelId = readString17(body, "model_id") ?? readString17(body, "modelId") ?? readString17(body, "model");
24684
+ const modelId = readString18(body, "model_id") ?? readString18(body, "modelId") ?? readString18(body, "model");
24056
24685
  if (!modelId) {
24057
24686
  throw new LinkHttpError(400, "model_id_required", "model_id is required");
24058
24687
  }
@@ -24087,7 +24716,7 @@ function registerConversationRoutes(router, options) {
24087
24716
  await authenticateRequest(ctx, paths);
24088
24717
  const language = readPreferredLanguage(ctx);
24089
24718
  const body = await readJsonBody(ctx.req);
24090
- const title = readString17(body, "title") ?? readString17(body, "name") ?? readString17(body, "display_name");
24719
+ const title = readString18(body, "title") ?? readString18(body, "name") ?? readString18(body, "display_name");
24091
24720
  if (!title) {
24092
24721
  throw new LinkHttpError(400, "title_required", "title is required");
24093
24722
  }
@@ -24281,7 +24910,7 @@ function registerConversationRoutes(router, options) {
24281
24910
  async (ctx) => {
24282
24911
  await authenticateRequest(ctx, paths);
24283
24912
  const body = await readJsonBody(ctx.req);
24284
- const scope = readString17(body, "scope") ?? "always";
24913
+ const scope = readString18(body, "scope") ?? "always";
24285
24914
  ctx.body = {
24286
24915
  ok: true,
24287
24916
  ...await conversations.resolveApproval({
@@ -24389,7 +25018,15 @@ function registerConversationRoutes(router, options) {
24389
25018
  }
24390
25019
  );
24391
25020
  }
24392
- async function prepareConversationListRead(conversations, logger, auth) {
25021
+ async function prepareConversationListRead(conversations, logger, auth, options = {}) {
25022
+ if (options.syncHermesSessions) {
25023
+ await conversations.syncHermesSessions().catch((error) => {
25024
+ void logger.warn("hermes_session_sync_failed", {
25025
+ source: "conversation_list_read",
25026
+ error: error instanceof Error ? error.message : String(error)
25027
+ });
25028
+ });
25029
+ }
24393
25030
  await conversations.backfillCronOwnership({
24394
25031
  accountId: auth.accountId,
24395
25032
  appInstanceId: auth.appInstanceId
@@ -24405,6 +25042,13 @@ async function prepareConversationListRead(conversations, logger, auth) {
24405
25042
  });
24406
25043
  });
24407
25044
  }
25045
+ function readConversationListCursor(query) {
25046
+ return readQueryString(query.cursor) ?? readQueryString(query.after) ?? readQueryString(query.page_cursor);
25047
+ }
25048
+ function readConversationListForce(query) {
25049
+ const raw = Array.isArray(query.force) ? query.force[0] : query.force;
25050
+ return readBoolean3(raw) === true;
25051
+ }
24408
25052
  function localizeConversationListPage(page, language) {
24409
25053
  return {
24410
25054
  ...page,
@@ -24492,7 +25136,7 @@ function resolveConversationEventCursor(input) {
24492
25136
  return Math.max(queryAfter, headerAfter);
24493
25137
  }
24494
25138
  function readConversationClearPlanTargetStatus(body) {
24495
- const raw = readString17(body, "target_status") ?? readString17(body, "targetStatus") ?? "active";
25139
+ const raw = readString18(body, "target_status") ?? readString18(body, "targetStatus") ?? "active";
24496
25140
  if (raw === "active" || raw === "archived") {
24497
25141
  return raw;
24498
25142
  }
@@ -24618,11 +25262,11 @@ function isSseRequestContext(ctx) {
24618
25262
  }
24619
25263
  return isSseRequestPath(ctx.path) || isActiveSseSocket(ctx.req.socket);
24620
25264
  }
24621
- function isSseRequestPath(path31) {
24622
- if (!path31) {
25265
+ function isSseRequestPath(path32) {
25266
+ if (!path32) {
24623
25267
  return false;
24624
25268
  }
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);
25269
+ 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
25270
  }
24627
25271
  function isExpectedClientDisconnectError2(error, options = {}) {
24628
25272
  if (!(error instanceof Error)) {
@@ -24723,6 +25367,7 @@ function registerCronJobRoutes(router, options) {
24723
25367
  router.get("/api/v1/profiles/:name/cron-jobs/:jobId", async (ctx) => {
24724
25368
  await authenticateRequest(ctx, paths);
24725
25369
  const profile = await getHermesProfileStatus(ctx.params.name, paths);
25370
+ assertCronJobId(ctx.params.jobId);
24726
25371
  ctx.set("cache-control", "no-store");
24727
25372
  const job = await getHermesCronJob(ctx.params.jobId, {
24728
25373
  logger,
@@ -24736,9 +25381,46 @@ function registerCronJobRoutes(router, options) {
24736
25381
  )
24737
25382
  };
24738
25383
  });
25384
+ router.get("/api/v1/profiles/:name/cron-jobs/:jobId/history", async (ctx) => {
25385
+ await authenticateRequest(ctx, paths);
25386
+ const profile = await getHermesProfileStatus(ctx.params.name, paths);
25387
+ assertCronJobId(ctx.params.jobId);
25388
+ ctx.set("cache-control", "no-store");
25389
+ await getHermesCronJob(ctx.params.jobId, {
25390
+ logger,
25391
+ profileName: profile.name
25392
+ });
25393
+ const historyPage = await listHermesCronJobHistory({
25394
+ profileName: profile.name,
25395
+ jobId: ctx.params.jobId,
25396
+ limit: readPositiveInteger2(ctx.query.limit),
25397
+ offset: readPositiveInteger2(ctx.query.offset)
25398
+ });
25399
+ const history = historyPage.entries.map((entry) => ({
25400
+ id: entry.id,
25401
+ run_at: entry.runAt,
25402
+ content: entry.content,
25403
+ failed: entry.failed,
25404
+ status: entry.status,
25405
+ job_name: entry.jobName ?? null,
25406
+ truncated: entry.truncated
25407
+ }));
25408
+ ctx.body = {
25409
+ ok: true,
25410
+ profile,
25411
+ history,
25412
+ pagination: {
25413
+ total: historyPage.total,
25414
+ offset: historyPage.offset,
25415
+ limit: historyPage.limit,
25416
+ has_more: historyPage.hasMore
25417
+ }
25418
+ };
25419
+ });
24739
25420
  router.patch("/api/v1/profiles/:name/cron-jobs/:jobId", async (ctx) => {
24740
25421
  const auth = await authenticateRequest(ctx, paths);
24741
25422
  const profile = await getHermesProfileStatus(ctx.params.name, paths);
25423
+ assertCronJobId(ctx.params.jobId);
24742
25424
  const body = await readJsonBody(ctx.req);
24743
25425
  const input = readCronJobUpdateInput(body);
24744
25426
  const deliverTouched = readOptionalCronText(body, "deliver").present;
@@ -24771,6 +25453,7 @@ function registerCronJobRoutes(router, options) {
24771
25453
  router.delete("/api/v1/profiles/:name/cron-jobs/:jobId", async (ctx) => {
24772
25454
  await authenticateRequest(ctx, paths);
24773
25455
  const profile = await getHermesProfileStatus(ctx.params.name, paths);
25456
+ assertCronJobId(ctx.params.jobId);
24774
25457
  await deleteHermesCronJob(ctx.params.jobId, {
24775
25458
  logger,
24776
25459
  profileName: profile.name
@@ -24781,6 +25464,7 @@ function registerCronJobRoutes(router, options) {
24781
25464
  router.post("/api/v1/profiles/:name/cron-jobs/:jobId/pause", async (ctx) => {
24782
25465
  await authenticateRequest(ctx, paths);
24783
25466
  const profile = await getHermesProfileStatus(ctx.params.name, paths);
25467
+ assertCronJobId(ctx.params.jobId);
24784
25468
  const job = await runHermesCronJobAction(ctx.params.jobId, "pause", {
24785
25469
  logger,
24786
25470
  profileName: profile.name
@@ -24796,6 +25480,7 @@ function registerCronJobRoutes(router, options) {
24796
25480
  router.post("/api/v1/profiles/:name/cron-jobs/:jobId/resume", async (ctx) => {
24797
25481
  await authenticateRequest(ctx, paths);
24798
25482
  const profile = await getHermesProfileStatus(ctx.params.name, paths);
25483
+ assertCronJobId(ctx.params.jobId);
24799
25484
  const job = await runHermesCronJobAction(ctx.params.jobId, "resume", {
24800
25485
  logger,
24801
25486
  profileName: profile.name
@@ -24811,6 +25496,7 @@ function registerCronJobRoutes(router, options) {
24811
25496
  router.post("/api/v1/profiles/:name/cron-jobs/:jobId/run", async (ctx) => {
24812
25497
  await authenticateRequest(ctx, paths);
24813
25498
  const profile = await getHermesProfileStatus(ctx.params.name, paths);
25499
+ assertCronJobId(ctx.params.jobId);
24814
25500
  const job = await runHermesCronJobAction(ctx.params.jobId, "run", {
24815
25501
  logger,
24816
25502
  profileName: profile.name
@@ -24844,7 +25530,7 @@ function toHermesCronJobInput(input) {
24844
25530
  };
24845
25531
  }
24846
25532
  async function bindAndDecorateCronJobForHermesLink(input) {
24847
- const jobId = readString17(input.job, "id") ?? readString17(input.job, "job_id");
25533
+ const jobId = readString18(input.job, "id") ?? readString18(input.job, "job_id");
24848
25534
  if (!jobId) {
24849
25535
  return input.job;
24850
25536
  }
@@ -24863,9 +25549,9 @@ async function bindAndDecorateCronJobForHermesLink(input) {
24863
25549
  }
24864
25550
  function readCronJobCreateInput(body) {
24865
25551
  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");
25552
+ const name = readString18(body, "name") ?? readString18(body, "title");
25553
+ const prompt = readString18(body, "prompt") ?? readString18(body, "description") ?? readString18(body, "task");
25554
+ const schedule = readString18(body, "schedule");
24869
25555
  if (!name) {
24870
25556
  throw new LinkHttpError(400, "cron_job_name_required", "name is required");
24871
25557
  }
@@ -24886,7 +25572,7 @@ function readCronJobCreateInput(body) {
24886
25572
  input.name = name;
24887
25573
  input.prompt = prompt;
24888
25574
  input.schedule = schedule;
24889
- input.deliver = readString17(body, "deliver") ?? HERMES_LINK_CRON_DELIVER;
25575
+ input.deliver = readString18(body, "deliver") ?? HERMES_LINK_CRON_DELIVER;
24890
25576
  const skills = readOptionalCronSkills(body);
24891
25577
  if (skills) {
24892
25578
  input.skills = skills;
@@ -25006,6 +25692,12 @@ function attachCronJobProfile(job, profile) {
25006
25692
  }
25007
25693
  };
25008
25694
  }
25695
+ function assertCronJobId(jobId) {
25696
+ if (/^[a-f0-9]{12}$/u.test(jobId)) {
25697
+ return;
25698
+ }
25699
+ throw new LinkHttpError(400, "cron_job_id_invalid", "Invalid cron job id");
25700
+ }
25009
25701
 
25010
25702
  // src/http/routes/gateway-reload.ts
25011
25703
  async function reloadGatewayAfterModelConfigChange(result, options) {
@@ -25213,9 +25905,9 @@ function registerModelConfigRoutes(router, options) {
25213
25905
  });
25214
25906
  }
25215
25907
  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");
25908
+ const id = readString18(body, "id") ?? readString18(body, "model_id") ?? readString18(body, "modelId");
25909
+ const provider = readString18(body, "provider") ?? readString18(body, "provider_key") ?? readString18(body, "providerKey");
25910
+ const baseUrl = readString18(body, "base_url") ?? readString18(body, "baseUrl");
25219
25911
  if (!id || !provider || !baseUrl) {
25220
25912
  throw new LinkHttpError(
25221
25913
  400,
@@ -25225,21 +25917,21 @@ function readModelConfigInput(body) {
25225
25917
  }
25226
25918
  return {
25227
25919
  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,
25920
+ originalModelId: readString18(body, "original_model_id") ?? readString18(body, "originalModelId") ?? readString18(body, "original_id") ?? void 0,
25921
+ originalProvider: readString18(body, "original_provider") ?? readString18(body, "originalProvider") ?? readString18(body, "original_provider_key") ?? readString18(body, "originalProviderKey") ?? void 0,
25922
+ originalBaseUrl: readString18(body, "original_base_url") ?? readString18(body, "originalBaseUrl") ?? void 0,
25923
+ originalApiMode: readString18(body, "original_api_mode") ?? readString18(body, "originalApiMode") ?? void 0,
25232
25924
  provider,
25233
- providerName: readString17(body, "provider_name") ?? readString17(body, "providerName") ?? void 0,
25925
+ providerName: readString18(body, "provider_name") ?? readString18(body, "providerName") ?? void 0,
25234
25926
  baseUrl,
25235
- apiKey: readString17(body, "api_key") ?? readString17(body, "apiKey") ?? void 0,
25236
- apiMode: readString17(body, "api_mode") ?? readString17(body, "apiMode") ?? void 0,
25927
+ apiKey: readString18(body, "api_key") ?? readString18(body, "apiKey") ?? void 0,
25928
+ apiMode: readString18(body, "api_mode") ?? readString18(body, "apiMode") ?? void 0,
25237
25929
  contextLength: readPositiveInteger2(
25238
25930
  body.context_length ?? body.contextLength
25239
25931
  ),
25240
- keyEnv: readString17(body, "key_env") ?? readString17(body, "keyEnv") ?? void 0,
25932
+ keyEnv: readString18(body, "key_env") ?? readString18(body, "keyEnv") ?? void 0,
25241
25933
  setDefault: readBoolean3(body.set_default ?? body.setDefault),
25242
- reasoningEffort: readString17(body, "reasoning_effort") ?? readString17(body, "reasoningEffort") ?? void 0,
25934
+ reasoningEffort: readString18(body, "reasoning_effort") ?? readString18(body, "reasoningEffort") ?? void 0,
25243
25935
  supportsVision: readNullableBoolean(
25244
25936
  body.supports_vision ?? body.supportsVision
25245
25937
  )
@@ -25247,28 +25939,28 @@ function readModelConfigInput(body) {
25247
25939
  }
25248
25940
  function readModelDefaultsInput(body) {
25249
25941
  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
25942
+ taskModelId: readString18(body, "task_model_id") ?? readString18(body, "taskModelId") ?? readString18(body, "default_model_id") ?? readString18(body, "defaultModelId") ?? void 0,
25943
+ taskModelProvider: readString18(body, "task_model_provider") ?? readString18(body, "taskModelProvider") ?? readString18(body, "default_model_provider") ?? readString18(body, "defaultModelProvider") ?? void 0,
25944
+ taskModelBaseUrl: readString18(body, "task_model_base_url") ?? readString18(body, "taskModelBaseUrl") ?? readString18(body, "default_model_base_url") ?? readString18(body, "defaultModelBaseUrl") ?? void 0,
25945
+ taskModelApiMode: readString18(body, "task_model_api_mode") ?? readString18(body, "taskModelApiMode") ?? readString18(body, "default_model_api_mode") ?? readString18(body, "defaultModelApiMode") ?? void 0,
25946
+ compressionModelId: readString18(body, "compression_model_id") ?? readString18(body, "compressionModelId") ?? void 0,
25947
+ compressionModelProvider: readString18(body, "compression_model_provider") ?? readString18(body, "compressionModelProvider") ?? void 0,
25948
+ compressionModelBaseUrl: readString18(body, "compression_model_base_url") ?? readString18(body, "compressionModelBaseUrl") ?? void 0,
25949
+ compressionModelApiMode: readString18(body, "compression_model_api_mode") ?? readString18(body, "compressionModelApiMode") ?? void 0,
25950
+ reasoningEffort: readString18(body, "reasoning_effort") ?? readString18(body, "reasoningEffort") ?? readString18(body, "default_reasoning_effort") ?? readString18(body, "defaultReasoningEffort") ?? void 0,
25951
+ imageInputMode: readString18(body, "image_input_mode") ?? readString18(body, "imageInputMode") ?? void 0
25260
25952
  };
25261
25953
  }
25262
25954
  function readModelDeleteInput(body) {
25263
- const id = readString17(body, "model_id") ?? readString17(body, "modelId");
25955
+ const id = readString18(body, "model_id") ?? readString18(body, "modelId");
25264
25956
  if (!id) {
25265
25957
  throw new LinkHttpError(400, "model_id_required", "model_id is required");
25266
25958
  }
25267
25959
  return {
25268
25960
  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
25961
+ provider: readString18(body, "provider") ?? readString18(body, "provider_key") ?? readString18(body, "providerKey") ?? void 0,
25962
+ baseUrl: readString18(body, "base_url") ?? readString18(body, "baseUrl") ?? void 0,
25963
+ apiMode: readString18(body, "api_mode") ?? readString18(body, "apiMode") ?? void 0
25272
25964
  };
25273
25965
  }
25274
25966
  function readNullableBoolean(value) {
@@ -25291,8 +25983,8 @@ function readNullableBoolean(value) {
25291
25983
  return void 0;
25292
25984
  }
25293
25985
  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");
25986
+ const sourceProfileName = readString18(body, "source_profile") ?? readString18(body, "sourceProfile") ?? readString18(body, "source_profile_name") ?? readString18(body, "sourceProfileName");
25987
+ const modelId = readString18(body, "model_id") ?? readString18(body, "modelId") ?? readString18(body, "id");
25296
25988
  if (!sourceProfileName || !modelId) {
25297
25989
  throw new LinkHttpError(
25298
25990
  400,
@@ -25303,9 +25995,9 @@ function readModelConfigImportInput(body) {
25303
25995
  return {
25304
25996
  sourceProfileName,
25305
25997
  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,
25998
+ provider: readString18(body, "provider") ?? readString18(body, "provider_key") ?? readString18(body, "providerKey") ?? void 0,
25999
+ baseUrl: readString18(body, "base_url") ?? readString18(body, "baseUrl") ?? void 0,
26000
+ apiMode: readString18(body, "api_mode") ?? readString18(body, "apiMode") ?? void 0,
25309
26001
  setDefault: readBoolean3(body.set_default ?? body.setDefault)
25310
26002
  };
25311
26003
  }
@@ -25396,20 +26088,20 @@ function errorMessage3(error) {
25396
26088
  }
25397
26089
 
25398
26090
  // src/hermes/profile-identity.ts
25399
- import { readFile as readFile15, stat as stat15 } from "fs/promises";
25400
- import path22 from "path";
26091
+ import { readFile as readFile15, stat as stat16 } from "fs/promises";
26092
+ import path23 from "path";
25401
26093
  var MAX_SOUL_MD_LENGTH = 2e4;
25402
26094
  async function readHermesProfileIdentity(profileName, paths) {
25403
26095
  await assertProfileExists2(profileName, paths);
25404
26096
  const soulPath = resolveSoulPath(profileName);
25405
26097
  const content = await readFile15(soulPath, "utf8").catch((error) => {
25406
- if (isNodeError17(error, "ENOENT")) {
26098
+ if (isNodeError18(error, "ENOENT")) {
25407
26099
  return null;
25408
26100
  }
25409
26101
  throw error;
25410
26102
  });
25411
- const fileStat = content === null ? null : await stat15(soulPath).catch((error) => {
25412
- if (isNodeError17(error, "ENOENT")) {
26103
+ const fileStat = content === null ? null : await stat16(soulPath).catch((error) => {
26104
+ if (isNodeError18(error, "ENOENT")) {
25413
26105
  return null;
25414
26106
  }
25415
26107
  throw error;
@@ -25454,9 +26146,9 @@ async function assertProfileExists2(profileName, paths) {
25454
26146
  }
25455
26147
  }
25456
26148
  function resolveSoulPath(profileName) {
25457
- return path22.join(resolveHermesProfileDir(profileName), "SOUL.md");
26149
+ return path23.join(resolveHermesProfileDir(profileName), "SOUL.md");
25458
26150
  }
25459
- function isNodeError17(error, code) {
26151
+ function isNodeError18(error, code) {
25460
26152
  return error instanceof Error && "code" in error && error.code === code;
25461
26153
  }
25462
26154
 
@@ -25465,18 +26157,18 @@ import { spawn as spawn2 } from "child_process";
25465
26157
  import { EventEmitter as EventEmitter2 } from "events";
25466
26158
  import {
25467
26159
  cp,
25468
- mkdir as mkdir11,
26160
+ mkdir as mkdir12,
25469
26161
  readFile as readFile17,
25470
- rm as rm6,
25471
- stat as stat17
26162
+ rm as rm7,
26163
+ stat as stat18
25472
26164
  } from "fs/promises";
25473
- import path24 from "path";
26165
+ import path25 from "path";
25474
26166
  import YAML5 from "yaml";
25475
26167
 
25476
26168
  // src/hermes/link-skill.ts
25477
- import { readFile as readFile16, stat as stat16 } from "fs/promises";
26169
+ import { readFile as readFile16, stat as stat17 } from "fs/promises";
25478
26170
  import os5 from "os";
25479
- import path23 from "path";
26171
+ import path24 from "path";
25480
26172
  import YAML4 from "yaml";
25481
26173
  var HERMES_LINK_SKILL_ROOT_DIR = "hermes-skills";
25482
26174
  var HERMES_LINK_SKILL_DIR = "hermes-link";
@@ -25561,7 +26253,7 @@ Do not modify Hermes profiles, delete user data, edit config files, or kill proc
25561
26253
  async function ensureHermesLinkSkillInstalledForProfiles(options = {}) {
25562
26254
  const paths = options.paths ?? resolveRuntimePaths();
25563
26255
  const externalDir = resolveHermesLinkSkillExternalDir(paths);
25564
- const skillPath = path23.join(
26256
+ const skillPath = path24.join(
25565
26257
  externalDir,
25566
26258
  HERMES_LINK_SKILL_DIR,
25567
26259
  HERMES_LINK_SKILL_FILE
@@ -25643,11 +26335,11 @@ function withDefaultProfilePlaceholder2(profiles) {
25643
26335
  ];
25644
26336
  }
25645
26337
  function resolveHermesLinkSkillExternalDir(paths = resolveRuntimePaths()) {
25646
- return path23.join(paths.homeDir, HERMES_LINK_SKILL_ROOT_DIR);
26338
+ return path24.join(paths.homeDir, HERMES_LINK_SKILL_ROOT_DIR);
25647
26339
  }
25648
26340
  async function writeHermesLinkSkill(skillPath) {
25649
26341
  const existing = await readFile16(skillPath, "utf8").catch((error) => {
25650
- if (isNodeError18(error, "ENOENT")) {
26342
+ if (isNodeError19(error, "ENOENT")) {
25651
26343
  return null;
25652
26344
  }
25653
26345
  throw error;
@@ -25706,7 +26398,7 @@ async function ensureProfileUsesExternalSkillDir(profile, externalDir) {
25706
26398
  async function readHermesConfigDocument3(configPath) {
25707
26399
  const existingRaw = await readFile16(configPath, "utf8").catch(
25708
26400
  (error) => {
25709
- if (isNodeError18(error, "ENOENT")) {
26401
+ if (isNodeError19(error, "ENOENT")) {
25710
26402
  return null;
25711
26403
  }
25712
26404
  throw error;
@@ -25738,11 +26430,11 @@ function appendExternalDir(current, externalDir, hermesHome) {
25738
26430
  const seen = new Set(
25739
26431
  entries.map((entry) => resolveExternalDirEntry(entry, hermesHome))
25740
26432
  );
25741
- const normalizedExternalDir = path23.resolve(externalDir);
26433
+ const normalizedExternalDir = path24.resolve(externalDir);
25742
26434
  return seen.has(normalizedExternalDir) ? entries : [...entries, normalizedExternalDir];
25743
26435
  }
25744
26436
  function externalDirsInclude(current, externalDir, hermesHome) {
25745
- const normalizedExternalDir = path23.resolve(externalDir);
26437
+ const normalizedExternalDir = path24.resolve(externalDir);
25746
26438
  return readExternalDirEntries(current).some(
25747
26439
  (entry) => resolveExternalDirEntry(entry, hermesHome) === normalizedExternalDir
25748
26440
  );
@@ -25753,14 +26445,14 @@ function readExternalDirEntries(value) {
25753
26445
  }
25754
26446
  function resolveExternalDirEntry(entry, hermesHome) {
25755
26447
  const expanded = expandHome(expandEnvVars(entry));
25756
- return path23.resolve(path23.isAbsolute(expanded) ? expanded : path23.join(hermesHome, expanded));
26448
+ return path24.resolve(path24.isAbsolute(expanded) ? expanded : path24.join(hermesHome, expanded));
25757
26449
  }
25758
26450
  function expandHome(value) {
25759
26451
  if (value === "~") {
25760
26452
  return os5.homedir();
25761
26453
  }
25762
- if (value.startsWith(`~${path23.sep}`) || value.startsWith("~/")) {
25763
- return path23.join(os5.homedir(), value.slice(2));
26454
+ if (value.startsWith(`~${path24.sep}`) || value.startsWith("~/")) {
26455
+ return path24.join(os5.homedir(), value.slice(2));
25764
26456
  }
25765
26457
  return value;
25766
26458
  }
@@ -25771,8 +26463,8 @@ function expandEnvVars(value) {
25771
26463
  );
25772
26464
  }
25773
26465
  async function pathIsDirectory2(filePath) {
25774
- return stat16(filePath).then((value) => value.isDirectory()).catch((error) => {
25775
- if (isNodeError18(error, "ENOENT")) {
26466
+ return stat17(filePath).then((value) => value.isDirectory()).catch((error) => {
26467
+ if (isNodeError19(error, "ENOENT")) {
25776
26468
  return false;
25777
26469
  }
25778
26470
  throw error;
@@ -25793,7 +26485,7 @@ function toRecord16(value) {
25793
26485
  function isRecord4(value) {
25794
26486
  return typeof value === "object" && value !== null && !Array.isArray(value);
25795
26487
  }
25796
- function isNodeError18(error, code) {
26488
+ function isNodeError19(error, code) {
25797
26489
  return error instanceof Error && "code" in error && error.code === code;
25798
26490
  }
25799
26491
 
@@ -25848,7 +26540,7 @@ async function startHermesProfileCreation(input, options) {
25848
26540
  identity_error: null,
25849
26541
  identity_template_id: normalized.identityTemplateId
25850
26542
  };
25851
- await mkdir11(options.paths.runDir, { recursive: true, mode: 448 });
26543
+ await mkdir12(options.paths.runDir, { recursive: true, mode: 448 });
25852
26544
  await writeProfileCreationState(options.paths, started);
25853
26545
  await writer.write(`
25854
26546
  === profile creation started ${startedAt} ===
@@ -26312,9 +27004,9 @@ function collectEnvKeys(value, keys = /* @__PURE__ */ new Set()) {
26312
27004
  return keys;
26313
27005
  }
26314
27006
  async function writeEnvValues(profileName, values) {
26315
- const envPath = path24.join(resolveHermesProfileDir(profileName), ".env");
27007
+ const envPath = path25.join(resolveHermesProfileDir(profileName), ".env");
26316
27008
  const existingRaw = await readFile17(envPath, "utf8").catch((error) => {
26317
- if (isNodeError19(error, "ENOENT")) {
27009
+ if (isNodeError20(error, "ENOENT")) {
26318
27010
  return "";
26319
27011
  }
26320
27012
  throw error;
@@ -26349,12 +27041,12 @@ async function writeEnvValues(profileName, values) {
26349
27041
  await atomicWriteFilePreservingMetadata(envPath, nextRaw);
26350
27042
  }
26351
27043
  async function copySkills(sourceProfile, targetProfile) {
26352
- const sourceSkills = path24.join(resolveHermesProfileDir(sourceProfile), "skills");
26353
- const targetSkills = path24.join(resolveHermesProfileDir(targetProfile), "skills");
27044
+ const sourceSkills = path25.join(resolveHermesProfileDir(sourceProfile), "skills");
27045
+ const targetSkills = path25.join(resolveHermesProfileDir(targetProfile), "skills");
26354
27046
  if (!await pathExists2(sourceSkills)) {
26355
27047
  return;
26356
27048
  }
26357
- await rm6(targetSkills, { recursive: true, force: true });
27049
+ await rm7(targetSkills, { recursive: true, force: true });
26358
27050
  await cp(sourceSkills, targetSkills, {
26359
27051
  recursive: true,
26360
27052
  force: true,
@@ -26375,7 +27067,7 @@ function copyProperty(source, target, key) {
26375
27067
  async function readYamlConfig(configPath) {
26376
27068
  const existingRaw = await readFile17(configPath, "utf8").catch(
26377
27069
  (error) => {
26378
- if (isNodeError19(error, "ENOENT")) {
27070
+ if (isNodeError20(error, "ENOENT")) {
26379
27071
  return null;
26380
27072
  }
26381
27073
  throw error;
@@ -26402,7 +27094,7 @@ async function failProfileCreation(input) {
26402
27094
  await input.writer.write(`
26403
27095
  Rolling back ${input.rollbackProfileName}...
26404
27096
  `);
26405
- await rm6(resolveHermesProfileDir(input.rollbackProfileName), {
27097
+ await rm7(resolveHermesProfileDir(input.rollbackProfileName), {
26406
27098
  recursive: true,
26407
27099
  force: true
26408
27100
  }).catch(() => void 0);
@@ -26445,24 +27137,24 @@ async function readProfileCreationLogLines(paths) {
26445
27137
  );
26446
27138
  }
26447
27139
  function profileCreationStatePath(paths) {
26448
- return path24.join(paths.runDir, "profile-create-state.json");
27140
+ return path25.join(paths.runDir, "profile-create-state.json");
26449
27141
  }
26450
27142
  function profileCreationLogPath(paths) {
26451
- return path24.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
27143
+ return path25.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
26452
27144
  }
26453
27145
  async function clearProfileCreationLogFiles(paths) {
26454
27146
  const primary = profileCreationLogPath(paths);
26455
27147
  await Promise.all([
26456
- rm6(primary, { force: true }).catch(() => void 0),
27148
+ rm7(primary, { force: true }).catch(() => void 0),
26457
27149
  ...Array.from(
26458
27150
  { length: PROFILE_CREATE_LOG_MAX_FILES },
26459
- (_, index) => rm6(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
27151
+ (_, index) => rm7(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
26460
27152
  )
26461
27153
  ]);
26462
27154
  }
26463
27155
  async function pathExists2(targetPath) {
26464
- return await stat17(targetPath).then(() => true).catch((error) => {
26465
- if (isNodeError19(error, "ENOENT")) {
27156
+ return await stat18(targetPath).then(() => true).catch((error) => {
27157
+ if (isNodeError20(error, "ENOENT")) {
26466
27158
  return false;
26467
27159
  }
26468
27160
  throw error;
@@ -26509,7 +27201,7 @@ function formatEnvValue2(value) {
26509
27201
  function escapeRegExp3(value) {
26510
27202
  return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
26511
27203
  }
26512
- function isNodeError19(error, code) {
27204
+ function isNodeError20(error, code) {
26513
27205
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
26514
27206
  }
26515
27207
 
@@ -26594,16 +27286,16 @@ function readProfilePermissionsInput(body) {
26594
27286
  const approvals = readOptionalObject(body, "approvals");
26595
27287
  if (approvals) {
26596
27288
  input.approvals = {
26597
- mode: readString17(approvals, "mode") ?? readString17(approvals, "approval_mode") ?? readString17(approvals, "approvalMode") ?? void 0,
27289
+ mode: readString18(approvals, "mode") ?? readString18(approvals, "approval_mode") ?? readString18(approvals, "approvalMode") ?? void 0,
26598
27290
  timeout: readPositiveInteger2(approvals.timeout),
26599
- cronMode: readString17(approvals, "cron_mode") ?? readString17(approvals, "cronMode") ?? void 0
27291
+ cronMode: readString18(approvals, "cron_mode") ?? readString18(approvals, "cronMode") ?? void 0
26600
27292
  };
26601
27293
  }
26602
27294
  const terminal = readOptionalObject(body, "terminal");
26603
27295
  if (terminal) {
26604
27296
  input.terminal = {
26605
- backend: readString17(terminal, "backend") ?? void 0,
26606
- cwd: readString17(terminal, "cwd") ?? void 0,
27297
+ backend: readString18(terminal, "backend") ?? void 0,
27298
+ cwd: readString18(terminal, "cwd") ?? void 0,
26607
27299
  containerCpu: readPositiveInteger2(
26608
27300
  terminal.container_cpu ?? terminal.containerCpu
26609
27301
  ),
@@ -26720,9 +27412,9 @@ import {
26720
27412
  access as access3,
26721
27413
  readdir as readdir10,
26722
27414
  readFile as readFile18,
26723
- stat as stat18
27415
+ stat as stat19
26724
27416
  } from "fs/promises";
26725
- import path25 from "path";
27417
+ import path26 from "path";
26726
27418
  import YAML6 from "yaml";
26727
27419
  var ENTRY_DELIMITER = "\n\xA7\n";
26728
27420
  var DEFAULT_MEMORY_LIMIT = 2200;
@@ -26866,14 +27558,23 @@ async function resetHermesMemoryStore(profileName, target) {
26866
27558
  return readHermesProfileMemory(profileName);
26867
27559
  }
26868
27560
  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
- );
27561
+ const hasLimitPatch = patch.memoryCharLimit !== void 0 || patch.userCharLimit !== void 0;
27562
+ const hasProviderPatch = Object.keys(patch).some(
27563
+ (key) => key !== "memoryCharLimit" && key !== "userCharLimit"
27564
+ );
27565
+ if (hasProviderPatch) {
27566
+ const provider = await readActiveMemoryProvider(profileName);
27567
+ if (!provider) {
27568
+ throw new HermesMemoryError(
27569
+ "memory_settings_builtin_only",
27570
+ "\u5F53\u524D Profile \u4F7F\u7528 built-in memory\uFF0C\u6CA1\u6709\u53EF\u4FDD\u5B58\u7684\u5916\u90E8 provider \u8BBE\u7F6E\u3002"
27571
+ );
27572
+ }
27573
+ await saveProviderSettings(profileName, provider, patch);
27574
+ }
27575
+ if (hasLimitPatch) {
27576
+ await patchHermesMemoryLimits(profileName, patch);
26875
27577
  }
26876
- await saveProviderSettings(profileName, provider, patch);
26877
27578
  return readHermesProfileMemory(profileName);
26878
27579
  }
26879
27580
  async function saveHermesMemoryProviderSettings(profileName, provider, patch) {
@@ -26910,9 +27611,9 @@ async function testHindsightProviderSettings(profileName, patch) {
26910
27611
  const mode = normalizeHindsightMode(
26911
27612
  patch.mode ?? config.mode ?? env.HINDSIGHT_MODE
26912
27613
  );
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);
27614
+ const apiUrl = readString19(patch.apiUrl) ?? readString19(config.api_url) ?? env.HINDSIGHT_API_URL ?? (mode === "cloud" ? HINDSIGHT_DEFAULT_API_URL : HINDSIGHT_DEFAULT_LOCAL_URL);
27615
+ const bankId = readString19(patch.bankId) ?? readString19(config.bank_id) ?? "hermes";
27616
+ const apiKey = readString19(patch.apiKey) ?? env.HINDSIGHT_API_KEY ?? readString19(config.apiKey) ?? readString19(config.api_key);
26916
27617
  const baseUrl = normalizeHttpUrl(apiUrl);
26917
27618
  if (!baseUrl) {
26918
27619
  return {
@@ -27044,7 +27745,7 @@ async function saveProviderSettings(profileName, provider, patch) {
27044
27745
  });
27045
27746
  await patchJsonProviderConfig(
27046
27747
  profileName,
27047
- path25.join("hindsight", "config.json"),
27748
+ path26.join("hindsight", "config.json"),
27048
27749
  {
27049
27750
  mode: patch.mode,
27050
27751
  api_url: patch.apiUrl,
@@ -27194,7 +27895,7 @@ async function patchHermesMemoryProvider(profileName, provider) {
27194
27895
  const configPath = resolveHermesConfigPath(profileName);
27195
27896
  const existingRaw = await readFile18(configPath, "utf8").catch(
27196
27897
  (error) => {
27197
- if (isNodeError20(error, "ENOENT")) {
27898
+ if (isNodeError21(error, "ENOENT")) {
27198
27899
  return null;
27199
27900
  }
27200
27901
  throw error;
@@ -27214,14 +27915,46 @@ async function patchHermesMemoryProvider(profileName, provider) {
27214
27915
  document.contents = document.createNode(config);
27215
27916
  await atomicWriteFilePreservingMetadata(configPath, document.toString());
27216
27917
  }
27918
+ async function patchHermesMemoryLimits(profileName, patch) {
27919
+ const configPath = resolveHermesConfigPath(profileName);
27920
+ const existingRaw = await readFile18(configPath, "utf8").catch(
27921
+ (error) => {
27922
+ if (isNodeError21(error, "ENOENT")) {
27923
+ return null;
27924
+ }
27925
+ throw error;
27926
+ }
27927
+ );
27928
+ const document = existingRaw ? YAML6.parseDocument(existingRaw) : new YAML6.Document({});
27929
+ const config = toRecord18(document.toJSON());
27930
+ const memory = toRecord18(config.memory);
27931
+ if (patch.memoryCharLimit !== void 0) {
27932
+ memory.memory_char_limit = patch.memoryCharLimit;
27933
+ }
27934
+ if (patch.userCharLimit !== void 0) {
27935
+ memory.user_char_limit = patch.userCharLimit;
27936
+ }
27937
+ config.memory = memory;
27938
+ if (existingRaw) {
27939
+ await atomicWriteFilePreservingMetadata(
27940
+ `${configPath}.bak.${Date.now()}`,
27941
+ existingRaw,
27942
+ {
27943
+ metadataSourcePath: configPath
27944
+ }
27945
+ );
27946
+ }
27947
+ document.contents = document.createNode(config);
27948
+ await atomicWriteFilePreservingMetadata(configPath, document.toString());
27949
+ }
27217
27950
  function resolveMemoryDir(profileName) {
27218
- return path25.join(resolveHermesProfileDir(profileName), "memories");
27951
+ return path26.join(resolveHermesProfileDir(profileName), "memories");
27219
27952
  }
27220
27953
  async function readMemoryStore(profileName, target, limits) {
27221
27954
  const filePath = memoryFilePath(profileName, target);
27222
27955
  const entries = await readMemoryEntries(filePath);
27223
- const fileStat = await stat18(filePath).catch((error) => {
27224
- if (isNodeError20(error, "ENOENT")) {
27956
+ const fileStat = await stat19(filePath).catch((error) => {
27957
+ if (isNodeError21(error, "ENOENT")) {
27225
27958
  return null;
27226
27959
  }
27227
27960
  throw error;
@@ -27250,7 +27983,7 @@ async function readMemoryStore(profileName, target, limits) {
27250
27983
  }
27251
27984
  async function readMemoryEntries(filePath) {
27252
27985
  const raw = await readFile18(filePath, "utf8").catch((error) => {
27253
- if (isNodeError20(error, "ENOENT")) {
27986
+ if (isNodeError21(error, "ENOENT")) {
27254
27987
  return "";
27255
27988
  }
27256
27989
  throw error;
@@ -27276,7 +28009,7 @@ async function writeMemoryEntries(profileName, target, entries) {
27276
28009
  );
27277
28010
  }
27278
28011
  function memoryFilePath(profileName, target) {
27279
- return path25.join(
28012
+ return path26.join(
27280
28013
  resolveMemoryDir(profileName),
27281
28014
  target === "user" ? "USER.md" : "MEMORY.md"
27282
28015
  );
@@ -27336,7 +28069,7 @@ async function readCustomProviderSetupSummary(profileName) {
27336
28069
  configurable: true,
27337
28070
  configured: true,
27338
28071
  configurationIssue: null,
27339
- providerConfigPath: path25.join(
28072
+ providerConfigPath: path26.join(
27340
28073
  resolveHermesProfileDir(profileName),
27341
28074
  "<provider>.json"
27342
28075
  ),
@@ -27414,7 +28147,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
27414
28147
  const config2 = await readJsonObject(
27415
28148
  memoryProviderConfigPath(profileName, "honcho") ?? ""
27416
28149
  );
27417
- return isConfiguredEnvValue(env.HONCHO_API_KEY) || isConfiguredEnvValue(readString18(config2.apiKey)) || isConfiguredEnvValue(readString18(config2.api_key)) || isConfiguredEnvValue(readString18(config2.baseUrl)) ? { configured: true, issue: null } : {
28150
+ return isConfiguredEnvValue(env.HONCHO_API_KEY) || isConfiguredEnvValue(readString19(config2.apiKey)) || isConfiguredEnvValue(readString19(config2.api_key)) || isConfiguredEnvValue(readString19(config2.baseUrl)) ? { configured: true, issue: null } : {
27418
28151
  configured: false,
27419
28152
  issue: "Honcho \u9700\u8981\u5148\u586B\u5199 API Key\uFF0C\u6216\u5728 honcho.json \u914D\u7F6E self-hosted baseUrl\u3002"
27420
28153
  };
@@ -27423,7 +28156,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
27423
28156
  const config2 = await readJsonObject(
27424
28157
  memoryProviderConfigPath(profileName, "mem0") ?? ""
27425
28158
  );
27426
- return isConfiguredEnvValue(env.MEM0_API_KEY) || isConfiguredEnvValue(readString18(config2.api_key)) ? { configured: true, issue: null } : {
28159
+ return isConfiguredEnvValue(env.MEM0_API_KEY) || isConfiguredEnvValue(readString19(config2.api_key)) ? { configured: true, issue: null } : {
27427
28160
  configured: false,
27428
28161
  issue: "Mem0 \u9700\u8981\u5148\u5728\u672C\u9875\u586B\u5199 API Key\uFF0CLink \u4F1A\u5199\u5165\u5F53\u524D Profile \u7684 .env\u3002"
27429
28162
  };
@@ -27465,7 +28198,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
27465
28198
  memoryProviderConfigPath(profileName, provider) ?? ""
27466
28199
  );
27467
28200
  const mode = normalizeHindsightMode(config.mode ?? env.HINDSIGHT_MODE);
27468
- const apiKey = readString18(config.apiKey) ?? readString18(config.api_key) ?? env.HINDSIGHT_API_KEY;
28201
+ const apiKey = readString19(config.apiKey) ?? readString19(config.api_key) ?? env.HINDSIGHT_API_KEY;
27469
28202
  if (mode === "cloud") {
27470
28203
  return isConfiguredEnvValue(apiKey) ? { configured: true, issue: null } : {
27471
28204
  configured: false,
@@ -27473,15 +28206,15 @@ async function readProviderConfigurationStatus(profileName, provider) {
27473
28206
  };
27474
28207
  }
27475
28208
  if (mode === "local_external") {
27476
- const apiUrl = readString18(config.api_url) ?? env.HINDSIGHT_API_URL ?? HINDSIGHT_DEFAULT_LOCAL_URL;
28209
+ const apiUrl = readString19(config.api_url) ?? env.HINDSIGHT_API_URL ?? HINDSIGHT_DEFAULT_LOCAL_URL;
27477
28210
  return isConfiguredEnvValue(apiUrl) ? { configured: true, issue: null } : {
27478
28211
  configured: false,
27479
28212
  issue: "Hindsight local_external \u9700\u8981\u914D\u7F6E\u53EF\u8BBF\u95EE\u7684 API URL\u3002"
27480
28213
  };
27481
28214
  }
27482
28215
  if (mode === "local_embedded") {
27483
- const llmProvider = readString18(config.llm_provider) ?? "openai";
27484
- const llmModel = readString18(config.llm_model);
28216
+ const llmProvider = readString19(config.llm_provider) ?? "openai";
28217
+ const llmModel = readString19(config.llm_model);
27485
28218
  if (!llmModel) {
27486
28219
  return {
27487
28220
  configured: false,
@@ -27489,7 +28222,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
27489
28222
  };
27490
28223
  }
27491
28224
  if (llmProvider === "openai_compatible" && !isConfiguredEnvValue(
27492
- readString18(config.llm_base_url) ?? env.HINDSIGHT_API_LLM_BASE_URL
28225
+ readString19(config.llm_base_url) ?? env.HINDSIGHT_API_LLM_BASE_URL
27493
28226
  )) {
27494
28227
  return {
27495
28228
  configured: false,
@@ -27497,7 +28230,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
27497
28230
  };
27498
28231
  }
27499
28232
  if (!["ollama", "lmstudio", "openai_compatible"].includes(llmProvider) && !isConfiguredEnvValue(
27500
- readString18(config.llmApiKey) ?? readString18(config.llm_api_key) ?? env.HINDSIGHT_LLM_API_KEY
28233
+ readString19(config.llmApiKey) ?? readString19(config.llm_api_key) ?? env.HINDSIGHT_LLM_API_KEY
27501
28234
  )) {
27502
28235
  return {
27503
28236
  configured: false,
@@ -27553,8 +28286,8 @@ async function readProviderSettings(profileName, provider) {
27553
28286
  secretSetting(
27554
28287
  "apiKey",
27555
28288
  "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))
28289
+ env.HONCHO_API_KEY ?? readString19(config.apiKey) ?? readString19(config.api_key),
28290
+ isConfiguredEnvValue(env.HONCHO_API_KEY) || isConfiguredEnvValue(readString19(config.apiKey)) || isConfiguredEnvValue(readString19(config.api_key))
27558
28291
  ),
27559
28292
  stringSetting("workspace", "Workspace", config.workspace ?? "hermes"),
27560
28293
  stringSetting("peerName", "\u7528\u6237 Peer", config.peerName ?? ""),
@@ -27596,8 +28329,8 @@ async function readProviderSettings(profileName, provider) {
27596
28329
  secretSetting(
27597
28330
  "apiKey",
27598
28331
  "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))
28332
+ env.MEM0_API_KEY ?? readString19(config.apiKey) ?? readString19(config.api_key),
28333
+ isConfiguredEnvValue(env.MEM0_API_KEY) || isConfiguredEnvValue(readString19(config.apiKey)) || isConfiguredEnvValue(readString19(config.api_key))
27601
28334
  ),
27602
28335
  stringSetting("userId", "User ID", config.user_id ?? "hermes-user"),
27603
28336
  stringSetting("agentId", "Agent ID", config.agent_id ?? "hermes"),
@@ -27682,8 +28415,8 @@ async function readProviderSettings(profileName, provider) {
27682
28415
  secretSetting(
27683
28416
  "apiKey",
27684
28417
  "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))
28418
+ env.HINDSIGHT_API_KEY ?? readString19(config.apiKey) ?? readString19(config.api_key),
28419
+ isConfiguredEnvValue(env.HINDSIGHT_API_KEY) || isConfiguredEnvValue(readString19(config.apiKey)) || isConfiguredEnvValue(readString19(config.api_key))
27687
28420
  ),
27688
28421
  stringSetting(
27689
28422
  "bankId",
@@ -27705,8 +28438,8 @@ async function readProviderSettings(profileName, provider) {
27705
28438
  secretSetting(
27706
28439
  "llmApiKey",
27707
28440
  "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))
28441
+ env.HINDSIGHT_LLM_API_KEY ?? readString19(config.llmApiKey) ?? readString19(config.llm_api_key),
28442
+ isConfiguredEnvValue(env.HINDSIGHT_LLM_API_KEY) || isConfiguredEnvValue(readString19(config.llmApiKey)) || isConfiguredEnvValue(readString19(config.llm_api_key))
27710
28443
  ),
27711
28444
  booleanSetting("autoRecall", "\u81EA\u52A8\u56DE\u5FC6", config.auto_recall ?? true),
27712
28445
  booleanSetting("autoRetain", "\u81EA\u52A8\u6C89\u6DC0", config.auto_retain ?? true),
@@ -27730,7 +28463,7 @@ async function readProviderSettings(profileName, provider) {
27730
28463
  stringSetting(
27731
28464
  "dbPath",
27732
28465
  "SQLite \u6570\u636E\u5E93\u8DEF\u5F84",
27733
- config.db_path ?? path25.join(resolveHermesProfileDir(profileName), "memory_store.db")
28466
+ config.db_path ?? path26.join(resolveHermesProfileDir(profileName), "memory_store.db")
27734
28467
  ),
27735
28468
  booleanSetting("autoExtract", "\u4F1A\u8BDD\u7ED3\u675F\u81EA\u52A8\u62BD\u53D6", config.auto_extract ?? false),
27736
28469
  numberSetting("defaultTrust", "\u9ED8\u8BA4\u4FE1\u4EFB\u5206", config.default_trust ?? 0.5),
@@ -27766,7 +28499,7 @@ async function readProviderSettings(profileName, provider) {
27766
28499
  stringSetting(
27767
28500
  "workingDirectory",
27768
28501
  "\u5DE5\u4F5C\u76EE\u5F55",
27769
- path25.join(resolveHermesProfileDir(profileName), "byterover"),
28502
+ path26.join(resolveHermesProfileDir(profileName), "byterover"),
27770
28503
  false
27771
28504
  )
27772
28505
  ];
@@ -27775,16 +28508,16 @@ async function readProviderSettings(profileName, provider) {
27775
28508
  }
27776
28509
  function memoryProviderConfigPath(profileName, provider) {
27777
28510
  if (provider === "honcho") {
27778
- return path25.join(resolveHermesProfileDir(profileName), "honcho.json");
28511
+ return path26.join(resolveHermesProfileDir(profileName), "honcho.json");
27779
28512
  }
27780
28513
  if (provider === "mem0") {
27781
- return path25.join(resolveHermesProfileDir(profileName), "mem0.json");
28514
+ return path26.join(resolveHermesProfileDir(profileName), "mem0.json");
27782
28515
  }
27783
28516
  if (provider === "supermemory") {
27784
- return path25.join(resolveHermesProfileDir(profileName), "supermemory.json");
28517
+ return path26.join(resolveHermesProfileDir(profileName), "supermemory.json");
27785
28518
  }
27786
28519
  if (provider === "hindsight") {
27787
- return path25.join(
28520
+ return path26.join(
27788
28521
  resolveHermesProfileDir(profileName),
27789
28522
  "hindsight",
27790
28523
  "config.json"
@@ -27793,13 +28526,13 @@ function memoryProviderConfigPath(profileName, provider) {
27793
28526
  return null;
27794
28527
  }
27795
28528
  function customProviderConfigPath(profileName, provider) {
27796
- return path25.join(
28529
+ return path26.join(
27797
28530
  resolveHermesProfileDir(profileName),
27798
28531
  `${normalizeCustomProviderId(provider)}.json`
27799
28532
  );
27800
28533
  }
27801
28534
  function customProviderRegistryPath(profileName) {
27802
- return path25.join(
28535
+ return path26.join(
27803
28536
  resolveHermesProfileDir(profileName),
27804
28537
  CUSTOM_PROVIDER_REGISTRY_FILE
27805
28538
  );
@@ -27807,7 +28540,7 @@ function customProviderRegistryPath(profileName) {
27807
28540
  async function readCustomProviderRegistry(profileName) {
27808
28541
  const raw = await readFile18(customProviderRegistryPath(profileName), "utf8").catch(
27809
28542
  (error) => {
27810
- if (isNodeError20(error, "ENOENT")) {
28543
+ if (isNodeError21(error, "ENOENT")) {
27811
28544
  return "";
27812
28545
  }
27813
28546
  throw error;
@@ -27825,11 +28558,11 @@ async function readCustomProviderRegistry(profileName) {
27825
28558
  return { id: id2, label: id2, description: "\u81EA\u5B9A\u4E49 memory provider\u3002" };
27826
28559
  }
27827
28560
  const record = toRecord18(item);
27828
- const id = normalizeCustomProviderId(readString18(record.id) ?? "");
28561
+ const id = normalizeCustomProviderId(readString19(record.id) ?? "");
27829
28562
  return {
27830
28563
  id,
27831
- label: readString18(record.label) ?? id,
27832
- description: readString18(record.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
28564
+ label: readString19(record.label) ?? id,
28565
+ description: readString19(record.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
27833
28566
  };
27834
28567
  }).filter((item) => item.id);
27835
28568
  } catch {
@@ -27851,10 +28584,10 @@ async function saveCustomProviderRegistryEntry(profileName, provider) {
27851
28584
  );
27852
28585
  }
27853
28586
  async function discoverUserMemoryProviderDescriptors(profileName) {
27854
- const pluginsDir = path25.join(resolveHermesProfileDir(profileName), "plugins");
28587
+ const pluginsDir = path26.join(resolveHermesProfileDir(profileName), "plugins");
27855
28588
  const entries = await readdir10(pluginsDir, { withFileTypes: true }).catch(
27856
28589
  (error) => {
27857
- if (isNodeError20(error, "ENOENT")) {
28590
+ if (isNodeError21(error, "ENOENT")) {
27858
28591
  return [];
27859
28592
  }
27860
28593
  throw error;
@@ -27871,21 +28604,21 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
27871
28604
  } catch {
27872
28605
  continue;
27873
28606
  }
27874
- const providerDir = path25.join(pluginsDir, entry.name);
28607
+ const providerDir = path26.join(pluginsDir, entry.name);
27875
28608
  if (!await isMemoryProviderPluginDir(providerDir)) {
27876
28609
  continue;
27877
28610
  }
27878
28611
  const meta = await readPluginMetadata(providerDir);
27879
28612
  descriptors.push({
27880
28613
  id: providerId,
27881
- label: readString18(meta.name) ?? providerId,
27882
- description: readString18(meta.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
28614
+ label: readString19(meta.name) ?? providerId,
28615
+ description: readString19(meta.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
27883
28616
  });
27884
28617
  }
27885
28618
  return descriptors;
27886
28619
  }
27887
28620
  async function isUserMemoryProviderInstalled(profileName, provider) {
27888
- const providerDir = path25.join(
28621
+ const providerDir = path26.join(
27889
28622
  resolveHermesProfileDir(profileName),
27890
28623
  "plugins",
27891
28624
  normalizeCustomProviderId(provider)
@@ -27893,9 +28626,9 @@ async function isUserMemoryProviderInstalled(profileName, provider) {
27893
28626
  return isMemoryProviderPluginDir(providerDir);
27894
28627
  }
27895
28628
  async function isMemoryProviderPluginDir(providerDir) {
27896
- const source = await readFile18(path25.join(providerDir, "__init__.py"), "utf8").catch(
28629
+ const source = await readFile18(path26.join(providerDir, "__init__.py"), "utf8").catch(
27897
28630
  (error) => {
27898
- if (isNodeError20(error, "ENOENT")) {
28631
+ if (isNodeError21(error, "ENOENT")) {
27899
28632
  return "";
27900
28633
  }
27901
28634
  throw error;
@@ -27905,9 +28638,9 @@ async function isMemoryProviderPluginDir(providerDir) {
27905
28638
  return sample.includes("register_memory_provider") || sample.includes("MemoryProvider");
27906
28639
  }
27907
28640
  async function readPluginMetadata(providerDir) {
27908
- const raw = await readFile18(path25.join(providerDir, "plugin.yaml"), "utf8").catch(
28641
+ const raw = await readFile18(path26.join(providerDir, "plugin.yaml"), "utf8").catch(
27909
28642
  (error) => {
27910
- if (isNodeError20(error, "ENOENT")) {
28643
+ if (isNodeError21(error, "ENOENT")) {
27911
28644
  return "";
27912
28645
  }
27913
28646
  throw error;
@@ -27917,10 +28650,10 @@ async function readPluginMetadata(providerDir) {
27917
28650
  }
27918
28651
  async function resolveByteRoverCli() {
27919
28652
  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"),
28653
+ ...(process.env.PATH ?? "").split(path26.delimiter).filter(Boolean).map((dir) => path26.join(dir, "brv")),
28654
+ path26.join(process.env.HOME ?? "", ".brv-cli", "bin", "brv"),
27922
28655
  "/usr/local/bin/brv",
27923
- path25.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
28656
+ path26.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
27924
28657
  ].filter(Boolean);
27925
28658
  for (const candidate of candidates) {
27926
28659
  const found = await access3(candidate).then(() => true).catch(() => false);
@@ -27933,7 +28666,7 @@ async function resolveByteRoverCli() {
27933
28666
  async function readHolographicProviderConfig(profileName) {
27934
28667
  const raw = await readFile18(resolveHermesConfigPath(profileName), "utf8").catch(
27935
28668
  (error) => {
27936
- if (isNodeError20(error, "ENOENT")) {
28669
+ if (isNodeError21(error, "ENOENT")) {
27937
28670
  return "";
27938
28671
  }
27939
28672
  throw error;
@@ -27947,7 +28680,7 @@ async function patchHolographicProviderConfig(profileName, patch) {
27947
28680
  const configPath = resolveHermesConfigPath(profileName);
27948
28681
  const existingRaw = await readFile18(configPath, "utf8").catch(
27949
28682
  (error) => {
27950
- if (isNodeError20(error, "ENOENT")) {
28683
+ if (isNodeError21(error, "ENOENT")) {
27951
28684
  return null;
27952
28685
  }
27953
28686
  throw error;
@@ -27981,9 +28714,9 @@ async function patchHermesMemoryEnv(profileName, patch) {
27981
28714
  if (entries.length === 0) {
27982
28715
  return;
27983
28716
  }
27984
- const envPath = path25.join(resolveHermesProfileDir(profileName), ".env");
28717
+ const envPath = path26.join(resolveHermesProfileDir(profileName), ".env");
27985
28718
  const existingRaw = await readFile18(envPath, "utf8").catch((error) => {
27986
- if (isNodeError20(error, "ENOENT")) {
28719
+ if (isNodeError21(error, "ENOENT")) {
27987
28720
  return "";
27988
28721
  }
27989
28722
  throw error;
@@ -28041,7 +28774,7 @@ function isMemoryEnvKeyWritable(key) {
28041
28774
  ].includes(key);
28042
28775
  }
28043
28776
  function normalizeHindsightMode(value) {
28044
- const mode = readString18(value) ?? "cloud";
28777
+ const mode = readString19(value) ?? "cloud";
28045
28778
  return mode === "local" ? "local_embedded" : mode;
28046
28779
  }
28047
28780
  function normalizeHttpUrl(value) {
@@ -28110,27 +28843,27 @@ function parseJsonObject2(text) {
28110
28843
  }
28111
28844
  }
28112
28845
  function readHindsightError(json) {
28113
- const detail = readString18(json.detail) ?? readString18(json.error);
28846
+ const detail = readString19(json.detail) ?? readString19(json.error);
28114
28847
  return detail ? `\uFF1A${detail}` : "";
28115
28848
  }
28116
28849
  function hindsightSemanticIssue(pathName, json) {
28117
28850
  if (pathName === "/health") {
28118
- const status = readString18(json.status);
28851
+ const status = readString19(json.status);
28119
28852
  return status && ["healthy", "ok"].includes(status.toLowerCase()) ? null : `\u5065\u5EB7\u72B6\u6001\u5F02\u5E38\uFF1A${status ?? "unknown"}`;
28120
28853
  }
28121
28854
  return null;
28122
28855
  }
28123
28856
  function summarizeHindsightProbe(pathName, json) {
28124
28857
  if (pathName === "/health") {
28125
- const status = readString18(json.status) ?? "ok";
28126
- const database = readString18(json.database);
28858
+ const status = readString19(json.status) ?? "ok";
28859
+ const database = readString19(json.database);
28127
28860
  return database ? `${status}, database ${database}` : status;
28128
28861
  }
28129
28862
  if (pathName === "/version") {
28130
- const version = readString18(json.api_version);
28863
+ const version = readString19(json.api_version);
28131
28864
  return version ? `API ${version}` : "version endpoint reachable";
28132
28865
  }
28133
- const bankId = readString18(json.bank_id);
28866
+ const bankId = readString19(json.bank_id);
28134
28867
  return bankId ? `bank ${bankId} reachable` : "bank config reachable";
28135
28868
  }
28136
28869
  async function readActiveMemoryProvider(profileName) {
@@ -28138,21 +28871,21 @@ async function readActiveMemoryProvider(profileName) {
28138
28871
  resolveHermesConfigPath(profileName),
28139
28872
  "utf8"
28140
28873
  ).catch((error) => {
28141
- if (isNodeError20(error, "ENOENT")) {
28874
+ if (isNodeError21(error, "ENOENT")) {
28142
28875
  return "";
28143
28876
  }
28144
28877
  throw error;
28145
28878
  });
28146
28879
  const config = raw ? toRecord18(YAML6.parse(raw)) : {};
28147
28880
  const memory = toRecord18(config.memory);
28148
- const provider = readString18(memory.provider);
28881
+ const provider = readString19(memory.provider);
28149
28882
  if (!provider || provider === "built-in" || provider === "builtin" || provider === "built_in") {
28150
28883
  return null;
28151
28884
  }
28152
28885
  return provider;
28153
28886
  }
28154
28887
  async function patchJsonProviderConfig(profileName, relativePath, patch) {
28155
- const configPath = path25.join(
28888
+ const configPath = path26.join(
28156
28889
  resolveHermesProfileDir(profileName),
28157
28890
  relativePath
28158
28891
  );
@@ -28171,7 +28904,7 @@ async function patchJsonProviderConfig(profileName, relativePath, patch) {
28171
28904
  }
28172
28905
  async function readJsonObject(filePath) {
28173
28906
  const raw = await readFile18(filePath, "utf8").catch((error) => {
28174
- if (isNodeError20(error, "ENOENT")) {
28907
+ if (isNodeError21(error, "ENOENT")) {
28175
28908
  return "{}";
28176
28909
  }
28177
28910
  throw error;
@@ -28181,7 +28914,7 @@ async function readJsonObject(filePath) {
28181
28914
  } catch {
28182
28915
  throw new HermesMemoryError(
28183
28916
  "memory_provider_config_invalid",
28184
- `${path25.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
28917
+ `${path26.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
28185
28918
  );
28186
28919
  }
28187
28920
  }
@@ -28202,7 +28935,7 @@ function stringSetting(key, label, value, editable = true) {
28202
28935
  return {
28203
28936
  key,
28204
28937
  label,
28205
- value: readString18(value) ?? "",
28938
+ value: readString19(value) ?? "",
28206
28939
  editable,
28207
28940
  kind: "string"
28208
28941
  };
@@ -28214,7 +28947,7 @@ function secretSetting(key, label, value, configured) {
28214
28947
  value: "",
28215
28948
  editable: true,
28216
28949
  kind: "secret",
28217
- configured: configured || isConfiguredEnvValue(readString18(value))
28950
+ configured: configured || isConfiguredEnvValue(readString19(value))
28218
28951
  };
28219
28952
  }
28220
28953
  function textSetting(key, label, value, editable = true) {
@@ -28227,7 +28960,7 @@ function textSetting(key, label, value, editable = true) {
28227
28960
  };
28228
28961
  }
28229
28962
  function selectSetting(key, label, value, options, editable = true) {
28230
- const stringValue = readString18(value) ?? options[0] ?? null;
28963
+ const stringValue = readString19(value) ?? options[0] ?? null;
28231
28964
  return { key, label, value: stringValue, editable, kind: "select", options };
28232
28965
  }
28233
28966
  async function readMemoryLimits(profileName) {
@@ -28235,7 +28968,7 @@ async function readMemoryLimits(profileName) {
28235
28968
  resolveHermesConfigPath(profileName),
28236
28969
  "utf8"
28237
28970
  ).catch((error) => {
28238
- if (isNodeError20(error, "ENOENT")) {
28971
+ if (isNodeError21(error, "ENOENT")) {
28239
28972
  return "";
28240
28973
  }
28241
28974
  throw error;
@@ -28300,7 +29033,7 @@ function hashString(value) {
28300
29033
  function toRecord18(value) {
28301
29034
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
28302
29035
  }
28303
- function readString18(value) {
29036
+ function readString19(value) {
28304
29037
  return typeof value === "string" && value.trim() ? value.trim() : null;
28305
29038
  }
28306
29039
  function readPositiveInteger3(value) {
@@ -28328,7 +29061,7 @@ function formatEnvValue3(value) {
28328
29061
  function escapeRegExp4(value) {
28329
29062
  return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
28330
29063
  }
28331
- function isNodeError20(error, code) {
29064
+ function isNodeError21(error, code) {
28332
29065
  return error instanceof Error && "code" in error && error.code === code;
28333
29066
  }
28334
29067
 
@@ -28404,7 +29137,7 @@ function registerProfileMemoryRoutes(router, options) {
28404
29137
  try {
28405
29138
  ctx.body = await saveHermesMemorySettings(
28406
29139
  ctx.params.name,
28407
- readMemorySettingsPatch(body)
29140
+ readMemorySettingsPatch(body, { includeLimits: true })
28408
29141
  );
28409
29142
  } catch (error) {
28410
29143
  throw toMemoryHttpError(error);
@@ -28459,7 +29192,7 @@ function registerProfileMemoryRoutes(router, options) {
28459
29192
  );
28460
29193
  }
28461
29194
  function readMemoryTarget(body) {
28462
- const raw = readString17(body, "target");
29195
+ const raw = readString18(body, "target");
28463
29196
  if (raw === "memory" || raw === "user") {
28464
29197
  return raw;
28465
29198
  }
@@ -28470,7 +29203,7 @@ function readMemoryTarget(body) {
28470
29203
  );
28471
29204
  }
28472
29205
  function readMemoryResetTarget(body) {
28473
- const raw = readString17(body, "target") ?? "all";
29206
+ const raw = readString18(body, "target") ?? "all";
28474
29207
  if (raw === "all" || raw === "memory" || raw === "user") {
28475
29208
  return raw;
28476
29209
  }
@@ -28481,7 +29214,7 @@ function readMemoryResetTarget(body) {
28481
29214
  );
28482
29215
  }
28483
29216
  function readRequiredMemoryContent(body) {
28484
- const content = readString17(body, "content") ?? readString17(body, "text");
29217
+ const content = readString18(body, "content") ?? readString18(body, "text");
28485
29218
  if (!content) {
28486
29219
  throw new LinkHttpError(
28487
29220
  400,
@@ -28492,7 +29225,7 @@ function readRequiredMemoryContent(body) {
28492
29225
  return content;
28493
29226
  }
28494
29227
  function readRequiredMemoryMatch(body) {
28495
- const oldText = readString17(body, "old_text") ?? readString17(body, "oldText") ?? readString17(body, "match");
29228
+ const oldText = readString18(body, "old_text") ?? readString18(body, "oldText") ?? readString18(body, "match");
28496
29229
  if (!oldText) {
28497
29230
  throw new LinkHttpError(
28498
29231
  400,
@@ -28503,7 +29236,7 @@ function readRequiredMemoryMatch(body) {
28503
29236
  return oldText;
28504
29237
  }
28505
29238
  function readRequiredMemoryProvider(body) {
28506
- const provider = readString17(body, "provider") ?? readString17(body, "provider_id") ?? readString17(body, "providerId");
29239
+ const provider = readString18(body, "provider") ?? readString18(body, "provider_id") ?? readString18(body, "providerId");
28507
29240
  if (!provider) {
28508
29241
  throw new LinkHttpError(
28509
29242
  400,
@@ -28513,9 +29246,27 @@ function readRequiredMemoryProvider(body) {
28513
29246
  }
28514
29247
  return provider;
28515
29248
  }
28516
- function readMemorySettingsPatch(body) {
29249
+ function readMemorySettingsPatch(body, options = {}) {
28517
29250
  const input = {};
28518
- const mode = readString17(body, "mode");
29251
+ if (options.includeLimits) {
29252
+ const memoryCharLimit = readOptionalPositiveInteger(
29253
+ body,
29254
+ "memory_char_limit",
29255
+ "memoryCharLimit"
29256
+ );
29257
+ if (memoryCharLimit !== void 0) {
29258
+ input.memoryCharLimit = memoryCharLimit;
29259
+ }
29260
+ const userCharLimit = readOptionalPositiveInteger(
29261
+ body,
29262
+ "user_char_limit",
29263
+ "userCharLimit"
29264
+ );
29265
+ if (userCharLimit !== void 0) {
29266
+ input.userCharLimit = userCharLimit;
29267
+ }
29268
+ }
29269
+ const mode = readString18(body, "mode");
28519
29270
  if (mode) {
28520
29271
  input.mode = mode;
28521
29272
  }
@@ -28531,7 +29282,7 @@ function readMemorySettingsPatch(body) {
28531
29282
  if (bankId !== void 0) {
28532
29283
  input.bankId = bankId;
28533
29284
  }
28534
- const llmProvider = readString17(body, "llm_provider") ?? readString17(body, "llmProvider");
29285
+ const llmProvider = readString18(body, "llm_provider") ?? readString18(body, "llmProvider");
28535
29286
  if (llmProvider) {
28536
29287
  input.llmProvider = llmProvider;
28537
29288
  }
@@ -28567,11 +29318,11 @@ function readMemorySettingsPatch(body) {
28567
29318
  if (autoRetain !== void 0) {
28568
29319
  input.autoRetain = autoRetain;
28569
29320
  }
28570
- const memoryMode = readString17(body, "memory_mode") ?? readString17(body, "memoryMode");
29321
+ const memoryMode = readString18(body, "memory_mode") ?? readString18(body, "memoryMode");
28571
29322
  if (memoryMode) {
28572
29323
  input.memoryMode = memoryMode;
28573
29324
  }
28574
- const recallBudget = readString17(body, "recall_budget") ?? readString17(body, "recallBudget");
29325
+ const recallBudget = readString18(body, "recall_budget") ?? readString18(body, "recallBudget");
28575
29326
  if (recallBudget) {
28576
29327
  input.recallBudget = recallBudget;
28577
29328
  }
@@ -28587,11 +29338,11 @@ function readMemorySettingsPatch(body) {
28587
29338
  if (profileFrequency !== void 0) {
28588
29339
  input.profileFrequency = profileFrequency;
28589
29340
  }
28590
- const captureMode = readString17(body, "capture_mode") ?? readString17(body, "captureMode");
29341
+ const captureMode = readString18(body, "capture_mode") ?? readString18(body, "captureMode");
28591
29342
  if (captureMode) {
28592
29343
  input.captureMode = captureMode;
28593
29344
  }
28594
- const searchMode = readString17(body, "search_mode") ?? readString17(body, "searchMode");
29345
+ const searchMode = readString18(body, "search_mode") ?? readString18(body, "searchMode");
28595
29346
  if (searchMode) {
28596
29347
  input.searchMode = searchMode;
28597
29348
  }
@@ -28625,11 +29376,11 @@ function readMemorySettingsPatch(body) {
28625
29376
  if (aiPeer !== void 0) {
28626
29377
  input.aiPeer = aiPeer;
28627
29378
  }
28628
- const recallMode = readString17(body, "recall_mode") ?? readString17(body, "recallMode");
29379
+ const recallMode = readString18(body, "recall_mode") ?? readString18(body, "recallMode");
28629
29380
  if (recallMode) {
28630
29381
  input.recallMode = recallMode;
28631
29382
  }
28632
- const writeFrequency = readString17(body, "write_frequency") ?? readString17(body, "writeFrequency");
29383
+ const writeFrequency = readString18(body, "write_frequency") ?? readString18(body, "writeFrequency");
28633
29384
  if (writeFrequency) {
28634
29385
  input.writeFrequency = writeFrequency;
28635
29386
  }
@@ -28637,7 +29388,7 @@ function readMemorySettingsPatch(body) {
28637
29388
  if (saveMessages !== void 0) {
28638
29389
  input.saveMessages = saveMessages;
28639
29390
  }
28640
- const sessionStrategy = readString17(body, "session_strategy") ?? readString17(body, "sessionStrategy");
29391
+ const sessionStrategy = readString18(body, "session_strategy") ?? readString18(body, "sessionStrategy");
28641
29392
  if (sessionStrategy) {
28642
29393
  input.sessionStrategy = sessionStrategy;
28643
29394
  }
@@ -28735,6 +29486,23 @@ function readOptionalNumber(value, key) {
28735
29486
  }
28736
29487
  return numberValue;
28737
29488
  }
29489
+ function readOptionalPositiveInteger(body, ...keys) {
29490
+ for (const key of keys) {
29491
+ if (!Object.prototype.hasOwnProperty.call(body, key)) {
29492
+ continue;
29493
+ }
29494
+ const value = readPositiveInteger2(body[key]);
29495
+ if (value === void 0) {
29496
+ throw new LinkHttpError(
29497
+ 400,
29498
+ "memory_settings_invalid",
29499
+ `${key} must be a positive integer`
29500
+ );
29501
+ }
29502
+ return value;
29503
+ }
29504
+ return void 0;
29505
+ }
28738
29506
  function readOptionalString(body, ...keys) {
28739
29507
  for (const key of keys) {
28740
29508
  if (Object.prototype.hasOwnProperty.call(body, key)) {
@@ -28770,7 +29538,7 @@ function toMemoryHttpError(error) {
28770
29538
 
28771
29539
  // src/hermes/skills.ts
28772
29540
  import { readFile as readFile19, readdir as readdir11 } from "fs/promises";
28773
- import path26 from "path";
29541
+ import path27 from "path";
28774
29542
  import YAML7 from "yaml";
28775
29543
  var HermesSkillNotFoundError = class extends Error {
28776
29544
  constructor(skillName) {
@@ -28784,7 +29552,7 @@ var EXCLUDED_SKILL_DIRS = /* @__PURE__ */ new Set([".git", ".github", ".hub"]);
28784
29552
  async function listHermesProfileSkills(profileName, paths = resolveRuntimePaths()) {
28785
29553
  const profile = await readExistingProfile(profileName, paths);
28786
29554
  const profileDir = resolveHermesProfileDir(profile.name);
28787
- const skillsRoot = path26.join(profileDir, "skills");
29555
+ const skillsRoot = path27.join(profileDir, "skills");
28788
29556
  const [skillFiles, disabled, provenance] = await Promise.all([
28789
29557
  findSkillFiles(skillsRoot),
28790
29558
  readDisabledSkillNames(resolveHermesConfigPath(profile.name)),
@@ -28863,7 +29631,7 @@ async function findSkillFiles(root) {
28863
29631
  async function collectSkillFiles(directory, results) {
28864
29632
  const entries = await readdir11(directory, { withFileTypes: true }).catch(
28865
29633
  (error) => {
28866
- if (isNodeError21(error, "ENOENT")) {
29634
+ if (isNodeError22(error, "ENOENT")) {
28867
29635
  return [];
28868
29636
  }
28869
29637
  throw error;
@@ -28875,7 +29643,7 @@ async function collectSkillFiles(directory, results) {
28875
29643
  if (EXCLUDED_SKILL_DIRS.has(entry.name)) {
28876
29644
  continue;
28877
29645
  }
28878
- const entryPath = path26.join(directory, entry.name);
29646
+ const entryPath = path27.join(directory, entry.name);
28879
29647
  if (entry.isDirectory()) {
28880
29648
  await collectSkillFiles(entryPath, results);
28881
29649
  continue;
@@ -28888,7 +29656,7 @@ async function collectSkillFiles(directory, results) {
28888
29656
  async function readSkillMetadata(input) {
28889
29657
  const raw = await readFile19(input.skillFile, "utf8").catch(
28890
29658
  (error) => {
28891
- if (isNodeError21(error, "ENOENT") || isNodeError21(error, "EACCES")) {
29659
+ if (isNodeError22(error, "ENOENT") || isNodeError22(error, "EACCES")) {
28892
29660
  return null;
28893
29661
  }
28894
29662
  throw error;
@@ -28897,16 +29665,16 @@ async function readSkillMetadata(input) {
28897
29665
  if (raw === null) {
28898
29666
  return null;
28899
29667
  }
28900
- const skillDir = path26.dirname(input.skillFile);
29668
+ const skillDir = path27.dirname(input.skillFile);
28901
29669
  const { frontmatter, body } = parseSkillDocument(raw.slice(0, 4e3));
28902
29670
  const name = normalizeSkillName(
28903
- readString19(frontmatter.name) ?? path26.basename(skillDir)
29671
+ readString20(frontmatter.name) ?? path27.basename(skillDir)
28904
29672
  );
28905
29673
  if (!name) {
28906
29674
  return null;
28907
29675
  }
28908
29676
  const description = normalizeDescription(
28909
- readString19(frontmatter.description) ?? firstBodyDescription(body)
29677
+ readString20(frontmatter.description) ?? firstBodyDescription(body)
28910
29678
  );
28911
29679
  const provenance = input.provenance.get(name) ?? {
28912
29680
  source: "local",
@@ -28919,7 +29687,7 @@ async function readSkillMetadata(input) {
28919
29687
  enabled: !input.disabled.has(name),
28920
29688
  source: provenance.source,
28921
29689
  trust: provenance.trust,
28922
- relativePath: path26.relative(input.skillsRoot, skillDir)
29690
+ relativePath: path27.relative(input.skillsRoot, skillDir)
28923
29691
  };
28924
29692
  }
28925
29693
  function parseSkillDocument(raw) {
@@ -28940,8 +29708,8 @@ function parseSkillDocument(raw) {
28940
29708
  }
28941
29709
  }
28942
29710
  function categoryFromPath(skillsRoot, skillFile) {
28943
- const relative = path26.relative(skillsRoot, skillFile);
28944
- const parts = relative.split(path26.sep).filter(Boolean);
29711
+ const relative = path27.relative(skillsRoot, skillFile);
29712
+ const parts = relative.split(path27.sep).filter(Boolean);
28945
29713
  return parts.length >= 3 ? parts[0] : null;
28946
29714
  }
28947
29715
  function firstBodyDescription(body) {
@@ -28965,7 +29733,7 @@ function normalizeDescription(value) {
28965
29733
  }
28966
29734
  async function readDisabledSkillNames(configPath) {
28967
29735
  const raw = await readFile19(configPath, "utf8").catch((error) => {
28968
- if (isNodeError21(error, "ENOENT")) {
29736
+ if (isNodeError22(error, "ENOENT")) {
28969
29737
  return "";
28970
29738
  }
28971
29739
  throw error;
@@ -28988,9 +29756,9 @@ async function readSkillProvenance(root) {
28988
29756
  return provenance;
28989
29757
  }
28990
29758
  async function readBundledSkillNames(root) {
28991
- const raw = await readFile19(path26.join(root, ".bundled_manifest"), "utf8").catch(
29759
+ const raw = await readFile19(path27.join(root, ".bundled_manifest"), "utf8").catch(
28992
29760
  (error) => {
28993
- if (isNodeError21(error, "ENOENT")) {
29761
+ if (isNodeError22(error, "ENOENT")) {
28994
29762
  return "";
28995
29763
  }
28996
29764
  throw error;
@@ -29011,9 +29779,9 @@ async function readBundledSkillNames(root) {
29011
29779
  return names;
29012
29780
  }
29013
29781
  async function readHubInstalledSkills(root) {
29014
- const raw = await readFile19(path26.join(root, ".hub", "lock.json"), "utf8").catch(
29782
+ const raw = await readFile19(path27.join(root, ".hub", "lock.json"), "utf8").catch(
29015
29783
  (error) => {
29016
- if (isNodeError21(error, "ENOENT")) {
29784
+ if (isNodeError22(error, "ENOENT")) {
29017
29785
  return "";
29018
29786
  }
29019
29787
  throw error;
@@ -29033,8 +29801,8 @@ async function readHubInstalledSkills(root) {
29033
29801
  for (const [name, rawEntry] of Object.entries(installed2)) {
29034
29802
  const entry = toRecord19(rawEntry);
29035
29803
  result.set(normalizeSkillName(name), {
29036
- source: readString19(entry.source) ?? "hub",
29037
- trust: readString19(entry.trust_level) ?? null
29804
+ source: readString20(entry.source) ?? "hub",
29805
+ trust: readString20(entry.trust_level) ?? null
29038
29806
  });
29039
29807
  }
29040
29808
  return result;
@@ -29085,7 +29853,7 @@ function compareCategoryNames(left, right) {
29085
29853
  async function readHermesConfigDocument4(configPath) {
29086
29854
  const existingRaw = await readFile19(configPath, "utf8").catch(
29087
29855
  (error) => {
29088
- if (isNodeError21(error, "ENOENT")) {
29856
+ if (isNodeError22(error, "ENOENT")) {
29089
29857
  return null;
29090
29858
  }
29091
29859
  throw error;
@@ -29118,7 +29886,7 @@ function readStringList4(value) {
29118
29886
  }
29119
29887
  return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
29120
29888
  }
29121
- function readString19(value) {
29889
+ function readString20(value) {
29122
29890
  return typeof value === "string" && value.trim() ? value.trim() : null;
29123
29891
  }
29124
29892
  function toRecord19(value) {
@@ -29132,7 +29900,7 @@ function ensureRecord5(target, key) {
29132
29900
  target[key] = current;
29133
29901
  return current;
29134
29902
  }
29135
- function isNodeError21(error, code) {
29903
+ function isNodeError22(error, code) {
29136
29904
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
29137
29905
  }
29138
29906
 
@@ -29489,7 +30257,7 @@ function registerRunRoutes(router, options) {
29489
30257
  router.post("/api/v1/runs", async (ctx) => {
29490
30258
  await authenticateRequest(ctx, paths);
29491
30259
  const body = await readJsonBody(ctx.req);
29492
- const input = readString17(body, "input");
30260
+ const input = readString18(body, "input");
29493
30261
  if (!input) {
29494
30262
  throw new LinkHttpError(400, "run_input_required", "input is required");
29495
30263
  }
@@ -29497,12 +30265,12 @@ function registerRunRoutes(router, options) {
29497
30265
  ctx.body = await createHermesRun(
29498
30266
  {
29499
30267
  input,
29500
- instructions: readString17(body, "instructions") ?? void 0,
30268
+ instructions: readString18(body, "instructions") ?? void 0,
29501
30269
  conversation_history: readConversationHistory(
29502
30270
  body.conversation_history ?? body.conversationHistory
29503
30271
  ),
29504
- session_id: readString17(body, "session_id") ?? readString17(body, "sessionId") ?? void 0,
29505
- session_key: readString17(body, "session_key") ?? readString17(body, "sessionKey") ?? void 0
30272
+ session_id: readString18(body, "session_id") ?? readString18(body, "sessionId") ?? void 0,
30273
+ session_key: readString18(body, "session_key") ?? readString18(body, "sessionKey") ?? void 0
29506
30274
  },
29507
30275
  { logger, profileName: readOptionalProfileName(body) }
29508
30276
  );
@@ -29673,8 +30441,8 @@ function readModelList(payload) {
29673
30441
  // src/hermes/updates.ts
29674
30442
  import { EventEmitter as EventEmitter3 } from "events";
29675
30443
  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";
30444
+ import { mkdir as mkdir13, readFile as readFile20, rm as rm8 } from "fs/promises";
30445
+ import path28 from "path";
29678
30446
  var SERVER_HERMES_RELEASES_LATEST_PATH = "/api/v1/hermes-agent/releases/latest";
29679
30447
  var RELEASE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
29680
30448
  var RELEASE_FETCH_TIMEOUT_MS = 5e3;
@@ -29737,7 +30505,7 @@ async function startHermesUpdate(options) {
29737
30505
  signal: null,
29738
30506
  error: null
29739
30507
  };
29740
- await mkdir12(options.paths.runDir, { recursive: true, mode: 448 });
30508
+ await mkdir13(options.paths.runDir, { recursive: true, mode: 448 });
29741
30509
  await writer.write(`
29742
30510
  === hermes update started ${startedAt} ===
29743
30511
  `);
@@ -29911,20 +30679,20 @@ function normalizeServerReleaseSnapshot(payload) {
29911
30679
  const remote = toNullableRecord(snapshot.remote);
29912
30680
  return {
29913
30681
  remote: remote ? normalizeServerRelease(remote) : null,
29914
- cacheState: readString20(snapshot, "cache_state") ?? readString20(snapshot, "cacheState"),
29915
- issue: readString20(snapshot, "issue")
30682
+ cacheState: readString21(snapshot, "cache_state") ?? readString21(snapshot, "cacheState"),
30683
+ issue: readString21(snapshot, "issue")
29916
30684
  };
29917
30685
  }
29918
30686
  function normalizeServerRelease(payload) {
29919
- const tag = readString20(payload, "tag");
29920
- const name = readString20(payload, "name");
30687
+ const tag = readString21(payload, "tag");
30688
+ const name = readString21(payload, "name");
29921
30689
  return {
29922
- version: readString20(payload, "version") ?? extractSemver(name) ?? extractTagSemver(tag),
30690
+ version: readString21(payload, "version") ?? extractSemver(name) ?? extractTagSemver(tag),
29923
30691
  tag,
29924
30692
  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()
30693
+ releaseUrl: readString21(payload, "releaseUrl") ?? readString21(payload, "release_url"),
30694
+ publishedAt: readString21(payload, "publishedAt") ?? readString21(payload, "published_at"),
30695
+ fetchedAt: readString21(payload, "fetchedAt") ?? readString21(payload, "fetched_at") ?? (/* @__PURE__ */ new Date()).toISOString()
29928
30696
  };
29929
30697
  }
29930
30698
  async function readReleaseCache(paths) {
@@ -29952,21 +30720,21 @@ async function readUpdateLogLines(paths) {
29952
30720
  );
29953
30721
  }
29954
30722
  function releaseCachePath(paths) {
29955
- return path27.join(paths.indexesDir, "hermes-release-check.json");
30723
+ return path28.join(paths.indexesDir, "hermes-release-check.json");
29956
30724
  }
29957
30725
  function updateStatePath(paths) {
29958
- return path27.join(paths.runDir, "hermes-update-state.json");
30726
+ return path28.join(paths.runDir, "hermes-update-state.json");
29959
30727
  }
29960
30728
  function updateLogPath(paths) {
29961
- return path27.join(paths.logsDir, UPDATE_LOG_FILE);
30729
+ return path28.join(paths.logsDir, UPDATE_LOG_FILE);
29962
30730
  }
29963
30731
  async function clearUpdateLogFiles(paths) {
29964
30732
  const primary = updateLogPath(paths);
29965
30733
  await Promise.all([
29966
- rm7(primary, { force: true }).catch(() => void 0),
30734
+ rm8(primary, { force: true }).catch(() => void 0),
29967
30735
  ...Array.from(
29968
30736
  { length: UPDATE_LOG_MAX_FILES },
29969
- (_, index) => rm7(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
30737
+ (_, index) => rm8(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
29970
30738
  )
29971
30739
  ]);
29972
30740
  }
@@ -30050,7 +30818,7 @@ function isRecentRunningState2(state) {
30050
30818
  const startedAt = Date.parse(state.started_at);
30051
30819
  return Number.isFinite(startedAt) && Date.now() - startedAt < 3e4;
30052
30820
  }
30053
- function readString20(payload, key) {
30821
+ function readString21(payload, key) {
30054
30822
  const value = payload[key];
30055
30823
  return typeof value === "string" && value.trim() ? value.trim() : null;
30056
30824
  }
@@ -30058,17 +30826,17 @@ function readString20(payload, key) {
30058
30826
  // src/link/updates.ts
30059
30827
  import { spawn as spawn5 } from "child_process";
30060
30828
  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";
30829
+ import { mkdir as mkdir16, readFile as readFile22, rm as rm11 } from "fs/promises";
30830
+ import path30 from "path";
30063
30831
 
30064
30832
  // src/daemon/process.ts
30065
30833
  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";
30834
+ import { mkdir as mkdir15, readFile as readFile21, rm as rm10, writeFile as writeFile4 } from "fs/promises";
30835
+ import path29 from "path";
30068
30836
 
30069
30837
  // src/daemon/service.ts
30070
30838
  import { createServer } from "http";
30071
- import { mkdir as mkdir13, rm as rm8, writeFile as writeFile3 } from "fs/promises";
30839
+ import { mkdir as mkdir14, rm as rm9, writeFile as writeFile3 } from "fs/promises";
30072
30840
 
30073
30841
  // src/relay/control-client.ts
30074
30842
  import WebSocket from "ws";
@@ -31113,11 +31881,11 @@ async function mergeLastReportedPublicRoutes(paths, snapshotInput) {
31113
31881
  const state = await readNetworkReportState(paths);
31114
31882
  return {
31115
31883
  ...snapshotInput,
31116
- publicIpv4s: uniqueStrings([
31884
+ publicIpv4s: uniqueStrings2([
31117
31885
  ...snapshotInput.publicIpv4s,
31118
31886
  ...state.lastReportedPublicIpv4s
31119
31887
  ]).slice(0, 2),
31120
- publicIpv6s: uniqueStrings([
31888
+ publicIpv6s: uniqueStrings2([
31121
31889
  ...snapshotInput.publicIpv6s,
31122
31890
  ...state.lastReportedPublicIpv6s
31123
31891
  ]).slice(0, 2)
@@ -31201,15 +31969,15 @@ function normalizeLanIps(value) {
31201
31969
  ];
31202
31970
  }
31203
31971
  function sameNetworkSnapshot(left, right) {
31204
- return sameStringList(left.lanIps, right.lanIps) && sameStringList(left.publicIpv4s, right.publicIpv4s) && sameStringList(left.publicIpv6s, right.publicIpv6s);
31972
+ return sameStringList2(left.lanIps, right.lanIps) && sameStringList2(left.publicIpv4s, right.publicIpv4s) && sameStringList2(left.publicIpv6s, right.publicIpv6s);
31205
31973
  }
31206
- function sameStringList(left, right) {
31974
+ function sameStringList2(left, right) {
31207
31975
  if (left.length !== right.length) {
31208
31976
  return false;
31209
31977
  }
31210
31978
  return left.every((value, index) => value === right[index]);
31211
31979
  }
31212
- function uniqueStrings(values) {
31980
+ function uniqueStrings2(values) {
31213
31981
  return [...new Set(values)];
31214
31982
  }
31215
31983
  function formatUtcDay(date) {
@@ -31844,7 +32612,7 @@ async function startLinkService(options = {}) {
31844
32612
  await logger.info("service_stopped");
31845
32613
  await logger.flush();
31846
32614
  if (options.writePidFile) {
31847
- await rm8(pidFilePath(paths), { force: true }).catch(() => void 0);
32615
+ await rm9(pidFilePath(paths), { force: true }).catch(() => void 0);
31848
32616
  }
31849
32617
  }
31850
32618
  };
@@ -31894,7 +32662,7 @@ function pidFilePath(paths = resolveRuntimePaths()) {
31894
32662
  return `${paths.runDir}/hermeslink.pid`;
31895
32663
  }
31896
32664
  async function writePidFile(paths) {
31897
- await mkdir13(paths.runDir, { recursive: true, mode: 448 });
32665
+ await mkdir14(paths.runDir, { recursive: true, mode: 448 });
31898
32666
  await writeFile3(pidFilePath(paths), `${process.pid}
31899
32667
  `, { mode: 384 });
31900
32668
  }
@@ -31989,8 +32757,8 @@ async function startDaemonProcess(paths = resolveRuntimePaths()) {
31989
32757
  return status;
31990
32758
  }
31991
32759
  }
31992
- await mkdir14(paths.logsDir, { recursive: true, mode: 448 });
31993
- await mkdir14(paths.runDir, { recursive: true, mode: 448 });
32760
+ await mkdir15(paths.logsDir, { recursive: true, mode: 448 });
32761
+ await mkdir15(paths.runDir, { recursive: true, mode: 448 });
31994
32762
  const scriptPath = currentCliScriptPath();
31995
32763
  const child = spawn4(process.execPath, [scriptPath, "daemon-supervisor"], {
31996
32764
  detached: true,
@@ -32008,10 +32776,10 @@ async function startDaemonProcess(paths = resolveRuntimePaths()) {
32008
32776
  return await getDaemonStatus(paths);
32009
32777
  }
32010
32778
  async function runDaemonSupervisor(paths = resolveRuntimePaths()) {
32011
- await mkdir14(paths.logsDir, { recursive: true, mode: 448 });
32779
+ await mkdir15(paths.logsDir, { recursive: true, mode: 448 });
32012
32780
  const log = createRotatingTextLogWriter({
32013
32781
  paths,
32014
- fileName: path28.basename(daemonLogFile(paths))
32782
+ fileName: path29.basename(daemonLogFile(paths))
32015
32783
  });
32016
32784
  const scriptPath = currentCliScriptPath();
32017
32785
  const write = (chunk) => {
@@ -32138,7 +32906,7 @@ async function stopDaemonProcess(paths = resolveRuntimePaths()) {
32138
32906
  try {
32139
32907
  process.kill(status.pid, "SIGTERM");
32140
32908
  } catch {
32141
- await rm9(pidFilePath(paths), { force: true }).catch(() => void 0);
32909
+ await rm10(pidFilePath(paths), { force: true }).catch(() => void 0);
32142
32910
  return await getDaemonStatus(paths);
32143
32911
  }
32144
32912
  for (let index = 0; index < 20; index += 1) {
@@ -32160,7 +32928,7 @@ async function stopDaemonProcess(paths = resolveRuntimePaths()) {
32160
32928
  }
32161
32929
  }
32162
32930
  if (!isProcessAlive3(status.pid) || !await pidBackedServiceIsReachable(paths)) {
32163
- await rm9(pidFilePath(paths), { force: true }).catch(() => void 0);
32931
+ await rm10(pidFilePath(paths), { force: true }).catch(() => void 0);
32164
32932
  }
32165
32933
  return await getDaemonStatus(paths);
32166
32934
  }
@@ -32168,7 +32936,7 @@ async function getDaemonStatus(paths = resolveRuntimePaths()) {
32168
32936
  const pidFile = pidFilePath(paths);
32169
32937
  const pid = await readPid(pidFile);
32170
32938
  if (pid && !isProcessAlive3(pid)) {
32171
- await rm9(pidFile, { force: true }).catch(() => void 0);
32939
+ await rm10(pidFile, { force: true }).catch(() => void 0);
32172
32940
  return {
32173
32941
  running: false,
32174
32942
  pid: null,
@@ -32309,10 +33077,10 @@ function terminateChild(child, previousForceKillTimer) {
32309
33077
  }
32310
33078
  }
32311
33079
  function supervisorStopIntentPath(paths) {
32312
- return path28.join(paths.runDir, "supervisor-stop-intent.json");
33080
+ return path29.join(paths.runDir, "supervisor-stop-intent.json");
32313
33081
  }
32314
33082
  async function writeSupervisorStopIntent(paths, pid) {
32315
- await mkdir14(paths.runDir, { recursive: true, mode: 448 });
33083
+ await mkdir15(paths.runDir, { recursive: true, mode: 448 });
32316
33084
  await writeFile4(
32317
33085
  supervisorStopIntentPath(paths),
32318
33086
  `${JSON.stringify({ pid, created_at: (/* @__PURE__ */ new Date()).toISOString() })}
@@ -32328,14 +33096,14 @@ async function consumeSupervisorStopIntent(paths, pid) {
32328
33096
  }
32329
33097
  const payload = parseSupervisorStopIntent(raw);
32330
33098
  if (!isValidSupervisorStopIntent(payload)) {
32331
- await rm9(filePath, { force: true }).catch(() => void 0);
33099
+ await rm10(filePath, { force: true }).catch(() => void 0);
32332
33100
  return false;
32333
33101
  }
32334
33102
  if (payload.pid !== pid) {
32335
- await rm9(filePath, { force: true }).catch(() => void 0);
33103
+ await rm10(filePath, { force: true }).catch(() => void 0);
32336
33104
  return false;
32337
33105
  }
32338
- await rm9(filePath, { force: true }).catch(() => void 0);
33106
+ await rm10(filePath, { force: true }).catch(() => void 0);
32339
33107
  return true;
32340
33108
  }
32341
33109
  async function clearExpiredSupervisorStopIntent(paths) {
@@ -32346,7 +33114,7 @@ async function clearExpiredSupervisorStopIntent(paths) {
32346
33114
  }
32347
33115
  const payload = parseSupervisorStopIntent(raw);
32348
33116
  if (!isValidSupervisorStopIntent(payload)) {
32349
- await rm9(filePath, { force: true }).catch(() => void 0);
33117
+ await rm10(filePath, { force: true }).catch(() => void 0);
32350
33118
  }
32351
33119
  }
32352
33120
  function parseSupervisorStopIntent(raw) {
@@ -32481,7 +33249,7 @@ async function startLinkUpdate(options) {
32481
33249
  error: null,
32482
33250
  manual_command: manualCommand
32483
33251
  };
32484
- await mkdir15(options.paths.runDir, { recursive: true, mode: 448 });
33252
+ await mkdir16(options.paths.runDir, { recursive: true, mode: 448 });
32485
33253
  await writer.write(
32486
33254
  `
32487
33255
  === link update started ${startedAt} target=${targetVersion} ===
@@ -32789,16 +33557,16 @@ function normalizeServerSnapshot(payload) {
32789
33557
  if (!policy) {
32790
33558
  return {
32791
33559
  remote: null,
32792
- issue: readString21(snapshot, "issue")
33560
+ issue: readString22(snapshot, "issue")
32793
33561
  };
32794
33562
  }
32795
33563
  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");
33564
+ const currentVersion = readString22(policy, "current_version") ?? readString22(policy, "currentVersion");
33565
+ const minSafeVersion = readString22(policy, "min_safe_version") ?? readString22(policy, "minSafeVersion");
32798
33566
  if (!currentVersion) {
32799
33567
  return {
32800
33568
  remote: null,
32801
- issue: readString21(snapshot, "issue")
33569
+ issue: readString22(snapshot, "issue")
32802
33570
  };
32803
33571
  }
32804
33572
  return {
@@ -32806,10 +33574,10 @@ function normalizeServerSnapshot(payload) {
32806
33574
  current_version: currentVersion,
32807
33575
  min_safe_version: minSafeVersion,
32808
33576
  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
33577
+ release_url: release ? readString22(release, "release_url") ?? readString22(release, "releaseUrl") : null,
33578
+ published_at: release ? readString22(release, "published_at") ?? readString22(release, "publishedAt") : null
32811
33579
  },
32812
- issue: readString21(snapshot, "issue")
33580
+ issue: readString22(snapshot, "issue")
32813
33581
  };
32814
33582
  }
32815
33583
  async function fetchCurrentLinkReleaseFromServer(options, fetcher, channel) {
@@ -32872,7 +33640,7 @@ async function buildOfficialInstallCommand(options, targetVersion) {
32872
33640
  };
32873
33641
  }
32874
33642
  function buildUnixInstallCommand(installerUrl) {
32875
- const nodeBinDir = path29.dirname(process.execPath);
33643
+ const nodeBinDir = path30.dirname(process.execPath);
32876
33644
  const fetchScript = [
32877
33645
  quoteShellToken(process.execPath),
32878
33646
  "--input-type=module",
@@ -33142,18 +33910,18 @@ async function readUpdateLogLines2(paths) {
33142
33910
  );
33143
33911
  }
33144
33912
  function updateStatePath2(paths) {
33145
- return path29.join(paths.runDir, "link-update-state.json");
33913
+ return path30.join(paths.runDir, "link-update-state.json");
33146
33914
  }
33147
33915
  function updateLogPath2(paths) {
33148
- return path29.join(paths.logsDir, UPDATE_LOG_FILE2);
33916
+ return path30.join(paths.logsDir, UPDATE_LOG_FILE2);
33149
33917
  }
33150
33918
  async function clearUpdateLogFiles2(paths) {
33151
33919
  const primary = updateLogPath2(paths);
33152
33920
  await Promise.all([
33153
- rm10(primary, { force: true }).catch(() => void 0),
33921
+ rm11(primary, { force: true }).catch(() => void 0),
33154
33922
  ...Array.from(
33155
33923
  { length: UPDATE_LOG_MAX_FILES2 },
33156
- (_, index) => rm10(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
33924
+ (_, index) => rm11(`${primary}.${index + 1}`, { force: true }).catch(() => void 0)
33157
33925
  )
33158
33926
  ]);
33159
33927
  }
@@ -33223,14 +33991,14 @@ function toRecord22(value) {
33223
33991
  function toNullableRecord2(value) {
33224
33992
  return typeof value === "object" && value !== null ? value : null;
33225
33993
  }
33226
- function readString21(payload, key) {
33994
+ function readString22(payload, key) {
33227
33995
  const value = payload[key];
33228
33996
  return typeof value === "string" && value.trim() ? value.trim() : null;
33229
33997
  }
33230
33998
 
33231
33999
  // src/pairing/pairing.ts
33232
- import path30 from "path";
33233
- import { rm as rm11 } from "fs/promises";
34000
+ import path31 from "path";
34001
+ import { rm as rm12 } from "fs/promises";
33234
34002
 
33235
34003
  // src/relay/bootstrap.ts
33236
34004
  var RelayNetworkError = class extends Error {
@@ -33492,7 +34260,7 @@ async function readPairingClaim(sessionId, paths = resolveRuntimePaths()) {
33492
34260
  };
33493
34261
  }
33494
34262
  async function clearPairingClaim(sessionId, paths = resolveRuntimePaths()) {
33495
- await rm11(pairingClaimPath(sessionId, paths), { force: true }).catch(() => void 0);
34263
+ await rm12(pairingClaimPath(sessionId, paths), { force: true }).catch(() => void 0);
33496
34264
  }
33497
34265
  async function claimPairing(input) {
33498
34266
  const paths = input.paths ?? resolveRuntimePaths();
@@ -33569,10 +34337,10 @@ async function loadRequiredIdentity2(paths) {
33569
34337
  }
33570
34338
  return identity;
33571
34339
  }
33572
- async function postServerJson(serverBaseUrl, path31, body, options) {
34340
+ async function postServerJson(serverBaseUrl, path32, body, options) {
33573
34341
  let response;
33574
34342
  try {
33575
- response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path31}`, {
34343
+ response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path32}`, {
33576
34344
  method: "POST",
33577
34345
  headers: {
33578
34346
  accept: "application/json",
@@ -33620,10 +34388,10 @@ function pairingErrorSnapshot(stage, error) {
33620
34388
  occurred_at: (/* @__PURE__ */ new Date()).toISOString()
33621
34389
  };
33622
34390
  }
33623
- async function patchServerJson(serverBaseUrl, path31, token, body, options) {
34391
+ async function patchServerJson(serverBaseUrl, path32, token, body, options) {
33624
34392
  let response;
33625
34393
  try {
33626
- response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path31}`, {
34394
+ response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path32}`, {
33627
34395
  method: "PATCH",
33628
34396
  headers: {
33629
34397
  accept: "application/json",
@@ -33671,10 +34439,10 @@ function createPairingNetworkError(input) {
33671
34439
  );
33672
34440
  }
33673
34441
  function pairingClaimPath(sessionId, paths) {
33674
- return path30.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
34442
+ return path31.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
33675
34443
  }
33676
34444
  function pairingSessionPath(sessionId, paths) {
33677
- return path30.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
34445
+ return path31.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
33678
34446
  }
33679
34447
  function qrPreferredUrls(routes) {
33680
34448
  return routes.preferredUrls.filter((url) => !url.includes("/api/v1/relay/links/")).slice(0, 1);
@@ -33754,8 +34522,8 @@ function registerSystemRoutes(router, options) {
33754
34522
  });
33755
34523
  router.post("/api/v1/pairing/claim", async (ctx) => {
33756
34524
  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");
34525
+ const sessionId = readString18(body, "session_id") ?? readString18(body, "sessionId");
34526
+ const claimToken = readString18(body, "claim_token") ?? readString18(body, "claimToken");
33759
34527
  if (!sessionId || !claimToken) {
33760
34528
  throw new LinkHttpError(
33761
34529
  400,
@@ -33766,10 +34534,10 @@ function registerSystemRoutes(router, options) {
33766
34534
  const claimed = await claimPairing({
33767
34535
  sessionId,
33768
34536
  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"),
34537
+ deviceLabel: readString18(body, "device_label") ?? readString18(body, "deviceLabel") ?? "HermesPilot App",
34538
+ devicePlatform: readString18(body, "device_platform") ?? readString18(body, "devicePlatform") ?? "unknown",
34539
+ deviceModel: readString18(body, "device_model") ?? readString18(body, "deviceModel"),
34540
+ appInstanceId: readString18(body, "app_instance_id") ?? readString18(body, "appInstanceId"),
33773
34541
  paths
33774
34542
  });
33775
34543
  ctx.body = claimed;
@@ -33849,9 +34617,9 @@ function registerSystemRoutes(router, options) {
33849
34617
  const body = await readJsonBody(ctx.req);
33850
34618
  const session = await createDeviceSession(
33851
34619
  {
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"),
34620
+ label: readString18(body, "device_label") ?? readString18(body, "deviceLabel") ?? "HermesPilot App",
34621
+ platform: readString18(body, "device_platform") ?? readString18(body, "devicePlatform") ?? "unknown",
34622
+ model: readString18(body, "device_model") ?? readString18(body, "deviceModel"),
33855
34623
  appInstanceId: auth.appInstanceId
33856
34624
  },
33857
34625
  paths
@@ -33880,7 +34648,7 @@ function registerSystemRoutes(router, options) {
33880
34648
  });
33881
34649
  router.post("/api/v1/auth/refresh", async (ctx) => {
33882
34650
  const body = await readJsonBody(ctx.req);
33883
- const refreshToken = readString17(body, "refresh_token") ?? readString17(body, "refreshToken");
34651
+ const refreshToken = readString18(body, "refresh_token") ?? readString18(body, "refreshToken");
33884
34652
  if (!refreshToken) {
33885
34653
  throw new LinkHttpError(
33886
34654
  400,
@@ -33891,10 +34659,10 @@ function registerSystemRoutes(router, options) {
33891
34659
  const session = await refreshDeviceSession(
33892
34660
  refreshToken,
33893
34661
  {
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")
34662
+ appInstanceId: readString18(body, "app_instance_id") ?? readString18(body, "appInstanceId"),
34663
+ label: readString18(body, "device_label") ?? readString18(body, "deviceLabel"),
34664
+ platform: readString18(body, "device_platform") ?? readString18(body, "devicePlatform"),
34665
+ model: readString18(body, "device_model") ?? readString18(body, "deviceModel")
33898
34666
  },
33899
34667
  paths
33900
34668
  );
@@ -33913,7 +34681,7 @@ function registerSystemRoutes(router, options) {
33913
34681
  });
33914
34682
  router.post("/api/v1/auth/logout", async (ctx) => {
33915
34683
  const body = await readJsonBody(ctx.req);
33916
- const refreshToken = readString17(body, "refresh_token") ?? readString17(body, "refreshToken");
34684
+ const refreshToken = readString18(body, "refresh_token") ?? readString18(body, "refreshToken");
33917
34685
  if (refreshToken) {
33918
34686
  await revokeDeviceRefreshToken(refreshToken, paths);
33919
34687
  }
@@ -34139,7 +34907,7 @@ function registerSystemRoutes(router, options) {
34139
34907
  router.patch("/api/v1/devices/:deviceId", async (ctx) => {
34140
34908
  const auth = await authenticateRequest(ctx, paths);
34141
34909
  const body = await readJsonBody(ctx.req);
34142
- const label = readString17(body, "label") ?? readString17(body, "device_label");
34910
+ const label = readString18(body, "label") ?? readString18(body, "device_label");
34143
34911
  if (!label) {
34144
34912
  throw new LinkHttpError(
34145
34913
  400,
@@ -34183,7 +34951,7 @@ function isActiveCronJob(job) {
34183
34951
  if (!enabled) {
34184
34952
  return false;
34185
34953
  }
34186
- const state = readString17(job, "state")?.toLowerCase();
34954
+ const state = readString18(job, "state")?.toLowerCase();
34187
34955
  return !["paused", "disabled", "completed", "deleted"].includes(state ?? "");
34188
34956
  }
34189
34957
  function filterLogsWithinHours(logs, hours, now = Date.now()) {
@@ -34277,8 +35045,8 @@ function registerLinkUpdateRoutes(router, options) {
34277
35045
  ctx.body = await startLinkUpdate({
34278
35046
  paths,
34279
35047
  logger,
34280
- channel: readString17(body, "channel"),
34281
- targetVersion: readString17(body, "target_version") ?? readString17(body, "targetVersion")
35048
+ channel: readString18(body, "channel"),
35049
+ targetVersion: readString18(body, "target_version") ?? readString18(body, "targetVersion")
34282
35050
  });
34283
35051
  });
34284
35052
  router.get("/api/v1/link/update/events", async (ctx) => {
@@ -34308,7 +35076,7 @@ import QRCode from "qrcode";
34308
35076
  function registerPairingRoutes(router, options) {
34309
35077
  const { paths } = options;
34310
35078
  router.get("/pair", async (ctx) => {
34311
- const sessionId = readString17(ctx.query, "session_id");
35079
+ const sessionId = readString18(ctx.query, "session_id");
34312
35080
  if (!sessionId) {
34313
35081
  throw new LinkHttpError(400, "pairing_session_required", "session_id is required");
34314
35082
  }
@@ -34333,7 +35101,7 @@ function registerPairingRoutes(router, options) {
34333
35101
  ctx.body = page;
34334
35102
  });
34335
35103
  router.get("/api/v1/pairing/session", async (ctx) => {
34336
- const sessionId = readString17(ctx.query, "session_id");
35104
+ const sessionId = readString18(ctx.query, "session_id");
34337
35105
  if (!sessionId) {
34338
35106
  throw new LinkHttpError(400, "pairing_session_required", "session_id is required");
34339
35107
  }
@@ -34784,7 +35552,7 @@ function registerInternalRoutes(router, options) {
34784
35552
  router.post("/internal/deliver", async (ctx) => {
34785
35553
  assertLoopbackRequest(ctx.req);
34786
35554
  const body = await readJsonBody(ctx.req);
34787
- const stagingDir = readString17(body, "staging_dir") ?? readString17(body, "stagingDir");
35555
+ const stagingDir = readString18(body, "staging_dir") ?? readString18(body, "stagingDir");
34788
35556
  if (!stagingDir) {
34789
35557
  throw new LinkHttpError(
34790
35558
  400,
@@ -34797,6 +35565,19 @@ function registerInternalRoutes(router, options) {
34797
35565
  ...await options.conversations.deliverStagedFiles(stagingDir)
34798
35566
  };
34799
35567
  });
35568
+ router.post("/internal/deliver-tool", async (ctx) => {
35569
+ assertLoopbackRequest(ctx.req);
35570
+ const body = await readJsonBody(ctx.req);
35571
+ ctx.body = {
35572
+ ok: true,
35573
+ ...await options.conversations.deliverFilesFromTool({
35574
+ profile: readString18(body, "profile") ?? readString18(body, "profile_name") ?? readString18(body, "profileName") ?? void 0,
35575
+ taskId: readString18(body, "task_id") ?? readString18(body, "taskId") ?? void 0,
35576
+ sessionId: readString18(body, "session_id") ?? readString18(body, "sessionId") ?? void 0,
35577
+ files: body.files ?? body.file ?? body.path
35578
+ })
35579
+ };
35580
+ });
34800
35581
  }
34801
35582
  function assertLoopbackRequest(request) {
34802
35583
  const address = request.socket.remoteAddress;