@hermespilot/link 0.8.4 → 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.4";
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";
@@ -15932,6 +15932,9 @@ function collectHermesSessionIds(manifest, snapshot) {
15932
15932
  return normalizeHermesSessionIds([
15933
15933
  manifest.hermes_session_id,
15934
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 ?? [],
15935
15938
  ...Object.values(manifest.profile_session_ids ?? {}),
15936
15939
  ...snapshot.runs.map((run) => run.hermes_session_id)
15937
15940
  ]);
@@ -15960,6 +15963,11 @@ function collectHermesSessionDeleteTargets(manifest, snapshot) {
15960
15963
  for (const sessionId of manifest.hermes_session_ids ?? []) {
15961
15964
  add(sessionId, defaultProfile);
15962
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
+ }
15963
15971
  for (const [profileName, sessionId] of Object.entries(
15964
15972
  manifest.profile_session_ids ?? {}
15965
15973
  )) {
@@ -21675,15 +21683,6 @@ async function syncHermesConversationMessages(paths, logger, input) {
21675
21683
  for (const candidate of candidates) {
21676
21684
  try {
21677
21685
  const candidateMessages = await readHermesLineageMessages(candidate);
21678
- if (candidateMessages.length === 0) {
21679
- continue;
21680
- }
21681
- const missingCandidateMessages = candidateMessages.filter(
21682
- (message, index) => !isRepresentedHermesRow(representedRows, message, index)
21683
- );
21684
- if (missingCandidateMessages.length === 0) {
21685
- continue;
21686
- }
21687
21686
  const profile = await resolveConversationProfileTarget(
21688
21687
  paths,
21689
21688
  candidate.profileName
@@ -21693,6 +21692,15 @@ async function syncHermesConversationMessages(paths, logger, input) {
21693
21692
  profileUid: profile.profileUid,
21694
21693
  profileName: profile.profileName
21695
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
+ }
21696
21704
  projectedMessages.push(
21697
21705
  ...toLinkMessages({
21698
21706
  conversationId: input.conversationId,
@@ -21726,13 +21734,36 @@ async function syncHermesConversationMessages(paths, logger, input) {
21726
21734
  exactKeys.forEach((key) => knownExactKeys.add(key));
21727
21735
  }
21728
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
+ }
21729
21757
  if (duplicateCleanup.removedCount > 0) {
21730
21758
  await store.writeSnapshot(input.conversationId, snapshot);
21731
- const currentManifest = await store.readManifest(input.conversationId);
21732
- const stats2 = buildConversationStats(currentManifest, snapshot);
21733
- 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 };
21734
21763
  await store.writeManifest(nextManifest2);
21735
21764
  await upsertConversationStats(paths, toStatsIndexRecord(nextManifest2, stats2));
21765
+ result.hermes_session_ids = manifestHermesSessionIds(nextManifest2);
21766
+ result.changed = true;
21736
21767
  }
21737
21768
  return result;
21738
21769
  }
@@ -21773,6 +21804,7 @@ async function syncHermesConversationMessages(paths, logger, input) {
21773
21804
  };
21774
21805
  await store.writeManifest(nextManifest);
21775
21806
  await upsertConversationStats(paths, toStatsIndexRecord(nextManifest, stats));
21807
+ result.hermes_session_ids = manifestHermesSessionIds(nextManifest);
21776
21808
  const appendEvent = input.appendEvent ?? ((conversationId, event) => store.appendEvent(conversationId, event));
21777
21809
  let lastEventSeq;
21778
21810
  for (const message of hydratedAppendedMessages) {
@@ -22474,6 +22506,16 @@ function lineageSessionIds(candidate) {
22474
22506
  candidate.session.id
22475
22507
  ]);
22476
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
+ }
22477
22519
  function lineageTitle(candidate) {
22478
22520
  const explicitLineageTitle = normalizeOptionalTitle(
22479
22521
  candidate.session._lineage_title
@@ -29665,6 +29707,123 @@ ${resolved}` : resolved;
29665
29707
  return true;
29666
29708
  });
29667
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
+ }
29668
29827
  async runHasAssistantOutput(conversationId, runId) {
29669
29828
  const snapshot = await this.deps.readSnapshot(conversationId).catch(() => null);
29670
29829
  const run = snapshot?.runs.find((item) => item.id === runId);
@@ -30228,6 +30387,12 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
30228
30387
  );
30229
30388
  }
30230
30389
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
30390
+ const clearedCompression = this.clearAutomaticContextCompressionInSnapshot({
30391
+ snapshot,
30392
+ run,
30393
+ status: "completed",
30394
+ completedAt
30395
+ });
30231
30396
  const usage = readUsage(source?.payload);
30232
30397
  const responseId = readResponseId(source?.payload);
30233
30398
  const hermesRunId = readRunId(source?.payload);
@@ -30328,6 +30493,15 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
30328
30493
  runId,
30329
30494
  expiredInputRequests
30330
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
+ }
30331
30505
  if (assistant) {
30332
30506
  await this.deps.appendEvent(conversationId, {
30333
30507
  type: "message.completed",
@@ -30404,6 +30578,12 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
30404
30578
  }
30405
30579
  run.status = "failed";
30406
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
+ });
30407
30587
  run.error_message = message;
30408
30588
  run.error_detail = source ? readErrorMessage4(source.payload) ?? void 0 : void 0;
30409
30589
  const language = run.language === "en" ? "en" : "zh-CN";
@@ -30454,6 +30634,15 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
30454
30634
  runId,
30455
30635
  expiredInputRequests
30456
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
+ }
30457
30646
  const contextUsage = contextUsagePayloadForSnapshot(snapshot);
30458
30647
  await this.deps.appendEvent(conversationId, {
30459
30648
  type: "run.failed",
@@ -30682,6 +30871,12 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
30682
30871
  const cancelledAt = (/* @__PURE__ */ new Date()).toISOString();
30683
30872
  run.status = "cancelled";
30684
30873
  run.completed_at = cancelledAt;
30874
+ const clearedCompression = this.clearAutomaticContextCompressionInSnapshot({
30875
+ snapshot,
30876
+ run,
30877
+ status: "cancelled",
30878
+ completedAt: cancelledAt
30879
+ });
30685
30880
  run.error_message = options.reason;
30686
30881
  const cancelledByGoalPause = options.reason === "goal paused by app";
30687
30882
  const assistant = snapshot.messages.find(
@@ -30723,6 +30918,14 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
30723
30918
  runId,
30724
30919
  cancelledInputRequests
30725
30920
  );
30921
+ if (clearedCompression) {
30922
+ await this.appendAutomaticContextCompressionClearedEvent({
30923
+ conversationId,
30924
+ run,
30925
+ marker: clearedCompression.marker,
30926
+ metadata: clearedCompression.metadata
30927
+ });
30928
+ }
30726
30929
  if (assistant) {
30727
30930
  await this.deps.appendEvent(conversationId, {
30728
30931
  type: "message.completed",
@@ -30975,6 +31178,36 @@ function contextCompressionGeneration(message) {
30975
31178
  function readPreviousSessionId(payload) {
30976
31179
  return readString21(payload, "old_session_id") ?? readString21(payload, "oldSessionId") ?? readString21(payload, "previous_session_id") ?? readString21(payload, "previousSessionId");
30977
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
+ }
30978
31211
  function readNextSessionId(payload) {
30979
31212
  const info = toRecord19(payload.info);
30980
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");
@@ -32286,6 +32519,11 @@ var ConversationService = class {
32286
32519
  }
32287
32520
  );
32288
32521
  }
32522
+ if (!options.beforeMessageId) {
32523
+ await this.runLifecycle.repairStaleAutomaticContextCompression(
32524
+ conversationId
32525
+ );
32526
+ }
32289
32527
  return this.queries.getMessages(conversationId, options);
32290
32528
  }
32291
32529
  async setConversationModel(conversationId, input) {
package/dist/cli/index.js CHANGED
@@ -55,7 +55,7 @@ import {
55
55
  stopDaemonProcess,
56
56
  summarizeUsageProbeEnsure,
57
57
  translate
58
- } from "../chunk-VVHNBT4E.js";
58
+ } from "../chunk-JJBIYFGY.js";
59
59
 
60
60
  // src/cli/index.ts
61
61
  import { Command } from "commander";
package/dist/http/app.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createApp
3
- } from "../chunk-VVHNBT4E.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.4",
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",