@hermespilot/link 0.7.9 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ import Router from "@koa/router";
5
5
  // src/conversations/conversation-service.ts
6
6
  import { EventEmitter } from "events";
7
7
  import { createHash as createHash7, randomUUID as randomUUID12 } from "crypto";
8
- import path25 from "path";
8
+ import path26 from "path";
9
9
 
10
10
  // src/database/link-database.ts
11
11
  import { mkdir } from "fs/promises";
@@ -2068,6 +2068,22 @@ function parseLanguage(value) {
2068
2068
  }
2069
2069
 
2070
2070
  // src/hermes/config.ts
2071
+ function filterHermesModelConfigsByProviderKeys(result, hiddenProviderKeys) {
2072
+ if (hiddenProviderKeys.size === 0) {
2073
+ return result;
2074
+ }
2075
+ const models = result.models.filter(
2076
+ (model) => !hiddenProviderKeys.has(model.provider)
2077
+ );
2078
+ const compressionModel = result.compressionModel && !hiddenProviderKeys.has(result.compressionModel.provider) ? result.compressionModel : null;
2079
+ return {
2080
+ ...result,
2081
+ defaultModel: models.some((model) => model.isDefault) ? result.defaultModel : null,
2082
+ compressionModelId: compressionModel ? result.compressionModelId : null,
2083
+ compressionModel,
2084
+ models
2085
+ };
2086
+ }
2071
2087
  var DEFAULT_HERMES_API_SERVER_HOST = "127.0.0.1";
2072
2088
  var DEFAULT_HERMES_API_SERVER_PORT = 8642;
2073
2089
  var PROFILE_API_SERVER_PORT_START = DEFAULT_HERMES_API_SERVER_PORT + 1;
@@ -2628,7 +2644,12 @@ async function importHermesModelConfig(input, targetProfileName = "default", tar
2628
2644
  const sourceApiKey = sourceKeyEnv ? source.env[sourceKeyEnv]?.trim() : source.inlineApiKey;
2629
2645
  let keyEnv = existingKeyEnv;
2630
2646
  if (!keyEnv && !existingInlineApiKey) {
2631
- keyEnv = sourceKeyEnv ?? (sourceApiKey ? buildApiKeyEnvName(source.model.providerName, source.model.id) : void 0);
2647
+ keyEnv = (sourceKeyEnv && !reservedHermesEnvKeys(targetEnv, config).has(sourceKeyEnv) ? sourceKeyEnv : void 0) ?? (sourceApiKey ? await buildUniqueApiKeyEnvName(targetProfileName, config, targetEnv, {
2648
+ providerName: source.model.providerName,
2649
+ modelId: source.model.id,
2650
+ providerKey: targetProviderKey,
2651
+ apiKey: sourceApiKey
2652
+ }) : void 0);
2632
2653
  }
2633
2654
  if (sourceApiKey && keyEnv && !existingInlineApiKey && (!existingKeyEnv || !targetEnv[keyEnv]?.trim())) {
2634
2655
  await writeHermesEnvValue(targetProfileName, keyEnv, sourceApiKey);
@@ -2771,7 +2792,12 @@ async function saveHermesModelConfig(input, profileName = "default", configPath
2771
2792
  );
2772
2793
  const entry = toRecord(providers[providerKey]);
2773
2794
  const existingKeyEnv = readString2(entry.key_env) ?? parseEnvReference(readString2(entry.api_key));
2774
- const keyEnv = normalized.keyEnv ?? existingKeyEnv ?? (normalized.apiKey ? buildApiKeyEnvName(normalized.providerName, normalized.id) : void 0);
2795
+ const keyEnv = normalized.keyEnv ?? existingKeyEnv ?? (normalized.apiKey ? await buildUniqueApiKeyEnvName(profileName, config, void 0, {
2796
+ providerName: normalized.providerName,
2797
+ modelId: normalized.id,
2798
+ providerKey,
2799
+ apiKey: normalized.apiKey
2800
+ }) : void 0);
2775
2801
  if (normalized.apiKey && keyEnv) {
2776
2802
  await writeHermesEnvValue(profileName, keyEnv, normalized.apiKey);
2777
2803
  }
@@ -2963,22 +2989,28 @@ async function saveHermesModelProviderConfig(input, profileName = "default", con
2963
2989
  const { document, config, existingRaw } = await readHermesConfigDocument(configPath);
2964
2990
  const providers = ensureProvidersRecordWithLegacyMigration(config);
2965
2991
  const requestedProviderKey = input.providerKey?.trim();
2992
+ const apiKey = input.apiKey?.trim();
2966
2993
  if (requestedProviderKey && !Object.prototype.hasOwnProperty.call(providers, requestedProviderKey)) {
2967
2994
  throw new Error(`provider "${requestedProviderKey}" is not configured`);
2968
2995
  }
2969
- const providerKey = requestedProviderKey ?? findProviderConfigKeyByVisibleEndpoint(providers, {
2996
+ const canReuseVisibleEndpoint = Boolean(requestedProviderKey) || !apiKey || Boolean(input.keyEnv?.trim());
2997
+ const providerKey = requestedProviderKey ?? (canReuseVisibleEndpoint ? findProviderConfigKeyByVisibleEndpoint(providers, {
2970
2998
  providerName,
2971
2999
  baseUrl,
2972
3000
  apiMode,
2973
3001
  keyEnv: input.keyEnv
2974
- }) ?? uniqueProviderConfigKey(
3002
+ }) : null) ?? uniqueProviderConfigKey(
2975
3003
  providerConfigKeyBase(void 0, providerName, baseUrl),
2976
3004
  providers
2977
3005
  );
2978
3006
  const entry = toRecord(providers[providerKey]);
2979
3007
  const existingKeyEnv = readString2(entry.key_env) ?? parseEnvReference(readString2(entry.api_key));
2980
- const apiKey = input.apiKey?.trim();
2981
- const keyEnv = input.keyEnv?.trim() || existingKeyEnv || (apiKey ? buildApiKeyEnvName(providerName, "PROVIDER") : void 0);
3008
+ const keyEnv = input.keyEnv?.trim() || existingKeyEnv || (apiKey ? await buildUniqueApiKeyEnvName(profileName, config, void 0, {
3009
+ providerName,
3010
+ modelId: "PROVIDER",
3011
+ providerKey,
3012
+ apiKey
3013
+ }) : void 0);
2982
3014
  if (apiKey && keyEnv) {
2983
3015
  await writeHermesEnvValue(profileName, keyEnv, apiKey);
2984
3016
  }
@@ -3020,6 +3052,9 @@ async function deleteHermesModelProviderConfig(providerKey, profileName = "defau
3020
3052
  if (!Object.prototype.hasOwnProperty.call(providers, normalizedProviderKey)) {
3021
3053
  throw new Error(`provider "${normalizedProviderKey}" is not configured`);
3022
3054
  }
3055
+ const removedKeyEnv = providerCredentialEnvKey(
3056
+ toRecord(providers[normalizedProviderKey])
3057
+ );
3023
3058
  delete providers[normalizedProviderKey];
3024
3059
  const backupPath = await writeHermesConfigDocument({
3025
3060
  configPath,
@@ -3027,6 +3062,7 @@ async function deleteHermesModelProviderConfig(providerKey, profileName = "defau
3027
3062
  config,
3028
3063
  existingRaw
3029
3064
  });
3065
+ await deleteHermesEnvValueIfUnreferenced(profileName, config, removedKeyEnv);
3030
3066
  return {
3031
3067
  ...await listHermesModelConfigs(profileName, configPath),
3032
3068
  backupPath,
@@ -3146,6 +3182,7 @@ async function deleteHermesModelConfig(input, profileName = "default", configPat
3146
3182
  );
3147
3183
  }
3148
3184
  const providers = ensureProvidersRecordWithLegacyMigration(config);
3185
+ const removedProviderKeyEnvs = /* @__PURE__ */ new Set();
3149
3186
  for (const [key, rawEntry] of Object.entries(providers)) {
3150
3187
  const entry = providerConfigToLegacyEntry(key, toRecord(rawEntry));
3151
3188
  if (!entry || !providerEntryMatchesManagedModel(entry, deletingModel)) {
@@ -3155,6 +3192,10 @@ async function deleteHermesModelConfig(input, profileName = "default", configPat
3155
3192
  if (nextEntry) {
3156
3193
  providers[key] = nextEntry;
3157
3194
  } else {
3195
+ const keyEnv = providerCredentialEnvKey(toRecord(rawEntry));
3196
+ if (keyEnv) {
3197
+ removedProviderKeyEnvs.add(keyEnv);
3198
+ }
3158
3199
  delete providers[key];
3159
3200
  }
3160
3201
  }
@@ -3200,6 +3241,9 @@ async function deleteHermesModelConfig(input, profileName = "default", configPat
3200
3241
  config,
3201
3242
  existingRaw
3202
3243
  });
3244
+ for (const keyEnv of removedProviderKeyEnvs) {
3245
+ await deleteHermesEnvValueIfUnreferenced(profileName, config, keyEnv);
3246
+ }
3203
3247
  const listed = await listHermesModelConfigs(profileName, configPath);
3204
3248
  return {
3205
3249
  ...listed,
@@ -6694,10 +6738,72 @@ function parseEnvReference(value) {
6694
6738
  const match = /^\$\{(?<key>[A-Za-z_][A-Za-z0-9_]*)\}$/u.exec(value.trim());
6695
6739
  return match?.groups?.key;
6696
6740
  }
6741
+ function providerCredentialEnvKey(entry) {
6742
+ return readString2(entry.key_env) ?? parseEnvReference(readString2(entry.api_key));
6743
+ }
6697
6744
  function buildApiKeyEnvName(providerName, modelId) {
6698
6745
  const base = `${providerName || modelId}`.trim().toUpperCase().replace(/[^A-Z0-9]+/gu, "_").replace(/^_+|_+$/gu, "");
6699
6746
  return `HERMES_${base || "MODEL"}_API_KEY`;
6700
6747
  }
6748
+ async function buildUniqueApiKeyEnvName(profileName, config, env, input) {
6749
+ const envValues = env ?? await readHermesEnvFile(profileName);
6750
+ const configReferences = collectEnvReferenceKeys(config);
6751
+ const reserved = /* @__PURE__ */ new Set([...Object.keys(envValues), ...configReferences]);
6752
+ const candidates = [
6753
+ buildApiKeyEnvName(input.providerName, input.modelId),
6754
+ buildApiKeyEnvName(input.providerKey, input.modelId)
6755
+ ];
6756
+ for (const candidate of uniqueStrings(candidates)) {
6757
+ if (input.apiKey && !configReferences.has(candidate) && envValues[candidate]?.trim() === input.apiKey.trim()) {
6758
+ return candidate;
6759
+ }
6760
+ if (!reserved.has(candidate)) {
6761
+ return candidate;
6762
+ }
6763
+ }
6764
+ const base = buildApiKeyEnvName(input.providerKey, input.modelId);
6765
+ let index = 2;
6766
+ while (reserved.has(apiKeyEnvNameWithSuffix(base, index))) {
6767
+ index += 1;
6768
+ }
6769
+ return apiKeyEnvNameWithSuffix(base, index);
6770
+ }
6771
+ function reservedHermesEnvKeys(env, config) {
6772
+ return /* @__PURE__ */ new Set([...Object.keys(env), ...collectEnvReferenceKeys(config)]);
6773
+ }
6774
+ function collectEnvReferenceKeys(value, keys = /* @__PURE__ */ new Set()) {
6775
+ if (typeof value === "string") {
6776
+ const key = parseEnvReference(value);
6777
+ if (key) {
6778
+ keys.add(key);
6779
+ }
6780
+ return keys;
6781
+ }
6782
+ if (Array.isArray(value)) {
6783
+ for (const item of value) {
6784
+ collectEnvReferenceKeys(item, keys);
6785
+ }
6786
+ return keys;
6787
+ }
6788
+ if (value && typeof value === "object") {
6789
+ for (const item of Object.values(value)) {
6790
+ collectEnvReferenceKeys(item, keys);
6791
+ }
6792
+ }
6793
+ return keys;
6794
+ }
6795
+ function uniqueStrings(values) {
6796
+ return [...new Set(values.filter((value) => value.trim()))];
6797
+ }
6798
+ function apiKeyEnvNameWithSuffix(base, index) {
6799
+ return base.endsWith("_API_KEY") ? `${base.slice(0, -"_API_KEY".length)}_${index}_API_KEY` : `${base}_${index}`;
6800
+ }
6801
+ async function deleteHermesEnvValueIfUnreferenced(profileName, config, key) {
6802
+ if (!key || collectEnvReferenceKeys(config).has(key)) {
6803
+ return;
6804
+ }
6805
+ await deleteHermesEnvValue(profileName, key);
6806
+ }
6701
6807
  function formatEnvValue(value) {
6702
6808
  return `"${value.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"').replace(/\n/gu, "\\n")}"`;
6703
6809
  }
@@ -7109,7 +7215,7 @@ function isConversationMissingError(error) {
7109
7215
  }
7110
7216
 
7111
7217
  // src/constants.ts
7112
- var LINK_VERSION = "0.7.9";
7218
+ var LINK_VERSION = "0.8.0";
7113
7219
  var LINK_COMMAND = "hermeslink";
7114
7220
  var LINK_DEFAULT_PORT = 52379;
7115
7221
  var LINK_RUNTIME_DIR_NAME = ".hermeslink";
@@ -12697,9 +12803,115 @@ function isNodeError6(error, code) {
12697
12803
  }
12698
12804
 
12699
12805
  // src/conversations/profile-runtime.ts
12806
+ import { stat as stat8 } from "fs/promises";
12807
+
12808
+ // src/hermes/model-provider-disconnects.ts
12700
12809
  import { stat as stat7 } from "fs/promises";
12701
- var PROFILE_NAME_PATTERN3 = /^[a-zA-Z0-9._-]{1,64}$/u;
12810
+ import os5 from "os";
12811
+ import path12 from "path";
12812
+ async function markAuthBackedModelProviderDisconnected(paths, profileName, providerKey) {
12813
+ const profileKey = normalizeProfileName3(profileName);
12814
+ const provider = providerKey.trim();
12815
+ if (!provider) {
12816
+ return;
12817
+ }
12818
+ const credentialMtimeMs = await readProviderCredentialMtimeMs(
12819
+ profileKey,
12820
+ provider
12821
+ );
12822
+ await updateJsonFile(
12823
+ disconnectedProvidersPath(paths),
12824
+ (current) => {
12825
+ const store = normalizeStore(current);
12826
+ const profiles = { ...store.profiles ?? {} };
12827
+ const profile = profiles[profileKey] ?? {};
12828
+ profiles[profileKey] = {
12829
+ ...profile,
12830
+ providers: {
12831
+ ...profile.providers ?? {},
12832
+ [provider]: {
12833
+ disconnectedAt: (/* @__PURE__ */ new Date()).toISOString(),
12834
+ credentialMtimeMs
12835
+ }
12836
+ }
12837
+ };
12838
+ return { ...store, profiles };
12839
+ }
12840
+ );
12841
+ }
12842
+ async function readDisconnectedAuthBackedModelProviderKeys(paths, profileName) {
12843
+ const store = normalizeStore(
12844
+ await readJsonFile(
12845
+ disconnectedProvidersPath(paths)
12846
+ ).catch(() => null)
12847
+ );
12848
+ const profile = store.profiles?.[normalizeProfileName3(profileName)];
12849
+ const providers = profile?.providers ?? {};
12850
+ const active = /* @__PURE__ */ new Set();
12851
+ for (const [provider, entry] of Object.entries(providers)) {
12852
+ if (await isDisconnectMarkerActive(profileName, provider, entry)) {
12853
+ active.add(provider);
12854
+ }
12855
+ }
12856
+ return active;
12857
+ }
12858
+ async function isDisconnectMarkerActive(profileName, providerKey, entry) {
12859
+ const currentMtimeMs = await readProviderCredentialMtimeMs(
12860
+ profileName,
12861
+ providerKey
12862
+ );
12863
+ if (entry.credentialMtimeMs === null || !Number.isFinite(entry.credentialMtimeMs)) {
12864
+ return currentMtimeMs === null;
12865
+ }
12866
+ if (currentMtimeMs === null || !Number.isFinite(currentMtimeMs)) {
12867
+ return true;
12868
+ }
12869
+ return currentMtimeMs <= entry.credentialMtimeMs + 1e3;
12870
+ }
12871
+ async function readProviderCredentialMtimeMs(profileName, providerKey) {
12872
+ for (const filePath of credentialFilesForProvider(profileName, providerKey)) {
12873
+ const stats = await stat7(filePath).catch((error) => {
12874
+ if (isNodeError7(error, "ENOENT")) {
12875
+ return null;
12876
+ }
12877
+ throw error;
12878
+ });
12879
+ if (stats) {
12880
+ return stats.mtimeMs;
12881
+ }
12882
+ }
12883
+ return null;
12884
+ }
12885
+ function credentialFilesForProvider(profileName, providerKey) {
12886
+ const profileDir = resolveHermesProfileDir(normalizeProfileName3(profileName));
12887
+ switch (providerKey) {
12888
+ case "qwen-oauth":
12889
+ return [path12.join(os5.homedir(), ".qwen", "oauth_creds.json")];
12890
+ case "google-gemini-cli":
12891
+ return [path12.join(profileDir, "auth", "google_oauth.json")];
12892
+ default:
12893
+ return [path12.join(profileDir, "auth.json")];
12894
+ }
12895
+ }
12896
+ function disconnectedProvidersPath(paths) {
12897
+ return path12.join(paths.homeDir, "model-provider-disconnects.json");
12898
+ }
12899
+ function normalizeStore(value) {
12900
+ return {
12901
+ version: 1,
12902
+ profiles: value?.profiles && typeof value.profiles === "object" ? value.profiles : {}
12903
+ };
12904
+ }
12702
12905
  function normalizeProfileName3(profileName) {
12906
+ return profileName.trim() || "default";
12907
+ }
12908
+ function isNodeError7(error, code) {
12909
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
12910
+ }
12911
+
12912
+ // src/conversations/profile-runtime.ts
12913
+ var PROFILE_NAME_PATTERN3 = /^[a-zA-Z0-9._-]{1,64}$/u;
12914
+ function normalizeProfileName4(profileName) {
12703
12915
  const value = profileName?.trim() || "default";
12704
12916
  if (!PROFILE_NAME_PATTERN3.test(value)) {
12705
12917
  throw new Error("invalid profile name");
@@ -12710,7 +12922,7 @@ function fallbackProfileDisplayName(profileName) {
12710
12922
  return profileName === "default" ? "Default" : profileName;
12711
12923
  }
12712
12924
  async function resolveConversationProfileTarget(paths, profileName) {
12713
- const normalized = normalizeProfileName3(profileName);
12925
+ const normalized = normalizeProfileName4(profileName);
12714
12926
  const identity = await ensureProfileIdentityForRuntime(paths, normalized);
12715
12927
  return {
12716
12928
  profileUid: identity.profileUid,
@@ -12720,7 +12932,7 @@ async function resolveConversationProfileTarget(paths, profileName) {
12720
12932
  };
12721
12933
  }
12722
12934
  async function readCurrentConversationRuntime(paths, manifest) {
12723
- const profileName = normalizeProfileName3(
12935
+ const profileName = normalizeProfileName4(
12724
12936
  manifest?.profile_name_snapshot ?? manifest?.profile
12725
12937
  );
12726
12938
  const identity = await ensureProfileIdentityForRuntime(paths, profileName);
@@ -12732,7 +12944,7 @@ async function readCurrentConversationRuntime(paths, manifest) {
12732
12944
  contextLength: void 0,
12733
12945
  reasoningEffort: void 0
12734
12946
  }));
12735
- const configuredDefaultModel = modelConfig.model ? await findConfiguredModel(profileName, modelConfig.model) : void 0;
12947
+ const configuredDefaultModel = modelConfig.model ? await findConfiguredModel(profileName, modelConfig.model, {}, paths) : void 0;
12736
12948
  const override = manifest?.model_override;
12737
12949
  const overrideModel = override?.id?.trim();
12738
12950
  const overrideProvider = override?.provider_name?.trim() || override?.provider?.trim();
@@ -12760,7 +12972,7 @@ async function readProfilePresentation(paths, profileName) {
12760
12972
  };
12761
12973
  }
12762
12974
  async function readConversationProfileSummary(paths, manifest) {
12763
- const profileName = normalizeProfileName3(
12975
+ const profileName = normalizeProfileName4(
12764
12976
  manifest.profile_name_snapshot ?? manifest.profile
12765
12977
  );
12766
12978
  const presentation = await readProfilePresentation(paths, profileName).catch(
@@ -12831,8 +13043,8 @@ async function buildConversationRuntimeMetadata(paths, manifest, snapshot) {
12831
13043
  async function ensureProfileIdentityForRuntime(paths, profileName) {
12832
13044
  const profilePath = resolveHermesProfileDir(profileName);
12833
13045
  if (profileName !== "default") {
12834
- const exists = await stat7(profilePath).then((value) => value.isDirectory()).catch((error) => {
12835
- if (isNodeError7(error, "ENOENT")) {
13046
+ const exists = await stat8(profilePath).then((value) => value.isDirectory()).catch((error) => {
13047
+ if (isNodeError8(error, "ENOENT")) {
12836
13048
  return false;
12837
13049
  }
12838
13050
  throw error;
@@ -12850,12 +13062,18 @@ async function ensureProfileIdentityForRuntime(paths, profileName) {
12850
13062
  profilePath
12851
13063
  });
12852
13064
  }
12853
- async function findConfiguredModel(profileName, modelId, selector = {}) {
13065
+ async function findConfiguredModel(profileName, modelId, selector = {}, paths) {
12854
13066
  const result = await listHermesModelConfigs(profileName).catch(() => null);
13067
+ const disconnectedProviders = paths ? await readDisconnectedAuthBackedModelProviderKeys(paths, profileName).catch(
13068
+ () => /* @__PURE__ */ new Set()
13069
+ ) : /* @__PURE__ */ new Set();
12855
13070
  return result?.models.find((model) => {
12856
13071
  if (model.id !== modelId) {
12857
13072
  return false;
12858
13073
  }
13074
+ if (disconnectedProviders.has(model.provider)) {
13075
+ return false;
13076
+ }
12859
13077
  if (selector.provider && model.provider !== selector.provider) {
12860
13078
  return false;
12861
13079
  }
@@ -12875,7 +13093,7 @@ function displayNameForProfile(profile) {
12875
13093
  const custom = profile.displayName?.trim();
12876
13094
  return custom || fallbackProfileDisplayName(profile.profileName);
12877
13095
  }
12878
- function isNodeError7(error, code) {
13096
+ function isNodeError8(error, code) {
12879
13097
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
12880
13098
  }
12881
13099
 
@@ -13295,9 +13513,15 @@ var ConversationCommandHandlers = class {
13295
13513
  }
13296
13514
  async modelStatusMessage(manifest, language = "zh-CN") {
13297
13515
  const runtime = await readCurrentConversationRuntime(this.deps.paths, manifest);
13298
- const configured = await listHermesModelConfigs(runtime.profileName).catch(
13299
- () => null
13300
- );
13516
+ const configured = await listHermesModelConfigs(runtime.profileName).then(
13517
+ async (result) => filterHermesModelConfigsByProviderKeys(
13518
+ result,
13519
+ await readDisconnectedAuthBackedModelProviderKeys(
13520
+ this.deps.paths,
13521
+ runtime.profileName
13522
+ )
13523
+ )
13524
+ ).catch(() => null);
13301
13525
  const lines = [
13302
13526
  commandText(
13303
13527
  language,
@@ -13408,7 +13632,12 @@ var ConversationCommandHandlers = class {
13408
13632
  response: await this.modelStatusMessage(input.manifest, input.language)
13409
13633
  });
13410
13634
  }
13411
- const configuredModel = await findConfiguredModel(runtime.profileName, model);
13635
+ const configuredModel = await findConfiguredModel(
13636
+ runtime.profileName,
13637
+ model,
13638
+ {},
13639
+ this.deps.paths
13640
+ );
13412
13641
  if (!configuredModel) {
13413
13642
  return this.deps.appendCommandResult({
13414
13643
  manifest: input.manifest,
@@ -13606,7 +13835,9 @@ var ConversationCommandHandlers = class {
13606
13835
  const reasoningEffort = arg;
13607
13836
  const configuredModel = await findConfiguredModel(
13608
13837
  runtime.profileName,
13609
- runtime.model
13838
+ runtime.model,
13839
+ {},
13840
+ this.deps.paths
13610
13841
  );
13611
13842
  if (!isReasoningEffortSupported(configuredModel, reasoningEffort)) {
13612
13843
  return this.deps.appendCommandResult({
@@ -13794,7 +14025,7 @@ function commandText(language, zh, en) {
13794
14025
 
13795
14026
  // src/conversations/delivery-staging.ts
13796
14027
  import { mkdir as mkdir7, rm as rm4 } from "fs/promises";
13797
- import path12 from "path";
14028
+ import path13 from "path";
13798
14029
  async function prepareDeliveryStagingRunDir(paths, conversationId, runId) {
13799
14030
  const directory = deliveryStagingRunDir(paths, conversationId, runId);
13800
14031
  await mkdir7(directory, { recursive: true, mode: 448 });
@@ -13807,14 +14038,14 @@ async function removeConversationDeliveryStaging(paths, conversationId) {
13807
14038
  });
13808
14039
  }
13809
14040
  function deliveryStagingRunDir(paths, conversationId, runId) {
13810
- return path12.join(
14041
+ return path13.join(
13811
14042
  deliveryStagingConversationDir(paths, conversationId),
13812
14043
  safePathSegment(runId, "run")
13813
14044
  );
13814
14045
  }
13815
14046
  function deliveryStagingConversationDir(paths, conversationId) {
13816
14047
  assertValidConversationId(conversationId);
13817
- return path12.join(paths.conversationsDir, conversationId, "delivery-staging");
14048
+ return path13.join(paths.conversationsDir, conversationId, "delivery-staging");
13818
14049
  }
13819
14050
  function safePathSegment(value, fallback) {
13820
14051
  const safe = value.trim().replaceAll(/[^a-zA-Z0-9._-]/gu, "_");
@@ -13824,7 +14055,7 @@ function safePathSegment(value, fallback) {
13824
14055
  // src/conversations/conversation-archive-plans.ts
13825
14056
  import { randomUUID as randomUUID7 } from "crypto";
13826
14057
  import { mkdir as mkdir8 } from "fs/promises";
13827
- import path13 from "path";
14058
+ import path14 from "path";
13828
14059
  var PLAN_ID_PATTERN = /^archive_[a-f0-9]{32}$/u;
13829
14060
  var ConversationArchivePlanStore = class {
13830
14061
  constructor(paths) {
@@ -13867,10 +14098,10 @@ var ConversationArchivePlanStore = class {
13867
14098
  await writeJsonFile(this.planPath(normalizedPlanId), plan);
13868
14099
  }
13869
14100
  plansDir() {
13870
- return path13.join(this.paths.indexesDir, "conversation-archive-plans");
14101
+ return path14.join(this.paths.indexesDir, "conversation-archive-plans");
13871
14102
  }
13872
14103
  planPath(planId) {
13873
- return path13.join(this.plansDir(), `${planId}.json`);
14104
+ return path14.join(this.plansDir(), `${planId}.json`);
13874
14105
  }
13875
14106
  };
13876
14107
  function normalizePlanId(planId) {
@@ -13888,7 +14119,7 @@ function normalizePlanId(planId) {
13888
14119
  // src/conversations/conversation-clear-plans.ts
13889
14120
  import { randomUUID as randomUUID8 } from "crypto";
13890
14121
  import { mkdir as mkdir9 } from "fs/promises";
13891
- import path14 from "path";
14122
+ import path15 from "path";
13892
14123
  var PLAN_ID_PATTERN2 = /^clear_[a-f0-9]{32}$/u;
13893
14124
  var ConversationClearPlanStore = class {
13894
14125
  constructor(paths) {
@@ -13932,10 +14163,10 @@ var ConversationClearPlanStore = class {
13932
14163
  await writeJsonFile(this.planPath(normalizedPlanId), plan);
13933
14164
  }
13934
14165
  plansDir() {
13935
- return path14.join(this.paths.indexesDir, "conversation-clear-plans");
14166
+ return path15.join(this.paths.indexesDir, "conversation-clear-plans");
13936
14167
  }
13937
14168
  planPath(planId) {
13938
- return path14.join(this.plansDir(), `${planId}.json`);
14169
+ return path15.join(this.plansDir(), `${planId}.json`);
13939
14170
  }
13940
14171
  };
13941
14172
  function normalizePlanId2(planId) {
@@ -14789,20 +15020,20 @@ function readAttachmentWaveform(attachment) {
14789
15020
  }
14790
15021
 
14791
15022
  // src/hermes/session-title.ts
14792
- import { stat as stat8 } from "fs/promises";
14793
- import path15 from "path";
15023
+ import { stat as stat9 } from "fs/promises";
15024
+ import path16 from "path";
14794
15025
  async function readHermesSessionTitle(sessionId, paths, profileName) {
14795
15026
  const trimmedSessionId = sessionId.trim();
14796
15027
  if (!trimmedSessionId) {
14797
15028
  return void 0;
14798
15029
  }
14799
15030
  const resolvedProfileName = isValidProfileName(profileName) ? profileName : "default";
14800
- const dbPath = path15.join(
15031
+ const dbPath = path16.join(
14801
15032
  resolveHermesProfileDir(resolvedProfileName),
14802
15033
  "state.db"
14803
15034
  );
14804
- const exists = await stat8(dbPath).then((value) => value.isFile()).catch((error) => {
14805
- if (isNodeError8(error, "ENOENT")) {
15035
+ const exists = await stat9(dbPath).then((value) => value.isFile()).catch((error) => {
15036
+ if (isNodeError9(error, "ENOENT")) {
14806
15037
  return false;
14807
15038
  }
14808
15039
  throw error;
@@ -14818,12 +15049,12 @@ async function readHermesCompressionTip(sessionId, paths, profileName) {
14818
15049
  return void 0;
14819
15050
  }
14820
15051
  const resolvedProfileName = isValidProfileName(profileName) ? profileName : "default";
14821
- const dbPath = path15.join(
15052
+ const dbPath = path16.join(
14822
15053
  resolveHermesProfileDir(resolvedProfileName),
14823
15054
  "state.db"
14824
15055
  );
14825
- const exists = await stat8(dbPath).then((value) => value.isFile()).catch((error) => {
14826
- if (isNodeError8(error, "ENOENT")) {
15056
+ const exists = await stat9(dbPath).then((value) => value.isFile()).catch((error) => {
15057
+ if (isNodeError9(error, "ENOENT")) {
14827
15058
  return false;
14828
15059
  }
14829
15060
  throw error;
@@ -14886,7 +15117,7 @@ function readCompressionTipFromStateDb(dbPath, sessionId) {
14886
15117
  db?.close();
14887
15118
  }
14888
15119
  }
14889
- function isNodeError8(error, code) {
15120
+ function isNodeError9(error, code) {
14890
15121
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
14891
15122
  }
14892
15123
  function isValidProfileName(value) {
@@ -15250,8 +15481,8 @@ function stripCompressionTitleSuffix(value) {
15250
15481
  }
15251
15482
 
15252
15483
  // src/conversations/history-builder.ts
15253
- import { readFile as readFile8, stat as stat9 } from "fs/promises";
15254
- import path16 from "path";
15484
+ import { readFile as readFile8, stat as stat10 } from "fs/promises";
15485
+ import path17 from "path";
15255
15486
  var HISTORY_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
15256
15487
  var HERMES_HISTORY_COLUMNS = [
15257
15488
  "role",
@@ -15323,13 +15554,13 @@ async function readHermesTranscriptHistory(sessionId, profileName) {
15323
15554
  }
15324
15555
  const normalizedProfileName = isValidProfileName2(profileName) ? profileName : "default";
15325
15556
  const profileDir = resolveHermesProfileDir(normalizedProfileName);
15326
- const dbPath = path16.join(profileDir, "state.db");
15557
+ const dbPath = path17.join(profileDir, "state.db");
15327
15558
  const sessionsDirConfig = await readHermesSessionsDir(normalizedProfileName).then((value) => ({
15328
15559
  sessionsDir: value.sessionsDir,
15329
15560
  configured: value.configured,
15330
15561
  configError: false
15331
15562
  })).catch(() => ({
15332
- sessionsDir: path16.join(profileDir, "sessions"),
15563
+ sessionsDir: path17.join(profileDir, "sessions"),
15333
15564
  configured: false,
15334
15565
  configError: true
15335
15566
  }));
@@ -15374,8 +15605,8 @@ async function readHermesTranscriptHistory(sessionId, profileName) {
15374
15605
  };
15375
15606
  }
15376
15607
  async function readHermesStateDbHistory(dbPath, sessionId) {
15377
- const exists = await stat9(dbPath).then((value) => value.isFile()).catch((error) => {
15378
- if (isNodeError9(error, "ENOENT")) {
15608
+ const exists = await stat10(dbPath).then((value) => value.isFile()).catch((error) => {
15609
+ if (isNodeError10(error, "ENOENT")) {
15379
15610
  return false;
15380
15611
  }
15381
15612
  throw error;
@@ -15492,7 +15723,7 @@ async function readHermesTranscriptFilesHistory(sessionsDir, sessionId) {
15492
15723
  async function readFirstExistingFile(paths) {
15493
15724
  for (const filePath of paths) {
15494
15725
  const raw = await readFile8(filePath, "utf8").catch((error) => {
15495
- if (isNodeError9(error, "ENOENT")) {
15726
+ if (isNodeError10(error, "ENOENT")) {
15496
15727
  return null;
15497
15728
  }
15498
15729
  throw error;
@@ -15505,8 +15736,8 @@ async function readFirstExistingFile(paths) {
15505
15736
  }
15506
15737
  function candidateTranscriptPaths(sessionsDir, sessionId, extension) {
15507
15738
  return [
15508
- path16.join(sessionsDir, `session_${sessionId}.${extension}`),
15509
- path16.join(sessionsDir, `${sessionId}.${extension}`)
15739
+ path17.join(sessionsDir, `session_${sessionId}.${extension}`),
15740
+ path17.join(sessionsDir, `${sessionId}.${extension}`)
15510
15741
  ];
15511
15742
  }
15512
15743
  function readHistoryRows(dbPath, sessionId) {
@@ -15706,7 +15937,7 @@ function readTableColumns(db, table) {
15706
15937
  db.prepare(`PRAGMA table_info(${table})`).all().map((row) => row.name).filter((name) => typeof name === "string")
15707
15938
  );
15708
15939
  }
15709
- function isNodeError9(error, code) {
15940
+ function isNodeError10(error, code) {
15710
15941
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
15711
15942
  }
15712
15943
  function isValidProfileName2(value) {
@@ -17361,13 +17592,13 @@ var ConversationOrchestrationCoordinator = class {
17361
17592
  };
17362
17593
  }
17363
17594
  async ensureManifestProfile(manifest, requestedProfileName) {
17364
- const existingProfileName = normalizeProfileName3(
17595
+ const existingProfileName = normalizeProfileName4(
17365
17596
  manifest.profile_name_snapshot ?? manifest.profile
17366
17597
  );
17367
17598
  const hasExistingProfile = Boolean(
17368
17599
  manifest.profile_name_snapshot ?? manifest.profile
17369
17600
  );
17370
- const nextProfileName = requestedProfileName ? normalizeProfileName3(requestedProfileName) : existingProfileName;
17601
+ const nextProfileName = requestedProfileName ? normalizeProfileName4(requestedProfileName) : existingProfileName;
17371
17602
  if ((!requestedProfileName || nextProfileName === existingProfileName) && hasExistingProfile && manifest.profile_uid) {
17372
17603
  return manifest;
17373
17604
  }
@@ -18697,7 +18928,7 @@ var ContextCompressionCoordinator = class {
18697
18928
  continue;
18698
18929
  }
18699
18930
  if (snapshot.runs.some(
18700
- (run) => run.status === "running" && run.kind !== "command" && normalizeProfileName4(run.profile_name_snapshot ?? run.profile) === profileName
18931
+ (run) => run.status === "running" && run.kind !== "command" && normalizeProfileName5(run.profile_name_snapshot ?? run.profile) === profileName
18701
18932
  )) {
18702
18933
  return true;
18703
18934
  }
@@ -18707,7 +18938,7 @@ var ContextCompressionCoordinator = class {
18707
18938
  async profileNameForRun(conversationId, runId) {
18708
18939
  const snapshot = await this.deps.store.readSnapshot(conversationId).catch(() => null);
18709
18940
  const run = snapshot?.runs.find((item) => item.id === runId);
18710
- return normalizeProfileName4(run?.profile_name_snapshot ?? run?.profile);
18941
+ return normalizeProfileName5(run?.profile_name_snapshot ?? run?.profile);
18711
18942
  }
18712
18943
  };
18713
18944
  function createContextCompressionMarker(input) {
@@ -18908,7 +19139,7 @@ function normalizeFocus(value) {
18908
19139
  function normalizeLanguage2(value) {
18909
19140
  return value === "en" ? "en" : "zh-CN";
18910
19141
  }
18911
- function normalizeProfileName4(value) {
19142
+ function normalizeProfileName5(value) {
18912
19143
  return value?.trim() || "default";
18913
19144
  }
18914
19145
  function localizedText(language, zh, en) {
@@ -18936,7 +19167,7 @@ import {
18936
19167
  rm as rm5,
18937
19168
  writeFile as writeFile2
18938
19169
  } from "fs/promises";
18939
- import path17 from "path";
19170
+ import path18 from "path";
18940
19171
  var ConversationStore = class {
18941
19172
  constructor(paths) {
18942
19173
  this.paths = paths;
@@ -18950,7 +19181,7 @@ var ConversationStore = class {
18950
19181
  const entries = await readdir5(this.paths.conversationsDir, {
18951
19182
  withFileTypes: true
18952
19183
  }).catch((error) => {
18953
- if (isNodeError10(error, "ENOENT")) {
19184
+ if (isNodeError11(error, "ENOENT")) {
18954
19185
  return [];
18955
19186
  }
18956
19187
  throw error;
@@ -19019,7 +19250,7 @@ var ConversationStore = class {
19019
19250
  await this.readManifest(conversationId);
19020
19251
  const raw = await readFile9(this.eventsPath(conversationId), "utf8").catch(
19021
19252
  (error) => {
19022
- if (isNodeError10(error, "ENOENT")) {
19253
+ if (isNodeError11(error, "ENOENT")) {
19023
19254
  return "";
19024
19255
  }
19025
19256
  throw error;
@@ -19067,40 +19298,40 @@ var ConversationStore = class {
19067
19298
  return manifest != null && manifest.status !== "deleted_soft";
19068
19299
  }
19069
19300
  removeConversationAttachments(conversationId) {
19070
- return rm5(path17.join(this.conversationDir(conversationId), "attachments"), {
19301
+ return rm5(path18.join(this.conversationDir(conversationId), "attachments"), {
19071
19302
  recursive: true,
19072
19303
  force: true
19073
19304
  });
19074
19305
  }
19075
19306
  conversationDir(conversationId) {
19076
19307
  assertValidConversationId(conversationId);
19077
- return path17.join(this.paths.conversationsDir, conversationId);
19308
+ return path18.join(this.paths.conversationsDir, conversationId);
19078
19309
  }
19079
19310
  manifestPath(conversationId) {
19080
- return path17.join(this.conversationDir(conversationId), "manifest.json");
19311
+ return path18.join(this.conversationDir(conversationId), "manifest.json");
19081
19312
  }
19082
19313
  snapshotPath(conversationId) {
19083
- return path17.join(this.conversationDir(conversationId), "snapshot.json");
19314
+ return path18.join(this.conversationDir(conversationId), "snapshot.json");
19084
19315
  }
19085
19316
  eventsPath(conversationId) {
19086
- return path17.join(this.conversationDir(conversationId), "events.ndjson");
19317
+ return path18.join(this.conversationDir(conversationId), "events.ndjson");
19087
19318
  }
19088
19319
  };
19089
19320
  function createEmptySnapshot2() {
19090
19321
  return { schema_version: 1, messages: [], runs: [] };
19091
19322
  }
19092
- function isNodeError10(error, code) {
19323
+ function isNodeError11(error, code) {
19093
19324
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
19094
19325
  }
19095
19326
 
19096
19327
  // src/conversations/hermes-session-sync.ts
19097
19328
  import { randomUUID as randomUUID11 } from "crypto";
19098
- import { readdir as readdir7, readFile as readFile11, stat as stat11 } from "fs/promises";
19099
- import path19 from "path";
19329
+ import { readdir as readdir7, readFile as readFile11, stat as stat12 } from "fs/promises";
19330
+ import path20 from "path";
19100
19331
 
19101
19332
  // src/conversations/delivery-import.ts
19102
- import { lstat as lstat2, readFile as readFile10, readdir as readdir6, stat as stat10 } from "fs/promises";
19103
- import path18 from "path";
19333
+ import { lstat as lstat2, readFile as readFile10, readdir as readdir6, stat as stat11 } from "fs/promises";
19334
+ import path19 from "path";
19104
19335
  var MAX_IMPORTED_BLOB_BYTES = 100 * 1024 * 1024;
19105
19336
  var MAX_MEDIA_IMPORT_FAILURES = 20;
19106
19337
  var MAX_DELIVERY_FILES = 50;
@@ -19171,16 +19402,16 @@ var SUPPORTED_DELIVERY_EXTENSIONS = /* @__PURE__ */ new Set([
19171
19402
  ".m4a"
19172
19403
  ]);
19173
19404
  function resolveDeliveryStagingTarget(paths, stagingDir) {
19174
- const resolvedDir = path18.resolve(stagingDir);
19175
- const relative = path18.relative(path18.resolve(paths.conversationsDir), resolvedDir);
19176
- if (!relative || relative.startsWith("..") || path18.isAbsolute(relative)) {
19405
+ const resolvedDir = path19.resolve(stagingDir);
19406
+ const relative = path19.relative(path19.resolve(paths.conversationsDir), resolvedDir);
19407
+ if (!relative || relative.startsWith("..") || path19.isAbsolute(relative)) {
19177
19408
  throw new LinkHttpError(
19178
19409
  400,
19179
19410
  "delivery_staging_invalid",
19180
19411
  "delivery staging directory must be inside Hermes Link conversations"
19181
19412
  );
19182
19413
  }
19183
- const segments = relative.split(path18.sep);
19414
+ const segments = relative.split(path19.sep);
19184
19415
  if (segments.length !== 3 || segments[1] !== DELIVERY_STAGING_SEGMENT || !segments[0] || !segments[2]) {
19185
19416
  throw new LinkHttpError(
19186
19417
  400,
@@ -19196,7 +19427,7 @@ function resolveDeliveryStagingTarget(paths, stagingDir) {
19196
19427
  }
19197
19428
  async function collectStagedDeliveryReferences(stagingDir) {
19198
19429
  const directoryStat = await lstat2(stagingDir).catch((error) => {
19199
- if (isNodeError11(error, "ENOENT")) {
19430
+ if (isNodeError12(error, "ENOENT")) {
19200
19431
  throw new LinkHttpError(
19201
19432
  404,
19202
19433
  "delivery_staging_not_found",
@@ -19216,7 +19447,7 @@ async function collectStagedDeliveryReferences(stagingDir) {
19216
19447
  return entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).filter((entry) => isSupportedDeliveryFilename(entry.name)).sort(
19217
19448
  (left, right) => left.name.localeCompare(right.name, "en", { numeric: true })
19218
19449
  ).slice(0, MAX_DELIVERY_FILES).map((entry) => {
19219
- const sourcePath = path18.join(stagingDir, entry.name);
19450
+ const sourcePath = path19.join(stagingDir, entry.name);
19220
19451
  const mime = inferMimeType(sourcePath);
19221
19452
  return {
19222
19453
  path: sourcePath,
@@ -19376,8 +19607,8 @@ function emptyImportResult(input) {
19376
19607
  }
19377
19608
  async function writeBlobFromFile(deps, conversationId, source) {
19378
19609
  const sourcePath = resolveMediaSourcePath(source.path);
19379
- const fileStat = await stat10(sourcePath).catch((error) => {
19380
- if (isNodeError11(error, "ENOENT")) {
19610
+ const fileStat = await stat11(sourcePath).catch((error) => {
19611
+ if (isNodeError12(error, "ENOENT")) {
19381
19612
  throw new LinkHttpError(
19382
19613
  404,
19383
19614
  "media_source_not_found",
@@ -19402,7 +19633,7 @@ async function writeBlobFromFile(deps, conversationId, source) {
19402
19633
  }
19403
19634
  return deps.writeBlob(conversationId, {
19404
19635
  bytes: await readFile10(sourcePath),
19405
- filename: path18.basename(sourcePath),
19636
+ filename: path19.basename(sourcePath),
19406
19637
  mime: source.mime ?? inferMimeType(sourcePath)
19407
19638
  });
19408
19639
  }
@@ -19411,11 +19642,11 @@ function describeMediaImportFailure(reference, sourceKey, error) {
19411
19642
  key: sourceKey,
19412
19643
  filename: sanitizeFilename(reference.path, "attachment"),
19413
19644
  reason: error instanceof Error ? error.message : String(error),
19414
- ...isNodeError11(error) && error.code ? { code: error.code } : {}
19645
+ ...isNodeError12(error) && error.code ? { code: error.code } : {}
19415
19646
  };
19416
19647
  }
19417
19648
  function isSupportedDeliveryFilename(filename) {
19418
- return SUPPORTED_DELIVERY_EXTENSIONS.has(path18.extname(filename).toLowerCase());
19649
+ return SUPPORTED_DELIVERY_EXTENSIONS.has(path19.extname(filename).toLowerCase());
19419
19650
  }
19420
19651
  function readString10(payload, key) {
19421
19652
  const value = payload[key];
@@ -19424,7 +19655,7 @@ function readString10(payload, key) {
19424
19655
  function toRecord10(value) {
19425
19656
  return typeof value === "object" && value !== null ? value : {};
19426
19657
  }
19427
- function isNodeError11(error, code) {
19658
+ function isNodeError12(error, code) {
19428
19659
  return typeof error === "object" && error !== null && "code" in error && (code === void 0 || error.code === code);
19429
19660
  }
19430
19661
 
@@ -19473,7 +19704,7 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
19473
19704
  const candidates = [];
19474
19705
  for (const profileName of profileNames) {
19475
19706
  const profileDir = resolveHermesProfileDir(profileName);
19476
- const dbPath = path19.join(profileDir, "state.db");
19707
+ const dbPath = path20.join(profileDir, "state.db");
19477
19708
  const sessions = await listProfileSessions(dbPath).catch((error) => {
19478
19709
  result.errors.push({
19479
19710
  profile: profileName,
@@ -19589,7 +19820,7 @@ async function syncHermesCronSessionIntoConversations(paths, logger, input) {
19589
19820
  const knownHermesSessions = await readKnownHermesSessions(store);
19590
19821
  const profileName = input.profileName.trim() || DEFAULT_PROFILE_NAME;
19591
19822
  const profileDir = resolveHermesProfileDir(profileName);
19592
- const dbPath = path19.join(profileDir, "state.db");
19823
+ const dbPath = path20.join(profileDir, "state.db");
19593
19824
  const sessions = await listProfileSessionsByIdPrefix(
19594
19825
  dbPath,
19595
19826
  `cron_${jobId}_`
@@ -20604,7 +20835,7 @@ async function discoverHermesProfileNames() {
20604
20835
  const profilesDir = resolveHermesProfilesDir();
20605
20836
  const entries = await readdir7(profilesDir, { withFileTypes: true }).catch(
20606
20837
  (error) => {
20607
- if (isNodeError12(error, "ENOENT")) {
20838
+ if (isNodeError13(error, "ENOENT")) {
20608
20839
  return [];
20609
20840
  }
20610
20841
  throw error;
@@ -20900,10 +21131,10 @@ async function readJsonlMessages(profileName, sessionId) {
20900
21131
  return [];
20901
21132
  }
20902
21133
  const profileDir = resolveHermesProfileDir(profileName);
20903
- const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path19.join(profileDir, "sessions"));
20904
- const transcriptPath = path19.join(sessionsDir, `${sessionId}.jsonl`);
21134
+ const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path20.join(profileDir, "sessions"));
21135
+ const transcriptPath = path20.join(sessionsDir, `${sessionId}.jsonl`);
20905
21136
  const raw = await readFile11(transcriptPath, "utf8").catch((error) => {
20906
- if (isNodeError12(error, "ENOENT")) {
21137
+ if (isNodeError13(error, "ENOENT")) {
20907
21138
  return "";
20908
21139
  }
20909
21140
  throw error;
@@ -21231,8 +21462,8 @@ function quoteIdentifier(value) {
21231
21462
  return `"${value.replaceAll('"', '""')}"`;
21232
21463
  }
21233
21464
  async function isFile(filePath) {
21234
- return stat11(filePath).then((value) => value.isFile()).catch((error) => {
21235
- if (isNodeError12(error, "ENOENT")) {
21465
+ return stat12(filePath).then((value) => value.isFile()).catch((error) => {
21466
+ if (isNodeError13(error, "ENOENT")) {
21236
21467
  return false;
21237
21468
  }
21238
21469
  throw error;
@@ -21265,13 +21496,13 @@ function readBoolean(value) {
21265
21496
  }
21266
21497
  return false;
21267
21498
  }
21268
- function isNodeError12(error, code) {
21499
+ function isNodeError13(error, code) {
21269
21500
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
21270
21501
  }
21271
21502
 
21272
21503
  // src/conversations/delivery-context.ts
21273
- import { copyFile, mkdir as mkdir11, stat as stat12 } from "fs/promises";
21274
- import path20 from "path";
21504
+ import { copyFile, mkdir as mkdir11, stat as stat13 } from "fs/promises";
21505
+ import path21 from "path";
21275
21506
  var ACTIVE_CONTEXTS = /* @__PURE__ */ new Map();
21276
21507
  var CONTEXT_TTL_MS = 2 * 60 * 60 * 1e3;
21277
21508
  var MAX_DELIVERY_TOOL_FILES = 50;
@@ -21343,10 +21574,10 @@ function findDeliveryContext(input) {
21343
21574
  );
21344
21575
  }
21345
21576
  async function copyFilesIntoDeliveryContext(paths, context, files) {
21346
- const stagingDir = path20.resolve(context.stagingDir);
21347
- const conversationsRoot = path20.resolve(paths.conversationsDir);
21348
- const relative = path20.relative(conversationsRoot, stagingDir);
21349
- if (!relative || relative.startsWith("..") || path20.isAbsolute(relative)) {
21577
+ const stagingDir = path21.resolve(context.stagingDir);
21578
+ const conversationsRoot = path21.resolve(paths.conversationsDir);
21579
+ const relative = path21.relative(conversationsRoot, stagingDir);
21580
+ if (!relative || relative.startsWith("..") || path21.isAbsolute(relative)) {
21350
21581
  throw new LinkHttpError(
21351
21582
  400,
21352
21583
  "delivery_staging_invalid",
@@ -21357,13 +21588,13 @@ async function copyFilesIntoDeliveryContext(paths, context, files) {
21357
21588
  const result = { staged: [], skipped: [] };
21358
21589
  const usedNames = /* @__PURE__ */ new Set();
21359
21590
  for (const [index, file] of files.slice(0, MAX_DELIVERY_TOOL_FILES).entries()) {
21360
- const sourcePath = path20.resolve(file.path);
21591
+ const sourcePath = path21.resolve(file.path);
21361
21592
  let baseName = sanitizeFilename(
21362
- file.caption || path20.basename(sourcePath),
21593
+ file.caption || path21.basename(sourcePath),
21363
21594
  `attachment-${index + 1}`
21364
21595
  );
21365
- const sourceExtension = path20.extname(sourcePath);
21366
- if (!path20.extname(baseName) && sourceExtension) {
21596
+ const sourceExtension = path21.extname(sourcePath);
21597
+ if (!path21.extname(baseName) && sourceExtension) {
21367
21598
  baseName = `${baseName}${sourceExtension}`;
21368
21599
  }
21369
21600
  const filename = uniqueStagingFilename(
@@ -21377,8 +21608,8 @@ async function copyFilesIntoDeliveryContext(paths, context, files) {
21377
21608
  });
21378
21609
  continue;
21379
21610
  }
21380
- const sourceStat = await stat12(sourcePath).catch((error) => {
21381
- if (isNodeError13(error, "ENOENT")) {
21611
+ const sourceStat = await stat13(sourcePath).catch((error) => {
21612
+ if (isNodeError14(error, "ENOENT")) {
21382
21613
  return null;
21383
21614
  }
21384
21615
  throw error;
@@ -21390,7 +21621,7 @@ async function copyFilesIntoDeliveryContext(paths, context, files) {
21390
21621
  });
21391
21622
  continue;
21392
21623
  }
21393
- const targetPath = path20.join(stagingDir, filename);
21624
+ const targetPath = path21.join(stagingDir, filename);
21394
21625
  await copyFile(sourcePath, targetPath);
21395
21626
  result.staged.push({
21396
21627
  source_path: sourcePath,
@@ -21433,7 +21664,7 @@ function normalizeDeliveryFileInputs(value) {
21433
21664
  });
21434
21665
  }
21435
21666
  function uniqueStagingFilename(filename, usedNames) {
21436
- const extension = path20.extname(filename);
21667
+ const extension = path21.extname(filename);
21437
21668
  const stem = filename.slice(0, filename.length - extension.length);
21438
21669
  let candidate = filename;
21439
21670
  let suffix = 2;
@@ -21469,7 +21700,7 @@ function readString12(payload, key) {
21469
21700
  const value = payload[key];
21470
21701
  return typeof value === "string" && value.trim() ? value.trim() : null;
21471
21702
  }
21472
- function isNodeError13(error, code) {
21703
+ function isNodeError14(error, code) {
21473
21704
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
21474
21705
  }
21475
21706
 
@@ -22175,9 +22406,9 @@ function readBoolean2(payload, key) {
22175
22406
 
22176
22407
  // src/hermes/stt.ts
22177
22408
  import { execFile as execFile3 } from "child_process";
22178
- import { access as access2, readFile as readFile12, stat as stat13 } from "fs/promises";
22179
- import os5 from "os";
22180
- import path21 from "path";
22409
+ import { access as access2, readFile as readFile12, stat as stat14 } from "fs/promises";
22410
+ import os6 from "os";
22411
+ import path22 from "path";
22181
22412
  import { promisify as promisify3 } from "util";
22182
22413
  var execFileAsync3 = promisify3(execFile3);
22183
22414
  var STT_RESULT_PREFIX = "__HERMES_LINK_STT__";
@@ -22272,7 +22503,7 @@ async function buildHermesSttEnv(profileName, hermesSourceRoot) {
22272
22503
  };
22273
22504
  const sourceRoot = hermesSourceRoot ?? await findDevHermesAgentSource();
22274
22505
  if (sourceRoot) {
22275
- env.PYTHONPATH = [sourceRoot, env.PYTHONPATH].filter(Boolean).join(path21.delimiter);
22506
+ env.PYTHONPATH = [sourceRoot, env.PYTHONPATH].filter(Boolean).join(path22.delimiter);
22276
22507
  }
22277
22508
  return env;
22278
22509
  }
@@ -22367,14 +22598,14 @@ async function resolveHermesPythonRuntime() {
22367
22598
  };
22368
22599
  }
22369
22600
  async function resolveExecutablePath(command) {
22370
- if (path21.isAbsolute(command)) {
22601
+ if (path22.isAbsolute(command)) {
22371
22602
  return await isExecutableFile2(command) ? command : null;
22372
22603
  }
22373
22604
  const pathEnv = process.env.PATH ?? "";
22374
22605
  const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
22375
- for (const dir of pathEnv.split(path21.delimiter)) {
22606
+ for (const dir of pathEnv.split(path22.delimiter)) {
22376
22607
  for (const extension of extensions) {
22377
- const candidate = path21.join(dir, `${command}${extension}`);
22608
+ const candidate = path22.join(dir, `${command}${extension}`);
22378
22609
  if (await isExecutableFile2(candidate)) {
22379
22610
  return candidate;
22380
22611
  }
@@ -22384,7 +22615,7 @@ async function resolveExecutablePath(command) {
22384
22615
  }
22385
22616
  async function isExecutableFile2(filePath) {
22386
22617
  try {
22387
- const info = await stat13(filePath);
22618
+ const info = await stat14(filePath);
22388
22619
  if (!info.isFile()) {
22389
22620
  return false;
22390
22621
  }
@@ -22396,11 +22627,11 @@ async function isExecutableFile2(filePath) {
22396
22627
  }
22397
22628
  async function findHermesVenvPython(sourceRoot) {
22398
22629
  const candidates = process.platform === "win32" ? [
22399
- path21.join(sourceRoot, "venv", "Scripts", "python.exe"),
22400
- path21.join(sourceRoot, ".venv", "Scripts", "python.exe")
22630
+ path22.join(sourceRoot, "venv", "Scripts", "python.exe"),
22631
+ path22.join(sourceRoot, ".venv", "Scripts", "python.exe")
22401
22632
  ] : [
22402
- path21.join(sourceRoot, "venv", "bin", "python"),
22403
- path21.join(sourceRoot, ".venv", "bin", "python")
22633
+ path22.join(sourceRoot, "venv", "bin", "python"),
22634
+ path22.join(sourceRoot, ".venv", "bin", "python")
22404
22635
  ];
22405
22636
  for (const candidate of candidates) {
22406
22637
  if (await isExecutableFile2(candidate)) {
@@ -22429,8 +22660,8 @@ function shebangToPythonCommand(shebang) {
22429
22660
  }
22430
22661
  async function findDevHermesAgentSource() {
22431
22662
  const candidates = [
22432
- path21.resolve(process.cwd(), "reference/hermes-agent"),
22433
- path21.resolve(process.cwd(), "../../reference/hermes-agent")
22663
+ path22.resolve(process.cwd(), "reference/hermes-agent"),
22664
+ path22.resolve(process.cwd(), "../../reference/hermes-agent")
22434
22665
  ];
22435
22666
  for (const candidate of candidates) {
22436
22667
  if (await isHermesAgentSourceRoot(candidate)) {
@@ -22446,7 +22677,7 @@ async function readHermesLauncherTarget(filePath) {
22446
22677
  line
22447
22678
  );
22448
22679
  const rawTarget = quoted?.groups?.target ?? /^\s*exec\s+(?<target>\S+)\s+(?:"\$@"|'\$@')/.exec(line)?.groups?.target;
22449
- if (!rawTarget || !path21.isAbsolute(rawTarget)) {
22680
+ if (!rawTarget || !path22.isAbsolute(rawTarget)) {
22450
22681
  continue;
22451
22682
  }
22452
22683
  if (await isExecutableFile2(rawTarget)) {
@@ -22476,12 +22707,12 @@ async function resolveHermesEntrypointRuntime(entrypointPath) {
22476
22707
  return null;
22477
22708
  }
22478
22709
  async function findHermesSourceRoot(executablePath) {
22479
- let cursor = path21.dirname(path21.resolve(executablePath));
22710
+ let cursor = path22.dirname(path22.resolve(executablePath));
22480
22711
  for (let index = 0; index < 6; index += 1) {
22481
22712
  if (await isHermesAgentSourceRoot(cursor)) {
22482
22713
  return cursor;
22483
22714
  }
22484
- const parent = path21.dirname(cursor);
22715
+ const parent = path22.dirname(cursor);
22485
22716
  if (parent === cursor) {
22486
22717
  break;
22487
22718
  }
@@ -22492,14 +22723,14 @@ async function findHermesSourceRoot(executablePath) {
22492
22723
  function hermesSourceRootCandidates() {
22493
22724
  const candidates = [
22494
22725
  process.env.HERMES_PYTHON_SRC_ROOT?.trim(),
22495
- path21.join(os5.homedir(), ".hermes", "hermes-agent"),
22726
+ path22.join(os6.homedir(), ".hermes", "hermes-agent"),
22496
22727
  "/usr/local/lib/hermes-agent",
22497
22728
  "/opt/hermes"
22498
22729
  ].filter((candidate) => Boolean(candidate));
22499
22730
  if (process.platform === "win32") {
22500
22731
  const localAppData = process.env.LOCALAPPDATA?.trim();
22501
22732
  if (localAppData) {
22502
- candidates.unshift(path21.join(localAppData, "hermes", "hermes-agent"));
22733
+ candidates.unshift(path22.join(localAppData, "hermes", "hermes-agent"));
22503
22734
  }
22504
22735
  }
22505
22736
  return candidates;
@@ -22509,10 +22740,10 @@ async function findExplicitHermesSourceRoot() {
22509
22740
  return explicit && await isHermesAgentSourceRoot(explicit) ? explicit : null;
22510
22741
  }
22511
22742
  async function isHermesAgentSourceRoot(candidate) {
22512
- return await isDirectory(candidate) && await isDirectory(path21.join(candidate, "tools")) && await isDirectory(path21.join(candidate, "hermes_cli"));
22743
+ return await isDirectory(candidate) && await isDirectory(path22.join(candidate, "tools")) && await isDirectory(path22.join(candidate, "hermes_cli"));
22513
22744
  }
22514
22745
  async function isDirectory(candidate) {
22515
- return stat13(candidate).then((info) => info.isDirectory()).catch(() => false);
22746
+ return stat14(candidate).then((info) => info.isDirectory()).catch(() => false);
22516
22747
  }
22517
22748
  function compactProcessOutput(value) {
22518
22749
  const compact = value.trim().replace(/\s+/gu, " ").slice(0, 500);
@@ -22520,14 +22751,14 @@ function compactProcessOutput(value) {
22520
22751
  }
22521
22752
 
22522
22753
  // src/hermes/usage-probe.ts
22523
- import { open as open3, readFile as readFile14, rm as rm6, stat as stat15 } from "fs/promises";
22524
- import path23 from "path";
22754
+ import { open as open3, readFile as readFile14, rm as rm6, stat as stat16 } from "fs/promises";
22755
+ import path24 from "path";
22525
22756
  import YAML3 from "yaml";
22526
22757
 
22527
22758
  // src/hermes/profiles.ts
22528
22759
  import { execFile as execFile4 } from "child_process";
22529
- import { readdir as readdir8, readFile as readFile13, rename as rename3, stat as stat14 } from "fs/promises";
22530
- import path22 from "path";
22760
+ import { readdir as readdir8, readFile as readFile13, rename as rename3, stat as stat15 } from "fs/promises";
22761
+ import path23 from "path";
22531
22762
  import { setTimeout as delay4 } from "timers/promises";
22532
22763
  import { promisify as promisify4 } from "util";
22533
22764
  import YAML2 from "yaml";
@@ -22546,7 +22777,7 @@ async function listHermesProfiles(paths = resolveRuntimePaths()) {
22546
22777
  const profilesDir = resolveHermesProfilesDir();
22547
22778
  const entries = await readdir8(profilesDir, { withFileTypes: true }).catch(
22548
22779
  (error) => {
22549
- if (isNodeError14(error, "ENOENT")) {
22780
+ if (isNodeError15(error, "ENOENT")) {
22550
22781
  return [];
22551
22782
  }
22552
22783
  throw error;
@@ -22600,8 +22831,8 @@ async function prepareHermesProfilesForUse(paths = resolveRuntimePaths()) {
22600
22831
  async function getHermesProfileStatus(name, paths = resolveRuntimePaths()) {
22601
22832
  assertProfileName(name);
22602
22833
  const profile = await profileInfo(name, paths);
22603
- const exists = await stat14(profile.path).then((value) => value.isDirectory()).catch((error) => {
22604
- if (isNodeError14(error, "ENOENT")) {
22834
+ const exists = await stat15(profile.path).then((value) => value.isDirectory()).catch((error) => {
22835
+ if (isNodeError15(error, "ENOENT")) {
22605
22836
  return false;
22606
22837
  }
22607
22838
  throw error;
@@ -22670,7 +22901,7 @@ async function readHermesProfileCapabilities(name) {
22670
22901
  return {
22671
22902
  defaultModel: listedModels?.defaultModel ?? null,
22672
22903
  modelCount: listedModels?.models.length ?? 0,
22673
- skillCount: await countSkills(path22.join(profileDir, "skills")).catch(
22904
+ skillCount: await countSkills(path23.join(profileDir, "skills")).catch(
22674
22905
  () => 0
22675
22906
  ),
22676
22907
  toolCount: await countConfiguredTools(name).catch(() => 0)
@@ -22713,8 +22944,8 @@ function assertProfileName(name) {
22713
22944
  }
22714
22945
  }
22715
22946
  async function pathExists(targetPath) {
22716
- return await stat14(targetPath).then(() => true).catch((error) => {
22717
- if (isNodeError14(error, "ENOENT")) {
22947
+ return await stat15(targetPath).then(() => true).catch((error) => {
22948
+ if (isNodeError15(error, "ENOENT")) {
22718
22949
  return false;
22719
22950
  }
22720
22951
  throw error;
@@ -22722,8 +22953,8 @@ async function pathExists(targetPath) {
22722
22953
  }
22723
22954
  async function hasDefaultProfileConfigSource() {
22724
22955
  const profilePath = resolveHermesProfileDir(DEFAULT_PROFILE);
22725
- const profileStat = await stat14(profilePath).catch((error) => {
22726
- if (isNodeError14(error, "ENOENT")) {
22956
+ const profileStat = await stat15(profilePath).catch((error) => {
22957
+ if (isNodeError15(error, "ENOENT")) {
22727
22958
  return null;
22728
22959
  }
22729
22960
  throw error;
@@ -22731,7 +22962,7 @@ async function hasDefaultProfileConfigSource() {
22731
22962
  if (!profileStat?.isDirectory()) {
22732
22963
  return false;
22733
22964
  }
22734
- return await pathExists(resolveHermesConfigPath(DEFAULT_PROFILE)) || await pathExists(path22.join(profilePath, ".env"));
22965
+ return await pathExists(resolveHermesConfigPath(DEFAULT_PROFILE)) || await pathExists(path23.join(profilePath, ".env"));
22735
22966
  }
22736
22967
  async function deleteHermesProfileWithCli(name) {
22737
22968
  try {
@@ -22844,7 +23075,7 @@ function isProcessRunning(pid) {
22844
23075
  process.kill(pid, 0);
22845
23076
  return true;
22846
23077
  } catch (error) {
22847
- return isNodeError14(error, "EPERM");
23078
+ return isNodeError15(error, "EPERM");
22848
23079
  }
22849
23080
  }
22850
23081
  async function waitForProfilePathToRemainAbsent(profilePath) {
@@ -22881,7 +23112,7 @@ function readExecErrorOutput2(error) {
22881
23112
  }
22882
23113
  return parts.join("\n");
22883
23114
  }
22884
- function isNodeError14(error, code) {
23115
+ function isNodeError15(error, code) {
22885
23116
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
22886
23117
  }
22887
23118
  function errorMessage(error) {
@@ -22893,7 +23124,7 @@ function escapeRegExp2(value) {
22893
23124
  async function countSkills(root) {
22894
23125
  const entries = await readdir8(root, { withFileTypes: true }).catch(
22895
23126
  (error) => {
22896
- if (isNodeError14(error, "ENOENT")) {
23127
+ if (isNodeError15(error, "ENOENT")) {
22897
23128
  return [];
22898
23129
  }
22899
23130
  throw error;
@@ -22901,7 +23132,7 @@ async function countSkills(root) {
22901
23132
  );
22902
23133
  let count = 0;
22903
23134
  for (const entry of entries) {
22904
- const entryPath = path22.join(root, entry.name);
23135
+ const entryPath = path23.join(root, entry.name);
22905
23136
  if (entry.name === ".git" || entry.name === ".hub") {
22906
23137
  continue;
22907
23138
  }
@@ -22920,7 +23151,7 @@ async function countConfiguredTools(profileName) {
22920
23151
  resolveHermesConfigPath(profileName),
22921
23152
  "utf8"
22922
23153
  ).catch((error) => {
22923
- if (isNodeError14(error, "ENOENT")) {
23154
+ if (isNodeError15(error, "ENOENT")) {
22924
23155
  return "";
22925
23156
  }
22926
23157
  throw error;
@@ -22991,10 +23222,10 @@ async function ensureHermesUsageProbeForProfiles(options = {}) {
22991
23222
  }
22992
23223
  async function ensureHermesUsageProbeForProfile(profileName, options = {}) {
22993
23224
  const paths = options.paths ?? resolveRuntimePaths();
22994
- const normalizedProfile = normalizeProfileName5(profileName);
23225
+ const normalizedProfile = normalizeProfileName6(profileName);
22995
23226
  const profilePath = resolveHermesProfileDir(normalizedProfile);
22996
23227
  const configPath = resolveHermesConfigPath(normalizedProfile);
22997
- const pluginPath = path23.join(
23228
+ const pluginPath = path24.join(
22998
23229
  profilePath,
22999
23230
  "plugins",
23000
23231
  HERMES_USAGE_PROBE_PLUGIN_KEY
@@ -23020,7 +23251,7 @@ async function ensureHermesUsageProbeForProfile(profileName, options = {}) {
23020
23251
  return { ...base(), skipped: true };
23021
23252
  }
23022
23253
  try {
23023
- await ensureDirectoryWithInheritedMetadata(path23.dirname(eventsPath), 448);
23254
+ await ensureDirectoryWithInheritedMetadata(path24.dirname(eventsPath), 448);
23024
23255
  const state = await readUsageProbeState(paths);
23025
23256
  const pluginState = state.profiles[normalizedProfile];
23026
23257
  const currentConfig = await readUsageProbeConfigStatus({
@@ -23116,11 +23347,11 @@ async function ensureHermesUsageProbeForProfile(profileName, options = {}) {
23116
23347
  }
23117
23348
  async function findHermesUsageProbeEventForRun(input) {
23118
23349
  const paths = input.paths ?? resolveRuntimePaths();
23119
- const profileName = normalizeProfileName5(input.profileName);
23350
+ const profileName = normalizeProfileName6(input.profileName);
23120
23351
  const eventsPath = resolveUsageProbeEventsPath(paths, profileName);
23121
23352
  const raw = await readTail2(eventsPath, MAX_EVENT_READ_BYTES).catch(
23122
23353
  (error) => {
23123
- if (isNodeError15(error, "ENOENT")) {
23354
+ if (isNodeError16(error, "ENOENT")) {
23124
23355
  return "";
23125
23356
  }
23126
23357
  throw error;
@@ -23153,7 +23384,7 @@ async function findHermesUsageProbeEventForRun(input) {
23153
23384
  return aggregateUsageProbeEvents(events.sort(compareUsageProbeEventsByTime));
23154
23385
  }
23155
23386
  function resolveUsageProbeEventsPath(paths = resolveRuntimePaths(), profileName = "default") {
23156
- return path23.join(
23387
+ return path24.join(
23157
23388
  paths.homeDir,
23158
23389
  USAGE_PROBE_DIR,
23159
23390
  safeProfileSegment(profileName),
@@ -23177,8 +23408,8 @@ function summarizeUsageProbeEnsure(result) {
23177
23408
  };
23178
23409
  }
23179
23410
  async function writeUsageProbePlugin(input) {
23180
- const manifestPath = path23.join(input.pluginPath, "plugin.yaml");
23181
- const initPath = path23.join(input.pluginPath, "__init__.py");
23411
+ const manifestPath = path24.join(input.pluginPath, "plugin.yaml");
23412
+ const initPath = path24.join(input.pluginPath, "__init__.py");
23182
23413
  const manifest = usageProbeManifest();
23183
23414
  const source = usageProbePythonSource(
23184
23415
  input.profileName,
@@ -23191,7 +23422,7 @@ async function writeUsageProbePlugin(input) {
23191
23422
  }
23192
23423
  async function writeManagedFile(filePath, content) {
23193
23424
  const existing = await readFile14(filePath, "utf8").catch((error) => {
23194
- if (isNodeError15(error, "ENOENT")) {
23425
+ if (isNodeError16(error, "ENOENT")) {
23195
23426
  return null;
23196
23427
  }
23197
23428
  throw error;
@@ -23294,13 +23525,13 @@ function hasManagedPluginEntry(entries) {
23294
23525
  return entries.includes(HERMES_USAGE_PROBE_PLUGIN_KEY) || entries.includes(LEGACY_PLUGIN_KEY);
23295
23526
  }
23296
23527
  function normalizeEnabledPluginConfig(plugins, enabledEntries, disabledEntries) {
23297
- const nextEnabled = uniqueStrings([
23528
+ const nextEnabled = uniqueStrings2([
23298
23529
  ...enabledEntries.filter(
23299
23530
  (entry) => entry !== LEGACY_PLUGIN_KEY && entry !== HERMES_USAGE_PROBE_PLUGIN_KEY
23300
23531
  ),
23301
23532
  HERMES_USAGE_PROBE_PLUGIN_KEY
23302
23533
  ]);
23303
- const nextDisabled = uniqueStrings(
23534
+ const nextDisabled = uniqueStrings2(
23304
23535
  disabledEntries.filter(
23305
23536
  (entry) => entry !== LEGACY_PLUGIN_KEY && entry !== HERMES_USAGE_PROBE_PLUGIN_KEY
23306
23537
  )
@@ -23315,12 +23546,12 @@ function normalizeEnabledPluginConfig(plugins, enabledEntries, disabledEntries)
23315
23546
  return changed;
23316
23547
  }
23317
23548
  function normalizeDisabledPluginConfig(plugins, enabledEntries, disabledEntries) {
23318
- const nextEnabled = uniqueStrings(
23549
+ const nextEnabled = uniqueStrings2(
23319
23550
  enabledEntries.filter(
23320
23551
  (entry) => entry !== LEGACY_PLUGIN_KEY && entry !== HERMES_USAGE_PROBE_PLUGIN_KEY
23321
23552
  )
23322
23553
  );
23323
- const nextDisabled = uniqueStrings([
23554
+ const nextDisabled = uniqueStrings2([
23324
23555
  ...disabledEntries.filter(
23325
23556
  (entry) => entry !== LEGACY_PLUGIN_KEY && entry !== HERMES_USAGE_PROBE_PLUGIN_KEY
23326
23557
  ),
@@ -23341,11 +23572,11 @@ function ensureApiServerToolsetEnabled(config) {
23341
23572
  if (apiServerToolsets.length === 0 && !Array.isArray(platformToolsets?.api_server) && !knownApiServerToolsets.includes(HERMESPILOT_LINK_TOOLSET)) {
23342
23573
  return false;
23343
23574
  }
23344
- const nextToolsets = uniqueStrings([
23575
+ const nextToolsets = uniqueStrings2([
23345
23576
  ...Array.isArray(platformToolsets?.api_server) ? apiServerToolsets : ["hermes-api-server"],
23346
23577
  HERMESPILOT_LINK_TOOLSET
23347
23578
  ]);
23348
- const nextKnownToolsets = uniqueStrings([
23579
+ const nextKnownToolsets = uniqueStrings2([
23349
23580
  ...knownApiServerToolsets,
23350
23581
  HERMESPILOT_LINK_TOOLSET
23351
23582
  ]);
@@ -23525,19 +23756,19 @@ async function disableUsageProbeAfterActivationFailure(input) {
23525
23756
  });
23526
23757
  }
23527
23758
  async function cleanupLegacyUsageProbePlugin(profilePath) {
23528
- const legacyPath = path23.join(profilePath, "plugins", LEGACY_PLUGIN_KEY);
23759
+ const legacyPath = path24.join(profilePath, "plugins", LEGACY_PLUGIN_KEY);
23529
23760
  const [manifest, init] = await Promise.all([
23530
- readFile14(path23.join(legacyPath, "plugin.yaml"), "utf8").catch(
23761
+ readFile14(path24.join(legacyPath, "plugin.yaml"), "utf8").catch(
23531
23762
  (error) => {
23532
- if (isNodeError15(error, "ENOENT")) {
23763
+ if (isNodeError16(error, "ENOENT")) {
23533
23764
  return null;
23534
23765
  }
23535
23766
  throw error;
23536
23767
  }
23537
23768
  ),
23538
- readFile14(path23.join(legacyPath, "__init__.py"), "utf8").catch(
23769
+ readFile14(path24.join(legacyPath, "__init__.py"), "utf8").catch(
23539
23770
  (error) => {
23540
- if (isNodeError15(error, "ENOENT")) {
23771
+ if (isNodeError16(error, "ENOENT")) {
23541
23772
  return null;
23542
23773
  }
23543
23774
  throw error;
@@ -23584,12 +23815,12 @@ async function rememberUsageProbeState(paths, profileName, patch) {
23584
23815
  }));
23585
23816
  }
23586
23817
  function usageProbeStatePath(paths) {
23587
- return path23.join(paths.homeDir, USAGE_PROBE_DIR, USAGE_PROBE_STATE_FILE);
23818
+ return path24.join(paths.homeDir, USAGE_PROBE_DIR, USAGE_PROBE_STATE_FILE);
23588
23819
  }
23589
23820
  async function readHermesConfigDocument2(configPath, language) {
23590
23821
  const existingRaw = await readFile14(configPath, "utf8").catch(
23591
23822
  (error) => {
23592
- if (isNodeError15(error, "ENOENT")) {
23823
+ if (isNodeError16(error, "ENOENT")) {
23593
23824
  return null;
23594
23825
  }
23595
23826
  throw error;
@@ -23620,7 +23851,7 @@ async function writeHermesConfigDocument2(input) {
23620
23851
  );
23621
23852
  }
23622
23853
  async function readTail2(filePath, maxBytes) {
23623
- const info = await stat15(filePath);
23854
+ const info = await stat16(filePath);
23624
23855
  const length = Math.min(info.size, maxBytes);
23625
23856
  if (length <= 0) {
23626
23857
  return "";
@@ -23996,7 +24227,7 @@ function withDefaultProfilePlaceholder(profiles) {
23996
24227
  ...profiles
23997
24228
  ];
23998
24229
  }
23999
- function normalizeProfileName5(profileName) {
24230
+ function normalizeProfileName6(profileName) {
24000
24231
  const value = profileName?.trim() || "default";
24001
24232
  if (!/^[a-zA-Z0-9._-]{1,64}$/u.test(value)) {
24002
24233
  throw new Error("invalid profile name");
@@ -24004,7 +24235,7 @@ function normalizeProfileName5(profileName) {
24004
24235
  return value;
24005
24236
  }
24006
24237
  function safeProfileSegment(profileName) {
24007
- return encodeURIComponent(normalizeProfileName5(profileName));
24238
+ return encodeURIComponent(normalizeProfileName6(profileName));
24008
24239
  }
24009
24240
  function ensureRecord2(target, key) {
24010
24241
  const existing = target[key];
@@ -24021,7 +24252,7 @@ function readStringList5(value) {
24021
24252
  }
24022
24253
  return value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
24023
24254
  }
24024
- function uniqueStrings(entries) {
24255
+ function uniqueStrings2(entries) {
24025
24256
  return [...new Set(entries.map((entry) => entry.trim()).filter(Boolean))];
24026
24257
  }
24027
24258
  function sameStringList(left, right) {
@@ -24047,8 +24278,8 @@ function isRecord3(value) {
24047
24278
  return typeof value === "object" && value !== null && !Array.isArray(value);
24048
24279
  }
24049
24280
  async function pathIsDirectory(filePath) {
24050
- return stat15(filePath).then((value) => value.isDirectory()).catch((error) => {
24051
- if (isNodeError15(error, "ENOENT")) {
24281
+ return stat16(filePath).then((value) => value.isDirectory()).catch((error) => {
24282
+ if (isNodeError16(error, "ENOENT")) {
24052
24283
  return false;
24053
24284
  }
24054
24285
  throw error;
@@ -24071,7 +24302,7 @@ function logEnsureResult(logger, source, profiles) {
24071
24302
  function errorMessage2(error) {
24072
24303
  return error instanceof Error ? error.message : String(error);
24073
24304
  }
24074
- function isNodeError15(error, code) {
24305
+ function isNodeError16(error, code) {
24075
24306
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
24076
24307
  }
24077
24308
 
@@ -24151,8 +24382,8 @@ function toRecord14(value) {
24151
24382
  }
24152
24383
 
24153
24384
  // src/conversations/run-transcript-enrichment.ts
24154
- import { readFile as readFile15, stat as stat16 } from "fs/promises";
24155
- import path24 from "path";
24385
+ import { readFile as readFile15, stat as stat17 } from "fs/promises";
24386
+ import path25 from "path";
24156
24387
  var MESSAGE_COLUMNS2 = [
24157
24388
  "id",
24158
24389
  "session_id",
@@ -24228,8 +24459,8 @@ async function readRunFinalAssistantText(input) {
24228
24459
  }
24229
24460
  async function readHermesTranscriptRows(profileName, sessionId) {
24230
24461
  const profileDir = resolveHermesProfileDir(profileName);
24231
- const dbPath = path24.join(profileDir, "state.db");
24232
- const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path24.join(profileDir, "sessions"));
24462
+ const dbPath = path25.join(profileDir, "state.db");
24463
+ const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path25.join(profileDir, "sessions"));
24233
24464
  const [dbRows, jsonlRows] = await Promise.all([
24234
24465
  readStateDbMessages2(dbPath, sessionId),
24235
24466
  readJsonlMessages2(sessionsDir, sessionId)
@@ -24292,9 +24523,9 @@ async function readJsonlMessages2(sessionsDir, sessionId) {
24292
24523
  if (!/^[A-Za-z0-9._:-]{1,160}$/u.test(sessionId)) {
24293
24524
  return [];
24294
24525
  }
24295
- const transcriptPath = path24.join(sessionsDir, `${sessionId}.jsonl`);
24526
+ const transcriptPath = path25.join(sessionsDir, `${sessionId}.jsonl`);
24296
24527
  const raw = await readFile15(transcriptPath, "utf8").catch((error) => {
24297
- if (isNodeError16(error, "ENOENT")) {
24528
+ if (isNodeError17(error, "ENOENT")) {
24298
24529
  return "";
24299
24530
  }
24300
24531
  throw error;
@@ -24508,8 +24739,8 @@ function quoteIdentifier2(value) {
24508
24739
  return `"${value.replaceAll('"', '""')}"`;
24509
24740
  }
24510
24741
  async function isFile2(filePath) {
24511
- return stat16(filePath).then((value) => value.isFile()).catch((error) => {
24512
- if (isNodeError16(error, "ENOENT")) {
24742
+ return stat17(filePath).then((value) => value.isFile()).catch((error) => {
24743
+ if (isNodeError17(error, "ENOENT")) {
24513
24744
  return false;
24514
24745
  }
24515
24746
  throw error;
@@ -24539,7 +24770,7 @@ function readNumber4(value) {
24539
24770
  function toRecord15(value) {
24540
24771
  return typeof value === "object" && value !== null ? value : {};
24541
24772
  }
24542
- function isNodeError16(error, code) {
24773
+ function isNodeError17(error, code) {
24543
24774
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
24544
24775
  }
24545
24776
 
@@ -27514,7 +27745,7 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
27514
27745
  async readHermesCronJobIds(profileName) {
27515
27746
  const jobs = await listHermesCronJobs({
27516
27747
  logger: this.deps.logger,
27517
- profileName: normalizeProfileName3(profileName),
27748
+ profileName: normalizeProfileName4(profileName),
27518
27749
  includeDisabled: true
27519
27750
  });
27520
27751
  return new Set(
@@ -27522,7 +27753,7 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
27522
27753
  );
27523
27754
  }
27524
27755
  async bindNewCronJobsCreatedByRun(input) {
27525
- const profileName = normalizeProfileName3(input.profileName);
27756
+ const profileName = normalizeProfileName4(input.profileName);
27526
27757
  const jobs = await listHermesCronJobs({
27527
27758
  logger: this.deps.logger,
27528
27759
  profileName,
@@ -27612,7 +27843,7 @@ function formatFilenameList(filenames, language = "zh-CN") {
27612
27843
  }
27613
27844
  async function readdirWithDirs(directory) {
27614
27845
  return readdir9(directory, { withFileTypes: true }).catch((error) => {
27615
- if (isNodeError17(error, "ENOENT")) {
27846
+ if (isNodeError18(error, "ENOENT")) {
27616
27847
  return [];
27617
27848
  }
27618
27849
  throw error;
@@ -28014,7 +28245,7 @@ function findPreviousHermesResponseId(snapshot, run) {
28014
28245
  }
28015
28246
  function normalizeRunProfileForCompare(profileName) {
28016
28247
  try {
28017
- return normalizeProfileName3(profileName);
28248
+ return normalizeProfileName4(profileName);
28018
28249
  } catch {
28019
28250
  return null;
28020
28251
  }
@@ -28147,7 +28378,7 @@ async function sleep(ms, signal) {
28147
28378
  );
28148
28379
  });
28149
28380
  }
28150
- function isNodeError17(error, code) {
28381
+ function isNodeError18(error, code) {
28151
28382
  if (typeof error !== "object" || error === null || !("code" in error)) {
28152
28383
  return false;
28153
28384
  }
@@ -28381,11 +28612,16 @@ var ConversationService = class {
28381
28612
  "model_id must be a valid model id"
28382
28613
  );
28383
28614
  }
28384
- const configuredModel = normalizedModelId ? await findConfiguredModel(profile.profileName, normalizedModelId, {
28385
- provider: input.modelProvider,
28386
- baseUrl: input.modelBaseUrl,
28387
- apiMode: input.modelApiMode
28388
- }) : null;
28615
+ const configuredModel = normalizedModelId ? await findConfiguredModel(
28616
+ profile.profileName,
28617
+ normalizedModelId,
28618
+ {
28619
+ provider: input.modelProvider,
28620
+ baseUrl: input.modelBaseUrl,
28621
+ apiMode: input.modelApiMode
28622
+ },
28623
+ this.paths
28624
+ ) : null;
28389
28625
  if (normalizedModelId && !configuredModel) {
28390
28626
  throw new LinkHttpError(
28391
28627
  404,
@@ -28475,10 +28711,10 @@ var ConversationService = class {
28475
28711
  };
28476
28712
  }
28477
28713
  async ensureCronInboxConversation(input = {}) {
28478
- const profileName = normalizeProfileName3(input.profileName);
28714
+ const profileName = normalizeProfileName4(input.profileName);
28479
28715
  for (const conversationId of await this.store.listConversationIds()) {
28480
28716
  const manifest = await this.store.readManifest(conversationId).catch(() => null);
28481
- if (manifest?.status === "active" && manifest.title === "HermesLink \u5B9A\u65F6\u4EFB\u52A1" && normalizeProfileName3(
28717
+ if (manifest?.status === "active" && manifest.title === "HermesLink \u5B9A\u65F6\u4EFB\u52A1" && normalizeProfileName4(
28482
28718
  manifest.profile_name_snapshot ?? manifest.profile
28483
28719
  ) === profileName) {
28484
28720
  return manifest.id;
@@ -28589,7 +28825,7 @@ var ConversationService = class {
28589
28825
  }
28590
28826
  const now = (/* @__PURE__ */ new Date()).toISOString();
28591
28827
  const createdAt = input.runAt ?? now;
28592
- const profileName = normalizeProfileName3(
28828
+ const profileName = normalizeProfileName4(
28593
28829
  manifest.profile_name_snapshot ?? manifest.profile ?? input.profileName
28594
28830
  );
28595
28831
  const ownerAccountId = input.accountId ?? manifest.owner_account_id;
@@ -28656,7 +28892,7 @@ var ConversationService = class {
28656
28892
  });
28657
28893
  }
28658
28894
  async findImportedCronConversation(input) {
28659
- const profileName = normalizeProfileName3(input.profileName);
28895
+ const profileName = normalizeProfileName4(input.profileName);
28660
28896
  const outputAt = Date.parse(input.runAt ?? "");
28661
28897
  const candidates = [];
28662
28898
  for (const conversationId of await this.store.listConversationIds()) {
@@ -28664,7 +28900,7 @@ var ConversationService = class {
28664
28900
  if (!manifest || manifest.status !== "active") {
28665
28901
  continue;
28666
28902
  }
28667
- if (normalizeProfileName3(
28903
+ if (normalizeProfileName4(
28668
28904
  manifest.profile_name_snapshot ?? manifest.profile
28669
28905
  ) !== profileName) {
28670
28906
  continue;
@@ -28912,7 +29148,8 @@ var ConversationService = class {
28912
29148
  const configuredModel = normalizedModelId ? await findConfiguredModel(
28913
29149
  currentRuntime.profile.name,
28914
29150
  normalizedModelId,
28915
- modelSelector
29151
+ modelSelector,
29152
+ this.paths
28916
29153
  ) : null;
28917
29154
  if (normalizedModelId && !configuredModel) {
28918
29155
  throw new LinkHttpError(
@@ -29016,7 +29253,7 @@ var ConversationService = class {
29016
29253
  this.paths,
29017
29254
  profileName
29018
29255
  );
29019
- const currentProfile = normalizeProfileName3(
29256
+ const currentProfile = normalizeProfileName4(
29020
29257
  manifest.profile_name_snapshot ?? manifest.profile
29021
29258
  );
29022
29259
  if (currentProfile === profile.profileName && (!manifest.profile_uid || manifest.profile_uid === profile.profileUid)) {
@@ -29306,7 +29543,7 @@ var ConversationService = class {
29306
29543
  "This approval request cannot be permanently allowed because Hermes did not return a pattern key."
29307
29544
  );
29308
29545
  }
29309
- const profileName = normalizeProfileName3(
29546
+ const profileName = normalizeProfileName4(
29310
29547
  run?.profile ?? match.message.sender.profile ?? manifest.profile_name_snapshot ?? manifest.profile
29311
29548
  );
29312
29549
  const result = await addHermesCommandAllowlistEntry(
@@ -29759,7 +29996,7 @@ var ConversationService = class {
29759
29996
  }
29760
29997
  }
29761
29998
  hermesArchiveStateSyncMarkerPath() {
29762
- return path25.join(this.paths.indexesDir, "hermes-archive-state-sync.json");
29999
+ return path26.join(this.paths.indexesDir, "hermes-archive-state-sync.json");
29763
30000
  }
29764
30001
  prepareClearAllConversationPlan(targetStatus) {
29765
30002
  return this.maintenance.prepareClearAllConversationPlan(targetStatus);
@@ -29789,7 +30026,7 @@ var ConversationService = class {
29789
30026
  return this.maintenance.deleteConversations(conversationIds);
29790
30027
  }
29791
30028
  async deleteLocalConversationsForProfile(input) {
29792
- const profileName = normalizeProfileName3(input.profileName);
30029
+ const profileName = normalizeProfileName4(input.profileName);
29793
30030
  const profileUid = input.profileUid?.trim() || null;
29794
30031
  const deletedConversationIds = [];
29795
30032
  for (const conversationId of await this.store.listConversationIds()) {
@@ -29874,7 +30111,7 @@ function conversationMatchesProfile(manifest, profileName, profileUid) {
29874
30111
  if (profileUid && manifest.profile_uid === profileUid) {
29875
30112
  return true;
29876
30113
  }
29877
- return normalizeProfileName3(manifest.profile_name_snapshot ?? manifest.profile) === profileName;
30114
+ return normalizeProfileName4(manifest.profile_name_snapshot ?? manifest.profile) === profileName;
29878
30115
  }
29879
30116
  function findApproval(snapshot, approvalId) {
29880
30117
  for (const message of snapshot.messages) {
@@ -32256,110 +32493,6 @@ function assertCronJobId(jobId) {
32256
32493
  // src/http/routes/model-configs.ts
32257
32494
  import { createHash as createHash9 } from "crypto";
32258
32495
 
32259
- // src/hermes/model-provider-disconnects.ts
32260
- import { stat as stat17 } from "fs/promises";
32261
- import os6 from "os";
32262
- import path26 from "path";
32263
- async function markAuthBackedModelProviderDisconnected(paths, profileName, providerKey) {
32264
- const profileKey = normalizeProfileName6(profileName);
32265
- const provider = providerKey.trim();
32266
- if (!provider) {
32267
- return;
32268
- }
32269
- const credentialMtimeMs = await readProviderCredentialMtimeMs(
32270
- profileKey,
32271
- provider
32272
- );
32273
- await updateJsonFile(
32274
- disconnectedProvidersPath(paths),
32275
- (current) => {
32276
- const store = normalizeStore(current);
32277
- const profiles = { ...store.profiles ?? {} };
32278
- const profile = profiles[profileKey] ?? {};
32279
- profiles[profileKey] = {
32280
- ...profile,
32281
- providers: {
32282
- ...profile.providers ?? {},
32283
- [provider]: {
32284
- disconnectedAt: (/* @__PURE__ */ new Date()).toISOString(),
32285
- credentialMtimeMs
32286
- }
32287
- }
32288
- };
32289
- return { ...store, profiles };
32290
- }
32291
- );
32292
- }
32293
- async function readDisconnectedAuthBackedModelProviderKeys(paths, profileName) {
32294
- const store = normalizeStore(
32295
- await readJsonFile(
32296
- disconnectedProvidersPath(paths)
32297
- ).catch(() => null)
32298
- );
32299
- const profile = store.profiles?.[normalizeProfileName6(profileName)];
32300
- const providers = profile?.providers ?? {};
32301
- const active = /* @__PURE__ */ new Set();
32302
- for (const [provider, entry] of Object.entries(providers)) {
32303
- if (await isDisconnectMarkerActive(profileName, provider, entry)) {
32304
- active.add(provider);
32305
- }
32306
- }
32307
- return active;
32308
- }
32309
- async function isDisconnectMarkerActive(profileName, providerKey, entry) {
32310
- const currentMtimeMs = await readProviderCredentialMtimeMs(
32311
- profileName,
32312
- providerKey
32313
- );
32314
- if (entry.credentialMtimeMs === null || !Number.isFinite(entry.credentialMtimeMs)) {
32315
- return currentMtimeMs === null;
32316
- }
32317
- if (currentMtimeMs === null || !Number.isFinite(currentMtimeMs)) {
32318
- return true;
32319
- }
32320
- return currentMtimeMs <= entry.credentialMtimeMs + 1e3;
32321
- }
32322
- async function readProviderCredentialMtimeMs(profileName, providerKey) {
32323
- for (const filePath of credentialFilesForProvider(profileName, providerKey)) {
32324
- const stats = await stat17(filePath).catch((error) => {
32325
- if (isNodeError18(error, "ENOENT")) {
32326
- return null;
32327
- }
32328
- throw error;
32329
- });
32330
- if (stats) {
32331
- return stats.mtimeMs;
32332
- }
32333
- }
32334
- return null;
32335
- }
32336
- function credentialFilesForProvider(profileName, providerKey) {
32337
- const profileDir = resolveHermesProfileDir(normalizeProfileName6(profileName));
32338
- switch (providerKey) {
32339
- case "qwen-oauth":
32340
- return [path26.join(os6.homedir(), ".qwen", "oauth_creds.json")];
32341
- case "google-gemini-cli":
32342
- return [path26.join(profileDir, "auth", "google_oauth.json")];
32343
- default:
32344
- return [path26.join(profileDir, "auth.json")];
32345
- }
32346
- }
32347
- function disconnectedProvidersPath(paths) {
32348
- return path26.join(paths.homeDir, "model-provider-disconnects.json");
32349
- }
32350
- function normalizeStore(value) {
32351
- return {
32352
- version: 1,
32353
- profiles: value?.profiles && typeof value.profiles === "object" ? value.profiles : {}
32354
- };
32355
- }
32356
- function normalizeProfileName6(profileName) {
32357
- return profileName.trim() || "default";
32358
- }
32359
- function isNodeError18(error, code) {
32360
- return typeof error === "object" && error !== null && "code" in error && error.code === code;
32361
- }
32362
-
32363
32496
  // src/model-catalog/catalog.ts
32364
32497
  import { randomInt } from "crypto";
32365
32498
  import { mkdir as mkdir12 } from "fs/promises";
@@ -32747,6 +32880,43 @@ async function reloadGatewayAfterProfileModelConfigChange(result, options) {
32747
32880
  label: "\u6A21\u578B\u914D\u7F6E"
32748
32881
  });
32749
32882
  }
32883
+ async function refreshRuntimeAfterModelConfigChange(result, options) {
32884
+ const profileName = options.profileName?.trim() || "default";
32885
+ const label = options.label ?? "\u6A21\u578B\u914D\u7F6E";
32886
+ if (resolveConversationRunBackend() !== "tui_gateway") {
32887
+ return profileName === "default" ? reloadGatewayAfterModelConfigChange(result, options) : reloadGatewayAfterProfileModelConfigChange(result, {
32888
+ ...options,
32889
+ profileName
32890
+ });
32891
+ }
32892
+ try {
32893
+ await restartTuiGatewayBackend({
32894
+ profileName,
32895
+ reason: "model_config_changed",
32896
+ logger: options.logger,
32897
+ paths: options.paths
32898
+ });
32899
+ return {
32900
+ ...result,
32901
+ tuiGatewayRestarted: true,
32902
+ requiresGatewayReload: false,
32903
+ restartHint: `${label}\u5DF2\u4FDD\u5B58\uFF0C${profileName} Profile \u7684 tui_gateway \u5DF2\u91CD\u542F\u3002\u65B0\u7684 Run \u4F1A\u8BFB\u53D6\u6700\u65B0\u914D\u7F6E\u3002`
32904
+ };
32905
+ } catch (error) {
32906
+ const message = error instanceof Error ? error.message : String(error);
32907
+ await options.logger.warn("tui_gateway_restart_after_model_config_failed", {
32908
+ profile: profileName,
32909
+ error: message
32910
+ });
32911
+ return {
32912
+ ...result,
32913
+ tuiGatewayRestarted: false,
32914
+ reloadError: message,
32915
+ requiresGatewayReload: true,
32916
+ restartHint: `${label}\u5DF2\u4FDD\u5B58\uFF0C\u4F46 ${profileName} Profile \u7684 tui_gateway \u81EA\u52A8\u91CD\u542F\u5931\u8D25\uFF1A${message}`
32917
+ };
32918
+ }
32919
+ }
32750
32920
  async function reloadGatewayAfterProfileConfigChange(result, options) {
32751
32921
  try {
32752
32922
  await reloadHermesGateway({
@@ -32793,7 +32963,7 @@ function registerModelConfigRoutes(router, options) {
32793
32963
  router.get("/api/v1/model-configs", async (ctx) => {
32794
32964
  await authenticateRequest(ctx, paths);
32795
32965
  ctx.set("cache-control", "no-store");
32796
- ctx.body = await listHermesModelConfigs();
32966
+ ctx.body = await listVisibleHermesModelConfigs("default", paths);
32797
32967
  });
32798
32968
  router.get("/api/v1/model-configs/catalog", async (ctx) => {
32799
32969
  await authenticateRequest(ctx, paths);
@@ -32831,8 +33001,16 @@ function registerModelConfigRoutes(router, options) {
32831
33001
  readModelProviderConfigInput(body),
32832
33002
  ctx.params.name
32833
33003
  );
33004
+ const applied = shouldReloadGatewayAfterModelConfigChange(body, {
33005
+ defaultReload: true
33006
+ }) ? await refreshRuntimeAfterModelConfigChange(result, {
33007
+ paths,
33008
+ logger,
33009
+ profileName: ctx.params.name,
33010
+ label: "\u6A21\u578B Provider \u914D\u7F6E"
33011
+ }) : markModelConfigAppliedWithoutGatewayReload(result);
32834
33012
  ctx.body = await enrichProviderModelConfigResult(
32835
- markModelConfigAppliedWithoutGatewayReload(result),
33013
+ applied,
32836
33014
  ctx.params.name,
32837
33015
  paths
32838
33016
  );
@@ -32865,8 +33043,16 @@ function registerModelConfigRoutes(router, options) {
32865
33043
  body,
32866
33044
  paths
32867
33045
  );
33046
+ const applied = shouldReloadGatewayAfterModelConfigChange(body, {
33047
+ defaultReload: true
33048
+ }) ? await refreshRuntimeAfterModelConfigChange(result, {
33049
+ paths,
33050
+ logger,
33051
+ profileName: ctx.params.name,
33052
+ label: "\u6A21\u578B Provider \u914D\u7F6E"
33053
+ }) : markModelConfigAppliedWithoutGatewayReload(result);
32868
33054
  ctx.body = await enrichProviderModelConfigResult(
32869
- markModelConfigAppliedWithoutGatewayReload(result),
33055
+ applied,
32870
33056
  ctx.params.name,
32871
33057
  paths
32872
33058
  );
@@ -32947,8 +33133,16 @@ function registerModelConfigRoutes(router, options) {
32947
33133
  },
32948
33134
  ctx.params.name
32949
33135
  );
33136
+ const applied = shouldReloadGatewayAfterModelConfigChange(body, {
33137
+ defaultReload: true
33138
+ }) ? await refreshRuntimeAfterModelConfigChange(result, {
33139
+ paths,
33140
+ logger,
33141
+ profileName: ctx.params.name,
33142
+ label: "\u6A21\u578B Provider \u914D\u7F6E"
33143
+ }) : markModelConfigAppliedWithoutGatewayReload(result);
32950
33144
  ctx.body = await enrichProviderModelConfigResult(
32951
- markModelConfigAppliedWithoutGatewayReload(result),
33145
+ applied,
32952
33146
  ctx.params.name,
32953
33147
  paths
32954
33148
  );
@@ -32980,8 +33174,14 @@ function registerModelConfigRoutes(router, options) {
32980
33174
  provider.providerKey,
32981
33175
  ctx.params.name
32982
33176
  );
33177
+ const applied = await refreshRuntimeAfterModelConfigChange(result, {
33178
+ paths,
33179
+ logger,
33180
+ profileName: ctx.params.name,
33181
+ label: "\u6A21\u578B Provider \u914D\u7F6E"
33182
+ });
32983
33183
  ctx.body = await enrichProviderModelConfigResult(
32984
- markModelConfigAppliedWithoutGatewayReload(result),
33184
+ applied,
32985
33185
  ctx.params.name,
32986
33186
  paths
32987
33187
  );
@@ -32999,7 +33199,12 @@ function registerModelConfigRoutes(router, options) {
32999
33199
  );
33000
33200
  }
33001
33201
  ctx.body = latest ? await enrichProviderModelConfigResult(
33002
- markModelConfigAppliedWithoutGatewayReload(latest),
33202
+ await refreshRuntimeAfterModelConfigChange(latest, {
33203
+ paths,
33204
+ logger,
33205
+ profileName: ctx.params.name,
33206
+ label: "\u6A21\u578B Provider \u914D\u7F6E"
33207
+ }),
33003
33208
  ctx.params.name,
33004
33209
  paths
33005
33210
  ) : await listModelProviderResponse(ctx.params.name, paths);
@@ -33012,7 +33217,7 @@ function registerModelConfigRoutes(router, options) {
33012
33217
  const result = await saveHermesModelConfig(readModelConfigInput(body));
33013
33218
  const applied = shouldReloadGatewayAfterModelConfigChange(body, {
33014
33219
  defaultReload: true
33015
- }) ? await reloadGatewayAfterModelConfigChange(result, {
33220
+ }) ? await refreshRuntimeAfterModelConfigChange(result, {
33016
33221
  paths,
33017
33222
  logger
33018
33223
  }) : markModelConfigAppliedWithoutGatewayReload(result);
@@ -33028,8 +33233,10 @@ function registerModelConfigRoutes(router, options) {
33028
33233
  router.patch("/api/v1/model-configs/defaults", async (ctx) => {
33029
33234
  await authenticateRequest(ctx, paths);
33030
33235
  const body = await readJsonBody(ctx.req);
33236
+ const input = readModelDefaultsInput(body);
33031
33237
  try {
33032
- const result = await saveHermesModelDefaults(readModelDefaultsInput(body));
33238
+ await assertModelDefaultsSelectable(input, "default", paths);
33239
+ const result = await saveHermesModelDefaults(input);
33033
33240
  ctx.body = await enrichProviderModelConfigResult(result, "default", paths);
33034
33241
  } catch (error) {
33035
33242
  throw toModelConfigHttpError(error);
@@ -33041,7 +33248,9 @@ function registerModelConfigRoutes(router, options) {
33041
33248
  const input = readModelDeleteInput(body);
33042
33249
  try {
33043
33250
  const result = await deleteHermesModelConfig(input);
33044
- const applied = shouldReloadGatewayAfterModelConfigChange(body) ? await reloadGatewayAfterModelConfigChange(result, {
33251
+ const applied = shouldReloadGatewayAfterModelConfigChange(body, {
33252
+ defaultReload: true
33253
+ }) ? await refreshRuntimeAfterModelConfigChange(result, {
33045
33254
  paths,
33046
33255
  logger
33047
33256
  }) : markModelConfigAppliedWithoutGatewayReload(result);
@@ -33058,7 +33267,7 @@ function registerModelConfigRoutes(router, options) {
33058
33267
  await authenticateRequest(ctx, paths);
33059
33268
  await getHermesProfileStatus(ctx.params.name, paths);
33060
33269
  ctx.set("cache-control", "no-store");
33061
- ctx.body = await listHermesModelConfigs(ctx.params.name);
33270
+ ctx.body = await listVisibleHermesModelConfigs(ctx.params.name, paths);
33062
33271
  });
33063
33272
  router.post("/api/v1/profiles/:name/model-configs", async (ctx) => {
33064
33273
  await authenticateRequest(ctx, paths);
@@ -33071,7 +33280,7 @@ function registerModelConfigRoutes(router, options) {
33071
33280
  );
33072
33281
  const applied = shouldReloadGatewayAfterModelConfigChange(body, {
33073
33282
  defaultReload: true
33074
- }) ? await reloadGatewayAfterProfileModelConfigChange(result, {
33283
+ }) ? await refreshRuntimeAfterModelConfigChange(result, {
33075
33284
  paths,
33076
33285
  logger,
33077
33286
  profileName: ctx.params.name
@@ -33093,7 +33302,9 @@ function registerModelConfigRoutes(router, options) {
33093
33302
  await getHermesProfileStatus(input.sourceProfileName, paths);
33094
33303
  try {
33095
33304
  const result = await importHermesModelConfig(input, ctx.params.name);
33096
- const applied = shouldReloadGatewayAfterModelConfigChange(body) ? await reloadGatewayAfterProfileModelConfigChange(result, {
33305
+ const applied = shouldReloadGatewayAfterModelConfigChange(body, {
33306
+ defaultReload: true
33307
+ }) ? await refreshRuntimeAfterModelConfigChange(result, {
33097
33308
  paths,
33098
33309
  logger,
33099
33310
  profileName: ctx.params.name
@@ -33111,11 +33322,10 @@ function registerModelConfigRoutes(router, options) {
33111
33322
  await authenticateRequest(ctx, paths);
33112
33323
  await getHermesProfileStatus(ctx.params.name, paths);
33113
33324
  const body = await readJsonBody(ctx.req);
33325
+ const input = readModelDefaultsInput(body);
33114
33326
  try {
33115
- const result = await saveHermesModelDefaults(
33116
- readModelDefaultsInput(body),
33117
- ctx.params.name
33118
- );
33327
+ await assertModelDefaultsSelectable(input, ctx.params.name, paths);
33328
+ const result = await saveHermesModelDefaults(input, ctx.params.name);
33119
33329
  ctx.body = await enrichProviderModelConfigResult(
33120
33330
  result,
33121
33331
  ctx.params.name,
@@ -33132,7 +33342,9 @@ function registerModelConfigRoutes(router, options) {
33132
33342
  const input = readModelDeleteInput(body);
33133
33343
  try {
33134
33344
  const result = await deleteHermesModelConfig(input, ctx.params.name);
33135
- const applied = shouldReloadGatewayAfterModelConfigChange(body) ? await reloadGatewayAfterProfileModelConfigChange(result, {
33345
+ const applied = shouldReloadGatewayAfterModelConfigChange(body, {
33346
+ defaultReload: true
33347
+ }) ? await refreshRuntimeAfterModelConfigChange(result, {
33136
33348
  paths,
33137
33349
  logger,
33138
33350
  profileName: ctx.params.name
@@ -33148,7 +33360,7 @@ function registerModelConfigRoutes(router, options) {
33148
33360
  });
33149
33361
  }
33150
33362
  async function listModelProviderResponse(profileName, paths, options = {}) {
33151
- const configs = await listHermesModelConfigs(profileName);
33363
+ const configs = options.includeDisconnected ? await listHermesModelConfigs(profileName) : await listVisibleHermesModelConfigs(profileName, paths);
33152
33364
  const providerConfigs = await listHermesModelProviderConfigs(profileName);
33153
33365
  const catalog = await readModelCapabilityCatalog(paths);
33154
33366
  const providers = groupManagedModelsByProvider(
@@ -33172,6 +33384,92 @@ async function listModelProviderResponse(profileName, paths, options = {}) {
33172
33384
  providers: visibleProviders
33173
33385
  };
33174
33386
  }
33387
+ async function listVisibleHermesModelConfigs(profileName, paths) {
33388
+ const configs = await listHermesModelConfigs(profileName);
33389
+ const disconnectedProviders = await readDisconnectedAuthBackedModelProviderKeys(
33390
+ paths,
33391
+ profileName
33392
+ );
33393
+ return filterHermesModelConfigsByProviderKeys(configs, disconnectedProviders);
33394
+ }
33395
+ async function assertModelDefaultsSelectable(input, profileName, paths) {
33396
+ const disconnectedProviders = await readDisconnectedAuthBackedModelProviderKeys(
33397
+ paths,
33398
+ profileName
33399
+ );
33400
+ assertProviderNotDisconnected(
33401
+ disconnectedProviders,
33402
+ input.taskModelProvider,
33403
+ "task model"
33404
+ );
33405
+ assertProviderNotDisconnected(
33406
+ disconnectedProviders,
33407
+ input.compressionModelProvider,
33408
+ "compression model"
33409
+ );
33410
+ if (disconnectedProviders.size === 0 || !input.taskModelId && !input.compressionModelId) {
33411
+ return;
33412
+ }
33413
+ const allConfigs = await listHermesModelConfigs(profileName);
33414
+ assertModelIdNotHiddenByDisconnect(allConfigs.models, disconnectedProviders, {
33415
+ id: input.taskModelId,
33416
+ provider: input.taskModelProvider,
33417
+ baseUrl: input.taskModelBaseUrl,
33418
+ apiMode: input.taskModelApiMode,
33419
+ label: "task model"
33420
+ });
33421
+ assertModelIdNotHiddenByDisconnect(allConfigs.models, disconnectedProviders, {
33422
+ id: input.compressionModelId,
33423
+ provider: input.compressionModelProvider,
33424
+ baseUrl: input.compressionModelBaseUrl,
33425
+ apiMode: input.compressionModelApiMode,
33426
+ label: "compression model"
33427
+ });
33428
+ }
33429
+ function assertProviderNotDisconnected(disconnectedProviders, provider, label) {
33430
+ if (provider && disconnectedProviders.has(provider)) {
33431
+ throw new LinkHttpError(
33432
+ 409,
33433
+ "model_provider_disconnected",
33434
+ `The ${label} provider "${provider}" has been disconnected from this Profile.`
33435
+ );
33436
+ }
33437
+ }
33438
+ function assertModelIdNotHiddenByDisconnect(models, disconnectedProviders, selector) {
33439
+ const id = selector.id?.trim();
33440
+ if (!id) {
33441
+ return;
33442
+ }
33443
+ const matchingModels = models.filter((model) => {
33444
+ if (model.id !== id) {
33445
+ return false;
33446
+ }
33447
+ if (selector.provider && model.provider !== selector.provider) {
33448
+ return false;
33449
+ }
33450
+ if (selector.baseUrl !== void 0 && normalizeUrlForIdentity(model.baseUrl) !== normalizeUrlForIdentity(selector.baseUrl)) {
33451
+ return false;
33452
+ }
33453
+ if (selector.apiMode && model.apiMode !== selector.apiMode) {
33454
+ return false;
33455
+ }
33456
+ return true;
33457
+ });
33458
+ if (matchingModels.some((model) => !disconnectedProviders.has(model.provider))) {
33459
+ return;
33460
+ }
33461
+ const hiddenMatch = matchingModels.find(
33462
+ (model) => disconnectedProviders.has(model.provider)
33463
+ );
33464
+ if (!hiddenMatch) {
33465
+ return;
33466
+ }
33467
+ throw new LinkHttpError(
33468
+ 409,
33469
+ "model_provider_disconnected",
33470
+ `The ${selector.label} "${id}" belongs to disconnected provider "${hiddenMatch.provider}".`
33471
+ );
33472
+ }
33175
33473
  async function enrichProviderModelConfigResult(result, profileName, paths) {
33176
33474
  const providerResponse = await listModelProviderResponse(profileName, paths);
33177
33475
  return {
@@ -33830,12 +34128,12 @@ function registerProfileCatalogRoutes(router, options) {
33830
34128
  ok: true,
33831
34129
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
33832
34130
  profiles: await Promise.all(
33833
- profiles.map((profile) => readProfileCatalogItem(profile))
34131
+ profiles.map((profile) => readProfileCatalogItem(profile, paths))
33834
34132
  )
33835
34133
  };
33836
34134
  });
33837
34135
  }
33838
- async function readProfileCatalogItem(profile) {
34136
+ async function readProfileCatalogItem(profile, paths) {
33839
34137
  const [apiServer, capabilities, permissions, modelConfigs] = await Promise.all([
33840
34138
  readCatalogField(
33841
34139
  "apiServer",
@@ -33849,7 +34147,10 @@ async function readProfileCatalogItem(profile) {
33849
34147
  "permissions",
33850
34148
  () => readHermesProfilePermissions(profile.name)
33851
34149
  ),
33852
- readCatalogField("modelConfigs", () => listHermesModelConfigs(profile.name))
34150
+ readCatalogField(
34151
+ "modelConfigs",
34152
+ () => listVisibleHermesModelConfigs2(profile.name, paths)
34153
+ )
33853
34154
  ]);
33854
34155
  return {
33855
34156
  profile,
@@ -33870,6 +34171,14 @@ async function readProfileCatalogItem(profile) {
33870
34171
  ]
33871
34172
  };
33872
34173
  }
34174
+ async function listVisibleHermesModelConfigs2(profileName, paths) {
34175
+ const configs = await listHermesModelConfigs(profileName);
34176
+ const disconnectedProviders = await readDisconnectedAuthBackedModelProviderKeys(
34177
+ paths,
34178
+ profileName
34179
+ );
34180
+ return filterHermesModelConfigsByProviderKeys(configs, disconnectedProviders);
34181
+ }
33873
34182
  async function readCatalogField(field, load) {
33874
34183
  try {
33875
34184
  return { value: await load(), errors: [] };
@@ -39698,11 +40007,11 @@ async function mergeLastReportedPublicRoutes(paths, snapshotInput) {
39698
40007
  const state = await readNetworkReportState(paths);
39699
40008
  return {
39700
40009
  ...snapshotInput,
39701
- publicIpv4s: uniqueStrings2([
40010
+ publicIpv4s: uniqueStrings3([
39702
40011
  ...snapshotInput.publicIpv4s,
39703
40012
  ...state.lastReportedPublicIpv4s
39704
40013
  ]).slice(0, 2),
39705
- publicIpv6s: uniqueStrings2([
40014
+ publicIpv6s: uniqueStrings3([
39706
40015
  ...snapshotInput.publicIpv6s,
39707
40016
  ...state.lastReportedPublicIpv6s
39708
40017
  ]).slice(0, 2)
@@ -39794,7 +40103,7 @@ function sameStringList2(left, right) {
39794
40103
  }
39795
40104
  return left.every((value, index) => value === right[index]);
39796
40105
  }
39797
- function uniqueStrings2(values) {
40106
+ function uniqueStrings3(values) {
39798
40107
  return [...new Set(values)];
39799
40108
  }
39800
40109
  function formatUtcDay(date) {