@hermespilot/link 0.8.5-beta.0 → 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-JJBIYFGY.js → chunk-QT55LT5X.js} +545 -30
- 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.5
|
|
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(
|
|
@@ -18253,6 +18261,13 @@ var ConversationOrchestrationCoordinator = class {
|
|
|
18253
18261
|
attachments: userAttachmentParts,
|
|
18254
18262
|
language: normalizeLanguage(input.language)
|
|
18255
18263
|
}) : null;
|
|
18264
|
+
if (goalRequest?.hermesSessionId) {
|
|
18265
|
+
manifest = addHermesSessionIdForProfileToManifest(
|
|
18266
|
+
manifest,
|
|
18267
|
+
goalRequest.hermesSessionId,
|
|
18268
|
+
runtime.profileName
|
|
18269
|
+
);
|
|
18270
|
+
}
|
|
18256
18271
|
if ((hasRunningRuns(snapshot) || queuedRunCount(snapshot) > 0) && queuedRunCount(snapshot) >= MAX_CONVERSATION_QUEUED_RUNS) {
|
|
18257
18272
|
throw new LinkHttpError(
|
|
18258
18273
|
409,
|
|
@@ -18467,6 +18482,7 @@ var ConversationOrchestrationCoordinator = class {
|
|
|
18467
18482
|
maxTurns: readGoalProgressFromStatusText(result.notice ?? result.output ?? "").maxTurns
|
|
18468
18483
|
}),
|
|
18469
18484
|
kickoffInput: result.message.trim(),
|
|
18485
|
+
...result.storedSessionId ? { hermesSessionId: result.storedSessionId } : {},
|
|
18470
18486
|
...result.notice ? { notice: result.notice } : {},
|
|
18471
18487
|
...result.output ? { output: result.output } : {}
|
|
18472
18488
|
};
|
|
@@ -20248,6 +20264,40 @@ var ContextCompressionCoordinator = class {
|
|
|
20248
20264
|
hasActiveCompression(snapshot) {
|
|
20249
20265
|
return snapshot.runs.some((run) => isActiveCompressionRun(run));
|
|
20250
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
|
+
}
|
|
20251
20301
|
async startLocked(input) {
|
|
20252
20302
|
const focus = normalizeFocus(input.focus);
|
|
20253
20303
|
const language = normalizeLanguage2(input.language);
|
|
@@ -21736,6 +21786,7 @@ async function syncHermesConversationMessages(paths, logger, input) {
|
|
|
21736
21786
|
if (appendedMessages.length === 0) {
|
|
21737
21787
|
let nextManifest2 = candidateProfiles.size > 0 || duplicateCleanup.removedCount > 0 ? await store.readManifest(input.conversationId) : null;
|
|
21738
21788
|
const currentManifest = nextManifest2;
|
|
21789
|
+
let lineageChanged = false;
|
|
21739
21790
|
if (nextManifest2) {
|
|
21740
21791
|
for (const profile of candidateProfiles.values()) {
|
|
21741
21792
|
nextManifest2 = mergeHermesLineageIntoManifest({
|
|
@@ -21747,6 +21798,7 @@ async function syncHermesConversationMessages(paths, logger, input) {
|
|
|
21747
21798
|
updatedAt: isoFromHermesTime(profile.candidate.session.last_active) ?? nextManifest2.updated_at
|
|
21748
21799
|
});
|
|
21749
21800
|
}
|
|
21801
|
+
lineageChanged = !manifestEquivalent(currentManifest, nextManifest2);
|
|
21750
21802
|
if (duplicateCleanup.removedCount > 0) {
|
|
21751
21803
|
nextManifest2 = {
|
|
21752
21804
|
...nextManifest2,
|
|
@@ -21757,13 +21809,30 @@ async function syncHermesConversationMessages(paths, logger, input) {
|
|
|
21757
21809
|
if (duplicateCleanup.removedCount > 0) {
|
|
21758
21810
|
await store.writeSnapshot(input.conversationId, snapshot);
|
|
21759
21811
|
}
|
|
21760
|
-
if (nextManifest2 && currentManifest && (duplicateCleanup.removedCount > 0 ||
|
|
21812
|
+
if (nextManifest2 && currentManifest && (duplicateCleanup.removedCount > 0 || lineageChanged)) {
|
|
21761
21813
|
const stats2 = buildConversationStats(nextManifest2, snapshot);
|
|
21762
21814
|
nextManifest2 = { ...nextManifest2, stats: stats2 };
|
|
21763
21815
|
await store.writeManifest(nextManifest2);
|
|
21764
21816
|
await upsertConversationStats(paths, toStatsIndexRecord(nextManifest2, stats2));
|
|
21765
21817
|
result.hermes_session_ids = manifestHermesSessionIds(nextManifest2);
|
|
21766
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
|
+
}
|
|
21767
21836
|
}
|
|
21768
21837
|
return result;
|
|
21769
21838
|
}
|
|
@@ -22194,6 +22263,18 @@ function isStrictImportedDuplicateRunMessage(paths, snapshot, message) {
|
|
|
22194
22263
|
if (!isMessageInsideRunCleanupWindow(message, run)) {
|
|
22195
22264
|
return false;
|
|
22196
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
|
+
}
|
|
22197
22278
|
if (message.role === "user") {
|
|
22198
22279
|
const user = snapshot.messages.find(
|
|
22199
22280
|
(item) => item.id === run.trigger_message_id
|
|
@@ -22209,6 +22290,20 @@ function isStrictImportedDuplicateRunMessage(paths, snapshot, message) {
|
|
|
22209
22290
|
return false;
|
|
22210
22291
|
});
|
|
22211
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
|
+
}
|
|
22212
22307
|
function isDuplicateImportedRunUser(paths, user, imported) {
|
|
22213
22308
|
const importedText = normalizedDuplicateText(imported);
|
|
22214
22309
|
if (!importedText) {
|
|
@@ -22281,7 +22376,7 @@ function normalizedDuplicateTextValue(value) {
|
|
|
22281
22376
|
return value.replace(/\r\n/gu, "\n").trim();
|
|
22282
22377
|
}
|
|
22283
22378
|
function collectRunHermesMessageWindows(run) {
|
|
22284
|
-
|
|
22379
|
+
let before = normalizeWatermarkPoint(
|
|
22285
22380
|
run.hermes_message_watermark?.before,
|
|
22286
22381
|
run.hermes_session_id
|
|
22287
22382
|
);
|
|
@@ -22292,10 +22387,15 @@ function collectRunHermesMessageWindows(run) {
|
|
|
22292
22387
|
run.hermes_message_watermark?.after,
|
|
22293
22388
|
run.hermes_session_id
|
|
22294
22389
|
);
|
|
22295
|
-
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();
|
|
22296
22393
|
if (!sessionId) {
|
|
22297
22394
|
return [];
|
|
22298
22395
|
}
|
|
22396
|
+
if (before.session_id !== sessionId) {
|
|
22397
|
+
before = { ...before, session_id: sessionId };
|
|
22398
|
+
}
|
|
22299
22399
|
return [
|
|
22300
22400
|
{
|
|
22301
22401
|
runId: run.id,
|
|
@@ -22419,6 +22519,9 @@ function isRowInsideRunWindow(window, row, role) {
|
|
|
22419
22519
|
const upperTimeMs = hermesTimestampMillis(window.after?.max_timestamp) ?? (allowTerminalGrace && window.completedAtMs !== void 0 ? window.completedAtMs + 2 * 60 * 1e3 : allowOpenEnded ? Number.POSITIVE_INFINITY : void 0);
|
|
22420
22520
|
return upperTimeMs !== void 0 && (beforeTimeMs !== void 0 ? rowTimeMs > lowerTimeMs : rowTimeMs >= lowerTimeMs) && rowTimeMs <= upperTimeMs;
|
|
22421
22521
|
}
|
|
22522
|
+
function isPlaceholderHermesSessionId(sessionId) {
|
|
22523
|
+
return sessionId.startsWith("hp_");
|
|
22524
|
+
}
|
|
22422
22525
|
function isVoicePart(part) {
|
|
22423
22526
|
return part.kind === "voice" || part.is_voice_note === true;
|
|
22424
22527
|
}
|
|
@@ -22777,6 +22880,13 @@ function mergeHermesLineageIntoManifest(input) {
|
|
|
22777
22880
|
function manifestEquivalent(left, right) {
|
|
22778
22881
|
return stableJson(left) === stableJson(right);
|
|
22779
22882
|
}
|
|
22883
|
+
async function summarizeSyncedConversation(paths, manifest, snapshot) {
|
|
22884
|
+
return toSummary(
|
|
22885
|
+
manifest,
|
|
22886
|
+
snapshot,
|
|
22887
|
+
await readConversationProfileSummary(paths, manifest)
|
|
22888
|
+
);
|
|
22889
|
+
}
|
|
22780
22890
|
function collectImportedHermesPrefix(snapshot) {
|
|
22781
22891
|
const rows = [];
|
|
22782
22892
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -29747,16 +29857,36 @@ ${resolved}` : resolved;
|
|
|
29747
29857
|
const detachedRunningRepaired = await this.repairDetachedRunningAutomaticContextCompression(conversationId);
|
|
29748
29858
|
return terminalRepaired || detachedRunningRepaired;
|
|
29749
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
|
+
}
|
|
29750
29883
|
async repairDetachedRunningAutomaticContextCompression(conversationId) {
|
|
29751
29884
|
const snapshot = await this.deps.readSnapshot(conversationId).catch(() => null);
|
|
29752
29885
|
const candidates = snapshot?.runs.filter(
|
|
29753
|
-
(run) => isRunningAutomaticContextCompression(run)
|
|
29886
|
+
(run) => isRunningAutomaticContextCompression(run)
|
|
29754
29887
|
) ?? [];
|
|
29755
29888
|
let repaired = false;
|
|
29756
29889
|
for (const run of candidates) {
|
|
29757
|
-
if (this.deps.activeRunControllers.has(run.id)) {
|
|
29758
|
-
continue;
|
|
29759
|
-
}
|
|
29760
29890
|
const previousSessionId = run.context_compression?.previous_session_id?.trim() || run.hermes_session_id;
|
|
29761
29891
|
const nextSessionId = await readHermesCompressionTip(
|
|
29762
29892
|
previousSessionId,
|
|
@@ -29775,11 +29905,177 @@ ${resolved}` : resolved;
|
|
|
29775
29905
|
if (!finished) {
|
|
29776
29906
|
continue;
|
|
29777
29907
|
}
|
|
29908
|
+
this.abortAndForgetActiveRunController(run.id);
|
|
29778
29909
|
await this.completeRun(conversationId, run.id);
|
|
29779
29910
|
repaired = true;
|
|
29780
29911
|
}
|
|
29781
29912
|
return repaired;
|
|
29782
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
|
+
}
|
|
29783
30079
|
clearAutomaticContextCompressionInSnapshot(input) {
|
|
29784
30080
|
if (input.run.context_compression?.status !== "compressing" || input.run.context_compression.source !== "auto") {
|
|
29785
30081
|
return null;
|
|
@@ -29835,6 +30131,17 @@ ${resolved}` : resolved;
|
|
|
29835
30131
|
}
|
|
29836
30132
|
return messageText2(assistant).length > 0 || (assistant.agent_events?.length ?? 0) > 0 || (assistant.approvals?.length ?? 0) > 0 || assistant.parts.some((part) => part.type !== "text");
|
|
29837
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
|
+
}
|
|
29838
30145
|
async appendAssistantTextFromCompletedResponse(conversationId, runId, event) {
|
|
29839
30146
|
const terminalText = extractResponseAssistantText(event.payload);
|
|
29840
30147
|
if (!terminalText?.trim()) {
|
|
@@ -31643,6 +31950,62 @@ function transcriptRecoveryCompletedEvent(hermesSessionId) {
|
|
|
31643
31950
|
}
|
|
31644
31951
|
};
|
|
31645
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
|
+
}
|
|
31646
32009
|
function readResponseId(payload) {
|
|
31647
32010
|
if (!payload) {
|
|
31648
32011
|
return null;
|
|
@@ -31665,6 +32028,10 @@ function isFailedRunStatus(status) {
|
|
|
31665
32028
|
function isCancelledRunStatus(status) {
|
|
31666
32029
|
return status === "cancelled" || status === "canceled" || status === "stopped" || status === "aborted";
|
|
31667
32030
|
}
|
|
32031
|
+
function isTerminalRunStatus(status) {
|
|
32032
|
+
const normalized = status.trim().toLowerCase();
|
|
32033
|
+
return isCompletedRunStatus(normalized) || isFailedRunStatus(normalized) || isCancelledRunStatus(normalized);
|
|
32034
|
+
}
|
|
31668
32035
|
function isTerminalConversationGoalStatus(status) {
|
|
31669
32036
|
return status === "done" || status === "paused" || status === "cleared";
|
|
31670
32037
|
}
|
|
@@ -31769,6 +32136,13 @@ function isNodeError18(error, code) {
|
|
|
31769
32136
|
// src/conversations/conversation-service.ts
|
|
31770
32137
|
var ALL_CONVERSATION_EVENTS = "conversation:*";
|
|
31771
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
|
+
];
|
|
31772
32146
|
function runtimeMatchesConfiguredModel(runtime, model) {
|
|
31773
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 ?? "");
|
|
31774
32148
|
}
|
|
@@ -31889,6 +32263,8 @@ var ConversationService = class {
|
|
|
31889
32263
|
runLifecycle;
|
|
31890
32264
|
hermesSessionSyncPromise = null;
|
|
31891
32265
|
cronDeliverySyncPromise = null;
|
|
32266
|
+
staleRunRepairRetryAttempts = /* @__PURE__ */ new Map();
|
|
32267
|
+
staleRunRepairRetryTimers = /* @__PURE__ */ new Map();
|
|
31892
32268
|
async withConversationLock(conversationId, task) {
|
|
31893
32269
|
const previous = this.conversationLocks.get(conversationId) ?? Promise.resolve();
|
|
31894
32270
|
let release;
|
|
@@ -31933,19 +32309,34 @@ var ConversationService = class {
|
|
|
31933
32309
|
return this.store.readActiveManifest(conversationId);
|
|
31934
32310
|
}
|
|
31935
32311
|
async listConversations() {
|
|
31936
|
-
return this.
|
|
32312
|
+
return this.repairStaleConversationSummaries(
|
|
32313
|
+
await this.queries.listConversations(),
|
|
32314
|
+
() => this.queries.listConversations()
|
|
32315
|
+
);
|
|
31937
32316
|
}
|
|
31938
|
-
listConversationPage(input = {}) {
|
|
31939
|
-
return this.
|
|
32317
|
+
async listConversationPage(input = {}) {
|
|
32318
|
+
return this.repairStaleConversationListPage(
|
|
32319
|
+
await this.queries.listConversationPage(input),
|
|
32320
|
+
() => this.queries.listConversationPage(input)
|
|
32321
|
+
);
|
|
31940
32322
|
}
|
|
31941
|
-
searchConversationPage(input = {}) {
|
|
31942
|
-
return this.
|
|
32323
|
+
async searchConversationPage(input = {}) {
|
|
32324
|
+
return this.repairStaleConversationListPage(
|
|
32325
|
+
await this.queries.searchConversationPage(input),
|
|
32326
|
+
() => this.queries.searchConversationPage(input)
|
|
32327
|
+
);
|
|
31943
32328
|
}
|
|
31944
|
-
listArchivedConversationPage(input = {}) {
|
|
31945
|
-
return this.
|
|
32329
|
+
async listArchivedConversationPage(input = {}) {
|
|
32330
|
+
return this.repairStaleConversationListPage(
|
|
32331
|
+
await this.queries.listArchivedConversationPage(input),
|
|
32332
|
+
() => this.queries.listArchivedConversationPage(input)
|
|
32333
|
+
);
|
|
31946
32334
|
}
|
|
31947
|
-
searchArchivedConversationPage(input = {}) {
|
|
31948
|
-
return this.
|
|
32335
|
+
async searchArchivedConversationPage(input = {}) {
|
|
32336
|
+
return this.repairStaleConversationListPage(
|
|
32337
|
+
await this.queries.searchArchivedConversationPage(input),
|
|
32338
|
+
() => this.queries.searchArchivedConversationPage(input)
|
|
32339
|
+
);
|
|
31949
32340
|
}
|
|
31950
32341
|
async getStatistics(filter = {}) {
|
|
31951
32342
|
return readLinkStatistics(this.paths, filter);
|
|
@@ -32510,19 +32901,15 @@ var ConversationService = class {
|
|
|
32510
32901
|
}
|
|
32511
32902
|
async getMessages(conversationId, options = {}) {
|
|
32512
32903
|
if (options.syncHermes === true && !options.beforeMessageId) {
|
|
32513
|
-
await this.
|
|
32514
|
-
|
|
32515
|
-
|
|
32516
|
-
|
|
32517
|
-
|
|
32518
|
-
|
|
32519
|
-
|
|
32520
|
-
|
|
32521
|
-
|
|
32522
|
-
if (!options.beforeMessageId) {
|
|
32523
|
-
await this.runLifecycle.repairStaleAutomaticContextCompression(
|
|
32524
|
-
conversationId
|
|
32525
|
-
);
|
|
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
|
+
}
|
|
32526
32913
|
}
|
|
32527
32914
|
return this.queries.getMessages(conversationId, options);
|
|
32528
32915
|
}
|
|
@@ -32548,6 +32935,7 @@ var ConversationService = class {
|
|
|
32548
32935
|
"model_id or reasoning_effort is required"
|
|
32549
32936
|
);
|
|
32550
32937
|
}
|
|
32938
|
+
await this.repairAndSyncConversationBeforeWrite(conversationId);
|
|
32551
32939
|
return this.withConversationLock(conversationId, async () => {
|
|
32552
32940
|
let manifest = await this.restoreArchivedConversationForUserContinuationLocked(
|
|
32553
32941
|
conversationId
|
|
@@ -32667,6 +33055,7 @@ var ConversationService = class {
|
|
|
32667
33055
|
});
|
|
32668
33056
|
}
|
|
32669
33057
|
async setConversationProfile(conversationId, profileName) {
|
|
33058
|
+
await this.repairAndSyncConversationBeforeWrite(conversationId);
|
|
32670
33059
|
return this.withConversationLock(conversationId, async () => {
|
|
32671
33060
|
let manifest = await this.store.readRunnableManifest(conversationId);
|
|
32672
33061
|
const snapshot = await this.store.readSnapshot(conversationId);
|
|
@@ -32823,6 +33212,7 @@ var ConversationService = class {
|
|
|
32823
33212
|
};
|
|
32824
33213
|
}
|
|
32825
33214
|
async listEvents(conversationId, after = 0) {
|
|
33215
|
+
await this.repairStaleConversationRuns(conversationId);
|
|
32826
33216
|
return (await this.queries.listEvents(conversationId, after)).map(toAppVisibleConversationEvent).filter((event) => event !== null);
|
|
32827
33217
|
}
|
|
32828
33218
|
subscribe(conversationId, listener) {
|
|
@@ -32860,15 +33250,19 @@ var ConversationService = class {
|
|
|
32860
33250
|
return manifest?.status === "active";
|
|
32861
33251
|
}
|
|
32862
33252
|
async sendMessage(input) {
|
|
33253
|
+
await this.repairAndSyncConversationBeforeWrite(input.conversationId);
|
|
32863
33254
|
return this.orchestration.sendMessage(input);
|
|
32864
33255
|
}
|
|
32865
33256
|
async setGoalPaused(conversationId, paused, language) {
|
|
33257
|
+
await this.repairAndSyncConversationBeforeWrite(conversationId);
|
|
32866
33258
|
return this.orchestration.setGoalPaused(conversationId, paused, language);
|
|
32867
33259
|
}
|
|
32868
33260
|
async clearGoal(conversationId, language) {
|
|
33261
|
+
await this.repairAndSyncConversationBeforeWrite(conversationId);
|
|
32869
33262
|
return this.orchestration.clearGoal(conversationId, language);
|
|
32870
33263
|
}
|
|
32871
33264
|
async startContextCompression(input) {
|
|
33265
|
+
await this.repairAndSyncConversationBeforeWrite(input.conversationId);
|
|
32872
33266
|
return this.contextCompression.start(input);
|
|
32873
33267
|
}
|
|
32874
33268
|
async cancelRun(conversationId, runId) {
|
|
@@ -32897,6 +33291,117 @@ var ConversationService = class {
|
|
|
32897
33291
|
}
|
|
32898
33292
|
return this.cancelRun(conversationId, runId);
|
|
32899
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
|
+
}
|
|
32900
33405
|
async guideQueuedRun(conversationId, runId) {
|
|
32901
33406
|
return this.orchestration.guideQueuedRun(conversationId, runId);
|
|
32902
33407
|
}
|
|
@@ -33874,6 +34379,9 @@ function liveActivityPhaseForEvent(event, run, contextOperation) {
|
|
|
33874
34379
|
if (type === "context_compression.timed_out") {
|
|
33875
34380
|
return "context_compression_timed_out";
|
|
33876
34381
|
}
|
|
34382
|
+
if (type === "context_compression.cancelled" || type === "context_compression.canceled") {
|
|
34383
|
+
return "cancelled";
|
|
34384
|
+
}
|
|
33877
34385
|
if (type === "run.completed") {
|
|
33878
34386
|
return "completed";
|
|
33879
34387
|
}
|
|
@@ -34009,6 +34517,13 @@ function readInteger4(value, key) {
|
|
|
34009
34517
|
}
|
|
34010
34518
|
return Math.max(0, Math.trunc(parsed));
|
|
34011
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
|
+
}
|
|
34012
34527
|
function approvalRestartText(language, zh, en) {
|
|
34013
34528
|
return language === "en" ? en : zh;
|
|
34014
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