@hermespilot/link 0.8.4 → 0.8.5
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.
- package/dist/{chunk-VVHNBT4E.js → chunk-QT55LT5X.js} +785 -32
- package/dist/cli/index.js +1 -1
- package/dist/http/app.d.ts +11 -0
- package/dist/http/app.js +1 -1
- package/package.json +1 -1
|
@@ -7721,7 +7721,7 @@ function isConversationMissingError(error) {
|
|
|
7721
7721
|
}
|
|
7722
7722
|
|
|
7723
7723
|
// src/constants.ts
|
|
7724
|
-
var LINK_VERSION = "0.8.
|
|
7724
|
+
var LINK_VERSION = "0.8.5";
|
|
7725
7725
|
var LINK_COMMAND = "hermeslink";
|
|
7726
7726
|
var LINK_DEFAULT_PORT = 52379;
|
|
7727
7727
|
var LINK_RUNTIME_DIR_NAME = ".hermeslink";
|
|
@@ -10966,6 +10966,8 @@ async function dispatchTuiGatewayCommand(input) {
|
|
|
10966
10966
|
output: readString5(result, "output") ?? void 0,
|
|
10967
10967
|
notice: readString5(result, "notice") ?? void 0,
|
|
10968
10968
|
message: readString5(result, "message") ?? void 0,
|
|
10969
|
+
runtimeSessionId: started.runtimeSessionId,
|
|
10970
|
+
storedSessionId: started.storedSessionId,
|
|
10969
10971
|
raw: result
|
|
10970
10972
|
};
|
|
10971
10973
|
}
|
|
@@ -14555,6 +14557,9 @@ function applyAppMessagePresentation(message) {
|
|
|
14555
14557
|
}
|
|
14556
14558
|
const text = messageText(message);
|
|
14557
14559
|
if (isHermesImportedMessage(message)) {
|
|
14560
|
+
if (message.role === "user" && isHermesGoalContinuationPrompt(text)) {
|
|
14561
|
+
return { ...message, visibility: "internal" };
|
|
14562
|
+
}
|
|
14558
14563
|
const strippedText = stripLeadingHermesInternalNote(text);
|
|
14559
14564
|
if (strippedText !== text) {
|
|
14560
14565
|
return strippedText ? withText(message, strippedText) : { ...message, visibility: "internal" };
|
|
@@ -14600,6 +14605,9 @@ function linkLifecycleMarkerPresentation(message) {
|
|
|
14600
14605
|
function isHermesImportedMessage(message) {
|
|
14601
14606
|
return message.hermes?.imported_from === "hermes" || HERMES_IMPORTED_MESSAGE_FORMATS.has(message.raw?.format ?? "");
|
|
14602
14607
|
}
|
|
14608
|
+
function isHermesGoalContinuationPrompt(text) {
|
|
14609
|
+
return text.replace(/\r\n/gu, "\n").startsWith("[Continuing toward your standing goal]\nGoal:");
|
|
14610
|
+
}
|
|
14603
14611
|
function isHermesInternalSystemStyleMarker(text) {
|
|
14604
14612
|
const normalized = text.trim();
|
|
14605
14613
|
return normalized.startsWith(
|
|
@@ -15932,6 +15940,9 @@ function collectHermesSessionIds(manifest, snapshot) {
|
|
|
15932
15940
|
return normalizeHermesSessionIds([
|
|
15933
15941
|
manifest.hermes_session_id,
|
|
15934
15942
|
...manifest.hermes_session_ids ?? [],
|
|
15943
|
+
manifest.hermes_lineage?.root_session_id,
|
|
15944
|
+
manifest.hermes_lineage?.current_session_id,
|
|
15945
|
+
...manifest.hermes_lineage?.session_ids ?? [],
|
|
15935
15946
|
...Object.values(manifest.profile_session_ids ?? {}),
|
|
15936
15947
|
...snapshot.runs.map((run) => run.hermes_session_id)
|
|
15937
15948
|
]);
|
|
@@ -15960,6 +15971,11 @@ function collectHermesSessionDeleteTargets(manifest, snapshot) {
|
|
|
15960
15971
|
for (const sessionId of manifest.hermes_session_ids ?? []) {
|
|
15961
15972
|
add(sessionId, defaultProfile);
|
|
15962
15973
|
}
|
|
15974
|
+
add(manifest.hermes_lineage?.root_session_id, defaultProfile);
|
|
15975
|
+
add(manifest.hermes_lineage?.current_session_id, defaultProfile);
|
|
15976
|
+
for (const sessionId of manifest.hermes_lineage?.session_ids ?? []) {
|
|
15977
|
+
add(sessionId, defaultProfile);
|
|
15978
|
+
}
|
|
15963
15979
|
for (const [profileName, sessionId] of Object.entries(
|
|
15964
15980
|
manifest.profile_session_ids ?? {}
|
|
15965
15981
|
)) {
|
|
@@ -18245,6 +18261,13 @@ var ConversationOrchestrationCoordinator = class {
|
|
|
18245
18261
|
attachments: userAttachmentParts,
|
|
18246
18262
|
language: normalizeLanguage(input.language)
|
|
18247
18263
|
}) : null;
|
|
18264
|
+
if (goalRequest?.hermesSessionId) {
|
|
18265
|
+
manifest = addHermesSessionIdForProfileToManifest(
|
|
18266
|
+
manifest,
|
|
18267
|
+
goalRequest.hermesSessionId,
|
|
18268
|
+
runtime.profileName
|
|
18269
|
+
);
|
|
18270
|
+
}
|
|
18248
18271
|
if ((hasRunningRuns(snapshot) || queuedRunCount(snapshot) > 0) && queuedRunCount(snapshot) >= MAX_CONVERSATION_QUEUED_RUNS) {
|
|
18249
18272
|
throw new LinkHttpError(
|
|
18250
18273
|
409,
|
|
@@ -18459,6 +18482,7 @@ var ConversationOrchestrationCoordinator = class {
|
|
|
18459
18482
|
maxTurns: readGoalProgressFromStatusText(result.notice ?? result.output ?? "").maxTurns
|
|
18460
18483
|
}),
|
|
18461
18484
|
kickoffInput: result.message.trim(),
|
|
18485
|
+
...result.storedSessionId ? { hermesSessionId: result.storedSessionId } : {},
|
|
18462
18486
|
...result.notice ? { notice: result.notice } : {},
|
|
18463
18487
|
...result.output ? { output: result.output } : {}
|
|
18464
18488
|
};
|
|
@@ -20240,6 +20264,40 @@ var ContextCompressionCoordinator = class {
|
|
|
20240
20264
|
hasActiveCompression(snapshot) {
|
|
20241
20265
|
return snapshot.runs.some((run) => isActiveCompressionRun(run));
|
|
20242
20266
|
}
|
|
20267
|
+
async repairDetachedRunningCompressions(conversationId) {
|
|
20268
|
+
const snapshot = await this.deps.store.readSnapshot(conversationId).catch(() => null);
|
|
20269
|
+
const candidates = snapshot?.runs.filter(
|
|
20270
|
+
(run) => isActiveCompressionRun(run) && !this.activeOperations.has(run.context_compression.operation_id)
|
|
20271
|
+
) ?? [];
|
|
20272
|
+
let repaired = false;
|
|
20273
|
+
for (const run of candidates) {
|
|
20274
|
+
const operation = run.context_compression;
|
|
20275
|
+
if (!operation) {
|
|
20276
|
+
continue;
|
|
20277
|
+
}
|
|
20278
|
+
const markerId = run.assistant_message_id;
|
|
20279
|
+
await this.finishCompression({
|
|
20280
|
+
conversationId,
|
|
20281
|
+
runId: run.id,
|
|
20282
|
+
markerId,
|
|
20283
|
+
operationId: operation.operation_id,
|
|
20284
|
+
generation: operation.generation,
|
|
20285
|
+
status: "failed",
|
|
20286
|
+
error: new LinkHttpError(
|
|
20287
|
+
502,
|
|
20288
|
+
"context_compression_detached",
|
|
20289
|
+
localizedText(
|
|
20290
|
+
normalizeLanguage2(run.language),
|
|
20291
|
+
"Hermes Link \u5DF2\u5931\u53BB\u8FD9\u6B21\u4E0A\u4E0B\u6587\u538B\u7F29\u7684\u5B9E\u65F6\u8FDE\u63A5\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
|
|
20292
|
+
"Hermes Link lost the realtime connection for this context compression. Try again shortly."
|
|
20293
|
+
)
|
|
20294
|
+
),
|
|
20295
|
+
language: normalizeLanguage2(run.language)
|
|
20296
|
+
});
|
|
20297
|
+
repaired = true;
|
|
20298
|
+
}
|
|
20299
|
+
return repaired;
|
|
20300
|
+
}
|
|
20243
20301
|
async startLocked(input) {
|
|
20244
20302
|
const focus = normalizeFocus(input.focus);
|
|
20245
20303
|
const language = normalizeLanguage2(input.language);
|
|
@@ -21675,15 +21733,6 @@ async function syncHermesConversationMessages(paths, logger, input) {
|
|
|
21675
21733
|
for (const candidate of candidates) {
|
|
21676
21734
|
try {
|
|
21677
21735
|
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
21736
|
const profile = await resolveConversationProfileTarget(
|
|
21688
21737
|
paths,
|
|
21689
21738
|
candidate.profileName
|
|
@@ -21693,6 +21742,15 @@ async function syncHermesConversationMessages(paths, logger, input) {
|
|
|
21693
21742
|
profileUid: profile.profileUid,
|
|
21694
21743
|
profileName: profile.profileName
|
|
21695
21744
|
});
|
|
21745
|
+
if (candidateMessages.length === 0) {
|
|
21746
|
+
continue;
|
|
21747
|
+
}
|
|
21748
|
+
const missingCandidateMessages = candidateMessages.filter(
|
|
21749
|
+
(message, index) => !isRepresentedHermesRow(representedRows, message, index)
|
|
21750
|
+
);
|
|
21751
|
+
if (missingCandidateMessages.length === 0) {
|
|
21752
|
+
continue;
|
|
21753
|
+
}
|
|
21696
21754
|
projectedMessages.push(
|
|
21697
21755
|
...toLinkMessages({
|
|
21698
21756
|
conversationId: input.conversationId,
|
|
@@ -21726,13 +21784,55 @@ async function syncHermesConversationMessages(paths, logger, input) {
|
|
|
21726
21784
|
exactKeys.forEach((key) => knownExactKeys.add(key));
|
|
21727
21785
|
}
|
|
21728
21786
|
if (appendedMessages.length === 0) {
|
|
21787
|
+
let nextManifest2 = candidateProfiles.size > 0 || duplicateCleanup.removedCount > 0 ? await store.readManifest(input.conversationId) : null;
|
|
21788
|
+
const currentManifest = nextManifest2;
|
|
21789
|
+
let lineageChanged = false;
|
|
21790
|
+
if (nextManifest2) {
|
|
21791
|
+
for (const profile of candidateProfiles.values()) {
|
|
21792
|
+
nextManifest2 = mergeHermesLineageIntoManifest({
|
|
21793
|
+
manifest: nextManifest2,
|
|
21794
|
+
candidate: profile.candidate,
|
|
21795
|
+
snapshot,
|
|
21796
|
+
profileUid: profile.profileUid,
|
|
21797
|
+
profileName: profile.profileName,
|
|
21798
|
+
updatedAt: isoFromHermesTime(profile.candidate.session.last_active) ?? nextManifest2.updated_at
|
|
21799
|
+
});
|
|
21800
|
+
}
|
|
21801
|
+
lineageChanged = !manifestEquivalent(currentManifest, nextManifest2);
|
|
21802
|
+
if (duplicateCleanup.removedCount > 0) {
|
|
21803
|
+
nextManifest2 = {
|
|
21804
|
+
...nextManifest2,
|
|
21805
|
+
stats: buildConversationStats(nextManifest2, snapshot)
|
|
21806
|
+
};
|
|
21807
|
+
}
|
|
21808
|
+
}
|
|
21729
21809
|
if (duplicateCleanup.removedCount > 0) {
|
|
21730
21810
|
await store.writeSnapshot(input.conversationId, snapshot);
|
|
21731
|
-
|
|
21732
|
-
|
|
21733
|
-
const
|
|
21811
|
+
}
|
|
21812
|
+
if (nextManifest2 && currentManifest && (duplicateCleanup.removedCount > 0 || lineageChanged)) {
|
|
21813
|
+
const stats2 = buildConversationStats(nextManifest2, snapshot);
|
|
21814
|
+
nextManifest2 = { ...nextManifest2, stats: stats2 };
|
|
21734
21815
|
await store.writeManifest(nextManifest2);
|
|
21735
21816
|
await upsertConversationStats(paths, toStatsIndexRecord(nextManifest2, stats2));
|
|
21817
|
+
result.hermes_session_ids = manifestHermesSessionIds(nextManifest2);
|
|
21818
|
+
result.changed = true;
|
|
21819
|
+
if (lineageChanged) {
|
|
21820
|
+
const appendEvent2 = input.appendEvent ?? ((conversationId, event2) => store.appendEvent(conversationId, event2));
|
|
21821
|
+
const event = await appendEvent2(input.conversationId, {
|
|
21822
|
+
type: "conversation.updated",
|
|
21823
|
+
payload: {
|
|
21824
|
+
source: "hermes_sync",
|
|
21825
|
+
hermes_session_id: nextManifest2.hermes_session_id,
|
|
21826
|
+
hermes_session_ids: manifestHermesSessionIds(nextManifest2),
|
|
21827
|
+
conversation: await summarizeSyncedConversation(
|
|
21828
|
+
paths,
|
|
21829
|
+
nextManifest2,
|
|
21830
|
+
snapshot
|
|
21831
|
+
)
|
|
21832
|
+
}
|
|
21833
|
+
});
|
|
21834
|
+
result.last_event_seq = event.seq;
|
|
21835
|
+
}
|
|
21736
21836
|
}
|
|
21737
21837
|
return result;
|
|
21738
21838
|
}
|
|
@@ -21773,6 +21873,7 @@ async function syncHermesConversationMessages(paths, logger, input) {
|
|
|
21773
21873
|
};
|
|
21774
21874
|
await store.writeManifest(nextManifest);
|
|
21775
21875
|
await upsertConversationStats(paths, toStatsIndexRecord(nextManifest, stats));
|
|
21876
|
+
result.hermes_session_ids = manifestHermesSessionIds(nextManifest);
|
|
21776
21877
|
const appendEvent = input.appendEvent ?? ((conversationId, event) => store.appendEvent(conversationId, event));
|
|
21777
21878
|
let lastEventSeq;
|
|
21778
21879
|
for (const message of hydratedAppendedMessages) {
|
|
@@ -22162,6 +22263,18 @@ function isStrictImportedDuplicateRunMessage(paths, snapshot, message) {
|
|
|
22162
22263
|
if (!isMessageInsideRunCleanupWindow(message, run)) {
|
|
22163
22264
|
return false;
|
|
22164
22265
|
}
|
|
22266
|
+
if (isImportedMessageCoveredByRunWindow(message, run)) {
|
|
22267
|
+
if (message.role === "user") {
|
|
22268
|
+
return snapshot.messages.some(
|
|
22269
|
+
(item) => item.id === run.trigger_message_id
|
|
22270
|
+
);
|
|
22271
|
+
}
|
|
22272
|
+
if (message.role === "assistant" || message.role === "tool") {
|
|
22273
|
+
return snapshot.messages.some(
|
|
22274
|
+
(item) => item.id === run.assistant_message_id
|
|
22275
|
+
);
|
|
22276
|
+
}
|
|
22277
|
+
}
|
|
22165
22278
|
if (message.role === "user") {
|
|
22166
22279
|
const user = snapshot.messages.find(
|
|
22167
22280
|
(item) => item.id === run.trigger_message_id
|
|
@@ -22177,6 +22290,20 @@ function isStrictImportedDuplicateRunMessage(paths, snapshot, message) {
|
|
|
22177
22290
|
return false;
|
|
22178
22291
|
});
|
|
22179
22292
|
}
|
|
22293
|
+
function isImportedMessageCoveredByRunWindow(message, run) {
|
|
22294
|
+
const rows = readHermesRawMessageRows(message.raw);
|
|
22295
|
+
if (rows.length === 0) {
|
|
22296
|
+
return false;
|
|
22297
|
+
}
|
|
22298
|
+
const windows = collectRunHermesMessageWindows(run);
|
|
22299
|
+
if (windows.length === 0) {
|
|
22300
|
+
return false;
|
|
22301
|
+
}
|
|
22302
|
+
return rows.every((row) => {
|
|
22303
|
+
const role = normalizeMessageRole(row.role);
|
|
22304
|
+
return windows.some((window) => isRowInsideRunWindow(window, row, role));
|
|
22305
|
+
});
|
|
22306
|
+
}
|
|
22180
22307
|
function isDuplicateImportedRunUser(paths, user, imported) {
|
|
22181
22308
|
const importedText = normalizedDuplicateText(imported);
|
|
22182
22309
|
if (!importedText) {
|
|
@@ -22249,7 +22376,7 @@ function normalizedDuplicateTextValue(value) {
|
|
|
22249
22376
|
return value.replace(/\r\n/gu, "\n").trim();
|
|
22250
22377
|
}
|
|
22251
22378
|
function collectRunHermesMessageWindows(run) {
|
|
22252
|
-
|
|
22379
|
+
let before = normalizeWatermarkPoint(
|
|
22253
22380
|
run.hermes_message_watermark?.before,
|
|
22254
22381
|
run.hermes_session_id
|
|
22255
22382
|
);
|
|
@@ -22260,10 +22387,15 @@ function collectRunHermesMessageWindows(run) {
|
|
|
22260
22387
|
run.hermes_message_watermark?.after,
|
|
22261
22388
|
run.hermes_session_id
|
|
22262
22389
|
);
|
|
22263
|
-
const
|
|
22390
|
+
const beforeSessionId = before.session_id?.trim();
|
|
22391
|
+
const afterSessionId = after?.session_id?.trim();
|
|
22392
|
+
const sessionId = beforeSessionId && isPlaceholderHermesSessionId(beforeSessionId) && afterSessionId && !isPlaceholderHermesSessionId(afterSessionId) ? afterSessionId : beforeSessionId || run.hermes_session_id?.trim();
|
|
22264
22393
|
if (!sessionId) {
|
|
22265
22394
|
return [];
|
|
22266
22395
|
}
|
|
22396
|
+
if (before.session_id !== sessionId) {
|
|
22397
|
+
before = { ...before, session_id: sessionId };
|
|
22398
|
+
}
|
|
22267
22399
|
return [
|
|
22268
22400
|
{
|
|
22269
22401
|
runId: run.id,
|
|
@@ -22387,6 +22519,9 @@ function isRowInsideRunWindow(window, row, role) {
|
|
|
22387
22519
|
const upperTimeMs = hermesTimestampMillis(window.after?.max_timestamp) ?? (allowTerminalGrace && window.completedAtMs !== void 0 ? window.completedAtMs + 2 * 60 * 1e3 : allowOpenEnded ? Number.POSITIVE_INFINITY : void 0);
|
|
22388
22520
|
return upperTimeMs !== void 0 && (beforeTimeMs !== void 0 ? rowTimeMs > lowerTimeMs : rowTimeMs >= lowerTimeMs) && rowTimeMs <= upperTimeMs;
|
|
22389
22521
|
}
|
|
22522
|
+
function isPlaceholderHermesSessionId(sessionId) {
|
|
22523
|
+
return sessionId.startsWith("hp_");
|
|
22524
|
+
}
|
|
22390
22525
|
function isVoicePart(part) {
|
|
22391
22526
|
return part.kind === "voice" || part.is_voice_note === true;
|
|
22392
22527
|
}
|
|
@@ -22474,6 +22609,16 @@ function lineageSessionIds(candidate) {
|
|
|
22474
22609
|
candidate.session.id
|
|
22475
22610
|
]);
|
|
22476
22611
|
}
|
|
22612
|
+
function manifestHermesSessionIds(manifest) {
|
|
22613
|
+
return normalizeSessionIds([
|
|
22614
|
+
manifest.hermes_session_id,
|
|
22615
|
+
...manifest.hermes_session_ids ?? [],
|
|
22616
|
+
manifest.hermes_lineage?.root_session_id,
|
|
22617
|
+
manifest.hermes_lineage?.current_session_id,
|
|
22618
|
+
...manifest.hermes_lineage?.session_ids ?? [],
|
|
22619
|
+
...Object.values(manifest.profile_session_ids ?? {})
|
|
22620
|
+
]);
|
|
22621
|
+
}
|
|
22477
22622
|
function lineageTitle(candidate) {
|
|
22478
22623
|
const explicitLineageTitle = normalizeOptionalTitle(
|
|
22479
22624
|
candidate.session._lineage_title
|
|
@@ -22735,6 +22880,13 @@ function mergeHermesLineageIntoManifest(input) {
|
|
|
22735
22880
|
function manifestEquivalent(left, right) {
|
|
22736
22881
|
return stableJson(left) === stableJson(right);
|
|
22737
22882
|
}
|
|
22883
|
+
async function summarizeSyncedConversation(paths, manifest, snapshot) {
|
|
22884
|
+
return toSummary(
|
|
22885
|
+
manifest,
|
|
22886
|
+
snapshot,
|
|
22887
|
+
await readConversationProfileSummary(paths, manifest)
|
|
22888
|
+
);
|
|
22889
|
+
}
|
|
22738
22890
|
function collectImportedHermesPrefix(snapshot) {
|
|
22739
22891
|
const rows = [];
|
|
22740
22892
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -29665,6 +29817,309 @@ ${resolved}` : resolved;
|
|
|
29665
29817
|
return true;
|
|
29666
29818
|
});
|
|
29667
29819
|
}
|
|
29820
|
+
async repairStaleAutomaticContextCompression(conversationId) {
|
|
29821
|
+
const terminalRepaired = await this.deps.withConversationLock(
|
|
29822
|
+
conversationId,
|
|
29823
|
+
async () => {
|
|
29824
|
+
const snapshot = await this.deps.readSnapshot(conversationId);
|
|
29825
|
+
const cleared = [];
|
|
29826
|
+
for (const run of snapshot.runs) {
|
|
29827
|
+
const status = staleAutomaticContextCompressionStatus(run);
|
|
29828
|
+
if (!status) {
|
|
29829
|
+
continue;
|
|
29830
|
+
}
|
|
29831
|
+
const result = this.clearAutomaticContextCompressionInSnapshot({
|
|
29832
|
+
snapshot,
|
|
29833
|
+
run,
|
|
29834
|
+
status,
|
|
29835
|
+
completedAt: run.completed_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
29836
|
+
});
|
|
29837
|
+
if (result) {
|
|
29838
|
+
cleared.push({ run, ...result });
|
|
29839
|
+
}
|
|
29840
|
+
}
|
|
29841
|
+
if (cleared.length === 0) {
|
|
29842
|
+
return false;
|
|
29843
|
+
}
|
|
29844
|
+
await this.deps.writeSnapshot(conversationId, snapshot);
|
|
29845
|
+
for (const item of cleared) {
|
|
29846
|
+
await this.appendAutomaticContextCompressionClearedEvent({
|
|
29847
|
+
conversationId,
|
|
29848
|
+
run: item.run,
|
|
29849
|
+
marker: item.marker,
|
|
29850
|
+
metadata: item.metadata
|
|
29851
|
+
});
|
|
29852
|
+
}
|
|
29853
|
+
await this.deps.persistConversationStats(conversationId, snapshot);
|
|
29854
|
+
return true;
|
|
29855
|
+
}
|
|
29856
|
+
);
|
|
29857
|
+
const detachedRunningRepaired = await this.repairDetachedRunningAutomaticContextCompression(conversationId);
|
|
29858
|
+
return terminalRepaired || detachedRunningRepaired;
|
|
29859
|
+
}
|
|
29860
|
+
async repairStaleConversationRuns(conversationId) {
|
|
29861
|
+
const compressionRepaired = await this.repairStaleAutomaticContextCompression(conversationId);
|
|
29862
|
+
const snapshot = await this.deps.readSnapshot(conversationId).catch(() => null);
|
|
29863
|
+
const candidates = snapshot?.runs.filter(
|
|
29864
|
+
(run) => run.status === "running" && run.kind !== "compression" && run.context_compression?.status !== "compressing" && !isRunningAutomaticContextCompression(run)
|
|
29865
|
+
) ?? [];
|
|
29866
|
+
let repaired = compressionRepaired;
|
|
29867
|
+
let shouldDrainQueue = compressionRepaired;
|
|
29868
|
+
let pending = false;
|
|
29869
|
+
for (const run of candidates) {
|
|
29870
|
+
const result = await this.repairDetachedRunningRun(conversationId, run);
|
|
29871
|
+
if (result.repaired) {
|
|
29872
|
+
repaired = true;
|
|
29873
|
+
}
|
|
29874
|
+
if (result.shouldDrainQueue) {
|
|
29875
|
+
shouldDrainQueue = true;
|
|
29876
|
+
}
|
|
29877
|
+
if (result.pending) {
|
|
29878
|
+
pending = true;
|
|
29879
|
+
}
|
|
29880
|
+
}
|
|
29881
|
+
return { repaired, shouldDrainQueue, pending };
|
|
29882
|
+
}
|
|
29883
|
+
async repairDetachedRunningAutomaticContextCompression(conversationId) {
|
|
29884
|
+
const snapshot = await this.deps.readSnapshot(conversationId).catch(() => null);
|
|
29885
|
+
const candidates = snapshot?.runs.filter(
|
|
29886
|
+
(run) => isRunningAutomaticContextCompression(run)
|
|
29887
|
+
) ?? [];
|
|
29888
|
+
let repaired = false;
|
|
29889
|
+
for (const run of candidates) {
|
|
29890
|
+
const previousSessionId = run.context_compression?.previous_session_id?.trim() || run.hermes_session_id;
|
|
29891
|
+
const nextSessionId = await readHermesCompressionTip(
|
|
29892
|
+
previousSessionId,
|
|
29893
|
+
this.deps.paths,
|
|
29894
|
+
run.profile
|
|
29895
|
+
).catch(() => void 0);
|
|
29896
|
+
if (!nextSessionId || nextSessionId === previousSessionId) {
|
|
29897
|
+
continue;
|
|
29898
|
+
}
|
|
29899
|
+
const finished = await this.finishAutomaticContextCompressionFromEvent({
|
|
29900
|
+
conversationId,
|
|
29901
|
+
runId: run.id,
|
|
29902
|
+
previousSessionId,
|
|
29903
|
+
nextSessionId
|
|
29904
|
+
});
|
|
29905
|
+
if (!finished) {
|
|
29906
|
+
continue;
|
|
29907
|
+
}
|
|
29908
|
+
this.abortAndForgetActiveRunController(run.id);
|
|
29909
|
+
await this.completeRun(conversationId, run.id);
|
|
29910
|
+
repaired = true;
|
|
29911
|
+
}
|
|
29912
|
+
return repaired;
|
|
29913
|
+
}
|
|
29914
|
+
async repairDetachedRunningRun(conversationId, candidate) {
|
|
29915
|
+
const snapshot = await this.deps.readSnapshot(conversationId).catch(() => null);
|
|
29916
|
+
const run = snapshot?.runs.find((item) => item.id === candidate.id);
|
|
29917
|
+
if (!run || run.status !== "running") {
|
|
29918
|
+
return { repaired: false, shouldDrainQueue: false };
|
|
29919
|
+
}
|
|
29920
|
+
const backend = run.hermes_backend ?? (run.hermes_run_id ? "runs" : run.hermes_rpc_session_id ? "tui_gateway" : void 0);
|
|
29921
|
+
const language = run.language === "en" ? "en" : "zh-CN";
|
|
29922
|
+
if (backend === "runs" && run.hermes_run_id) {
|
|
29923
|
+
const terminal = await this.readRunTerminalEventOnce({
|
|
29924
|
+
conversationId,
|
|
29925
|
+
run,
|
|
29926
|
+
language
|
|
29927
|
+
});
|
|
29928
|
+
if (terminal.event) {
|
|
29929
|
+
this.abortAndForgetActiveRunController(run.id);
|
|
29930
|
+
const handled = await this.handleNormalizedHermesEvent({
|
|
29931
|
+
backend: "runs",
|
|
29932
|
+
conversationId,
|
|
29933
|
+
runId: run.id,
|
|
29934
|
+
event: terminal.event,
|
|
29935
|
+
profileName: run.profile,
|
|
29936
|
+
cronJobIdsBeforeRun: null
|
|
29937
|
+
});
|
|
29938
|
+
if (handled) {
|
|
29939
|
+
this.deps.scheduleTitleRefresh(conversationId);
|
|
29940
|
+
return { repaired: true, shouldDrainQueue: true };
|
|
29941
|
+
}
|
|
29942
|
+
}
|
|
29943
|
+
if (terminal.pending) {
|
|
29944
|
+
return { repaired: false, shouldDrainQueue: false, pending: true };
|
|
29945
|
+
}
|
|
29946
|
+
}
|
|
29947
|
+
if (backend === "tui_gateway" && run.hermes_rpc_session_id) {
|
|
29948
|
+
const liveSession = await readTuiGatewayLiveSession({
|
|
29949
|
+
profileName: run.profile,
|
|
29950
|
+
runtimeSessionId: run.hermes_rpc_session_id,
|
|
29951
|
+
storedSessionId: run.hermes_session_id,
|
|
29952
|
+
logger: this.deps.logger,
|
|
29953
|
+
paths: this.deps.paths
|
|
29954
|
+
}).catch((error) => {
|
|
29955
|
+
void this.deps.logger.debug("stale_tui_gateway_live_probe_failed", {
|
|
29956
|
+
conversation_id: conversationId,
|
|
29957
|
+
run_id: run.id,
|
|
29958
|
+
hermes_rpc_session_id: run.hermes_rpc_session_id,
|
|
29959
|
+
error: error instanceof Error ? error.message : String(error)
|
|
29960
|
+
});
|
|
29961
|
+
return null;
|
|
29962
|
+
});
|
|
29963
|
+
if (liveSession?.running) {
|
|
29964
|
+
await this.deps.logger.debug("stale_run_left_running_live_session", {
|
|
29965
|
+
conversation_id: conversationId,
|
|
29966
|
+
run_id: run.id,
|
|
29967
|
+
hermes_rpc_session_id: run.hermes_rpc_session_id,
|
|
29968
|
+
upstream_status: liveSession.status
|
|
29969
|
+
});
|
|
29970
|
+
return { repaired: false, shouldDrainQueue: false, pending: true };
|
|
29971
|
+
}
|
|
29972
|
+
}
|
|
29973
|
+
if (await this.runHasDisplayableAssistantOutput(conversationId, run.id)) {
|
|
29974
|
+
this.abortAndForgetActiveRunController(run.id);
|
|
29975
|
+
await this.completeRun(conversationId, run.id);
|
|
29976
|
+
this.deps.scheduleTitleRefresh(conversationId);
|
|
29977
|
+
return { repaired: true, shouldDrainQueue: true };
|
|
29978
|
+
}
|
|
29979
|
+
const recovered = await this.recoverDetachedRunFromHermesTranscript({
|
|
29980
|
+
conversationId,
|
|
29981
|
+
run
|
|
29982
|
+
});
|
|
29983
|
+
if (recovered) {
|
|
29984
|
+
this.abortAndForgetActiveRunController(run.id);
|
|
29985
|
+
this.deps.scheduleTitleRefresh(conversationId);
|
|
29986
|
+
return { repaired: true, shouldDrainQueue: true };
|
|
29987
|
+
}
|
|
29988
|
+
const stillRunning = await this.deps.readSnapshot(conversationId).then(
|
|
29989
|
+
(latest) => latest.runs.some(
|
|
29990
|
+
(item) => item.id === run.id && item.status === "running"
|
|
29991
|
+
)
|
|
29992
|
+
).catch(() => false);
|
|
29993
|
+
if (!stillRunning || this.deps.activeRunControllers.has(run.id)) {
|
|
29994
|
+
return { repaired: false, shouldDrainQueue: false };
|
|
29995
|
+
}
|
|
29996
|
+
await this.failRun(
|
|
29997
|
+
conversationId,
|
|
29998
|
+
run.id,
|
|
29999
|
+
detachedRunRecoveryFailedMessage(language)
|
|
30000
|
+
);
|
|
30001
|
+
return { repaired: true, shouldDrainQueue: true };
|
|
30002
|
+
}
|
|
30003
|
+
abortAndForgetActiveRunController(runId) {
|
|
30004
|
+
const active = this.deps.activeRunControllers.get(runId);
|
|
30005
|
+
active?.controller.abort();
|
|
30006
|
+
this.deps.activeRunControllers.delete(runId);
|
|
30007
|
+
}
|
|
30008
|
+
async readRunTerminalEventOnce(input) {
|
|
30009
|
+
const hermesRunId = input.run.hermes_run_id;
|
|
30010
|
+
if (!hermesRunId) {
|
|
30011
|
+
return { event: null, pending: false };
|
|
30012
|
+
}
|
|
30013
|
+
let status;
|
|
30014
|
+
try {
|
|
30015
|
+
status = await readHermesRunStatus(hermesRunId, {
|
|
30016
|
+
logger: this.deps.logger,
|
|
30017
|
+
profileName: input.run.profile,
|
|
30018
|
+
language: input.language
|
|
30019
|
+
});
|
|
30020
|
+
} catch (error) {
|
|
30021
|
+
await this.deps.logger.warn("stale_run_status_probe_failed", {
|
|
30022
|
+
conversation_id: input.conversationId,
|
|
30023
|
+
run_id: input.run.id,
|
|
30024
|
+
hermes_run_id: hermesRunId,
|
|
30025
|
+
error: error instanceof Error ? error.message : String(error)
|
|
30026
|
+
});
|
|
30027
|
+
return { event: null, pending: false };
|
|
30028
|
+
}
|
|
30029
|
+
if (!status) {
|
|
30030
|
+
return { event: null, pending: false };
|
|
30031
|
+
}
|
|
30032
|
+
if (status.session_id) {
|
|
30033
|
+
await this.rememberRunHermesSessionId(
|
|
30034
|
+
input.conversationId,
|
|
30035
|
+
input.run.id,
|
|
30036
|
+
status.session_id
|
|
30037
|
+
);
|
|
30038
|
+
}
|
|
30039
|
+
return {
|
|
30040
|
+
event: hermesRunStatusToTerminalEvent(status),
|
|
30041
|
+
pending: !isTerminalRunStatus(status.status)
|
|
30042
|
+
};
|
|
30043
|
+
}
|
|
30044
|
+
async recoverDetachedRunFromHermesTranscript(input) {
|
|
30045
|
+
const recoveredText = await readRunFinalAssistantText({
|
|
30046
|
+
profileName: input.run.profile,
|
|
30047
|
+
hermesSessionId: input.run.hermes_session_id,
|
|
30048
|
+
runStartedAt: input.run.started_at
|
|
30049
|
+
}).catch(async (error) => {
|
|
30050
|
+
await this.deps.logger.warn("stale_run_transcript_recovery_read_failed", {
|
|
30051
|
+
conversation_id: input.conversationId,
|
|
30052
|
+
run_id: input.run.id,
|
|
30053
|
+
hermes_session_id: input.run.hermes_session_id,
|
|
30054
|
+
error: error instanceof Error ? error.message : String(error)
|
|
30055
|
+
});
|
|
30056
|
+
return null;
|
|
30057
|
+
});
|
|
30058
|
+
if (!recoveredText?.trim()) {
|
|
30059
|
+
return false;
|
|
30060
|
+
}
|
|
30061
|
+
await this.persistHermesEvent(
|
|
30062
|
+
input.conversationId,
|
|
30063
|
+
input.run.id,
|
|
30064
|
+
transcriptRecoveryDeltaEvent(input.run.hermes_session_id, recoveredText)
|
|
30065
|
+
);
|
|
30066
|
+
await this.completeRun(
|
|
30067
|
+
input.conversationId,
|
|
30068
|
+
input.run.id,
|
|
30069
|
+
transcriptRecoveryCompletedEvent(input.run.hermes_session_id)
|
|
30070
|
+
);
|
|
30071
|
+
await this.deps.logger.info("stale_run_transcript_recovered", {
|
|
30072
|
+
conversation_id: input.conversationId,
|
|
30073
|
+
run_id: input.run.id,
|
|
30074
|
+
hermes_session_id: input.run.hermes_session_id,
|
|
30075
|
+
content_length: recoveredText.length
|
|
30076
|
+
});
|
|
30077
|
+
return true;
|
|
30078
|
+
}
|
|
30079
|
+
clearAutomaticContextCompressionInSnapshot(input) {
|
|
30080
|
+
if (input.run.context_compression?.status !== "compressing" || input.run.context_compression.source !== "auto") {
|
|
30081
|
+
return null;
|
|
30082
|
+
}
|
|
30083
|
+
const marker = findContextCompressionMarker(
|
|
30084
|
+
input.snapshot,
|
|
30085
|
+
input.run.context_compression.operation_id
|
|
30086
|
+
);
|
|
30087
|
+
if (!marker) {
|
|
30088
|
+
return null;
|
|
30089
|
+
}
|
|
30090
|
+
const metadata = {
|
|
30091
|
+
...input.run.context_compression,
|
|
30092
|
+
status: input.status,
|
|
30093
|
+
completed_at: input.completedAt
|
|
30094
|
+
};
|
|
30095
|
+
input.run.context_compression = metadata;
|
|
30096
|
+
updateContextCompressionMarker({
|
|
30097
|
+
message: marker,
|
|
30098
|
+
metadata,
|
|
30099
|
+
language: input.run.language === "en" ? "en" : "zh-CN"
|
|
30100
|
+
});
|
|
30101
|
+
return { marker, metadata };
|
|
30102
|
+
}
|
|
30103
|
+
async appendAutomaticContextCompressionClearedEvent(input) {
|
|
30104
|
+
await this.deps.appendEvent(input.conversationId, {
|
|
30105
|
+
type: contextCompressionClearedEventType(input.metadata.status),
|
|
30106
|
+
message_id: input.marker.id,
|
|
30107
|
+
run_id: input.run.id,
|
|
30108
|
+
payload: {
|
|
30109
|
+
message: input.marker,
|
|
30110
|
+
run: input.run,
|
|
30111
|
+
operation: input.metadata,
|
|
30112
|
+
automatic: true,
|
|
30113
|
+
session_rotated: false
|
|
30114
|
+
},
|
|
30115
|
+
...input.event ? {
|
|
30116
|
+
raw: {
|
|
30117
|
+
format: "hermes-run-event",
|
|
30118
|
+
payload: input.event.rawPayload
|
|
30119
|
+
}
|
|
30120
|
+
} : {}
|
|
30121
|
+
});
|
|
30122
|
+
}
|
|
29668
30123
|
async runHasAssistantOutput(conversationId, runId) {
|
|
29669
30124
|
const snapshot = await this.deps.readSnapshot(conversationId).catch(() => null);
|
|
29670
30125
|
const run = snapshot?.runs.find((item) => item.id === runId);
|
|
@@ -29676,6 +30131,17 @@ ${resolved}` : resolved;
|
|
|
29676
30131
|
}
|
|
29677
30132
|
return messageText2(assistant).length > 0 || (assistant.agent_events?.length ?? 0) > 0 || (assistant.approvals?.length ?? 0) > 0 || assistant.parts.some((part) => part.type !== "text");
|
|
29678
30133
|
}
|
|
30134
|
+
async runHasDisplayableAssistantOutput(conversationId, runId) {
|
|
30135
|
+
const snapshot = await this.deps.readSnapshot(conversationId).catch(() => null);
|
|
30136
|
+
const run = snapshot?.runs.find((item) => item.id === runId);
|
|
30137
|
+
const assistant = snapshot?.messages.find(
|
|
30138
|
+
(item) => item.id === run?.assistant_message_id
|
|
30139
|
+
);
|
|
30140
|
+
if (!assistant) {
|
|
30141
|
+
return false;
|
|
30142
|
+
}
|
|
30143
|
+
return messageText2(assistant).trim().length > 0 || assistant.parts.some((part) => part.type !== "text");
|
|
30144
|
+
}
|
|
29679
30145
|
async appendAssistantTextFromCompletedResponse(conversationId, runId, event) {
|
|
29680
30146
|
const terminalText = extractResponseAssistantText(event.payload);
|
|
29681
30147
|
if (!terminalText?.trim()) {
|
|
@@ -30228,6 +30694,12 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
|
|
|
30228
30694
|
);
|
|
30229
30695
|
}
|
|
30230
30696
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
30697
|
+
const clearedCompression = this.clearAutomaticContextCompressionInSnapshot({
|
|
30698
|
+
snapshot,
|
|
30699
|
+
run,
|
|
30700
|
+
status: "completed",
|
|
30701
|
+
completedAt
|
|
30702
|
+
});
|
|
30231
30703
|
const usage = readUsage(source?.payload);
|
|
30232
30704
|
const responseId = readResponseId(source?.payload);
|
|
30233
30705
|
const hermesRunId = readRunId(source?.payload);
|
|
@@ -30328,6 +30800,15 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
|
|
|
30328
30800
|
runId,
|
|
30329
30801
|
expiredInputRequests
|
|
30330
30802
|
);
|
|
30803
|
+
if (clearedCompression) {
|
|
30804
|
+
await this.appendAutomaticContextCompressionClearedEvent({
|
|
30805
|
+
conversationId,
|
|
30806
|
+
run,
|
|
30807
|
+
marker: clearedCompression.marker,
|
|
30808
|
+
metadata: clearedCompression.metadata,
|
|
30809
|
+
event: source
|
|
30810
|
+
});
|
|
30811
|
+
}
|
|
30331
30812
|
if (assistant) {
|
|
30332
30813
|
await this.deps.appendEvent(conversationId, {
|
|
30333
30814
|
type: "message.completed",
|
|
@@ -30404,6 +30885,12 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
|
|
|
30404
30885
|
}
|
|
30405
30886
|
run.status = "failed";
|
|
30406
30887
|
run.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
30888
|
+
const clearedCompression = this.clearAutomaticContextCompressionInSnapshot({
|
|
30889
|
+
snapshot,
|
|
30890
|
+
run,
|
|
30891
|
+
status: "failed",
|
|
30892
|
+
completedAt: run.completed_at
|
|
30893
|
+
});
|
|
30407
30894
|
run.error_message = message;
|
|
30408
30895
|
run.error_detail = source ? readErrorMessage4(source.payload) ?? void 0 : void 0;
|
|
30409
30896
|
const language = run.language === "en" ? "en" : "zh-CN";
|
|
@@ -30454,6 +30941,15 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
|
|
|
30454
30941
|
runId,
|
|
30455
30942
|
expiredInputRequests
|
|
30456
30943
|
);
|
|
30944
|
+
if (clearedCompression) {
|
|
30945
|
+
await this.appendAutomaticContextCompressionClearedEvent({
|
|
30946
|
+
conversationId,
|
|
30947
|
+
run,
|
|
30948
|
+
marker: clearedCompression.marker,
|
|
30949
|
+
metadata: clearedCompression.metadata,
|
|
30950
|
+
event: source
|
|
30951
|
+
});
|
|
30952
|
+
}
|
|
30457
30953
|
const contextUsage = contextUsagePayloadForSnapshot(snapshot);
|
|
30458
30954
|
await this.deps.appendEvent(conversationId, {
|
|
30459
30955
|
type: "run.failed",
|
|
@@ -30682,6 +31178,12 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
|
|
|
30682
31178
|
const cancelledAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
30683
31179
|
run.status = "cancelled";
|
|
30684
31180
|
run.completed_at = cancelledAt;
|
|
31181
|
+
const clearedCompression = this.clearAutomaticContextCompressionInSnapshot({
|
|
31182
|
+
snapshot,
|
|
31183
|
+
run,
|
|
31184
|
+
status: "cancelled",
|
|
31185
|
+
completedAt: cancelledAt
|
|
31186
|
+
});
|
|
30685
31187
|
run.error_message = options.reason;
|
|
30686
31188
|
const cancelledByGoalPause = options.reason === "goal paused by app";
|
|
30687
31189
|
const assistant = snapshot.messages.find(
|
|
@@ -30723,6 +31225,14 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
|
|
|
30723
31225
|
runId,
|
|
30724
31226
|
cancelledInputRequests
|
|
30725
31227
|
);
|
|
31228
|
+
if (clearedCompression) {
|
|
31229
|
+
await this.appendAutomaticContextCompressionClearedEvent({
|
|
31230
|
+
conversationId,
|
|
31231
|
+
run,
|
|
31232
|
+
marker: clearedCompression.marker,
|
|
31233
|
+
metadata: clearedCompression.metadata
|
|
31234
|
+
});
|
|
31235
|
+
}
|
|
30726
31236
|
if (assistant) {
|
|
30727
31237
|
await this.deps.appendEvent(conversationId, {
|
|
30728
31238
|
type: "message.completed",
|
|
@@ -30975,6 +31485,36 @@ function contextCompressionGeneration(message) {
|
|
|
30975
31485
|
function readPreviousSessionId(payload) {
|
|
30976
31486
|
return readString21(payload, "old_session_id") ?? readString21(payload, "oldSessionId") ?? readString21(payload, "previous_session_id") ?? readString21(payload, "previousSessionId");
|
|
30977
31487
|
}
|
|
31488
|
+
function contextCompressionClearedEventType(status) {
|
|
31489
|
+
if (status === "completed") {
|
|
31490
|
+
return "context_compression.completed";
|
|
31491
|
+
}
|
|
31492
|
+
if (status === "cancelled") {
|
|
31493
|
+
return "context_compression.cancelled";
|
|
31494
|
+
}
|
|
31495
|
+
if (status === "timed_out") {
|
|
31496
|
+
return "context_compression.timed_out";
|
|
31497
|
+
}
|
|
31498
|
+
return "context_compression.failed";
|
|
31499
|
+
}
|
|
31500
|
+
function staleAutomaticContextCompressionStatus(run) {
|
|
31501
|
+
if (run.context_compression?.status !== "compressing" || run.context_compression.source !== "auto") {
|
|
31502
|
+
return null;
|
|
31503
|
+
}
|
|
31504
|
+
if (run.status === "completed") {
|
|
31505
|
+
return "completed";
|
|
31506
|
+
}
|
|
31507
|
+
if (run.status === "failed") {
|
|
31508
|
+
return "failed";
|
|
31509
|
+
}
|
|
31510
|
+
if (run.status === "cancelled") {
|
|
31511
|
+
return "cancelled";
|
|
31512
|
+
}
|
|
31513
|
+
return null;
|
|
31514
|
+
}
|
|
31515
|
+
function isRunningAutomaticContextCompression(run) {
|
|
31516
|
+
return run.status === "running" && run.context_compression?.status === "compressing" && run.context_compression.source === "auto";
|
|
31517
|
+
}
|
|
30978
31518
|
function readNextSessionId(payload) {
|
|
30979
31519
|
const info = toRecord19(payload.info);
|
|
30980
31520
|
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");
|
|
@@ -31410,6 +31950,62 @@ function transcriptRecoveryCompletedEvent(hermesSessionId) {
|
|
|
31410
31950
|
}
|
|
31411
31951
|
};
|
|
31412
31952
|
}
|
|
31953
|
+
function hermesRunStatusToTerminalEvent(status) {
|
|
31954
|
+
if (!status) {
|
|
31955
|
+
return null;
|
|
31956
|
+
}
|
|
31957
|
+
const normalizedStatus = status.status.trim().toLowerCase();
|
|
31958
|
+
if (isCompletedRunStatus(normalizedStatus)) {
|
|
31959
|
+
return {
|
|
31960
|
+
eventName: "run.completed",
|
|
31961
|
+
payloadType: "run.completed",
|
|
31962
|
+
payload: {
|
|
31963
|
+
type: "run.completed",
|
|
31964
|
+
run_id: status.run_id,
|
|
31965
|
+
output: status.output,
|
|
31966
|
+
usage: status.usage,
|
|
31967
|
+
status: status.status
|
|
31968
|
+
},
|
|
31969
|
+
rawPayload: status.raw
|
|
31970
|
+
};
|
|
31971
|
+
}
|
|
31972
|
+
if (isFailedRunStatus(normalizedStatus)) {
|
|
31973
|
+
return {
|
|
31974
|
+
eventName: "run.failed",
|
|
31975
|
+
payloadType: "run.failed",
|
|
31976
|
+
payload: {
|
|
31977
|
+
type: "run.failed",
|
|
31978
|
+
run_id: status.run_id,
|
|
31979
|
+
error: {
|
|
31980
|
+
message: readStatusErrorMessage(status.error) ?? "Hermes run failed"
|
|
31981
|
+
},
|
|
31982
|
+
status: status.status,
|
|
31983
|
+
usage: status.usage
|
|
31984
|
+
},
|
|
31985
|
+
rawPayload: status.raw
|
|
31986
|
+
};
|
|
31987
|
+
}
|
|
31988
|
+
if (isCancelledRunStatus(normalizedStatus)) {
|
|
31989
|
+
return {
|
|
31990
|
+
eventName: "run.cancelled",
|
|
31991
|
+
payloadType: "run.cancelled",
|
|
31992
|
+
payload: {
|
|
31993
|
+
type: "run.cancelled",
|
|
31994
|
+
run_id: status.run_id,
|
|
31995
|
+
status: status.status
|
|
31996
|
+
},
|
|
31997
|
+
rawPayload: status.raw
|
|
31998
|
+
};
|
|
31999
|
+
}
|
|
32000
|
+
return null;
|
|
32001
|
+
}
|
|
32002
|
+
function detachedRunRecoveryFailedMessage(language) {
|
|
32003
|
+
return localizedRunText(
|
|
32004
|
+
language,
|
|
32005
|
+
"Hermes Link \u5DF2\u5931\u53BB\u8FD9\u6B21\u8FD0\u884C\u7684\u5B9E\u65F6\u8FDE\u63A5\uFF0C\u4E14\u65E0\u6CD5\u4ECE Hermes \u672C\u5730\u8BB0\u5F55\u6062\u590D\u6700\u7EC8\u7ED3\u679C\u3002\u8BF7\u91CD\u65B0\u53D1\u9001\uFF0C\u6216\u5728 Hermes Desktop \u4E2D\u67E5\u770B\u8BE5\u4F1A\u8BDD\u3002",
|
|
32006
|
+
"Hermes Link lost the realtime connection for this run and could not recover the final result from local Hermes records. Send the message again, or check this conversation in Hermes Desktop."
|
|
32007
|
+
);
|
|
32008
|
+
}
|
|
31413
32009
|
function readResponseId(payload) {
|
|
31414
32010
|
if (!payload) {
|
|
31415
32011
|
return null;
|
|
@@ -31432,6 +32028,10 @@ function isFailedRunStatus(status) {
|
|
|
31432
32028
|
function isCancelledRunStatus(status) {
|
|
31433
32029
|
return status === "cancelled" || status === "canceled" || status === "stopped" || status === "aborted";
|
|
31434
32030
|
}
|
|
32031
|
+
function isTerminalRunStatus(status) {
|
|
32032
|
+
const normalized = status.trim().toLowerCase();
|
|
32033
|
+
return isCompletedRunStatus(normalized) || isFailedRunStatus(normalized) || isCancelledRunStatus(normalized);
|
|
32034
|
+
}
|
|
31435
32035
|
function isTerminalConversationGoalStatus(status) {
|
|
31436
32036
|
return status === "done" || status === "paused" || status === "cleared";
|
|
31437
32037
|
}
|
|
@@ -31536,6 +32136,13 @@ function isNodeError18(error, code) {
|
|
|
31536
32136
|
// src/conversations/conversation-service.ts
|
|
31537
32137
|
var ALL_CONVERSATION_EVENTS = "conversation:*";
|
|
31538
32138
|
var HERMES_ARCHIVE_STATE_SYNC_ID = "hermes-agent-archive-state-v1";
|
|
32139
|
+
var STALE_RUN_REPAIR_RETRY_DELAYS_MS = [
|
|
32140
|
+
2e3,
|
|
32141
|
+
5e3,
|
|
32142
|
+
15e3,
|
|
32143
|
+
3e4,
|
|
32144
|
+
6e4
|
|
32145
|
+
];
|
|
31539
32146
|
function runtimeMatchesConfiguredModel(runtime, model) {
|
|
31540
32147
|
return runtime.model.id === model.id && (runtime.model.provider_key ?? runtime.model.provider ?? "") === model.provider && normalizeModelEndpoint(runtime.model.base_url) === normalizeModelEndpoint(model.baseUrl) && (runtime.model.api_mode ?? "") === (model.apiMode ?? "");
|
|
31541
32148
|
}
|
|
@@ -31656,6 +32263,8 @@ var ConversationService = class {
|
|
|
31656
32263
|
runLifecycle;
|
|
31657
32264
|
hermesSessionSyncPromise = null;
|
|
31658
32265
|
cronDeliverySyncPromise = null;
|
|
32266
|
+
staleRunRepairRetryAttempts = /* @__PURE__ */ new Map();
|
|
32267
|
+
staleRunRepairRetryTimers = /* @__PURE__ */ new Map();
|
|
31659
32268
|
async withConversationLock(conversationId, task) {
|
|
31660
32269
|
const previous = this.conversationLocks.get(conversationId) ?? Promise.resolve();
|
|
31661
32270
|
let release;
|
|
@@ -31700,19 +32309,34 @@ var ConversationService = class {
|
|
|
31700
32309
|
return this.store.readActiveManifest(conversationId);
|
|
31701
32310
|
}
|
|
31702
32311
|
async listConversations() {
|
|
31703
|
-
return this.
|
|
32312
|
+
return this.repairStaleConversationSummaries(
|
|
32313
|
+
await this.queries.listConversations(),
|
|
32314
|
+
() => this.queries.listConversations()
|
|
32315
|
+
);
|
|
31704
32316
|
}
|
|
31705
|
-
listConversationPage(input = {}) {
|
|
31706
|
-
return this.
|
|
32317
|
+
async listConversationPage(input = {}) {
|
|
32318
|
+
return this.repairStaleConversationListPage(
|
|
32319
|
+
await this.queries.listConversationPage(input),
|
|
32320
|
+
() => this.queries.listConversationPage(input)
|
|
32321
|
+
);
|
|
31707
32322
|
}
|
|
31708
|
-
searchConversationPage(input = {}) {
|
|
31709
|
-
return this.
|
|
32323
|
+
async searchConversationPage(input = {}) {
|
|
32324
|
+
return this.repairStaleConversationListPage(
|
|
32325
|
+
await this.queries.searchConversationPage(input),
|
|
32326
|
+
() => this.queries.searchConversationPage(input)
|
|
32327
|
+
);
|
|
31710
32328
|
}
|
|
31711
|
-
listArchivedConversationPage(input = {}) {
|
|
31712
|
-
return this.
|
|
32329
|
+
async listArchivedConversationPage(input = {}) {
|
|
32330
|
+
return this.repairStaleConversationListPage(
|
|
32331
|
+
await this.queries.listArchivedConversationPage(input),
|
|
32332
|
+
() => this.queries.listArchivedConversationPage(input)
|
|
32333
|
+
);
|
|
31713
32334
|
}
|
|
31714
|
-
searchArchivedConversationPage(input = {}) {
|
|
31715
|
-
return this.
|
|
32335
|
+
async searchArchivedConversationPage(input = {}) {
|
|
32336
|
+
return this.repairStaleConversationListPage(
|
|
32337
|
+
await this.queries.searchArchivedConversationPage(input),
|
|
32338
|
+
() => this.queries.searchArchivedConversationPage(input)
|
|
32339
|
+
);
|
|
31716
32340
|
}
|
|
31717
32341
|
async getStatistics(filter = {}) {
|
|
31718
32342
|
return readLinkStatistics(this.paths, filter);
|
|
@@ -32277,14 +32901,15 @@ var ConversationService = class {
|
|
|
32277
32901
|
}
|
|
32278
32902
|
async getMessages(conversationId, options = {}) {
|
|
32279
32903
|
if (options.syncHermes === true && !options.beforeMessageId) {
|
|
32280
|
-
await this.
|
|
32281
|
-
|
|
32282
|
-
|
|
32283
|
-
|
|
32284
|
-
|
|
32285
|
-
|
|
32286
|
-
|
|
32287
|
-
|
|
32904
|
+
await this.repairAndSyncConversation(conversationId, "messages_read");
|
|
32905
|
+
} else if (!options.beforeMessageId) {
|
|
32906
|
+
const repaired = await this.repairStaleConversationRuns(conversationId);
|
|
32907
|
+
if (repaired) {
|
|
32908
|
+
await this.syncHermesConversationMessagesBestEffort(
|
|
32909
|
+
conversationId,
|
|
32910
|
+
"messages_read_after_repair"
|
|
32911
|
+
);
|
|
32912
|
+
}
|
|
32288
32913
|
}
|
|
32289
32914
|
return this.queries.getMessages(conversationId, options);
|
|
32290
32915
|
}
|
|
@@ -32310,6 +32935,7 @@ var ConversationService = class {
|
|
|
32310
32935
|
"model_id or reasoning_effort is required"
|
|
32311
32936
|
);
|
|
32312
32937
|
}
|
|
32938
|
+
await this.repairAndSyncConversationBeforeWrite(conversationId);
|
|
32313
32939
|
return this.withConversationLock(conversationId, async () => {
|
|
32314
32940
|
let manifest = await this.restoreArchivedConversationForUserContinuationLocked(
|
|
32315
32941
|
conversationId
|
|
@@ -32429,6 +33055,7 @@ var ConversationService = class {
|
|
|
32429
33055
|
});
|
|
32430
33056
|
}
|
|
32431
33057
|
async setConversationProfile(conversationId, profileName) {
|
|
33058
|
+
await this.repairAndSyncConversationBeforeWrite(conversationId);
|
|
32432
33059
|
return this.withConversationLock(conversationId, async () => {
|
|
32433
33060
|
let manifest = await this.store.readRunnableManifest(conversationId);
|
|
32434
33061
|
const snapshot = await this.store.readSnapshot(conversationId);
|
|
@@ -32585,6 +33212,7 @@ var ConversationService = class {
|
|
|
32585
33212
|
};
|
|
32586
33213
|
}
|
|
32587
33214
|
async listEvents(conversationId, after = 0) {
|
|
33215
|
+
await this.repairStaleConversationRuns(conversationId);
|
|
32588
33216
|
return (await this.queries.listEvents(conversationId, after)).map(toAppVisibleConversationEvent).filter((event) => event !== null);
|
|
32589
33217
|
}
|
|
32590
33218
|
subscribe(conversationId, listener) {
|
|
@@ -32622,15 +33250,19 @@ var ConversationService = class {
|
|
|
32622
33250
|
return manifest?.status === "active";
|
|
32623
33251
|
}
|
|
32624
33252
|
async sendMessage(input) {
|
|
33253
|
+
await this.repairAndSyncConversationBeforeWrite(input.conversationId);
|
|
32625
33254
|
return this.orchestration.sendMessage(input);
|
|
32626
33255
|
}
|
|
32627
33256
|
async setGoalPaused(conversationId, paused, language) {
|
|
33257
|
+
await this.repairAndSyncConversationBeforeWrite(conversationId);
|
|
32628
33258
|
return this.orchestration.setGoalPaused(conversationId, paused, language);
|
|
32629
33259
|
}
|
|
32630
33260
|
async clearGoal(conversationId, language) {
|
|
33261
|
+
await this.repairAndSyncConversationBeforeWrite(conversationId);
|
|
32631
33262
|
return this.orchestration.clearGoal(conversationId, language);
|
|
32632
33263
|
}
|
|
32633
33264
|
async startContextCompression(input) {
|
|
33265
|
+
await this.repairAndSyncConversationBeforeWrite(input.conversationId);
|
|
32634
33266
|
return this.contextCompression.start(input);
|
|
32635
33267
|
}
|
|
32636
33268
|
async cancelRun(conversationId, runId) {
|
|
@@ -32659,6 +33291,117 @@ var ConversationService = class {
|
|
|
32659
33291
|
}
|
|
32660
33292
|
return this.cancelRun(conversationId, runId);
|
|
32661
33293
|
}
|
|
33294
|
+
async repairStaleConversationRuns(conversationId) {
|
|
33295
|
+
const compressionRepaired = await this.contextCompression.repairDetachedRunningCompressions(conversationId).catch((error) => {
|
|
33296
|
+
void this.logger.warn("conversation_stale_compression_repair_failed", {
|
|
33297
|
+
conversation_id: conversationId,
|
|
33298
|
+
error: error instanceof Error ? error.message : String(error)
|
|
33299
|
+
});
|
|
33300
|
+
return false;
|
|
33301
|
+
});
|
|
33302
|
+
const result = await this.runLifecycle.repairStaleConversationRuns(conversationId).catch((error) => {
|
|
33303
|
+
void this.logger.warn("conversation_stale_run_repair_failed", {
|
|
33304
|
+
conversation_id: conversationId,
|
|
33305
|
+
error: error instanceof Error ? error.message : String(error)
|
|
33306
|
+
});
|
|
33307
|
+
return null;
|
|
33308
|
+
});
|
|
33309
|
+
const hasPendingRepair = result?.pending === true;
|
|
33310
|
+
const repaired = compressionRepaired || result?.repaired === true || result?.shouldDrainQueue === true;
|
|
33311
|
+
const shouldDrainQueue = compressionRepaired || result?.shouldDrainQueue || await this.conversationQueueNeedsDrain(conversationId);
|
|
33312
|
+
if (!shouldDrainQueue) {
|
|
33313
|
+
if (hasPendingRepair) {
|
|
33314
|
+
this.scheduleStaleRunRepairRetry(conversationId);
|
|
33315
|
+
} else {
|
|
33316
|
+
this.clearStaleRunRepairRetry(conversationId);
|
|
33317
|
+
}
|
|
33318
|
+
return repaired;
|
|
33319
|
+
}
|
|
33320
|
+
if (hasPendingRepair) {
|
|
33321
|
+
this.scheduleStaleRunRepairRetry(conversationId);
|
|
33322
|
+
} else {
|
|
33323
|
+
this.clearStaleRunRepairRetry(conversationId);
|
|
33324
|
+
}
|
|
33325
|
+
void this.orchestration.startNextQueuedRun(conversationId).catch((error) => {
|
|
33326
|
+
void this.logger.warn("conversation_queue_drain_failed", {
|
|
33327
|
+
conversation_id: conversationId,
|
|
33328
|
+
error: error instanceof Error ? error.message : String(error)
|
|
33329
|
+
});
|
|
33330
|
+
});
|
|
33331
|
+
return true;
|
|
33332
|
+
}
|
|
33333
|
+
async repairAndSyncConversationBeforeWrite(conversationId) {
|
|
33334
|
+
await this.repairAndSyncConversation(conversationId, "conversation_write");
|
|
33335
|
+
}
|
|
33336
|
+
async repairAndSyncConversation(conversationId, source) {
|
|
33337
|
+
await this.repairStaleConversationRuns(conversationId);
|
|
33338
|
+
await this.syncHermesConversationMessagesBestEffort(conversationId, source);
|
|
33339
|
+
await this.repairStaleConversationRuns(conversationId);
|
|
33340
|
+
}
|
|
33341
|
+
async syncHermesConversationMessagesBestEffort(conversationId, source) {
|
|
33342
|
+
await this.syncHermesConversationMessages(conversationId).catch(
|
|
33343
|
+
(error) => {
|
|
33344
|
+
void this.logger.warn("hermes_conversation_message_sync_failed", {
|
|
33345
|
+
source,
|
|
33346
|
+
conversation_id: conversationId,
|
|
33347
|
+
error: error instanceof Error ? error.message : String(error)
|
|
33348
|
+
});
|
|
33349
|
+
}
|
|
33350
|
+
);
|
|
33351
|
+
}
|
|
33352
|
+
async conversationQueueNeedsDrain(conversationId) {
|
|
33353
|
+
const snapshot = await this.store.readSnapshot(conversationId).catch(() => null);
|
|
33354
|
+
return Boolean(
|
|
33355
|
+
snapshot && hasQueuedRuns(snapshot) && !hasRunningRuns(snapshot)
|
|
33356
|
+
);
|
|
33357
|
+
}
|
|
33358
|
+
async repairStaleConversationSummaries(summaries, reread) {
|
|
33359
|
+
let repaired = false;
|
|
33360
|
+
for (const summary of summaries) {
|
|
33361
|
+
if (!conversationSummaryNeedsStaleRunRepair(summary)) {
|
|
33362
|
+
continue;
|
|
33363
|
+
}
|
|
33364
|
+
await this.repairStaleConversationRuns(summary.id);
|
|
33365
|
+
repaired = true;
|
|
33366
|
+
}
|
|
33367
|
+
if (!repaired) {
|
|
33368
|
+
return summaries;
|
|
33369
|
+
}
|
|
33370
|
+
return reread();
|
|
33371
|
+
}
|
|
33372
|
+
async repairStaleConversationListPage(page, reread) {
|
|
33373
|
+
let repaired = false;
|
|
33374
|
+
for (const summary of page.conversations) {
|
|
33375
|
+
if (!conversationSummaryNeedsStaleRunRepair(summary)) {
|
|
33376
|
+
continue;
|
|
33377
|
+
}
|
|
33378
|
+
await this.repairStaleConversationRuns(summary.id);
|
|
33379
|
+
repaired = true;
|
|
33380
|
+
}
|
|
33381
|
+
return repaired ? reread() : page;
|
|
33382
|
+
}
|
|
33383
|
+
scheduleStaleRunRepairRetry(conversationId) {
|
|
33384
|
+
if (this.staleRunRepairRetryTimers.has(conversationId)) {
|
|
33385
|
+
return;
|
|
33386
|
+
}
|
|
33387
|
+
const attempt = this.staleRunRepairRetryAttempts.get(conversationId) ?? 0;
|
|
33388
|
+
const delayMs = STALE_RUN_REPAIR_RETRY_DELAYS_MS[Math.min(attempt, STALE_RUN_REPAIR_RETRY_DELAYS_MS.length - 1)];
|
|
33389
|
+
this.staleRunRepairRetryAttempts.set(conversationId, attempt + 1);
|
|
33390
|
+
const timer = setTimeout(() => {
|
|
33391
|
+
this.staleRunRepairRetryTimers.delete(conversationId);
|
|
33392
|
+
void this.repairStaleConversationRuns(conversationId);
|
|
33393
|
+
}, delayMs);
|
|
33394
|
+
timer.unref?.();
|
|
33395
|
+
this.staleRunRepairRetryTimers.set(conversationId, timer);
|
|
33396
|
+
}
|
|
33397
|
+
clearStaleRunRepairRetry(conversationId) {
|
|
33398
|
+
this.staleRunRepairRetryAttempts.delete(conversationId);
|
|
33399
|
+
const timer = this.staleRunRepairRetryTimers.get(conversationId);
|
|
33400
|
+
if (timer) {
|
|
33401
|
+
clearTimeout(timer);
|
|
33402
|
+
this.staleRunRepairRetryTimers.delete(conversationId);
|
|
33403
|
+
}
|
|
33404
|
+
}
|
|
32662
33405
|
async guideQueuedRun(conversationId, runId) {
|
|
32663
33406
|
return this.orchestration.guideQueuedRun(conversationId, runId);
|
|
32664
33407
|
}
|
|
@@ -33636,6 +34379,9 @@ function liveActivityPhaseForEvent(event, run, contextOperation) {
|
|
|
33636
34379
|
if (type === "context_compression.timed_out") {
|
|
33637
34380
|
return "context_compression_timed_out";
|
|
33638
34381
|
}
|
|
34382
|
+
if (type === "context_compression.cancelled" || type === "context_compression.canceled") {
|
|
34383
|
+
return "cancelled";
|
|
34384
|
+
}
|
|
33639
34385
|
if (type === "run.completed") {
|
|
33640
34386
|
return "completed";
|
|
33641
34387
|
}
|
|
@@ -33771,6 +34517,13 @@ function readInteger4(value, key) {
|
|
|
33771
34517
|
}
|
|
33772
34518
|
return Math.max(0, Math.trunc(parsed));
|
|
33773
34519
|
}
|
|
34520
|
+
function conversationSummaryNeedsStaleRunRepair(summary) {
|
|
34521
|
+
const eventStream = summary.event_stream;
|
|
34522
|
+
if (!eventStream) {
|
|
34523
|
+
return false;
|
|
34524
|
+
}
|
|
34525
|
+
return eventStream.has_active_runs || eventStream.reason === "context_compression" || eventStream.reason === "active_run" || eventStream.reason === "queued_run" || eventStream.reason === "unknown";
|
|
34526
|
+
}
|
|
33774
34527
|
function approvalRestartText(language, zh, en) {
|
|
33775
34528
|
return language === "en" ? en : zh;
|
|
33776
34529
|
}
|
package/dist/cli/index.js
CHANGED
package/dist/http/app.d.ts
CHANGED
|
@@ -626,6 +626,8 @@ declare class ConversationService {
|
|
|
626
626
|
private readonly runLifecycle;
|
|
627
627
|
private hermesSessionSyncPromise;
|
|
628
628
|
private cronDeliverySyncPromise;
|
|
629
|
+
private readonly staleRunRepairRetryAttempts;
|
|
630
|
+
private readonly staleRunRepairRetryTimers;
|
|
629
631
|
constructor(paths: RuntimePaths, logger: FileLogger);
|
|
630
632
|
private withConversationLock;
|
|
631
633
|
private restoreArchivedConversationForUserContinuationLocked;
|
|
@@ -753,6 +755,15 @@ declare class ConversationService {
|
|
|
753
755
|
startContextCompression(input: StartContextCompressionInput): Promise<ContextCompressionResult>;
|
|
754
756
|
cancelRun(conversationId: string, runId: string): Promise<CancelRunResult>;
|
|
755
757
|
cancelRunById(runId: string): Promise<CancelRunResult>;
|
|
758
|
+
private repairStaleConversationRuns;
|
|
759
|
+
private repairAndSyncConversationBeforeWrite;
|
|
760
|
+
private repairAndSyncConversation;
|
|
761
|
+
private syncHermesConversationMessagesBestEffort;
|
|
762
|
+
private conversationQueueNeedsDrain;
|
|
763
|
+
private repairStaleConversationSummaries;
|
|
764
|
+
private repairStaleConversationListPage;
|
|
765
|
+
private scheduleStaleRunRepairRetry;
|
|
766
|
+
private clearStaleRunRepairRetry;
|
|
756
767
|
guideQueuedRun(conversationId: string, runId: string): Promise<QueuedRunActionResult>;
|
|
757
768
|
cancelQueuedRun(conversationId: string, runId: string): Promise<QueuedRunActionResult>;
|
|
758
769
|
resolveApproval(input: {
|
package/dist/http/app.js
CHANGED