@hermespilot/link 0.8.3 → 0.8.4

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.4";
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();
@@ -19735,14 +19765,16 @@ var ConversationQueryCoordinator = class {
19735
19765
  status,
19736
19766
  limit,
19737
19767
  cursor,
19738
- workspace: options.workspace
19768
+ workspace: options.workspace,
19769
+ category: options.category
19739
19770
  }),
19740
19771
  listPage: (pageCursor) => listConversationStatsPage(this.deps.paths, {
19741
19772
  status,
19742
19773
  limit,
19743
19774
  cursor: pageCursor,
19744
19775
  workspace: options.workspace
19745
- })
19776
+ }),
19777
+ category: options.category
19746
19778
  });
19747
19779
  }
19748
19780
  async searchConversationPage(options = {}) {
@@ -19790,7 +19822,8 @@ var ConversationQueryCoordinator = class {
19790
19822
  usedIndex = true;
19791
19823
  const summaries = await this.summarizeIndexedConversations(
19792
19824
  input.status,
19793
- indexedPage.records
19825
+ indexedPage.records,
19826
+ input.category
19794
19827
  );
19795
19828
  for (const summary of summaries) {
19796
19829
  if (!seenConversationIds.add(summary.id)) {
@@ -19830,15 +19863,18 @@ var ConversationQueryCoordinator = class {
19830
19863
  }
19831
19864
  };
19832
19865
  }
19833
- async listConversationsFromStore(status = "active", workspace) {
19866
+ async listConversationsFromStore(status = "active", options = {}) {
19834
19867
  const summaries = [];
19835
19868
  for (const conversationId of await this.deps.store.listConversationIds()) {
19836
19869
  const manifest = await this.deps.store.readManifest(conversationId).catch(() => null);
19837
- if (!manifest || manifest.status !== status || !matchesWorkspaceFilter(manifest, workspace)) {
19870
+ if (!manifest || manifest.status !== status || !matchesWorkspaceFilter(manifest, options.workspace)) {
19838
19871
  continue;
19839
19872
  }
19840
19873
  const snapshot = await this.deps.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
19841
19874
  const refreshed = await this.deps.metadata.refreshTitleFromHermes(manifest, { snapshot }).catch(() => manifest);
19875
+ if (!matchesConversationCategory(refreshed, snapshot, options.category)) {
19876
+ continue;
19877
+ }
19842
19878
  summaries.push(await this.summarizeConversation(refreshed, snapshot));
19843
19879
  }
19844
19880
  return summaries.sort(
@@ -19846,10 +19882,10 @@ var ConversationQueryCoordinator = class {
19846
19882
  );
19847
19883
  }
19848
19884
  async listConversationPageFromStore(input) {
19849
- const all = await this.listConversationsFromStore(
19850
- input.status,
19851
- input.workspace
19852
- );
19885
+ const all = await this.listConversationsFromStore(input.status, {
19886
+ workspace: input.workspace,
19887
+ category: input.category
19888
+ });
19853
19889
  const startIndex = input.cursor ? all.findIndex(
19854
19890
  (summary) => isAfterConversationListCursor(summary, input.cursor)
19855
19891
  ) : 0;
@@ -19868,7 +19904,7 @@ var ConversationQueryCoordinator = class {
19868
19904
  }
19869
19905
  };
19870
19906
  }
19871
- async summarizeIndexedConversations(status, records) {
19907
+ async summarizeIndexedConversations(status, records, category = "all") {
19872
19908
  const summaries = [];
19873
19909
  for (const record of records) {
19874
19910
  const manifest = await this.deps.store.readManifest(record.conversationId).catch(() => null);
@@ -19877,6 +19913,9 @@ var ConversationQueryCoordinator = class {
19877
19913
  }
19878
19914
  const snapshot = await this.deps.store.readSnapshot(record.conversationId).catch(() => emptySnapshot2());
19879
19915
  const refreshed = await this.deps.metadata.refreshTitleFromHermes(manifest, { snapshot }).catch(() => manifest);
19916
+ if (!matchesConversationCategory(refreshed, snapshot, category)) {
19917
+ continue;
19918
+ }
19880
19919
  summaries.push(await this.summarizeConversation(refreshed, snapshot));
19881
19920
  }
19882
19921
  return summaries;
@@ -20145,6 +20184,13 @@ function matchesWorkspaceFilter(manifest, filter) {
20145
20184
  }
20146
20185
  return workspaceId === filter.workspaceId;
20147
20186
  }
20187
+ function matchesConversationCategory(manifest, snapshot, category = "all") {
20188
+ if (category === "all") {
20189
+ return true;
20190
+ }
20191
+ const isCron = manifestHasCronSignal(manifest, snapshot);
20192
+ return category === "cron" ? isCron : !isCron;
20193
+ }
20148
20194
  function isAfterConversationListCursor(summary, cursor) {
20149
20195
  return summary.updated_at < cursor.updatedAt || summary.updated_at === cursor.updatedAt && summary.id < cursor.conversationId;
20150
20196
  }
@@ -21392,6 +21438,7 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
21392
21438
  eligible_sessions: 0,
21393
21439
  imported_count: 0,
21394
21440
  reprojected_count: 0,
21441
+ hermes_archived_count: 0,
21395
21442
  skipped_existing: 0,
21396
21443
  skipped_hidden: 0,
21397
21444
  skipped_empty_transcript: 0,
@@ -21417,6 +21464,21 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
21417
21464
  continue;
21418
21465
  }
21419
21466
  if (isHiddenSession(session)) {
21467
+ if (isArchivedSession(session)) {
21468
+ const candidate = { profileName, profileDir, dbPath, session };
21469
+ result.hermes_archived_count += await archiveKnownHermesConversation({
21470
+ paths,
21471
+ store,
21472
+ knownHermesSessions,
21473
+ candidate
21474
+ }).catch((error) => {
21475
+ result.errors.push({
21476
+ profile: profileName,
21477
+ message: error instanceof Error ? error.message : String(error)
21478
+ });
21479
+ return 0;
21480
+ });
21481
+ }
21420
21482
  result.skipped_hidden += 1;
21421
21483
  continue;
21422
21484
  }
@@ -21502,7 +21564,7 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
21502
21564
  }
21503
21565
  }
21504
21566
  }
21505
- if (result.imported_count > 0 || result.reprojected_count > 0 || result.errors.length > 0) {
21567
+ if (result.imported_count > 0 || result.reprojected_count > 0 || result.hermes_archived_count > 0 || result.errors.length > 0) {
21506
21568
  void logger.info("hermes_session_sync_completed", { ...result });
21507
21569
  } else {
21508
21570
  void logger.debug("hermes_session_sync_completed", { ...result });
@@ -21769,7 +21831,7 @@ async function importHermesSession(input) {
21769
21831
  id: conversationId,
21770
21832
  schema_version: 1,
21771
21833
  kind: "direct",
21772
- title: normalizeTitle(title),
21834
+ title: normalizeTitle2(title),
21773
21835
  title_source: title ? "hermes" : "default",
21774
21836
  status: "active",
21775
21837
  hermes_session_id: sessionId,
@@ -22489,6 +22551,59 @@ async function readExistingHermesConversations(store, conversationIds) {
22489
22551
  }
22490
22552
  return conversations;
22491
22553
  }
22554
+ async function archiveKnownHermesConversation(input) {
22555
+ const conversationIds = findKnownConversationIdsForCandidate(
22556
+ input.knownHermesSessions,
22557
+ input.candidate
22558
+ );
22559
+ if (conversationIds.length === 0) {
22560
+ return 0;
22561
+ }
22562
+ const archivedAt = readString12(input.candidate.session, "archived_at") ?? (/* @__PURE__ */ new Date()).toISOString();
22563
+ let archivedCount = 0;
22564
+ for (const conversation of await readExistingHermesConversations(
22565
+ input.store,
22566
+ conversationIds
22567
+ )) {
22568
+ if (conversation.manifest.status !== "active") {
22569
+ continue;
22570
+ }
22571
+ const nextManifest = {
22572
+ ...conversation.manifest,
22573
+ status: "archived",
22574
+ updated_at: archivedAt,
22575
+ archived_at: archivedAt,
22576
+ hermes_session_ids: normalizeSessionIds([
22577
+ conversation.manifest.hermes_session_id,
22578
+ ...conversation.manifest.hermes_session_ids ?? [],
22579
+ ...conversation.manifest.hermes_lineage?.session_ids ?? [],
22580
+ ...lineageSessionIds(input.candidate)
22581
+ ]),
22582
+ ...lineageManifestPatch(input.candidate)
22583
+ };
22584
+ const stats = buildConversationStats(nextManifest, conversation.snapshot);
22585
+ const archivedManifest = { ...nextManifest, stats };
22586
+ await input.store.writeManifest(archivedManifest);
22587
+ await input.store.appendEvent(conversation.conversationId, {
22588
+ type: "conversation.archived",
22589
+ payload: {
22590
+ archived_at: archivedAt,
22591
+ source: "hermes",
22592
+ hermes_session_id: input.candidate.session.id
22593
+ }
22594
+ });
22595
+ const eventManifest = await input.store.readManifest(conversation.conversationId);
22596
+ const eventStats = buildConversationStats(eventManifest, conversation.snapshot);
22597
+ const indexedManifest = { ...eventManifest, stats: eventStats };
22598
+ await input.store.writeManifest(indexedManifest);
22599
+ await upsertConversationStats(
22600
+ input.paths,
22601
+ toStatsIndexRecord(indexedManifest, eventStats)
22602
+ );
22603
+ archivedCount += 1;
22604
+ }
22605
+ return archivedCount;
22606
+ }
22492
22607
  function selectCanonicalHermesConversation(conversations) {
22493
22608
  const ranked = [...conversations].sort((left, right) => {
22494
22609
  const rightScore = canonicalConversationScore(right);
@@ -22601,7 +22716,7 @@ function mergeHermesLineageIntoManifest(input) {
22601
22716
  };
22602
22717
  const title = deriveHermesConversationTitle(input.candidate, input.snapshot);
22603
22718
  if (title && canSyncHermesTitle(input.manifest)) {
22604
- nextBase.title = normalizeTitle(title);
22719
+ nextBase.title = normalizeTitle2(title);
22605
22720
  nextBase.title_source = "hermes";
22606
22721
  }
22607
22722
  const baseStats = buildConversationStats(nextBase, input.snapshot);
@@ -23640,7 +23755,7 @@ function senderForRole(input) {
23640
23755
  function firstUserText(snapshot) {
23641
23756
  return snapshot.messages.find((message) => message.role === "user")?.parts.find((part) => part.type === "text")?.text?.slice(0, 80);
23642
23757
  }
23643
- function normalizeTitle(value) {
23758
+ function normalizeTitle2(value) {
23644
23759
  const normalized = value?.replace(/\s+/gu, " ").trim();
23645
23760
  return normalized || DEFAULT_CONVERSATION_TITLE;
23646
23761
  }
@@ -23753,6 +23868,10 @@ function isHiddenSession(session) {
23753
23868
  const visibility = readString12(session, "visibility")?.toLowerCase();
23754
23869
  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
23870
  }
23871
+ function isArchivedSession(session) {
23872
+ const status = readString12(session, "status")?.toLowerCase();
23873
+ return readBoolean2(session.archived) || Boolean(readString12(session, "archived_at")) || status === "archived";
23874
+ }
23756
23875
  function readTableColumns2(db, tableName) {
23757
23876
  try {
23758
23877
  const rows = db.prepare(`PRAGMA table_info(${quoteIdentifier(tableName)})`).all();
@@ -32215,7 +32334,7 @@ var ConversationService = class {
32215
32334
  );
32216
32335
  }
32217
32336
  const shouldApplyModelConfig = configuredModel != null && !runtimeMatchesConfiguredModel(currentRuntime, configuredModel);
32218
- const modelConfigToApply = shouldApplyModelConfig ? configuredModel : void 0;
32337
+ const modelConfigToApply = configuredModel ?? void 0;
32219
32338
  const sessionConfig = await setTuiGatewaySessionModelConfig({
32220
32339
  hermesSessionId: manifest.hermes_session_id,
32221
32340
  profileName: currentRuntime.profile.name,
@@ -34553,7 +34672,8 @@ function registerConversationRoutes(router, options) {
34553
34672
  const result = await conversations.listConversationPage({
34554
34673
  limit: readLimit(ctx.query.limit),
34555
34674
  cursor,
34556
- workspace: readConversationWorkspaceFilter(ctx.query)
34675
+ workspace: readConversationWorkspaceFilter(ctx.query),
34676
+ category: readConversationCategoryFilter(ctx.query)
34557
34677
  });
34558
34678
  const localized = localizeConversationListPage(result, language);
34559
34679
  ctx.body = {
@@ -35284,6 +35404,21 @@ function readConversationWorkspaceFilter(query) {
35284
35404
  }
35285
35405
  return { kind: "workspace", workspaceId: workspace };
35286
35406
  }
35407
+ function readConversationCategoryFilter(query) {
35408
+ const category = readQueryString(query.category) ?? readQueryString(query.conversation_category) ?? readQueryString(query.conversationCategory);
35409
+ const normalized = category?.trim().toLowerCase();
35410
+ if (!normalized || normalized === "all") {
35411
+ return "all";
35412
+ }
35413
+ if (normalized === "chat" || normalized === "cron") {
35414
+ return normalized;
35415
+ }
35416
+ throw new LinkHttpError(
35417
+ 400,
35418
+ "conversation_category_invalid",
35419
+ "conversation category must be all, chat or cron"
35420
+ );
35421
+ }
35287
35422
  function readConversationWorkspaceId(body) {
35288
35423
  if (Object.prototype.hasOwnProperty.call(body, "workspace_id") || Object.prototype.hasOwnProperty.call(body, "workspaceId")) {
35289
35424
  return readString23(body, "workspace_id") ?? readString23(body, "workspaceId");
@@ -46206,6 +46341,7 @@ function registerSystemRoutes(router, options) {
46206
46341
  conversation_delete: true,
46207
46342
  conversation_bulk_delete: true,
46208
46343
  conversation_clear_plan: true,
46344
+ conversation_categories: true,
46209
46345
  conversation_cancel: true,
46210
46346
  conversation_queue_controls: true,
46211
46347
  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-VVHNBT4E.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-VVHNBT4E.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.4",
4
4
  "private": false,
5
5
  "description": "Hermes Link companion service and CLI for connecting hermes-agent through HermesPilot",
6
6
  "license": "MIT",