hermes-web-ui 0.4.1 → 0.4.2

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.
Files changed (67) hide show
  1. package/dist/client/assets/{Add-BpktEZsz.js → Add-COjp_zn3.js} +1 -1
  2. package/dist/client/assets/{Button-Cnfw-IjX.js → Button-pgPN7RfC.js} +1 -1
  3. package/dist/client/assets/{ChannelsView-8vPg4mUj.js → ChannelsView-qFMvJ9rS.js} +1 -1
  4. package/dist/client/assets/ChatView-DVQv3z6i.js +127 -0
  5. package/dist/client/assets/ChatView-w9DKSiie.css +1 -0
  6. package/dist/client/assets/{Close-U2JC-gNs.js → Close-CXC76d7R.js} +1 -1
  7. package/dist/client/assets/{FormItem-hNEKqj3O.js → FormItem-DIcSMjKz.js} +1 -1
  8. package/dist/client/assets/{GatewaysView-Y-OpJkmm.js → GatewaysView-BeaZvbdd.js} +1 -1
  9. package/dist/client/assets/{Input-DrKhBiZw.js → Input-CxMM68Q2.js} +1 -1
  10. package/dist/client/assets/{InputNumber-CpgDdt3i.js → InputNumber-Btyn8bQo.js} +1 -1
  11. package/dist/client/assets/{JobsView-DPWQMuXD.js → JobsView-DhosFF52.js} +2 -2
  12. package/dist/client/assets/{LoginView-i8prLGX4.js → LoginView-DrjDX7xD.js} +1 -1
  13. package/dist/client/assets/{LogsView-DKijLNKW.js → LogsView-BpW_vgEs.js} +1 -1
  14. package/dist/client/assets/{MarkdownRenderer-DENPDjvG.js → MarkdownRenderer-G4nly1xR.js} +1 -1
  15. package/dist/client/assets/{MemoryView-FsdXJHO2.js → MemoryView-CP3K8mDG.js} +1 -1
  16. package/dist/client/assets/{Modal-R2Z6-Zms.js → Modal-rKb1tfQv.js} +1 -1
  17. package/dist/client/assets/{ModelsView-CUhcDxQn.js → ModelsView-DdQUiiu6.js} +1 -1
  18. package/dist/client/assets/{Popconfirm-LcqynqV7.js → Popconfirm-DnYCLqNF.js} +1 -1
  19. package/dist/client/assets/{Popover-BMyFTs0l.js → Popover-DAXLiB78.js} +1 -1
  20. package/dist/client/assets/{ProfilesView-CeMhWRo4.js → ProfilesView-Bkhqt3QB.js} +1 -1
  21. package/dist/client/assets/{Select-DuDO62HK.js → Select-CpNY3UGb.js} +1 -1
  22. package/dist/client/assets/{SettingRow-Dqpk_Y1X.js → SettingRow-B4_BuJSm.js} +1 -1
  23. package/dist/client/assets/{SettingsView-C3sd8K0e.css → SettingsView-CX312EGG.css} +1 -1
  24. package/dist/client/assets/{SettingsView-C-Wmo-pf.js → SettingsView-ZwzxJZVc.js} +2 -2
  25. package/dist/client/assets/{SkillsView--XG2gvdt.js → SkillsView-B3X9y2kZ.js} +1 -1
  26. package/dist/client/assets/{Spin-CNthlR40.js → Spin-CAOjab_F.js} +1 -1
  27. package/dist/client/assets/{Suffix-CeHN3ihg.js → Suffix-DQWmhzzW.js} +1 -1
  28. package/dist/client/assets/{Switch-HUSzpquo.js → Switch-haCJAcVn.js} +1 -1
  29. package/dist/client/assets/{Tag-bcPrHTQq.js → Tag-CKzgX6hi.js} +1 -1
  30. package/dist/client/assets/{TerminalView-CxtsiKer.js → TerminalView-CmSON012.js} +1 -1
  31. package/dist/client/assets/{Tooltip-CsnrCUqC.js → Tooltip-U6DmMv6W.js} +1 -1
  32. package/dist/client/assets/{UsageView-w5xY3YK-.js → UsageView-rmIKdR1u.js} +1 -1
  33. package/dist/client/assets/{Warning-B-4ukuIo.js → Warning-B2AD2ulw.js} +1 -1
  34. package/dist/client/assets/{_plugin-vue_export-helper-DTysiCFw.js → _plugin-vue_export-helper-DXZ6IauR.js} +1 -1
  35. package/dist/client/assets/{app-Cj6XwFKm.js → app-Du5RZCLa.js} +1 -1
  36. package/dist/client/assets/app-lD2ZIoa4.js +1 -0
  37. package/dist/client/assets/{browser-DkMcmggy.js → browser-DcLLT85r.js} +1 -1
  38. package/dist/client/assets/chat-DJP53-h1.js +6 -0
  39. package/dist/client/assets/composables-CjmuafMO.js +1 -0
  40. package/dist/client/assets/{fade-in.cssr-CkQvzPci.js → fade-in.cssr-CkQxIGt5.js} +1 -1
  41. package/dist/client/assets/index-BqaBjme9.js +284 -0
  42. package/dist/client/assets/{jobs-B_aJckhl.js → jobs-BnXwbdOs.js} +1 -1
  43. package/dist/client/assets/{light-B5Gclu7u.js → light-BZfVIZ0W.js} +1 -1
  44. package/dist/client/assets/{light-UxDRH3KV.js → light-Bdfae2y3.js} +1 -1
  45. package/dist/client/assets/{light-CZF9XJcN.js → light-Dgzhn0jz.js} +1 -1
  46. package/dist/client/assets/{light-DSFGnDIn.js → light-Jn66LOmu.js} +1 -1
  47. package/dist/client/assets/{light-CwhS-d6R.js → light-ZuZhSzix.js} +1 -1
  48. package/dist/client/assets/{light-ZT7fg6Ar.js → light-ba4gwt1q.js} +1 -1
  49. package/dist/client/assets/{models-BVMLuhRp.js → models-mCQyOPuV.js} +1 -1
  50. package/dist/client/assets/{pinia-Cw7gKk_G.js → pinia-Jb4QOAzE.js} +1 -1
  51. package/dist/client/assets/{profiles-PYziKhyQ.js → profiles-PisKQKsd.js} +1 -1
  52. package/dist/client/assets/{router-DZ0K5mgk.js → router-qWVkGgap.js} +2 -2
  53. package/dist/client/assets/session-browser-prefs-Bh1and1f.js +1 -0
  54. package/dist/client/assets/{sessions-BmGawaJ1.js → sessions-smjenK_5.js} +1 -1
  55. package/dist/client/assets/{skills-D_FCqgt0.js → skills-BIFy9nXD.js} +1 -1
  56. package/dist/client/assets/{use-message-Cu2H6Jgg.js → use-message-Do_vF1fs.js} +1 -1
  57. package/dist/client/assets/{useTheme-CNjnv-8-.js → useTheme-BFm8wMmc.js} +1 -1
  58. package/dist/client/index.html +27 -27
  59. package/dist/server/index.js +456 -76
  60. package/dist/server/index.js.map +4 -4
  61. package/package.json +1 -1
  62. package/dist/client/assets/ChatView-BndkT9Ak.js +0 -127
  63. package/dist/client/assets/ChatView-Bp0GIJ5d.css +0 -1
  64. package/dist/client/assets/app-BPvkNIt-.js +0 -1
  65. package/dist/client/assets/chat-CJVCt7tU.js +0 -6
  66. package/dist/client/assets/composables-BVHKoSa5.js +0 -1
  67. package/dist/client/assets/index-6U3b19b2.js +0 -284
@@ -40045,7 +40045,19 @@ function resolveHermesBin() {
40045
40045
  return "hermes";
40046
40046
  }
40047
40047
  var HERMES_BIN2 = resolveHermesBin();
40048
- async function listSessions(source, limit) {
40048
+ function parseSessionExport(stdout) {
40049
+ const lines = stdout.trim().split("\n").filter(Boolean);
40050
+ const sessions2 = [];
40051
+ for (const line of lines) {
40052
+ try {
40053
+ const raw = JSON.parse(line);
40054
+ sessions2.push(raw);
40055
+ } catch {
40056
+ }
40057
+ }
40058
+ return sessions2;
40059
+ }
40060
+ async function exportSessionsRaw(source) {
40049
40061
  const args2 = ["sessions", "export", "-"];
40050
40062
  if (source) args2.push("--source", source);
40051
40063
  try {
@@ -40055,53 +40067,52 @@ async function listSessions(source, limit) {
40055
40067
  timeout: 3e4,
40056
40068
  ...execOpts
40057
40069
  });
40058
- const lines = stdout.trim().split("\n").filter(Boolean);
40059
- const sessions2 = [];
40060
- for (const line of lines) {
40061
- try {
40062
- const raw = JSON.parse(line);
40063
- let title = raw.title;
40064
- if (!title && raw.messages) {
40065
- const firstUser = raw.messages.find((m) => m.role === "user");
40066
- if (firstUser?.content) {
40067
- const t = String(firstUser.content).slice(0, 40);
40068
- title = t + (String(firstUser.content).length > 40 ? "..." : "");
40069
- }
40070
- }
40071
- sessions2.push({
40072
- id: raw.id,
40073
- source: raw.source,
40074
- user_id: raw.user_id,
40075
- model: raw.model,
40076
- title,
40077
- started_at: raw.started_at,
40078
- ended_at: raw.ended_at,
40079
- end_reason: raw.end_reason,
40080
- message_count: raw.message_count,
40081
- tool_call_count: raw.tool_call_count,
40082
- input_tokens: raw.input_tokens,
40083
- output_tokens: raw.output_tokens,
40084
- cache_read_tokens: raw.cache_read_tokens || 0,
40085
- cache_write_tokens: raw.cache_write_tokens || 0,
40086
- reasoning_tokens: raw.reasoning_tokens || 0,
40087
- billing_provider: raw.billing_provider,
40088
- estimated_cost_usd: raw.estimated_cost_usd,
40089
- actual_cost_usd: raw.actual_cost_usd ?? null,
40090
- cost_status: raw.cost_status || ""
40091
- });
40092
- } catch {
40093
- }
40094
- }
40095
- sessions2.sort((a, b) => b.started_at - a.started_at);
40096
- if (limit && limit > 0) {
40097
- return sessions2.slice(0, limit);
40098
- }
40099
- return sessions2;
40070
+ return parseSessionExport(stdout);
40100
40071
  } catch (err) {
40101
40072
  logger.error(err, "Hermes CLI: sessions export failed");
40102
40073
  throw new Error(`Failed to list sessions: ${err.message}`);
40103
40074
  }
40104
40075
  }
40076
+ async function listSessions(source, limit) {
40077
+ const raws = await exportSessionsRaw(source);
40078
+ const sessions2 = [];
40079
+ for (const raw of raws) {
40080
+ let title = raw.title;
40081
+ if (!title && raw.messages) {
40082
+ const firstUser = raw.messages.find((m) => m.role === "user");
40083
+ if (firstUser?.content) {
40084
+ const t = String(firstUser.content).slice(0, 40);
40085
+ title = t + (String(firstUser.content).length > 40 ? "..." : "");
40086
+ }
40087
+ }
40088
+ sessions2.push({
40089
+ id: raw.id,
40090
+ source: raw.source,
40091
+ user_id: raw.user_id,
40092
+ model: raw.model,
40093
+ title,
40094
+ started_at: raw.started_at,
40095
+ ended_at: raw.ended_at,
40096
+ end_reason: raw.end_reason,
40097
+ message_count: raw.message_count,
40098
+ tool_call_count: raw.tool_call_count,
40099
+ input_tokens: raw.input_tokens,
40100
+ output_tokens: raw.output_tokens,
40101
+ cache_read_tokens: raw.cache_read_tokens || 0,
40102
+ cache_write_tokens: raw.cache_write_tokens || 0,
40103
+ reasoning_tokens: raw.reasoning_tokens || 0,
40104
+ billing_provider: raw.billing_provider,
40105
+ estimated_cost_usd: raw.estimated_cost_usd,
40106
+ actual_cost_usd: raw.actual_cost_usd ?? null,
40107
+ cost_status: raw.cost_status || ""
40108
+ });
40109
+ }
40110
+ sessions2.sort((a, b) => b.started_at - a.started_at);
40111
+ if (limit && limit > 0) {
40112
+ return sessions2.slice(0, limit);
40113
+ }
40114
+ return sessions2;
40115
+ }
40105
40116
  async function getSession(id) {
40106
40117
  const args2 = ["sessions", "export", "-", "--session-id", id];
40107
40118
  try {
@@ -40110,10 +40121,9 @@ async function getSession(id) {
40110
40121
  timeout: 3e4,
40111
40122
  ...execOpts
40112
40123
  });
40113
- const lines = stdout.trim().split("\n").filter(Boolean);
40114
- if (lines.length === 0) return null;
40115
- if (!lines[0].startsWith("{")) return null;
40116
- const raw = JSON.parse(lines[0]);
40124
+ const raws = parseSessionExport(stdout);
40125
+ if (raws.length === 0) return null;
40126
+ const raw = raws[0];
40117
40127
  return {
40118
40128
  id: raw.id,
40119
40129
  source: raw.source,
@@ -40410,7 +40420,7 @@ async function importProfile(archivePath, name) {
40410
40420
  }
40411
40421
 
40412
40422
  // packages/server/src/controllers/health.ts
40413
- var LOCAL_VERSION = true ? "0.4.1" : (() => {
40423
+ var LOCAL_VERSION = true ? "0.4.2" : (() => {
40414
40424
  try {
40415
40425
  const { readFileSync: readFileSync8 } = null;
40416
40426
  const { resolve: resolve8 } = null;
@@ -40676,6 +40686,309 @@ async function handleUpdate(ctx) {
40676
40686
  var updateRoutes = new router_default();
40677
40687
  updateRoutes.post("/api/hermes/update", handleUpdate);
40678
40688
 
40689
+ // packages/server/src/services/hermes/conversations.ts
40690
+ var LINEAGE_TOLERANCE_SECONDS = 3;
40691
+ var LIVE_WINDOW_SECONDS = 300;
40692
+ var EXPORT_CACHE_TTL_MS = 3e4;
40693
+ var DEFAULT_CONVERSATION_LIMIT = 200;
40694
+ var SYNTHETIC_USER_PREFIXES = [
40695
+ "[system:",
40696
+ "you've reached the maximum number of tool-calling iterations allowed.",
40697
+ "you have reached the maximum number of tool-calling iterations allowed."
40698
+ ];
40699
+ var exportCache = /* @__PURE__ */ new Map();
40700
+ function cacheKey(source) {
40701
+ return source || "__all__";
40702
+ }
40703
+ function safeText(value) {
40704
+ if (typeof value === "string") return value;
40705
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
40706
+ return "";
40707
+ }
40708
+ function textFromContent(value) {
40709
+ if (typeof value === "string") return value;
40710
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
40711
+ if (Array.isArray(value)) {
40712
+ return value.map((item) => textFromContent(item).trim()).filter(Boolean).join("\n");
40713
+ }
40714
+ if (!value || typeof value !== "object") return "";
40715
+ const record = value;
40716
+ const directKeys = ["text", "content", "value"];
40717
+ for (const key of directKeys) {
40718
+ const direct = record[key];
40719
+ if (typeof direct === "string") return direct;
40720
+ if (Array.isArray(direct)) {
40721
+ const nested = textFromContent(direct);
40722
+ if (nested) return nested;
40723
+ }
40724
+ }
40725
+ const nestedKeys = ["parts", "children", "items"];
40726
+ for (const key of nestedKeys) {
40727
+ if (Array.isArray(record[key])) {
40728
+ const nested = textFromContent(record[key]);
40729
+ if (nested) return nested;
40730
+ }
40731
+ }
40732
+ const flattened = Object.values(record).map((entry) => textFromContent(entry).trim()).filter(Boolean).join("\n");
40733
+ if (flattened) return flattened;
40734
+ try {
40735
+ return JSON.stringify(record);
40736
+ } catch {
40737
+ return "";
40738
+ }
40739
+ }
40740
+ function normalizeText(value) {
40741
+ return textFromContent(value).replace(/\s+/g, " ").trim().toLowerCase();
40742
+ }
40743
+ function excerpt(value, width = 80) {
40744
+ const text = textFromContent(value).replace(/\s+/g, " ").trim();
40745
+ if (!text) return "";
40746
+ return text.length > width ? `${text.slice(0, width)}\u2026` : text;
40747
+ }
40748
+ function isSyntheticUserText(content) {
40749
+ const text = normalizeText(content);
40750
+ return SYNTHETIC_USER_PREFIXES.some((prefix) => text.startsWith(prefix));
40751
+ }
40752
+ function visibleHumanMessage(message2) {
40753
+ const role = safeText(message2.role);
40754
+ const content = textFromContent(message2.content).trim();
40755
+ if (!content) return false;
40756
+ if (role !== "user" && role !== "assistant") return false;
40757
+ if (role === "user" && isSyntheticUserText(content)) return false;
40758
+ return true;
40759
+ }
40760
+ function firstVisibleHumanText(messages) {
40761
+ const firstVisible = messages.find(visibleHumanMessage);
40762
+ return firstVisible ? textFromContent(firstVisible.content).trim() : "";
40763
+ }
40764
+ function maxMessageTimestamp(messages) {
40765
+ return messages.reduce((max, message2) => {
40766
+ const timestamp2 = Number(message2.timestamp || 0);
40767
+ return Number.isFinite(timestamp2) && timestamp2 > max ? timestamp2 : max;
40768
+ }, 0);
40769
+ }
40770
+ function enrichSession(session, nowSeconds) {
40771
+ const messages = Array.isArray(session.messages) ? session.messages : [];
40772
+ const preview = excerpt(firstVisibleHumanText(messages));
40773
+ const lastActive = maxMessageTimestamp(messages) || Number(session.ended_at || session.started_at || 0);
40774
+ const endedAt = session.ended_at ?? null;
40775
+ return {
40776
+ ...session,
40777
+ parent_session_id: session.parent_session_id ?? null,
40778
+ preview,
40779
+ last_active: lastActive,
40780
+ is_active: endedAt == null && nowSeconds - lastActive <= LIVE_WINDOW_SECONDS
40781
+ };
40782
+ }
40783
+ function sortByRecency(items) {
40784
+ return [...items].sort((a, b) => {
40785
+ if (b.last_active !== a.last_active) return b.last_active - a.last_active;
40786
+ if (b.started_at !== a.started_at) return b.started_at - a.started_at;
40787
+ return a.id.localeCompare(b.id);
40788
+ });
40789
+ }
40790
+ function timingMatchesParent(parent, child) {
40791
+ if (!parent || !child || parent.ended_at == null) return false;
40792
+ return Math.abs(Number(child.started_at || 0) - Number(parent.ended_at || 0)) <= LINEAGE_TOLERANCE_SECONDS;
40793
+ }
40794
+ function isBranchRoot(session, byId) {
40795
+ if (!session?.parent_session_id) return false;
40796
+ const parent = byId.get(session.parent_session_id);
40797
+ return !!parent && parent.end_reason === "branched" && timingMatchesParent(parent, session);
40798
+ }
40799
+ function isVisibleRoot(session, byId) {
40800
+ if (!session || session.source === "tool") return false;
40801
+ return session.parent_session_id == null || isBranchRoot(session, byId);
40802
+ }
40803
+ function continuationCandidates(parent, byId, childrenByParent) {
40804
+ const childIds = childrenByParent.get(parent.id) || [];
40805
+ return childIds.map((childId) => byId.get(childId)).filter((child) => !!child).filter((child) => child.source !== "tool").filter((child) => child.source === parent.source).filter((child) => timingMatchesParent(parent, child)).sort((a, b) => {
40806
+ const aDelta = Math.abs(Number(a.started_at || 0) - Number(parent.ended_at || 0));
40807
+ const bDelta = Math.abs(Number(b.started_at || 0) - Number(parent.ended_at || 0));
40808
+ if (aDelta !== bDelta) return aDelta - bDelta;
40809
+ return a.id.localeCompare(b.id);
40810
+ });
40811
+ }
40812
+ function nextContinuationChild(parent, byId, childrenByParent) {
40813
+ if (parent.end_reason !== "compression") return null;
40814
+ const candidates = continuationCandidates(parent, byId, childrenByParent);
40815
+ if (candidates.length === 1) return candidates[0];
40816
+ const exactPreviewMatches = candidates.filter((child) => {
40817
+ const childPreview = normalizeText(child.preview);
40818
+ const parentPreview = normalizeText(parent.preview);
40819
+ return !!childPreview && childPreview === parentPreview;
40820
+ });
40821
+ if (exactPreviewMatches.length === 1) return exactPreviewMatches[0];
40822
+ return null;
40823
+ }
40824
+ function collectConversationChain(rootId, byId, childrenByParent) {
40825
+ const chain = [];
40826
+ const seen = /* @__PURE__ */ new Set();
40827
+ let current = byId.get(rootId) || null;
40828
+ while (current && !seen.has(current.id)) {
40829
+ chain.push(current);
40830
+ seen.add(current.id);
40831
+ current = nextContinuationChild(current, byId, childrenByParent);
40832
+ }
40833
+ return chain;
40834
+ }
40835
+ function sessionMessages(session) {
40836
+ return Array.isArray(session.messages) ? session.messages : [];
40837
+ }
40838
+ function normalizeVisibleMessage(message2, session, index) {
40839
+ if (!visibleHumanMessage(message2)) return null;
40840
+ const role = safeText(message2.role);
40841
+ const content = textFromContent(message2.content).trim();
40842
+ if (role !== "user" && role !== "assistant") return null;
40843
+ if (!content) return null;
40844
+ const rawTimestamp = Number(message2.timestamp);
40845
+ const timestamp2 = Number.isFinite(rawTimestamp) && rawTimestamp > 0 ? rawTimestamp : Number(session.ended_at || session.started_at || 0);
40846
+ const id = message2.id ?? `${session.id}:${index}:${timestamp2}`;
40847
+ return {
40848
+ id,
40849
+ session_id: safeText(message2.session_id || session.id),
40850
+ role,
40851
+ content,
40852
+ timestamp: timestamp2
40853
+ };
40854
+ }
40855
+ function visibleMessagesForSessions(sessions2) {
40856
+ return sessions2.flatMap((session) => sessionMessages(session).map((message2, index) => normalizeVisibleMessage({ ...message2, session_id: safeText(message2.session_id || session.id) }, session, index))).filter((message2) => !!message2).sort((a, b) => {
40857
+ if (a.timestamp !== b.timestamp) return a.timestamp - b.timestamp;
40858
+ return String(a.id).localeCompare(String(b.id));
40859
+ });
40860
+ }
40861
+ function hasVisibleHumanMessages(sessions2) {
40862
+ return visibleMessagesForSessions(sessions2).length > 0;
40863
+ }
40864
+ function toSummary(session) {
40865
+ return {
40866
+ id: session.id,
40867
+ source: safeText(session.source),
40868
+ model: safeText(session.model),
40869
+ title: session.title ?? null,
40870
+ started_at: Number(session.started_at || 0),
40871
+ ended_at: session.ended_at ?? null,
40872
+ last_active: session.last_active,
40873
+ message_count: Number(session.message_count || 0),
40874
+ tool_call_count: Number(session.tool_call_count || 0),
40875
+ input_tokens: Number(session.input_tokens || 0),
40876
+ output_tokens: Number(session.output_tokens || 0),
40877
+ cache_read_tokens: Number(session.cache_read_tokens || 0),
40878
+ cache_write_tokens: Number(session.cache_write_tokens || 0),
40879
+ reasoning_tokens: Number(session.reasoning_tokens || 0),
40880
+ billing_provider: session.billing_provider ?? null,
40881
+ estimated_cost_usd: Number(session.estimated_cost_usd || 0),
40882
+ actual_cost_usd: session.actual_cost_usd ?? null,
40883
+ cost_status: safeText(session.cost_status),
40884
+ preview: session.preview,
40885
+ is_active: session.is_active,
40886
+ thread_session_count: 1
40887
+ };
40888
+ }
40889
+ function aggregateSummary(rootId, byId, childrenByParent) {
40890
+ const chain = collectConversationChain(rootId, byId, childrenByParent);
40891
+ if (!chain.length || !hasVisibleHumanMessages(chain)) return null;
40892
+ const root = chain[0];
40893
+ const last = chain[chain.length - 1];
40894
+ const title = root.title || excerpt(firstVisibleHumanText(chain.flatMap(sessionMessages)), 72) || null;
40895
+ const preview = root.preview || excerpt(firstVisibleHumanText(chain.flatMap(sessionMessages)));
40896
+ const costStatuses = Array.from(new Set(chain.map((session) => safeText(session.cost_status)).filter(Boolean)));
40897
+ return {
40898
+ ...toSummary(root),
40899
+ title,
40900
+ preview,
40901
+ model: safeText(last?.model || root.model),
40902
+ ended_at: last?.ended_at ?? null,
40903
+ last_active: Math.max(...chain.map((session) => session.last_active)),
40904
+ is_active: chain.some((session) => session.is_active),
40905
+ billing_provider: last?.billing_provider ?? root.billing_provider ?? null,
40906
+ cost_status: costStatuses.length === 1 ? costStatuses[0] : "mixed",
40907
+ thread_session_count: chain.length,
40908
+ message_count: chain.reduce((sum, session) => sum + Number(session.message_count || 0), 0),
40909
+ tool_call_count: chain.reduce((sum, session) => sum + Number(session.tool_call_count || 0), 0),
40910
+ input_tokens: chain.reduce((sum, session) => sum + Number(session.input_tokens || 0), 0),
40911
+ output_tokens: chain.reduce((sum, session) => sum + Number(session.output_tokens || 0), 0),
40912
+ cache_read_tokens: chain.reduce((sum, session) => sum + Number(session.cache_read_tokens || 0), 0),
40913
+ cache_write_tokens: chain.reduce((sum, session) => sum + Number(session.cache_write_tokens || 0), 0),
40914
+ reasoning_tokens: chain.reduce((sum, session) => sum + Number(session.reasoning_tokens || 0), 0),
40915
+ estimated_cost_usd: chain.reduce((sum, session) => sum + Number(session.estimated_cost_usd || 0), 0),
40916
+ actual_cost_usd: chain.reduce((sum, session) => {
40917
+ const actual = session.actual_cost_usd;
40918
+ if (actual == null) return sum;
40919
+ return (sum || 0) + Number(actual);
40920
+ }, null)
40921
+ };
40922
+ }
40923
+ async function loadSessions(source) {
40924
+ const key = cacheKey(source);
40925
+ const nowMs = Date.now();
40926
+ const cached = exportCache.get(key);
40927
+ const raws = cached && cached.expires_at_ms > nowMs ? cached.sessions : await exportSessionsRaw(source);
40928
+ if (!cached || cached.expires_at_ms <= nowMs) {
40929
+ exportCache.set(key, {
40930
+ expires_at_ms: nowMs + EXPORT_CACHE_TTL_MS,
40931
+ sessions: raws
40932
+ });
40933
+ }
40934
+ const nowSeconds = nowMs / 1e3;
40935
+ return raws.map((raw) => enrichSession(raw, nowSeconds));
40936
+ }
40937
+ async function listConversationSummaries(options = {}) {
40938
+ const humanOnly = options.humanOnly !== false;
40939
+ const limit = options.limit && options.limit > 0 ? options.limit : DEFAULT_CONVERSATION_LIMIT;
40940
+ const sessions2 = await loadSessions(options.source);
40941
+ const byId = new Map(sessions2.map((session) => [session.id, session]));
40942
+ const childrenByParent = /* @__PURE__ */ new Map();
40943
+ for (const session of sessions2) {
40944
+ const key = session.parent_session_id ?? null;
40945
+ const siblings = childrenByParent.get(key) || [];
40946
+ siblings.push(session.id);
40947
+ childrenByParent.set(key, siblings);
40948
+ }
40949
+ if (!humanOnly) {
40950
+ return sortByRecency(
40951
+ sessions2.filter((session) => session.source !== "tool").map(toSummary)
40952
+ ).slice(0, limit);
40953
+ }
40954
+ const summaries = sessions2.filter((session) => isVisibleRoot(session, byId)).map((session) => aggregateSummary(session.id, byId, childrenByParent)).filter((summary) => !!summary);
40955
+ return sortByRecency(summaries).slice(0, limit);
40956
+ }
40957
+ async function getConversationDetail(sessionId, options = {}) {
40958
+ const humanOnly = options.humanOnly !== false;
40959
+ const sessions2 = await loadSessions(options.source);
40960
+ const byId = new Map(sessions2.map((session) => [session.id, session]));
40961
+ const childrenByParent = /* @__PURE__ */ new Map();
40962
+ for (const session of sessions2) {
40963
+ const key = session.parent_session_id ?? null;
40964
+ const siblings = childrenByParent.get(key) || [];
40965
+ siblings.push(session.id);
40966
+ childrenByParent.set(key, siblings);
40967
+ }
40968
+ if (!humanOnly) {
40969
+ const session = byId.get(sessionId);
40970
+ if (!session || session.source === "tool") return null;
40971
+ const messages2 = visibleMessagesForSessions([session]);
40972
+ return {
40973
+ session_id: sessionId,
40974
+ messages: messages2,
40975
+ visible_count: messages2.length,
40976
+ thread_session_count: 1
40977
+ };
40978
+ }
40979
+ const root = byId.get(sessionId);
40980
+ if (!isVisibleRoot(root, byId)) return null;
40981
+ const chain = collectConversationChain(sessionId, byId, childrenByParent);
40982
+ const messages = visibleMessagesForSessions(chain);
40983
+ if (!messages.length) return null;
40984
+ return {
40985
+ session_id: sessionId,
40986
+ messages,
40987
+ visible_count: messages.length,
40988
+ thread_session_count: chain.length
40989
+ };
40990
+ }
40991
+
40679
40992
  // packages/server/src/services/hermes/sessions-db.ts
40680
40993
  init_hermes_profile();
40681
40994
  var SQLITE_AVAILABLE = (() => {
@@ -40787,6 +41100,33 @@ async function listSessionSummaries(source, limit = 2e3) {
40787
41100
 
40788
41101
  // packages/server/src/controllers/hermes/sessions.ts
40789
41102
  init_logger();
41103
+ function parseHumanOnly(value) {
41104
+ if (typeof value !== "string") return true;
41105
+ return value !== "false" && value !== "0";
41106
+ }
41107
+ function parseLimit(value) {
41108
+ if (typeof value !== "string") return void 0;
41109
+ const parsed = parseInt(value, 10);
41110
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
41111
+ }
41112
+ async function listConversations(ctx) {
41113
+ const source = ctx.query.source || void 0;
41114
+ const humanOnly = parseHumanOnly(ctx.query.humanOnly);
41115
+ const limit = parseLimit(ctx.query.limit);
41116
+ const sessions2 = await listConversationSummaries({ source, humanOnly, limit });
41117
+ ctx.body = { sessions: sessions2 };
41118
+ }
41119
+ async function getConversationMessages(ctx) {
41120
+ const source = ctx.query.source || void 0;
41121
+ const humanOnly = parseHumanOnly(ctx.query.humanOnly);
41122
+ const detail = await getConversationDetail(ctx.params.id, { source, humanOnly });
41123
+ if (!detail) {
41124
+ ctx.status = 404;
41125
+ ctx.body = { error: "Conversation not found" };
41126
+ return;
41127
+ }
41128
+ ctx.body = detail;
41129
+ }
40790
41130
  async function list(ctx) {
40791
41131
  const source = ctx.query.source || void 0;
40792
41132
  const limit = ctx.query.limit ? parseInt(ctx.query.limit, 10) : void 0;
@@ -40836,6 +41176,8 @@ async function rename(ctx) {
40836
41176
 
40837
41177
  // packages/server/src/routes/hermes/sessions.ts
40838
41178
  var sessionRoutes = new router_default();
41179
+ sessionRoutes.get("/api/hermes/sessions/conversations", listConversations);
41180
+ sessionRoutes.get("/api/hermes/sessions/conversations/:id/messages", getConversationMessages);
40839
41181
  sessionRoutes.get("/api/hermes/sessions", list);
40840
41182
  sessionRoutes.get("/api/hermes/sessions/:id", get);
40841
41183
  sessionRoutes.delete("/api/hermes/sessions/:id", remove);
@@ -41073,6 +41415,7 @@ init_hermes_profile();
41073
41415
  init_logger();
41074
41416
  var PROVIDER_ENV_MAP = {
41075
41417
  openrouter: { api_key_env: "OPENROUTER_API_KEY", base_url_env: "" },
41418
+ "glm-coding-plan": { api_key_env: "", base_url_env: "" },
41076
41419
  zai: { api_key_env: "GLM_API_KEY", base_url_env: "" },
41077
41420
  "kimi-coding-cn": { api_key_env: "KIMI_CN_API_KEY", base_url_env: "" },
41078
41421
  moonshot: { api_key_env: "MOONSHOT_API_KEY", base_url_env: "" },
@@ -41459,8 +41802,8 @@ var PROVIDER_PRESETS = [
41459
41802
  models: ["glm-5.1", "glm-5", "glm-5v-turbo", "glm-5-turbo", "glm-4.7", "glm-4.5", "glm-4.5-flash"]
41460
41803
  },
41461
41804
  {
41462
- label: "GLMCodingPlan",
41463
- value: "glm",
41805
+ label: "GLM-Coding-Plan",
41806
+ value: "glm-coding-plan",
41464
41807
  builtin: true,
41465
41808
  base_url: "https://api.z.ai/api/anthropic",
41466
41809
  models: ["glm-5.1", "glm-5", "glm-5-turbo", "glm-4.7", "glm-4.5", "glm-4.5-flash"]
@@ -41732,7 +42075,9 @@ async function getAvailable(ctx) {
41732
42075
  if (!cp.base_url) return null;
41733
42076
  const providerKey = `custom:${cp.name.trim().toLowerCase().replace(/ /g, "-")}`;
41734
42077
  const baseUrl = cp.base_url.replace(/\/+$/, "");
41735
- let models = [cp.model];
42078
+ const bareKey = cp.name.trim().toLowerCase().replace(/ /g, "-");
42079
+ const builtinPreset = PROVIDER_PRESETS.find((p) => p.value === bareKey);
42080
+ let models = builtinPreset?.models?.length ? [...builtinPreset.models] : [cp.model];
41736
42081
  if (cp.api_key) {
41737
42082
  try {
41738
42083
  const fetched = await fetchProviderModels(baseUrl, cp.api_key);
@@ -41740,7 +42085,9 @@ async function getAvailable(ctx) {
41740
42085
  } catch {
41741
42086
  }
41742
42087
  }
41743
- return { providerKey, label: cp.name, base_url: baseUrl, models, api_key: cp.api_key || "" };
42088
+ const label = builtinPreset?.label || cp.name;
42089
+ const presetBaseUrl = builtinPreset?.base_url || "";
42090
+ return { providerKey, label, base_url: presetBaseUrl || baseUrl, models, api_key: cp.api_key || "" };
41744
42091
  })
41745
42092
  );
41746
42093
  for (const result of customFetches) {
@@ -41811,6 +42158,7 @@ init_hermes_profile();
41811
42158
  init_logger();
41812
42159
  async function create2(ctx) {
41813
42160
  const { name, base_url, api_key, model, providerKey } = ctx.request.body;
42161
+ console.log(name, base_url, api_key, model, providerKey);
41814
42162
  if (!name || !base_url || !model) {
41815
42163
  ctx.status = 400;
41816
42164
  ctx.body = { error: "Missing name, base_url, or model" };
@@ -41824,36 +42172,55 @@ async function create2(ctx) {
41824
42172
  try {
41825
42173
  const poolKey = providerKey || `custom:${name.trim().toLowerCase().replace(/ /g, "-")}`;
41826
42174
  const isBuiltin = poolKey in PROVIDER_ENV_MAP;
42175
+ const config2 = await readConfigYaml();
42176
+ if (typeof config2.model !== "object" || config2.model === null) {
42177
+ config2.model = {};
42178
+ }
41827
42179
  if (!isBuiltin) {
41828
- const config3 = await readConfigYaml();
41829
- if (!Array.isArray(config3.custom_providers)) {
41830
- config3.custom_providers = [];
42180
+ if (!Array.isArray(config2.custom_providers)) {
42181
+ config2.custom_providers = [];
41831
42182
  }
41832
- const existing = config3.custom_providers.find(
41833
- (e) => `custom:${e.name.trim().toLowerCase().replace(/ /g, "-")}` === poolKey
42183
+ const existing = config2.custom_providers.find(
42184
+ (e) => `custom:${e.name}` === poolKey
41834
42185
  );
41835
42186
  if (existing) {
41836
42187
  existing.base_url = base_url;
41837
42188
  existing.api_key = api_key;
41838
42189
  existing.model = model;
41839
42190
  } else {
41840
- config3.custom_providers.push({ name, base_url, api_key, model });
42191
+ config2.custom_providers.push({ name: name.trim().toLowerCase().replace(/ /g, "-"), base_url, api_key, model });
41841
42192
  }
41842
- await writeConfigYaml(config3);
41843
- }
41844
- const envMapping = isBuiltin ? PROVIDER_ENV_MAP[poolKey] || PROVIDER_ENV_MAP[providerKey || ""] : null;
41845
- if (envMapping) {
41846
- await saveEnvValue(envMapping.api_key_env, api_key);
41847
- if (envMapping.base_url_env) {
41848
- await saveEnvValue(envMapping.base_url_env, base_url);
42193
+ config2.model.default = model;
42194
+ config2.model.provider = poolKey;
42195
+ } else {
42196
+ console.log(PROVIDER_ENV_MAP[poolKey]);
42197
+ if (PROVIDER_ENV_MAP[poolKey].api_key_env) {
42198
+ await saveEnvValue(PROVIDER_ENV_MAP[poolKey].api_key_env, api_key);
42199
+ if (PROVIDER_ENV_MAP[poolKey].base_url_env) {
42200
+ await saveEnvValue(PROVIDER_ENV_MAP[poolKey].base_url_env, base_url);
42201
+ }
42202
+ config2.model.default = model;
42203
+ config2.model.provider = poolKey;
42204
+ } else {
42205
+ if (!Array.isArray(config2.custom_providers)) {
42206
+ config2.custom_providers = [];
42207
+ }
42208
+ const existing = config2.custom_providers.find(
42209
+ (e) => `custom:${e.name}` === `custom:${poolKey}`
42210
+ );
42211
+ if (existing) {
42212
+ existing.base_url = base_url;
42213
+ existing.api_key = api_key;
42214
+ existing.model = model;
42215
+ } else {
42216
+ config2.custom_providers.push({ name: poolKey, base_url, api_key, model });
42217
+ }
42218
+ config2.model.default = model;
42219
+ config2.model.provider = `custom:${poolKey}`;
41849
42220
  }
41850
42221
  }
41851
- const config2 = await readConfigYaml();
41852
- if (typeof config2.model !== "object" || config2.model === null) {
41853
- config2.model = {};
41854
- }
41855
- config2.model.default = model;
41856
- config2.model.provider = poolKey;
42222
+ delete config2.model.base_url;
42223
+ delete config2.model.api_key;
41857
42224
  await writeConfigYaml(config2);
41858
42225
  try {
41859
42226
  await restartGateway();
@@ -41955,17 +42322,27 @@ async function remove3(ctx) {
41955
42322
  if (currentProvider === poolKey) {
41956
42323
  const freshConfig = await readConfigYaml();
41957
42324
  const remaining = Array.isArray(freshConfig.custom_providers) ? freshConfig.custom_providers : [];
41958
- const fallbackCp = remaining[0];
41959
- if (fallbackCp) {
42325
+ if (remaining.length > 0) {
42326
+ const fallbackCp = remaining[0];
41960
42327
  const fallbackKey = `custom:${fallbackCp.name.trim().toLowerCase().replace(/ /g, "-")}`;
41961
42328
  if (typeof freshConfig.model !== "object" || freshConfig.model === null) {
41962
42329
  freshConfig.model = {};
41963
42330
  }
41964
42331
  freshConfig.model.default = fallbackCp.model;
41965
42332
  freshConfig.model.provider = fallbackKey;
42333
+ delete freshConfig.model.base_url;
42334
+ delete freshConfig.model.api_key;
42335
+ await writeConfigYaml(freshConfig);
42336
+ } else {
42337
+ freshConfig.model = {};
41966
42338
  await writeConfigYaml(freshConfig);
41967
42339
  }
41968
42340
  }
42341
+ try {
42342
+ await restartGateway();
42343
+ } catch (e) {
42344
+ logger.error(e, "Gateway restart failed");
42345
+ }
41969
42346
  ctx.body = { success: true };
41970
42347
  } catch (err) {
41971
42348
  ctx.status = 500;
@@ -46668,14 +47045,17 @@ async function proxy(ctx) {
46668
47045
  const profile = resolveProfile(ctx);
46669
47046
  const upstream = resolveUpstream(ctx);
46670
47047
  const upstreamPath = ctx.path.replace(/^\/api\/hermes\/v1/, "/v1").replace(/^\/api\/hermes/, "/api");
46671
- const url2 = `${upstream}${upstreamPath}${ctx.search || ""}`;
47048
+ const params = new URLSearchParams(ctx.search || "");
47049
+ params.delete("token");
47050
+ const search = params.toString();
47051
+ const url2 = `${upstream}${upstreamPath}${search ? `?${search}` : ""}`;
46672
47052
  const headers = {};
46673
47053
  for (const [key, value] of Object.entries(ctx.headers)) {
46674
47054
  if (value == null) continue;
46675
47055
  const lower = key.toLowerCase();
46676
47056
  if (lower === "host") {
46677
47057
  headers["host"] = new URL(upstream).host;
46678
- } else if (lower === "origin" || lower === "referer" || lower === "connection") {
47058
+ } else if (lower === "origin" || lower === "referer" || lower === "connection" || lower === "authorization") {
46679
47059
  continue;
46680
47060
  } else {
46681
47061
  const v = Array.isArray(value) ? value[0] : value;
@@ -46783,7 +47163,7 @@ var import_path13 = require("path");
46783
47163
  var import_promises13 = require("fs/promises");
46784
47164
  var import_fs12 = require("fs");
46785
47165
  init_logger();
46786
- var APP_VERSION = true ? "0.4.1" : (() => {
47166
+ var APP_VERSION = true ? "0.4.2" : (() => {
46787
47167
  try {
46788
47168
  return JSON.parse(readFileSync7((0, import_path13.resolve)(__dirname, "../../package.json"), "utf-8")).version;
46789
47169
  } catch {