@hermespilot/link 0.3.4 → 0.3.6

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.
@@ -4,7 +4,7 @@ import Router from "@koa/router";
4
4
 
5
5
  // src/conversations/conversation-service.ts
6
6
  import { EventEmitter } from "events";
7
- import { randomUUID as randomUUID6 } from "crypto";
7
+ import { randomUUID as randomUUID7 } from "crypto";
8
8
 
9
9
  // src/database/link-database.ts
10
10
  import { mkdir } from "fs/promises";
@@ -3637,7 +3637,7 @@ async function listCronOutputFiles(profileName, jobId) {
3637
3637
  mtimeMs: fileStat.mtimeMs
3638
3638
  });
3639
3639
  }
3640
- return files.sort((left, right) => left.mtimeMs - right.mtimeMs).map(({ path: path24, mtime }) => ({ path: path24, mtime }));
3640
+ return files.sort((left, right) => left.mtimeMs - right.mtimeMs).map(({ path: path25, mtime }) => ({ path: path25, mtime }));
3641
3641
  }
3642
3642
  async function readCronOutput(outputPath) {
3643
3643
  const content = await readFile3(outputPath, "utf8");
@@ -3724,7 +3724,7 @@ import os2 from "os";
3724
3724
  import path5 from "path";
3725
3725
 
3726
3726
  // src/constants.ts
3727
- var LINK_VERSION = "0.3.4";
3727
+ var LINK_VERSION = "0.3.6";
3728
3728
  var LINK_COMMAND = "hermeslink";
3729
3729
  var LINK_DEFAULT_PORT = 52379;
3730
3730
  var LINK_RUNTIME_DIR_NAME = ".hermeslink";
@@ -4859,6 +4859,9 @@ function toRecord3(value) {
4859
4859
  }
4860
4860
 
4861
4861
  // src/conversations/statistics.ts
4862
+ var ESTIMATED_CONTEXT_CHARS_PER_TOKEN = 4;
4863
+ var ESTIMATED_CONTEXT_BASE_OVERHEAD_TOKENS = 256;
4864
+ var ESTIMATED_CONTEXT_PER_MESSAGE_OVERHEAD_TOKENS = 8;
4862
4865
  function buildConversationStats(manifest, snapshot) {
4863
4866
  if (manifest.stats && manifest.status !== "active" && snapshot.messages.length === 0 && snapshot.runs.length === 0) {
4864
4867
  return manifest.stats;
@@ -4973,12 +4976,21 @@ function readUsage(payload) {
4973
4976
  return void 0;
4974
4977
  }
4975
4978
  const usage = toRecord4(payload.usage);
4976
- const inputTokens = readInteger(usage, "input_tokens") ?? readInteger(usage, "prompt_tokens") ?? readInteger(payload, "input_tokens") ?? readInteger(payload, "prompt_tokens");
4977
- const outputTokens = readInteger(usage, "output_tokens") ?? readInteger(usage, "completion_tokens") ?? readInteger(payload, "output_tokens") ?? readInteger(payload, "completion_tokens");
4978
- const totalTokens = readInteger(usage, "total_tokens") ?? readInteger(payload, "total_tokens") ?? (inputTokens ?? 0) + (outputTokens ?? 0);
4979
- const contextWindow = readInteger(usage, "context_window") ?? readInteger(usage, "context_max") ?? readInteger(payload, "context_window") ?? readInteger(payload, "context_max");
4980
- const explicitContextTokens = readInteger(usage, "context_tokens") ?? readInteger(usage, "context_used") ?? readInteger(usage, "current_context_tokens") ?? readInteger(usage, "last_prompt_tokens") ?? readInteger(payload, "context_tokens") ?? readInteger(payload, "context_used") ?? readInteger(payload, "current_context_tokens") ?? readInteger(payload, "last_prompt_tokens");
4981
- if (!inputTokens && !outputTokens && !totalTokens) {
4979
+ const response = toRecord4(payload.response);
4980
+ const responseUsage = toRecord4(response.usage);
4981
+ const context = firstRecord(
4982
+ payload.context,
4983
+ usage.context,
4984
+ response.context,
4985
+ responseUsage.context
4986
+ );
4987
+ const inputTokens = readInteger(usage, "input_tokens") ?? readInteger(usage, "prompt_tokens") ?? readInteger(responseUsage, "input_tokens") ?? readInteger(responseUsage, "prompt_tokens") ?? readInteger(payload, "input_tokens") ?? readInteger(payload, "prompt_tokens");
4988
+ const outputTokens = readInteger(usage, "output_tokens") ?? readInteger(usage, "completion_tokens") ?? readInteger(responseUsage, "output_tokens") ?? readInteger(responseUsage, "completion_tokens") ?? readInteger(payload, "output_tokens") ?? readInteger(payload, "completion_tokens");
4989
+ const totalTokens = readInteger(usage, "total_tokens") ?? readInteger(responseUsage, "total_tokens") ?? readInteger(payload, "total_tokens") ?? (inputTokens ?? 0) + (outputTokens ?? 0);
4990
+ const contextWindow = readInteger(context, "window_tokens") ?? readInteger(context, "windowTokens") ?? readInteger(context, "context_window") ?? readInteger(context, "context_max") ?? readInteger(context, "context_length") ?? readInteger(usage, "context_window") ?? readInteger(usage, "context_max") ?? readInteger(usage, "context_length") ?? readInteger(responseUsage, "context_window") ?? readInteger(responseUsage, "context_max") ?? readInteger(responseUsage, "context_length") ?? readInteger(response, "context_window") ?? readInteger(response, "context_max") ?? readInteger(response, "context_length") ?? readInteger(payload, "context_window") ?? readInteger(payload, "context_max") ?? readInteger(payload, "context_length");
4991
+ const explicitContextTokens = readInteger(context, "used_tokens") ?? readInteger(context, "usedTokens") ?? readInteger(context, "context_tokens") ?? readInteger(context, "context_used") ?? readInteger(context, "current_context_tokens") ?? readInteger(context, "last_prompt_tokens") ?? readInteger(usage, "context_tokens") ?? readInteger(usage, "context_used") ?? readInteger(usage, "current_context_tokens") ?? readInteger(usage, "last_prompt_tokens") ?? readInteger(responseUsage, "context_tokens") ?? readInteger(responseUsage, "context_used") ?? readInteger(responseUsage, "current_context_tokens") ?? readInteger(responseUsage, "last_prompt_tokens") ?? readInteger(response, "context_tokens") ?? readInteger(response, "context_used") ?? readInteger(response, "current_context_tokens") ?? readInteger(response, "last_prompt_tokens") ?? readInteger(payload, "context_tokens") ?? readInteger(payload, "context_used") ?? readInteger(payload, "current_context_tokens") ?? readInteger(payload, "last_prompt_tokens");
4992
+ const explicitUsagePercent = readInteger(context, "usage_percent") ?? readInteger(context, "context_percent") ?? readInteger(usage, "usage_percent") ?? readInteger(usage, "context_percent") ?? readInteger(responseUsage, "usage_percent") ?? readInteger(responseUsage, "context_percent") ?? readInteger(response, "usage_percent") ?? readInteger(response, "context_percent") ?? readInteger(payload, "usage_percent") ?? readInteger(payload, "context_percent");
4993
+ if (!inputTokens && !outputTokens && !totalTokens && explicitContextTokens === void 0) {
4982
4994
  return void 0;
4983
4995
  }
4984
4996
  return {
@@ -4987,14 +4999,44 @@ function readUsage(payload) {
4987
4999
  total_tokens: totalTokens,
4988
5000
  ...explicitContextTokens !== void 0 ? { context_tokens: explicitContextTokens } : {},
4989
5001
  ...contextWindow !== void 0 ? { context_window: contextWindow } : {},
5002
+ ...explicitContextTokens !== void 0 ? { context_source: "explicit" } : {},
4990
5003
  ...explicitContextTokens !== void 0 && contextWindow ? {
4991
- usage_percent: Math.min(
5004
+ usage_percent: explicitUsagePercent !== void 0 ? Math.min(100, explicitUsagePercent) : Math.min(
4992
5005
  100,
4993
5006
  Math.round(explicitContextTokens / contextWindow * 100)
4994
5007
  )
4995
5008
  } : {}
4996
5009
  };
4997
5010
  }
5011
+ function estimateContextUsage(input) {
5012
+ const currentInput = input.currentInput.trim();
5013
+ const instructions = input.instructions?.trim() ?? "";
5014
+ const messageCount = input.conversationHistory.length + (currentInput ? 1 : 0) + (instructions ? 1 : 0);
5015
+ if (messageCount === 0) {
5016
+ return void 0;
5017
+ }
5018
+ const serializedRequest = JSON.stringify({
5019
+ instructions: instructions || void 0,
5020
+ conversation_history: input.conversationHistory,
5021
+ input: currentInput || void 0
5022
+ });
5023
+ const estimatedTokens = Math.ceil(serializedRequest.length / ESTIMATED_CONTEXT_CHARS_PER_TOKEN) + ESTIMATED_CONTEXT_BASE_OVERHEAD_TOKENS + messageCount * ESTIMATED_CONTEXT_PER_MESSAGE_OVERHEAD_TOKENS;
5024
+ const contextTokens = input.contextWindow ? Math.min(input.contextWindow, estimatedTokens) : estimatedTokens;
5025
+ return {
5026
+ input_tokens: 0,
5027
+ output_tokens: 0,
5028
+ total_tokens: 0,
5029
+ context_tokens: contextTokens,
5030
+ ...input.contextWindow !== void 0 ? { context_window: input.contextWindow } : {},
5031
+ ...input.contextWindow ? {
5032
+ usage_percent: Math.min(
5033
+ 100,
5034
+ Math.round(contextTokens / input.contextWindow * 100)
5035
+ )
5036
+ } : {},
5037
+ context_source: "estimated"
5038
+ };
5039
+ }
4998
5040
  function isAgentRun(run) {
4999
5041
  return run.kind !== "command";
5000
5042
  }
@@ -5015,6 +5057,14 @@ function readInteger(payload, key) {
5015
5057
  function toRecord4(value) {
5016
5058
  return typeof value === "object" && value !== null ? value : {};
5017
5059
  }
5060
+ function firstRecord(...values) {
5061
+ for (const value of values) {
5062
+ if (typeof value === "object" && value !== null) {
5063
+ return value;
5064
+ }
5065
+ }
5066
+ return {};
5067
+ }
5018
5068
 
5019
5069
  // src/conversations/blob-store.ts
5020
5070
  import { randomUUID as randomUUID3 } from "crypto";
@@ -5730,7 +5780,7 @@ async function buildConversationRuntimeMetadata(paths, manifest, snapshot) {
5730
5780
  };
5731
5781
  const contextWindow = current.contextWindow ?? usage.context_window ?? usageRun?.context_window;
5732
5782
  const contextTokens = usage.context_tokens;
5733
- const contextSource = contextTokens === void 0 ? "unknown" : "explicit";
5783
+ const contextSource = contextTokens === void 0 ? "unknown" : usage.context_source ?? "explicit";
5734
5784
  const provider = current.provider ?? usageRun?.provider;
5735
5785
  const reasoningEffort = current.reasoningEffort;
5736
5786
  return {
@@ -6324,12 +6374,12 @@ var ConversationCommandHandlers = class {
6324
6374
  };
6325
6375
  function formatContextUsageLines(runtime) {
6326
6376
  const windowTokens = runtime.context.window_tokens ?? runtime.context.context_window;
6327
- if (runtime.context.source === "explicit") {
6377
+ if (runtime.context.source === "explicit" || runtime.context.source === "estimated") {
6328
6378
  const usedTokens = runtime.context.used_tokens ?? runtime.context.input_tokens;
6329
6379
  const percent = runtime.context.usage_percent ?? (windowTokens && windowTokens > 0 ? Math.min(100, Math.round(usedTokens / windowTokens * 100)) : void 0);
6330
6380
  return [
6331
6381
  `\u4E0A\u4E0B\u6587\uFF1A${usedTokens}${windowTokens ? ` / ${windowTokens}` : ""}${percent === void 0 ? "" : `\uFF08${percent}%\uFF09`}`,
6332
- "\u6765\u6E90\uFF1A\u6A21\u578B\u8FD4\u56DE"
6382
+ runtime.context.source === "estimated" ? "\u6765\u6E90\uFF1A\u672C\u5730\u4F30\u7B97" : "\u6765\u6E90\uFF1A\u6A21\u578B\u8FD4\u56DE"
6333
6383
  ];
6334
6384
  }
6335
6385
  return [
@@ -8516,6 +8566,13 @@ function isNodeError8(error, code) {
8516
8566
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
8517
8567
  }
8518
8568
 
8569
+ // src/conversations/hermes-session-sync.ts
8570
+ import { randomUUID as randomUUID6 } from "crypto";
8571
+ import { readdir as readdir6, readFile as readFile9, stat as stat8 } from "fs/promises";
8572
+ import { createRequire as createRequire3 } from "module";
8573
+ import os4 from "os";
8574
+ import path14 from "path";
8575
+
8519
8576
  // src/conversations/delivery-import.ts
8520
8577
  import { lstat, readFile as readFile8, readdir as readdir5, stat as stat7 } from "fs/promises";
8521
8578
  import path13 from "path";
@@ -8846,145 +8903,1431 @@ function isNodeError9(error, code) {
8846
8903
  return typeof error === "object" && error !== null && "code" in error && (code === void 0 || error.code === code);
8847
8904
  }
8848
8905
 
8849
- // src/conversations/run-lifecycle.ts
8850
- import { readdir as readdir6 } from "fs/promises";
8851
-
8852
- // src/hermes/api-server.ts
8853
- async function listHermesModels(options = {}) {
8854
- const response = await callHermesApi("/v1/models", { method: "GET" }, options);
8855
- if (response.status === 404) {
8856
- return { models: [] };
8906
+ // src/conversations/hermes-session-sync.ts
8907
+ var nodeRequire3 = createRequire3(import.meta.url);
8908
+ var PROFILE_NAME_PATTERN3 = /^[a-zA-Z0-9._-]{1,64}$/u;
8909
+ var DEFAULT_PROFILE_NAME = "default";
8910
+ var MAX_IMPORTABLE_SESSIONS = 100;
8911
+ var HIDDEN_SESSION_SOURCES = /* @__PURE__ */ new Set(["tool"]);
8912
+ var HERMES_IMPORT_PROJECTION_VERSION = "turn_blocks_v3";
8913
+ var IMPORTED_MEDIA_PLACEHOLDER_RUN_ID = "imported_from_hermes";
8914
+ var MAX_IMPORTED_HERMES_MEDIA_BYTES = 100 * 1024 * 1024;
8915
+ var MESSAGE_COLUMNS = [
8916
+ "id",
8917
+ "session_id",
8918
+ "role",
8919
+ "content",
8920
+ "tool_call_id",
8921
+ "tool_calls",
8922
+ "tool_name",
8923
+ "timestamp",
8924
+ "token_count",
8925
+ "finish_reason",
8926
+ "reasoning",
8927
+ "reasoning_content",
8928
+ "reasoning_details",
8929
+ "codex_reasoning_items"
8930
+ ];
8931
+ async function syncHermesSessionsIntoConversations(paths, logger, options = {}) {
8932
+ const maxImports = options.maxImports ?? MAX_IMPORTABLE_SESSIONS;
8933
+ const store = new ConversationStore(paths);
8934
+ const knownHermesSessions = await readKnownHermesSessions(store);
8935
+ const profileNames = await discoverHermesProfileNames();
8936
+ const result = {
8937
+ scanned_profiles: profileNames.length,
8938
+ scanned_sessions: 0,
8939
+ eligible_sessions: 0,
8940
+ imported_count: 0,
8941
+ reprojected_count: 0,
8942
+ skipped_existing: 0,
8943
+ skipped_hidden: 0,
8944
+ skipped_deleted: 0,
8945
+ skipped_over_limit: 0,
8946
+ errors: []
8947
+ };
8948
+ const candidates = [];
8949
+ for (const profileName of profileNames) {
8950
+ const profileDir = resolveHermesProfileDir(profileName);
8951
+ const dbPath = path14.join(profileDir, "state.db");
8952
+ const sessions = await listProfileSessions(dbPath).catch((error) => {
8953
+ result.errors.push({
8954
+ profile: profileName,
8955
+ message: error instanceof Error ? error.message : String(error)
8956
+ });
8957
+ return [];
8958
+ });
8959
+ result.scanned_sessions += sessions.length;
8960
+ for (const session of sessions) {
8961
+ if (isDeletedSession(session)) {
8962
+ result.skipped_deleted += 1;
8963
+ continue;
8964
+ }
8965
+ if (isHiddenSession(session)) {
8966
+ result.skipped_hidden += 1;
8967
+ continue;
8968
+ }
8969
+ result.eligible_sessions += 1;
8970
+ candidates.push({ profileName, profileDir, dbPath, session });
8971
+ }
8857
8972
  }
8858
- return await readJsonResponse(response);
8859
- }
8860
- async function listHermesCronJobs(options = {}) {
8861
- const query = options.includeDisabled ? "?include_disabled=true" : "";
8862
- const response = await callHermesApi(
8863
- `/api/jobs${query}`,
8864
- { method: "GET" },
8865
- options
8866
- );
8867
- const payload = await readJsonResponse(response);
8868
- const jobs = payload.jobs;
8869
- return Array.isArray(jobs) ? jobs.filter(isRecord).map((job) => ({ ...job })) : [];
8973
+ candidates.sort((left, right) => {
8974
+ const rightTime = readNumber2(right.session.last_active) ?? 0;
8975
+ const leftTime = readNumber2(left.session.last_active) ?? 0;
8976
+ return rightTime - leftTime;
8977
+ });
8978
+ const importableCandidates = candidates.slice(0, maxImports);
8979
+ result.skipped_over_limit = Math.max(0, candidates.length - maxImports);
8980
+ for (const candidate of importableCandidates) {
8981
+ if (knownHermesSessions.ids.has(candidate.session.id)) {
8982
+ result.skipped_existing += 1;
8983
+ const reprojected = await reprojectExistingHermesConversation({
8984
+ paths,
8985
+ store,
8986
+ logger,
8987
+ candidate,
8988
+ conversationIds: knownHermesSessions.conversationIdsBySessionId.get(
8989
+ candidate.session.id
8990
+ ) ?? []
8991
+ }).catch((error) => {
8992
+ result.errors.push({
8993
+ profile: candidate.profileName,
8994
+ message: error instanceof Error ? error.message : String(error)
8995
+ });
8996
+ return false;
8997
+ });
8998
+ if (reprojected) {
8999
+ result.reprojected_count += 1;
9000
+ }
9001
+ continue;
9002
+ }
9003
+ const imported = await importHermesSession({
9004
+ paths,
9005
+ store,
9006
+ logger,
9007
+ candidate,
9008
+ existingHermesSessionIds: knownHermesSessions.ids
9009
+ }).catch((error) => {
9010
+ result.errors.push({
9011
+ profile: candidate.profileName,
9012
+ message: error instanceof Error ? error.message : String(error)
9013
+ });
9014
+ return false;
9015
+ });
9016
+ if (imported) {
9017
+ result.imported_count += 1;
9018
+ }
9019
+ }
9020
+ if (result.imported_count > 0 || result.reprojected_count > 0 || result.errors.length > 0) {
9021
+ void logger.info("hermes_session_sync_completed", { ...result });
9022
+ } else {
9023
+ void logger.debug("hermes_session_sync_completed", { ...result });
9024
+ }
9025
+ return result;
8870
9026
  }
8871
- async function getHermesCronJob(jobId, options = {}) {
8872
- const response = await callHermesApi(
8873
- `/api/jobs/${encodeURIComponent(jobId)}`,
8874
- { method: "GET" },
8875
- options
9027
+ async function importHermesSession(input) {
9028
+ const { paths, store, logger, candidate, existingHermesSessionIds } = input;
9029
+ const profile = await resolveConversationProfileTarget(
9030
+ paths,
9031
+ candidate.profileName
8876
9032
  );
8877
- const payload = await readJsonResponse(response);
8878
- if (!isRecord(payload.job)) {
8879
- throw new LinkHttpError(
8880
- 502,
8881
- "hermes_cron_job_invalid",
8882
- "Hermes API Server did not return a cron job"
8883
- );
9033
+ const sessionId = candidate.session.id;
9034
+ const messages = await readHermesSessionMessages(candidate);
9035
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9036
+ const createdAt = isoFromHermesTime(candidate.session.started_at) ?? now;
9037
+ const updatedAt = isoFromHermesTime(candidate.session.last_active) ?? isoFromHermesTime(messages.at(-1)?.timestamp) ?? createdAt;
9038
+ const conversationId = createConversationId();
9039
+ const snapshot = {
9040
+ schema_version: 1,
9041
+ messages: toLinkMessages({
9042
+ conversationId,
9043
+ profileName: profile.profileName,
9044
+ profileUid: profile.profileUid,
9045
+ profileDisplayName: profile.profileDisplayName,
9046
+ sessionId,
9047
+ messages
9048
+ }),
9049
+ runs: []
9050
+ };
9051
+ const title = readString9(candidate.session, "title") ?? firstUserText(snapshot);
9052
+ const manifest = {
9053
+ id: conversationId,
9054
+ schema_version: 1,
9055
+ kind: "direct",
9056
+ title: normalizeTitle(title),
9057
+ title_source: title ? "hermes" : "default",
9058
+ status: "active",
9059
+ hermes_session_id: sessionId,
9060
+ hermes_session_ids: [sessionId],
9061
+ profile_uid: profile.profileUid,
9062
+ profile_name_snapshot: profile.profileName,
9063
+ profile: profile.profileName,
9064
+ created_at: createdAt,
9065
+ updated_at: updatedAt,
9066
+ last_event_seq: 0
9067
+ };
9068
+ await store.createConversation(manifest, snapshot);
9069
+ await hydrateImportedConversationMedia({
9070
+ paths,
9071
+ store,
9072
+ logger,
9073
+ conversationId
9074
+ });
9075
+ const hydratedSnapshot = await store.readSnapshot(conversationId);
9076
+ await store.appendEvent(conversationId, {
9077
+ type: "conversation.created",
9078
+ payload: {
9079
+ imported_from: "hermes",
9080
+ hermes_session_id: sessionId,
9081
+ profile: {
9082
+ uid: profile.profileUid,
9083
+ name: profile.profileName,
9084
+ display_name: profile.profileDisplayName,
9085
+ avatar_url: profile.profileAvatarUrl
9086
+ }
9087
+ }
9088
+ });
9089
+ for (const message of hydratedSnapshot.messages) {
9090
+ await store.appendEvent(conversationId, {
9091
+ type: "message.created",
9092
+ message_id: message.id,
9093
+ payload: { message, imported_from: "hermes" },
9094
+ raw: message.raw
9095
+ });
8884
9096
  }
8885
- return { ...payload.job };
8886
- }
8887
- async function createHermesCronJob(input, options = {}) {
8888
- const response = await callHermesApi(
8889
- "/api/jobs",
8890
- {
8891
- method: "POST",
8892
- body: JSON.stringify(input),
8893
- headers: { "content-type": "application/json" }
8894
- },
8895
- options
9097
+ const stats = buildConversationStats(
9098
+ await store.readManifest(conversationId),
9099
+ hydratedSnapshot
8896
9100
  );
8897
- const payload = await readJsonResponse(response);
8898
- if (!isRecord(payload.job)) {
8899
- throw new LinkHttpError(
8900
- 502,
8901
- "hermes_cron_job_invalid",
8902
- "Hermes API Server did not return a cron job"
9101
+ await store.writeManifest({
9102
+ ...await store.readManifest(conversationId),
9103
+ stats
9104
+ });
9105
+ await upsertConversationStats(
9106
+ paths,
9107
+ toStatsIndexRecord(await store.readManifest(conversationId), stats)
9108
+ );
9109
+ existingHermesSessionIds.add(sessionId);
9110
+ return true;
9111
+ }
9112
+ async function reprojectExistingHermesConversation(input) {
9113
+ let changed = false;
9114
+ for (const conversationId of input.conversationIds) {
9115
+ const manifest = await input.store.readManifest(conversationId).catch(() => null);
9116
+ if (!manifest || manifest.status !== "active") {
9117
+ continue;
9118
+ }
9119
+ const snapshot = await input.store.readSnapshot(conversationId).catch(() => null);
9120
+ if (!snapshot) {
9121
+ continue;
9122
+ }
9123
+ const prefix = collectImportedHermesPrefix(snapshot);
9124
+ if (!prefix?.needsUpgrade || prefix.messages.length === 0) {
9125
+ continue;
9126
+ }
9127
+ const profile = await resolveConversationProfileTarget(
9128
+ input.paths,
9129
+ manifest.profile_name_snapshot ?? manifest.profile ?? input.candidate.profileName
8903
9130
  );
9131
+ const nextSnapshot = {
9132
+ ...snapshot,
9133
+ messages: [
9134
+ ...toLinkMessages({
9135
+ conversationId,
9136
+ profileName: profile.profileName,
9137
+ profileUid: profile.profileUid,
9138
+ profileDisplayName: profile.profileDisplayName,
9139
+ sessionId: input.candidate.session.id,
9140
+ messages: prefix.messages
9141
+ }),
9142
+ ...snapshot.messages.slice(prefix.endIndex)
9143
+ ]
9144
+ };
9145
+ await input.store.writeSnapshot(conversationId, nextSnapshot);
9146
+ await hydrateImportedConversationMedia({
9147
+ paths: input.paths,
9148
+ store: input.store,
9149
+ logger: input.logger,
9150
+ conversationId
9151
+ });
9152
+ const hydratedSnapshot = await input.store.readSnapshot(conversationId);
9153
+ const stats = buildConversationStats(manifest, hydratedSnapshot);
9154
+ await input.store.writeManifest({ ...manifest, stats });
9155
+ await upsertConversationStats(
9156
+ input.paths,
9157
+ toStatsIndexRecord({ ...manifest, stats }, stats)
9158
+ );
9159
+ changed = true;
8904
9160
  }
8905
- return { ...payload.job };
9161
+ return changed;
8906
9162
  }
8907
- async function updateHermesCronJob(jobId, input, options = {}) {
8908
- const response = await callHermesApi(
8909
- `/api/jobs/${encodeURIComponent(jobId)}`,
8910
- {
8911
- method: "PATCH",
8912
- body: JSON.stringify(input),
8913
- headers: { "content-type": "application/json" }
8914
- },
8915
- options
8916
- );
8917
- const payload = await readJsonResponse(response);
8918
- if (!isRecord(payload.job)) {
8919
- throw new LinkHttpError(
8920
- 502,
8921
- "hermes_cron_job_invalid",
8922
- "Hermes API Server did not return a cron job"
8923
- );
9163
+ function collectImportedHermesPrefix(snapshot) {
9164
+ const rows = [];
9165
+ const seen = /* @__PURE__ */ new Set();
9166
+ let needsProjectionVersion = false;
9167
+ let hasToolMetadata = false;
9168
+ let hasMediaDeliveryMarkup = false;
9169
+ let endIndex = 0;
9170
+ for (; endIndex < snapshot.messages.length; endIndex += 1) {
9171
+ const message = snapshot.messages[endIndex];
9172
+ if (!isHermesImportedMessage(message)) {
9173
+ break;
9174
+ }
9175
+ if (message.hermes?.import_projection !== HERMES_IMPORT_PROJECTION_VERSION) {
9176
+ needsProjectionVersion = true;
9177
+ }
9178
+ if (message.role === "tool") {
9179
+ hasToolMetadata = true;
9180
+ }
9181
+ for (const row of readHermesRawMessageRows(message.raw)) {
9182
+ appendHermesRowOnce(rows, seen, row);
9183
+ if (hasHermesToolMetadata(row)) {
9184
+ hasToolMetadata = true;
9185
+ }
9186
+ if (collectMediaTags(normalizeContent(row.content)).length > 0) {
9187
+ hasMediaDeliveryMarkup = true;
9188
+ }
9189
+ }
9190
+ for (const event of message.agent_events ?? []) {
9191
+ for (const row of readHermesRowsFromAgentEvent(event)) {
9192
+ appendHermesRowOnce(rows, seen, row);
9193
+ if (hasHermesToolMetadata(row)) {
9194
+ hasToolMetadata = true;
9195
+ }
9196
+ if (collectMediaTags(normalizeContent(row.content)).length > 0) {
9197
+ hasMediaDeliveryMarkup = true;
9198
+ }
9199
+ }
9200
+ }
8924
9201
  }
8925
- return { ...payload.job };
9202
+ if (rows.length === 0 || endIndex === 0) {
9203
+ return null;
9204
+ }
9205
+ return {
9206
+ endIndex,
9207
+ messages: rows,
9208
+ needsUpgrade: needsProjectionVersion && (hasToolMetadata || hasMediaDeliveryMarkup)
9209
+ };
8926
9210
  }
8927
- async function deleteHermesCronJob(jobId, options = {}) {
8928
- const response = await callHermesApi(
8929
- `/api/jobs/${encodeURIComponent(jobId)}`,
8930
- { method: "DELETE" },
8931
- options
8932
- );
8933
- await readJsonResponse(response);
9211
+ function isHermesImportedMessage(message) {
9212
+ return message.hermes?.imported_from === "hermes" || message.raw?.format === "hermes-message" || message.raw?.format === "hermes-message-group";
8934
9213
  }
8935
- async function runHermesCronJobAction(jobId, action, options = {}) {
8936
- const response = await callHermesApi(
8937
- `/api/jobs/${encodeURIComponent(jobId)}/${action}`,
8938
- { method: "POST" },
8939
- options
8940
- );
8941
- const payload = await readJsonResponse(response);
8942
- if (!isRecord(payload.job)) {
8943
- throw new LinkHttpError(
8944
- 502,
8945
- "hermes_cron_job_invalid",
8946
- "Hermes API Server did not return a cron job"
8947
- );
9214
+ function readHermesRowsFromAgentEvent(event) {
9215
+ if (event.raw?.format !== "hermes-message" && event.raw?.format !== "hermes-message-group") {
9216
+ return [];
8948
9217
  }
8949
- return { ...payload.job };
8950
- }
8951
- async function createHermesRun(input, options = {}) {
8952
- const response = await callHermesApi(
8953
- "/v1/runs",
8954
- {
8955
- method: "POST",
8956
- body: JSON.stringify(input),
8957
- headers: { "content-type": "application/json" }
8958
- },
8959
- options
9218
+ const payload = toRecord8(event.raw.payload);
9219
+ const message = toRecord8(payload.message);
9220
+ if (normalizeMessageRole(readString9(message, "role") ?? void 0) === "tool") {
9221
+ return [message];
9222
+ }
9223
+ return readHermesRawMessageRows(event.raw).filter(
9224
+ (row) => Boolean(readString9(row, "role"))
8960
9225
  );
8961
- if (response.status === 404 || response.status === 503) {
8962
- assertHermesRunsApiSupported(
8963
- await readHermesVersion().catch(() => null),
8964
- response.status
8965
- );
8966
- throw new LinkHttpError(
8967
- 503,
8968
- "hermes_api_server_unavailable",
8969
- "Hermes API Server is unavailable"
8970
- );
9226
+ }
9227
+ function appendHermesRowOnce(rows, seen, row) {
9228
+ const key = hermesRowKey(row, rows.length);
9229
+ if (seen.has(key)) {
9230
+ return;
8971
9231
  }
8972
- const payload = await readJsonResponse(response);
8973
- const runId = readString9(payload, "run_id") ?? readString9(payload, "runId") ?? readString9(payload, "id");
8974
- if (!runId) {
8975
- throw new LinkHttpError(
8976
- 502,
8977
- "hermes_run_invalid",
8978
- "Hermes API Server did not return a run id"
8979
- );
9232
+ seen.add(key);
9233
+ rows.push(row);
9234
+ }
9235
+ function hermesRowKey(row, fallbackIndex) {
9236
+ if (row.id !== void 0 && row.id !== null) {
9237
+ return `id:${row.id}`;
8980
9238
  }
8981
- return { run_id: runId, fallback: false };
9239
+ return `fallback:${fallbackIndex}:${row.role ?? ""}:${row.timestamp ?? ""}:${normalizeContent(row.content)}`;
8982
9240
  }
8983
- async function streamHermesRunEvents(runId, options = {}) {
8984
- const response = await callHermesApi(
8985
- `/v1/runs/${encodeURIComponent(runId)}/events`,
8986
- { method: "GET", signal: options.signal },
8987
- options
9241
+ function hasHermesToolMetadata(row) {
9242
+ return normalizeMessageRole(row.role) === "tool" || readHermesToolCalls(row).length > 0 || Boolean(readString9(row, "tool_call_id")) || Boolean(readString9(row, "tool_name"));
9243
+ }
9244
+ function toLinkMessages(input) {
9245
+ const linkMessages = [];
9246
+ let pendingToolCalls = [];
9247
+ const toolCallsById = /* @__PURE__ */ new Map();
9248
+ let currentAssistant = null;
9249
+ const finishAssistantTurn = () => {
9250
+ if (!currentAssistant) {
9251
+ return;
9252
+ }
9253
+ pendingToolCalls.forEach((pending, index) => {
9254
+ const event = projectHermesToolCallWithoutOutputEvent({
9255
+ conversationId: input.conversationId,
9256
+ messageId: pending.message.id,
9257
+ pending,
9258
+ index
9259
+ });
9260
+ if (event) {
9261
+ attachAgentEventToMessage(pending.message, event, event.created_at);
9262
+ }
9263
+ });
9264
+ pendingToolCalls = [];
9265
+ toolCallsById.clear();
9266
+ currentAssistant = null;
9267
+ };
9268
+ input.messages.forEach((message, index) => {
9269
+ const role = normalizeMessageRole(message.role);
9270
+ if (role === "tool") {
9271
+ const pending = consumePendingToolCall({
9272
+ toolMessage: message,
9273
+ pendingToolCalls,
9274
+ toolCallsById
9275
+ });
9276
+ let target = pending?.message ?? currentAssistant;
9277
+ let createdSynthetic = false;
9278
+ if (!target) {
9279
+ target = createSyntheticToolAssistantMessage({
9280
+ ...input,
9281
+ message,
9282
+ index
9283
+ });
9284
+ linkMessages.push(target);
9285
+ currentAssistant = target;
9286
+ createdSynthetic = true;
9287
+ }
9288
+ if (!createdSynthetic) {
9289
+ appendHermesRawMessage(target, message);
9290
+ rememberHermesMessageId(target, message);
9291
+ }
9292
+ const event = projectHermesToolCompletedEvent({
9293
+ conversationId: input.conversationId,
9294
+ messageId: target.id,
9295
+ sourceMessage: message,
9296
+ pending,
9297
+ index
9298
+ });
9299
+ if (event) {
9300
+ attachAgentEventToMessage(target, event, event.created_at);
9301
+ }
9302
+ return;
9303
+ }
9304
+ if (role === "assistant") {
9305
+ if (!currentAssistant) {
9306
+ currentAssistant = toLinkMessage({
9307
+ conversationId: input.conversationId,
9308
+ profileName: input.profileName,
9309
+ profileUid: input.profileUid,
9310
+ profileDisplayName: input.profileDisplayName,
9311
+ sessionId: input.sessionId,
9312
+ message,
9313
+ index
9314
+ });
9315
+ linkMessages.push(currentAssistant);
9316
+ } else {
9317
+ appendHermesRawMessage(currentAssistant, message);
9318
+ rememberHermesMessageId(currentAssistant, message);
9319
+ appendAssistantTextToMessage({
9320
+ message: currentAssistant,
9321
+ text: normalizeContent(message.content),
9322
+ updatedAt: isoFromHermesTime(message.timestamp) ?? currentAssistant.updated_at
9323
+ });
9324
+ }
9325
+ for (const toolCall of readHermesToolCalls(message)) {
9326
+ const pending = { message: currentAssistant, toolCall };
9327
+ pendingToolCalls.push(pending);
9328
+ if (toolCall.id) {
9329
+ toolCallsById.set(toolCall.id, pending);
9330
+ }
9331
+ const event = projectHermesToolStartedEvent({
9332
+ conversationId: input.conversationId,
9333
+ messageId: currentAssistant.id,
9334
+ sourceMessage: message,
9335
+ toolCall,
9336
+ index
9337
+ });
9338
+ if (event) {
9339
+ attachAgentEventToMessage(currentAssistant, event, event.created_at);
9340
+ }
9341
+ }
9342
+ return;
9343
+ }
9344
+ finishAssistantTurn();
9345
+ const linkMessage = toLinkMessage({
9346
+ conversationId: input.conversationId,
9347
+ profileName: input.profileName,
9348
+ profileUid: input.profileUid,
9349
+ profileDisplayName: input.profileDisplayName,
9350
+ sessionId: input.sessionId,
9351
+ message,
9352
+ index
9353
+ });
9354
+ linkMessages.push(linkMessage);
9355
+ });
9356
+ finishAssistantTurn();
9357
+ return linkMessages;
9358
+ }
9359
+ function consumePendingToolCall(input) {
9360
+ const toolCallId = readString9(input.toolMessage, "tool_call_id");
9361
+ const toolName = readString9(input.toolMessage, "tool_name");
9362
+ let pending = toolCallId ? input.toolCallsById.get(toolCallId) : void 0;
9363
+ if (!pending && toolName) {
9364
+ pending = input.pendingToolCalls.find(
9365
+ (item) => item.toolCall.name === toolName
9366
+ );
9367
+ }
9368
+ if (!pending && !toolCallId) {
9369
+ pending = input.pendingToolCalls[0];
9370
+ }
9371
+ if (!pending) {
9372
+ return void 0;
9373
+ }
9374
+ const index = input.pendingToolCalls.indexOf(pending);
9375
+ if (index >= 0) {
9376
+ input.pendingToolCalls.splice(index, 1);
9377
+ }
9378
+ if (pending.toolCall.id) {
9379
+ input.toolCallsById.delete(pending.toolCall.id);
9380
+ }
9381
+ return pending;
9382
+ }
9383
+ function createSyntheticToolAssistantMessage(input) {
9384
+ const message = toLinkMessage({
9385
+ conversationId: input.conversationId,
9386
+ profileName: input.profileName,
9387
+ profileUid: input.profileUid,
9388
+ profileDisplayName: input.profileDisplayName,
9389
+ sessionId: input.sessionId,
9390
+ message: {
9391
+ ...input.message,
9392
+ role: "assistant",
9393
+ content: null
9394
+ },
9395
+ index: input.index
9396
+ });
9397
+ message.raw = {
9398
+ format: "hermes-message",
9399
+ payload: input.message
9400
+ };
9401
+ return message;
9402
+ }
9403
+ function readHermesToolCalls(message) {
9404
+ const decoded = parseJsonValue(message.tool_calls) ?? message.tool_calls;
9405
+ const values = Array.isArray(decoded) ? decoded : decoded ? [decoded] : [];
9406
+ return values.map((value) => normalizeHermesToolCall(value)).filter(
9407
+ (toolCall) => Boolean(toolCall)
9408
+ );
9409
+ }
9410
+ function normalizeHermesToolCall(value) {
9411
+ const record = toRecord8(value);
9412
+ if (Object.keys(record).length === 0) {
9413
+ return null;
9414
+ }
9415
+ const fn = toRecord8(record.function);
9416
+ const id = readString9(record, "id") ?? readString9(record, "call_id") ?? readString9(record, "tool_call_id") ?? readString9(fn, "id") ?? void 0;
9417
+ const name = readString9(fn, "name") ?? readString9(record, "name") ?? readString9(record, "tool_name") ?? readString9(record, "tool") ?? "tool";
9418
+ const rawArguments = fn.arguments ?? record.arguments ?? record.args ?? record.input;
9419
+ return {
9420
+ ...id ? { id } : {},
9421
+ name,
9422
+ ...rawArguments === void 0 ? {} : { arguments: parseJsonValue(rawArguments) ?? rawArguments },
9423
+ raw: value
9424
+ };
9425
+ }
9426
+ function projectHermesToolStartedEvent(input) {
9427
+ const createdAt = isoFromHermesTime(input.sourceMessage.timestamp) ?? new Date(Date.now() + input.index).toISOString();
9428
+ return projectHermesAgentEvent({
9429
+ conversationId: input.conversationId,
9430
+ messageId: input.messageId,
9431
+ type: "tool.started",
9432
+ createdAt,
9433
+ seq: input.index + 1,
9434
+ payload: {
9435
+ type: "tool.started",
9436
+ tool: input.toolCall.name,
9437
+ tool_name: input.toolCall.name,
9438
+ name: input.toolCall.name,
9439
+ ...input.toolCall.id ? { tool_call_id: input.toolCall.id, id: input.toolCall.id } : {},
9440
+ ...input.toolCall.arguments === void 0 ? {} : { arguments: input.toolCall.arguments },
9441
+ tool_call: input.toolCall.raw
9442
+ },
9443
+ raw: {
9444
+ format: "hermes-message",
9445
+ payload: {
9446
+ message: input.sourceMessage,
9447
+ tool_call: input.toolCall.raw
9448
+ }
9449
+ }
9450
+ });
9451
+ }
9452
+ function projectHermesToolCompletedEvent(input) {
9453
+ const createdAt = isoFromHermesTime(input.sourceMessage.timestamp) ?? new Date(Date.now() + input.index).toISOString();
9454
+ const output = normalizeContent(input.sourceMessage.content);
9455
+ const parsedOutput = parseJsonValue(output);
9456
+ const toolCallId = readString9(input.sourceMessage, "tool_call_id") ?? input.pending?.toolCall.id;
9457
+ const toolName = readString9(input.sourceMessage, "tool_name") ?? input.pending?.toolCall.name ?? "tool";
9458
+ return projectHermesAgentEvent({
9459
+ conversationId: input.conversationId,
9460
+ messageId: input.messageId,
9461
+ type: "tool.completed",
9462
+ createdAt,
9463
+ seq: input.index + 1,
9464
+ payload: {
9465
+ type: "tool.completed",
9466
+ tool: toolName,
9467
+ tool_name: toolName,
9468
+ name: toolName,
9469
+ ...toolCallId ? { tool_call_id: toolCallId, id: toolCallId } : {},
9470
+ ...input.pending?.toolCall.arguments === void 0 ? {} : { arguments: input.pending.toolCall.arguments },
9471
+ output,
9472
+ content: output,
9473
+ result: parsedOutput ?? output,
9474
+ ...input.pending ? { tool_call: input.pending.toolCall.raw } : {}
9475
+ },
9476
+ raw: {
9477
+ format: "hermes-message",
9478
+ payload: {
9479
+ message: input.sourceMessage,
9480
+ ...input.pending ? { tool_call: input.pending.toolCall.raw } : {}
9481
+ }
9482
+ }
9483
+ });
9484
+ }
9485
+ function projectHermesToolCallWithoutOutputEvent(input) {
9486
+ return projectHermesAgentEvent({
9487
+ conversationId: input.conversationId,
9488
+ messageId: input.messageId,
9489
+ type: "tool.completed",
9490
+ createdAt: input.pending.message.updated_at,
9491
+ seq: input.index + 1,
9492
+ payload: {
9493
+ type: "tool.completed",
9494
+ tool: input.pending.toolCall.name,
9495
+ tool_name: input.pending.toolCall.name,
9496
+ name: input.pending.toolCall.name,
9497
+ ...input.pending.toolCall.id ? {
9498
+ tool_call_id: input.pending.toolCall.id,
9499
+ id: input.pending.toolCall.id
9500
+ } : {},
9501
+ ...input.pending.toolCall.arguments === void 0 ? {} : { arguments: input.pending.toolCall.arguments },
9502
+ tool_call: input.pending.toolCall.raw
9503
+ },
9504
+ raw: {
9505
+ format: "hermes-tool-call",
9506
+ payload: {
9507
+ tool_call: input.pending.toolCall.raw,
9508
+ note: "tool_output_not_found"
9509
+ }
9510
+ }
9511
+ });
9512
+ }
9513
+ function projectHermesAgentEvent(input) {
9514
+ const event = {
9515
+ seq: input.seq,
9516
+ type: input.type,
9517
+ conversation_id: input.conversationId,
9518
+ message_id: input.messageId,
9519
+ created_at: input.createdAt,
9520
+ payload: input.payload,
9521
+ raw: input.raw
9522
+ };
9523
+ return projectConversationAgentEvent(event);
9524
+ }
9525
+ function attachAgentEventToMessage(message, event, updatedAt) {
9526
+ ensureTextBlockForMessage(message);
9527
+ message.agent_events = upsertAgentEventProjection(
9528
+ message.agent_events ?? [],
9529
+ event
9530
+ );
9531
+ appendAgentEventBlock(message, event, updatedAt);
9532
+ message.updated_at = latestTimestamp(message.updated_at, updatedAt);
9533
+ }
9534
+ function appendAssistantTextToMessage(input) {
9535
+ const text = input.text;
9536
+ if (!text) {
9537
+ return;
9538
+ }
9539
+ appendTextPart(input.message, text);
9540
+ if (input.message.blocks?.length) {
9541
+ appendTextBlock(input.message, text, input.updatedAt);
9542
+ }
9543
+ input.message.updated_at = latestTimestamp(
9544
+ input.message.updated_at,
9545
+ input.updatedAt
9546
+ );
9547
+ }
9548
+ function appendTextPart(message, text) {
9549
+ const existing = message.parts.find((part) => part.type === "text");
9550
+ if (existing) {
9551
+ existing.text = joinImportedText(existing.text ?? "", text);
9552
+ return;
9553
+ }
9554
+ message.parts.push({ type: "text", text });
9555
+ }
9556
+ function appendTextBlock(message, text, updatedAt) {
9557
+ const blocks = [...message.blocks ?? []];
9558
+ const last = blocks.at(-1);
9559
+ if (last?.type === "text") {
9560
+ blocks[blocks.length - 1] = {
9561
+ ...last,
9562
+ text: joinImportedText(last.text, text),
9563
+ updated_at: updatedAt
9564
+ };
9565
+ } else {
9566
+ blocks.push({
9567
+ id: `text_${blocks.length + 1}`,
9568
+ type: "text",
9569
+ text,
9570
+ created_at: updatedAt,
9571
+ updated_at: updatedAt
9572
+ });
9573
+ }
9574
+ message.blocks = blocks;
9575
+ }
9576
+ function ensureTextBlockForMessage(message) {
9577
+ if (message.blocks?.length) {
9578
+ return;
9579
+ }
9580
+ const text = message.parts.filter((part) => part.type === "text" && part.text).map((part) => part.text).join("");
9581
+ if (!text) {
9582
+ return;
9583
+ }
9584
+ message.blocks = [
9585
+ {
9586
+ id: "text_1",
9587
+ type: "text",
9588
+ text,
9589
+ created_at: message.created_at,
9590
+ updated_at: message.updated_at
9591
+ }
9592
+ ];
9593
+ }
9594
+ function appendAgentEventBlock(message, event, updatedAt) {
9595
+ const blocks = [...message.blocks ?? []];
9596
+ const matchingIndex = blocks.findIndex((block) => {
9597
+ if (block.type !== "agent_events") {
9598
+ return false;
9599
+ }
9600
+ return upsertAgentEventProjection(block.events, event).length === block.events.length;
9601
+ });
9602
+ const targetIndex = matchingIndex >= 0 ? matchingIndex : blocks.at(-1)?.type === "agent_events" ? blocks.length - 1 : -1;
9603
+ if (targetIndex >= 0) {
9604
+ const block = blocks[targetIndex];
9605
+ if (block.type === "agent_events") {
9606
+ blocks[targetIndex] = {
9607
+ ...block,
9608
+ events: upsertAgentEventProjection(block.events, event),
9609
+ updated_at: updatedAt
9610
+ };
9611
+ }
9612
+ } else {
9613
+ blocks.push({
9614
+ id: `tools_${blocks.length + 1}`,
9615
+ type: "agent_events",
9616
+ events: [event],
9617
+ created_at: updatedAt,
9618
+ updated_at: updatedAt
9619
+ });
9620
+ }
9621
+ message.blocks = blocks;
9622
+ }
9623
+ function latestTimestamp(left, right) {
9624
+ const leftTime = Date.parse(left);
9625
+ const rightTime = Date.parse(right);
9626
+ if (Number.isNaN(leftTime)) {
9627
+ return right;
9628
+ }
9629
+ if (Number.isNaN(rightTime)) {
9630
+ return left;
9631
+ }
9632
+ return leftTime >= rightTime ? left : right;
9633
+ }
9634
+ async function readKnownHermesSessions(store) {
9635
+ const ids = /* @__PURE__ */ new Set();
9636
+ const conversationIdsBySessionId = /* @__PURE__ */ new Map();
9637
+ for (const conversationId of await store.listConversationIds()) {
9638
+ const manifest = await store.readManifest(conversationId).catch(() => null);
9639
+ if (!manifest) {
9640
+ continue;
9641
+ }
9642
+ if (manifest.hermes_session_id) {
9643
+ ids.add(manifest.hermes_session_id);
9644
+ rememberKnownHermesConversation(
9645
+ conversationIdsBySessionId,
9646
+ manifest.hermes_session_id,
9647
+ conversationId
9648
+ );
9649
+ }
9650
+ for (const sessionId of manifest.hermes_session_ids ?? []) {
9651
+ ids.add(sessionId);
9652
+ rememberKnownHermesConversation(
9653
+ conversationIdsBySessionId,
9654
+ sessionId,
9655
+ conversationId
9656
+ );
9657
+ }
9658
+ }
9659
+ return { ids, conversationIdsBySessionId };
9660
+ }
9661
+ function rememberKnownHermesConversation(map, sessionId, conversationId) {
9662
+ const current = map.get(sessionId) ?? [];
9663
+ if (!current.includes(conversationId)) {
9664
+ map.set(sessionId, [...current, conversationId]);
9665
+ }
9666
+ }
9667
+ async function discoverHermesProfileNames() {
9668
+ const names = /* @__PURE__ */ new Set([DEFAULT_PROFILE_NAME]);
9669
+ const profilesDir = path14.join(os4.homedir(), ".hermes", "profiles");
9670
+ const entries = await readdir6(profilesDir, { withFileTypes: true }).catch(
9671
+ (error) => {
9672
+ if (isNodeError10(error, "ENOENT")) {
9673
+ return [];
9674
+ }
9675
+ throw error;
9676
+ }
9677
+ );
9678
+ for (const entry of entries) {
9679
+ if (entry.isDirectory() && PROFILE_NAME_PATTERN3.test(entry.name)) {
9680
+ names.add(entry.name);
9681
+ }
9682
+ }
9683
+ return [...names].sort((left, right) => {
9684
+ if (left === DEFAULT_PROFILE_NAME) {
9685
+ return -1;
9686
+ }
9687
+ if (right === DEFAULT_PROFILE_NAME) {
9688
+ return 1;
9689
+ }
9690
+ return left.localeCompare(right);
9691
+ });
9692
+ }
9693
+ async function listProfileSessions(dbPath) {
9694
+ if (!await isFile(dbPath)) {
9695
+ return [];
9696
+ }
9697
+ let db = null;
9698
+ try {
9699
+ const { DatabaseSync } = nodeRequire3(
9700
+ "node:sqlite"
9701
+ );
9702
+ db = new DatabaseSync(dbPath, {
9703
+ readOnly: true,
9704
+ timeout: 1e3
9705
+ });
9706
+ const sessionColumns = readTableColumns(db, "sessions");
9707
+ if (!sessionColumns.has("id")) {
9708
+ return [];
9709
+ }
9710
+ const messageColumns = readTableColumns(db, "messages");
9711
+ const selectColumns = [...sessionColumns].map((column) => `s.${quoteIdentifier(column)}`).join(", ");
9712
+ const lastActiveSql = messageColumns.has("timestamp") ? `COALESCE(
9713
+ (SELECT MAX(m.timestamp) FROM messages m WHERE m.session_id = s.id),
9714
+ s.started_at,
9715
+ 0
9716
+ ) AS last_active` : "COALESCE(s.started_at, 0) AS last_active";
9717
+ const rows = db.prepare(
9718
+ `
9719
+ SELECT ${selectColumns}, ${lastActiveSql}
9720
+ FROM sessions s
9721
+ ORDER BY last_active DESC
9722
+ `
9723
+ ).all();
9724
+ return projectCompressionTips(rows);
9725
+ } finally {
9726
+ db?.close();
9727
+ }
9728
+ }
9729
+ function appendHermesRawMessage(message, row) {
9730
+ const rows = readHermesRawMessageRows(message.raw);
9731
+ message.raw = rows.length === 0 ? {
9732
+ format: "hermes-message",
9733
+ payload: row
9734
+ } : {
9735
+ format: "hermes-message-group",
9736
+ payload: { messages: [...rows, row] }
9737
+ };
9738
+ }
9739
+ function readHermesRawMessageRows(raw) {
9740
+ if (!raw) {
9741
+ return [];
9742
+ }
9743
+ if (raw.format === "hermes-message-group") {
9744
+ const payload = toRecord8(raw.payload);
9745
+ return Array.isArray(payload.messages) ? payload.messages.filter(
9746
+ (item) => typeof item === "object" && item !== null
9747
+ ) : [];
9748
+ }
9749
+ if (raw.format === "hermes-message") {
9750
+ return typeof raw.payload === "object" && raw.payload !== null ? [raw.payload] : [];
9751
+ }
9752
+ return [];
9753
+ }
9754
+ function rememberHermesMessageId(message, row) {
9755
+ if (row.id === void 0 || row.id === null) {
9756
+ return;
9757
+ }
9758
+ const existing = Array.isArray(message.hermes?.message_ids) ? message.hermes.message_ids : message.hermes?.message_id === void 0 ? [] : [message.hermes.message_id];
9759
+ const id = row.id;
9760
+ message.hermes = {
9761
+ ...message.hermes ?? {},
9762
+ message_ids: existing.includes(id) ? existing : [...existing, id]
9763
+ };
9764
+ }
9765
+ function joinImportedText(left, right) {
9766
+ if (!left) {
9767
+ return right;
9768
+ }
9769
+ if (!right) {
9770
+ return left;
9771
+ }
9772
+ if (/\s$/u.test(left) || /^\s/u.test(right)) {
9773
+ return `${left}${right}`;
9774
+ }
9775
+ return `${left}
9776
+
9777
+ ${right}`;
9778
+ }
9779
+ function projectCompressionTips(rows) {
9780
+ const byId = /* @__PURE__ */ new Map();
9781
+ const childrenByParent = /* @__PURE__ */ new Map();
9782
+ for (const row of rows) {
9783
+ const id = readString9(row, "id");
9784
+ if (!id) {
9785
+ continue;
9786
+ }
9787
+ byId.set(id, row);
9788
+ const parentId = readString9(row, "parent_session_id");
9789
+ if (parentId) {
9790
+ const children = childrenByParent.get(parentId) ?? [];
9791
+ children.push(row);
9792
+ childrenByParent.set(parentId, children);
9793
+ }
9794
+ }
9795
+ const projected = [];
9796
+ for (const row of rows) {
9797
+ const id = readString9(row, "id");
9798
+ if (!id || readString9(row, "parent_session_id")) {
9799
+ continue;
9800
+ }
9801
+ let tip = row;
9802
+ const visited = /* @__PURE__ */ new Set([id]);
9803
+ while (readString9(tip, "end_reason") === "compression") {
9804
+ const tipId2 = readString9(tip, "id");
9805
+ if (!tipId2) {
9806
+ break;
9807
+ }
9808
+ const next = (childrenByParent.get(tipId2) ?? []).filter((child) => readString9(child, "id")).sort(
9809
+ (left, right) => (readNumber2(right.last_active) ?? 0) - (readNumber2(left.last_active) ?? 0)
9810
+ )[0];
9811
+ const nextId = next ? readString9(next, "id") : null;
9812
+ if (!next || !nextId || visited.has(nextId)) {
9813
+ break;
9814
+ }
9815
+ tip = next;
9816
+ visited.add(nextId);
9817
+ }
9818
+ const tipId = readString9(tip, "id");
9819
+ if (tipId) {
9820
+ projected.push({
9821
+ ...tip,
9822
+ id: tipId,
9823
+ _lineage_root_id: id,
9824
+ started_at: readNumber2(row.started_at) ?? readNumber2(tip.started_at)
9825
+ });
9826
+ }
9827
+ }
9828
+ return projected;
9829
+ }
9830
+ async function readHermesSessionMessages(candidate) {
9831
+ const [dbMessages, jsonlMessages] = await Promise.all([
9832
+ readStateDbMessages(candidate.dbPath, candidate.session.id),
9833
+ readJsonlMessages(candidate.profileName, candidate.session.id)
9834
+ ]);
9835
+ return jsonlMessages.length > dbMessages.length ? jsonlMessages : dbMessages;
9836
+ }
9837
+ async function readStateDbMessages(dbPath, sessionId) {
9838
+ if (!await isFile(dbPath)) {
9839
+ return [];
9840
+ }
9841
+ let db = null;
9842
+ try {
9843
+ const { DatabaseSync } = nodeRequire3(
9844
+ "node:sqlite"
9845
+ );
9846
+ db = new DatabaseSync(dbPath, {
9847
+ readOnly: true,
9848
+ timeout: 1e3
9849
+ });
9850
+ const columns = readTableColumns(db, "messages");
9851
+ if (!columns.has("session_id") || !columns.has("role")) {
9852
+ return [];
9853
+ }
9854
+ const selectColumns = MESSAGE_COLUMNS.map(
9855
+ (column) => columns.has(column) ? quoteIdentifier(column) : `NULL AS ${column}`
9856
+ ).join(", ");
9857
+ return db.prepare(
9858
+ `
9859
+ SELECT ${selectColumns}
9860
+ FROM messages
9861
+ WHERE session_id = ?
9862
+ ORDER BY timestamp, id
9863
+ `
9864
+ ).all(sessionId);
9865
+ } catch {
9866
+ return [];
9867
+ } finally {
9868
+ db?.close();
9869
+ }
9870
+ }
9871
+ async function readJsonlMessages(profileName, sessionId) {
9872
+ if (!/^[A-Za-z0-9._:-]{1,160}$/u.test(sessionId)) {
9873
+ return [];
9874
+ }
9875
+ const profileDir = resolveHermesProfileDir(profileName);
9876
+ const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path14.join(profileDir, "sessions"));
9877
+ const transcriptPath = path14.join(sessionsDir, `${sessionId}.jsonl`);
9878
+ const raw = await readFile9(transcriptPath, "utf8").catch((error) => {
9879
+ if (isNodeError10(error, "ENOENT")) {
9880
+ return "";
9881
+ }
9882
+ throw error;
9883
+ });
9884
+ if (!raw.trim()) {
9885
+ return [];
9886
+ }
9887
+ const rows = [];
9888
+ for (const line of raw.split(/\r?\n/u)) {
9889
+ if (!line.trim()) {
9890
+ continue;
9891
+ }
9892
+ try {
9893
+ const parsed = JSON.parse(line);
9894
+ const normalized = normalizeJsonlMessage(parsed);
9895
+ if (normalized) {
9896
+ rows.push(normalized);
9897
+ }
9898
+ } catch {
9899
+ continue;
9900
+ }
9901
+ }
9902
+ return rows;
9903
+ }
9904
+ function normalizeJsonlMessage(row) {
9905
+ const role = readString9(row, "role");
9906
+ if (!role) {
9907
+ return null;
9908
+ }
9909
+ const content = normalizeContent(row.content);
9910
+ const timestamp = readNumber2(row.timestamp) ?? readNumber2(row.created_at) ?? readNumber2(row.createdAt);
9911
+ return {
9912
+ ...row,
9913
+ role,
9914
+ content,
9915
+ timestamp: timestamp ?? void 0
9916
+ };
9917
+ }
9918
+ async function hydrateImportedConversationMedia(input) {
9919
+ const snapshot = await input.store.readSnapshot(input.conversationId);
9920
+ const imports = snapshot.messages.map((message) => ({
9921
+ messageId: message.id,
9922
+ references: collectMessageMediaReferences(message)
9923
+ })).filter((item) => item.references.length > 0);
9924
+ if (imports.length === 0) {
9925
+ return;
9926
+ }
9927
+ const outcomes = /* @__PURE__ */ new Map();
9928
+ for (const item of imports) {
9929
+ const result = await importMediaReferencesForMessage(
9930
+ {
9931
+ logger: input.logger,
9932
+ readSnapshot: (conversationId) => input.store.readSnapshot(conversationId),
9933
+ writeSnapshot: (conversationId, nextSnapshot2) => input.store.writeSnapshot(conversationId, nextSnapshot2),
9934
+ appendEvent: async (conversationId, event) => ({
9935
+ seq: 0,
9936
+ conversation_id: conversationId,
9937
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
9938
+ ...event
9939
+ }),
9940
+ writeBlob: (conversationId, blob) => writeConversationBlob(input.paths, conversationId, blob, {
9941
+ maxBytes: MAX_IMPORTED_HERMES_MEDIA_BYTES
9942
+ })
9943
+ },
9944
+ {
9945
+ conversationId: input.conversationId,
9946
+ runId: IMPORTED_MEDIA_PLACEHOLDER_RUN_ID,
9947
+ messageId: item.messageId,
9948
+ references: item.references
9949
+ }
9950
+ );
9951
+ if (result.imported_count > 0 || result.failed_count > 0) {
9952
+ outcomes.set(item.messageId, {
9953
+ imported: result.imported_count > 0,
9954
+ failed: result.failed_count > 0
9955
+ });
9956
+ }
9957
+ }
9958
+ if (outcomes.size === 0) {
9959
+ return;
9960
+ }
9961
+ const nextSnapshot = await input.store.readSnapshot(input.conversationId);
9962
+ let changed = false;
9963
+ for (const message of nextSnapshot.messages) {
9964
+ const outcome = outcomes.get(message.id);
9965
+ if (!outcome) {
9966
+ continue;
9967
+ }
9968
+ if (outcome.imported) {
9969
+ cleanMessageTextParts(message);
9970
+ changed = true;
9971
+ }
9972
+ if (outcome.failed) {
9973
+ changed = appendImportedMediaImportFailureNotice(message) || changed;
9974
+ }
9975
+ }
9976
+ if (changed) {
9977
+ await input.store.writeSnapshot(input.conversationId, nextSnapshot);
9978
+ }
9979
+ }
9980
+ function collectMessageMediaReferences(message) {
9981
+ return message.parts.flatMap(
9982
+ (part) => part.type === "text" && part.text ? collectMediaTags(part.text) : []
9983
+ );
9984
+ }
9985
+ function appendImportedMediaImportFailureNotice(message) {
9986
+ const hermes = toRecord8(message.hermes);
9987
+ if (hermes.media_import_failure_notice_appended === true) {
9988
+ return false;
9989
+ }
9990
+ const failures = readMediaImportFailures(message);
9991
+ if (failures.length === 0) {
9992
+ return false;
9993
+ }
9994
+ const notice = formatImportedMediaImportFailureNotice(failures);
9995
+ const textPart = message.parts.find((part) => part.type === "text");
9996
+ if (textPart) {
9997
+ const currentText = textPart.text ?? "";
9998
+ const separator = currentText.trim().length > 0 ? "\n\n" : "";
9999
+ textPart.text = `${currentText.trimEnd()}${separator}${notice}`;
10000
+ } else {
10001
+ message.parts.unshift({ type: "text", text: notice });
10002
+ }
10003
+ message.hermes = {
10004
+ ...hermes,
10005
+ media_import_failure_notice_appended: true
10006
+ };
10007
+ return true;
10008
+ }
10009
+ function formatImportedMediaImportFailureNotice(failures) {
10010
+ const filenames = failures.map((failure) => failure.filename);
10011
+ const target = filenames.length === 1 ? `\u6587\u4EF6\u201C${filenames[0]}\u201D` : `${filenames.length} \u4E2A\u6587\u4EF6\uFF08${formatImportedFilenameList(filenames)}\uFF09`;
10012
+ const permissionDenied = failures.some((failure) => {
10013
+ const code = failure.code?.toUpperCase();
10014
+ return code === "EPERM" || code === "EACCES" || /operation not permitted|permission denied/iu.test(failure.reason);
10015
+ });
10016
+ if (permissionDenied) {
10017
+ return `${target}\u6CA1\u80FD\u4F5C\u4E3A\u9644\u4EF6\u5BFC\u5165\uFF1AHermes Link \u8BFB\u53D6\u6587\u4EF6\u65F6\u88AB macOS \u62D2\u7EDD\u4E86\u3002\u8BF7\u5728\u201C\u7CFB\u7EDF\u8BBE\u7F6E > \u9690\u79C1\u4E0E\u5B89\u5168\u6027 > \u5B8C\u5168\u78C1\u76D8\u8BBF\u95EE\u6743\u9650\u201D\u91CC\u7ED9\u8FD0\u884C Link \u7684\u7EC8\u7AEF\u6216 Node \u6388\u6743\u540E\u91CD\u8BD5\u3002`;
10018
+ }
10019
+ return `${target}\u6CA1\u80FD\u4F5C\u4E3A\u9644\u4EF6\u5BFC\u5165\uFF1AHermes Link \u8BFB\u53D6\u672C\u673A\u6587\u4EF6\u5931\u8D25\u4E86\uFF0C\u8BF7\u786E\u8BA4\u6587\u4EF6\u8FD8\u5728\u539F\u4F4D\u7F6E\u5E76\u7A0D\u540E\u91CD\u8BD5\u3002`;
10020
+ }
10021
+ function formatImportedFilenameList(filenames) {
10022
+ const preview = filenames.slice(0, 3).map((filename) => `\u201C${filename}\u201D`);
10023
+ const remaining = filenames.length - preview.length;
10024
+ return remaining > 0 ? `${preview.join("\u3001")} \u7B49 ${filenames.length} \u4E2A` : preview.join("\u3001");
10025
+ }
10026
+ function toLinkMessage(input) {
10027
+ const role = normalizeMessageRole(input.message.role);
10028
+ const text = normalizeContent(input.message.content);
10029
+ const createdAt = isoFromHermesTime(input.message.timestamp) ?? new Date(Date.now() + input.index).toISOString();
10030
+ return {
10031
+ id: `msg_${randomUUID6().replaceAll("-", "")}`,
10032
+ schema_version: 1,
10033
+ conversation_id: input.conversationId,
10034
+ role,
10035
+ status: "completed",
10036
+ created_at: createdAt,
10037
+ updated_at: createdAt,
10038
+ sender: senderForRole({
10039
+ role,
10040
+ profileName: input.profileName,
10041
+ profileUid: input.profileUid,
10042
+ profileDisplayName: input.profileDisplayName
10043
+ }),
10044
+ parts: text ? [{ type: "text", text }] : [],
10045
+ attachments: [],
10046
+ hermes: {
10047
+ session_id: input.sessionId,
10048
+ message_id: input.message.id,
10049
+ imported_from: "hermes",
10050
+ import_projection: HERMES_IMPORT_PROJECTION_VERSION
10051
+ },
10052
+ raw: {
10053
+ format: "hermes-message",
10054
+ payload: input.message
10055
+ }
10056
+ };
10057
+ }
10058
+ function senderForRole(input) {
10059
+ switch (input.role) {
10060
+ case "user":
10061
+ return { id: "hermes_user", type: "human", display_name: "Me" };
10062
+ case "assistant":
10063
+ return {
10064
+ id: `agent_${input.profileName}`,
10065
+ type: "agent",
10066
+ display_name: input.profileDisplayName,
10067
+ profile_uid: input.profileUid,
10068
+ profile: input.profileName
10069
+ };
10070
+ case "tool":
10071
+ return { id: "hermes_tool", type: "tool", display_name: "Tool" };
10072
+ case "system":
10073
+ return { id: "hermes_system", type: "system", display_name: "System" };
10074
+ }
10075
+ }
10076
+ function firstUserText(snapshot) {
10077
+ return snapshot.messages.find((message) => message.role === "user")?.parts.find((part) => part.type === "text")?.text?.slice(0, 80);
10078
+ }
10079
+ function normalizeTitle(value) {
10080
+ const normalized = value?.replace(/\s+/gu, " ").trim();
10081
+ return normalized || DEFAULT_CONVERSATION_TITLE;
10082
+ }
10083
+ function normalizeMessageRole(value) {
10084
+ switch (value?.trim().toLowerCase()) {
10085
+ case "user":
10086
+ return "user";
10087
+ case "assistant":
10088
+ return "assistant";
10089
+ case "tool":
10090
+ return "tool";
10091
+ case "system":
10092
+ return "system";
10093
+ default:
10094
+ return "system";
10095
+ }
10096
+ }
10097
+ function normalizeContent(value) {
10098
+ if (typeof value === "string") {
10099
+ return value;
10100
+ }
10101
+ if (Array.isArray(value)) {
10102
+ return value.map((item) => {
10103
+ if (typeof item === "string") {
10104
+ return item;
10105
+ }
10106
+ if (typeof item === "object" && item !== null) {
10107
+ return readString9(item, "text") ?? "";
10108
+ }
10109
+ return "";
10110
+ }).filter(Boolean).join("");
10111
+ }
10112
+ return "";
10113
+ }
10114
+ function parseJsonValue(value) {
10115
+ if (typeof value !== "string") {
10116
+ return void 0;
10117
+ }
10118
+ const trimmed = value.trim();
10119
+ if (!trimmed) {
10120
+ return void 0;
10121
+ }
10122
+ try {
10123
+ return JSON.parse(trimmed);
10124
+ } catch {
10125
+ return void 0;
10126
+ }
10127
+ }
10128
+ function toRecord8(value) {
10129
+ return typeof value === "object" && value !== null ? value : {};
10130
+ }
10131
+ function isDeletedSession(session) {
10132
+ return readBoolean(session.deleted) || readBoolean(session.is_deleted) || Boolean(readString9(session, "deleted_at")) || ["deleted", "removed"].includes(readString9(session, "status") ?? "");
10133
+ }
10134
+ function isHiddenSession(session) {
10135
+ const source = readString9(session, "source")?.toLowerCase();
10136
+ const status = readString9(session, "status")?.toLowerCase();
10137
+ const visibility = readString9(session, "visibility")?.toLowerCase();
10138
+ return Boolean(source && HIDDEN_SESSION_SOURCES.has(source)) || readBoolean(session.hidden) || readBoolean(session.archived) || Boolean(readString9(session, "archived_at")) || status === "hidden" || status === "archived" || visibility === "hidden" || visibility === "hide";
10139
+ }
10140
+ function readTableColumns(db, tableName) {
10141
+ try {
10142
+ const rows = db.prepare(`PRAGMA table_info(${quoteIdentifier(tableName)})`).all();
10143
+ return new Set(
10144
+ rows.map((row) => typeof row.name === "string" ? row.name : "").filter(Boolean)
10145
+ );
10146
+ } catch {
10147
+ return /* @__PURE__ */ new Set();
10148
+ }
10149
+ }
10150
+ function quoteIdentifier(value) {
10151
+ return `"${value.replaceAll('"', '""')}"`;
10152
+ }
10153
+ async function isFile(filePath) {
10154
+ return stat8(filePath).then((value) => value.isFile()).catch((error) => {
10155
+ if (isNodeError10(error, "ENOENT")) {
10156
+ return false;
10157
+ }
10158
+ throw error;
10159
+ });
10160
+ }
10161
+ function createConversationId() {
10162
+ return `conv_${randomUUID6().replaceAll("-", "")}`;
10163
+ }
10164
+ function isoFromHermesTime(value) {
10165
+ const numeric = readNumber2(value);
10166
+ if (!numeric || numeric <= 0) {
10167
+ return void 0;
10168
+ }
10169
+ const millis = numeric > 1e10 ? numeric : numeric * 1e3;
10170
+ return new Date(millis).toISOString();
10171
+ }
10172
+ function readString9(payload, key) {
10173
+ const value = payload[key];
10174
+ return typeof value === "string" && value.trim() ? value.trim() : null;
10175
+ }
10176
+ function readNumber2(value) {
10177
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
10178
+ }
10179
+ function readBoolean(value) {
10180
+ if (value === true || value === 1) {
10181
+ return true;
10182
+ }
10183
+ if (typeof value === "string") {
10184
+ return ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
10185
+ }
10186
+ return false;
10187
+ }
10188
+ function isNodeError10(error, code) {
10189
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
10190
+ }
10191
+
10192
+ // src/conversations/run-lifecycle.ts
10193
+ import { readdir as readdir7 } from "fs/promises";
10194
+
10195
+ // src/hermes/api-server.ts
10196
+ async function listHermesModels(options = {}) {
10197
+ const response = await callHermesApi("/v1/models", { method: "GET" }, options);
10198
+ if (response.status === 404) {
10199
+ return { models: [] };
10200
+ }
10201
+ return await readJsonResponse(response);
10202
+ }
10203
+ async function listHermesCronJobs(options = {}) {
10204
+ const query = options.includeDisabled ? "?include_disabled=true" : "";
10205
+ const response = await callHermesApi(
10206
+ `/api/jobs${query}`,
10207
+ { method: "GET" },
10208
+ options
10209
+ );
10210
+ const payload = await readJsonResponse(response);
10211
+ const jobs = payload.jobs;
10212
+ return Array.isArray(jobs) ? jobs.filter(isRecord).map((job) => ({ ...job })) : [];
10213
+ }
10214
+ async function getHermesCronJob(jobId, options = {}) {
10215
+ const response = await callHermesApi(
10216
+ `/api/jobs/${encodeURIComponent(jobId)}`,
10217
+ { method: "GET" },
10218
+ options
10219
+ );
10220
+ const payload = await readJsonResponse(response);
10221
+ if (!isRecord(payload.job)) {
10222
+ throw new LinkHttpError(
10223
+ 502,
10224
+ "hermes_cron_job_invalid",
10225
+ "Hermes API Server did not return a cron job"
10226
+ );
10227
+ }
10228
+ return { ...payload.job };
10229
+ }
10230
+ async function createHermesCronJob(input, options = {}) {
10231
+ const response = await callHermesApi(
10232
+ "/api/jobs",
10233
+ {
10234
+ method: "POST",
10235
+ body: JSON.stringify(input),
10236
+ headers: { "content-type": "application/json" }
10237
+ },
10238
+ options
10239
+ );
10240
+ const payload = await readJsonResponse(response);
10241
+ if (!isRecord(payload.job)) {
10242
+ throw new LinkHttpError(
10243
+ 502,
10244
+ "hermes_cron_job_invalid",
10245
+ "Hermes API Server did not return a cron job"
10246
+ );
10247
+ }
10248
+ return { ...payload.job };
10249
+ }
10250
+ async function updateHermesCronJob(jobId, input, options = {}) {
10251
+ const response = await callHermesApi(
10252
+ `/api/jobs/${encodeURIComponent(jobId)}`,
10253
+ {
10254
+ method: "PATCH",
10255
+ body: JSON.stringify(input),
10256
+ headers: { "content-type": "application/json" }
10257
+ },
10258
+ options
10259
+ );
10260
+ const payload = await readJsonResponse(response);
10261
+ if (!isRecord(payload.job)) {
10262
+ throw new LinkHttpError(
10263
+ 502,
10264
+ "hermes_cron_job_invalid",
10265
+ "Hermes API Server did not return a cron job"
10266
+ );
10267
+ }
10268
+ return { ...payload.job };
10269
+ }
10270
+ async function deleteHermesCronJob(jobId, options = {}) {
10271
+ const response = await callHermesApi(
10272
+ `/api/jobs/${encodeURIComponent(jobId)}`,
10273
+ { method: "DELETE" },
10274
+ options
10275
+ );
10276
+ await readJsonResponse(response);
10277
+ }
10278
+ async function runHermesCronJobAction(jobId, action, options = {}) {
10279
+ const response = await callHermesApi(
10280
+ `/api/jobs/${encodeURIComponent(jobId)}/${action}`,
10281
+ { method: "POST" },
10282
+ options
10283
+ );
10284
+ const payload = await readJsonResponse(response);
10285
+ if (!isRecord(payload.job)) {
10286
+ throw new LinkHttpError(
10287
+ 502,
10288
+ "hermes_cron_job_invalid",
10289
+ "Hermes API Server did not return a cron job"
10290
+ );
10291
+ }
10292
+ return { ...payload.job };
10293
+ }
10294
+ async function createHermesRun(input, options = {}) {
10295
+ const response = await callHermesApi(
10296
+ "/v1/runs",
10297
+ {
10298
+ method: "POST",
10299
+ body: JSON.stringify(input),
10300
+ headers: { "content-type": "application/json" }
10301
+ },
10302
+ options
10303
+ );
10304
+ if (response.status === 404 || response.status === 503) {
10305
+ assertHermesRunsApiSupported(
10306
+ await readHermesVersion().catch(() => null),
10307
+ response.status
10308
+ );
10309
+ throw new LinkHttpError(
10310
+ 503,
10311
+ "hermes_api_server_unavailable",
10312
+ "Hermes API Server is unavailable"
10313
+ );
10314
+ }
10315
+ const payload = await readJsonResponse(response);
10316
+ const runId = readString10(payload, "run_id") ?? readString10(payload, "runId") ?? readString10(payload, "id");
10317
+ if (!runId) {
10318
+ throw new LinkHttpError(
10319
+ 502,
10320
+ "hermes_run_invalid",
10321
+ "Hermes API Server did not return a run id"
10322
+ );
10323
+ }
10324
+ return { run_id: runId, fallback: false };
10325
+ }
10326
+ async function streamHermesRunEvents(runId, options = {}) {
10327
+ const response = await callHermesApi(
10328
+ `/v1/runs/${encodeURIComponent(runId)}/events`,
10329
+ { method: "GET", signal: options.signal },
10330
+ options
8988
10331
  );
8989
10332
  assertHermesRunsApiSupported(
8990
10333
  await readHermesVersion().catch(() => null),
@@ -9083,10 +10426,10 @@ async function cancelHermesRun(runId, options = {}) {
9083
10426
  );
9084
10427
  }
9085
10428
  }
9086
- async function callHermesApi(path24, init, options) {
10429
+ async function callHermesApi(path25, init, options) {
9087
10430
  const method = init.method ?? "GET";
9088
10431
  const startedAt = Date.now();
9089
- void options.logger?.debug("hermes_api_request_started", { method, path: path24 });
10432
+ void options.logger?.debug("hermes_api_request_started", { method, path: path25 });
9090
10433
  const availability = await ensureHermesApiServerAvailable({
9091
10434
  fetchImpl: options.fetchImpl,
9092
10435
  logger: options.logger,
@@ -9094,21 +10437,21 @@ async function callHermesApi(path24, init, options) {
9094
10437
  });
9095
10438
  let config = availability.configResult.apiServer;
9096
10439
  const fetcher = options.fetchImpl ?? fetch;
9097
- const request = () => fetchHermesApi(fetcher, config, path24, init, options);
10440
+ const request = () => fetchHermesApi(fetcher, config, path25, init, options);
9098
10441
  let response;
9099
10442
  try {
9100
10443
  response = await request();
9101
10444
  } catch (error) {
9102
- logHermesApiError(options.logger, method, path24, startedAt, error);
10445
+ logHermesApiError(options.logger, method, path25, startedAt, error);
9103
10446
  throw error;
9104
10447
  }
9105
10448
  if (response.status !== 401) {
9106
- logHermesApiResponse(options.logger, method, path24, startedAt, response);
10449
+ logHermesApiResponse(options.logger, method, path25, startedAt, response);
9107
10450
  return response;
9108
10451
  }
9109
10452
  void options.logger?.warn("hermes_api_request_retrying_after_401", {
9110
10453
  method,
9111
- path: path24,
10454
+ path: path25,
9112
10455
  duration_ms: Date.now() - startedAt
9113
10456
  });
9114
10457
  const refreshedAvailability = await ensureHermesApiServerAvailable({
@@ -9121,20 +10464,20 @@ async function callHermesApi(path24, init, options) {
9121
10464
  try {
9122
10465
  response = await request();
9123
10466
  } catch (error) {
9124
- logHermesApiError(options.logger, method, path24, startedAt, error);
10467
+ logHermesApiError(options.logger, method, path25, startedAt, error);
9125
10468
  throw error;
9126
10469
  }
9127
- logHermesApiResponse(options.logger, method, path24, startedAt, response);
10470
+ logHermesApiResponse(options.logger, method, path25, startedAt, response);
9128
10471
  return response;
9129
10472
  }
9130
- async function fetchHermesApi(fetcher, config, path24, init, options) {
10473
+ async function fetchHermesApi(fetcher, config, path25, init, options) {
9131
10474
  const headers = new Headers(init.headers);
9132
10475
  headers.set("accept", headers.get("accept") ?? "application/json");
9133
10476
  if (config.key) {
9134
10477
  headers.set("x-api-key", config.key);
9135
10478
  headers.set("authorization", `Bearer ${config.key}`);
9136
10479
  }
9137
- return await fetcher(`http://127.0.0.1:${config.port}${path24}`, {
10480
+ return await fetcher(`http://127.0.0.1:${config.port}${path25}`, {
9138
10481
  ...init,
9139
10482
  headers
9140
10483
  }).catch((error) => {
@@ -9142,7 +10485,7 @@ async function fetchHermesApi(fetcher, config, path24, init, options) {
9142
10485
  throw error;
9143
10486
  }
9144
10487
  void options.logger?.warn("hermes_api_server_connect_failed", {
9145
- path: path24,
10488
+ path: path25,
9146
10489
  port: config.port ?? null,
9147
10490
  error: error instanceof Error ? error.message : String(error)
9148
10491
  });
@@ -9153,10 +10496,10 @@ async function fetchHermesApi(fetcher, config, path24, init, options) {
9153
10496
  );
9154
10497
  });
9155
10498
  }
9156
- function logHermesApiResponse(logger, method, path24, startedAt, response) {
10499
+ function logHermesApiResponse(logger, method, path25, startedAt, response) {
9157
10500
  const fields = {
9158
10501
  method,
9159
- path: path24,
10502
+ path: path25,
9160
10503
  status: response.status,
9161
10504
  duration_ms: Date.now() - startedAt
9162
10505
  };
@@ -9176,10 +10519,10 @@ async function logHermesApiFailureResponse(logger, fields, response) {
9176
10519
  ...upstreamError ? { upstream_error: upstreamError } : {}
9177
10520
  });
9178
10521
  }
9179
- function logHermesApiError(logger, method, path24, startedAt, error) {
10522
+ function logHermesApiError(logger, method, path25, startedAt, error) {
9180
10523
  void logger?.warn("hermes_api_request_failed", {
9181
10524
  method,
9182
- path: path24,
10525
+ path: path25,
9183
10526
  duration_ms: Date.now() - startedAt,
9184
10527
  ...error instanceof LinkHttpError ? { status: error.status, code: error.code } : {},
9185
10528
  error: error instanceof Error ? error.message : String(error)
@@ -9228,23 +10571,23 @@ function isRecord(value) {
9228
10571
  }
9229
10572
  function readUpstreamMessage(payload, raw) {
9230
10573
  const error = typeof payload?.error === "object" && payload.error !== null ? payload.error : null;
9231
- const message = readString9(error ?? {}, "message") ?? readString9(payload ?? {}, "message");
10574
+ const message = readString10(error ?? {}, "message") ?? readString10(payload ?? {}, "message");
9232
10575
  if (message) {
9233
10576
  return message;
9234
10577
  }
9235
10578
  const body = raw.trim().replace(/\s+/gu, " ").slice(0, 500);
9236
10579
  return body || "empty response body";
9237
10580
  }
9238
- function readString9(payload, key) {
10581
+ function readString10(payload, key) {
9239
10582
  const value = payload[key];
9240
10583
  return typeof value === "string" && value.trim() ? value.trim() : null;
9241
10584
  }
9242
10585
 
9243
10586
  // src/conversations/history-builder.ts
9244
- import { readFile as readFile9, stat as stat8 } from "fs/promises";
9245
- import { createRequire as createRequire3 } from "module";
9246
- import path14 from "path";
9247
- var nodeRequire3 = createRequire3(import.meta.url);
10587
+ import { readFile as readFile10, stat as stat9 } from "fs/promises";
10588
+ import { createRequire as createRequire4 } from "module";
10589
+ import path15 from "path";
10590
+ var nodeRequire4 = createRequire4(import.meta.url);
9248
10591
  var HISTORY_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
9249
10592
  var HERMES_HISTORY_COLUMNS = [
9250
10593
  "role",
@@ -9305,13 +10648,13 @@ async function readHermesTranscriptHistory(sessionId, profileName) {
9305
10648
  }
9306
10649
  const normalizedProfileName = isValidProfileName2(profileName) ? profileName : "default";
9307
10650
  const profileDir = resolveHermesProfileDir(normalizedProfileName);
9308
- const dbPath = path14.join(profileDir, "state.db");
10651
+ const dbPath = path15.join(profileDir, "state.db");
9309
10652
  const sessionsDirConfig = await readHermesSessionsDir(normalizedProfileName).then((value) => ({
9310
10653
  sessionsDir: value.sessionsDir,
9311
10654
  configured: value.configured,
9312
10655
  configError: false
9313
10656
  })).catch(() => ({
9314
- sessionsDir: path14.join(profileDir, "sessions"),
10657
+ sessionsDir: path15.join(profileDir, "sessions"),
9315
10658
  configured: false,
9316
10659
  configError: true
9317
10660
  }));
@@ -9351,8 +10694,8 @@ async function readHermesTranscriptHistory(sessionId, profileName) {
9351
10694
  };
9352
10695
  }
9353
10696
  async function readHermesStateDbHistory(dbPath, sessionId) {
9354
- const exists = await stat8(dbPath).then((value) => value.isFile()).catch((error) => {
9355
- if (isNodeError10(error, "ENOENT")) {
10697
+ const exists = await stat9(dbPath).then((value) => value.isFile()).catch((error) => {
10698
+ if (isNodeError11(error, "ENOENT")) {
9356
10699
  return false;
9357
10700
  }
9358
10701
  throw error;
@@ -9369,9 +10712,9 @@ async function readHermesJsonlHistory(sessionsDir, sessionId) {
9369
10712
  if (!isValidSessionFileStem(sessionId)) {
9370
10713
  return empty;
9371
10714
  }
9372
- const transcriptPath = path14.join(sessionsDir, `${sessionId}.jsonl`);
9373
- const raw = await readFile9(transcriptPath, "utf8").catch((error) => {
9374
- if (isNodeError10(error, "ENOENT")) {
10715
+ const transcriptPath = path15.join(sessionsDir, `${sessionId}.jsonl`);
10716
+ const raw = await readFile10(transcriptPath, "utf8").catch((error) => {
10717
+ if (isNodeError11(error, "ENOENT")) {
9375
10718
  return "";
9376
10719
  }
9377
10720
  throw error;
@@ -9406,14 +10749,14 @@ async function readHermesJsonlHistory(sessionsDir, sessionId) {
9406
10749
  function readHistoryRows(dbPath, sessionId) {
9407
10750
  let db = null;
9408
10751
  try {
9409
- const { DatabaseSync } = nodeRequire3(
10752
+ const { DatabaseSync } = nodeRequire4(
9410
10753
  "node:sqlite"
9411
10754
  );
9412
10755
  db = new DatabaseSync(dbPath, {
9413
10756
  readOnly: true,
9414
10757
  timeout: 1e3
9415
10758
  });
9416
- const columns = readTableColumns(db, "messages");
10759
+ const columns = readTableColumns2(db, "messages");
9417
10760
  if (!columns.has("role") || !columns.has("content")) {
9418
10761
  return [];
9419
10762
  }
@@ -9518,7 +10861,7 @@ function normalizeHistoryMessage(role, content) {
9518
10861
  if (role !== "user" && role !== "assistant") {
9519
10862
  return null;
9520
10863
  }
9521
- const text = normalizeContent(content).trim();
10864
+ const text = normalizeContent2(content).trim();
9522
10865
  if (!text) {
9523
10866
  return null;
9524
10867
  }
@@ -9529,7 +10872,7 @@ function normalizeHistoryRecord(record) {
9529
10872
  if (!role) {
9530
10873
  return null;
9531
10874
  }
9532
- const content = normalizeContent(record.content);
10875
+ const content = normalizeContent2(record.content);
9533
10876
  const message = { role, content };
9534
10877
  assignString(message, "tool_call_id", record.tool_call_id);
9535
10878
  assignString(message, "tool_name", record.tool_name);
@@ -9549,7 +10892,7 @@ function normalizeHistoryRecord(record) {
9549
10892
  }
9550
10893
  return message;
9551
10894
  }
9552
- function normalizeContent(value) {
10895
+ function normalizeContent2(value) {
9553
10896
  if (typeof value === "string") {
9554
10897
  return value;
9555
10898
  }
@@ -9591,12 +10934,12 @@ function hasReplayMetadata(message) {
9591
10934
  message.tool_call_id || message.tool_name || message.tool_calls || message.reasoning || message.reasoning_content || message.reasoning_details || message.codex_reasoning_items
9592
10935
  );
9593
10936
  }
9594
- function readTableColumns(db, table) {
10937
+ function readTableColumns2(db, table) {
9595
10938
  return new Set(
9596
10939
  db.prepare(`PRAGMA table_info(${table})`).all().map((row) => row.name).filter((name) => typeof name === "string")
9597
10940
  );
9598
10941
  }
9599
- function isNodeError10(error, code) {
10942
+ function isNodeError11(error, code) {
9600
10943
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
9601
10944
  }
9602
10945
  function isValidProfileName2(value) {
@@ -9614,8 +10957,8 @@ function normalizeProfileForCompare(value) {
9614
10957
 
9615
10958
  // src/hermes/stt.ts
9616
10959
  import { execFile as execFile3 } from "child_process";
9617
- import { access as access2, readFile as readFile10, stat as stat9 } from "fs/promises";
9618
- import path15 from "path";
10960
+ import { access as access2, readFile as readFile11, stat as stat10 } from "fs/promises";
10961
+ import path16 from "path";
9619
10962
  import { promisify as promisify3 } from "util";
9620
10963
  var execFileAsync3 = promisify3(execFile3);
9621
10964
  var STT_RESULT_PREFIX = "__HERMES_LINK_STT__";
@@ -9710,7 +11053,7 @@ async function buildHermesSttEnv(profileName) {
9710
11053
  };
9711
11054
  const devSource = await findDevHermesAgentSource();
9712
11055
  if (devSource) {
9713
- env.PYTHONPATH = [devSource, env.PYTHONPATH].filter(Boolean).join(path15.delimiter);
11056
+ env.PYTHONPATH = [devSource, env.PYTHONPATH].filter(Boolean).join(path16.delimiter);
9714
11057
  }
9715
11058
  return env;
9716
11059
  }
@@ -9757,14 +11100,14 @@ async function resolveHermesPythonCommand() {
9757
11100
  };
9758
11101
  }
9759
11102
  async function resolveExecutablePath(command) {
9760
- if (path15.isAbsolute(command)) {
11103
+ if (path16.isAbsolute(command)) {
9761
11104
  return await isExecutableFile(command) ? command : null;
9762
11105
  }
9763
11106
  const pathEnv = process.env.PATH ?? "";
9764
11107
  const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
9765
- for (const dir of pathEnv.split(path15.delimiter)) {
11108
+ for (const dir of pathEnv.split(path16.delimiter)) {
9766
11109
  for (const extension of extensions) {
9767
- const candidate = path15.join(dir, `${command}${extension}`);
11110
+ const candidate = path16.join(dir, `${command}${extension}`);
9768
11111
  if (await isExecutableFile(candidate)) {
9769
11112
  return candidate;
9770
11113
  }
@@ -9774,7 +11117,7 @@ async function resolveExecutablePath(command) {
9774
11117
  }
9775
11118
  async function isExecutableFile(filePath) {
9776
11119
  try {
9777
- const info = await stat9(filePath);
11120
+ const info = await stat10(filePath);
9778
11121
  if (!info.isFile()) {
9779
11122
  return false;
9780
11123
  }
@@ -9785,7 +11128,7 @@ async function isExecutableFile(filePath) {
9785
11128
  }
9786
11129
  }
9787
11130
  async function readShebang(filePath) {
9788
- const raw = await readFile10(filePath, "utf8").catch(() => "");
11131
+ const raw = await readFile11(filePath, "utf8").catch(() => "");
9789
11132
  const firstLine = raw.split(/\r?\n/u)[0]?.trim() ?? "";
9790
11133
  return firstLine.startsWith("#!") ? firstLine.slice(2).trim() : null;
9791
11134
  }
@@ -9804,8 +11147,8 @@ function shebangToPythonCommand(shebang) {
9804
11147
  }
9805
11148
  async function findDevHermesAgentSource() {
9806
11149
  const candidates = [
9807
- path15.resolve(process.cwd(), "reference/hermes-agent"),
9808
- path15.resolve(process.cwd(), "../../reference/hermes-agent")
11150
+ path16.resolve(process.cwd(), "reference/hermes-agent"),
11151
+ path16.resolve(process.cwd(), "../../reference/hermes-agent")
9809
11152
  ];
9810
11153
  for (const candidate of candidates) {
9811
11154
  if (await isDirectory(candidate)) {
@@ -9815,7 +11158,7 @@ async function findDevHermesAgentSource() {
9815
11158
  return null;
9816
11159
  }
9817
11160
  async function isDirectory(candidate) {
9818
- return stat9(candidate).then((info) => info.isDirectory()).catch(() => false);
11161
+ return stat10(candidate).then((info) => info.isDirectory()).catch(() => false);
9819
11162
  }
9820
11163
  function compactProcessOutput(value) {
9821
11164
  const compact = value.trim().replace(/\s+/gu, " ").slice(0, 500);
@@ -9867,8 +11210,8 @@ function parseSseBlock(block) {
9867
11210
  if (decoded === null) {
9868
11211
  return null;
9869
11212
  }
9870
- const payload = toRecord8(decoded);
9871
- const payloadType = (readString10(payload, "type") ?? readString10(payload, "event") ?? readString10(payload, "object") ?? eventName) || "message";
11213
+ const payload = toRecord9(decoded);
11214
+ const payloadType = (readString11(payload, "type") ?? readString11(payload, "event") ?? readString11(payload, "object") ?? eventName) || "message";
9872
11215
  return { eventName, payloadType, payload, rawPayload: decoded ?? raw };
9873
11216
  }
9874
11217
  function decodeJson(value) {
@@ -9884,11 +11227,11 @@ function decodeJson(value) {
9884
11227
  return { type: "message.delta", delta: value };
9885
11228
  }
9886
11229
  }
9887
- function readString10(body, key) {
11230
+ function readString11(body, key) {
9888
11231
  const value = body[key];
9889
11232
  return typeof value === "string" && value.trim() ? value.trim() : null;
9890
11233
  }
9891
- function toRecord8(value) {
11234
+ function toRecord9(value) {
9892
11235
  return typeof value === "object" && value !== null ? value : {};
9893
11236
  }
9894
11237
 
@@ -9912,8 +11255,8 @@ function normalizeHermesStreamEvent(event) {
9912
11255
  };
9913
11256
  }
9914
11257
  if (event.eventName === "hermes.tool.progress") {
9915
- const toolName = readString11(event.payload, "tool") ?? readString11(event.payload, "name") ?? "tool";
9916
- const preview = readString11(event.payload, "label") ?? readString11(event.payload, "preview") ?? toolName;
11258
+ const toolName = readString12(event.payload, "tool") ?? readString12(event.payload, "name") ?? "tool";
11259
+ const preview = readString12(event.payload, "label") ?? readString12(event.payload, "preview") ?? toolName;
9917
11260
  return {
9918
11261
  ...event,
9919
11262
  payloadType: "tool.started",
@@ -9981,12 +11324,12 @@ function normalizeHermesResponseEvent(event) {
9981
11324
  }
9982
11325
  }
9983
11326
  function normalizeResponseOutputItemAdded(event) {
9984
- const item = toRecord9(event.payload.item);
9985
- if (readString11(item, "type") !== "function_call") {
11327
+ const item = toRecord10(event.payload.item);
11328
+ if (readString12(item, "type") !== "function_call") {
9986
11329
  return null;
9987
11330
  }
9988
- const toolName = readString11(item, "name") ?? "tool";
9989
- const argumentsValue = parseJsonValue(item.arguments) ?? item.arguments;
11331
+ const toolName = readString12(item, "name") ?? "tool";
11332
+ const argumentsValue = parseJsonValue2(item.arguments) ?? item.arguments;
9990
11333
  return {
9991
11334
  ...event,
9992
11335
  payloadType: "tool.started",
@@ -9995,60 +11338,60 @@ function normalizeResponseOutputItemAdded(event) {
9995
11338
  tool: toolName,
9996
11339
  tool_name: toolName,
9997
11340
  name: toolName,
9998
- tool_call_id: readString11(item, "call_id") ?? readString11(item, "id"),
11341
+ tool_call_id: readString12(item, "call_id") ?? readString12(item, "id"),
9999
11342
  arguments: argumentsValue,
10000
11343
  preview: toolName,
10001
- response_item_id: readString11(item, "id") ?? void 0
11344
+ response_item_id: readString12(item, "id") ?? void 0
10002
11345
  }
10003
11346
  };
10004
11347
  }
10005
11348
  function normalizeResponseOutputItemDone(event) {
10006
- const item = toRecord9(event.payload.item);
10007
- if (readString11(item, "type") !== "function_call_output") {
11349
+ const item = toRecord10(event.payload.item);
11350
+ if (readString12(item, "type") !== "function_call_output") {
10008
11351
  return null;
10009
11352
  }
10010
11353
  const output = readResponseItemOutput(item.output);
10011
- const parsedOutput = parseJsonValue(output);
11354
+ const parsedOutput = parseJsonValue2(output);
10012
11355
  return {
10013
11356
  ...event,
10014
11357
  payloadType: "tool.completed",
10015
11358
  payload: {
10016
11359
  type: "tool.completed",
10017
- tool_call_id: readString11(item, "call_id") ?? readString11(item, "id"),
10018
- status: readString11(item, "status") ?? "completed",
11360
+ tool_call_id: readString12(item, "call_id") ?? readString12(item, "id"),
11361
+ status: readString12(item, "status") ?? "completed",
10019
11362
  output,
10020
11363
  content: output,
10021
11364
  result: parsedOutput ?? output,
10022
- response_item_id: readString11(item, "id") ?? void 0
11365
+ response_item_id: readString12(item, "id") ?? void 0
10023
11366
  }
10024
11367
  };
10025
11368
  }
10026
11369
  function normalizeResponseCompleted(event) {
10027
- const response = toRecord9(event.payload.response);
11370
+ const response = toRecord10(event.payload.response);
10028
11371
  return {
10029
11372
  ...event,
10030
11373
  payloadType: "run.completed",
10031
11374
  payload: {
10032
11375
  type: "run.completed",
10033
- response_id: readString11(response, "id") ?? readString11(event.payload, "id"),
10034
- usage: toRecord9(response.usage),
11376
+ response_id: readString12(response, "id") ?? readString12(event.payload, "id"),
11377
+ usage: toRecord10(response.usage),
10035
11378
  response
10036
11379
  }
10037
11380
  };
10038
11381
  }
10039
11382
  function normalizeResponseFailed(event) {
10040
- const response = toRecord9(event.payload.response);
10041
- const error = toRecord9(response.error);
11383
+ const response = toRecord10(event.payload.response);
11384
+ const error = toRecord10(response.error);
10042
11385
  return {
10043
11386
  ...event,
10044
11387
  payloadType: "run.failed",
10045
11388
  payload: {
10046
11389
  type: "run.failed",
10047
- response_id: readString11(response, "id") ?? readString11(event.payload, "id"),
11390
+ response_id: readString12(response, "id") ?? readString12(event.payload, "id"),
10048
11391
  error: {
10049
- message: readString11(error, "message") ?? readString11(event.payload, "message") ?? "Hermes run failed"
11392
+ message: readString12(error, "message") ?? readString12(event.payload, "message") ?? "Hermes run failed"
10050
11393
  },
10051
- usage: toRecord9(response.usage),
11394
+ usage: toRecord10(response.usage),
10052
11395
  response
10053
11396
  }
10054
11397
  };
@@ -10072,8 +11415,8 @@ function readErrorMessage2(payload) {
10072
11415
  if (typeof payload.error === "string" && payload.error.trim()) {
10073
11416
  return payload.error.trim();
10074
11417
  }
10075
- const error = toRecord9(payload.error);
10076
- return readString11(error, "message") ?? readString11(payload, "message");
11418
+ const error = toRecord10(payload.error);
11419
+ return readString12(error, "message") ?? readString12(payload, "message");
10077
11420
  }
10078
11421
  function readDelta(payload) {
10079
11422
  return readText2(payload, "delta") ?? readText2(payload, "text") ?? readText2(payload, "content");
@@ -10108,20 +11451,21 @@ function isTopLevelErrorEvent(event) {
10108
11451
  }
10109
11452
  function readChatCompletionDelta(payload) {
10110
11453
  const choice = readFirstChoice(payload);
10111
- const delta = toRecord9(choice.delta);
11454
+ const delta = toRecord10(choice.delta);
10112
11455
  return readText2(delta, "content");
10113
11456
  }
10114
11457
  function readChatCompletionFinishReason(payload) {
10115
11458
  const choice = readFirstChoice(payload);
10116
- return readString11(choice, "finish_reason") ?? readString11(choice, "finishReason");
11459
+ return readString12(choice, "finish_reason") ?? readString12(choice, "finishReason");
10117
11460
  }
10118
11461
  function readChatCompletionUsage(payload) {
10119
- const usage = toRecord9(payload.usage);
11462
+ const usage = toRecord10(payload.usage);
10120
11463
  const input = readInteger2(usage, "prompt_tokens") ?? readInteger2(usage, "input_tokens");
10121
11464
  const output = readInteger2(usage, "completion_tokens") ?? readInteger2(usage, "output_tokens");
10122
11465
  const total = readInteger2(usage, "total_tokens");
10123
- const contextWindow = readInteger2(usage, "context_window") ?? readInteger2(usage, "context_max");
11466
+ const contextWindow = readInteger2(usage, "context_window") ?? readInteger2(usage, "context_max") ?? readInteger2(usage, "context_length");
10124
11467
  const explicitContextTokens = readInteger2(usage, "context_tokens") ?? readInteger2(usage, "context_used") ?? readInteger2(usage, "current_context_tokens") ?? readInteger2(usage, "last_prompt_tokens");
11468
+ const explicitUsagePercent = readInteger2(usage, "usage_percent") ?? readInteger2(usage, "context_percent");
10125
11469
  if (input === void 0 && output === void 0 && total === void 0) {
10126
11470
  return void 0;
10127
11471
  }
@@ -10132,7 +11476,7 @@ function readChatCompletionUsage(payload) {
10132
11476
  ...explicitContextTokens !== void 0 ? { context_tokens: explicitContextTokens } : {},
10133
11477
  ...contextWindow !== void 0 ? { context_window: contextWindow } : {},
10134
11478
  ...explicitContextTokens !== void 0 && contextWindow ? {
10135
- usage_percent: Math.min(
11479
+ usage_percent: explicitUsagePercent !== void 0 ? Math.min(100, explicitUsagePercent) : Math.min(
10136
11480
  100,
10137
11481
  Math.round(explicitContextTokens / contextWindow * 100)
10138
11482
  )
@@ -10144,7 +11488,7 @@ function readFirstChoice(payload) {
10144
11488
  if (!Array.isArray(choices)) {
10145
11489
  return {};
10146
11490
  }
10147
- return toRecord9(choices[0]);
11491
+ return toRecord10(choices[0]);
10148
11492
  }
10149
11493
  function readInteger2(payload, key) {
10150
11494
  const value = payload[key];
@@ -10157,7 +11501,7 @@ function readInteger2(payload, key) {
10157
11501
  }
10158
11502
  return void 0;
10159
11503
  }
10160
- function readString11(payload, key) {
11504
+ function readString12(payload, key) {
10161
11505
  const value = payload[key];
10162
11506
  return typeof value === "string" && value.trim() ? value.trim() : null;
10163
11507
  }
@@ -10172,10 +11516,10 @@ function readResponseItemOutput(value) {
10172
11516
  if (!Array.isArray(value)) {
10173
11517
  return stringifyJsonValue(value);
10174
11518
  }
10175
- const text = value.map(toRecord9).map((part) => readText2(part, "text") ?? readText2(part, "content") ?? "").join("");
11519
+ const text = value.map(toRecord10).map((part) => readText2(part, "text") ?? readText2(part, "content") ?? "").join("");
10176
11520
  return text || stringifyJsonValue(value);
10177
11521
  }
10178
- function parseJsonValue(value) {
11522
+ function parseJsonValue2(value) {
10179
11523
  if (typeof value !== "string" || !value.trim()) {
10180
11524
  return null;
10181
11525
  }
@@ -10198,7 +11542,7 @@ function stringifyJsonValue(value) {
10198
11542
  return String(value);
10199
11543
  }
10200
11544
  }
10201
- function toRecord9(value) {
11545
+ function toRecord10(value) {
10202
11546
  return typeof value === "object" && value !== null ? value : {};
10203
11547
  }
10204
11548
 
@@ -10279,10 +11623,20 @@ var ConversationRunLifecycle = class {
10279
11623
  });
10280
11624
  return void 0;
10281
11625
  });
11626
+ const instructions = buildRunInstructions(run, deliveryStagingDir);
11627
+ const estimatedUsage = estimateContextUsage({
11628
+ conversationHistory: conversationHistory.messages,
11629
+ currentInput: resolvedInput,
11630
+ instructions,
11631
+ contextWindow: run.context_window
11632
+ });
11633
+ if (estimatedUsage) {
11634
+ await this.updateRun(conversationId, runId, { usage: estimatedUsage });
11635
+ }
10282
11636
  const response = await streamHermesResponses(
10283
11637
  {
10284
11638
  input: resolvedInput,
10285
- instructions: buildRunInstructions(run, deliveryStagingDir),
11639
+ instructions,
10286
11640
  session_id: hermesSessionId,
10287
11641
  model: run.model,
10288
11642
  ...previousResponseId ? { previous_response_id: previousResponseId } : {},
@@ -10615,7 +11969,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10615
11969
  assistant.agent_events ?? [],
10616
11970
  agentEvent
10617
11971
  );
10618
- appendAgentEventBlock(assistant, agentEvent, event.created_at);
11972
+ appendAgentEventBlock2(assistant, agentEvent, event.created_at);
10619
11973
  assistant.updated_at = event.created_at;
10620
11974
  await this.deps.writeSnapshot(conversationId, snapshot);
10621
11975
  }
@@ -10684,8 +12038,8 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10684
12038
  }
10685
12039
  const textPart = assistant.parts.find((part) => part.type === "text");
10686
12040
  const currentText = textPart?.text ?? "";
10687
- const pendingDeliveryText = readString12(
10688
- toRecord10(assistant.hermes),
12041
+ const pendingDeliveryText = readString13(
12042
+ toRecord11(assistant.hermes),
10689
12043
  "pending_media_delivery_text"
10690
12044
  );
10691
12045
  const normalizedDelta = normalizeStreamingTextDelta(
@@ -10700,7 +12054,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10700
12054
  pendingDeliveryText ?? ""
10701
12055
  );
10702
12056
  const nextHermes = {
10703
- ...toRecord10(assistant.hermes),
12057
+ ...toRecord11(assistant.hermes),
10704
12058
  ...extracted.pendingText ? { pending_media_delivery_text: extracted.pendingText } : {}
10705
12059
  };
10706
12060
  if (!extracted.pendingText) {
@@ -10716,7 +12070,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10716
12070
  assistant.updated_at = (/* @__PURE__ */ new Date()).toISOString();
10717
12071
  assistant.raw = { format: "hermes-run-event", payload: event.rawPayload };
10718
12072
  if (extracted.visibleText) {
10719
- appendTextBlock(assistant, extracted.visibleText, assistant.updated_at);
12073
+ appendTextBlock2(assistant, extracted.visibleText, assistant.updated_at);
10720
12074
  }
10721
12075
  await this.deps.writeSnapshot(conversationId, snapshot);
10722
12076
  if (extracted.visibleText) {
@@ -10785,7 +12139,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10785
12139
  run.hermes_response_id = responseId;
10786
12140
  }
10787
12141
  if (usage) {
10788
- run.usage = usage;
12142
+ run.usage = mergeRunUsage(run.usage, usage);
10789
12143
  }
10790
12144
  if (assistant) {
10791
12145
  assistant.status = "completed";
@@ -10837,7 +12191,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10837
12191
  const visibleMessage = formatFailureMessage(message, run.error_detail);
10838
12192
  const usage = readUsage(source?.payload);
10839
12193
  if (usage) {
10840
- run.usage = usage;
12194
+ run.usage = mergeRunUsage(run.usage, usage);
10841
12195
  }
10842
12196
  const assistant = snapshot.messages.find(
10843
12197
  (item) => item.id === run.assistant_message_id
@@ -10856,6 +12210,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10856
12210
  }
10857
12211
  }
10858
12212
  await this.deps.writeSnapshot(conversationId, snapshot);
12213
+ const contextUsage = contextUsagePayload(run);
10859
12214
  await this.deps.appendEvent(conversationId, {
10860
12215
  type: "run.failed",
10861
12216
  message_id: assistant?.id,
@@ -10864,6 +12219,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10864
12219
  error: { message },
10865
12220
  ...run.error_detail ? { error_detail: run.error_detail } : {},
10866
12221
  run,
12222
+ ...contextUsage ? { context: contextUsage } : {},
10867
12223
  ...source ? { hermes: source.payload } : {}
10868
12224
  },
10869
12225
  ...source ? { raw: { format: "hermes-run-event", payload: source.rawPayload } } : {}
@@ -10967,7 +12323,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
10967
12323
  includeDisabled: true
10968
12324
  });
10969
12325
  return new Set(
10970
- jobs.map((job) => readString12(job, "id") ?? readString12(job, "job_id")).filter((id) => Boolean(id))
12326
+ jobs.map((job) => readString13(job, "id") ?? readString13(job, "job_id")).filter((id) => Boolean(id))
10971
12327
  );
10972
12328
  }
10973
12329
  async bindNewCronJobsCreatedByRun(input) {
@@ -11003,7 +12359,7 @@ function buildRunInstructions(run, deliveryStagingDir) {
11003
12359
  ].join("\n");
11004
12360
  }
11005
12361
  function appendMediaImportFailureNotice(message) {
11006
- const hermes = toRecord10(message.hermes);
12362
+ const hermes = toRecord11(message.hermes);
11007
12363
  if (hermes.media_import_failure_notice_appended === true) {
11008
12364
  return;
11009
12365
  }
@@ -11046,18 +12402,18 @@ function formatFilenameList(filenames) {
11046
12402
  return remaining > 0 ? `${preview.join("\u3001")} \u7B49 ${filenames.length} \u4E2A` : preview.join("\u3001");
11047
12403
  }
11048
12404
  async function readdirWithDirs(directory) {
11049
- return readdir6(directory, { withFileTypes: true }).catch((error) => {
11050
- if (isNodeError11(error, "ENOENT")) {
12405
+ return readdir7(directory, { withFileTypes: true }).catch((error) => {
12406
+ if (isNodeError12(error, "ENOENT")) {
11051
12407
  return [];
11052
12408
  }
11053
12409
  throw error;
11054
12410
  });
11055
12411
  }
11056
- function readString12(payload, key) {
12412
+ function readString13(payload, key) {
11057
12413
  const value = payload[key];
11058
12414
  return typeof value === "string" && value.trim() ? value.trim() : null;
11059
12415
  }
11060
- function toRecord10(value) {
12416
+ function toRecord11(value) {
11061
12417
  return typeof value === "object" && value !== null ? value : {};
11062
12418
  }
11063
12419
  function formatFailureMessage(message, detail) {
@@ -11073,17 +12429,17 @@ function isFileSearchCompletion(payloadType, payload) {
11073
12429
  if (payloadType !== "tool.completed") {
11074
12430
  return false;
11075
12431
  }
11076
- const tool = toRecord10(payload.tool);
11077
- const toolCall = toRecord10(payload.tool_call ?? payload.toolCall);
11078
- const fn = toRecord10(toolCall.function ?? payload.function);
12432
+ const tool = toRecord11(payload.tool);
12433
+ const toolCall = toRecord11(payload.tool_call ?? payload.toolCall);
12434
+ const fn = toRecord11(toolCall.function ?? payload.function);
11079
12435
  const candidates = [
11080
- readString12(payload, "tool_name"),
11081
- readString12(payload, "toolName"),
11082
- readString12(payload, "name"),
11083
- readString12(payload, "tool"),
11084
- readString12(tool, "name"),
11085
- readString12(toolCall, "name"),
11086
- readString12(fn, "name")
12436
+ readString13(payload, "tool_name"),
12437
+ readString13(payload, "toolName"),
12438
+ readString13(payload, "name"),
12439
+ readString13(payload, "tool"),
12440
+ readString13(tool, "name"),
12441
+ readString13(toolCall, "name"),
12442
+ readString13(fn, "name")
11087
12443
  ].filter((value) => Boolean(value)).map(normalizeToolName);
11088
12444
  return candidates.some(
11089
12445
  (name) => [
@@ -11126,7 +12482,7 @@ function isVoicePart(part) {
11126
12482
  function normalizeToolName(value) {
11127
12483
  return value.trim().toLowerCase().replace(/[\s-]+/gu, "_");
11128
12484
  }
11129
- function appendTextBlock(message, delta, updatedAt) {
12485
+ function appendTextBlock2(message, delta, updatedAt) {
11130
12486
  if (!delta) {
11131
12487
  return;
11132
12488
  }
@@ -11149,7 +12505,7 @@ function appendTextBlock(message, delta, updatedAt) {
11149
12505
  }
11150
12506
  message.blocks = blocks;
11151
12507
  }
11152
- function appendAgentEventBlock(message, event, updatedAt) {
12508
+ function appendAgentEventBlock2(message, event, updatedAt) {
11153
12509
  const blocks = [...message.blocks ?? []];
11154
12510
  const matchingIndex = blocks.findIndex((block) => {
11155
12511
  if (block.type !== "agent_events") {
@@ -11195,6 +12551,7 @@ function contextUsagePayload(run) {
11195
12551
  source: "unknown"
11196
12552
  };
11197
12553
  }
12554
+ const contextSource = usage.context_source ?? "explicit";
11198
12555
  return {
11199
12556
  input_tokens: contextTokens,
11200
12557
  output_tokens: usage?.output_tokens ?? 0,
@@ -11202,7 +12559,7 @@ function contextUsagePayload(run) {
11202
12559
  ...contextWindow ? { context_window: contextWindow } : {},
11203
12560
  used_tokens: contextTokens,
11204
12561
  ...contextWindow ? { window_tokens: contextWindow } : {},
11205
- source: "explicit",
12562
+ source: contextSource,
11206
12563
  ...usage?.usage_percent !== void 0 ? { usage_percent: usage.usage_percent } : contextWindow ? {
11207
12564
  usage_percent: Math.min(
11208
12565
  100,
@@ -11211,6 +12568,41 @@ function contextUsagePayload(run) {
11211
12568
  } : {}
11212
12569
  };
11213
12570
  }
12571
+ function mergeRunUsage(previous, next) {
12572
+ const nextContextWindow = next.context_window ?? previous?.context_window;
12573
+ const nextContextTokens = next.context_tokens ?? refineEstimatedContextTokens(
12574
+ previous,
12575
+ next.input_tokens,
12576
+ nextContextWindow
12577
+ );
12578
+ const nextContextSource = next.context_tokens !== void 0 ? next.context_source ?? "explicit" : nextContextTokens !== void 0 ? previous?.context_source : void 0;
12579
+ return {
12580
+ ...next,
12581
+ ...nextContextTokens !== void 0 ? { context_tokens: nextContextTokens } : {},
12582
+ ...nextContextWindow !== void 0 ? { context_window: nextContextWindow } : {},
12583
+ ...nextContextSource ? { context_source: nextContextSource } : {},
12584
+ ...nextContextTokens !== void 0 && nextContextWindow ? {
12585
+ usage_percent: next.usage_percent ?? previous?.usage_percent ?? Math.min(
12586
+ 100,
12587
+ Math.round(nextContextTokens / nextContextWindow * 100)
12588
+ )
12589
+ } : {}
12590
+ };
12591
+ }
12592
+ function refineEstimatedContextTokens(previous, inputTokens, contextWindow) {
12593
+ if (previous?.context_source !== "estimated") {
12594
+ return void 0;
12595
+ }
12596
+ const currentEstimate = previous.context_tokens;
12597
+ if (currentEstimate === void 0) {
12598
+ return void 0;
12599
+ }
12600
+ const upperBound = inputTokens > 0 ? contextWindow ? Math.min(inputTokens, contextWindow) : inputTokens : contextWindow;
12601
+ if (upperBound === void 0) {
12602
+ return currentEstimate;
12603
+ }
12604
+ return Math.min(currentEstimate, upperBound);
12605
+ }
11214
12606
  function findPreviousHermesResponseId(snapshot, run) {
11215
12607
  const currentProfile = normalizeRunProfileForCompare(run.profile);
11216
12608
  if (!currentProfile) {
@@ -11238,10 +12630,10 @@ function readResponseId(payload) {
11238
12630
  if (!payload) {
11239
12631
  return null;
11240
12632
  }
11241
- const response = toRecord10(payload.response);
11242
- return readString12(payload, "response_id") ?? readString12(response, "id");
12633
+ const response = toRecord11(payload.response);
12634
+ return readString13(payload, "response_id") ?? readString13(response, "id");
11243
12635
  }
11244
- function isNodeError11(error, code) {
12636
+ function isNodeError12(error, code) {
11245
12637
  if (typeof error !== "object" || error === null || !("code" in error)) {
11246
12638
  return false;
11247
12639
  }
@@ -11330,6 +12722,7 @@ var ConversationService = class {
11330
12722
  orchestration;
11331
12723
  queries;
11332
12724
  runLifecycle;
12725
+ hermesSessionSyncPromise = null;
11333
12726
  async withConversationLock(conversationId, task) {
11334
12727
  const previous = this.conversationLocks.get(conversationId) ?? Promise.resolve();
11335
12728
  let release;
@@ -11390,7 +12783,7 @@ var ConversationService = class {
11390
12783
  async createConversation(input = {}) {
11391
12784
  await this.store.ensureConversationsDir();
11392
12785
  const now = (/* @__PURE__ */ new Date()).toISOString();
11393
- const id = `conv_${randomUUID6().replaceAll("-", "")}`;
12786
+ const id = `conv_${randomUUID7().replaceAll("-", "")}`;
11394
12787
  const title = input.title?.trim() || DEFAULT_CONVERSATION_TITLE;
11395
12788
  const profile = await resolveConversationProfileTarget(
11396
12789
  this.paths,
@@ -11477,7 +12870,7 @@ var ConversationService = class {
11477
12870
  manifest.profile_name_snapshot ?? manifest.profile ?? input.profileName
11478
12871
  );
11479
12872
  const message = {
11480
- id: `msg_${randomUUID6().replaceAll("-", "")}`,
12873
+ id: `msg_${randomUUID7().replaceAll("-", "")}`,
11481
12874
  schema_version: 1,
11482
12875
  conversation_id: manifest.id,
11483
12876
  role: "assistant",
@@ -11521,6 +12914,29 @@ var ConversationService = class {
11521
12914
  async syncCronDeliveries() {
11522
12915
  await syncHermesLinkCronDeliveries(this.paths, this, this.logger);
11523
12916
  }
12917
+ async syncHermesSessions() {
12918
+ if (this.hermesSessionSyncPromise) {
12919
+ return this.hermesSessionSyncPromise;
12920
+ }
12921
+ const task = (async () => {
12922
+ const result = await syncHermesSessionsIntoConversations(
12923
+ this.paths,
12924
+ this.logger
12925
+ );
12926
+ if (result.imported_count > 0 || result.reprojected_count > 0) {
12927
+ await this.rebuildStatisticsIndex();
12928
+ }
12929
+ return result;
12930
+ })();
12931
+ this.hermesSessionSyncPromise = task;
12932
+ try {
12933
+ return await task;
12934
+ } finally {
12935
+ if (this.hermesSessionSyncPromise === task) {
12936
+ this.hermesSessionSyncPromise = null;
12937
+ }
12938
+ }
12939
+ }
11524
12940
  async deliverStagedFiles(stagingDir) {
11525
12941
  const target = resolveDeliveryStagingTarget(this.paths, stagingDir);
11526
12942
  return this.withConversationLock(target.conversationId, async () => {
@@ -12046,7 +13462,7 @@ function findApproval(snapshot, approvalId) {
12046
13462
  }
12047
13463
 
12048
13464
  // src/identity/identity.ts
12049
- import { generateKeyPairSync, randomUUID as randomUUID7, sign } from "crypto";
13465
+ import { generateKeyPairSync, randomUUID as randomUUID8, sign } from "crypto";
12050
13466
  import { mkdir as mkdir9, chmod } from "fs/promises";
12051
13467
  import { z } from "zod";
12052
13468
  var linkIdentitySchema = z.object({
@@ -12074,7 +13490,7 @@ async function ensureIdentity(paths = resolveRuntimePaths()) {
12074
13490
  const { publicKey, privateKey } = generateKeyPairSync("ed25519");
12075
13491
  const now = (/* @__PURE__ */ new Date()).toISOString();
12076
13492
  const identity = {
12077
- install_id: `install_${randomUUID7().replaceAll("-", "")}`,
13493
+ install_id: `install_${randomUUID8().replaceAll("-", "")}`,
12078
13494
  link_id: null,
12079
13495
  public_key_pem: publicKey.export({ type: "spki", format: "pem" }).toString(),
12080
13496
  private_key_pem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
@@ -12111,7 +13527,7 @@ function getIdentityStatus(identity) {
12111
13527
  }
12112
13528
 
12113
13529
  // src/security/devices.ts
12114
- import { randomBytes as randomBytes2, randomUUID as randomUUID8, timingSafeEqual, createHash as createHash4 } from "crypto";
13530
+ import { randomBytes as randomBytes2, randomUUID as randomUUID9, timingSafeEqual, createHash as createHash4 } from "crypto";
12115
13531
  var ACCESS_TOKEN_TTL_MS = 15 * 60 * 1e3;
12116
13532
  var REFRESH_TOKEN_TTL_MS = 90 * 24 * 60 * 60 * 1e3;
12117
13533
  var DEVICE_SEEN_WRITE_INTERVAL_MS = 60 * 60 * 1e3;
@@ -12129,7 +13545,7 @@ async function createDeviceSession(input, paths = resolveRuntimePaths()) {
12129
13545
  }
12130
13546
  }
12131
13547
  const device = {
12132
- id: `dev_${randomUUID8().replaceAll("-", "")}`,
13548
+ id: `dev_${randomUUID9().replaceAll("-", "")}`,
12133
13549
  label: normalizeDeviceLabel(input.label),
12134
13550
  platform: normalizeDevicePlatform(input.platform),
12135
13551
  model: normalizeDeviceModel(input.model),
@@ -12578,12 +13994,12 @@ async function readRawBody(request, maxBytes) {
12578
13994
  }
12579
13995
  return Buffer.concat(chunks);
12580
13996
  }
12581
- function readString13(body, key) {
13997
+ function readString14(body, key) {
12582
13998
  const value = body[key];
12583
13999
  return typeof value === "string" && value.trim() ? value.trim() : null;
12584
14000
  }
12585
14001
  function readOptionalProfileName(body) {
12586
- return readString13(body, "profile") ?? readString13(body, "profile_name") ?? readString13(body, "profileName") ?? void 0;
14002
+ return readString14(body, "profile") ?? readString14(body, "profile_name") ?? readString14(body, "profileName") ?? void 0;
12587
14003
  }
12588
14004
  function readStringArray(body, ...keys) {
12589
14005
  for (const key of keys) {
@@ -12621,7 +14037,7 @@ function readPositiveInteger2(value) {
12621
14037
  }
12622
14038
  return void 0;
12623
14039
  }
12624
- function readBoolean(value) {
14040
+ function readBoolean2(value) {
12625
14041
  if (typeof value === "boolean") {
12626
14042
  return value;
12627
14043
  }
@@ -12724,7 +14140,7 @@ function readMessageAttachments(value) {
12724
14140
  }
12725
14141
  const kind = readAttachmentString(record.kind);
12726
14142
  const type = readAttachmentString(record.type);
12727
- const isVoiceNote = readBoolean(record.is_voice_note) ?? readBoolean(record.isVoiceNote);
14143
+ const isVoiceNote = readBoolean2(record.is_voice_note) ?? readBoolean2(record.isVoiceNote);
12728
14144
  const durationMs = readPositiveInteger2(record.duration_ms) ?? readPositiveInteger2(record.durationMs);
12729
14145
  const waveform = readAttachmentWaveform2(
12730
14146
  record.waveform ?? record.waveform_samples ?? record.waveformSamples
@@ -12790,13 +14206,84 @@ function isLanHost(hostname) {
12790
14206
  }
12791
14207
 
12792
14208
  // src/http/sse.ts
14209
+ var DEFAULT_SSE_RETRY_MS = 1e3;
14210
+ var DEFAULT_SSE_HEARTBEAT_MS = 15e3;
14211
+ function beginSseStream(request, response, options = {}) {
14212
+ const retryMs = normalizeRetryMs(options.retryMs);
14213
+ const heartbeatMs = Math.max(1e3, options.heartbeatMs ?? DEFAULT_SSE_HEARTBEAT_MS);
14214
+ response.statusCode = 200;
14215
+ response.setHeader("content-type", "text/event-stream; charset=utf-8");
14216
+ response.setHeader("cache-control", "no-store");
14217
+ response.setHeader("connection", "keep-alive");
14218
+ response.flushHeaders();
14219
+ writeSseRetry(response, retryMs);
14220
+ writeSseComment(response, options.initialComment ?? "connected");
14221
+ let closed = false;
14222
+ let heartbeat = null;
14223
+ const cleanup = () => {
14224
+ if (closed) {
14225
+ return;
14226
+ }
14227
+ closed = true;
14228
+ if (heartbeat != null) {
14229
+ clearInterval(heartbeat);
14230
+ heartbeat = null;
14231
+ }
14232
+ request.off("close", cleanup);
14233
+ response.off("close", cleanup);
14234
+ options.onClose?.();
14235
+ if (!response.writableEnded && !response.destroyed) {
14236
+ response.end();
14237
+ }
14238
+ };
14239
+ heartbeat = setInterval(() => {
14240
+ if (response.writableEnded || response.destroyed) {
14241
+ cleanup();
14242
+ return;
14243
+ }
14244
+ writeSseComment(response);
14245
+ }, heartbeatMs);
14246
+ heartbeat.unref();
14247
+ request.once("close", cleanup);
14248
+ response.once("close", cleanup);
14249
+ return cleanup;
14250
+ }
12793
14251
  function writeSseEvent(response, event) {
12794
- response.write(`event: ${event.type}
14252
+ writeJsonSseEvent(response, {
14253
+ event: event.type,
14254
+ data: event,
14255
+ id: event.seq
14256
+ });
14257
+ }
14258
+ function writeJsonSseEvent(response, event) {
14259
+ if (event.retryMs != null) {
14260
+ response.write(`retry: ${normalizeRetryMs(event.retryMs)}
14261
+ `);
14262
+ }
14263
+ if (event.id != null && event.id !== "") {
14264
+ response.write(`id: ${event.id}
14265
+ `);
14266
+ }
14267
+ response.write(`event: ${event.event}
12795
14268
  `);
12796
- response.write(`data: ${JSON.stringify(event)}
14269
+ response.write(`data: ${JSON.stringify(event.data)}
12797
14270
 
12798
14271
  `);
12799
14272
  }
14273
+ function writeSseComment(response, comment = "keep-alive") {
14274
+ response.write(`: ${comment}
14275
+
14276
+ `);
14277
+ }
14278
+ function writeSseRetry(response, retryMs) {
14279
+ response.write(`retry: ${normalizeRetryMs(retryMs)}
14280
+
14281
+ `);
14282
+ }
14283
+ function normalizeRetryMs(retryMs) {
14284
+ const parsed = Number.isFinite(retryMs) ? Math.trunc(retryMs) : DEFAULT_SSE_RETRY_MS;
14285
+ return parsed >= 0 ? parsed : DEFAULT_SSE_RETRY_MS;
14286
+ }
12800
14287
 
12801
14288
  // src/http/routes/conversations.ts
12802
14289
  function registerConversationRoutes(router, options) {
@@ -12816,7 +14303,7 @@ function registerConversationRoutes(router, options) {
12816
14303
  ctx.body = {
12817
14304
  ok: true,
12818
14305
  conversation: await conversations.createConversation({
12819
- title: readString13(body, "title") ?? void 0,
14306
+ title: readString14(body, "title") ?? void 0,
12820
14307
  profileName: readOptionalProfileName(body)
12821
14308
  })
12822
14309
  };
@@ -12840,54 +14327,49 @@ function registerConversationRoutes(router, options) {
12840
14327
  const notificationOnly = mode === "notifications";
12841
14328
  ctx.respond = false;
12842
14329
  const response = ctx.res;
12843
- response.statusCode = 200;
12844
- response.setHeader("content-type", "text/event-stream; charset=utf-8");
12845
- response.setHeader("cache-control", "no-store");
12846
- response.setHeader("connection", "keep-alive");
12847
- const unsubscribe = conversations.subscribeAll((event) => {
14330
+ let unsubscribe = () => {
14331
+ };
14332
+ beginSseStream(ctx.req, response, {
14333
+ onClose: () => unsubscribe()
14334
+ });
14335
+ unsubscribe = conversations.subscribeAll((event) => {
12848
14336
  if (notificationOnly && !isConversationNotificationEvent(event)) {
12849
14337
  return;
12850
14338
  }
12851
14339
  writeSseEvent(response, event);
12852
14340
  });
12853
- const cleanup = () => {
12854
- unsubscribe();
12855
- response.end();
12856
- };
12857
- ctx.req.on("close", cleanup);
12858
14341
  });
12859
14342
  router.get("/api/v1/conversations/:conversationId/events", async (ctx) => {
12860
14343
  await authenticateRequest(ctx, paths);
12861
- const after = readInteger3(ctx.query.after) ?? 0;
14344
+ const after = resolveConversationEventCursor({
14345
+ queryAfter: ctx.query.after,
14346
+ lastEventIdHeader: ctx.req.headers["last-event-id"]
14347
+ });
12862
14348
  const history = await conversations.listEvents(
12863
14349
  ctx.params.conversationId,
12864
14350
  after
12865
14351
  );
12866
14352
  ctx.respond = false;
12867
14353
  const response = ctx.res;
12868
- response.statusCode = 200;
12869
- response.setHeader("content-type", "text/event-stream; charset=utf-8");
12870
- response.setHeader("cache-control", "no-store");
12871
- response.setHeader("connection", "keep-alive");
14354
+ let unsubscribe = () => {
14355
+ };
14356
+ beginSseStream(ctx.req, response, {
14357
+ onClose: () => unsubscribe()
14358
+ });
12872
14359
  for (const event of history) {
12873
14360
  writeSseEvent(response, event);
12874
14361
  }
12875
- const unsubscribe = conversations.subscribe(
14362
+ unsubscribe = conversations.subscribe(
12876
14363
  ctx.params.conversationId,
12877
14364
  (event) => {
12878
14365
  writeSseEvent(response, event);
12879
14366
  }
12880
14367
  );
12881
- const cleanup = () => {
12882
- unsubscribe();
12883
- response.end();
12884
- };
12885
- ctx.req.on("close", cleanup);
12886
14368
  });
12887
14369
  router.post("/api/v1/conversations/:conversationId/messages", async (ctx) => {
12888
14370
  await authenticateRequest(ctx, paths);
12889
14371
  const body = await readJsonBody(ctx.req);
12890
- const content = readString13(body, "content") ?? readString13(body, "text") ?? readString13(body, "input") ?? "";
14372
+ const content = readString14(body, "content") ?? readString14(body, "text") ?? readString14(body, "input") ?? "";
12891
14373
  const attachments = readMessageAttachments(body.attachments ?? body.blobs);
12892
14374
  if (!content && attachments.length === 0) {
12893
14375
  throw new LinkHttpError(
@@ -12903,7 +14385,7 @@ function registerConversationRoutes(router, options) {
12903
14385
  conversationId: ctx.params.conversationId,
12904
14386
  content,
12905
14387
  attachments,
12906
- clientMessageId: readString13(body, "client_message_id") ?? readString13(body, "clientMessageId") ?? void 0,
14388
+ clientMessageId: readString14(body, "client_message_id") ?? readString14(body, "clientMessageId") ?? void 0,
12907
14389
  idempotencyKey: readHeader(ctx, "idempotency-key") ?? void 0,
12908
14390
  profileName: readOptionalProfileName(body)
12909
14391
  })
@@ -12912,7 +14394,7 @@ function registerConversationRoutes(router, options) {
12912
14394
  router.patch("/api/v1/conversations/:conversationId/model", async (ctx) => {
12913
14395
  await authenticateRequest(ctx, paths);
12914
14396
  const body = await readJsonBody(ctx.req);
12915
- const modelId = readString13(body, "model_id") ?? readString13(body, "modelId") ?? readString13(body, "model");
14397
+ const modelId = readString14(body, "model_id") ?? readString14(body, "modelId") ?? readString14(body, "model");
12916
14398
  if (!modelId) {
12917
14399
  throw new LinkHttpError(400, "model_id_required", "model_id is required");
12918
14400
  }
@@ -12942,7 +14424,7 @@ function registerConversationRoutes(router, options) {
12942
14424
  router.patch("/api/v1/conversations/:conversationId/title", async (ctx) => {
12943
14425
  await authenticateRequest(ctx, paths);
12944
14426
  const body = await readJsonBody(ctx.req);
12945
- const title = readString13(body, "title") ?? readString13(body, "name") ?? readString13(body, "display_name");
14427
+ const title = readString14(body, "title") ?? readString14(body, "name") ?? readString14(body, "display_name");
12946
14428
  if (!title) {
12947
14429
  throw new LinkHttpError(400, "title_required", "title is required");
12948
14430
  }
@@ -13006,7 +14488,7 @@ function registerConversationRoutes(router, options) {
13006
14488
  async (ctx) => {
13007
14489
  await authenticateRequest(ctx, paths);
13008
14490
  const body = await readJsonBody(ctx.req);
13009
- const scope = readString13(body, "scope") ?? "always";
14491
+ const scope = readString14(body, "scope") ?? "always";
13010
14492
  ctx.body = {
13011
14493
  ok: true,
13012
14494
  ...await conversations.resolveApproval({
@@ -13085,6 +14567,19 @@ function registerConversationRoutes(router, options) {
13085
14567
  }
13086
14568
  );
13087
14569
  }
14570
+ function resolveConversationEventCursor(input) {
14571
+ const queryAfter = readInteger3(input.queryAfter) ?? 0;
14572
+ const headerAfter = readNonNegativeIntegerHeader(input.lastEventIdHeader) ?? 0;
14573
+ return Math.max(queryAfter, headerAfter);
14574
+ }
14575
+ function readNonNegativeIntegerHeader(value) {
14576
+ const raw = Array.isArray(value) ? value[0] : value;
14577
+ if (!raw) {
14578
+ return null;
14579
+ }
14580
+ const parsed = Number.parseInt(raw, 10);
14581
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null;
14582
+ }
13088
14583
  function contentDispositionInline(filename) {
13089
14584
  const fallback = asciiFilenameFallback(filename);
13090
14585
  return `inline; filename="${fallback}"; filename*=UTF-8''${encodeRfc5987Value(filename)}`;
@@ -13166,26 +14661,26 @@ function createHttpErrorMiddleware(logger) {
13166
14661
  }
13167
14662
 
13168
14663
  // src/hermes/profiles.ts
13169
- import { mkdir as mkdir10, readdir as readdir7, readFile as readFile11, rename as rename4, rm as rm6, stat as stat10 } from "fs/promises";
13170
- import os4 from "os";
13171
- import path16 from "path";
14664
+ import { mkdir as mkdir10, readdir as readdir8, readFile as readFile12, rename as rename4, rm as rm6, stat as stat11 } from "fs/promises";
14665
+ import os5 from "os";
14666
+ import path17 from "path";
13172
14667
  import YAML2 from "yaml";
13173
14668
  var DEFAULT_PROFILE = "default";
13174
- var PROFILE_NAME_PATTERN3 = /^[a-zA-Z0-9._-]{1,64}$/;
14669
+ var PROFILE_NAME_PATTERN4 = /^[a-zA-Z0-9._-]{1,64}$/;
13175
14670
  async function listHermesProfiles(paths = resolveRuntimePaths()) {
13176
14671
  const profiles = /* @__PURE__ */ new Map();
13177
14672
  profiles.set(DEFAULT_PROFILE, await profileInfo(DEFAULT_PROFILE, paths));
13178
- const profilesDir = path16.join(os4.homedir(), ".hermes", "profiles");
13179
- const entries = await readdir7(profilesDir, { withFileTypes: true }).catch(
14673
+ const profilesDir = path17.join(os5.homedir(), ".hermes", "profiles");
14674
+ const entries = await readdir8(profilesDir, { withFileTypes: true }).catch(
13180
14675
  (error) => {
13181
- if (isNodeError12(error, "ENOENT")) {
14676
+ if (isNodeError13(error, "ENOENT")) {
13182
14677
  return [];
13183
14678
  }
13184
14679
  throw error;
13185
14680
  }
13186
14681
  );
13187
14682
  for (const entry of entries) {
13188
- if (entry.isDirectory() && PROFILE_NAME_PATTERN3.test(entry.name)) {
14683
+ if (entry.isDirectory() && PROFILE_NAME_PATTERN4.test(entry.name)) {
13189
14684
  profiles.set(entry.name, await profileInfo(entry.name, paths));
13190
14685
  }
13191
14686
  }
@@ -13202,8 +14697,8 @@ async function listHermesProfiles(paths = resolveRuntimePaths()) {
13202
14697
  async function getHermesProfileStatus(name, paths = resolveRuntimePaths()) {
13203
14698
  assertProfileName(name);
13204
14699
  const profile = await profileInfo(name, paths);
13205
- const exists = await stat10(profile.path).then((value) => value.isDirectory()).catch((error) => {
13206
- if (isNodeError12(error, "ENOENT")) {
14700
+ const exists = await stat11(profile.path).then((value) => value.isDirectory()).catch((error) => {
14701
+ if (isNodeError13(error, "ENOENT")) {
13207
14702
  return false;
13208
14703
  }
13209
14704
  throw error;
@@ -13243,8 +14738,8 @@ async function updateHermesProfileMetadata(name, metadata, paths = resolveRuntim
13243
14738
  async function deleteHermesProfile(name, paths = resolveRuntimePaths()) {
13244
14739
  assertMutableProfile(name);
13245
14740
  const profile = await profileInfo(name, paths);
13246
- const exists = await stat10(profile.path).then((value) => value.isDirectory()).catch((error) => {
13247
- if (isNodeError12(error, "ENOENT")) {
14741
+ const exists = await stat11(profile.path).then((value) => value.isDirectory()).catch((error) => {
14742
+ if (isNodeError13(error, "ENOENT")) {
13248
14743
  return false;
13249
14744
  }
13250
14745
  throw error;
@@ -13270,7 +14765,7 @@ async function readHermesProfileCapabilities(name) {
13270
14765
  return {
13271
14766
  defaultModel: listedModels?.defaultModel ?? null,
13272
14767
  modelCount: listedModels?.models.length ?? 0,
13273
- skillCount: await countSkills(path16.join(profileDir, "skills")).catch(
14768
+ skillCount: await countSkills(path17.join(profileDir, "skills")).catch(
13274
14769
  () => 0
13275
14770
  ),
13276
14771
  toolCount: await countConfiguredTools(name).catch(() => 0)
@@ -13308,17 +14803,17 @@ function assertMutableProfile(name) {
13308
14803
  }
13309
14804
  }
13310
14805
  function assertProfileName(name) {
13311
- if (!PROFILE_NAME_PATTERN3.test(name)) {
14806
+ if (!PROFILE_NAME_PATTERN4.test(name)) {
13312
14807
  throw new LinkHttpError(400, "invalid_profile_name", "invalid profile name");
13313
14808
  }
13314
14809
  }
13315
- function isNodeError12(error, code) {
14810
+ function isNodeError13(error, code) {
13316
14811
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
13317
14812
  }
13318
14813
  async function countSkills(root) {
13319
- const entries = await readdir7(root, { withFileTypes: true }).catch(
14814
+ const entries = await readdir8(root, { withFileTypes: true }).catch(
13320
14815
  (error) => {
13321
- if (isNodeError12(error, "ENOENT")) {
14816
+ if (isNodeError13(error, "ENOENT")) {
13322
14817
  return [];
13323
14818
  }
13324
14819
  throw error;
@@ -13326,7 +14821,7 @@ async function countSkills(root) {
13326
14821
  );
13327
14822
  let count = 0;
13328
14823
  for (const entry of entries) {
13329
- const entryPath = path16.join(root, entry.name);
14824
+ const entryPath = path17.join(root, entry.name);
13330
14825
  if (entry.name === ".git" || entry.name === ".hub") {
13331
14826
  continue;
13332
14827
  }
@@ -13341,11 +14836,11 @@ async function countSkills(root) {
13341
14836
  return count;
13342
14837
  }
13343
14838
  async function countConfiguredTools(profileName) {
13344
- const raw = await readFile11(
14839
+ const raw = await readFile12(
13345
14840
  resolveHermesConfigPath(profileName),
13346
14841
  "utf8"
13347
14842
  ).catch((error) => {
13348
- if (isNodeError12(error, "ENOENT")) {
14843
+ if (isNodeError13(error, "ENOENT")) {
13349
14844
  return "";
13350
14845
  }
13351
14846
  throw error;
@@ -13353,14 +14848,14 @@ async function countConfiguredTools(profileName) {
13353
14848
  if (!raw.trim()) {
13354
14849
  return 0;
13355
14850
  }
13356
- const config = toRecord11(YAML2.parse(raw));
14851
+ const config = toRecord12(YAML2.parse(raw));
13357
14852
  const toolsets = /* @__PURE__ */ new Set();
13358
14853
  collectToolsetValues(config.toolsets, toolsets);
13359
- const platformToolsets = toRecord11(config.platform_toolsets);
14854
+ const platformToolsets = toRecord12(config.platform_toolsets);
13360
14855
  for (const value of Object.values(platformToolsets)) {
13361
14856
  collectToolsetValues(value, toolsets);
13362
14857
  }
13363
- const mcpServers = Object.keys(toRecord11(config.mcp_servers)).length;
14858
+ const mcpServers = Object.keys(toRecord12(config.mcp_servers)).length;
13364
14859
  return toolsets.size + mcpServers;
13365
14860
  }
13366
14861
  function collectToolsetValues(value, target) {
@@ -13374,7 +14869,7 @@ function collectToolsetValues(value, target) {
13374
14869
  target.add(value.trim());
13375
14870
  }
13376
14871
  }
13377
- function toRecord11(value) {
14872
+ function toRecord12(value) {
13378
14873
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
13379
14874
  }
13380
14875
 
@@ -13571,7 +15066,7 @@ function toHermesCronJobInput(input) {
13571
15066
  };
13572
15067
  }
13573
15068
  async function bindAndDecorateCronJobForHermesLink(input) {
13574
- const jobId = readString13(input.job, "id") ?? readString13(input.job, "job_id");
15069
+ const jobId = readString14(input.job, "id") ?? readString14(input.job, "job_id");
13575
15070
  if (!jobId) {
13576
15071
  return input.job;
13577
15072
  }
@@ -13588,9 +15083,9 @@ async function bindAndDecorateCronJobForHermesLink(input) {
13588
15083
  }
13589
15084
  function readCronJobCreateInput(body) {
13590
15085
  const input = {};
13591
- const name = readString13(body, "name") ?? readString13(body, "title");
13592
- const prompt = readString13(body, "prompt") ?? readString13(body, "description") ?? readString13(body, "task");
13593
- const schedule = readString13(body, "schedule");
15086
+ const name = readString14(body, "name") ?? readString14(body, "title");
15087
+ const prompt = readString14(body, "prompt") ?? readString14(body, "description") ?? readString14(body, "task");
15088
+ const schedule = readString14(body, "schedule");
13594
15089
  if (!name) {
13595
15090
  throw new LinkHttpError(400, "cron_job_name_required", "name is required");
13596
15091
  }
@@ -13611,7 +15106,7 @@ function readCronJobCreateInput(body) {
13611
15106
  input.name = name;
13612
15107
  input.prompt = prompt;
13613
15108
  input.schedule = schedule;
13614
- input.deliver = readString13(body, "deliver") ?? HERMES_LINK_CRON_DELIVER;
15109
+ input.deliver = readString14(body, "deliver") ?? HERMES_LINK_CRON_DELIVER;
13615
15110
  const skills = readOptionalCronSkills(body);
13616
15111
  if (skills) {
13617
15112
  input.skills = skills;
@@ -13669,7 +15164,7 @@ function readCronJobUpdateInput(body) {
13669
15164
  if (repeat !== void 0) {
13670
15165
  input.repeat = repeat;
13671
15166
  }
13672
- const enabled = readBoolean(body.enabled);
15167
+ const enabled = readBoolean2(body.enabled);
13673
15168
  if (enabled !== void 0) {
13674
15169
  input.enabled = enabled;
13675
15170
  }
@@ -13839,7 +15334,7 @@ function registerModelConfigRoutes(router, options) {
13839
15334
  router.delete("/api/v1/model-configs", async (ctx) => {
13840
15335
  await authenticateRequest(ctx, paths);
13841
15336
  const body = await readJsonBody(ctx.req);
13842
- const modelId = readString13(body, "model_id") ?? readString13(body, "modelId");
15337
+ const modelId = readString14(body, "model_id") ?? readString14(body, "modelId");
13843
15338
  if (!modelId) {
13844
15339
  throw new LinkHttpError(400, "model_id_required", "model_id is required");
13845
15340
  }
@@ -13894,7 +15389,7 @@ function registerModelConfigRoutes(router, options) {
13894
15389
  await authenticateRequest(ctx, paths);
13895
15390
  await getHermesProfileStatus(ctx.params.name, paths);
13896
15391
  const body = await readJsonBody(ctx.req);
13897
- const modelId = readString13(body, "model_id") ?? readString13(body, "modelId");
15392
+ const modelId = readString14(body, "model_id") ?? readString14(body, "modelId");
13898
15393
  if (!modelId) {
13899
15394
  throw new LinkHttpError(400, "model_id_required", "model_id is required");
13900
15395
  }
@@ -13911,9 +15406,9 @@ function registerModelConfigRoutes(router, options) {
13911
15406
  });
13912
15407
  }
13913
15408
  function readModelConfigInput(body) {
13914
- const id = readString13(body, "id") ?? readString13(body, "model_id") ?? readString13(body, "modelId");
13915
- const provider = readString13(body, "provider") ?? readString13(body, "provider_key") ?? readString13(body, "providerKey");
13916
- const baseUrl = readString13(body, "base_url") ?? readString13(body, "baseUrl");
15409
+ const id = readString14(body, "id") ?? readString14(body, "model_id") ?? readString14(body, "modelId");
15410
+ const provider = readString14(body, "provider") ?? readString14(body, "provider_key") ?? readString14(body, "providerKey");
15411
+ const baseUrl = readString14(body, "base_url") ?? readString14(body, "baseUrl");
13917
15412
  if (!id || !provider || !baseUrl) {
13918
15413
  throw new LinkHttpError(
13919
15414
  400,
@@ -13923,28 +15418,28 @@ function readModelConfigInput(body) {
13923
15418
  }
13924
15419
  return {
13925
15420
  id,
13926
- originalModelId: readString13(body, "original_model_id") ?? readString13(body, "originalModelId") ?? readString13(body, "original_id") ?? void 0,
15421
+ originalModelId: readString14(body, "original_model_id") ?? readString14(body, "originalModelId") ?? readString14(body, "original_id") ?? void 0,
13927
15422
  provider,
13928
- providerName: readString13(body, "provider_name") ?? readString13(body, "providerName") ?? void 0,
15423
+ providerName: readString14(body, "provider_name") ?? readString14(body, "providerName") ?? void 0,
13929
15424
  baseUrl,
13930
- apiKey: readString13(body, "api_key") ?? readString13(body, "apiKey") ?? void 0,
13931
- apiMode: readString13(body, "api_mode") ?? readString13(body, "apiMode") ?? void 0,
15425
+ apiKey: readString14(body, "api_key") ?? readString14(body, "apiKey") ?? void 0,
15426
+ apiMode: readString14(body, "api_mode") ?? readString14(body, "apiMode") ?? void 0,
13932
15427
  contextLength: readPositiveInteger2(
13933
15428
  body.context_length ?? body.contextLength
13934
15429
  ),
13935
- keyEnv: readString13(body, "key_env") ?? readString13(body, "keyEnv") ?? void 0,
13936
- setDefault: readBoolean(body.set_default ?? body.setDefault),
13937
- reasoningEffort: readString13(body, "reasoning_effort") ?? readString13(body, "reasoningEffort") ?? void 0
15430
+ keyEnv: readString14(body, "key_env") ?? readString14(body, "keyEnv") ?? void 0,
15431
+ setDefault: readBoolean2(body.set_default ?? body.setDefault),
15432
+ reasoningEffort: readString14(body, "reasoning_effort") ?? readString14(body, "reasoningEffort") ?? void 0
13938
15433
  };
13939
15434
  }
13940
15435
  function readModelDefaultsInput(body) {
13941
15436
  return {
13942
- taskModelId: readString13(body, "task_model_id") ?? readString13(body, "taskModelId") ?? readString13(body, "default_model_id") ?? readString13(body, "defaultModelId") ?? void 0,
13943
- compressionModelId: readString13(body, "compression_model_id") ?? readString13(body, "compressionModelId") ?? void 0
15437
+ taskModelId: readString14(body, "task_model_id") ?? readString14(body, "taskModelId") ?? readString14(body, "default_model_id") ?? readString14(body, "defaultModelId") ?? void 0,
15438
+ compressionModelId: readString14(body, "compression_model_id") ?? readString14(body, "compressionModelId") ?? void 0
13944
15439
  };
13945
15440
  }
13946
15441
  function shouldReloadGatewayAfterModelConfigChange(body) {
13947
- const explicit = readBoolean(body.reload_gateway ?? body.reloadGateway) ?? (readBoolean(body.skip_gateway_reload ?? body.skipGatewayReload) === true ? false : void 0);
15442
+ const explicit = readBoolean2(body.reload_gateway ?? body.reloadGateway) ?? (readBoolean2(body.skip_gateway_reload ?? body.skipGatewayReload) === true ? false : void 0);
13948
15443
  return explicit ?? true;
13949
15444
  }
13950
15445
  function markModelConfigAppliedWithoutGatewayReload(result) {
@@ -14025,19 +15520,19 @@ import {
14025
15520
  copyFile as copyFile2,
14026
15521
  cp,
14027
15522
  mkdir as mkdir11,
14028
- readFile as readFile12,
15523
+ readFile as readFile13,
14029
15524
  rm as rm7,
14030
- stat as stat11,
15525
+ stat as stat12,
14031
15526
  writeFile as writeFile4,
14032
15527
  rename as rename5
14033
15528
  } from "fs/promises";
14034
- import path17 from "path";
15529
+ import path18 from "path";
14035
15530
  import YAML3 from "yaml";
14036
15531
  var PROFILE_CREATE_LOG_FILE = "profile-create.log";
14037
15532
  var PROFILE_CREATE_LOG_MAX_FILES = 3;
14038
15533
  var MAX_PROFILE_CREATE_LOG_LINES = 260;
14039
15534
  var MAX_OUTPUT_LINE_LENGTH = 1200;
14040
- var PROFILE_NAME_PATTERN4 = /^[a-z0-9][a-z0-9_-]{0,63}$/u;
15535
+ var PROFILE_NAME_PATTERN5 = /^[a-z0-9][a-z0-9_-]{0,63}$/u;
14041
15536
  var ALL_COPY_SCOPES = [
14042
15537
  "models",
14043
15538
  "skills",
@@ -14289,7 +15784,7 @@ function normalizeOptionalProfileName(value) {
14289
15784
  if (!trimmed) {
14290
15785
  return null;
14291
15786
  }
14292
- if (!PROFILE_NAME_PATTERN4.test(trimmed)) {
15787
+ if (!PROFILE_NAME_PATTERN5.test(trimmed)) {
14293
15788
  throw new LinkHttpError(
14294
15789
  400,
14295
15790
  "invalid_profile_name",
@@ -14421,7 +15916,7 @@ function copyModelConfig(source, target) {
14421
15916
  copied[key] = cloneJson(source[key]);
14422
15917
  }
14423
15918
  }
14424
- const sourceAuxiliary = toRecord12(source.auxiliary);
15919
+ const sourceAuxiliary = toRecord13(source.auxiliary);
14425
15920
  if (Object.prototype.hasOwnProperty.call(sourceAuxiliary, "compression")) {
14426
15921
  const targetAuxiliary = ensureRecord2(target, "auxiliary");
14427
15922
  targetAuxiliary.compression = cloneJson(sourceAuxiliary.compression);
@@ -14430,12 +15925,12 @@ function copyModelConfig(source, target) {
14430
15925
  return copied;
14431
15926
  }
14432
15927
  function copyToolPermissionsConfig(source, target) {
14433
- const sourcePlatformToolsets = toRecord12(source.platform_toolsets);
15928
+ const sourcePlatformToolsets = toRecord13(source.platform_toolsets);
14434
15929
  if (Object.prototype.hasOwnProperty.call(sourcePlatformToolsets, "api_server")) {
14435
15930
  const targetPlatformToolsets = ensureRecord2(target, "platform_toolsets");
14436
15931
  targetPlatformToolsets.api_server = cloneJson(sourcePlatformToolsets.api_server);
14437
15932
  }
14438
- const sourceStt = toRecord12(source.stt);
15933
+ const sourceStt = toRecord13(source.stt);
14439
15934
  if (Object.prototype.hasOwnProperty.call(sourceStt, "enabled")) {
14440
15935
  const targetStt = ensureRecord2(target, "stt");
14441
15936
  targetStt.enabled = cloneJson(sourceStt.enabled);
@@ -14482,9 +15977,9 @@ function collectEnvKeys(value, keys = /* @__PURE__ */ new Set()) {
14482
15977
  return keys;
14483
15978
  }
14484
15979
  async function writeEnvValues(profileName, values) {
14485
- const envPath = path17.join(resolveHermesProfileDir(profileName), ".env");
14486
- const existingRaw = await readFile12(envPath, "utf8").catch((error) => {
14487
- if (isNodeError13(error, "ENOENT")) {
15980
+ const envPath = path18.join(resolveHermesProfileDir(profileName), ".env");
15981
+ const existingRaw = await readFile13(envPath, "utf8").catch((error) => {
15982
+ if (isNodeError14(error, "ENOENT")) {
14488
15983
  return "";
14489
15984
  }
14490
15985
  throw error;
@@ -14509,7 +16004,7 @@ async function writeEnvValues(profileName, values) {
14509
16004
  nextLines.push(`${key}=${formatEnvValue2(value)}`);
14510
16005
  }
14511
16006
  const nextRaw = nextLines.join("\n").replace(/\n*$/u, "\n");
14512
- await mkdir11(path17.dirname(envPath), { recursive: true, mode: 448 });
16007
+ await mkdir11(path18.dirname(envPath), { recursive: true, mode: 448 });
14513
16008
  if (existingRaw) {
14514
16009
  await copyFile2(envPath, `${envPath}.bak.${Date.now()}`);
14515
16010
  }
@@ -14518,8 +16013,8 @@ async function writeEnvValues(profileName, values) {
14518
16013
  await rename5(tempPath, envPath);
14519
16014
  }
14520
16015
  async function copySkills(sourceProfile, targetProfile) {
14521
- const sourceSkills = path17.join(resolveHermesProfileDir(sourceProfile), "skills");
14522
- const targetSkills = path17.join(resolveHermesProfileDir(targetProfile), "skills");
16016
+ const sourceSkills = path18.join(resolveHermesProfileDir(sourceProfile), "skills");
16017
+ const targetSkills = path18.join(resolveHermesProfileDir(targetProfile), "skills");
14523
16018
  if (!await pathExists(sourceSkills)) {
14524
16019
  return;
14525
16020
  }
@@ -14538,21 +16033,21 @@ function copyProperty(source, target, key) {
14538
16033
  }
14539
16034
  }
14540
16035
  async function readYamlConfig(configPath) {
14541
- const existingRaw = await readFile12(configPath, "utf8").catch(
16036
+ const existingRaw = await readFile13(configPath, "utf8").catch(
14542
16037
  (error) => {
14543
- if (isNodeError13(error, "ENOENT")) {
16038
+ if (isNodeError14(error, "ENOENT")) {
14544
16039
  return null;
14545
16040
  }
14546
16041
  throw error;
14547
16042
  }
14548
16043
  );
14549
16044
  return {
14550
- config: toRecord12(existingRaw ? YAML3.parse(existingRaw) : {}),
16045
+ config: toRecord13(existingRaw ? YAML3.parse(existingRaw) : {}),
14551
16046
  existingRaw
14552
16047
  };
14553
16048
  }
14554
16049
  async function writeYamlConfig(configPath, input) {
14555
- await mkdir11(path17.dirname(configPath), { recursive: true, mode: 448 });
16050
+ await mkdir11(path18.dirname(configPath), { recursive: true, mode: 448 });
14556
16051
  if (input.existingRaw) {
14557
16052
  await copyFile2(configPath, `${configPath}.bak.${Date.now()}`);
14558
16053
  }
@@ -14600,7 +16095,7 @@ async function writeProfileCreationState(paths, state) {
14600
16095
  await writeJsonFile(profileCreationStatePath(paths), state);
14601
16096
  }
14602
16097
  async function readProfileCreationLogLines(paths) {
14603
- const raw = await readFile12(profileCreationLogPath(paths), "utf8").catch(() => "");
16098
+ const raw = await readFile13(profileCreationLogPath(paths), "utf8").catch(() => "");
14604
16099
  if (!raw.trim()) {
14605
16100
  return [];
14606
16101
  }
@@ -14609,10 +16104,10 @@ async function readProfileCreationLogLines(paths) {
14609
16104
  );
14610
16105
  }
14611
16106
  function profileCreationStatePath(paths) {
14612
- return path17.join(paths.runDir, "profile-create-state.json");
16107
+ return path18.join(paths.runDir, "profile-create-state.json");
14613
16108
  }
14614
16109
  function profileCreationLogPath(paths) {
14615
- return path17.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
16110
+ return path18.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
14616
16111
  }
14617
16112
  async function clearProfileCreationLogFiles(paths) {
14618
16113
  const primary = profileCreationLogPath(paths);
@@ -14625,8 +16120,8 @@ async function clearProfileCreationLogFiles(paths) {
14625
16120
  ]);
14626
16121
  }
14627
16122
  async function pathExists(targetPath) {
14628
- return await stat11(targetPath).then(() => true).catch((error) => {
14629
- if (isNodeError13(error, "ENOENT")) {
16123
+ return await stat12(targetPath).then(() => true).catch((error) => {
16124
+ if (isNodeError14(error, "ENOENT")) {
14630
16125
  return false;
14631
16126
  }
14632
16127
  throw error;
@@ -14658,7 +16153,7 @@ function ensureRecord2(target, key) {
14658
16153
  target[key] = next;
14659
16154
  return next;
14660
16155
  }
14661
- function toRecord12(value) {
16156
+ function toRecord13(value) {
14662
16157
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
14663
16158
  }
14664
16159
  function cloneJson(value) {
@@ -14673,7 +16168,7 @@ function formatEnvValue2(value) {
14673
16168
  function escapeRegExp2(value) {
14674
16169
  return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
14675
16170
  }
14676
- function isNodeError13(error, code) {
16171
+ function isNodeError14(error, code) {
14677
16172
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
14678
16173
  }
14679
16174
 
@@ -14758,16 +16253,16 @@ function readProfilePermissionsInput(body) {
14758
16253
  const approvals = readOptionalObject(body, "approvals");
14759
16254
  if (approvals) {
14760
16255
  input.approvals = {
14761
- mode: readString13(approvals, "mode") ?? readString13(approvals, "approval_mode") ?? readString13(approvals, "approvalMode") ?? void 0,
16256
+ mode: readString14(approvals, "mode") ?? readString14(approvals, "approval_mode") ?? readString14(approvals, "approvalMode") ?? void 0,
14762
16257
  timeout: readPositiveInteger2(approvals.timeout),
14763
- cronMode: readString13(approvals, "cron_mode") ?? readString13(approvals, "cronMode") ?? void 0
16258
+ cronMode: readString14(approvals, "cron_mode") ?? readString14(approvals, "cronMode") ?? void 0
14764
16259
  };
14765
16260
  }
14766
16261
  const terminal = readOptionalObject(body, "terminal");
14767
16262
  if (terminal) {
14768
16263
  input.terminal = {
14769
- backend: readString13(terminal, "backend") ?? void 0,
14770
- cwd: readString13(terminal, "cwd") ?? void 0,
16264
+ backend: readString14(terminal, "backend") ?? void 0,
16265
+ cwd: readString14(terminal, "cwd") ?? void 0,
14771
16266
  containerCpu: readPositiveInteger2(
14772
16267
  terminal.container_cpu ?? terminal.containerCpu
14773
16268
  ),
@@ -14777,7 +16272,7 @@ function readProfilePermissionsInput(body) {
14777
16272
  containerDisk: readPositiveInteger2(
14778
16273
  terminal.container_disk ?? terminal.containerDisk
14779
16274
  ),
14780
- containerPersistent: readBoolean(
16275
+ containerPersistent: readBoolean2(
14781
16276
  terminal.container_persistent ?? terminal.containerPersistent
14782
16277
  )
14783
16278
  };
@@ -14789,7 +16284,7 @@ function readProfilePermissionsInput(body) {
14789
16284
  toolsets.enabled_toolsets ?? toolsets.enabledToolsets ?? toolsets.enabled,
14790
16285
  "toolsets.enabled"
14791
16286
  ) ?? void 0,
14792
- mcpEnabled: readBoolean(toolsets.mcp_enabled ?? toolsets.mcpEnabled)
16287
+ mcpEnabled: readBoolean2(toolsets.mcp_enabled ?? toolsets.mcpEnabled)
14793
16288
  };
14794
16289
  }
14795
16290
  if (Object.keys(input).length === 0) {
@@ -14884,13 +16379,13 @@ import {
14884
16379
  access as access3,
14885
16380
  copyFile as copyFile3,
14886
16381
  mkdir as mkdir12,
14887
- readdir as readdir8,
14888
- readFile as readFile13,
16382
+ readdir as readdir9,
16383
+ readFile as readFile14,
14889
16384
  rename as rename6,
14890
- stat as stat12,
16385
+ stat as stat13,
14891
16386
  writeFile as writeFile5
14892
16387
  } from "fs/promises";
14893
- import path18 from "path";
16388
+ import path19 from "path";
14894
16389
  import YAML4 from "yaml";
14895
16390
  var ENTRY_DELIMITER = "\n\xA7\n";
14896
16391
  var DEFAULT_MEMORY_LIMIT = 2200;
@@ -15107,7 +16602,7 @@ async function saveProviderSettings(profileName, provider, patch) {
15107
16602
  if (provider === "hindsight") {
15108
16603
  await patchJsonProviderConfig(
15109
16604
  profileName,
15110
- path18.join("hindsight", "config.json"),
16605
+ path19.join("hindsight", "config.json"),
15111
16606
  {
15112
16607
  mode: patch.mode,
15113
16608
  api_url: patch.apiUrl,
@@ -15160,7 +16655,7 @@ async function patchCustomProviderConfig(profileName, provider, patch) {
15160
16655
  "\u81EA\u5B9A\u4E49 memory provider \u914D\u7F6E\u5FC5\u987B\u662F\u6709\u6548\u7684 JSON object\u3002"
15161
16656
  );
15162
16657
  }
15163
- const config = toRecord13(parsed);
16658
+ const config = toRecord14(parsed);
15164
16659
  if (Object.keys(config).length === 0 && parsed !== null) {
15165
16660
  throw new HermesMemoryError(
15166
16661
  "memory_provider_config_invalid",
@@ -15201,7 +16696,7 @@ function isSensitiveConfigKey(key) {
15201
16696
  }
15202
16697
  async function writeCustomProviderConfig(profileName, provider, config) {
15203
16698
  const configPath = customProviderConfigPath(profileName, provider);
15204
- await mkdir12(path18.dirname(configPath), { recursive: true, mode: 448 });
16699
+ await mkdir12(path19.dirname(configPath), { recursive: true, mode: 448 });
15205
16700
  await writeFile5(configPath, `${JSON.stringify(config, null, 2)}
15206
16701
  `, {
15207
16702
  encoding: "utf8",
@@ -15252,21 +16747,21 @@ function normalizeCustomProviderId(provider) {
15252
16747
  }
15253
16748
  async function patchHermesMemoryProvider(profileName, provider) {
15254
16749
  const configPath = resolveHermesConfigPath(profileName);
15255
- const existingRaw = await readFile13(configPath, "utf8").catch(
16750
+ const existingRaw = await readFile14(configPath, "utf8").catch(
15256
16751
  (error) => {
15257
- if (isNodeError14(error, "ENOENT")) {
16752
+ if (isNodeError15(error, "ENOENT")) {
15258
16753
  return null;
15259
16754
  }
15260
16755
  throw error;
15261
16756
  }
15262
16757
  );
15263
16758
  const document = existingRaw ? YAML4.parseDocument(existingRaw) : new YAML4.Document({});
15264
- const config = toRecord13(document.toJSON());
15265
- const memory = toRecord13(config.memory);
16759
+ const config = toRecord14(document.toJSON());
16760
+ const memory = toRecord14(config.memory);
15266
16761
  memory.provider = provider === "built-in" ? "" : provider;
15267
16762
  config.memory = memory;
15268
16763
  const backupPath = existingRaw ? `${configPath}.bak.${Date.now()}` : null;
15269
- await mkdir12(path18.dirname(configPath), { recursive: true, mode: 448 });
16764
+ await mkdir12(path19.dirname(configPath), { recursive: true, mode: 448 });
15270
16765
  if (backupPath) {
15271
16766
  await copyFile3(configPath, backupPath);
15272
16767
  }
@@ -15279,13 +16774,13 @@ async function patchHermesMemoryProvider(profileName, provider) {
15279
16774
  await rename6(tempPath, configPath);
15280
16775
  }
15281
16776
  function resolveMemoryDir(profileName) {
15282
- return path18.join(resolveHermesProfileDir(profileName), "memories");
16777
+ return path19.join(resolveHermesProfileDir(profileName), "memories");
15283
16778
  }
15284
16779
  async function readMemoryStore(profileName, target, limits) {
15285
16780
  const filePath = memoryFilePath(profileName, target);
15286
16781
  const entries = await readMemoryEntries(filePath);
15287
- const fileStat = await stat12(filePath).catch((error) => {
15288
- if (isNodeError14(error, "ENOENT")) {
16782
+ const fileStat = await stat13(filePath).catch((error) => {
16783
+ if (isNodeError15(error, "ENOENT")) {
15289
16784
  return null;
15290
16785
  }
15291
16786
  throw error;
@@ -15313,8 +16808,8 @@ async function readMemoryStore(profileName, target, limits) {
15313
16808
  };
15314
16809
  }
15315
16810
  async function readMemoryEntries(filePath) {
15316
- const raw = await readFile13(filePath, "utf8").catch((error) => {
15317
- if (isNodeError14(error, "ENOENT")) {
16811
+ const raw = await readFile14(filePath, "utf8").catch((error) => {
16812
+ if (isNodeError15(error, "ENOENT")) {
15318
16813
  return "";
15319
16814
  }
15320
16815
  throw error;
@@ -15334,9 +16829,9 @@ async function mutateMemoryEntries(profileName, target, mutate) {
15334
16829
  async function writeMemoryEntries(profileName, target, entries) {
15335
16830
  assertWithinLimit(target, entries, await readMemoryLimits(profileName));
15336
16831
  const filePath = memoryFilePath(profileName, target);
15337
- const dir = path18.dirname(filePath);
16832
+ const dir = path19.dirname(filePath);
15338
16833
  await mkdir12(dir, { recursive: true, mode: 448 });
15339
- const tempPath = path18.join(
16834
+ const tempPath = path19.join(
15340
16835
  dir,
15341
16836
  `.mem_${process.pid}_${Date.now()}_${target}.tmp`
15342
16837
  );
@@ -15347,7 +16842,7 @@ async function writeMemoryEntries(profileName, target, entries) {
15347
16842
  await rename6(tempPath, filePath);
15348
16843
  }
15349
16844
  function memoryFilePath(profileName, target) {
15350
- return path18.join(
16845
+ return path19.join(
15351
16846
  resolveMemoryDir(profileName),
15352
16847
  target === "user" ? "USER.md" : "MEMORY.md"
15353
16848
  );
@@ -15407,7 +16902,7 @@ async function readCustomProviderSetupSummary(profileName) {
15407
16902
  configurable: true,
15408
16903
  configured: true,
15409
16904
  configurationIssue: null,
15410
- providerConfigPath: path18.join(
16905
+ providerConfigPath: path19.join(
15411
16906
  resolveHermesProfileDir(profileName),
15412
16907
  "<provider>.json"
15413
16908
  ),
@@ -15485,7 +16980,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
15485
16980
  const config2 = await readJsonObject(
15486
16981
  memoryProviderConfigPath(profileName, "honcho") ?? ""
15487
16982
  );
15488
- return isConfiguredEnvValue(env.HONCHO_API_KEY) || isConfiguredEnvValue(readString14(config2.apiKey)) || isConfiguredEnvValue(readString14(config2.api_key)) || isConfiguredEnvValue(readString14(config2.baseUrl)) ? { configured: true, issue: null } : {
16983
+ return isConfiguredEnvValue(env.HONCHO_API_KEY) || isConfiguredEnvValue(readString15(config2.apiKey)) || isConfiguredEnvValue(readString15(config2.api_key)) || isConfiguredEnvValue(readString15(config2.baseUrl)) ? { configured: true, issue: null } : {
15489
16984
  configured: false,
15490
16985
  issue: "Honcho \u9700\u8981\u5148\u914D\u7F6E HONCHO_API_KEY\uFF0C\u6216\u5728 honcho.json \u914D\u7F6E self-hosted baseUrl\u3002"
15491
16986
  };
@@ -15494,7 +16989,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
15494
16989
  const config2 = await readJsonObject(
15495
16990
  memoryProviderConfigPath(profileName, "mem0") ?? ""
15496
16991
  );
15497
- return isConfiguredEnvValue(env.MEM0_API_KEY) || isConfiguredEnvValue(readString14(config2.api_key)) ? { configured: true, issue: null } : {
16992
+ return isConfiguredEnvValue(env.MEM0_API_KEY) || isConfiguredEnvValue(readString15(config2.api_key)) ? { configured: true, issue: null } : {
15498
16993
  configured: false,
15499
16994
  issue: "Mem0 \u9700\u8981\u5148\u5728\u672C\u673A Hermes .env \u914D\u7F6E MEM0_API_KEY\u3002"
15500
16995
  };
@@ -15536,7 +17031,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
15536
17031
  memoryProviderConfigPath(profileName, provider) ?? ""
15537
17032
  );
15538
17033
  const mode = normalizeHindsightMode(config.mode ?? env.HINDSIGHT_MODE);
15539
- const apiKey = readString14(config.apiKey) ?? readString14(config.api_key) ?? env.HINDSIGHT_API_KEY;
17034
+ const apiKey = readString15(config.apiKey) ?? readString15(config.api_key) ?? env.HINDSIGHT_API_KEY;
15540
17035
  if (mode === "cloud") {
15541
17036
  return isConfiguredEnvValue(apiKey) ? { configured: true, issue: null } : {
15542
17037
  configured: false,
@@ -15544,15 +17039,15 @@ async function readProviderConfigurationStatus(profileName, provider) {
15544
17039
  };
15545
17040
  }
15546
17041
  if (mode === "local_external") {
15547
- const apiUrl = readString14(config.api_url) ?? env.HINDSIGHT_API_URL ?? HINDSIGHT_DEFAULT_LOCAL_URL;
17042
+ const apiUrl = readString15(config.api_url) ?? env.HINDSIGHT_API_URL ?? HINDSIGHT_DEFAULT_LOCAL_URL;
15548
17043
  return isConfiguredEnvValue(apiUrl) ? { configured: true, issue: null } : {
15549
17044
  configured: false,
15550
17045
  issue: "Hindsight local_external \u9700\u8981\u914D\u7F6E\u53EF\u8BBF\u95EE\u7684 API URL\u3002"
15551
17046
  };
15552
17047
  }
15553
17048
  if (mode === "local_embedded") {
15554
- const llmProvider = readString14(config.llm_provider) ?? "openai";
15555
- const llmModel = readString14(config.llm_model);
17049
+ const llmProvider = readString15(config.llm_provider) ?? "openai";
17050
+ const llmModel = readString15(config.llm_model);
15556
17051
  if (!llmModel) {
15557
17052
  return {
15558
17053
  configured: false,
@@ -15560,7 +17055,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
15560
17055
  };
15561
17056
  }
15562
17057
  if (llmProvider === "openai_compatible" && !isConfiguredEnvValue(
15563
- readString14(config.llm_base_url) ?? env.HINDSIGHT_API_LLM_BASE_URL
17058
+ readString15(config.llm_base_url) ?? env.HINDSIGHT_API_LLM_BASE_URL
15564
17059
  )) {
15565
17060
  return {
15566
17061
  configured: false,
@@ -15568,7 +17063,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
15568
17063
  };
15569
17064
  }
15570
17065
  if (!["ollama", "lmstudio", "openai_compatible"].includes(llmProvider) && !isConfiguredEnvValue(
15571
- readString14(config.llmApiKey) ?? readString14(config.llm_api_key) ?? env.HINDSIGHT_LLM_API_KEY
17066
+ readString15(config.llmApiKey) ?? readString15(config.llm_api_key) ?? env.HINDSIGHT_LLM_API_KEY
15572
17067
  )) {
15573
17068
  return {
15574
17069
  configured: false,
@@ -15708,8 +17203,8 @@ async function readProviderSettings(profileName, provider) {
15708
17203
  const config = await readJsonObject(
15709
17204
  memoryProviderConfigPath(profileName, provider) ?? ""
15710
17205
  );
15711
- const banks = toRecord13(config.banks);
15712
- const hermesBank = toRecord13(banks.hermes);
17206
+ const banks = toRecord14(config.banks);
17207
+ const hermesBank = toRecord14(banks.hermes);
15713
17208
  const mode = normalizeHindsightMode(config.mode);
15714
17209
  return [
15715
17210
  selectSetting("mode", "\u8FDE\u63A5\u6A21\u5F0F", mode, [
@@ -15761,7 +17256,7 @@ async function readProviderSettings(profileName, provider) {
15761
17256
  stringSetting(
15762
17257
  "dbPath",
15763
17258
  "SQLite \u6570\u636E\u5E93\u8DEF\u5F84",
15764
- config.db_path ?? path18.join(resolveHermesProfileDir(profileName), "memory_store.db")
17259
+ config.db_path ?? path19.join(resolveHermesProfileDir(profileName), "memory_store.db")
15765
17260
  ),
15766
17261
  booleanSetting("autoExtract", "\u4F1A\u8BDD\u7ED3\u675F\u81EA\u52A8\u62BD\u53D6", config.auto_extract ?? false),
15767
17262
  numberSetting("defaultTrust", "\u9ED8\u8BA4\u4FE1\u4EFB\u5206", config.default_trust ?? 0.5),
@@ -15784,7 +17279,7 @@ async function readProviderSettings(profileName, provider) {
15784
17279
  stringSetting(
15785
17280
  "workingDirectory",
15786
17281
  "\u5DE5\u4F5C\u76EE\u5F55",
15787
- path18.join(resolveHermesProfileDir(profileName), "byterover"),
17282
+ path19.join(resolveHermesProfileDir(profileName), "byterover"),
15788
17283
  false
15789
17284
  )
15790
17285
  ];
@@ -15793,16 +17288,16 @@ async function readProviderSettings(profileName, provider) {
15793
17288
  }
15794
17289
  function memoryProviderConfigPath(profileName, provider) {
15795
17290
  if (provider === "honcho") {
15796
- return path18.join(resolveHermesProfileDir(profileName), "honcho.json");
17291
+ return path19.join(resolveHermesProfileDir(profileName), "honcho.json");
15797
17292
  }
15798
17293
  if (provider === "mem0") {
15799
- return path18.join(resolveHermesProfileDir(profileName), "mem0.json");
17294
+ return path19.join(resolveHermesProfileDir(profileName), "mem0.json");
15800
17295
  }
15801
17296
  if (provider === "supermemory") {
15802
- return path18.join(resolveHermesProfileDir(profileName), "supermemory.json");
17297
+ return path19.join(resolveHermesProfileDir(profileName), "supermemory.json");
15803
17298
  }
15804
17299
  if (provider === "hindsight") {
15805
- return path18.join(
17300
+ return path19.join(
15806
17301
  resolveHermesProfileDir(profileName),
15807
17302
  "hindsight",
15808
17303
  "config.json"
@@ -15811,21 +17306,21 @@ function memoryProviderConfigPath(profileName, provider) {
15811
17306
  return null;
15812
17307
  }
15813
17308
  function customProviderConfigPath(profileName, provider) {
15814
- return path18.join(
17309
+ return path19.join(
15815
17310
  resolveHermesProfileDir(profileName),
15816
17311
  `${normalizeCustomProviderId(provider)}.json`
15817
17312
  );
15818
17313
  }
15819
17314
  function customProviderRegistryPath(profileName) {
15820
- return path18.join(
17315
+ return path19.join(
15821
17316
  resolveHermesProfileDir(profileName),
15822
17317
  CUSTOM_PROVIDER_REGISTRY_FILE
15823
17318
  );
15824
17319
  }
15825
17320
  async function readCustomProviderRegistry(profileName) {
15826
- const raw = await readFile13(customProviderRegistryPath(profileName), "utf8").catch(
17321
+ const raw = await readFile14(customProviderRegistryPath(profileName), "utf8").catch(
15827
17322
  (error) => {
15828
- if (isNodeError14(error, "ENOENT")) {
17323
+ if (isNodeError15(error, "ENOENT")) {
15829
17324
  return "";
15830
17325
  }
15831
17326
  throw error;
@@ -15836,18 +17331,18 @@ async function readCustomProviderRegistry(profileName) {
15836
17331
  }
15837
17332
  try {
15838
17333
  const parsed = JSON.parse(raw);
15839
- const providers = Array.isArray(parsed) ? parsed : Array.isArray(toRecord13(parsed).providers) ? toRecord13(parsed).providers : [];
17334
+ const providers = Array.isArray(parsed) ? parsed : Array.isArray(toRecord14(parsed).providers) ? toRecord14(parsed).providers : [];
15840
17335
  return providers.map((item) => {
15841
17336
  if (typeof item === "string") {
15842
17337
  const id2 = normalizeCustomProviderId(item);
15843
17338
  return { id: id2, label: id2, description: "\u81EA\u5B9A\u4E49 memory provider\u3002" };
15844
17339
  }
15845
- const record = toRecord13(item);
15846
- const id = normalizeCustomProviderId(readString14(record.id) ?? "");
17340
+ const record = toRecord14(item);
17341
+ const id = normalizeCustomProviderId(readString15(record.id) ?? "");
15847
17342
  return {
15848
17343
  id,
15849
- label: readString14(record.label) ?? id,
15850
- description: readString14(record.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
17344
+ label: readString15(record.label) ?? id,
17345
+ description: readString15(record.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
15851
17346
  };
15852
17347
  }).filter((item) => item.id);
15853
17348
  } catch {
@@ -15862,7 +17357,7 @@ async function saveCustomProviderRegistryEntry(profileName, provider) {
15862
17357
  { id: providerId, label: providerId, description: "\u81EA\u5B9A\u4E49 memory provider\u3002" }
15863
17358
  ].sort((left, right) => left.id.localeCompare(right.id));
15864
17359
  const registryPath2 = customProviderRegistryPath(profileName);
15865
- await mkdir12(path18.dirname(registryPath2), { recursive: true, mode: 448 });
17360
+ await mkdir12(path19.dirname(registryPath2), { recursive: true, mode: 448 });
15866
17361
  await writeFile5(
15867
17362
  registryPath2,
15868
17363
  `${JSON.stringify({ providers }, null, 2)}
@@ -15871,10 +17366,10 @@ async function saveCustomProviderRegistryEntry(profileName, provider) {
15871
17366
  );
15872
17367
  }
15873
17368
  async function discoverUserMemoryProviderDescriptors(profileName) {
15874
- const pluginsDir = path18.join(resolveHermesProfileDir(profileName), "plugins");
15875
- const entries = await readdir8(pluginsDir, { withFileTypes: true }).catch(
17369
+ const pluginsDir = path19.join(resolveHermesProfileDir(profileName), "plugins");
17370
+ const entries = await readdir9(pluginsDir, { withFileTypes: true }).catch(
15876
17371
  (error) => {
15877
- if (isNodeError14(error, "ENOENT")) {
17372
+ if (isNodeError15(error, "ENOENT")) {
15878
17373
  return [];
15879
17374
  }
15880
17375
  throw error;
@@ -15891,21 +17386,21 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
15891
17386
  } catch {
15892
17387
  continue;
15893
17388
  }
15894
- const providerDir = path18.join(pluginsDir, entry.name);
17389
+ const providerDir = path19.join(pluginsDir, entry.name);
15895
17390
  if (!await isMemoryProviderPluginDir(providerDir)) {
15896
17391
  continue;
15897
17392
  }
15898
17393
  const meta = await readPluginMetadata(providerDir);
15899
17394
  descriptors.push({
15900
17395
  id: providerId,
15901
- label: readString14(meta.name) ?? providerId,
15902
- description: readString14(meta.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
17396
+ label: readString15(meta.name) ?? providerId,
17397
+ description: readString15(meta.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
15903
17398
  });
15904
17399
  }
15905
17400
  return descriptors;
15906
17401
  }
15907
17402
  async function isUserMemoryProviderInstalled(profileName, provider) {
15908
- const providerDir = path18.join(
17403
+ const providerDir = path19.join(
15909
17404
  resolveHermesProfileDir(profileName),
15910
17405
  "plugins",
15911
17406
  normalizeCustomProviderId(provider)
@@ -15913,9 +17408,9 @@ async function isUserMemoryProviderInstalled(profileName, provider) {
15913
17408
  return isMemoryProviderPluginDir(providerDir);
15914
17409
  }
15915
17410
  async function isMemoryProviderPluginDir(providerDir) {
15916
- const source = await readFile13(path18.join(providerDir, "__init__.py"), "utf8").catch(
17411
+ const source = await readFile14(path19.join(providerDir, "__init__.py"), "utf8").catch(
15917
17412
  (error) => {
15918
- if (isNodeError14(error, "ENOENT")) {
17413
+ if (isNodeError15(error, "ENOENT")) {
15919
17414
  return "";
15920
17415
  }
15921
17416
  throw error;
@@ -15925,22 +17420,22 @@ async function isMemoryProviderPluginDir(providerDir) {
15925
17420
  return sample.includes("register_memory_provider") || sample.includes("MemoryProvider");
15926
17421
  }
15927
17422
  async function readPluginMetadata(providerDir) {
15928
- const raw = await readFile13(path18.join(providerDir, "plugin.yaml"), "utf8").catch(
17423
+ const raw = await readFile14(path19.join(providerDir, "plugin.yaml"), "utf8").catch(
15929
17424
  (error) => {
15930
- if (isNodeError14(error, "ENOENT")) {
17425
+ if (isNodeError15(error, "ENOENT")) {
15931
17426
  return "";
15932
17427
  }
15933
17428
  throw error;
15934
17429
  }
15935
17430
  );
15936
- return raw ? toRecord13(YAML4.parse(raw)) : {};
17431
+ return raw ? toRecord14(YAML4.parse(raw)) : {};
15937
17432
  }
15938
17433
  async function resolveByteRoverCli() {
15939
17434
  const candidates = [
15940
- ...(process.env.PATH ?? "").split(path18.delimiter).filter(Boolean).map((dir) => path18.join(dir, "brv")),
15941
- path18.join(process.env.HOME ?? "", ".brv-cli", "bin", "brv"),
17435
+ ...(process.env.PATH ?? "").split(path19.delimiter).filter(Boolean).map((dir) => path19.join(dir, "brv")),
17436
+ path19.join(process.env.HOME ?? "", ".brv-cli", "bin", "brv"),
15942
17437
  "/usr/local/bin/brv",
15943
- path18.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
17438
+ path19.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
15944
17439
  ].filter(Boolean);
15945
17440
  for (const candidate of candidates) {
15946
17441
  const found = await access3(candidate).then(() => true).catch(() => false);
@@ -15951,32 +17446,32 @@ async function resolveByteRoverCli() {
15951
17446
  return null;
15952
17447
  }
15953
17448
  async function readHolographicProviderConfig(profileName) {
15954
- const raw = await readFile13(resolveHermesConfigPath(profileName), "utf8").catch(
17449
+ const raw = await readFile14(resolveHermesConfigPath(profileName), "utf8").catch(
15955
17450
  (error) => {
15956
- if (isNodeError14(error, "ENOENT")) {
17451
+ if (isNodeError15(error, "ENOENT")) {
15957
17452
  return "";
15958
17453
  }
15959
17454
  throw error;
15960
17455
  }
15961
17456
  );
15962
- const config = raw ? toRecord13(YAML4.parse(raw)) : {};
15963
- const plugins = toRecord13(config.plugins);
15964
- return toRecord13(plugins["hermes-memory-store"]);
17457
+ const config = raw ? toRecord14(YAML4.parse(raw)) : {};
17458
+ const plugins = toRecord14(config.plugins);
17459
+ return toRecord14(plugins["hermes-memory-store"]);
15965
17460
  }
15966
17461
  async function patchHolographicProviderConfig(profileName, patch) {
15967
17462
  const configPath = resolveHermesConfigPath(profileName);
15968
- const existingRaw = await readFile13(configPath, "utf8").catch(
17463
+ const existingRaw = await readFile14(configPath, "utf8").catch(
15969
17464
  (error) => {
15970
- if (isNodeError14(error, "ENOENT")) {
17465
+ if (isNodeError15(error, "ENOENT")) {
15971
17466
  return null;
15972
17467
  }
15973
17468
  throw error;
15974
17469
  }
15975
17470
  );
15976
17471
  const document = existingRaw ? YAML4.parseDocument(existingRaw) : new YAML4.Document({});
15977
- const config = toRecord13(document.toJSON());
15978
- const plugins = toRecord13(config.plugins);
15979
- const memoryStore = toRecord13(plugins["hermes-memory-store"]);
17472
+ const config = toRecord14(document.toJSON());
17473
+ const plugins = toRecord14(config.plugins);
17474
+ const memoryStore = toRecord14(plugins["hermes-memory-store"]);
15980
17475
  for (const [key, value] of Object.entries(patch)) {
15981
17476
  if (value !== void 0) {
15982
17477
  memoryStore[key] = value;
@@ -15984,7 +17479,7 @@ async function patchHolographicProviderConfig(profileName, patch) {
15984
17479
  }
15985
17480
  plugins["hermes-memory-store"] = memoryStore;
15986
17481
  config.plugins = plugins;
15987
- await mkdir12(path18.dirname(configPath), { recursive: true, mode: 448 });
17482
+ await mkdir12(path19.dirname(configPath), { recursive: true, mode: 448 });
15988
17483
  if (existingRaw) {
15989
17484
  await copyFile3(configPath, `${configPath}.bak.${Date.now()}`);
15990
17485
  }
@@ -16003,9 +17498,9 @@ async function patchHermesMemoryEnv(profileName, patch) {
16003
17498
  if (entries.length === 0) {
16004
17499
  return;
16005
17500
  }
16006
- const envPath = path18.join(resolveHermesProfileDir(profileName), ".env");
16007
- const existingRaw = await readFile13(envPath, "utf8").catch((error) => {
16008
- if (isNodeError14(error, "ENOENT")) {
17501
+ const envPath = path19.join(resolveHermesProfileDir(profileName), ".env");
17502
+ const existingRaw = await readFile14(envPath, "utf8").catch((error) => {
17503
+ if (isNodeError15(error, "ENOENT")) {
16009
17504
  return "";
16010
17505
  }
16011
17506
  throw error;
@@ -16035,7 +17530,7 @@ async function patchHermesMemoryEnv(profileName, patch) {
16035
17530
  }
16036
17531
  }
16037
17532
  const nextRaw = nextLines.join("\n").replace(/\n*$/u, "\n");
16038
- await mkdir12(path18.dirname(envPath), { recursive: true, mode: 448 });
17533
+ await mkdir12(path19.dirname(envPath), { recursive: true, mode: 448 });
16039
17534
  if (existingRaw) {
16040
17535
  await copyFile3(envPath, `${envPath}.bak.${Date.now()}`);
16041
17536
  }
@@ -16054,29 +17549,29 @@ function isMemoryEnvKeyWritable(key) {
16054
17549
  ].includes(key);
16055
17550
  }
16056
17551
  function normalizeHindsightMode(value) {
16057
- const mode = readString14(value) ?? "cloud";
17552
+ const mode = readString15(value) ?? "cloud";
16058
17553
  return mode === "local" ? "local_embedded" : mode;
16059
17554
  }
16060
17555
  async function readActiveMemoryProvider(profileName) {
16061
- const raw = await readFile13(
17556
+ const raw = await readFile14(
16062
17557
  resolveHermesConfigPath(profileName),
16063
17558
  "utf8"
16064
17559
  ).catch((error) => {
16065
- if (isNodeError14(error, "ENOENT")) {
17560
+ if (isNodeError15(error, "ENOENT")) {
16066
17561
  return "";
16067
17562
  }
16068
17563
  throw error;
16069
17564
  });
16070
- const config = raw ? toRecord13(YAML4.parse(raw)) : {};
16071
- const memory = toRecord13(config.memory);
16072
- const provider = readString14(memory.provider);
17565
+ const config = raw ? toRecord14(YAML4.parse(raw)) : {};
17566
+ const memory = toRecord14(config.memory);
17567
+ const provider = readString15(memory.provider);
16073
17568
  if (!provider || provider === "built-in" || provider === "builtin" || provider === "built_in") {
16074
17569
  return null;
16075
17570
  }
16076
17571
  return provider;
16077
17572
  }
16078
17573
  async function patchJsonProviderConfig(profileName, relativePath, patch) {
16079
- const configPath = path18.join(
17574
+ const configPath = path19.join(
16080
17575
  resolveHermesProfileDir(profileName),
16081
17576
  relativePath
16082
17577
  );
@@ -16087,7 +17582,7 @@ async function patchJsonProviderConfig(profileName, relativePath, patch) {
16087
17582
  next[key] = value;
16088
17583
  }
16089
17584
  }
16090
- await mkdir12(path18.dirname(configPath), { recursive: true, mode: 448 });
17585
+ await mkdir12(path19.dirname(configPath), { recursive: true, mode: 448 });
16091
17586
  await writeFile5(configPath, `${JSON.stringify(next, null, 2)}
16092
17587
  `, {
16093
17588
  encoding: "utf8",
@@ -16095,18 +17590,18 @@ async function patchJsonProviderConfig(profileName, relativePath, patch) {
16095
17590
  });
16096
17591
  }
16097
17592
  async function readJsonObject(filePath) {
16098
- const raw = await readFile13(filePath, "utf8").catch((error) => {
16099
- if (isNodeError14(error, "ENOENT")) {
17593
+ const raw = await readFile14(filePath, "utf8").catch((error) => {
17594
+ if (isNodeError15(error, "ENOENT")) {
16100
17595
  return "{}";
16101
17596
  }
16102
17597
  throw error;
16103
17598
  });
16104
17599
  try {
16105
- return toRecord13(JSON.parse(raw || "{}"));
17600
+ return toRecord14(JSON.parse(raw || "{}"));
16106
17601
  } catch {
16107
17602
  throw new HermesMemoryError(
16108
17603
  "memory_provider_config_invalid",
16109
- `${path18.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
17604
+ `${path19.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
16110
17605
  );
16111
17606
  }
16112
17607
  }
@@ -16114,7 +17609,7 @@ function booleanSetting(key, label, value) {
16114
17609
  return {
16115
17610
  key,
16116
17611
  label,
16117
- value: readBoolean2(value) ?? false,
17612
+ value: readBoolean3(value) ?? false,
16118
17613
  editable: true,
16119
17614
  kind: "boolean"
16120
17615
  };
@@ -16127,7 +17622,7 @@ function stringSetting(key, label, value, editable = true) {
16127
17622
  return {
16128
17623
  key,
16129
17624
  label,
16130
- value: readString14(value) ?? "",
17625
+ value: readString15(value) ?? "",
16131
17626
  editable,
16132
17627
  kind: "string"
16133
17628
  };
@@ -16142,21 +17637,21 @@ function textSetting(key, label, value, editable = true) {
16142
17637
  };
16143
17638
  }
16144
17639
  function selectSetting(key, label, value, options, editable = true) {
16145
- const stringValue = readString14(value) ?? options[0] ?? null;
17640
+ const stringValue = readString15(value) ?? options[0] ?? null;
16146
17641
  return { key, label, value: stringValue, editable, kind: "select", options };
16147
17642
  }
16148
17643
  async function readMemoryLimits(profileName) {
16149
- const raw = await readFile13(
17644
+ const raw = await readFile14(
16150
17645
  resolveHermesConfigPath(profileName),
16151
17646
  "utf8"
16152
17647
  ).catch((error) => {
16153
- if (isNodeError14(error, "ENOENT")) {
17648
+ if (isNodeError15(error, "ENOENT")) {
16154
17649
  return "";
16155
17650
  }
16156
17651
  throw error;
16157
17652
  });
16158
- const config = raw ? toRecord13(YAML4.parse(raw)) : {};
16159
- const memory = toRecord13(config.memory);
17653
+ const config = raw ? toRecord14(YAML4.parse(raw)) : {};
17654
+ const memory = toRecord14(config.memory);
16160
17655
  return {
16161
17656
  memory: readPositiveInteger3(memory.memory_char_limit) ?? DEFAULT_MEMORY_LIMIT,
16162
17657
  user: readPositiveInteger3(memory.user_char_limit) ?? DEFAULT_USER_LIMIT
@@ -16212,17 +17707,17 @@ function hashString(value) {
16212
17707
  }
16213
17708
  return hash.toString(16);
16214
17709
  }
16215
- function toRecord13(value) {
17710
+ function toRecord14(value) {
16216
17711
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
16217
17712
  }
16218
- function readString14(value) {
17713
+ function readString15(value) {
16219
17714
  return typeof value === "string" && value.trim() ? value.trim() : null;
16220
17715
  }
16221
17716
  function readPositiveInteger3(value) {
16222
17717
  const numberValue = typeof value === "number" ? value : typeof value === "string" ? Number(value.trim()) : NaN;
16223
17718
  return Number.isFinite(numberValue) && numberValue > 0 ? Math.floor(numberValue) : void 0;
16224
17719
  }
16225
- function readBoolean2(value) {
17720
+ function readBoolean3(value) {
16226
17721
  if (typeof value === "boolean") {
16227
17722
  return value;
16228
17723
  }
@@ -16243,7 +17738,7 @@ function formatEnvValue3(value) {
16243
17738
  function escapeRegExp3(value) {
16244
17739
  return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
16245
17740
  }
16246
- function isNodeError14(error, code) {
17741
+ function isNodeError15(error, code) {
16247
17742
  return error instanceof Error && "code" in error && error.code === code;
16248
17743
  }
16249
17744
 
@@ -16357,7 +17852,7 @@ function registerProfileMemoryRoutes(router, options) {
16357
17852
  );
16358
17853
  }
16359
17854
  function readMemoryTarget(body) {
16360
- const raw = readString13(body, "target");
17855
+ const raw = readString14(body, "target");
16361
17856
  if (raw === "memory" || raw === "user") {
16362
17857
  return raw;
16363
17858
  }
@@ -16368,7 +17863,7 @@ function readMemoryTarget(body) {
16368
17863
  );
16369
17864
  }
16370
17865
  function readMemoryResetTarget(body) {
16371
- const raw = readString13(body, "target") ?? "all";
17866
+ const raw = readString14(body, "target") ?? "all";
16372
17867
  if (raw === "all" || raw === "memory" || raw === "user") {
16373
17868
  return raw;
16374
17869
  }
@@ -16379,7 +17874,7 @@ function readMemoryResetTarget(body) {
16379
17874
  );
16380
17875
  }
16381
17876
  function readRequiredMemoryContent(body) {
16382
- const content = readString13(body, "content") ?? readString13(body, "text");
17877
+ const content = readString14(body, "content") ?? readString14(body, "text");
16383
17878
  if (!content) {
16384
17879
  throw new LinkHttpError(
16385
17880
  400,
@@ -16390,7 +17885,7 @@ function readRequiredMemoryContent(body) {
16390
17885
  return content;
16391
17886
  }
16392
17887
  function readRequiredMemoryMatch(body) {
16393
- const oldText = readString13(body, "old_text") ?? readString13(body, "oldText") ?? readString13(body, "match");
17888
+ const oldText = readString14(body, "old_text") ?? readString14(body, "oldText") ?? readString14(body, "match");
16394
17889
  if (!oldText) {
16395
17890
  throw new LinkHttpError(
16396
17891
  400,
@@ -16401,7 +17896,7 @@ function readRequiredMemoryMatch(body) {
16401
17896
  return oldText;
16402
17897
  }
16403
17898
  function readRequiredMemoryProvider(body) {
16404
- const provider = readString13(body, "provider") ?? readString13(body, "provider_id") ?? readString13(body, "providerId");
17899
+ const provider = readString14(body, "provider") ?? readString14(body, "provider_id") ?? readString14(body, "providerId");
16405
17900
  if (!provider) {
16406
17901
  throw new LinkHttpError(
16407
17902
  400,
@@ -16413,7 +17908,7 @@ function readRequiredMemoryProvider(body) {
16413
17908
  }
16414
17909
  function readMemorySettingsPatch(body) {
16415
17910
  const input = {};
16416
- const mode = readString13(body, "mode");
17911
+ const mode = readString14(body, "mode");
16417
17912
  if (mode) {
16418
17913
  input.mode = mode;
16419
17914
  }
@@ -16425,7 +17920,7 @@ function readMemorySettingsPatch(body) {
16425
17920
  if (bankId !== void 0) {
16426
17921
  input.bankId = bankId;
16427
17922
  }
16428
- const llmProvider = readString13(body, "llm_provider") ?? readString13(body, "llmProvider");
17923
+ const llmProvider = readString14(body, "llm_provider") ?? readString14(body, "llmProvider");
16429
17924
  if (llmProvider) {
16430
17925
  input.llmProvider = llmProvider;
16431
17926
  }
@@ -16441,23 +17936,23 @@ function readMemorySettingsPatch(body) {
16441
17936
  if (containerTag !== void 0) {
16442
17937
  input.containerTag = containerTag;
16443
17938
  }
16444
- const autoRecall = readBoolean(body.auto_recall ?? body.autoRecall);
17939
+ const autoRecall = readBoolean2(body.auto_recall ?? body.autoRecall);
16445
17940
  if (autoRecall !== void 0) {
16446
17941
  input.autoRecall = autoRecall;
16447
17942
  }
16448
- const autoCapture = readBoolean(body.auto_capture ?? body.autoCapture);
17943
+ const autoCapture = readBoolean2(body.auto_capture ?? body.autoCapture);
16449
17944
  if (autoCapture !== void 0) {
16450
17945
  input.autoCapture = autoCapture;
16451
17946
  }
16452
- const autoRetain = readBoolean(body.auto_retain ?? body.autoRetain);
17947
+ const autoRetain = readBoolean2(body.auto_retain ?? body.autoRetain);
16453
17948
  if (autoRetain !== void 0) {
16454
17949
  input.autoRetain = autoRetain;
16455
17950
  }
16456
- const memoryMode = readString13(body, "memory_mode") ?? readString13(body, "memoryMode");
17951
+ const memoryMode = readString14(body, "memory_mode") ?? readString14(body, "memoryMode");
16457
17952
  if (memoryMode) {
16458
17953
  input.memoryMode = memoryMode;
16459
17954
  }
16460
- const recallBudget = readString13(body, "recall_budget") ?? readString13(body, "recallBudget");
17955
+ const recallBudget = readString14(body, "recall_budget") ?? readString14(body, "recallBudget");
16461
17956
  if (recallBudget) {
16462
17957
  input.recallBudget = recallBudget;
16463
17958
  }
@@ -16473,11 +17968,11 @@ function readMemorySettingsPatch(body) {
16473
17968
  if (profileFrequency !== void 0) {
16474
17969
  input.profileFrequency = profileFrequency;
16475
17970
  }
16476
- const captureMode = readString13(body, "capture_mode") ?? readString13(body, "captureMode");
17971
+ const captureMode = readString14(body, "capture_mode") ?? readString14(body, "captureMode");
16477
17972
  if (captureMode) {
16478
17973
  input.captureMode = captureMode;
16479
17974
  }
16480
- const searchMode = readString13(body, "search_mode") ?? readString13(body, "searchMode");
17975
+ const searchMode = readString14(body, "search_mode") ?? readString14(body, "searchMode");
16481
17976
  if (searchMode) {
16482
17977
  input.searchMode = searchMode;
16483
17978
  }
@@ -16511,19 +18006,19 @@ function readMemorySettingsPatch(body) {
16511
18006
  if (aiPeer !== void 0) {
16512
18007
  input.aiPeer = aiPeer;
16513
18008
  }
16514
- const recallMode = readString13(body, "recall_mode") ?? readString13(body, "recallMode");
18009
+ const recallMode = readString14(body, "recall_mode") ?? readString14(body, "recallMode");
16515
18010
  if (recallMode) {
16516
18011
  input.recallMode = recallMode;
16517
18012
  }
16518
- const writeFrequency = readString13(body, "write_frequency") ?? readString13(body, "writeFrequency");
18013
+ const writeFrequency = readString14(body, "write_frequency") ?? readString14(body, "writeFrequency");
16519
18014
  if (writeFrequency) {
16520
18015
  input.writeFrequency = writeFrequency;
16521
18016
  }
16522
- const saveMessages = readBoolean(body.save_messages ?? body.saveMessages);
18017
+ const saveMessages = readBoolean2(body.save_messages ?? body.saveMessages);
16523
18018
  if (saveMessages !== void 0) {
16524
18019
  input.saveMessages = saveMessages;
16525
18020
  }
16526
- const sessionStrategy = readString13(body, "session_strategy") ?? readString13(body, "sessionStrategy");
18021
+ const sessionStrategy = readString14(body, "session_strategy") ?? readString14(body, "sessionStrategy");
16527
18022
  if (sessionStrategy) {
16528
18023
  input.sessionStrategy = sessionStrategy;
16529
18024
  }
@@ -16559,7 +18054,7 @@ function readMemorySettingsPatch(body) {
16559
18054
  if (agentId !== void 0) {
16560
18055
  input.agentId = agentId;
16561
18056
  }
16562
- const rerank = readBoolean(body.rerank);
18057
+ const rerank = readBoolean2(body.rerank);
16563
18058
  if (rerank !== void 0) {
16564
18059
  input.rerank = rerank;
16565
18060
  }
@@ -16583,7 +18078,7 @@ function readMemorySettingsPatch(body) {
16583
18078
  if (dbPath !== void 0) {
16584
18079
  input.dbPath = dbPath;
16585
18080
  }
16586
- const autoExtract = readBoolean(body.auto_extract ?? body.autoExtract);
18081
+ const autoExtract = readBoolean2(body.auto_extract ?? body.autoExtract);
16587
18082
  if (autoExtract !== void 0) {
16588
18083
  input.autoExtract = autoExtract;
16589
18084
  }
@@ -16654,12 +18149,12 @@ function toMemoryHttpError(error) {
16654
18149
  import {
16655
18150
  copyFile as copyFile4,
16656
18151
  mkdir as mkdir13,
16657
- readFile as readFile14,
16658
- readdir as readdir9,
18152
+ readFile as readFile15,
18153
+ readdir as readdir10,
16659
18154
  rename as rename7,
16660
18155
  writeFile as writeFile6
16661
18156
  } from "fs/promises";
16662
- import path19 from "path";
18157
+ import path20 from "path";
16663
18158
  import YAML5 from "yaml";
16664
18159
  var HermesSkillNotFoundError = class extends Error {
16665
18160
  constructor(skillName) {
@@ -16673,7 +18168,7 @@ var EXCLUDED_SKILL_DIRS = /* @__PURE__ */ new Set([".git", ".github", ".hub"]);
16673
18168
  async function listHermesProfileSkills(profileName, paths = resolveRuntimePaths()) {
16674
18169
  const profile = await readExistingProfile(profileName, paths);
16675
18170
  const profileDir = resolveHermesProfileDir(profile.name);
16676
- const skillsRoot = path19.join(profileDir, "skills");
18171
+ const skillsRoot = path20.join(profileDir, "skills");
16677
18172
  const [skillFiles, disabled, provenance] = await Promise.all([
16678
18173
  findSkillFiles(skillsRoot),
16679
18174
  readDisabledSkillNames(resolveHermesConfigPath(profile.name)),
@@ -16750,9 +18245,9 @@ async function findSkillFiles(root) {
16750
18245
  return results.sort((left, right) => left.localeCompare(right));
16751
18246
  }
16752
18247
  async function collectSkillFiles(directory, results) {
16753
- const entries = await readdir9(directory, { withFileTypes: true }).catch(
18248
+ const entries = await readdir10(directory, { withFileTypes: true }).catch(
16754
18249
  (error) => {
16755
- if (isNodeError15(error, "ENOENT")) {
18250
+ if (isNodeError16(error, "ENOENT")) {
16756
18251
  return [];
16757
18252
  }
16758
18253
  throw error;
@@ -16764,7 +18259,7 @@ async function collectSkillFiles(directory, results) {
16764
18259
  if (EXCLUDED_SKILL_DIRS.has(entry.name)) {
16765
18260
  continue;
16766
18261
  }
16767
- const entryPath = path19.join(directory, entry.name);
18262
+ const entryPath = path20.join(directory, entry.name);
16768
18263
  if (entry.isDirectory()) {
16769
18264
  await collectSkillFiles(entryPath, results);
16770
18265
  continue;
@@ -16775,9 +18270,9 @@ async function collectSkillFiles(directory, results) {
16775
18270
  }
16776
18271
  }
16777
18272
  async function readSkillMetadata(input) {
16778
- const raw = await readFile14(input.skillFile, "utf8").catch(
18273
+ const raw = await readFile15(input.skillFile, "utf8").catch(
16779
18274
  (error) => {
16780
- if (isNodeError15(error, "ENOENT") || isNodeError15(error, "EACCES")) {
18275
+ if (isNodeError16(error, "ENOENT") || isNodeError16(error, "EACCES")) {
16781
18276
  return null;
16782
18277
  }
16783
18278
  throw error;
@@ -16786,16 +18281,16 @@ async function readSkillMetadata(input) {
16786
18281
  if (raw === null) {
16787
18282
  return null;
16788
18283
  }
16789
- const skillDir = path19.dirname(input.skillFile);
18284
+ const skillDir = path20.dirname(input.skillFile);
16790
18285
  const { frontmatter, body } = parseSkillDocument(raw.slice(0, 4e3));
16791
18286
  const name = normalizeSkillName(
16792
- readString15(frontmatter.name) ?? path19.basename(skillDir)
18287
+ readString16(frontmatter.name) ?? path20.basename(skillDir)
16793
18288
  );
16794
18289
  if (!name) {
16795
18290
  return null;
16796
18291
  }
16797
18292
  const description = normalizeDescription(
16798
- readString15(frontmatter.description) ?? firstBodyDescription(body)
18293
+ readString16(frontmatter.description) ?? firstBodyDescription(body)
16799
18294
  );
16800
18295
  const provenance = input.provenance.get(name) ?? {
16801
18296
  source: "local",
@@ -16808,7 +18303,7 @@ async function readSkillMetadata(input) {
16808
18303
  enabled: !input.disabled.has(name),
16809
18304
  source: provenance.source,
16810
18305
  trust: provenance.trust,
16811
- relativePath: path19.relative(input.skillsRoot, skillDir)
18306
+ relativePath: path20.relative(input.skillsRoot, skillDir)
16812
18307
  };
16813
18308
  }
16814
18309
  function parseSkillDocument(raw) {
@@ -16821,7 +18316,7 @@ function parseSkillDocument(raw) {
16821
18316
  }
16822
18317
  try {
16823
18318
  return {
16824
- frontmatter: toRecord14(YAML5.parse(match[1] ?? "")),
18319
+ frontmatter: toRecord15(YAML5.parse(match[1] ?? "")),
16825
18320
  body: content.slice(match[0].length)
16826
18321
  };
16827
18322
  } catch {
@@ -16829,8 +18324,8 @@ function parseSkillDocument(raw) {
16829
18324
  }
16830
18325
  }
16831
18326
  function categoryFromPath(skillsRoot, skillFile) {
16832
- const relative = path19.relative(skillsRoot, skillFile);
16833
- const parts = relative.split(path19.sep).filter(Boolean);
18327
+ const relative = path20.relative(skillsRoot, skillFile);
18328
+ const parts = relative.split(path20.sep).filter(Boolean);
16834
18329
  return parts.length >= 3 ? parts[0] : null;
16835
18330
  }
16836
18331
  function firstBodyDescription(body) {
@@ -16853,8 +18348,8 @@ function normalizeDescription(value) {
16853
18348
  return `${description.slice(0, MAX_DESCRIPTION_LENGTH - 3)}...`;
16854
18349
  }
16855
18350
  async function readDisabledSkillNames(configPath) {
16856
- const raw = await readFile14(configPath, "utf8").catch((error) => {
16857
- if (isNodeError15(error, "ENOENT")) {
18351
+ const raw = await readFile15(configPath, "utf8").catch((error) => {
18352
+ if (isNodeError16(error, "ENOENT")) {
16858
18353
  return "";
16859
18354
  }
16860
18355
  throw error;
@@ -16862,8 +18357,8 @@ async function readDisabledSkillNames(configPath) {
16862
18357
  if (!raw.trim()) {
16863
18358
  return /* @__PURE__ */ new Set();
16864
18359
  }
16865
- const config = toRecord14(YAML5.parse(raw));
16866
- const skills = toRecord14(config.skills);
18360
+ const config = toRecord15(YAML5.parse(raw));
18361
+ const skills = toRecord15(config.skills);
16867
18362
  return new Set(readStringList3(skills.disabled));
16868
18363
  }
16869
18364
  async function readSkillProvenance(root) {
@@ -16877,9 +18372,9 @@ async function readSkillProvenance(root) {
16877
18372
  return provenance;
16878
18373
  }
16879
18374
  async function readBundledSkillNames(root) {
16880
- const raw = await readFile14(path19.join(root, ".bundled_manifest"), "utf8").catch(
18375
+ const raw = await readFile15(path20.join(root, ".bundled_manifest"), "utf8").catch(
16881
18376
  (error) => {
16882
- if (isNodeError15(error, "ENOENT")) {
18377
+ if (isNodeError16(error, "ENOENT")) {
16883
18378
  return "";
16884
18379
  }
16885
18380
  throw error;
@@ -16900,9 +18395,9 @@ async function readBundledSkillNames(root) {
16900
18395
  return names;
16901
18396
  }
16902
18397
  async function readHubInstalledSkills(root) {
16903
- const raw = await readFile14(path19.join(root, ".hub", "lock.json"), "utf8").catch(
18398
+ const raw = await readFile15(path20.join(root, ".hub", "lock.json"), "utf8").catch(
16904
18399
  (error) => {
16905
- if (isNodeError15(error, "ENOENT")) {
18400
+ if (isNodeError16(error, "ENOENT")) {
16906
18401
  return "";
16907
18402
  }
16908
18403
  throw error;
@@ -16913,17 +18408,17 @@ async function readHubInstalledSkills(root) {
16913
18408
  }
16914
18409
  let lock;
16915
18410
  try {
16916
- lock = toRecord14(JSON.parse(raw));
18411
+ lock = toRecord15(JSON.parse(raw));
16917
18412
  } catch {
16918
18413
  return /* @__PURE__ */ new Map();
16919
18414
  }
16920
- const installed = toRecord14(lock.installed);
18415
+ const installed = toRecord15(lock.installed);
16921
18416
  const result = /* @__PURE__ */ new Map();
16922
18417
  for (const [name, rawEntry] of Object.entries(installed)) {
16923
- const entry = toRecord14(rawEntry);
18418
+ const entry = toRecord15(rawEntry);
16924
18419
  result.set(normalizeSkillName(name), {
16925
- source: readString15(entry.source) ?? "hub",
16926
- trust: readString15(entry.trust_level) ?? null
18420
+ source: readString16(entry.source) ?? "hub",
18421
+ trust: readString16(entry.trust_level) ?? null
16927
18422
  });
16928
18423
  }
16929
18424
  return result;
@@ -16972,9 +18467,9 @@ function compareCategoryNames(left, right) {
16972
18467
  return left.localeCompare(right);
16973
18468
  }
16974
18469
  async function readHermesConfigDocument2(configPath) {
16975
- const existingRaw = await readFile14(configPath, "utf8").catch(
18470
+ const existingRaw = await readFile15(configPath, "utf8").catch(
16976
18471
  (error) => {
16977
- if (isNodeError15(error, "ENOENT")) {
18472
+ if (isNodeError16(error, "ENOENT")) {
16978
18473
  return null;
16979
18474
  }
16980
18475
  throw error;
@@ -16983,13 +18478,13 @@ async function readHermesConfigDocument2(configPath) {
16983
18478
  const document = existingRaw ? YAML5.parseDocument(existingRaw) : new YAML5.Document({});
16984
18479
  return {
16985
18480
  document,
16986
- config: toRecord14(document.toJSON()),
18481
+ config: toRecord15(document.toJSON()),
16987
18482
  existingRaw
16988
18483
  };
16989
18484
  }
16990
18485
  async function writeHermesConfigDocument2(input) {
16991
18486
  const backupPath = input.existingRaw ? `${input.configPath}.bak.${Date.now()}` : null;
16992
- await mkdir13(path19.dirname(input.configPath), { recursive: true, mode: 448 });
18487
+ await mkdir13(path20.dirname(input.configPath), { recursive: true, mode: 448 });
16993
18488
  if (backupPath) {
16994
18489
  await copyFile4(input.configPath, backupPath);
16995
18490
  }
@@ -17005,21 +18500,21 @@ function readStringList3(value) {
17005
18500
  }
17006
18501
  return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
17007
18502
  }
17008
- function readString15(value) {
18503
+ function readString16(value) {
17009
18504
  return typeof value === "string" && value.trim() ? value.trim() : null;
17010
18505
  }
17011
- function toRecord14(value) {
18506
+ function toRecord15(value) {
17012
18507
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
17013
18508
  }
17014
18509
  function ensureRecord3(target, key) {
17015
- const current = toRecord14(target[key]);
18510
+ const current = toRecord15(target[key]);
17016
18511
  if (current === target[key]) {
17017
18512
  return current;
17018
18513
  }
17019
18514
  target[key] = current;
17020
18515
  return current;
17021
18516
  }
17022
- function isNodeError15(error, code) {
18517
+ function isNodeError16(error, code) {
17023
18518
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
17024
18519
  }
17025
18520
 
@@ -17034,7 +18529,7 @@ function registerProfileSkillRoutes(router, options) {
17034
18529
  router.patch("/api/v1/profiles/:name/skills/:skillName", async (ctx) => {
17035
18530
  await authenticateRequest(ctx, paths);
17036
18531
  const body = await readJsonBody(ctx.req);
17037
- const enabled = readBoolean(body.enabled);
18532
+ const enabled = readBoolean2(body.enabled);
17038
18533
  if (enabled === void 0) {
17039
18534
  throw new LinkHttpError(
17040
18535
  400,
@@ -17078,22 +18573,18 @@ function registerProfileRoutes(router, options) {
17078
18573
  await authenticateRequest(ctx, paths);
17079
18574
  ctx.respond = false;
17080
18575
  const response = ctx.res;
17081
- response.statusCode = 200;
17082
- response.setHeader("content-type", "text/event-stream; charset=utf-8");
17083
- response.setHeader("cache-control", "no-store");
17084
- response.setHeader("connection", "keep-alive");
18576
+ let unsubscribe = () => {
18577
+ };
18578
+ beginSseStream(ctx.req, response, {
18579
+ onClose: () => unsubscribe()
18580
+ });
17085
18581
  writeProfileCreationSseEvent(
17086
18582
  response,
17087
18583
  await readHermesProfileCreationStatus(paths)
17088
18584
  );
17089
- const unsubscribe = subscribeHermesProfileCreationStatus((status) => {
18585
+ unsubscribe = subscribeHermesProfileCreationStatus((status) => {
17090
18586
  writeProfileCreationSseEvent(response, status);
17091
18587
  });
17092
- const cleanup = () => {
17093
- unsubscribe();
17094
- response.end();
17095
- };
17096
- ctx.req.on("close", cleanup);
17097
18588
  });
17098
18589
  router.get("/api/v1/profiles/:name/status", async (ctx) => {
17099
18590
  await authenticateRequest(ctx, paths);
@@ -17309,10 +18800,10 @@ function isProfileAvatarUrl(value) {
17309
18800
  return isHttpUrl(value) || /^data:image\/[a-z0-9.+-]+;base64,/iu.test(value);
17310
18801
  }
17311
18802
  function writeProfileCreationSseEvent(response, status) {
17312
- response.write("event: profile.creation.status\n");
17313
- response.write(`data: ${JSON.stringify(status)}
17314
-
17315
- `);
18803
+ writeJsonSseEvent(response, {
18804
+ event: "profile.creation.status",
18805
+ data: status
18806
+ });
17316
18807
  }
17317
18808
 
17318
18809
  // src/http/routes/runs.ts
@@ -17322,7 +18813,7 @@ function registerRunRoutes(router, options) {
17322
18813
  router.post("/api/v1/runs", async (ctx) => {
17323
18814
  await authenticateRequest(ctx, paths);
17324
18815
  const body = await readJsonBody(ctx.req);
17325
- const input = readString13(body, "input");
18816
+ const input = readString14(body, "input");
17326
18817
  if (!input) {
17327
18818
  throw new LinkHttpError(400, "run_input_required", "input is required");
17328
18819
  }
@@ -17330,11 +18821,11 @@ function registerRunRoutes(router, options) {
17330
18821
  ctx.body = await createHermesRun(
17331
18822
  {
17332
18823
  input,
17333
- instructions: readString13(body, "instructions") ?? void 0,
18824
+ instructions: readString14(body, "instructions") ?? void 0,
17334
18825
  conversation_history: readConversationHistory(
17335
18826
  body.conversation_history ?? body.conversationHistory
17336
18827
  ),
17337
- session_id: readString13(body, "session_id") ?? readString13(body, "sessionId") ?? void 0
18828
+ session_id: readString14(body, "session_id") ?? readString14(body, "sessionId") ?? void 0
17338
18829
  },
17339
18830
  { logger, profileName: readOptionalProfileName(body) }
17340
18831
  );
@@ -17505,8 +18996,8 @@ function readModelList(payload) {
17505
18996
  // src/hermes/updates.ts
17506
18997
  import { EventEmitter as EventEmitter3 } from "events";
17507
18998
  import { spawn as spawn3 } from "child_process";
17508
- import { mkdir as mkdir14, readFile as readFile15, rm as rm8 } from "fs/promises";
17509
- import path20 from "path";
18999
+ import { mkdir as mkdir14, readFile as readFile16, rm as rm8 } from "fs/promises";
19000
+ import path21 from "path";
17510
19001
  var SERVER_HERMES_RELEASES_LATEST_PATH = "/api/v1/hermes-agent/releases/latest";
17511
19002
  var RELEASE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
17512
19003
  var RELEASE_FETCH_TIMEOUT_MS = 5e3;
@@ -17727,24 +19218,24 @@ async function readRemoteRelease(options, now) {
17727
19218
  }
17728
19219
  }
17729
19220
  function normalizeServerReleaseSnapshot(payload) {
17730
- const snapshot = toRecord15(payload);
19221
+ const snapshot = toRecord16(payload);
17731
19222
  const remote = toNullableRecord(snapshot.remote);
17732
19223
  return {
17733
19224
  remote: remote ? normalizeServerRelease(remote) : null,
17734
- cacheState: readString16(snapshot, "cache_state") ?? readString16(snapshot, "cacheState"),
17735
- issue: readString16(snapshot, "issue")
19225
+ cacheState: readString17(snapshot, "cache_state") ?? readString17(snapshot, "cacheState"),
19226
+ issue: readString17(snapshot, "issue")
17736
19227
  };
17737
19228
  }
17738
19229
  function normalizeServerRelease(payload) {
17739
- const tag = readString16(payload, "tag");
17740
- const name = readString16(payload, "name");
19230
+ const tag = readString17(payload, "tag");
19231
+ const name = readString17(payload, "name");
17741
19232
  return {
17742
- version: readString16(payload, "version") ?? extractSemver(name) ?? extractTagSemver(tag),
19233
+ version: readString17(payload, "version") ?? extractSemver(name) ?? extractTagSemver(tag),
17743
19234
  tag,
17744
19235
  name,
17745
- releaseUrl: readString16(payload, "releaseUrl") ?? readString16(payload, "release_url"),
17746
- publishedAt: readString16(payload, "publishedAt") ?? readString16(payload, "published_at"),
17747
- fetchedAt: readString16(payload, "fetchedAt") ?? readString16(payload, "fetched_at") ?? (/* @__PURE__ */ new Date()).toISOString()
19236
+ releaseUrl: readString17(payload, "releaseUrl") ?? readString17(payload, "release_url"),
19237
+ publishedAt: readString17(payload, "publishedAt") ?? readString17(payload, "published_at"),
19238
+ fetchedAt: readString17(payload, "fetchedAt") ?? readString17(payload, "fetched_at") ?? (/* @__PURE__ */ new Date()).toISOString()
17748
19239
  };
17749
19240
  }
17750
19241
  async function readReleaseCache(paths) {
@@ -17763,7 +19254,7 @@ async function writeUpdateState(paths, state) {
17763
19254
  await writeJsonFile(updateStatePath(paths), state);
17764
19255
  }
17765
19256
  async function readUpdateLogLines(paths) {
17766
- const raw = await readFile15(updateLogPath(paths), "utf8").catch(() => "");
19257
+ const raw = await readFile16(updateLogPath(paths), "utf8").catch(() => "");
17767
19258
  if (!raw.trim()) {
17768
19259
  return [];
17769
19260
  }
@@ -17772,13 +19263,13 @@ async function readUpdateLogLines(paths) {
17772
19263
  );
17773
19264
  }
17774
19265
  function releaseCachePath(paths) {
17775
- return path20.join(paths.indexesDir, "hermes-release-check.json");
19266
+ return path21.join(paths.indexesDir, "hermes-release-check.json");
17776
19267
  }
17777
19268
  function updateStatePath(paths) {
17778
- return path20.join(paths.runDir, "hermes-update-state.json");
19269
+ return path21.join(paths.runDir, "hermes-update-state.json");
17779
19270
  }
17780
19271
  function updateLogPath(paths) {
17781
- return path20.join(paths.logsDir, UPDATE_LOG_FILE);
19272
+ return path21.join(paths.logsDir, UPDATE_LOG_FILE);
17782
19273
  }
17783
19274
  async function clearUpdateLogFiles(paths) {
17784
19275
  const primary = updateLogPath(paths);
@@ -17816,7 +19307,7 @@ function compareSemver2(left, right) {
17816
19307
  }
17817
19308
  return 0;
17818
19309
  }
17819
- function toRecord15(value) {
19310
+ function toRecord16(value) {
17820
19311
  return typeof value === "object" && value !== null ? value : {};
17821
19312
  }
17822
19313
  function toNullableRecord(value) {
@@ -17862,7 +19353,7 @@ function isRecentRunningState2(state) {
17862
19353
  const startedAt = Date.parse(state.started_at);
17863
19354
  return Number.isFinite(startedAt) && Date.now() - startedAt < 3e4;
17864
19355
  }
17865
- function readString16(payload, key) {
19356
+ function readString17(payload, key) {
17866
19357
  const value = payload[key];
17867
19358
  return typeof value === "string" && value.trim() ? value.trim() : null;
17868
19359
  }
@@ -17870,13 +19361,13 @@ function readString16(payload, key) {
17870
19361
  // src/link/updates.ts
17871
19362
  import { spawn as spawn5 } from "child_process";
17872
19363
  import { EventEmitter as EventEmitter4 } from "events";
17873
- import { mkdir as mkdir17, readFile as readFile17, rm as rm11 } from "fs/promises";
17874
- import path22 from "path";
19364
+ import { mkdir as mkdir17, readFile as readFile18, rm as rm11 } from "fs/promises";
19365
+ import path23 from "path";
17875
19366
 
17876
19367
  // src/daemon/process.ts
17877
19368
  import { spawn as spawn4 } from "child_process";
17878
- import { mkdir as mkdir16, readFile as readFile16, rm as rm10 } from "fs/promises";
17879
- import path21 from "path";
19369
+ import { mkdir as mkdir16, readFile as readFile17, rm as rm10 } from "fs/promises";
19370
+ import path22 from "path";
17880
19371
 
17881
19372
  // src/daemon/service.ts
17882
19373
  import { createServer } from "http";
@@ -18054,7 +19545,7 @@ async function handleFrame(socket, raw, localPort, abortControllers) {
18054
19545
  // src/runtime/system-info.ts
18055
19546
  import { execFileSync } from "child_process";
18056
19547
  import { readFileSync } from "fs";
18057
- import os5 from "os";
19548
+ import os6 from "os";
18058
19549
  function readLinkSystemInfo() {
18059
19550
  const platform = process.platform;
18060
19551
  const hostname = readHostname(platform);
@@ -18093,7 +19584,7 @@ function readHostname(platform) {
18093
19584
  return computerName;
18094
19585
  }
18095
19586
  }
18096
- return normalizeText(os5.hostname());
19587
+ return normalizeText(os6.hostname());
18097
19588
  }
18098
19589
  function readOsLabel(platform) {
18099
19590
  if (platform === "darwin") {
@@ -18101,12 +19592,12 @@ function readOsLabel(platform) {
18101
19592
  return version ? `macOS ${version}` : "macOS";
18102
19593
  }
18103
19594
  if (platform === "linux") {
18104
- return readLinuxOsRelease() ?? `Linux ${os5.release()}`;
19595
+ return readLinuxOsRelease() ?? `Linux ${os6.release()}`;
18105
19596
  }
18106
19597
  if (platform === "win32") {
18107
- return `Windows ${os5.release()}`;
19598
+ return `Windows ${os6.release()}`;
18108
19599
  }
18109
- return `${os5.type()} ${os5.release()}`.trim();
19600
+ return `${os6.type()} ${os6.release()}`.trim();
18110
19601
  }
18111
19602
  function readLinuxOsRelease() {
18112
19603
  for (const file of ["/etc/os-release", "/usr/lib/os-release"]) {
@@ -18153,11 +19644,11 @@ function truncateText(value, maxLength) {
18153
19644
  }
18154
19645
 
18155
19646
  // src/topology/network.ts
18156
- import os7 from "os";
19647
+ import os8 from "os";
18157
19648
 
18158
19649
  // src/topology/environment.ts
18159
19650
  import { existsSync, readFileSync as readFileSync2 } from "fs";
18160
- import os6 from "os";
19651
+ import os7 from "os";
18161
19652
  function detectRuntimeEnvironment(env = process.env) {
18162
19653
  if (isWsl(env)) {
18163
19654
  return {
@@ -18186,7 +19677,7 @@ function isWsl(env) {
18186
19677
  if (env.WSL_DISTRO_NAME || env.WSL_INTEROP) {
18187
19678
  return true;
18188
19679
  }
18189
- const release = os6.release().toLowerCase();
19680
+ const release = os7.release().toLowerCase();
18190
19681
  return release.includes("microsoft") || release.includes("wsl");
18191
19682
  }
18192
19683
  function isContainer(env) {
@@ -18231,7 +19722,7 @@ async function discoverRouteCandidates(options) {
18231
19722
  };
18232
19723
  }
18233
19724
  function discoverLanIps() {
18234
- return discoverLanIpsFromInterfaces(os7.networkInterfaces());
19725
+ return discoverLanIpsFromInterfaces(os8.networkInterfaces());
18235
19726
  }
18236
19727
  function discoverLanIpsFromInterfaces(interfaces) {
18237
19728
  const result = /* @__PURE__ */ new Set();
@@ -18724,6 +20215,33 @@ function startCronDeliveryScheduler(options) {
18724
20215
  }
18725
20216
  };
18726
20217
  }
20218
+ function startHermesSessionSyncScheduler(options) {
20219
+ let running = false;
20220
+ const syncSessions = async () => {
20221
+ if (running) {
20222
+ return;
20223
+ }
20224
+ running = true;
20225
+ try {
20226
+ await options.conversations.syncHermesSessions();
20227
+ } catch (error) {
20228
+ void options.logger.warn("hermes_session_sync_failed", {
20229
+ error: error instanceof Error ? error.message : String(error)
20230
+ });
20231
+ } finally {
20232
+ running = false;
20233
+ }
20234
+ };
20235
+ const timer = setInterval(() => {
20236
+ void syncSessions();
20237
+ }, options.intervalMs ?? 10 * 60 * 1e3);
20238
+ timer.unref?.();
20239
+ return {
20240
+ close() {
20241
+ clearInterval(timer);
20242
+ }
20243
+ };
20244
+ }
18727
20245
 
18728
20246
  // src/daemon/service.ts
18729
20247
  async function startLinkService(options = {}) {
@@ -18744,11 +20262,21 @@ async function startLinkService(options = {}) {
18744
20262
  }
18745
20263
  const conversations = new ConversationService(paths, logger);
18746
20264
  await conversations.rebuildStatisticsIndex();
20265
+ const triggerHermesSessionSync = () => {
20266
+ void conversations.syncHermesSessions().catch((error) => {
20267
+ void logger.warn("hermes_session_sync_failed", {
20268
+ error: error instanceof Error ? error.message : String(error)
20269
+ });
20270
+ });
20271
+ };
18747
20272
  const app = await createApp({
18748
20273
  paths,
18749
20274
  logger,
18750
20275
  conversations,
18751
- onPairingClaimed: options.onPairingClaimed
20276
+ onPairingClaimed: async () => {
20277
+ triggerHermesSessionSync();
20278
+ await options.onPairingClaimed?.();
20279
+ }
18752
20280
  });
18753
20281
  const server = createServer(app.callback());
18754
20282
  try {
@@ -18768,11 +20296,16 @@ async function startLinkService(options = {}) {
18768
20296
  port: config.port,
18769
20297
  link_id: identity?.link_id ?? null
18770
20298
  });
20299
+ triggerHermesSessionSync();
18771
20300
  const scheduler = startCronDeliveryScheduler({
18772
20301
  paths,
18773
20302
  conversations,
18774
20303
  logger
18775
20304
  });
20305
+ const hermesSessionSyncScheduler = startHermesSessionSyncScheduler({
20306
+ conversations,
20307
+ logger
20308
+ });
18776
20309
  let relay = null;
18777
20310
  if (identity?.link_id) {
18778
20311
  relay = connectRelayControl({
@@ -18805,6 +20338,7 @@ async function startLinkService(options = {}) {
18805
20338
  return {
18806
20339
  async close() {
18807
20340
  scheduler.close();
20341
+ hermesSessionSyncScheduler.close();
18808
20342
  lanIpMonitor.close();
18809
20343
  relay?.close();
18810
20344
  await closeServer(server);
@@ -18917,7 +20451,7 @@ async function runDaemonSupervisor(paths = resolveRuntimePaths()) {
18917
20451
  await mkdir16(paths.logsDir, { recursive: true, mode: 448 });
18918
20452
  const log = createRotatingTextLogWriter({
18919
20453
  paths,
18920
- fileName: path21.basename(daemonLogFile(paths))
20454
+ fileName: path22.basename(daemonLogFile(paths))
18921
20455
  });
18922
20456
  const scriptPath = currentCliScriptPath();
18923
20457
  const child = spawn4(process.execPath, [scriptPath, "daemon", "--foreground"], {
@@ -19046,7 +20580,7 @@ function currentCliScriptPath() {
19046
20580
  return process.argv[1];
19047
20581
  }
19048
20582
  async function readPid(filePath) {
19049
- const raw = await readFile16(filePath, "utf8").catch(() => null);
20583
+ const raw = await readFile17(filePath, "utf8").catch(() => null);
19050
20584
  if (!raw) {
19051
20585
  return null;
19052
20586
  }
@@ -19372,21 +20906,21 @@ async function readRemoteLinkPolicy(options) {
19372
20906
  }
19373
20907
  }
19374
20908
  function normalizeServerSnapshot(payload) {
19375
- const snapshot = toRecord16(payload);
20909
+ const snapshot = toRecord17(payload);
19376
20910
  const policy = toNullableRecord2(snapshot.policy);
19377
20911
  if (!policy) {
19378
20912
  return {
19379
20913
  remote: null,
19380
- issue: readString17(snapshot, "issue")
20914
+ issue: readString18(snapshot, "issue")
19381
20915
  };
19382
20916
  }
19383
20917
  const release = toNullableRecord2(snapshot.release);
19384
- const currentVersion = readString17(policy, "current_version") ?? readString17(policy, "currentVersion");
19385
- const minSafeVersion = readString17(policy, "min_safe_version") ?? readString17(policy, "minSafeVersion");
20918
+ const currentVersion = readString18(policy, "current_version") ?? readString18(policy, "currentVersion");
20919
+ const minSafeVersion = readString18(policy, "min_safe_version") ?? readString18(policy, "minSafeVersion");
19386
20920
  if (!currentVersion) {
19387
20921
  return {
19388
20922
  remote: null,
19389
- issue: readString17(snapshot, "issue")
20923
+ issue: readString18(snapshot, "issue")
19390
20924
  };
19391
20925
  }
19392
20926
  return {
@@ -19394,10 +20928,10 @@ function normalizeServerSnapshot(payload) {
19394
20928
  current_version: currentVersion,
19395
20929
  min_safe_version: minSafeVersion,
19396
20930
  target_version: currentVersion,
19397
- release_url: release ? readString17(release, "release_url") ?? readString17(release, "releaseUrl") : null,
19398
- published_at: release ? readString17(release, "published_at") ?? readString17(release, "publishedAt") : null
20931
+ release_url: release ? readString18(release, "release_url") ?? readString18(release, "releaseUrl") : null,
20932
+ published_at: release ? readString18(release, "published_at") ?? readString18(release, "publishedAt") : null
19399
20933
  },
19400
- issue: readString17(snapshot, "issue")
20934
+ issue: readString18(snapshot, "issue")
19401
20935
  };
19402
20936
  }
19403
20937
  async function fetchCurrentLinkReleaseFromServer(options, fetcher) {
@@ -19447,7 +20981,7 @@ async function writeUpdateState2(paths, state) {
19447
20981
  await writeJsonFile(updateStatePath2(paths), state);
19448
20982
  }
19449
20983
  async function readUpdateLogLines2(paths) {
19450
- const raw = await readFile17(updateLogPath2(paths), "utf8").catch(() => "");
20984
+ const raw = await readFile18(updateLogPath2(paths), "utf8").catch(() => "");
19451
20985
  if (!raw.trim()) {
19452
20986
  return [];
19453
20987
  }
@@ -19456,10 +20990,10 @@ async function readUpdateLogLines2(paths) {
19456
20990
  );
19457
20991
  }
19458
20992
  function updateStatePath2(paths) {
19459
- return path22.join(paths.runDir, "link-update-state.json");
20993
+ return path23.join(paths.runDir, "link-update-state.json");
19460
20994
  }
19461
20995
  function updateLogPath2(paths) {
19462
- return path22.join(paths.logsDir, UPDATE_LOG_FILE2);
20996
+ return path23.join(paths.logsDir, UPDATE_LOG_FILE2);
19463
20997
  }
19464
20998
  async function clearUpdateLogFiles2(paths) {
19465
20999
  const primary = updateLogPath2(paths);
@@ -19511,19 +21045,19 @@ function isProcessAlive4(pid) {
19511
21045
  return false;
19512
21046
  }
19513
21047
  }
19514
- function toRecord16(value) {
21048
+ function toRecord17(value) {
19515
21049
  return typeof value === "object" && value !== null ? value : {};
19516
21050
  }
19517
21051
  function toNullableRecord2(value) {
19518
21052
  return typeof value === "object" && value !== null ? value : null;
19519
21053
  }
19520
- function readString17(payload, key) {
21054
+ function readString18(payload, key) {
19521
21055
  const value = payload[key];
19522
21056
  return typeof value === "string" && value.trim() ? value.trim() : null;
19523
21057
  }
19524
21058
 
19525
21059
  // src/pairing/pairing.ts
19526
- import path23 from "path";
21060
+ import path24 from "path";
19527
21061
  import { rm as rm12 } from "fs/promises";
19528
21062
 
19529
21063
  // src/relay/bootstrap.ts
@@ -19799,8 +21333,8 @@ async function loadRequiredIdentity2(paths) {
19799
21333
  }
19800
21334
  return identity;
19801
21335
  }
19802
- async function postServerJson(serverBaseUrl, path24, body) {
19803
- const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path24}`, {
21336
+ async function postServerJson(serverBaseUrl, path25, body) {
21337
+ const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path25}`, {
19804
21338
  method: "POST",
19805
21339
  headers: {
19806
21340
  accept: "application/json",
@@ -19840,8 +21374,8 @@ function pairingErrorSnapshot(stage, error) {
19840
21374
  occurred_at: (/* @__PURE__ */ new Date()).toISOString()
19841
21375
  };
19842
21376
  }
19843
- async function patchServerJson(serverBaseUrl, path24, token, body) {
19844
- const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path24}`, {
21377
+ async function patchServerJson(serverBaseUrl, path25, token, body) {
21378
+ const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path25}`, {
19845
21379
  method: "PATCH",
19846
21380
  headers: {
19847
21381
  accept: "application/json",
@@ -19872,10 +21406,10 @@ function readErrorMessage5(payload) {
19872
21406
  return typeof message === "string" ? message : null;
19873
21407
  }
19874
21408
  function pairingClaimPath(sessionId, paths) {
19875
- return path23.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
21409
+ return path24.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
19876
21410
  }
19877
21411
  function pairingSessionPath(sessionId, paths) {
19878
- return path23.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
21412
+ return path24.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
19879
21413
  }
19880
21414
  function qrPreferredUrls(routes) {
19881
21415
  return routes.preferredUrls.filter((url) => !url.includes("/api/v1/relay/links/")).slice(0, 1);
@@ -19946,8 +21480,8 @@ function registerSystemRoutes(router, options) {
19946
21480
  });
19947
21481
  router.post("/api/v1/pairing/claim", async (ctx) => {
19948
21482
  const body = await readJsonBody(ctx.req);
19949
- const sessionId = readString13(body, "session_id") ?? readString13(body, "sessionId");
19950
- const claimToken = readString13(body, "claim_token") ?? readString13(body, "claimToken");
21483
+ const sessionId = readString14(body, "session_id") ?? readString14(body, "sessionId");
21484
+ const claimToken = readString14(body, "claim_token") ?? readString14(body, "claimToken");
19951
21485
  if (!sessionId || !claimToken) {
19952
21486
  throw new LinkHttpError(
19953
21487
  400,
@@ -19958,10 +21492,10 @@ function registerSystemRoutes(router, options) {
19958
21492
  const claimed = await claimPairing({
19959
21493
  sessionId,
19960
21494
  claimToken,
19961
- deviceLabel: readString13(body, "device_label") ?? readString13(body, "deviceLabel") ?? "HermesPilot App",
19962
- devicePlatform: readString13(body, "device_platform") ?? readString13(body, "devicePlatform") ?? "unknown",
19963
- deviceModel: readString13(body, "device_model") ?? readString13(body, "deviceModel"),
19964
- appInstanceId: readString13(body, "app_instance_id") ?? readString13(body, "appInstanceId"),
21495
+ deviceLabel: readString14(body, "device_label") ?? readString14(body, "deviceLabel") ?? "HermesPilot App",
21496
+ devicePlatform: readString14(body, "device_platform") ?? readString14(body, "devicePlatform") ?? "unknown",
21497
+ deviceModel: readString14(body, "device_model") ?? readString14(body, "deviceModel"),
21498
+ appInstanceId: readString14(body, "app_instance_id") ?? readString14(body, "appInstanceId"),
19965
21499
  paths
19966
21500
  });
19967
21501
  ctx.body = claimed;
@@ -20036,9 +21570,9 @@ function registerSystemRoutes(router, options) {
20036
21570
  const body = await readJsonBody(ctx.req);
20037
21571
  const session = await createDeviceSession(
20038
21572
  {
20039
- label: readString13(body, "device_label") ?? readString13(body, "deviceLabel") ?? "HermesPilot App",
20040
- platform: readString13(body, "device_platform") ?? readString13(body, "devicePlatform") ?? "unknown",
20041
- model: readString13(body, "device_model") ?? readString13(body, "deviceModel"),
21573
+ label: readString14(body, "device_label") ?? readString14(body, "deviceLabel") ?? "HermesPilot App",
21574
+ platform: readString14(body, "device_platform") ?? readString14(body, "devicePlatform") ?? "unknown",
21575
+ model: readString14(body, "device_model") ?? readString14(body, "deviceModel"),
20042
21576
  appInstanceId: auth.appInstanceId
20043
21577
  },
20044
21578
  paths
@@ -20067,7 +21601,7 @@ function registerSystemRoutes(router, options) {
20067
21601
  });
20068
21602
  router.post("/api/v1/auth/refresh", async (ctx) => {
20069
21603
  const body = await readJsonBody(ctx.req);
20070
- const refreshToken = readString13(body, "refresh_token") ?? readString13(body, "refreshToken");
21604
+ const refreshToken = readString14(body, "refresh_token") ?? readString14(body, "refreshToken");
20071
21605
  if (!refreshToken) {
20072
21606
  throw new LinkHttpError(
20073
21607
  400,
@@ -20078,10 +21612,10 @@ function registerSystemRoutes(router, options) {
20078
21612
  const session = await refreshDeviceSession(
20079
21613
  refreshToken,
20080
21614
  {
20081
- appInstanceId: readString13(body, "app_instance_id") ?? readString13(body, "appInstanceId"),
20082
- label: readString13(body, "device_label") ?? readString13(body, "deviceLabel"),
20083
- platform: readString13(body, "device_platform") ?? readString13(body, "devicePlatform"),
20084
- model: readString13(body, "device_model") ?? readString13(body, "deviceModel")
21615
+ appInstanceId: readString14(body, "app_instance_id") ?? readString14(body, "appInstanceId"),
21616
+ label: readString14(body, "device_label") ?? readString14(body, "deviceLabel"),
21617
+ platform: readString14(body, "device_platform") ?? readString14(body, "devicePlatform"),
21618
+ model: readString14(body, "device_model") ?? readString14(body, "deviceModel")
20085
21619
  },
20086
21620
  paths
20087
21621
  );
@@ -20100,7 +21634,7 @@ function registerSystemRoutes(router, options) {
20100
21634
  });
20101
21635
  router.post("/api/v1/auth/logout", async (ctx) => {
20102
21636
  const body = await readJsonBody(ctx.req);
20103
- const refreshToken = readString13(body, "refresh_token") ?? readString13(body, "refreshToken");
21637
+ const refreshToken = readString14(body, "refresh_token") ?? readString14(body, "refreshToken");
20104
21638
  if (refreshToken) {
20105
21639
  await revokeDeviceRefreshToken(refreshToken, paths);
20106
21640
  }
@@ -20325,7 +21859,7 @@ function registerSystemRoutes(router, options) {
20325
21859
  router.patch("/api/v1/devices/:deviceId", async (ctx) => {
20326
21860
  const auth = await authenticateRequest(ctx, paths);
20327
21861
  const body = await readJsonBody(ctx.req);
20328
- const label = readString13(body, "label") ?? readString13(body, "device_label");
21862
+ const label = readString14(body, "label") ?? readString14(body, "device_label");
20329
21863
  if (!label) {
20330
21864
  throw new LinkHttpError(
20331
21865
  400,
@@ -20365,11 +21899,11 @@ async function readActiveCronJobCount(profiles, logger) {
20365
21899
  }, 0);
20366
21900
  }
20367
21901
  function isActiveCronJob(job) {
20368
- const enabled = readBoolean(job.enabled) ?? true;
21902
+ const enabled = readBoolean2(job.enabled) ?? true;
20369
21903
  if (!enabled) {
20370
21904
  return false;
20371
21905
  }
20372
- const state = readString13(job, "state")?.toLowerCase();
21906
+ const state = readString14(job, "state")?.toLowerCase();
20373
21907
  return !["paused", "disabled", "completed", "deleted"].includes(state ?? "");
20374
21908
  }
20375
21909
  function filterLogsWithinHours(logs, hours, now = Date.now()) {
@@ -20421,26 +21955,22 @@ function registerHermesUpdateRoutes(router, options) {
20421
21955
  await authenticateRequest(ctx, paths);
20422
21956
  ctx.respond = false;
20423
21957
  const response = ctx.res;
20424
- response.statusCode = 200;
20425
- response.setHeader("content-type", "text/event-stream; charset=utf-8");
20426
- response.setHeader("cache-control", "no-store");
20427
- response.setHeader("connection", "keep-alive");
21958
+ let unsubscribe = () => {
21959
+ };
21960
+ beginSseStream(ctx.req, response, {
21961
+ onClose: () => unsubscribe()
21962
+ });
20428
21963
  writeUpdateSseEvent(response, await readHermesUpdateStatus(paths));
20429
- const unsubscribe = subscribeHermesUpdateStatus((status) => {
21964
+ unsubscribe = subscribeHermesUpdateStatus((status) => {
20430
21965
  writeUpdateSseEvent(response, status);
20431
21966
  });
20432
- const cleanup = () => {
20433
- unsubscribe();
20434
- response.end();
20435
- };
20436
- ctx.req.on("close", cleanup);
20437
21967
  });
20438
21968
  }
20439
21969
  function writeUpdateSseEvent(response, status) {
20440
- response.write("event: hermes.update.status\n");
20441
- response.write(`data: ${JSON.stringify(status)}
20442
-
20443
- `);
21970
+ writeJsonSseEvent(response, {
21971
+ event: "hermes.update.status",
21972
+ data: status
21973
+ });
20444
21974
  }
20445
21975
 
20446
21976
  // src/http/routes/link-updates.ts
@@ -20463,33 +21993,29 @@ function registerLinkUpdateRoutes(router, options) {
20463
21993
  ctx.body = await startLinkUpdate({
20464
21994
  paths,
20465
21995
  logger,
20466
- targetVersion: readString13(body, "target_version") ?? readString13(body, "targetVersion")
21996
+ targetVersion: readString14(body, "target_version") ?? readString14(body, "targetVersion")
20467
21997
  });
20468
21998
  });
20469
21999
  router.get("/api/v1/link/update/events", async (ctx) => {
20470
22000
  await authenticateRequest(ctx, paths);
20471
22001
  ctx.respond = false;
20472
22002
  const response = ctx.res;
20473
- response.statusCode = 200;
20474
- response.setHeader("content-type", "text/event-stream; charset=utf-8");
20475
- response.setHeader("cache-control", "no-store");
20476
- response.setHeader("connection", "keep-alive");
22003
+ let unsubscribe = () => {
22004
+ };
22005
+ beginSseStream(ctx.req, response, {
22006
+ onClose: () => unsubscribe()
22007
+ });
20477
22008
  writeUpdateSseEvent2(response, await readLinkUpdateStatus(paths));
20478
- const unsubscribe = subscribeLinkUpdateStatus((status) => {
22009
+ unsubscribe = subscribeLinkUpdateStatus((status) => {
20479
22010
  writeUpdateSseEvent2(response, status);
20480
22011
  });
20481
- const cleanup = () => {
20482
- unsubscribe();
20483
- response.end();
20484
- };
20485
- ctx.req.on("close", cleanup);
20486
22012
  });
20487
22013
  }
20488
22014
  function writeUpdateSseEvent2(response, status) {
20489
- response.write("event: link.update.status\n");
20490
- response.write(`data: ${JSON.stringify(status)}
20491
-
20492
- `);
22015
+ writeJsonSseEvent(response, {
22016
+ event: "link.update.status",
22017
+ data: status
22018
+ });
20493
22019
  }
20494
22020
 
20495
22021
  // src/http/routes/pairing.ts
@@ -20497,7 +22023,7 @@ import QRCode from "qrcode";
20497
22023
  function registerPairingRoutes(router, options) {
20498
22024
  const { paths } = options;
20499
22025
  router.get("/pair", async (ctx) => {
20500
- const sessionId = readString13(ctx.query, "session_id");
22026
+ const sessionId = readString14(ctx.query, "session_id");
20501
22027
  if (!sessionId) {
20502
22028
  throw new LinkHttpError(400, "pairing_session_required", "session_id is required");
20503
22029
  }
@@ -20522,7 +22048,7 @@ function registerPairingRoutes(router, options) {
20522
22048
  ctx.body = page;
20523
22049
  });
20524
22050
  router.get("/api/v1/pairing/session", async (ctx) => {
20525
- const sessionId = readString13(ctx.query, "session_id");
22051
+ const sessionId = readString14(ctx.query, "session_id");
20526
22052
  if (!sessionId) {
20527
22053
  throw new LinkHttpError(400, "pairing_session_required", "session_id is required");
20528
22054
  }
@@ -20926,7 +22452,7 @@ function registerInternalRoutes(router, options) {
20926
22452
  router.post("/internal/deliver", async (ctx) => {
20927
22453
  assertLoopbackRequest(ctx.req);
20928
22454
  const body = await readJsonBody(ctx.req);
20929
- const stagingDir = readString13(body, "staging_dir") ?? readString13(body, "stagingDir");
22455
+ const stagingDir = readString14(body, "staging_dir") ?? readString14(body, "stagingDir");
20930
22456
  if (!stagingDir) {
20931
22457
  throw new LinkHttpError(
20932
22458
  400,