@hermespilot/link 0.8.3 → 0.8.5-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7721,7 +7721,7 @@ function isConversationMissingError(error) {
7721
7721
  }
7722
7722
 
7723
7723
  // src/constants.ts
7724
- var LINK_VERSION = "0.8.3";
7724
+ var LINK_VERSION = "0.8.5-beta.0";
7725
7725
  var LINK_COMMAND = "hermeslink";
7726
7726
  var LINK_DEFAULT_PORT = 52379;
7727
7727
  var LINK_RUNTIME_DIR_NAME = ".hermeslink";
@@ -14803,6 +14803,8 @@ function toSummary(manifest, snapshot, profile) {
14803
14803
  id: manifest.id,
14804
14804
  title: manifest.title,
14805
14805
  title_source: manifest.title_source,
14806
+ category: manifestHasCronSignal(manifest, snapshot) ? "cron" : "chat",
14807
+ is_cron: manifestHasCronSignal(manifest, snapshot),
14806
14808
  workspace_id: manifest.workspace_id ?? null,
14807
14809
  created_at: manifest.created_at,
14808
14810
  updated_at: manifest.updated_at,
@@ -14823,6 +14825,34 @@ function toSummary(manifest, snapshot, profile) {
14823
14825
  } : null
14824
14826
  };
14825
14827
  }
14828
+ function manifestHasCronSignal(manifest, snapshot) {
14829
+ const sessionIds = [
14830
+ manifest.hermes_session_id,
14831
+ ...manifest.hermes_session_ids ?? [],
14832
+ manifest.hermes_lineage?.root_session_id,
14833
+ manifest.hermes_lineage?.current_session_id,
14834
+ ...manifest.hermes_lineage?.session_ids ?? []
14835
+ ].map((value) => value?.trim()).filter((value) => Boolean(value));
14836
+ if (sessionIds.some((sessionId) => sessionId.startsWith("cron_"))) {
14837
+ return true;
14838
+ }
14839
+ if (snapshot?.messages.some((message) => {
14840
+ const raw = message.raw;
14841
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
14842
+ const format = raw.format;
14843
+ if (format === "hermes-link-cron-delivery") {
14844
+ return true;
14845
+ }
14846
+ }
14847
+ return Boolean(message.hermes?.cron_job_id || message.hermes?.cron_output_path);
14848
+ })) {
14849
+ return true;
14850
+ }
14851
+ return normalizeTitle(manifest.title) === "HermesLink \u5B9A\u65F6\u4EFB\u52A1";
14852
+ }
14853
+ function normalizeTitle(value) {
14854
+ return value.trim();
14855
+ }
14826
14856
  function isDefaultConversationTitle(title) {
14827
14857
  const normalized = title.trim().toLowerCase();
14828
14858
  return !normalized || normalized === "untitled" || normalized === "new chat" || normalized === DEFAULT_CONVERSATION_TITLE.toLowerCase();
@@ -15902,6 +15932,9 @@ function collectHermesSessionIds(manifest, snapshot) {
15902
15932
  return normalizeHermesSessionIds([
15903
15933
  manifest.hermes_session_id,
15904
15934
  ...manifest.hermes_session_ids ?? [],
15935
+ manifest.hermes_lineage?.root_session_id,
15936
+ manifest.hermes_lineage?.current_session_id,
15937
+ ...manifest.hermes_lineage?.session_ids ?? [],
15905
15938
  ...Object.values(manifest.profile_session_ids ?? {}),
15906
15939
  ...snapshot.runs.map((run) => run.hermes_session_id)
15907
15940
  ]);
@@ -15930,6 +15963,11 @@ function collectHermesSessionDeleteTargets(manifest, snapshot) {
15930
15963
  for (const sessionId of manifest.hermes_session_ids ?? []) {
15931
15964
  add(sessionId, defaultProfile);
15932
15965
  }
15966
+ add(manifest.hermes_lineage?.root_session_id, defaultProfile);
15967
+ add(manifest.hermes_lineage?.current_session_id, defaultProfile);
15968
+ for (const sessionId of manifest.hermes_lineage?.session_ids ?? []) {
15969
+ add(sessionId, defaultProfile);
15970
+ }
15933
15971
  for (const [profileName, sessionId] of Object.entries(
15934
15972
  manifest.profile_session_ids ?? {}
15935
15973
  )) {
@@ -19735,14 +19773,16 @@ var ConversationQueryCoordinator = class {
19735
19773
  status,
19736
19774
  limit,
19737
19775
  cursor,
19738
- workspace: options.workspace
19776
+ workspace: options.workspace,
19777
+ category: options.category
19739
19778
  }),
19740
19779
  listPage: (pageCursor) => listConversationStatsPage(this.deps.paths, {
19741
19780
  status,
19742
19781
  limit,
19743
19782
  cursor: pageCursor,
19744
19783
  workspace: options.workspace
19745
- })
19784
+ }),
19785
+ category: options.category
19746
19786
  });
19747
19787
  }
19748
19788
  async searchConversationPage(options = {}) {
@@ -19790,7 +19830,8 @@ var ConversationQueryCoordinator = class {
19790
19830
  usedIndex = true;
19791
19831
  const summaries = await this.summarizeIndexedConversations(
19792
19832
  input.status,
19793
- indexedPage.records
19833
+ indexedPage.records,
19834
+ input.category
19794
19835
  );
19795
19836
  for (const summary of summaries) {
19796
19837
  if (!seenConversationIds.add(summary.id)) {
@@ -19830,15 +19871,18 @@ var ConversationQueryCoordinator = class {
19830
19871
  }
19831
19872
  };
19832
19873
  }
19833
- async listConversationsFromStore(status = "active", workspace) {
19874
+ async listConversationsFromStore(status = "active", options = {}) {
19834
19875
  const summaries = [];
19835
19876
  for (const conversationId of await this.deps.store.listConversationIds()) {
19836
19877
  const manifest = await this.deps.store.readManifest(conversationId).catch(() => null);
19837
- if (!manifest || manifest.status !== status || !matchesWorkspaceFilter(manifest, workspace)) {
19878
+ if (!manifest || manifest.status !== status || !matchesWorkspaceFilter(manifest, options.workspace)) {
19838
19879
  continue;
19839
19880
  }
19840
19881
  const snapshot = await this.deps.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
19841
19882
  const refreshed = await this.deps.metadata.refreshTitleFromHermes(manifest, { snapshot }).catch(() => manifest);
19883
+ if (!matchesConversationCategory(refreshed, snapshot, options.category)) {
19884
+ continue;
19885
+ }
19842
19886
  summaries.push(await this.summarizeConversation(refreshed, snapshot));
19843
19887
  }
19844
19888
  return summaries.sort(
@@ -19846,10 +19890,10 @@ var ConversationQueryCoordinator = class {
19846
19890
  );
19847
19891
  }
19848
19892
  async listConversationPageFromStore(input) {
19849
- const all = await this.listConversationsFromStore(
19850
- input.status,
19851
- input.workspace
19852
- );
19893
+ const all = await this.listConversationsFromStore(input.status, {
19894
+ workspace: input.workspace,
19895
+ category: input.category
19896
+ });
19853
19897
  const startIndex = input.cursor ? all.findIndex(
19854
19898
  (summary) => isAfterConversationListCursor(summary, input.cursor)
19855
19899
  ) : 0;
@@ -19868,7 +19912,7 @@ var ConversationQueryCoordinator = class {
19868
19912
  }
19869
19913
  };
19870
19914
  }
19871
- async summarizeIndexedConversations(status, records) {
19915
+ async summarizeIndexedConversations(status, records, category = "all") {
19872
19916
  const summaries = [];
19873
19917
  for (const record of records) {
19874
19918
  const manifest = await this.deps.store.readManifest(record.conversationId).catch(() => null);
@@ -19877,6 +19921,9 @@ var ConversationQueryCoordinator = class {
19877
19921
  }
19878
19922
  const snapshot = await this.deps.store.readSnapshot(record.conversationId).catch(() => emptySnapshot2());
19879
19923
  const refreshed = await this.deps.metadata.refreshTitleFromHermes(manifest, { snapshot }).catch(() => manifest);
19924
+ if (!matchesConversationCategory(refreshed, snapshot, category)) {
19925
+ continue;
19926
+ }
19880
19927
  summaries.push(await this.summarizeConversation(refreshed, snapshot));
19881
19928
  }
19882
19929
  return summaries;
@@ -20145,6 +20192,13 @@ function matchesWorkspaceFilter(manifest, filter) {
20145
20192
  }
20146
20193
  return workspaceId === filter.workspaceId;
20147
20194
  }
20195
+ function matchesConversationCategory(manifest, snapshot, category = "all") {
20196
+ if (category === "all") {
20197
+ return true;
20198
+ }
20199
+ const isCron = manifestHasCronSignal(manifest, snapshot);
20200
+ return category === "cron" ? isCron : !isCron;
20201
+ }
20148
20202
  function isAfterConversationListCursor(summary, cursor) {
20149
20203
  return summary.updated_at < cursor.updatedAt || summary.updated_at === cursor.updatedAt && summary.id < cursor.conversationId;
20150
20204
  }
@@ -21392,6 +21446,7 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
21392
21446
  eligible_sessions: 0,
21393
21447
  imported_count: 0,
21394
21448
  reprojected_count: 0,
21449
+ hermes_archived_count: 0,
21395
21450
  skipped_existing: 0,
21396
21451
  skipped_hidden: 0,
21397
21452
  skipped_empty_transcript: 0,
@@ -21417,6 +21472,21 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
21417
21472
  continue;
21418
21473
  }
21419
21474
  if (isHiddenSession(session)) {
21475
+ if (isArchivedSession(session)) {
21476
+ const candidate = { profileName, profileDir, dbPath, session };
21477
+ result.hermes_archived_count += await archiveKnownHermesConversation({
21478
+ paths,
21479
+ store,
21480
+ knownHermesSessions,
21481
+ candidate
21482
+ }).catch((error) => {
21483
+ result.errors.push({
21484
+ profile: profileName,
21485
+ message: error instanceof Error ? error.message : String(error)
21486
+ });
21487
+ return 0;
21488
+ });
21489
+ }
21420
21490
  result.skipped_hidden += 1;
21421
21491
  continue;
21422
21492
  }
@@ -21502,7 +21572,7 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
21502
21572
  }
21503
21573
  }
21504
21574
  }
21505
- if (result.imported_count > 0 || result.reprojected_count > 0 || result.errors.length > 0) {
21575
+ if (result.imported_count > 0 || result.reprojected_count > 0 || result.hermes_archived_count > 0 || result.errors.length > 0) {
21506
21576
  void logger.info("hermes_session_sync_completed", { ...result });
21507
21577
  } else {
21508
21578
  void logger.debug("hermes_session_sync_completed", { ...result });
@@ -21613,15 +21683,6 @@ async function syncHermesConversationMessages(paths, logger, input) {
21613
21683
  for (const candidate of candidates) {
21614
21684
  try {
21615
21685
  const candidateMessages = await readHermesLineageMessages(candidate);
21616
- if (candidateMessages.length === 0) {
21617
- continue;
21618
- }
21619
- const missingCandidateMessages = candidateMessages.filter(
21620
- (message, index) => !isRepresentedHermesRow(representedRows, message, index)
21621
- );
21622
- if (missingCandidateMessages.length === 0) {
21623
- continue;
21624
- }
21625
21686
  const profile = await resolveConversationProfileTarget(
21626
21687
  paths,
21627
21688
  candidate.profileName
@@ -21631,6 +21692,15 @@ async function syncHermesConversationMessages(paths, logger, input) {
21631
21692
  profileUid: profile.profileUid,
21632
21693
  profileName: profile.profileName
21633
21694
  });
21695
+ if (candidateMessages.length === 0) {
21696
+ continue;
21697
+ }
21698
+ const missingCandidateMessages = candidateMessages.filter(
21699
+ (message, index) => !isRepresentedHermesRow(representedRows, message, index)
21700
+ );
21701
+ if (missingCandidateMessages.length === 0) {
21702
+ continue;
21703
+ }
21634
21704
  projectedMessages.push(
21635
21705
  ...toLinkMessages({
21636
21706
  conversationId: input.conversationId,
@@ -21664,13 +21734,36 @@ async function syncHermesConversationMessages(paths, logger, input) {
21664
21734
  exactKeys.forEach((key) => knownExactKeys.add(key));
21665
21735
  }
21666
21736
  if (appendedMessages.length === 0) {
21737
+ let nextManifest2 = candidateProfiles.size > 0 || duplicateCleanup.removedCount > 0 ? await store.readManifest(input.conversationId) : null;
21738
+ const currentManifest = nextManifest2;
21739
+ if (nextManifest2) {
21740
+ for (const profile of candidateProfiles.values()) {
21741
+ nextManifest2 = mergeHermesLineageIntoManifest({
21742
+ manifest: nextManifest2,
21743
+ candidate: profile.candidate,
21744
+ snapshot,
21745
+ profileUid: profile.profileUid,
21746
+ profileName: profile.profileName,
21747
+ updatedAt: isoFromHermesTime(profile.candidate.session.last_active) ?? nextManifest2.updated_at
21748
+ });
21749
+ }
21750
+ if (duplicateCleanup.removedCount > 0) {
21751
+ nextManifest2 = {
21752
+ ...nextManifest2,
21753
+ stats: buildConversationStats(nextManifest2, snapshot)
21754
+ };
21755
+ }
21756
+ }
21667
21757
  if (duplicateCleanup.removedCount > 0) {
21668
21758
  await store.writeSnapshot(input.conversationId, snapshot);
21669
- const currentManifest = await store.readManifest(input.conversationId);
21670
- const stats2 = buildConversationStats(currentManifest, snapshot);
21671
- const nextManifest2 = { ...currentManifest, stats: stats2 };
21759
+ }
21760
+ if (nextManifest2 && currentManifest && (duplicateCleanup.removedCount > 0 || !manifestEquivalent(currentManifest, nextManifest2))) {
21761
+ const stats2 = buildConversationStats(nextManifest2, snapshot);
21762
+ nextManifest2 = { ...nextManifest2, stats: stats2 };
21672
21763
  await store.writeManifest(nextManifest2);
21673
21764
  await upsertConversationStats(paths, toStatsIndexRecord(nextManifest2, stats2));
21765
+ result.hermes_session_ids = manifestHermesSessionIds(nextManifest2);
21766
+ result.changed = true;
21674
21767
  }
21675
21768
  return result;
21676
21769
  }
@@ -21711,6 +21804,7 @@ async function syncHermesConversationMessages(paths, logger, input) {
21711
21804
  };
21712
21805
  await store.writeManifest(nextManifest);
21713
21806
  await upsertConversationStats(paths, toStatsIndexRecord(nextManifest, stats));
21807
+ result.hermes_session_ids = manifestHermesSessionIds(nextManifest);
21714
21808
  const appendEvent = input.appendEvent ?? ((conversationId, event) => store.appendEvent(conversationId, event));
21715
21809
  let lastEventSeq;
21716
21810
  for (const message of hydratedAppendedMessages) {
@@ -21769,7 +21863,7 @@ async function importHermesSession(input) {
21769
21863
  id: conversationId,
21770
21864
  schema_version: 1,
21771
21865
  kind: "direct",
21772
- title: normalizeTitle(title),
21866
+ title: normalizeTitle2(title),
21773
21867
  title_source: title ? "hermes" : "default",
21774
21868
  status: "active",
21775
21869
  hermes_session_id: sessionId,
@@ -22412,6 +22506,16 @@ function lineageSessionIds(candidate) {
22412
22506
  candidate.session.id
22413
22507
  ]);
22414
22508
  }
22509
+ function manifestHermesSessionIds(manifest) {
22510
+ return normalizeSessionIds([
22511
+ manifest.hermes_session_id,
22512
+ ...manifest.hermes_session_ids ?? [],
22513
+ manifest.hermes_lineage?.root_session_id,
22514
+ manifest.hermes_lineage?.current_session_id,
22515
+ ...manifest.hermes_lineage?.session_ids ?? [],
22516
+ ...Object.values(manifest.profile_session_ids ?? {})
22517
+ ]);
22518
+ }
22415
22519
  function lineageTitle(candidate) {
22416
22520
  const explicitLineageTitle = normalizeOptionalTitle(
22417
22521
  candidate.session._lineage_title
@@ -22489,6 +22593,59 @@ async function readExistingHermesConversations(store, conversationIds) {
22489
22593
  }
22490
22594
  return conversations;
22491
22595
  }
22596
+ async function archiveKnownHermesConversation(input) {
22597
+ const conversationIds = findKnownConversationIdsForCandidate(
22598
+ input.knownHermesSessions,
22599
+ input.candidate
22600
+ );
22601
+ if (conversationIds.length === 0) {
22602
+ return 0;
22603
+ }
22604
+ const archivedAt = readString12(input.candidate.session, "archived_at") ?? (/* @__PURE__ */ new Date()).toISOString();
22605
+ let archivedCount = 0;
22606
+ for (const conversation of await readExistingHermesConversations(
22607
+ input.store,
22608
+ conversationIds
22609
+ )) {
22610
+ if (conversation.manifest.status !== "active") {
22611
+ continue;
22612
+ }
22613
+ const nextManifest = {
22614
+ ...conversation.manifest,
22615
+ status: "archived",
22616
+ updated_at: archivedAt,
22617
+ archived_at: archivedAt,
22618
+ hermes_session_ids: normalizeSessionIds([
22619
+ conversation.manifest.hermes_session_id,
22620
+ ...conversation.manifest.hermes_session_ids ?? [],
22621
+ ...conversation.manifest.hermes_lineage?.session_ids ?? [],
22622
+ ...lineageSessionIds(input.candidate)
22623
+ ]),
22624
+ ...lineageManifestPatch(input.candidate)
22625
+ };
22626
+ const stats = buildConversationStats(nextManifest, conversation.snapshot);
22627
+ const archivedManifest = { ...nextManifest, stats };
22628
+ await input.store.writeManifest(archivedManifest);
22629
+ await input.store.appendEvent(conversation.conversationId, {
22630
+ type: "conversation.archived",
22631
+ payload: {
22632
+ archived_at: archivedAt,
22633
+ source: "hermes",
22634
+ hermes_session_id: input.candidate.session.id
22635
+ }
22636
+ });
22637
+ const eventManifest = await input.store.readManifest(conversation.conversationId);
22638
+ const eventStats = buildConversationStats(eventManifest, conversation.snapshot);
22639
+ const indexedManifest = { ...eventManifest, stats: eventStats };
22640
+ await input.store.writeManifest(indexedManifest);
22641
+ await upsertConversationStats(
22642
+ input.paths,
22643
+ toStatsIndexRecord(indexedManifest, eventStats)
22644
+ );
22645
+ archivedCount += 1;
22646
+ }
22647
+ return archivedCount;
22648
+ }
22492
22649
  function selectCanonicalHermesConversation(conversations) {
22493
22650
  const ranked = [...conversations].sort((left, right) => {
22494
22651
  const rightScore = canonicalConversationScore(right);
@@ -22601,7 +22758,7 @@ function mergeHermesLineageIntoManifest(input) {
22601
22758
  };
22602
22759
  const title = deriveHermesConversationTitle(input.candidate, input.snapshot);
22603
22760
  if (title && canSyncHermesTitle(input.manifest)) {
22604
- nextBase.title = normalizeTitle(title);
22761
+ nextBase.title = normalizeTitle2(title);
22605
22762
  nextBase.title_source = "hermes";
22606
22763
  }
22607
22764
  const baseStats = buildConversationStats(nextBase, input.snapshot);
@@ -23640,7 +23797,7 @@ function senderForRole(input) {
23640
23797
  function firstUserText(snapshot) {
23641
23798
  return snapshot.messages.find((message) => message.role === "user")?.parts.find((part) => part.type === "text")?.text?.slice(0, 80);
23642
23799
  }
23643
- function normalizeTitle(value) {
23800
+ function normalizeTitle2(value) {
23644
23801
  const normalized = value?.replace(/\s+/gu, " ").trim();
23645
23802
  return normalized || DEFAULT_CONVERSATION_TITLE;
23646
23803
  }
@@ -23753,6 +23910,10 @@ function isHiddenSession(session) {
23753
23910
  const visibility = readString12(session, "visibility")?.toLowerCase();
23754
23911
  return Boolean(source && HIDDEN_SESSION_SOURCES.has(source)) || readBoolean2(session.hidden) || readBoolean2(session.archived) || Boolean(readString12(session, "archived_at")) || status === "hidden" || status === "archived" || visibility === "hidden" || visibility === "hide";
23755
23912
  }
23913
+ function isArchivedSession(session) {
23914
+ const status = readString12(session, "status")?.toLowerCase();
23915
+ return readBoolean2(session.archived) || Boolean(readString12(session, "archived_at")) || status === "archived";
23916
+ }
23756
23917
  function readTableColumns2(db, tableName) {
23757
23918
  try {
23758
23919
  const rows = db.prepare(`PRAGMA table_info(${quoteIdentifier(tableName)})`).all();
@@ -29546,6 +29707,123 @@ ${resolved}` : resolved;
29546
29707
  return true;
29547
29708
  });
29548
29709
  }
29710
+ async repairStaleAutomaticContextCompression(conversationId) {
29711
+ const terminalRepaired = await this.deps.withConversationLock(
29712
+ conversationId,
29713
+ async () => {
29714
+ const snapshot = await this.deps.readSnapshot(conversationId);
29715
+ const cleared = [];
29716
+ for (const run of snapshot.runs) {
29717
+ const status = staleAutomaticContextCompressionStatus(run);
29718
+ if (!status) {
29719
+ continue;
29720
+ }
29721
+ const result = this.clearAutomaticContextCompressionInSnapshot({
29722
+ snapshot,
29723
+ run,
29724
+ status,
29725
+ completedAt: run.completed_at ?? (/* @__PURE__ */ new Date()).toISOString()
29726
+ });
29727
+ if (result) {
29728
+ cleared.push({ run, ...result });
29729
+ }
29730
+ }
29731
+ if (cleared.length === 0) {
29732
+ return false;
29733
+ }
29734
+ await this.deps.writeSnapshot(conversationId, snapshot);
29735
+ for (const item of cleared) {
29736
+ await this.appendAutomaticContextCompressionClearedEvent({
29737
+ conversationId,
29738
+ run: item.run,
29739
+ marker: item.marker,
29740
+ metadata: item.metadata
29741
+ });
29742
+ }
29743
+ await this.deps.persistConversationStats(conversationId, snapshot);
29744
+ return true;
29745
+ }
29746
+ );
29747
+ const detachedRunningRepaired = await this.repairDetachedRunningAutomaticContextCompression(conversationId);
29748
+ return terminalRepaired || detachedRunningRepaired;
29749
+ }
29750
+ async repairDetachedRunningAutomaticContextCompression(conversationId) {
29751
+ const snapshot = await this.deps.readSnapshot(conversationId).catch(() => null);
29752
+ const candidates = snapshot?.runs.filter(
29753
+ (run) => isRunningAutomaticContextCompression(run) && !this.deps.activeRunControllers.has(run.id)
29754
+ ) ?? [];
29755
+ let repaired = false;
29756
+ for (const run of candidates) {
29757
+ if (this.deps.activeRunControllers.has(run.id)) {
29758
+ continue;
29759
+ }
29760
+ const previousSessionId = run.context_compression?.previous_session_id?.trim() || run.hermes_session_id;
29761
+ const nextSessionId = await readHermesCompressionTip(
29762
+ previousSessionId,
29763
+ this.deps.paths,
29764
+ run.profile
29765
+ ).catch(() => void 0);
29766
+ if (!nextSessionId || nextSessionId === previousSessionId) {
29767
+ continue;
29768
+ }
29769
+ const finished = await this.finishAutomaticContextCompressionFromEvent({
29770
+ conversationId,
29771
+ runId: run.id,
29772
+ previousSessionId,
29773
+ nextSessionId
29774
+ });
29775
+ if (!finished) {
29776
+ continue;
29777
+ }
29778
+ await this.completeRun(conversationId, run.id);
29779
+ repaired = true;
29780
+ }
29781
+ return repaired;
29782
+ }
29783
+ clearAutomaticContextCompressionInSnapshot(input) {
29784
+ if (input.run.context_compression?.status !== "compressing" || input.run.context_compression.source !== "auto") {
29785
+ return null;
29786
+ }
29787
+ const marker = findContextCompressionMarker(
29788
+ input.snapshot,
29789
+ input.run.context_compression.operation_id
29790
+ );
29791
+ if (!marker) {
29792
+ return null;
29793
+ }
29794
+ const metadata = {
29795
+ ...input.run.context_compression,
29796
+ status: input.status,
29797
+ completed_at: input.completedAt
29798
+ };
29799
+ input.run.context_compression = metadata;
29800
+ updateContextCompressionMarker({
29801
+ message: marker,
29802
+ metadata,
29803
+ language: input.run.language === "en" ? "en" : "zh-CN"
29804
+ });
29805
+ return { marker, metadata };
29806
+ }
29807
+ async appendAutomaticContextCompressionClearedEvent(input) {
29808
+ await this.deps.appendEvent(input.conversationId, {
29809
+ type: contextCompressionClearedEventType(input.metadata.status),
29810
+ message_id: input.marker.id,
29811
+ run_id: input.run.id,
29812
+ payload: {
29813
+ message: input.marker,
29814
+ run: input.run,
29815
+ operation: input.metadata,
29816
+ automatic: true,
29817
+ session_rotated: false
29818
+ },
29819
+ ...input.event ? {
29820
+ raw: {
29821
+ format: "hermes-run-event",
29822
+ payload: input.event.rawPayload
29823
+ }
29824
+ } : {}
29825
+ });
29826
+ }
29549
29827
  async runHasAssistantOutput(conversationId, runId) {
29550
29828
  const snapshot = await this.deps.readSnapshot(conversationId).catch(() => null);
29551
29829
  const run = snapshot?.runs.find((item) => item.id === runId);
@@ -30109,6 +30387,12 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
30109
30387
  );
30110
30388
  }
30111
30389
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
30390
+ const clearedCompression = this.clearAutomaticContextCompressionInSnapshot({
30391
+ snapshot,
30392
+ run,
30393
+ status: "completed",
30394
+ completedAt
30395
+ });
30112
30396
  const usage = readUsage(source?.payload);
30113
30397
  const responseId = readResponseId(source?.payload);
30114
30398
  const hermesRunId = readRunId(source?.payload);
@@ -30209,6 +30493,15 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
30209
30493
  runId,
30210
30494
  expiredInputRequests
30211
30495
  );
30496
+ if (clearedCompression) {
30497
+ await this.appendAutomaticContextCompressionClearedEvent({
30498
+ conversationId,
30499
+ run,
30500
+ marker: clearedCompression.marker,
30501
+ metadata: clearedCompression.metadata,
30502
+ event: source
30503
+ });
30504
+ }
30212
30505
  if (assistant) {
30213
30506
  await this.deps.appendEvent(conversationId, {
30214
30507
  type: "message.completed",
@@ -30285,6 +30578,12 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
30285
30578
  }
30286
30579
  run.status = "failed";
30287
30580
  run.completed_at = (/* @__PURE__ */ new Date()).toISOString();
30581
+ const clearedCompression = this.clearAutomaticContextCompressionInSnapshot({
30582
+ snapshot,
30583
+ run,
30584
+ status: "failed",
30585
+ completedAt: run.completed_at
30586
+ });
30288
30587
  run.error_message = message;
30289
30588
  run.error_detail = source ? readErrorMessage4(source.payload) ?? void 0 : void 0;
30290
30589
  const language = run.language === "en" ? "en" : "zh-CN";
@@ -30335,6 +30634,15 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
30335
30634
  runId,
30336
30635
  expiredInputRequests
30337
30636
  );
30637
+ if (clearedCompression) {
30638
+ await this.appendAutomaticContextCompressionClearedEvent({
30639
+ conversationId,
30640
+ run,
30641
+ marker: clearedCompression.marker,
30642
+ metadata: clearedCompression.metadata,
30643
+ event: source
30644
+ });
30645
+ }
30338
30646
  const contextUsage = contextUsagePayloadForSnapshot(snapshot);
30339
30647
  await this.deps.appendEvent(conversationId, {
30340
30648
  type: "run.failed",
@@ -30563,6 +30871,12 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
30563
30871
  const cancelledAt = (/* @__PURE__ */ new Date()).toISOString();
30564
30872
  run.status = "cancelled";
30565
30873
  run.completed_at = cancelledAt;
30874
+ const clearedCompression = this.clearAutomaticContextCompressionInSnapshot({
30875
+ snapshot,
30876
+ run,
30877
+ status: "cancelled",
30878
+ completedAt: cancelledAt
30879
+ });
30566
30880
  run.error_message = options.reason;
30567
30881
  const cancelledByGoalPause = options.reason === "goal paused by app";
30568
30882
  const assistant = snapshot.messages.find(
@@ -30604,6 +30918,14 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
30604
30918
  runId,
30605
30919
  cancelledInputRequests
30606
30920
  );
30921
+ if (clearedCompression) {
30922
+ await this.appendAutomaticContextCompressionClearedEvent({
30923
+ conversationId,
30924
+ run,
30925
+ marker: clearedCompression.marker,
30926
+ metadata: clearedCompression.metadata
30927
+ });
30928
+ }
30607
30929
  if (assistant) {
30608
30930
  await this.deps.appendEvent(conversationId, {
30609
30931
  type: "message.completed",
@@ -30856,6 +31178,36 @@ function contextCompressionGeneration(message) {
30856
31178
  function readPreviousSessionId(payload) {
30857
31179
  return readString21(payload, "old_session_id") ?? readString21(payload, "oldSessionId") ?? readString21(payload, "previous_session_id") ?? readString21(payload, "previousSessionId");
30858
31180
  }
31181
+ function contextCompressionClearedEventType(status) {
31182
+ if (status === "completed") {
31183
+ return "context_compression.completed";
31184
+ }
31185
+ if (status === "cancelled") {
31186
+ return "context_compression.cancelled";
31187
+ }
31188
+ if (status === "timed_out") {
31189
+ return "context_compression.timed_out";
31190
+ }
31191
+ return "context_compression.failed";
31192
+ }
31193
+ function staleAutomaticContextCompressionStatus(run) {
31194
+ if (run.context_compression?.status !== "compressing" || run.context_compression.source !== "auto") {
31195
+ return null;
31196
+ }
31197
+ if (run.status === "completed") {
31198
+ return "completed";
31199
+ }
31200
+ if (run.status === "failed") {
31201
+ return "failed";
31202
+ }
31203
+ if (run.status === "cancelled") {
31204
+ return "cancelled";
31205
+ }
31206
+ return null;
31207
+ }
31208
+ function isRunningAutomaticContextCompression(run) {
31209
+ return run.status === "running" && run.context_compression?.status === "compressing" && run.context_compression.source === "auto";
31210
+ }
30859
31211
  function readNextSessionId(payload) {
30860
31212
  const info = toRecord19(payload.info);
30861
31213
  return readString21(payload, "session_key") ?? readString21(payload, "sessionKey") ?? readString21(payload, "stored_session_id") ?? readString21(payload, "storedSessionId") ?? readString21(payload, "session_id") ?? readString21(payload, "sessionId") ?? readString21(info, "session_key") ?? readString21(info, "sessionKey");
@@ -32167,6 +32519,11 @@ var ConversationService = class {
32167
32519
  }
32168
32520
  );
32169
32521
  }
32522
+ if (!options.beforeMessageId) {
32523
+ await this.runLifecycle.repairStaleAutomaticContextCompression(
32524
+ conversationId
32525
+ );
32526
+ }
32170
32527
  return this.queries.getMessages(conversationId, options);
32171
32528
  }
32172
32529
  async setConversationModel(conversationId, input) {
@@ -32215,7 +32572,7 @@ var ConversationService = class {
32215
32572
  );
32216
32573
  }
32217
32574
  const shouldApplyModelConfig = configuredModel != null && !runtimeMatchesConfiguredModel(currentRuntime, configuredModel);
32218
- const modelConfigToApply = shouldApplyModelConfig ? configuredModel : void 0;
32575
+ const modelConfigToApply = configuredModel ?? void 0;
32219
32576
  const sessionConfig = await setTuiGatewaySessionModelConfig({
32220
32577
  hermesSessionId: manifest.hermes_session_id,
32221
32578
  profileName: currentRuntime.profile.name,
@@ -34553,7 +34910,8 @@ function registerConversationRoutes(router, options) {
34553
34910
  const result = await conversations.listConversationPage({
34554
34911
  limit: readLimit(ctx.query.limit),
34555
34912
  cursor,
34556
- workspace: readConversationWorkspaceFilter(ctx.query)
34913
+ workspace: readConversationWorkspaceFilter(ctx.query),
34914
+ category: readConversationCategoryFilter(ctx.query)
34557
34915
  });
34558
34916
  const localized = localizeConversationListPage(result, language);
34559
34917
  ctx.body = {
@@ -35284,6 +35642,21 @@ function readConversationWorkspaceFilter(query) {
35284
35642
  }
35285
35643
  return { kind: "workspace", workspaceId: workspace };
35286
35644
  }
35645
+ function readConversationCategoryFilter(query) {
35646
+ const category = readQueryString(query.category) ?? readQueryString(query.conversation_category) ?? readQueryString(query.conversationCategory);
35647
+ const normalized = category?.trim().toLowerCase();
35648
+ if (!normalized || normalized === "all") {
35649
+ return "all";
35650
+ }
35651
+ if (normalized === "chat" || normalized === "cron") {
35652
+ return normalized;
35653
+ }
35654
+ throw new LinkHttpError(
35655
+ 400,
35656
+ "conversation_category_invalid",
35657
+ "conversation category must be all, chat or cron"
35658
+ );
35659
+ }
35287
35660
  function readConversationWorkspaceId(body) {
35288
35661
  if (Object.prototype.hasOwnProperty.call(body, "workspace_id") || Object.prototype.hasOwnProperty.call(body, "workspaceId")) {
35289
35662
  return readString23(body, "workspace_id") ?? readString23(body, "workspaceId");
@@ -46206,6 +46579,7 @@ function registerSystemRoutes(router, options) {
46206
46579
  conversation_delete: true,
46207
46580
  conversation_bulk_delete: true,
46208
46581
  conversation_clear_plan: true,
46582
+ conversation_categories: true,
46209
46583
  conversation_cancel: true,
46210
46584
  conversation_queue_controls: true,
46211
46585
  conversation_queue_limit: MAX_CONVERSATION_QUEUED_RUNS,
package/dist/cli/index.js CHANGED
@@ -55,7 +55,7 @@ import {
55
55
  stopDaemonProcess,
56
56
  summarizeUsageProbeEnsure,
57
57
  translate
58
- } from "../chunk-JWKKE7ZJ.js";
58
+ } from "../chunk-JJBIYFGY.js";
59
59
 
60
60
  // src/cli/index.ts
61
61
  import { Command } from "commander";
@@ -12,6 +12,8 @@ interface ConversationSummary {
12
12
  title: string;
13
13
  display_title?: string;
14
14
  title_source?: ConversationTitleSource;
15
+ category?: ConversationCategory;
16
+ is_cron?: boolean;
15
17
  workspace_id?: string | null;
16
18
  created_at: string;
17
19
  updated_at: string;
@@ -31,6 +33,7 @@ interface ConversationSummary {
31
33
  content_preview: string;
32
34
  } | null;
33
35
  }
36
+ type ConversationCategory = "chat" | "cron";
34
37
  interface ConversationListPageInfo {
35
38
  limit: number;
36
39
  has_more: boolean;
@@ -556,12 +559,21 @@ interface ConversationWorkspace {
556
559
  updated_at: string;
557
560
  }
558
561
 
562
+ type ConversationListCategory = 'all' | 'chat' | 'cron';
563
+ interface ConversationListPageOptions {
564
+ limit?: number;
565
+ cursor?: string;
566
+ workspace?: ConversationWorkspaceFilter;
567
+ category?: ConversationListCategory;
568
+ }
569
+
559
570
  interface HermesSessionSyncResult {
560
571
  scanned_profiles: number;
561
572
  scanned_sessions: number;
562
573
  eligible_sessions: number;
563
574
  imported_count: number;
564
575
  reprojected_count: number;
576
+ hermes_archived_count: number;
565
577
  skipped_existing: number;
566
578
  skipped_hidden: number;
567
579
  skipped_empty_transcript: number;
@@ -618,11 +630,7 @@ declare class ConversationService {
618
630
  private withConversationLock;
619
631
  private restoreArchivedConversationForUserContinuationLocked;
620
632
  listConversations(): Promise<ConversationSummary[]>;
621
- listConversationPage(input?: {
622
- limit?: number;
623
- cursor?: string;
624
- workspace?: ConversationWorkspaceFilter;
625
- }): Promise<ConversationListPage>;
633
+ listConversationPage(input?: ConversationListPageOptions): Promise<ConversationListPage>;
626
634
  searchConversationPage(input?: {
627
635
  limit?: number;
628
636
  cursor?: string;
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-JWKKE7ZJ.js";
3
+ } from "../chunk-JJBIYFGY.js";
4
4
  export {
5
5
  createApp
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hermespilot/link",
3
- "version": "0.8.3",
3
+ "version": "0.8.5-beta.0",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",