@hermespilot/link 0.3.5 → 0.3.6
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-ZQO7TU7G.js → chunk-773L37RF.js} +1375 -1074
- package/dist/cli/index.js +1 -1
- package/dist/http/app.d.ts +2 -1
- package/dist/http/app.js +1 -1
- package/package.json +1 -1
|
@@ -3724,7 +3724,7 @@ import os2 from "os";
|
|
|
3724
3724
|
import path5 from "path";
|
|
3725
3725
|
|
|
3726
3726
|
// src/constants.ts
|
|
3727
|
-
var LINK_VERSION = "0.3.
|
|
3727
|
+
var LINK_VERSION = "0.3.6";
|
|
3728
3728
|
var LINK_COMMAND = "hermeslink";
|
|
3729
3729
|
var LINK_DEFAULT_PORT = 52379;
|
|
3730
3730
|
var LINK_RUNTIME_DIR_NAME = ".hermeslink";
|
|
@@ -4859,6 +4859,9 @@ function toRecord3(value) {
|
|
|
4859
4859
|
}
|
|
4860
4860
|
|
|
4861
4861
|
// src/conversations/statistics.ts
|
|
4862
|
+
var ESTIMATED_CONTEXT_CHARS_PER_TOKEN = 4;
|
|
4863
|
+
var ESTIMATED_CONTEXT_BASE_OVERHEAD_TOKENS = 256;
|
|
4864
|
+
var ESTIMATED_CONTEXT_PER_MESSAGE_OVERHEAD_TOKENS = 8;
|
|
4862
4865
|
function buildConversationStats(manifest, snapshot) {
|
|
4863
4866
|
if (manifest.stats && manifest.status !== "active" && snapshot.messages.length === 0 && snapshot.runs.length === 0) {
|
|
4864
4867
|
return manifest.stats;
|
|
@@ -4973,12 +4976,21 @@ function readUsage(payload) {
|
|
|
4973
4976
|
return void 0;
|
|
4974
4977
|
}
|
|
4975
4978
|
const usage = toRecord4(payload.usage);
|
|
4976
|
-
const
|
|
4977
|
-
const
|
|
4978
|
-
const
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4979
|
+
const response = toRecord4(payload.response);
|
|
4980
|
+
const responseUsage = toRecord4(response.usage);
|
|
4981
|
+
const context = firstRecord(
|
|
4982
|
+
payload.context,
|
|
4983
|
+
usage.context,
|
|
4984
|
+
response.context,
|
|
4985
|
+
responseUsage.context
|
|
4986
|
+
);
|
|
4987
|
+
const inputTokens = readInteger(usage, "input_tokens") ?? readInteger(usage, "prompt_tokens") ?? readInteger(responseUsage, "input_tokens") ?? readInteger(responseUsage, "prompt_tokens") ?? readInteger(payload, "input_tokens") ?? readInteger(payload, "prompt_tokens");
|
|
4988
|
+
const outputTokens = readInteger(usage, "output_tokens") ?? readInteger(usage, "completion_tokens") ?? readInteger(responseUsage, "output_tokens") ?? readInteger(responseUsage, "completion_tokens") ?? readInteger(payload, "output_tokens") ?? readInteger(payload, "completion_tokens");
|
|
4989
|
+
const totalTokens = readInteger(usage, "total_tokens") ?? readInteger(responseUsage, "total_tokens") ?? readInteger(payload, "total_tokens") ?? (inputTokens ?? 0) + (outputTokens ?? 0);
|
|
4990
|
+
const contextWindow = readInteger(context, "window_tokens") ?? readInteger(context, "windowTokens") ?? readInteger(context, "context_window") ?? readInteger(context, "context_max") ?? readInteger(context, "context_length") ?? readInteger(usage, "context_window") ?? readInteger(usage, "context_max") ?? readInteger(usage, "context_length") ?? readInteger(responseUsage, "context_window") ?? readInteger(responseUsage, "context_max") ?? readInteger(responseUsage, "context_length") ?? readInteger(response, "context_window") ?? readInteger(response, "context_max") ?? readInteger(response, "context_length") ?? readInteger(payload, "context_window") ?? readInteger(payload, "context_max") ?? readInteger(payload, "context_length");
|
|
4991
|
+
const explicitContextTokens = readInteger(context, "used_tokens") ?? readInteger(context, "usedTokens") ?? readInteger(context, "context_tokens") ?? readInteger(context, "context_used") ?? readInteger(context, "current_context_tokens") ?? readInteger(context, "last_prompt_tokens") ?? readInteger(usage, "context_tokens") ?? readInteger(usage, "context_used") ?? readInteger(usage, "current_context_tokens") ?? readInteger(usage, "last_prompt_tokens") ?? readInteger(responseUsage, "context_tokens") ?? readInteger(responseUsage, "context_used") ?? readInteger(responseUsage, "current_context_tokens") ?? readInteger(responseUsage, "last_prompt_tokens") ?? readInteger(response, "context_tokens") ?? readInteger(response, "context_used") ?? readInteger(response, "current_context_tokens") ?? readInteger(response, "last_prompt_tokens") ?? readInteger(payload, "context_tokens") ?? readInteger(payload, "context_used") ?? readInteger(payload, "current_context_tokens") ?? readInteger(payload, "last_prompt_tokens");
|
|
4992
|
+
const explicitUsagePercent = readInteger(context, "usage_percent") ?? readInteger(context, "context_percent") ?? readInteger(usage, "usage_percent") ?? readInteger(usage, "context_percent") ?? readInteger(responseUsage, "usage_percent") ?? readInteger(responseUsage, "context_percent") ?? readInteger(response, "usage_percent") ?? readInteger(response, "context_percent") ?? readInteger(payload, "usage_percent") ?? readInteger(payload, "context_percent");
|
|
4993
|
+
if (!inputTokens && !outputTokens && !totalTokens && explicitContextTokens === void 0) {
|
|
4982
4994
|
return void 0;
|
|
4983
4995
|
}
|
|
4984
4996
|
return {
|
|
@@ -4987,14 +4999,44 @@ function readUsage(payload) {
|
|
|
4987
4999
|
total_tokens: totalTokens,
|
|
4988
5000
|
...explicitContextTokens !== void 0 ? { context_tokens: explicitContextTokens } : {},
|
|
4989
5001
|
...contextWindow !== void 0 ? { context_window: contextWindow } : {},
|
|
5002
|
+
...explicitContextTokens !== void 0 ? { context_source: "explicit" } : {},
|
|
4990
5003
|
...explicitContextTokens !== void 0 && contextWindow ? {
|
|
4991
|
-
usage_percent: Math.min(
|
|
5004
|
+
usage_percent: explicitUsagePercent !== void 0 ? Math.min(100, explicitUsagePercent) : Math.min(
|
|
4992
5005
|
100,
|
|
4993
5006
|
Math.round(explicitContextTokens / contextWindow * 100)
|
|
4994
5007
|
)
|
|
4995
5008
|
} : {}
|
|
4996
5009
|
};
|
|
4997
5010
|
}
|
|
5011
|
+
function estimateContextUsage(input) {
|
|
5012
|
+
const currentInput = input.currentInput.trim();
|
|
5013
|
+
const instructions = input.instructions?.trim() ?? "";
|
|
5014
|
+
const messageCount = input.conversationHistory.length + (currentInput ? 1 : 0) + (instructions ? 1 : 0);
|
|
5015
|
+
if (messageCount === 0) {
|
|
5016
|
+
return void 0;
|
|
5017
|
+
}
|
|
5018
|
+
const serializedRequest = JSON.stringify({
|
|
5019
|
+
instructions: instructions || void 0,
|
|
5020
|
+
conversation_history: input.conversationHistory,
|
|
5021
|
+
input: currentInput || void 0
|
|
5022
|
+
});
|
|
5023
|
+
const estimatedTokens = Math.ceil(serializedRequest.length / ESTIMATED_CONTEXT_CHARS_PER_TOKEN) + ESTIMATED_CONTEXT_BASE_OVERHEAD_TOKENS + messageCount * ESTIMATED_CONTEXT_PER_MESSAGE_OVERHEAD_TOKENS;
|
|
5024
|
+
const contextTokens = input.contextWindow ? Math.min(input.contextWindow, estimatedTokens) : estimatedTokens;
|
|
5025
|
+
return {
|
|
5026
|
+
input_tokens: 0,
|
|
5027
|
+
output_tokens: 0,
|
|
5028
|
+
total_tokens: 0,
|
|
5029
|
+
context_tokens: contextTokens,
|
|
5030
|
+
...input.contextWindow !== void 0 ? { context_window: input.contextWindow } : {},
|
|
5031
|
+
...input.contextWindow ? {
|
|
5032
|
+
usage_percent: Math.min(
|
|
5033
|
+
100,
|
|
5034
|
+
Math.round(contextTokens / input.contextWindow * 100)
|
|
5035
|
+
)
|
|
5036
|
+
} : {},
|
|
5037
|
+
context_source: "estimated"
|
|
5038
|
+
};
|
|
5039
|
+
}
|
|
4998
5040
|
function isAgentRun(run) {
|
|
4999
5041
|
return run.kind !== "command";
|
|
5000
5042
|
}
|
|
@@ -5015,6 +5057,14 @@ function readInteger(payload, key) {
|
|
|
5015
5057
|
function toRecord4(value) {
|
|
5016
5058
|
return typeof value === "object" && value !== null ? value : {};
|
|
5017
5059
|
}
|
|
5060
|
+
function firstRecord(...values) {
|
|
5061
|
+
for (const value of values) {
|
|
5062
|
+
if (typeof value === "object" && value !== null) {
|
|
5063
|
+
return value;
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
return {};
|
|
5067
|
+
}
|
|
5018
5068
|
|
|
5019
5069
|
// src/conversations/blob-store.ts
|
|
5020
5070
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
@@ -5730,7 +5780,7 @@ async function buildConversationRuntimeMetadata(paths, manifest, snapshot) {
|
|
|
5730
5780
|
};
|
|
5731
5781
|
const contextWindow = current.contextWindow ?? usage.context_window ?? usageRun?.context_window;
|
|
5732
5782
|
const contextTokens = usage.context_tokens;
|
|
5733
|
-
const contextSource = contextTokens === void 0 ? "unknown" : "explicit";
|
|
5783
|
+
const contextSource = contextTokens === void 0 ? "unknown" : usage.context_source ?? "explicit";
|
|
5734
5784
|
const provider = current.provider ?? usageRun?.provider;
|
|
5735
5785
|
const reasoningEffort = current.reasoningEffort;
|
|
5736
5786
|
return {
|
|
@@ -6324,12 +6374,12 @@ var ConversationCommandHandlers = class {
|
|
|
6324
6374
|
};
|
|
6325
6375
|
function formatContextUsageLines(runtime) {
|
|
6326
6376
|
const windowTokens = runtime.context.window_tokens ?? runtime.context.context_window;
|
|
6327
|
-
if (runtime.context.source === "explicit") {
|
|
6377
|
+
if (runtime.context.source === "explicit" || runtime.context.source === "estimated") {
|
|
6328
6378
|
const usedTokens = runtime.context.used_tokens ?? runtime.context.input_tokens;
|
|
6329
6379
|
const percent = runtime.context.usage_percent ?? (windowTokens && windowTokens > 0 ? Math.min(100, Math.round(usedTokens / windowTokens * 100)) : void 0);
|
|
6330
6380
|
return [
|
|
6331
6381
|
`\u4E0A\u4E0B\u6587\uFF1A${usedTokens}${windowTokens ? ` / ${windowTokens}` : ""}${percent === void 0 ? "" : `\uFF08${percent}%\uFF09`}`,
|
|
6332
|
-
"\u6765\u6E90\uFF1A\u6A21\u578B\u8FD4\u56DE"
|
|
6382
|
+
runtime.context.source === "estimated" ? "\u6765\u6E90\uFF1A\u672C\u5730\u4F30\u7B97" : "\u6765\u6E90\uFF1A\u6A21\u578B\u8FD4\u56DE"
|
|
6333
6383
|
];
|
|
6334
6384
|
}
|
|
6335
6385
|
return [
|
|
@@ -8518,307 +8568,664 @@ function isNodeError8(error, code) {
|
|
|
8518
8568
|
|
|
8519
8569
|
// src/conversations/hermes-session-sync.ts
|
|
8520
8570
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
8521
|
-
import { readdir as
|
|
8571
|
+
import { readdir as readdir6, readFile as readFile9, stat as stat8 } from "fs/promises";
|
|
8522
8572
|
import { createRequire as createRequire3 } from "module";
|
|
8523
8573
|
import os4 from "os";
|
|
8574
|
+
import path14 from "path";
|
|
8575
|
+
|
|
8576
|
+
// src/conversations/delivery-import.ts
|
|
8577
|
+
import { lstat, readFile as readFile8, readdir as readdir5, stat as stat7 } from "fs/promises";
|
|
8524
8578
|
import path13 from "path";
|
|
8525
|
-
var
|
|
8526
|
-
var
|
|
8527
|
-
var
|
|
8528
|
-
var
|
|
8529
|
-
var
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
"
|
|
8533
|
-
"
|
|
8534
|
-
"
|
|
8535
|
-
"
|
|
8536
|
-
"
|
|
8537
|
-
"
|
|
8538
|
-
"
|
|
8539
|
-
"
|
|
8540
|
-
"
|
|
8541
|
-
"
|
|
8542
|
-
"
|
|
8543
|
-
"
|
|
8544
|
-
"
|
|
8545
|
-
"
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8579
|
+
var MAX_IMPORTED_BLOB_BYTES = 100 * 1024 * 1024;
|
|
8580
|
+
var MAX_MEDIA_IMPORT_FAILURES = 20;
|
|
8581
|
+
var MAX_DELIVERY_FILES = 50;
|
|
8582
|
+
var DELIVERY_STAGING_SEGMENT = "delivery-staging";
|
|
8583
|
+
var SUPPORTED_DELIVERY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
8584
|
+
".png",
|
|
8585
|
+
".jpg",
|
|
8586
|
+
".jpeg",
|
|
8587
|
+
".gif",
|
|
8588
|
+
".webp",
|
|
8589
|
+
".heic",
|
|
8590
|
+
".pdf",
|
|
8591
|
+
".txt",
|
|
8592
|
+
".log",
|
|
8593
|
+
".md",
|
|
8594
|
+
".markdown",
|
|
8595
|
+
".json",
|
|
8596
|
+
".jsonl",
|
|
8597
|
+
".yaml",
|
|
8598
|
+
".yml",
|
|
8599
|
+
".toml",
|
|
8600
|
+
".ini",
|
|
8601
|
+
".xml",
|
|
8602
|
+
".html",
|
|
8603
|
+
".css",
|
|
8604
|
+
".js",
|
|
8605
|
+
".ts",
|
|
8606
|
+
".jsx",
|
|
8607
|
+
".tsx",
|
|
8608
|
+
".dart",
|
|
8609
|
+
".py",
|
|
8610
|
+
".java",
|
|
8611
|
+
".kt",
|
|
8612
|
+
".swift",
|
|
8613
|
+
".go",
|
|
8614
|
+
".rs",
|
|
8615
|
+
".rb",
|
|
8616
|
+
".php",
|
|
8617
|
+
".c",
|
|
8618
|
+
".cc",
|
|
8619
|
+
".cpp",
|
|
8620
|
+
".h",
|
|
8621
|
+
".hpp",
|
|
8622
|
+
".cs",
|
|
8623
|
+
".sql",
|
|
8624
|
+
".csv",
|
|
8625
|
+
".tsv",
|
|
8626
|
+
".doc",
|
|
8627
|
+
".docx",
|
|
8628
|
+
".xls",
|
|
8629
|
+
".xlsx",
|
|
8630
|
+
".ppt",
|
|
8631
|
+
".pptx",
|
|
8632
|
+
".zip",
|
|
8633
|
+
".rar",
|
|
8634
|
+
".7z",
|
|
8635
|
+
".tar",
|
|
8636
|
+
".gz",
|
|
8637
|
+
".mp4",
|
|
8638
|
+
".mov",
|
|
8639
|
+
".avi",
|
|
8640
|
+
".mkv",
|
|
8641
|
+
".webm",
|
|
8642
|
+
".ogg",
|
|
8643
|
+
".opus",
|
|
8644
|
+
".mp3",
|
|
8645
|
+
".wav",
|
|
8646
|
+
".m4a"
|
|
8647
|
+
]);
|
|
8648
|
+
function resolveDeliveryStagingTarget(paths, stagingDir) {
|
|
8649
|
+
const resolvedDir = path13.resolve(stagingDir);
|
|
8650
|
+
const relative = path13.relative(path13.resolve(paths.conversationsDir), resolvedDir);
|
|
8651
|
+
if (!relative || relative.startsWith("..") || path13.isAbsolute(relative)) {
|
|
8652
|
+
throw new LinkHttpError(
|
|
8653
|
+
400,
|
|
8654
|
+
"delivery_staging_invalid",
|
|
8655
|
+
"delivery staging directory must be inside Hermes Link conversations"
|
|
8656
|
+
);
|
|
8657
|
+
}
|
|
8658
|
+
const segments = relative.split(path13.sep);
|
|
8659
|
+
if (segments.length !== 3 || segments[1] !== DELIVERY_STAGING_SEGMENT || !segments[0] || !segments[2]) {
|
|
8660
|
+
throw new LinkHttpError(
|
|
8661
|
+
400,
|
|
8662
|
+
"delivery_staging_invalid",
|
|
8663
|
+
"delivery staging directory is invalid"
|
|
8664
|
+
);
|
|
8665
|
+
}
|
|
8666
|
+
return {
|
|
8667
|
+
conversationId: segments[0],
|
|
8668
|
+
runId: segments[2],
|
|
8669
|
+
stagingDir: resolvedDir
|
|
8563
8670
|
};
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
return [];
|
|
8574
|
-
});
|
|
8575
|
-
result.scanned_sessions += sessions.length;
|
|
8576
|
-
for (const session of sessions) {
|
|
8577
|
-
if (isDeletedSession(session)) {
|
|
8578
|
-
result.skipped_deleted += 1;
|
|
8579
|
-
continue;
|
|
8580
|
-
}
|
|
8581
|
-
if (isHiddenSession(session)) {
|
|
8582
|
-
result.skipped_hidden += 1;
|
|
8583
|
-
continue;
|
|
8584
|
-
}
|
|
8585
|
-
result.eligible_sessions += 1;
|
|
8586
|
-
candidates.push({ profileName, profileDir, dbPath, session });
|
|
8671
|
+
}
|
|
8672
|
+
async function collectStagedDeliveryReferences(stagingDir) {
|
|
8673
|
+
const directoryStat = await lstat(stagingDir).catch((error) => {
|
|
8674
|
+
if (isNodeError9(error, "ENOENT")) {
|
|
8675
|
+
throw new LinkHttpError(
|
|
8676
|
+
404,
|
|
8677
|
+
"delivery_staging_not_found",
|
|
8678
|
+
"delivery staging directory was not found"
|
|
8679
|
+
);
|
|
8587
8680
|
}
|
|
8588
|
-
|
|
8589
|
-
candidates.sort((left, right) => {
|
|
8590
|
-
const rightTime = readNumber2(right.session.last_active) ?? 0;
|
|
8591
|
-
const leftTime = readNumber2(left.session.last_active) ?? 0;
|
|
8592
|
-
return rightTime - leftTime;
|
|
8681
|
+
throw error;
|
|
8593
8682
|
});
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
paths,
|
|
8601
|
-
store,
|
|
8602
|
-
candidate,
|
|
8603
|
-
conversationIds: knownHermesSessions.conversationIdsBySessionId.get(
|
|
8604
|
-
candidate.session.id
|
|
8605
|
-
) ?? []
|
|
8606
|
-
}).catch((error) => {
|
|
8607
|
-
result.errors.push({
|
|
8608
|
-
profile: candidate.profileName,
|
|
8609
|
-
message: error instanceof Error ? error.message : String(error)
|
|
8610
|
-
});
|
|
8611
|
-
return false;
|
|
8612
|
-
});
|
|
8613
|
-
if (reprojected) {
|
|
8614
|
-
result.reprojected_count += 1;
|
|
8615
|
-
}
|
|
8616
|
-
continue;
|
|
8617
|
-
}
|
|
8618
|
-
const imported = await importHermesSession({
|
|
8619
|
-
paths,
|
|
8620
|
-
store,
|
|
8621
|
-
candidate,
|
|
8622
|
-
existingHermesSessionIds: knownHermesSessions.ids
|
|
8623
|
-
}).catch((error) => {
|
|
8624
|
-
result.errors.push({
|
|
8625
|
-
profile: candidate.profileName,
|
|
8626
|
-
message: error instanceof Error ? error.message : String(error)
|
|
8627
|
-
});
|
|
8628
|
-
return false;
|
|
8629
|
-
});
|
|
8630
|
-
if (imported) {
|
|
8631
|
-
result.imported_count += 1;
|
|
8632
|
-
}
|
|
8633
|
-
}
|
|
8634
|
-
if (result.imported_count > 0 || result.reprojected_count > 0 || result.errors.length > 0) {
|
|
8635
|
-
void logger.info("hermes_session_sync_completed", { ...result });
|
|
8636
|
-
} else {
|
|
8637
|
-
void logger.debug("hermes_session_sync_completed", { ...result });
|
|
8683
|
+
if (!directoryStat.isDirectory()) {
|
|
8684
|
+
throw new LinkHttpError(
|
|
8685
|
+
400,
|
|
8686
|
+
"delivery_staging_not_directory",
|
|
8687
|
+
"delivery staging path is not a directory"
|
|
8688
|
+
);
|
|
8638
8689
|
}
|
|
8639
|
-
|
|
8690
|
+
const entries = await readdir5(stagingDir, { withFileTypes: true });
|
|
8691
|
+
return entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).filter((entry) => isSupportedDeliveryFilename(entry.name)).sort(
|
|
8692
|
+
(left, right) => left.name.localeCompare(right.name, "en", { numeric: true })
|
|
8693
|
+
).slice(0, MAX_DELIVERY_FILES).map((entry) => {
|
|
8694
|
+
const sourcePath = path13.join(stagingDir, entry.name);
|
|
8695
|
+
const mime = inferMimeType(sourcePath);
|
|
8696
|
+
return {
|
|
8697
|
+
path: sourcePath,
|
|
8698
|
+
kind: mediaKindForMime(mime),
|
|
8699
|
+
mime
|
|
8700
|
+
};
|
|
8701
|
+
});
|
|
8640
8702
|
}
|
|
8641
|
-
async function
|
|
8642
|
-
const
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8703
|
+
async function importMediaReferencesForMessage(deps, input) {
|
|
8704
|
+
const references = input.references.slice(0, input.maxReferences ?? MAX_DELIVERY_FILES);
|
|
8705
|
+
if (references.length === 0) {
|
|
8706
|
+
return emptyImportResult(input);
|
|
8707
|
+
}
|
|
8708
|
+
const snapshot = await deps.readSnapshot(input.conversationId);
|
|
8709
|
+
const assistant = snapshot.messages.find(
|
|
8710
|
+
(message) => message.id === input.messageId
|
|
8646
8711
|
);
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8650
|
-
const createdAt = isoFromHermesTime(candidate.session.started_at) ?? now;
|
|
8651
|
-
const updatedAt = isoFromHermesTime(candidate.session.last_active) ?? isoFromHermesTime(messages.at(-1)?.timestamp) ?? createdAt;
|
|
8652
|
-
const conversationId = createConversationId();
|
|
8653
|
-
const snapshot = {
|
|
8654
|
-
schema_version: 1,
|
|
8655
|
-
messages: toLinkMessages({
|
|
8656
|
-
conversationId,
|
|
8657
|
-
profileName: profile.profileName,
|
|
8658
|
-
profileUid: profile.profileUid,
|
|
8659
|
-
profileDisplayName: profile.profileDisplayName,
|
|
8660
|
-
sessionId,
|
|
8661
|
-
messages
|
|
8662
|
-
}),
|
|
8663
|
-
runs: []
|
|
8664
|
-
};
|
|
8665
|
-
const title = readString8(candidate.session, "title") ?? firstUserText(snapshot);
|
|
8666
|
-
const manifest = {
|
|
8667
|
-
id: conversationId,
|
|
8668
|
-
schema_version: 1,
|
|
8669
|
-
kind: "direct",
|
|
8670
|
-
title: normalizeTitle(title),
|
|
8671
|
-
title_source: title ? "hermes" : "default",
|
|
8672
|
-
status: "active",
|
|
8673
|
-
hermes_session_id: sessionId,
|
|
8674
|
-
hermes_session_ids: [sessionId],
|
|
8675
|
-
profile_uid: profile.profileUid,
|
|
8676
|
-
profile_name_snapshot: profile.profileName,
|
|
8677
|
-
profile: profile.profileName,
|
|
8678
|
-
created_at: createdAt,
|
|
8679
|
-
updated_at: updatedAt,
|
|
8680
|
-
last_event_seq: 0
|
|
8681
|
-
};
|
|
8682
|
-
await store.createConversation(manifest, snapshot);
|
|
8683
|
-
await store.appendEvent(conversationId, {
|
|
8684
|
-
type: "conversation.created",
|
|
8685
|
-
payload: {
|
|
8686
|
-
imported_from: "hermes",
|
|
8687
|
-
hermes_session_id: sessionId,
|
|
8688
|
-
profile: {
|
|
8689
|
-
uid: profile.profileUid,
|
|
8690
|
-
name: profile.profileName,
|
|
8691
|
-
display_name: profile.profileDisplayName,
|
|
8692
|
-
avatar_url: profile.profileAvatarUrl
|
|
8693
|
-
}
|
|
8694
|
-
}
|
|
8695
|
-
});
|
|
8696
|
-
for (const message of snapshot.messages) {
|
|
8697
|
-
await store.appendEvent(conversationId, {
|
|
8698
|
-
type: "message.created",
|
|
8699
|
-
message_id: message.id,
|
|
8700
|
-
payload: { message, imported_from: "hermes" },
|
|
8701
|
-
raw: message.raw
|
|
8702
|
-
});
|
|
8712
|
+
if (!assistant) {
|
|
8713
|
+
return emptyImportResult(input);
|
|
8703
8714
|
}
|
|
8704
|
-
const
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
await store.writeManifest({
|
|
8709
|
-
...await store.readManifest(conversationId),
|
|
8710
|
-
stats
|
|
8711
|
-
});
|
|
8712
|
-
await upsertConversationStats(
|
|
8713
|
-
paths,
|
|
8714
|
-
toStatsIndexRecord(await store.readManifest(conversationId), stats)
|
|
8715
|
+
const importedSourceKeys = readImportedMediaSourceKeys(assistant);
|
|
8716
|
+
const failedSourceKeys = readFailedMediaSourceKeys(assistant);
|
|
8717
|
+
const failureRecordsByKey = new Map(
|
|
8718
|
+
readMediaImportFailures(assistant).map((failure) => [failure.key, failure])
|
|
8715
8719
|
);
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
const snapshot = await input.store.readSnapshot(conversationId).catch(() => null);
|
|
8727
|
-
if (!snapshot) {
|
|
8728
|
-
continue;
|
|
8729
|
-
}
|
|
8730
|
-
const prefix = collectImportedHermesPrefix(snapshot);
|
|
8731
|
-
if (!prefix?.needsUpgrade || prefix.messages.length === 0) {
|
|
8732
|
-
continue;
|
|
8733
|
-
}
|
|
8734
|
-
const profile = await resolveConversationProfileTarget(
|
|
8735
|
-
input.paths,
|
|
8736
|
-
manifest.profile_name_snapshot ?? manifest.profile ?? input.candidate.profileName
|
|
8737
|
-
);
|
|
8738
|
-
const nextSnapshot = {
|
|
8739
|
-
...snapshot,
|
|
8740
|
-
messages: [
|
|
8741
|
-
...toLinkMessages({
|
|
8742
|
-
conversationId,
|
|
8743
|
-
profileName: profile.profileName,
|
|
8744
|
-
profileUid: profile.profileUid,
|
|
8745
|
-
profileDisplayName: profile.profileDisplayName,
|
|
8746
|
-
sessionId: input.candidate.session.id,
|
|
8747
|
-
messages: prefix.messages
|
|
8748
|
-
}),
|
|
8749
|
-
...snapshot.messages.slice(prefix.endIndex)
|
|
8750
|
-
]
|
|
8751
|
-
};
|
|
8752
|
-
const stats = buildConversationStats(manifest, nextSnapshot);
|
|
8753
|
-
await input.store.writeSnapshot(conversationId, nextSnapshot);
|
|
8754
|
-
await input.store.writeManifest({ ...manifest, stats });
|
|
8755
|
-
await upsertConversationStats(
|
|
8756
|
-
input.paths,
|
|
8757
|
-
toStatsIndexRecord({ ...manifest, stats }, stats)
|
|
8758
|
-
);
|
|
8759
|
-
changed = true;
|
|
8760
|
-
}
|
|
8761
|
-
return changed;
|
|
8762
|
-
}
|
|
8763
|
-
function collectImportedHermesPrefix(snapshot) {
|
|
8764
|
-
const rows = [];
|
|
8765
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8766
|
-
let needsProjectionVersion = false;
|
|
8767
|
-
let hasToolMetadata = false;
|
|
8768
|
-
let endIndex = 0;
|
|
8769
|
-
for (; endIndex < snapshot.messages.length; endIndex += 1) {
|
|
8770
|
-
const message = snapshot.messages[endIndex];
|
|
8771
|
-
if (!isHermesImportedMessage(message)) {
|
|
8772
|
-
break;
|
|
8773
|
-
}
|
|
8774
|
-
if (message.hermes?.import_projection !== HERMES_IMPORT_PROJECTION_VERSION) {
|
|
8775
|
-
needsProjectionVersion = true;
|
|
8776
|
-
}
|
|
8777
|
-
if (message.role === "tool") {
|
|
8778
|
-
hasToolMetadata = true;
|
|
8779
|
-
}
|
|
8780
|
-
for (const row of readHermesRawMessageRows(message.raw)) {
|
|
8781
|
-
appendHermesRowOnce(rows, seen, row);
|
|
8782
|
-
if (hasHermesToolMetadata(row)) {
|
|
8783
|
-
hasToolMetadata = true;
|
|
8720
|
+
const importedParts = [];
|
|
8721
|
+
const newFailures = [];
|
|
8722
|
+
let skippedCount = 0;
|
|
8723
|
+
for (const reference of references) {
|
|
8724
|
+
let sourceKey;
|
|
8725
|
+
try {
|
|
8726
|
+
sourceKey = mediaSourceKey(reference.path);
|
|
8727
|
+
if (importedSourceKeys.has(sourceKey) || failedSourceKeys.has(sourceKey)) {
|
|
8728
|
+
skippedCount += 1;
|
|
8729
|
+
continue;
|
|
8784
8730
|
}
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
8731
|
+
const blob = await writeBlobFromFile(deps, input.conversationId, reference);
|
|
8732
|
+
const part = {
|
|
8733
|
+
type: reference.kind ?? mediaKindForMime(blob.mime),
|
|
8734
|
+
blob: blob.id,
|
|
8735
|
+
mime: blob.mime,
|
|
8736
|
+
size: blob.size,
|
|
8737
|
+
filename: blob.filename,
|
|
8738
|
+
url: `/api/v1/conversations/${encodeURIComponent(input.conversationId)}/blobs/${encodeURIComponent(blob.id)}`
|
|
8739
|
+
};
|
|
8740
|
+
assistant.parts.push(part);
|
|
8741
|
+
assistant.attachments.push({
|
|
8742
|
+
blob_id: blob.id,
|
|
8743
|
+
mime: blob.mime,
|
|
8744
|
+
size: blob.size,
|
|
8745
|
+
filename: blob.filename,
|
|
8746
|
+
source: "hermes_output"
|
|
8747
|
+
});
|
|
8748
|
+
importedSourceKeys.add(sourceKey);
|
|
8749
|
+
importedParts.push(part);
|
|
8750
|
+
} catch (error) {
|
|
8751
|
+
if (sourceKey && !failedSourceKeys.has(sourceKey)) {
|
|
8752
|
+
const failure = describeMediaImportFailure(reference, sourceKey, error);
|
|
8753
|
+
failedSourceKeys.add(sourceKey);
|
|
8754
|
+
failureRecordsByKey.set(sourceKey, failure);
|
|
8755
|
+
newFailures.push(failure);
|
|
8792
8756
|
}
|
|
8757
|
+
void deps.logger.warn("conversation_media_import_failed", {
|
|
8758
|
+
conversation_id: input.conversationId,
|
|
8759
|
+
run_id: input.runId,
|
|
8760
|
+
message_id: input.messageId,
|
|
8761
|
+
error: error instanceof Error ? error.message : String(error)
|
|
8762
|
+
});
|
|
8793
8763
|
}
|
|
8794
8764
|
}
|
|
8795
|
-
if (
|
|
8796
|
-
return
|
|
8765
|
+
if (importedParts.length === 0 && newFailures.length === 0) {
|
|
8766
|
+
return {
|
|
8767
|
+
...emptyImportResult(input),
|
|
8768
|
+
discovered_count: references.length,
|
|
8769
|
+
skipped_count: skippedCount
|
|
8770
|
+
};
|
|
8771
|
+
}
|
|
8772
|
+
assistant.hermes = {
|
|
8773
|
+
...toRecord7(assistant.hermes),
|
|
8774
|
+
imported_media_source_keys: [...importedSourceKeys],
|
|
8775
|
+
media_import_failed_source_keys: [...failedSourceKeys],
|
|
8776
|
+
media_import_failures: [...failureRecordsByKey.values()].slice(
|
|
8777
|
+
-MAX_MEDIA_IMPORT_FAILURES
|
|
8778
|
+
)
|
|
8779
|
+
};
|
|
8780
|
+
assistant.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
8781
|
+
await deps.writeSnapshot(input.conversationId, snapshot);
|
|
8782
|
+
let lastEventSeq;
|
|
8783
|
+
if (importedParts.length > 0) {
|
|
8784
|
+
const event = await deps.appendEvent(input.conversationId, {
|
|
8785
|
+
type: "message.parts.created",
|
|
8786
|
+
message_id: input.messageId,
|
|
8787
|
+
run_id: input.runId,
|
|
8788
|
+
payload: { parts: importedParts }
|
|
8789
|
+
});
|
|
8790
|
+
lastEventSeq = event.seq;
|
|
8797
8791
|
}
|
|
8798
8792
|
return {
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8793
|
+
conversation_id: input.conversationId,
|
|
8794
|
+
run_id: input.runId,
|
|
8795
|
+
message_id: input.messageId,
|
|
8796
|
+
discovered_count: references.length,
|
|
8797
|
+
imported_count: importedParts.length,
|
|
8798
|
+
skipped_count: skippedCount,
|
|
8799
|
+
failed_count: newFailures.length,
|
|
8800
|
+
parts: importedParts,
|
|
8801
|
+
...lastEventSeq ? { last_event_seq: lastEventSeq } : {}
|
|
8802
8802
|
};
|
|
8803
8803
|
}
|
|
8804
|
-
function
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
if (event.raw?.format !== "hermes-message" && event.raw?.format !== "hermes-message-group") {
|
|
8804
|
+
function readMediaImportFailures(message) {
|
|
8805
|
+
const hermes = toRecord7(message.hermes);
|
|
8806
|
+
const failures = hermes.media_import_failures;
|
|
8807
|
+
if (!Array.isArray(failures)) {
|
|
8809
8808
|
return [];
|
|
8810
8809
|
}
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
8810
|
+
return failures.flatMap((item) => {
|
|
8811
|
+
const record = toRecord7(item);
|
|
8812
|
+
const key = readString8(record, "key");
|
|
8813
|
+
const filename = readString8(record, "filename");
|
|
8814
|
+
const reason = readString8(record, "reason");
|
|
8815
|
+
if (!key || !filename || !reason) {
|
|
8816
|
+
return [];
|
|
8817
|
+
}
|
|
8818
|
+
return [
|
|
8819
|
+
{
|
|
8820
|
+
key,
|
|
8821
|
+
filename,
|
|
8822
|
+
reason,
|
|
8823
|
+
...readString8(record, "code") ? { code: readString8(record, "code") } : {}
|
|
8824
|
+
}
|
|
8825
|
+
];
|
|
8826
|
+
});
|
|
8827
|
+
}
|
|
8828
|
+
function readFailedMediaSourceKeys(message) {
|
|
8829
|
+
const hermes = toRecord7(message.hermes);
|
|
8830
|
+
const keys = hermes.media_import_failed_source_keys;
|
|
8831
|
+
if (!Array.isArray(keys)) {
|
|
8832
|
+
return /* @__PURE__ */ new Set();
|
|
8815
8833
|
}
|
|
8816
|
-
return
|
|
8817
|
-
(
|
|
8834
|
+
return new Set(
|
|
8835
|
+
keys.filter(
|
|
8836
|
+
(key) => typeof key === "string" && key.length > 0
|
|
8837
|
+
)
|
|
8818
8838
|
);
|
|
8819
8839
|
}
|
|
8820
|
-
function
|
|
8821
|
-
|
|
8840
|
+
function emptyImportResult(input) {
|
|
8841
|
+
return {
|
|
8842
|
+
conversation_id: input.conversationId,
|
|
8843
|
+
run_id: input.runId,
|
|
8844
|
+
message_id: input.messageId,
|
|
8845
|
+
discovered_count: 0,
|
|
8846
|
+
imported_count: 0,
|
|
8847
|
+
skipped_count: 0,
|
|
8848
|
+
failed_count: 0,
|
|
8849
|
+
parts: []
|
|
8850
|
+
};
|
|
8851
|
+
}
|
|
8852
|
+
async function writeBlobFromFile(deps, conversationId, source) {
|
|
8853
|
+
const sourcePath = resolveMediaSourcePath(source.path);
|
|
8854
|
+
const fileStat = await stat7(sourcePath).catch((error) => {
|
|
8855
|
+
if (isNodeError9(error, "ENOENT")) {
|
|
8856
|
+
throw new LinkHttpError(
|
|
8857
|
+
404,
|
|
8858
|
+
"media_source_not_found",
|
|
8859
|
+
"Hermes output file was not found"
|
|
8860
|
+
);
|
|
8861
|
+
}
|
|
8862
|
+
throw error;
|
|
8863
|
+
});
|
|
8864
|
+
if (!fileStat.isFile()) {
|
|
8865
|
+
throw new LinkHttpError(
|
|
8866
|
+
400,
|
|
8867
|
+
"media_source_not_file",
|
|
8868
|
+
"Hermes output media source is not a file"
|
|
8869
|
+
);
|
|
8870
|
+
}
|
|
8871
|
+
if (fileStat.size > MAX_IMPORTED_BLOB_BYTES) {
|
|
8872
|
+
throw new LinkHttpError(
|
|
8873
|
+
413,
|
|
8874
|
+
"media_source_too_large",
|
|
8875
|
+
"Hermes output media source is too large"
|
|
8876
|
+
);
|
|
8877
|
+
}
|
|
8878
|
+
return deps.writeBlob(conversationId, {
|
|
8879
|
+
bytes: await readFile8(sourcePath),
|
|
8880
|
+
filename: path13.basename(sourcePath),
|
|
8881
|
+
mime: source.mime ?? inferMimeType(sourcePath)
|
|
8882
|
+
});
|
|
8883
|
+
}
|
|
8884
|
+
function describeMediaImportFailure(reference, sourceKey, error) {
|
|
8885
|
+
return {
|
|
8886
|
+
key: sourceKey,
|
|
8887
|
+
filename: sanitizeFilename(reference.path, "attachment"),
|
|
8888
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
8889
|
+
...isNodeError9(error) && error.code ? { code: error.code } : {}
|
|
8890
|
+
};
|
|
8891
|
+
}
|
|
8892
|
+
function isSupportedDeliveryFilename(filename) {
|
|
8893
|
+
return SUPPORTED_DELIVERY_EXTENSIONS.has(path13.extname(filename).toLowerCase());
|
|
8894
|
+
}
|
|
8895
|
+
function readString8(payload, key) {
|
|
8896
|
+
const value = payload[key];
|
|
8897
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
8898
|
+
}
|
|
8899
|
+
function toRecord7(value) {
|
|
8900
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
8901
|
+
}
|
|
8902
|
+
function isNodeError9(error, code) {
|
|
8903
|
+
return typeof error === "object" && error !== null && "code" in error && (code === void 0 || error.code === code);
|
|
8904
|
+
}
|
|
8905
|
+
|
|
8906
|
+
// src/conversations/hermes-session-sync.ts
|
|
8907
|
+
var nodeRequire3 = createRequire3(import.meta.url);
|
|
8908
|
+
var PROFILE_NAME_PATTERN3 = /^[a-zA-Z0-9._-]{1,64}$/u;
|
|
8909
|
+
var DEFAULT_PROFILE_NAME = "default";
|
|
8910
|
+
var MAX_IMPORTABLE_SESSIONS = 100;
|
|
8911
|
+
var HIDDEN_SESSION_SOURCES = /* @__PURE__ */ new Set(["tool"]);
|
|
8912
|
+
var HERMES_IMPORT_PROJECTION_VERSION = "turn_blocks_v3";
|
|
8913
|
+
var IMPORTED_MEDIA_PLACEHOLDER_RUN_ID = "imported_from_hermes";
|
|
8914
|
+
var MAX_IMPORTED_HERMES_MEDIA_BYTES = 100 * 1024 * 1024;
|
|
8915
|
+
var MESSAGE_COLUMNS = [
|
|
8916
|
+
"id",
|
|
8917
|
+
"session_id",
|
|
8918
|
+
"role",
|
|
8919
|
+
"content",
|
|
8920
|
+
"tool_call_id",
|
|
8921
|
+
"tool_calls",
|
|
8922
|
+
"tool_name",
|
|
8923
|
+
"timestamp",
|
|
8924
|
+
"token_count",
|
|
8925
|
+
"finish_reason",
|
|
8926
|
+
"reasoning",
|
|
8927
|
+
"reasoning_content",
|
|
8928
|
+
"reasoning_details",
|
|
8929
|
+
"codex_reasoning_items"
|
|
8930
|
+
];
|
|
8931
|
+
async function syncHermesSessionsIntoConversations(paths, logger, options = {}) {
|
|
8932
|
+
const maxImports = options.maxImports ?? MAX_IMPORTABLE_SESSIONS;
|
|
8933
|
+
const store = new ConversationStore(paths);
|
|
8934
|
+
const knownHermesSessions = await readKnownHermesSessions(store);
|
|
8935
|
+
const profileNames = await discoverHermesProfileNames();
|
|
8936
|
+
const result = {
|
|
8937
|
+
scanned_profiles: profileNames.length,
|
|
8938
|
+
scanned_sessions: 0,
|
|
8939
|
+
eligible_sessions: 0,
|
|
8940
|
+
imported_count: 0,
|
|
8941
|
+
reprojected_count: 0,
|
|
8942
|
+
skipped_existing: 0,
|
|
8943
|
+
skipped_hidden: 0,
|
|
8944
|
+
skipped_deleted: 0,
|
|
8945
|
+
skipped_over_limit: 0,
|
|
8946
|
+
errors: []
|
|
8947
|
+
};
|
|
8948
|
+
const candidates = [];
|
|
8949
|
+
for (const profileName of profileNames) {
|
|
8950
|
+
const profileDir = resolveHermesProfileDir(profileName);
|
|
8951
|
+
const dbPath = path14.join(profileDir, "state.db");
|
|
8952
|
+
const sessions = await listProfileSessions(dbPath).catch((error) => {
|
|
8953
|
+
result.errors.push({
|
|
8954
|
+
profile: profileName,
|
|
8955
|
+
message: error instanceof Error ? error.message : String(error)
|
|
8956
|
+
});
|
|
8957
|
+
return [];
|
|
8958
|
+
});
|
|
8959
|
+
result.scanned_sessions += sessions.length;
|
|
8960
|
+
for (const session of sessions) {
|
|
8961
|
+
if (isDeletedSession(session)) {
|
|
8962
|
+
result.skipped_deleted += 1;
|
|
8963
|
+
continue;
|
|
8964
|
+
}
|
|
8965
|
+
if (isHiddenSession(session)) {
|
|
8966
|
+
result.skipped_hidden += 1;
|
|
8967
|
+
continue;
|
|
8968
|
+
}
|
|
8969
|
+
result.eligible_sessions += 1;
|
|
8970
|
+
candidates.push({ profileName, profileDir, dbPath, session });
|
|
8971
|
+
}
|
|
8972
|
+
}
|
|
8973
|
+
candidates.sort((left, right) => {
|
|
8974
|
+
const rightTime = readNumber2(right.session.last_active) ?? 0;
|
|
8975
|
+
const leftTime = readNumber2(left.session.last_active) ?? 0;
|
|
8976
|
+
return rightTime - leftTime;
|
|
8977
|
+
});
|
|
8978
|
+
const importableCandidates = candidates.slice(0, maxImports);
|
|
8979
|
+
result.skipped_over_limit = Math.max(0, candidates.length - maxImports);
|
|
8980
|
+
for (const candidate of importableCandidates) {
|
|
8981
|
+
if (knownHermesSessions.ids.has(candidate.session.id)) {
|
|
8982
|
+
result.skipped_existing += 1;
|
|
8983
|
+
const reprojected = await reprojectExistingHermesConversation({
|
|
8984
|
+
paths,
|
|
8985
|
+
store,
|
|
8986
|
+
logger,
|
|
8987
|
+
candidate,
|
|
8988
|
+
conversationIds: knownHermesSessions.conversationIdsBySessionId.get(
|
|
8989
|
+
candidate.session.id
|
|
8990
|
+
) ?? []
|
|
8991
|
+
}).catch((error) => {
|
|
8992
|
+
result.errors.push({
|
|
8993
|
+
profile: candidate.profileName,
|
|
8994
|
+
message: error instanceof Error ? error.message : String(error)
|
|
8995
|
+
});
|
|
8996
|
+
return false;
|
|
8997
|
+
});
|
|
8998
|
+
if (reprojected) {
|
|
8999
|
+
result.reprojected_count += 1;
|
|
9000
|
+
}
|
|
9001
|
+
continue;
|
|
9002
|
+
}
|
|
9003
|
+
const imported = await importHermesSession({
|
|
9004
|
+
paths,
|
|
9005
|
+
store,
|
|
9006
|
+
logger,
|
|
9007
|
+
candidate,
|
|
9008
|
+
existingHermesSessionIds: knownHermesSessions.ids
|
|
9009
|
+
}).catch((error) => {
|
|
9010
|
+
result.errors.push({
|
|
9011
|
+
profile: candidate.profileName,
|
|
9012
|
+
message: error instanceof Error ? error.message : String(error)
|
|
9013
|
+
});
|
|
9014
|
+
return false;
|
|
9015
|
+
});
|
|
9016
|
+
if (imported) {
|
|
9017
|
+
result.imported_count += 1;
|
|
9018
|
+
}
|
|
9019
|
+
}
|
|
9020
|
+
if (result.imported_count > 0 || result.reprojected_count > 0 || result.errors.length > 0) {
|
|
9021
|
+
void logger.info("hermes_session_sync_completed", { ...result });
|
|
9022
|
+
} else {
|
|
9023
|
+
void logger.debug("hermes_session_sync_completed", { ...result });
|
|
9024
|
+
}
|
|
9025
|
+
return result;
|
|
9026
|
+
}
|
|
9027
|
+
async function importHermesSession(input) {
|
|
9028
|
+
const { paths, store, logger, candidate, existingHermesSessionIds } = input;
|
|
9029
|
+
const profile = await resolveConversationProfileTarget(
|
|
9030
|
+
paths,
|
|
9031
|
+
candidate.profileName
|
|
9032
|
+
);
|
|
9033
|
+
const sessionId = candidate.session.id;
|
|
9034
|
+
const messages = await readHermesSessionMessages(candidate);
|
|
9035
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9036
|
+
const createdAt = isoFromHermesTime(candidate.session.started_at) ?? now;
|
|
9037
|
+
const updatedAt = isoFromHermesTime(candidate.session.last_active) ?? isoFromHermesTime(messages.at(-1)?.timestamp) ?? createdAt;
|
|
9038
|
+
const conversationId = createConversationId();
|
|
9039
|
+
const snapshot = {
|
|
9040
|
+
schema_version: 1,
|
|
9041
|
+
messages: toLinkMessages({
|
|
9042
|
+
conversationId,
|
|
9043
|
+
profileName: profile.profileName,
|
|
9044
|
+
profileUid: profile.profileUid,
|
|
9045
|
+
profileDisplayName: profile.profileDisplayName,
|
|
9046
|
+
sessionId,
|
|
9047
|
+
messages
|
|
9048
|
+
}),
|
|
9049
|
+
runs: []
|
|
9050
|
+
};
|
|
9051
|
+
const title = readString9(candidate.session, "title") ?? firstUserText(snapshot);
|
|
9052
|
+
const manifest = {
|
|
9053
|
+
id: conversationId,
|
|
9054
|
+
schema_version: 1,
|
|
9055
|
+
kind: "direct",
|
|
9056
|
+
title: normalizeTitle(title),
|
|
9057
|
+
title_source: title ? "hermes" : "default",
|
|
9058
|
+
status: "active",
|
|
9059
|
+
hermes_session_id: sessionId,
|
|
9060
|
+
hermes_session_ids: [sessionId],
|
|
9061
|
+
profile_uid: profile.profileUid,
|
|
9062
|
+
profile_name_snapshot: profile.profileName,
|
|
9063
|
+
profile: profile.profileName,
|
|
9064
|
+
created_at: createdAt,
|
|
9065
|
+
updated_at: updatedAt,
|
|
9066
|
+
last_event_seq: 0
|
|
9067
|
+
};
|
|
9068
|
+
await store.createConversation(manifest, snapshot);
|
|
9069
|
+
await hydrateImportedConversationMedia({
|
|
9070
|
+
paths,
|
|
9071
|
+
store,
|
|
9072
|
+
logger,
|
|
9073
|
+
conversationId
|
|
9074
|
+
});
|
|
9075
|
+
const hydratedSnapshot = await store.readSnapshot(conversationId);
|
|
9076
|
+
await store.appendEvent(conversationId, {
|
|
9077
|
+
type: "conversation.created",
|
|
9078
|
+
payload: {
|
|
9079
|
+
imported_from: "hermes",
|
|
9080
|
+
hermes_session_id: sessionId,
|
|
9081
|
+
profile: {
|
|
9082
|
+
uid: profile.profileUid,
|
|
9083
|
+
name: profile.profileName,
|
|
9084
|
+
display_name: profile.profileDisplayName,
|
|
9085
|
+
avatar_url: profile.profileAvatarUrl
|
|
9086
|
+
}
|
|
9087
|
+
}
|
|
9088
|
+
});
|
|
9089
|
+
for (const message of hydratedSnapshot.messages) {
|
|
9090
|
+
await store.appendEvent(conversationId, {
|
|
9091
|
+
type: "message.created",
|
|
9092
|
+
message_id: message.id,
|
|
9093
|
+
payload: { message, imported_from: "hermes" },
|
|
9094
|
+
raw: message.raw
|
|
9095
|
+
});
|
|
9096
|
+
}
|
|
9097
|
+
const stats = buildConversationStats(
|
|
9098
|
+
await store.readManifest(conversationId),
|
|
9099
|
+
hydratedSnapshot
|
|
9100
|
+
);
|
|
9101
|
+
await store.writeManifest({
|
|
9102
|
+
...await store.readManifest(conversationId),
|
|
9103
|
+
stats
|
|
9104
|
+
});
|
|
9105
|
+
await upsertConversationStats(
|
|
9106
|
+
paths,
|
|
9107
|
+
toStatsIndexRecord(await store.readManifest(conversationId), stats)
|
|
9108
|
+
);
|
|
9109
|
+
existingHermesSessionIds.add(sessionId);
|
|
9110
|
+
return true;
|
|
9111
|
+
}
|
|
9112
|
+
async function reprojectExistingHermesConversation(input) {
|
|
9113
|
+
let changed = false;
|
|
9114
|
+
for (const conversationId of input.conversationIds) {
|
|
9115
|
+
const manifest = await input.store.readManifest(conversationId).catch(() => null);
|
|
9116
|
+
if (!manifest || manifest.status !== "active") {
|
|
9117
|
+
continue;
|
|
9118
|
+
}
|
|
9119
|
+
const snapshot = await input.store.readSnapshot(conversationId).catch(() => null);
|
|
9120
|
+
if (!snapshot) {
|
|
9121
|
+
continue;
|
|
9122
|
+
}
|
|
9123
|
+
const prefix = collectImportedHermesPrefix(snapshot);
|
|
9124
|
+
if (!prefix?.needsUpgrade || prefix.messages.length === 0) {
|
|
9125
|
+
continue;
|
|
9126
|
+
}
|
|
9127
|
+
const profile = await resolveConversationProfileTarget(
|
|
9128
|
+
input.paths,
|
|
9129
|
+
manifest.profile_name_snapshot ?? manifest.profile ?? input.candidate.profileName
|
|
9130
|
+
);
|
|
9131
|
+
const nextSnapshot = {
|
|
9132
|
+
...snapshot,
|
|
9133
|
+
messages: [
|
|
9134
|
+
...toLinkMessages({
|
|
9135
|
+
conversationId,
|
|
9136
|
+
profileName: profile.profileName,
|
|
9137
|
+
profileUid: profile.profileUid,
|
|
9138
|
+
profileDisplayName: profile.profileDisplayName,
|
|
9139
|
+
sessionId: input.candidate.session.id,
|
|
9140
|
+
messages: prefix.messages
|
|
9141
|
+
}),
|
|
9142
|
+
...snapshot.messages.slice(prefix.endIndex)
|
|
9143
|
+
]
|
|
9144
|
+
};
|
|
9145
|
+
await input.store.writeSnapshot(conversationId, nextSnapshot);
|
|
9146
|
+
await hydrateImportedConversationMedia({
|
|
9147
|
+
paths: input.paths,
|
|
9148
|
+
store: input.store,
|
|
9149
|
+
logger: input.logger,
|
|
9150
|
+
conversationId
|
|
9151
|
+
});
|
|
9152
|
+
const hydratedSnapshot = await input.store.readSnapshot(conversationId);
|
|
9153
|
+
const stats = buildConversationStats(manifest, hydratedSnapshot);
|
|
9154
|
+
await input.store.writeManifest({ ...manifest, stats });
|
|
9155
|
+
await upsertConversationStats(
|
|
9156
|
+
input.paths,
|
|
9157
|
+
toStatsIndexRecord({ ...manifest, stats }, stats)
|
|
9158
|
+
);
|
|
9159
|
+
changed = true;
|
|
9160
|
+
}
|
|
9161
|
+
return changed;
|
|
9162
|
+
}
|
|
9163
|
+
function collectImportedHermesPrefix(snapshot) {
|
|
9164
|
+
const rows = [];
|
|
9165
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9166
|
+
let needsProjectionVersion = false;
|
|
9167
|
+
let hasToolMetadata = false;
|
|
9168
|
+
let hasMediaDeliveryMarkup = false;
|
|
9169
|
+
let endIndex = 0;
|
|
9170
|
+
for (; endIndex < snapshot.messages.length; endIndex += 1) {
|
|
9171
|
+
const message = snapshot.messages[endIndex];
|
|
9172
|
+
if (!isHermesImportedMessage(message)) {
|
|
9173
|
+
break;
|
|
9174
|
+
}
|
|
9175
|
+
if (message.hermes?.import_projection !== HERMES_IMPORT_PROJECTION_VERSION) {
|
|
9176
|
+
needsProjectionVersion = true;
|
|
9177
|
+
}
|
|
9178
|
+
if (message.role === "tool") {
|
|
9179
|
+
hasToolMetadata = true;
|
|
9180
|
+
}
|
|
9181
|
+
for (const row of readHermesRawMessageRows(message.raw)) {
|
|
9182
|
+
appendHermesRowOnce(rows, seen, row);
|
|
9183
|
+
if (hasHermesToolMetadata(row)) {
|
|
9184
|
+
hasToolMetadata = true;
|
|
9185
|
+
}
|
|
9186
|
+
if (collectMediaTags(normalizeContent(row.content)).length > 0) {
|
|
9187
|
+
hasMediaDeliveryMarkup = true;
|
|
9188
|
+
}
|
|
9189
|
+
}
|
|
9190
|
+
for (const event of message.agent_events ?? []) {
|
|
9191
|
+
for (const row of readHermesRowsFromAgentEvent(event)) {
|
|
9192
|
+
appendHermesRowOnce(rows, seen, row);
|
|
9193
|
+
if (hasHermesToolMetadata(row)) {
|
|
9194
|
+
hasToolMetadata = true;
|
|
9195
|
+
}
|
|
9196
|
+
if (collectMediaTags(normalizeContent(row.content)).length > 0) {
|
|
9197
|
+
hasMediaDeliveryMarkup = true;
|
|
9198
|
+
}
|
|
9199
|
+
}
|
|
9200
|
+
}
|
|
9201
|
+
}
|
|
9202
|
+
if (rows.length === 0 || endIndex === 0) {
|
|
9203
|
+
return null;
|
|
9204
|
+
}
|
|
9205
|
+
return {
|
|
9206
|
+
endIndex,
|
|
9207
|
+
messages: rows,
|
|
9208
|
+
needsUpgrade: needsProjectionVersion && (hasToolMetadata || hasMediaDeliveryMarkup)
|
|
9209
|
+
};
|
|
9210
|
+
}
|
|
9211
|
+
function isHermesImportedMessage(message) {
|
|
9212
|
+
return message.hermes?.imported_from === "hermes" || message.raw?.format === "hermes-message" || message.raw?.format === "hermes-message-group";
|
|
9213
|
+
}
|
|
9214
|
+
function readHermesRowsFromAgentEvent(event) {
|
|
9215
|
+
if (event.raw?.format !== "hermes-message" && event.raw?.format !== "hermes-message-group") {
|
|
9216
|
+
return [];
|
|
9217
|
+
}
|
|
9218
|
+
const payload = toRecord8(event.raw.payload);
|
|
9219
|
+
const message = toRecord8(payload.message);
|
|
9220
|
+
if (normalizeMessageRole(readString9(message, "role") ?? void 0) === "tool") {
|
|
9221
|
+
return [message];
|
|
9222
|
+
}
|
|
9223
|
+
return readHermesRawMessageRows(event.raw).filter(
|
|
9224
|
+
(row) => Boolean(readString9(row, "role"))
|
|
9225
|
+
);
|
|
9226
|
+
}
|
|
9227
|
+
function appendHermesRowOnce(rows, seen, row) {
|
|
9228
|
+
const key = hermesRowKey(row, rows.length);
|
|
8822
9229
|
if (seen.has(key)) {
|
|
8823
9230
|
return;
|
|
8824
9231
|
}
|
|
@@ -8832,7 +9239,7 @@ function hermesRowKey(row, fallbackIndex) {
|
|
|
8832
9239
|
return `fallback:${fallbackIndex}:${row.role ?? ""}:${row.timestamp ?? ""}:${normalizeContent(row.content)}`;
|
|
8833
9240
|
}
|
|
8834
9241
|
function hasHermesToolMetadata(row) {
|
|
8835
|
-
return normalizeMessageRole(row.role) === "tool" || readHermesToolCalls(row).length > 0 || Boolean(
|
|
9242
|
+
return normalizeMessageRole(row.role) === "tool" || readHermesToolCalls(row).length > 0 || Boolean(readString9(row, "tool_call_id")) || Boolean(readString9(row, "tool_name"));
|
|
8836
9243
|
}
|
|
8837
9244
|
function toLinkMessages(input) {
|
|
8838
9245
|
const linkMessages = [];
|
|
@@ -8950,8 +9357,8 @@ function toLinkMessages(input) {
|
|
|
8950
9357
|
return linkMessages;
|
|
8951
9358
|
}
|
|
8952
9359
|
function consumePendingToolCall(input) {
|
|
8953
|
-
const toolCallId =
|
|
8954
|
-
const toolName =
|
|
9360
|
+
const toolCallId = readString9(input.toolMessage, "tool_call_id");
|
|
9361
|
+
const toolName = readString9(input.toolMessage, "tool_name");
|
|
8955
9362
|
let pending = toolCallId ? input.toolCallsById.get(toolCallId) : void 0;
|
|
8956
9363
|
if (!pending && toolName) {
|
|
8957
9364
|
pending = input.pendingToolCalls.find(
|
|
@@ -9001,13 +9408,13 @@ function readHermesToolCalls(message) {
|
|
|
9001
9408
|
);
|
|
9002
9409
|
}
|
|
9003
9410
|
function normalizeHermesToolCall(value) {
|
|
9004
|
-
const record =
|
|
9411
|
+
const record = toRecord8(value);
|
|
9005
9412
|
if (Object.keys(record).length === 0) {
|
|
9006
9413
|
return null;
|
|
9007
9414
|
}
|
|
9008
|
-
const fn =
|
|
9009
|
-
const id =
|
|
9010
|
-
const name =
|
|
9415
|
+
const fn = toRecord8(record.function);
|
|
9416
|
+
const id = readString9(record, "id") ?? readString9(record, "call_id") ?? readString9(record, "tool_call_id") ?? readString9(fn, "id") ?? void 0;
|
|
9417
|
+
const name = readString9(fn, "name") ?? readString9(record, "name") ?? readString9(record, "tool_name") ?? readString9(record, "tool") ?? "tool";
|
|
9011
9418
|
const rawArguments = fn.arguments ?? record.arguments ?? record.args ?? record.input;
|
|
9012
9419
|
return {
|
|
9013
9420
|
...id ? { id } : {},
|
|
@@ -9046,8 +9453,8 @@ function projectHermesToolCompletedEvent(input) {
|
|
|
9046
9453
|
const createdAt = isoFromHermesTime(input.sourceMessage.timestamp) ?? new Date(Date.now() + input.index).toISOString();
|
|
9047
9454
|
const output = normalizeContent(input.sourceMessage.content);
|
|
9048
9455
|
const parsedOutput = parseJsonValue(output);
|
|
9049
|
-
const toolCallId =
|
|
9050
|
-
const toolName =
|
|
9456
|
+
const toolCallId = readString9(input.sourceMessage, "tool_call_id") ?? input.pending?.toolCall.id;
|
|
9457
|
+
const toolName = readString9(input.sourceMessage, "tool_name") ?? input.pending?.toolCall.name ?? "tool";
|
|
9051
9458
|
return projectHermesAgentEvent({
|
|
9052
9459
|
conversationId: input.conversationId,
|
|
9053
9460
|
messageId: input.messageId,
|
|
@@ -9215,219 +9622,75 @@ function appendAgentEventBlock(message, event, updatedAt) {
|
|
|
9215
9622
|
}
|
|
9216
9623
|
function latestTimestamp(left, right) {
|
|
9217
9624
|
const leftTime = Date.parse(left);
|
|
9218
|
-
const rightTime = Date.parse(right);
|
|
9219
|
-
if (Number.isNaN(leftTime)) {
|
|
9220
|
-
return right;
|
|
9221
|
-
}
|
|
9222
|
-
if (Number.isNaN(rightTime)) {
|
|
9223
|
-
return left;
|
|
9224
|
-
}
|
|
9225
|
-
return leftTime >= rightTime ? left : right;
|
|
9226
|
-
}
|
|
9227
|
-
async function readKnownHermesSessions(store) {
|
|
9228
|
-
const ids = /* @__PURE__ */ new Set();
|
|
9229
|
-
const conversationIdsBySessionId = /* @__PURE__ */ new Map();
|
|
9230
|
-
for (const conversationId of await store.listConversationIds()) {
|
|
9231
|
-
const manifest = await store.readManifest(conversationId).catch(() => null);
|
|
9232
|
-
if (!manifest) {
|
|
9233
|
-
continue;
|
|
9234
|
-
}
|
|
9235
|
-
if (manifest.hermes_session_id) {
|
|
9236
|
-
ids.add(manifest.hermes_session_id);
|
|
9237
|
-
rememberKnownHermesConversation(
|
|
9238
|
-
conversationIdsBySessionId,
|
|
9239
|
-
manifest.hermes_session_id,
|
|
9240
|
-
conversationId
|
|
9241
|
-
);
|
|
9242
|
-
}
|
|
9243
|
-
for (const sessionId of manifest.hermes_session_ids ?? []) {
|
|
9244
|
-
ids.add(sessionId);
|
|
9245
|
-
rememberKnownHermesConversation(
|
|
9246
|
-
conversationIdsBySessionId,
|
|
9247
|
-
sessionId,
|
|
9248
|
-
conversationId
|
|
9249
|
-
);
|
|
9250
|
-
}
|
|
9251
|
-
}
|
|
9252
|
-
return { ids, conversationIdsBySessionId };
|
|
9253
|
-
}
|
|
9254
|
-
function rememberKnownHermesConversation(map, sessionId, conversationId) {
|
|
9255
|
-
const current = map.get(sessionId) ?? [];
|
|
9256
|
-
if (!current.includes(conversationId)) {
|
|
9257
|
-
map.set(sessionId, [...current, conversationId]);
|
|
9258
|
-
}
|
|
9259
|
-
}
|
|
9260
|
-
async function discoverHermesProfileNames() {
|
|
9261
|
-
const names = /* @__PURE__ */ new Set([DEFAULT_PROFILE_NAME]);
|
|
9262
|
-
const profilesDir = path13.join(os4.homedir(), ".hermes", "profiles");
|
|
9263
|
-
const entries = await readdir5(profilesDir, { withFileTypes: true }).catch(
|
|
9264
|
-
(error) => {
|
|
9265
|
-
if (isNodeError9(error, "ENOENT")) {
|
|
9266
|
-
return [];
|
|
9267
|
-
}
|
|
9268
|
-
throw error;
|
|
9269
|
-
}
|
|
9270
|
-
);
|
|
9271
|
-
for (const entry of entries) {
|
|
9272
|
-
if (entry.isDirectory() && PROFILE_NAME_PATTERN3.test(entry.name)) {
|
|
9273
|
-
names.add(entry.name);
|
|
9274
|
-
}
|
|
9275
|
-
}
|
|
9276
|
-
return [...names].sort((left, right) => {
|
|
9277
|
-
if (left === DEFAULT_PROFILE_NAME) {
|
|
9278
|
-
return -1;
|
|
9279
|
-
}
|
|
9280
|
-
if (right === DEFAULT_PROFILE_NAME) {
|
|
9281
|
-
return 1;
|
|
9282
|
-
}
|
|
9283
|
-
return left.localeCompare(right);
|
|
9284
|
-
});
|
|
9285
|
-
}
|
|
9286
|
-
async function listProfileSessions(dbPath) {
|
|
9287
|
-
if (!await isFile(dbPath)) {
|
|
9288
|
-
return [];
|
|
9289
|
-
}
|
|
9290
|
-
let db = null;
|
|
9291
|
-
try {
|
|
9292
|
-
const { DatabaseSync } = nodeRequire3(
|
|
9293
|
-
"node:sqlite"
|
|
9294
|
-
);
|
|
9295
|
-
db = new DatabaseSync(dbPath, {
|
|
9296
|
-
readOnly: true,
|
|
9297
|
-
timeout: 1e3
|
|
9298
|
-
});
|
|
9299
|
-
const sessionColumns = readTableColumns(db, "sessions");
|
|
9300
|
-
if (!sessionColumns.has("id")) {
|
|
9301
|
-
return [];
|
|
9302
|
-
}
|
|
9303
|
-
const messageColumns = readTableColumns(db, "messages");
|
|
9304
|
-
const selectColumns = [...sessionColumns].map((column) => `s.${quoteIdentifier(column)}`).join(", ");
|
|
9305
|
-
const lastActiveSql = messageColumns.has("timestamp") ? `COALESCE(
|
|
9306
|
-
(SELECT MAX(m.timestamp) FROM messages m WHERE m.session_id = s.id),
|
|
9307
|
-
s.started_at,
|
|
9308
|
-
0
|
|
9309
|
-
) AS last_active` : "COALESCE(s.started_at, 0) AS last_active";
|
|
9310
|
-
const rows = db.prepare(
|
|
9311
|
-
`
|
|
9312
|
-
SELECT ${selectColumns}, ${lastActiveSql}
|
|
9313
|
-
FROM sessions s
|
|
9314
|
-
ORDER BY last_active DESC
|
|
9315
|
-
`
|
|
9316
|
-
).all();
|
|
9317
|
-
return projectCompressionTips(rows);
|
|
9318
|
-
} finally {
|
|
9319
|
-
db?.close();
|
|
9320
|
-
}
|
|
9321
|
-
}
|
|
9322
|
-
function appendHermesRawMessage(message, row) {
|
|
9323
|
-
const rows = readHermesRawMessageRows(message.raw);
|
|
9324
|
-
message.raw = rows.length === 0 ? {
|
|
9325
|
-
format: "hermes-message",
|
|
9326
|
-
payload: row
|
|
9327
|
-
} : {
|
|
9328
|
-
format: "hermes-message-group",
|
|
9329
|
-
payload: { messages: [...rows, row] }
|
|
9330
|
-
};
|
|
9331
|
-
}
|
|
9332
|
-
function readHermesRawMessageRows(raw) {
|
|
9333
|
-
if (!raw) {
|
|
9334
|
-
return [];
|
|
9335
|
-
}
|
|
9336
|
-
if (raw.format === "hermes-message-group") {
|
|
9337
|
-
const payload = toRecord7(raw.payload);
|
|
9338
|
-
return Array.isArray(payload.messages) ? payload.messages.filter(
|
|
9339
|
-
(item) => typeof item === "object" && item !== null
|
|
9340
|
-
) : [];
|
|
9341
|
-
}
|
|
9342
|
-
if (raw.format === "hermes-message") {
|
|
9343
|
-
return typeof raw.payload === "object" && raw.payload !== null ? [raw.payload] : [];
|
|
9344
|
-
}
|
|
9345
|
-
return [];
|
|
9346
|
-
}
|
|
9347
|
-
function rememberHermesMessageId(message, row) {
|
|
9348
|
-
if (row.id === void 0 || row.id === null) {
|
|
9349
|
-
return;
|
|
9350
|
-
}
|
|
9351
|
-
const existing = Array.isArray(message.hermes?.message_ids) ? message.hermes.message_ids : message.hermes?.message_id === void 0 ? [] : [message.hermes.message_id];
|
|
9352
|
-
const id = row.id;
|
|
9353
|
-
message.hermes = {
|
|
9354
|
-
...message.hermes ?? {},
|
|
9355
|
-
message_ids: existing.includes(id) ? existing : [...existing, id]
|
|
9356
|
-
};
|
|
9357
|
-
}
|
|
9358
|
-
function joinImportedText(left, right) {
|
|
9359
|
-
if (!left) {
|
|
9625
|
+
const rightTime = Date.parse(right);
|
|
9626
|
+
if (Number.isNaN(leftTime)) {
|
|
9360
9627
|
return right;
|
|
9361
9628
|
}
|
|
9362
|
-
if (
|
|
9629
|
+
if (Number.isNaN(rightTime)) {
|
|
9363
9630
|
return left;
|
|
9364
9631
|
}
|
|
9365
|
-
|
|
9366
|
-
return `${left}${right}`;
|
|
9367
|
-
}
|
|
9368
|
-
return `${left}
|
|
9369
|
-
|
|
9370
|
-
${right}`;
|
|
9632
|
+
return leftTime >= rightTime ? left : right;
|
|
9371
9633
|
}
|
|
9372
|
-
function
|
|
9373
|
-
const
|
|
9374
|
-
const
|
|
9375
|
-
for (const
|
|
9376
|
-
const
|
|
9377
|
-
if (!
|
|
9634
|
+
async function readKnownHermesSessions(store) {
|
|
9635
|
+
const ids = /* @__PURE__ */ new Set();
|
|
9636
|
+
const conversationIdsBySessionId = /* @__PURE__ */ new Map();
|
|
9637
|
+
for (const conversationId of await store.listConversationIds()) {
|
|
9638
|
+
const manifest = await store.readManifest(conversationId).catch(() => null);
|
|
9639
|
+
if (!manifest) {
|
|
9378
9640
|
continue;
|
|
9379
9641
|
}
|
|
9380
|
-
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9642
|
+
if (manifest.hermes_session_id) {
|
|
9643
|
+
ids.add(manifest.hermes_session_id);
|
|
9644
|
+
rememberKnownHermesConversation(
|
|
9645
|
+
conversationIdsBySessionId,
|
|
9646
|
+
manifest.hermes_session_id,
|
|
9647
|
+
conversationId
|
|
9648
|
+
);
|
|
9386
9649
|
}
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9650
|
+
for (const sessionId of manifest.hermes_session_ids ?? []) {
|
|
9651
|
+
ids.add(sessionId);
|
|
9652
|
+
rememberKnownHermesConversation(
|
|
9653
|
+
conversationIdsBySessionId,
|
|
9654
|
+
sessionId,
|
|
9655
|
+
conversationId
|
|
9656
|
+
);
|
|
9393
9657
|
}
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
|
|
9406
|
-
|
|
9658
|
+
}
|
|
9659
|
+
return { ids, conversationIdsBySessionId };
|
|
9660
|
+
}
|
|
9661
|
+
function rememberKnownHermesConversation(map, sessionId, conversationId) {
|
|
9662
|
+
const current = map.get(sessionId) ?? [];
|
|
9663
|
+
if (!current.includes(conversationId)) {
|
|
9664
|
+
map.set(sessionId, [...current, conversationId]);
|
|
9665
|
+
}
|
|
9666
|
+
}
|
|
9667
|
+
async function discoverHermesProfileNames() {
|
|
9668
|
+
const names = /* @__PURE__ */ new Set([DEFAULT_PROFILE_NAME]);
|
|
9669
|
+
const profilesDir = path14.join(os4.homedir(), ".hermes", "profiles");
|
|
9670
|
+
const entries = await readdir6(profilesDir, { withFileTypes: true }).catch(
|
|
9671
|
+
(error) => {
|
|
9672
|
+
if (isNodeError10(error, "ENOENT")) {
|
|
9673
|
+
return [];
|
|
9407
9674
|
}
|
|
9408
|
-
|
|
9409
|
-
visited.add(nextId);
|
|
9675
|
+
throw error;
|
|
9410
9676
|
}
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
id: tipId,
|
|
9416
|
-
_lineage_root_id: id,
|
|
9417
|
-
started_at: readNumber2(row.started_at) ?? readNumber2(tip.started_at)
|
|
9418
|
-
});
|
|
9677
|
+
);
|
|
9678
|
+
for (const entry of entries) {
|
|
9679
|
+
if (entry.isDirectory() && PROFILE_NAME_PATTERN3.test(entry.name)) {
|
|
9680
|
+
names.add(entry.name);
|
|
9419
9681
|
}
|
|
9420
9682
|
}
|
|
9421
|
-
return
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9683
|
+
return [...names].sort((left, right) => {
|
|
9684
|
+
if (left === DEFAULT_PROFILE_NAME) {
|
|
9685
|
+
return -1;
|
|
9686
|
+
}
|
|
9687
|
+
if (right === DEFAULT_PROFILE_NAME) {
|
|
9688
|
+
return 1;
|
|
9689
|
+
}
|
|
9690
|
+
return left.localeCompare(right);
|
|
9691
|
+
});
|
|
9429
9692
|
}
|
|
9430
|
-
async function
|
|
9693
|
+
async function listProfileSessions(dbPath) {
|
|
9431
9694
|
if (!await isFile(dbPath)) {
|
|
9432
9695
|
return [];
|
|
9433
9696
|
}
|
|
@@ -9440,568 +9703,490 @@ async function readStateDbMessages(dbPath, sessionId) {
|
|
|
9440
9703
|
readOnly: true,
|
|
9441
9704
|
timeout: 1e3
|
|
9442
9705
|
});
|
|
9443
|
-
const
|
|
9444
|
-
if (!
|
|
9706
|
+
const sessionColumns = readTableColumns(db, "sessions");
|
|
9707
|
+
if (!sessionColumns.has("id")) {
|
|
9445
9708
|
return [];
|
|
9446
9709
|
}
|
|
9447
|
-
const
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
ORDER BY timestamp, id
|
|
9710
|
+
const messageColumns = readTableColumns(db, "messages");
|
|
9711
|
+
const selectColumns = [...sessionColumns].map((column) => `s.${quoteIdentifier(column)}`).join(", ");
|
|
9712
|
+
const lastActiveSql = messageColumns.has("timestamp") ? `COALESCE(
|
|
9713
|
+
(SELECT MAX(m.timestamp) FROM messages m WHERE m.session_id = s.id),
|
|
9714
|
+
s.started_at,
|
|
9715
|
+
0
|
|
9716
|
+
) AS last_active` : "COALESCE(s.started_at, 0) AS last_active";
|
|
9717
|
+
const rows = db.prepare(
|
|
9456
9718
|
`
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
}
|
|
9464
|
-
|
|
9465
|
-
if (!/^[A-Za-z0-9._:-]{1,160}$/u.test(sessionId)) {
|
|
9466
|
-
return [];
|
|
9467
|
-
}
|
|
9468
|
-
const profileDir = resolveHermesProfileDir(profileName);
|
|
9469
|
-
const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path13.join(profileDir, "sessions"));
|
|
9470
|
-
const transcriptPath = path13.join(sessionsDir, `${sessionId}.jsonl`);
|
|
9471
|
-
const raw = await readFile8(transcriptPath, "utf8").catch((error) => {
|
|
9472
|
-
if (isNodeError9(error, "ENOENT")) {
|
|
9473
|
-
return "";
|
|
9474
|
-
}
|
|
9475
|
-
throw error;
|
|
9476
|
-
});
|
|
9477
|
-
if (!raw.trim()) {
|
|
9478
|
-
return [];
|
|
9479
|
-
}
|
|
9480
|
-
const rows = [];
|
|
9481
|
-
for (const line of raw.split(/\r?\n/u)) {
|
|
9482
|
-
if (!line.trim()) {
|
|
9483
|
-
continue;
|
|
9484
|
-
}
|
|
9485
|
-
try {
|
|
9486
|
-
const parsed = JSON.parse(line);
|
|
9487
|
-
const normalized = normalizeJsonlMessage(parsed);
|
|
9488
|
-
if (normalized) {
|
|
9489
|
-
rows.push(normalized);
|
|
9490
|
-
}
|
|
9491
|
-
} catch {
|
|
9492
|
-
continue;
|
|
9493
|
-
}
|
|
9494
|
-
}
|
|
9495
|
-
return rows;
|
|
9496
|
-
}
|
|
9497
|
-
function normalizeJsonlMessage(row) {
|
|
9498
|
-
const role = readString8(row, "role");
|
|
9499
|
-
if (!role) {
|
|
9500
|
-
return null;
|
|
9501
|
-
}
|
|
9502
|
-
const content = normalizeContent(row.content);
|
|
9503
|
-
const timestamp = readNumber2(row.timestamp) ?? readNumber2(row.created_at) ?? readNumber2(row.createdAt);
|
|
9504
|
-
return {
|
|
9505
|
-
...row,
|
|
9506
|
-
role,
|
|
9507
|
-
content,
|
|
9508
|
-
timestamp: timestamp ?? void 0
|
|
9509
|
-
};
|
|
9510
|
-
}
|
|
9511
|
-
function toLinkMessage(input) {
|
|
9512
|
-
const role = normalizeMessageRole(input.message.role);
|
|
9513
|
-
const text = normalizeContent(input.message.content);
|
|
9514
|
-
const createdAt = isoFromHermesTime(input.message.timestamp) ?? new Date(Date.now() + input.index).toISOString();
|
|
9515
|
-
return {
|
|
9516
|
-
id: `msg_${randomUUID6().replaceAll("-", "")}`,
|
|
9517
|
-
schema_version: 1,
|
|
9518
|
-
conversation_id: input.conversationId,
|
|
9519
|
-
role,
|
|
9520
|
-
status: "completed",
|
|
9521
|
-
created_at: createdAt,
|
|
9522
|
-
updated_at: createdAt,
|
|
9523
|
-
sender: senderForRole({
|
|
9524
|
-
role,
|
|
9525
|
-
profileName: input.profileName,
|
|
9526
|
-
profileUid: input.profileUid,
|
|
9527
|
-
profileDisplayName: input.profileDisplayName
|
|
9528
|
-
}),
|
|
9529
|
-
parts: text ? [{ type: "text", text }] : [],
|
|
9530
|
-
attachments: [],
|
|
9531
|
-
hermes: {
|
|
9532
|
-
session_id: input.sessionId,
|
|
9533
|
-
message_id: input.message.id,
|
|
9534
|
-
imported_from: "hermes",
|
|
9535
|
-
import_projection: HERMES_IMPORT_PROJECTION_VERSION
|
|
9536
|
-
},
|
|
9537
|
-
raw: {
|
|
9538
|
-
format: "hermes-message",
|
|
9539
|
-
payload: input.message
|
|
9540
|
-
}
|
|
9541
|
-
};
|
|
9542
|
-
}
|
|
9543
|
-
function senderForRole(input) {
|
|
9544
|
-
switch (input.role) {
|
|
9545
|
-
case "user":
|
|
9546
|
-
return { id: "hermes_user", type: "human", display_name: "Me" };
|
|
9547
|
-
case "assistant":
|
|
9548
|
-
return {
|
|
9549
|
-
id: `agent_${input.profileName}`,
|
|
9550
|
-
type: "agent",
|
|
9551
|
-
display_name: input.profileDisplayName,
|
|
9552
|
-
profile_uid: input.profileUid,
|
|
9553
|
-
profile: input.profileName
|
|
9554
|
-
};
|
|
9555
|
-
case "tool":
|
|
9556
|
-
return { id: "hermes_tool", type: "tool", display_name: "Tool" };
|
|
9557
|
-
case "system":
|
|
9558
|
-
return { id: "hermes_system", type: "system", display_name: "System" };
|
|
9559
|
-
}
|
|
9560
|
-
}
|
|
9561
|
-
function firstUserText(snapshot) {
|
|
9562
|
-
return snapshot.messages.find((message) => message.role === "user")?.parts.find((part) => part.type === "text")?.text?.slice(0, 80);
|
|
9563
|
-
}
|
|
9564
|
-
function normalizeTitle(value) {
|
|
9565
|
-
const normalized = value?.replace(/\s+/gu, " ").trim();
|
|
9566
|
-
return normalized || DEFAULT_CONVERSATION_TITLE;
|
|
9567
|
-
}
|
|
9568
|
-
function normalizeMessageRole(value) {
|
|
9569
|
-
switch (value?.trim().toLowerCase()) {
|
|
9570
|
-
case "user":
|
|
9571
|
-
return "user";
|
|
9572
|
-
case "assistant":
|
|
9573
|
-
return "assistant";
|
|
9574
|
-
case "tool":
|
|
9575
|
-
return "tool";
|
|
9576
|
-
case "system":
|
|
9577
|
-
return "system";
|
|
9578
|
-
default:
|
|
9579
|
-
return "system";
|
|
9580
|
-
}
|
|
9581
|
-
}
|
|
9582
|
-
function normalizeContent(value) {
|
|
9583
|
-
if (typeof value === "string") {
|
|
9584
|
-
return value;
|
|
9585
|
-
}
|
|
9586
|
-
if (Array.isArray(value)) {
|
|
9587
|
-
return value.map((item) => {
|
|
9588
|
-
if (typeof item === "string") {
|
|
9589
|
-
return item;
|
|
9590
|
-
}
|
|
9591
|
-
if (typeof item === "object" && item !== null) {
|
|
9592
|
-
return readString8(item, "text") ?? "";
|
|
9593
|
-
}
|
|
9594
|
-
return "";
|
|
9595
|
-
}).filter(Boolean).join("");
|
|
9719
|
+
SELECT ${selectColumns}, ${lastActiveSql}
|
|
9720
|
+
FROM sessions s
|
|
9721
|
+
ORDER BY last_active DESC
|
|
9722
|
+
`
|
|
9723
|
+
).all();
|
|
9724
|
+
return projectCompressionTips(rows);
|
|
9725
|
+
} finally {
|
|
9726
|
+
db?.close();
|
|
9596
9727
|
}
|
|
9597
|
-
return "";
|
|
9598
9728
|
}
|
|
9599
|
-
function
|
|
9600
|
-
|
|
9601
|
-
|
|
9729
|
+
function appendHermesRawMessage(message, row) {
|
|
9730
|
+
const rows = readHermesRawMessageRows(message.raw);
|
|
9731
|
+
message.raw = rows.length === 0 ? {
|
|
9732
|
+
format: "hermes-message",
|
|
9733
|
+
payload: row
|
|
9734
|
+
} : {
|
|
9735
|
+
format: "hermes-message-group",
|
|
9736
|
+
payload: { messages: [...rows, row] }
|
|
9737
|
+
};
|
|
9738
|
+
}
|
|
9739
|
+
function readHermesRawMessageRows(raw) {
|
|
9740
|
+
if (!raw) {
|
|
9741
|
+
return [];
|
|
9602
9742
|
}
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
return
|
|
9743
|
+
if (raw.format === "hermes-message-group") {
|
|
9744
|
+
const payload = toRecord8(raw.payload);
|
|
9745
|
+
return Array.isArray(payload.messages) ? payload.messages.filter(
|
|
9746
|
+
(item) => typeof item === "object" && item !== null
|
|
9747
|
+
) : [];
|
|
9606
9748
|
}
|
|
9607
|
-
|
|
9608
|
-
return
|
|
9609
|
-
} catch {
|
|
9610
|
-
return void 0;
|
|
9749
|
+
if (raw.format === "hermes-message") {
|
|
9750
|
+
return typeof raw.payload === "object" && raw.payload !== null ? [raw.payload] : [];
|
|
9611
9751
|
}
|
|
9752
|
+
return [];
|
|
9612
9753
|
}
|
|
9613
|
-
function
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
function isDeletedSession(session) {
|
|
9617
|
-
return readBoolean(session.deleted) || readBoolean(session.is_deleted) || Boolean(readString8(session, "deleted_at")) || ["deleted", "removed"].includes(readString8(session, "status") ?? "");
|
|
9618
|
-
}
|
|
9619
|
-
function isHiddenSession(session) {
|
|
9620
|
-
const source = readString8(session, "source")?.toLowerCase();
|
|
9621
|
-
const status = readString8(session, "status")?.toLowerCase();
|
|
9622
|
-
const visibility = readString8(session, "visibility")?.toLowerCase();
|
|
9623
|
-
return Boolean(source && HIDDEN_SESSION_SOURCES.has(source)) || readBoolean(session.hidden) || readBoolean(session.archived) || Boolean(readString8(session, "archived_at")) || status === "hidden" || status === "archived" || visibility === "hidden" || visibility === "hide";
|
|
9624
|
-
}
|
|
9625
|
-
function readTableColumns(db, tableName) {
|
|
9626
|
-
try {
|
|
9627
|
-
const rows = db.prepare(`PRAGMA table_info(${quoteIdentifier(tableName)})`).all();
|
|
9628
|
-
return new Set(
|
|
9629
|
-
rows.map((row) => typeof row.name === "string" ? row.name : "").filter(Boolean)
|
|
9630
|
-
);
|
|
9631
|
-
} catch {
|
|
9632
|
-
return /* @__PURE__ */ new Set();
|
|
9754
|
+
function rememberHermesMessageId(message, row) {
|
|
9755
|
+
if (row.id === void 0 || row.id === null) {
|
|
9756
|
+
return;
|
|
9633
9757
|
}
|
|
9758
|
+
const existing = Array.isArray(message.hermes?.message_ids) ? message.hermes.message_ids : message.hermes?.message_id === void 0 ? [] : [message.hermes.message_id];
|
|
9759
|
+
const id = row.id;
|
|
9760
|
+
message.hermes = {
|
|
9761
|
+
...message.hermes ?? {},
|
|
9762
|
+
message_ids: existing.includes(id) ? existing : [...existing, id]
|
|
9763
|
+
};
|
|
9634
9764
|
}
|
|
9635
|
-
function
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
async function isFile(filePath) {
|
|
9639
|
-
return stat7(filePath).then((value) => value.isFile()).catch((error) => {
|
|
9640
|
-
if (isNodeError9(error, "ENOENT")) {
|
|
9641
|
-
return false;
|
|
9642
|
-
}
|
|
9643
|
-
throw error;
|
|
9644
|
-
});
|
|
9645
|
-
}
|
|
9646
|
-
function createConversationId() {
|
|
9647
|
-
return `conv_${randomUUID6().replaceAll("-", "")}`;
|
|
9648
|
-
}
|
|
9649
|
-
function isoFromHermesTime(value) {
|
|
9650
|
-
const numeric = readNumber2(value);
|
|
9651
|
-
if (!numeric || numeric <= 0) {
|
|
9652
|
-
return void 0;
|
|
9765
|
+
function joinImportedText(left, right) {
|
|
9766
|
+
if (!left) {
|
|
9767
|
+
return right;
|
|
9653
9768
|
}
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
}
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
}
|
|
9661
|
-
|
|
9662
|
-
|
|
9769
|
+
if (!right) {
|
|
9770
|
+
return left;
|
|
9771
|
+
}
|
|
9772
|
+
if (/\s$/u.test(left) || /^\s/u.test(right)) {
|
|
9773
|
+
return `${left}${right}`;
|
|
9774
|
+
}
|
|
9775
|
+
return `${left}
|
|
9776
|
+
|
|
9777
|
+
${right}`;
|
|
9663
9778
|
}
|
|
9664
|
-
function
|
|
9665
|
-
|
|
9666
|
-
|
|
9779
|
+
function projectCompressionTips(rows) {
|
|
9780
|
+
const byId = /* @__PURE__ */ new Map();
|
|
9781
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
9782
|
+
for (const row of rows) {
|
|
9783
|
+
const id = readString9(row, "id");
|
|
9784
|
+
if (!id) {
|
|
9785
|
+
continue;
|
|
9786
|
+
}
|
|
9787
|
+
byId.set(id, row);
|
|
9788
|
+
const parentId = readString9(row, "parent_session_id");
|
|
9789
|
+
if (parentId) {
|
|
9790
|
+
const children = childrenByParent.get(parentId) ?? [];
|
|
9791
|
+
children.push(row);
|
|
9792
|
+
childrenByParent.set(parentId, children);
|
|
9793
|
+
}
|
|
9667
9794
|
}
|
|
9668
|
-
|
|
9669
|
-
|
|
9795
|
+
const projected = [];
|
|
9796
|
+
for (const row of rows) {
|
|
9797
|
+
const id = readString9(row, "id");
|
|
9798
|
+
if (!id || readString9(row, "parent_session_id")) {
|
|
9799
|
+
continue;
|
|
9800
|
+
}
|
|
9801
|
+
let tip = row;
|
|
9802
|
+
const visited = /* @__PURE__ */ new Set([id]);
|
|
9803
|
+
while (readString9(tip, "end_reason") === "compression") {
|
|
9804
|
+
const tipId2 = readString9(tip, "id");
|
|
9805
|
+
if (!tipId2) {
|
|
9806
|
+
break;
|
|
9807
|
+
}
|
|
9808
|
+
const next = (childrenByParent.get(tipId2) ?? []).filter((child) => readString9(child, "id")).sort(
|
|
9809
|
+
(left, right) => (readNumber2(right.last_active) ?? 0) - (readNumber2(left.last_active) ?? 0)
|
|
9810
|
+
)[0];
|
|
9811
|
+
const nextId = next ? readString9(next, "id") : null;
|
|
9812
|
+
if (!next || !nextId || visited.has(nextId)) {
|
|
9813
|
+
break;
|
|
9814
|
+
}
|
|
9815
|
+
tip = next;
|
|
9816
|
+
visited.add(nextId);
|
|
9817
|
+
}
|
|
9818
|
+
const tipId = readString9(tip, "id");
|
|
9819
|
+
if (tipId) {
|
|
9820
|
+
projected.push({
|
|
9821
|
+
...tip,
|
|
9822
|
+
id: tipId,
|
|
9823
|
+
_lineage_root_id: id,
|
|
9824
|
+
started_at: readNumber2(row.started_at) ?? readNumber2(tip.started_at)
|
|
9825
|
+
});
|
|
9826
|
+
}
|
|
9670
9827
|
}
|
|
9671
|
-
return
|
|
9828
|
+
return projected;
|
|
9672
9829
|
}
|
|
9673
|
-
function
|
|
9674
|
-
|
|
9830
|
+
async function readHermesSessionMessages(candidate) {
|
|
9831
|
+
const [dbMessages, jsonlMessages] = await Promise.all([
|
|
9832
|
+
readStateDbMessages(candidate.dbPath, candidate.session.id),
|
|
9833
|
+
readJsonlMessages(candidate.profileName, candidate.session.id)
|
|
9834
|
+
]);
|
|
9835
|
+
return jsonlMessages.length > dbMessages.length ? jsonlMessages : dbMessages;
|
|
9675
9836
|
}
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
import path14 from "path";
|
|
9680
|
-
var MAX_IMPORTED_BLOB_BYTES = 100 * 1024 * 1024;
|
|
9681
|
-
var MAX_MEDIA_IMPORT_FAILURES = 20;
|
|
9682
|
-
var MAX_DELIVERY_FILES = 50;
|
|
9683
|
-
var DELIVERY_STAGING_SEGMENT = "delivery-staging";
|
|
9684
|
-
var SUPPORTED_DELIVERY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
9685
|
-
".png",
|
|
9686
|
-
".jpg",
|
|
9687
|
-
".jpeg",
|
|
9688
|
-
".gif",
|
|
9689
|
-
".webp",
|
|
9690
|
-
".heic",
|
|
9691
|
-
".pdf",
|
|
9692
|
-
".txt",
|
|
9693
|
-
".log",
|
|
9694
|
-
".md",
|
|
9695
|
-
".markdown",
|
|
9696
|
-
".json",
|
|
9697
|
-
".jsonl",
|
|
9698
|
-
".yaml",
|
|
9699
|
-
".yml",
|
|
9700
|
-
".toml",
|
|
9701
|
-
".ini",
|
|
9702
|
-
".xml",
|
|
9703
|
-
".html",
|
|
9704
|
-
".css",
|
|
9705
|
-
".js",
|
|
9706
|
-
".ts",
|
|
9707
|
-
".jsx",
|
|
9708
|
-
".tsx",
|
|
9709
|
-
".dart",
|
|
9710
|
-
".py",
|
|
9711
|
-
".java",
|
|
9712
|
-
".kt",
|
|
9713
|
-
".swift",
|
|
9714
|
-
".go",
|
|
9715
|
-
".rs",
|
|
9716
|
-
".rb",
|
|
9717
|
-
".php",
|
|
9718
|
-
".c",
|
|
9719
|
-
".cc",
|
|
9720
|
-
".cpp",
|
|
9721
|
-
".h",
|
|
9722
|
-
".hpp",
|
|
9723
|
-
".cs",
|
|
9724
|
-
".sql",
|
|
9725
|
-
".csv",
|
|
9726
|
-
".tsv",
|
|
9727
|
-
".doc",
|
|
9728
|
-
".docx",
|
|
9729
|
-
".xls",
|
|
9730
|
-
".xlsx",
|
|
9731
|
-
".ppt",
|
|
9732
|
-
".pptx",
|
|
9733
|
-
".zip",
|
|
9734
|
-
".rar",
|
|
9735
|
-
".7z",
|
|
9736
|
-
".tar",
|
|
9737
|
-
".gz",
|
|
9738
|
-
".mp4",
|
|
9739
|
-
".mov",
|
|
9740
|
-
".avi",
|
|
9741
|
-
".mkv",
|
|
9742
|
-
".webm",
|
|
9743
|
-
".ogg",
|
|
9744
|
-
".opus",
|
|
9745
|
-
".mp3",
|
|
9746
|
-
".wav",
|
|
9747
|
-
".m4a"
|
|
9748
|
-
]);
|
|
9749
|
-
function resolveDeliveryStagingTarget(paths, stagingDir) {
|
|
9750
|
-
const resolvedDir = path14.resolve(stagingDir);
|
|
9751
|
-
const relative = path14.relative(path14.resolve(paths.conversationsDir), resolvedDir);
|
|
9752
|
-
if (!relative || relative.startsWith("..") || path14.isAbsolute(relative)) {
|
|
9753
|
-
throw new LinkHttpError(
|
|
9754
|
-
400,
|
|
9755
|
-
"delivery_staging_invalid",
|
|
9756
|
-
"delivery staging directory must be inside Hermes Link conversations"
|
|
9757
|
-
);
|
|
9837
|
+
async function readStateDbMessages(dbPath, sessionId) {
|
|
9838
|
+
if (!await isFile(dbPath)) {
|
|
9839
|
+
return [];
|
|
9758
9840
|
}
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
"delivery_staging_invalid",
|
|
9764
|
-
"delivery staging directory is invalid"
|
|
9841
|
+
let db = null;
|
|
9842
|
+
try {
|
|
9843
|
+
const { DatabaseSync } = nodeRequire3(
|
|
9844
|
+
"node:sqlite"
|
|
9765
9845
|
);
|
|
9846
|
+
db = new DatabaseSync(dbPath, {
|
|
9847
|
+
readOnly: true,
|
|
9848
|
+
timeout: 1e3
|
|
9849
|
+
});
|
|
9850
|
+
const columns = readTableColumns(db, "messages");
|
|
9851
|
+
if (!columns.has("session_id") || !columns.has("role")) {
|
|
9852
|
+
return [];
|
|
9853
|
+
}
|
|
9854
|
+
const selectColumns = MESSAGE_COLUMNS.map(
|
|
9855
|
+
(column) => columns.has(column) ? quoteIdentifier(column) : `NULL AS ${column}`
|
|
9856
|
+
).join(", ");
|
|
9857
|
+
return db.prepare(
|
|
9858
|
+
`
|
|
9859
|
+
SELECT ${selectColumns}
|
|
9860
|
+
FROM messages
|
|
9861
|
+
WHERE session_id = ?
|
|
9862
|
+
ORDER BY timestamp, id
|
|
9863
|
+
`
|
|
9864
|
+
).all(sessionId);
|
|
9865
|
+
} catch {
|
|
9866
|
+
return [];
|
|
9867
|
+
} finally {
|
|
9868
|
+
db?.close();
|
|
9766
9869
|
}
|
|
9767
|
-
return {
|
|
9768
|
-
conversationId: segments[0],
|
|
9769
|
-
runId: segments[2],
|
|
9770
|
-
stagingDir: resolvedDir
|
|
9771
|
-
};
|
|
9772
9870
|
}
|
|
9773
|
-
async function
|
|
9774
|
-
|
|
9871
|
+
async function readJsonlMessages(profileName, sessionId) {
|
|
9872
|
+
if (!/^[A-Za-z0-9._:-]{1,160}$/u.test(sessionId)) {
|
|
9873
|
+
return [];
|
|
9874
|
+
}
|
|
9875
|
+
const profileDir = resolveHermesProfileDir(profileName);
|
|
9876
|
+
const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path14.join(profileDir, "sessions"));
|
|
9877
|
+
const transcriptPath = path14.join(sessionsDir, `${sessionId}.jsonl`);
|
|
9878
|
+
const raw = await readFile9(transcriptPath, "utf8").catch((error) => {
|
|
9775
9879
|
if (isNodeError10(error, "ENOENT")) {
|
|
9776
|
-
|
|
9777
|
-
404,
|
|
9778
|
-
"delivery_staging_not_found",
|
|
9779
|
-
"delivery staging directory was not found"
|
|
9780
|
-
);
|
|
9880
|
+
return "";
|
|
9781
9881
|
}
|
|
9782
9882
|
throw error;
|
|
9783
9883
|
});
|
|
9784
|
-
if (!
|
|
9785
|
-
|
|
9786
|
-
400,
|
|
9787
|
-
"delivery_staging_not_directory",
|
|
9788
|
-
"delivery staging path is not a directory"
|
|
9789
|
-
);
|
|
9884
|
+
if (!raw.trim()) {
|
|
9885
|
+
return [];
|
|
9790
9886
|
}
|
|
9791
|
-
const
|
|
9792
|
-
|
|
9793
|
-
(
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
9887
|
+
const rows = [];
|
|
9888
|
+
for (const line of raw.split(/\r?\n/u)) {
|
|
9889
|
+
if (!line.trim()) {
|
|
9890
|
+
continue;
|
|
9891
|
+
}
|
|
9892
|
+
try {
|
|
9893
|
+
const parsed = JSON.parse(line);
|
|
9894
|
+
const normalized = normalizeJsonlMessage(parsed);
|
|
9895
|
+
if (normalized) {
|
|
9896
|
+
rows.push(normalized);
|
|
9897
|
+
}
|
|
9898
|
+
} catch {
|
|
9899
|
+
continue;
|
|
9900
|
+
}
|
|
9901
|
+
}
|
|
9902
|
+
return rows;
|
|
9803
9903
|
}
|
|
9804
|
-
|
|
9805
|
-
const
|
|
9806
|
-
if (
|
|
9807
|
-
return
|
|
9904
|
+
function normalizeJsonlMessage(row) {
|
|
9905
|
+
const role = readString9(row, "role");
|
|
9906
|
+
if (!role) {
|
|
9907
|
+
return null;
|
|
9808
9908
|
}
|
|
9809
|
-
const
|
|
9810
|
-
const
|
|
9811
|
-
|
|
9812
|
-
|
|
9813
|
-
|
|
9814
|
-
|
|
9909
|
+
const content = normalizeContent(row.content);
|
|
9910
|
+
const timestamp = readNumber2(row.timestamp) ?? readNumber2(row.created_at) ?? readNumber2(row.createdAt);
|
|
9911
|
+
return {
|
|
9912
|
+
...row,
|
|
9913
|
+
role,
|
|
9914
|
+
content,
|
|
9915
|
+
timestamp: timestamp ?? void 0
|
|
9916
|
+
};
|
|
9917
|
+
}
|
|
9918
|
+
async function hydrateImportedConversationMedia(input) {
|
|
9919
|
+
const snapshot = await input.store.readSnapshot(input.conversationId);
|
|
9920
|
+
const imports = snapshot.messages.map((message) => ({
|
|
9921
|
+
messageId: message.id,
|
|
9922
|
+
references: collectMessageMediaReferences(message)
|
|
9923
|
+
})).filter((item) => item.references.length > 0);
|
|
9924
|
+
if (imports.length === 0) {
|
|
9925
|
+
return;
|
|
9815
9926
|
}
|
|
9816
|
-
const
|
|
9817
|
-
const
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
filename: blob.filename,
|
|
9839
|
-
url: `/api/v1/conversations/${encodeURIComponent(input.conversationId)}/blobs/${encodeURIComponent(blob.id)}`
|
|
9840
|
-
};
|
|
9841
|
-
assistant.parts.push(part);
|
|
9842
|
-
assistant.attachments.push({
|
|
9843
|
-
blob_id: blob.id,
|
|
9844
|
-
mime: blob.mime,
|
|
9845
|
-
size: blob.size,
|
|
9846
|
-
filename: blob.filename,
|
|
9847
|
-
source: "hermes_output"
|
|
9848
|
-
});
|
|
9849
|
-
importedSourceKeys.add(sourceKey);
|
|
9850
|
-
importedParts.push(part);
|
|
9851
|
-
} catch (error) {
|
|
9852
|
-
if (sourceKey && !failedSourceKeys.has(sourceKey)) {
|
|
9853
|
-
const failure = describeMediaImportFailure(reference, sourceKey, error);
|
|
9854
|
-
failedSourceKeys.add(sourceKey);
|
|
9855
|
-
failureRecordsByKey.set(sourceKey, failure);
|
|
9856
|
-
newFailures.push(failure);
|
|
9927
|
+
const outcomes = /* @__PURE__ */ new Map();
|
|
9928
|
+
for (const item of imports) {
|
|
9929
|
+
const result = await importMediaReferencesForMessage(
|
|
9930
|
+
{
|
|
9931
|
+
logger: input.logger,
|
|
9932
|
+
readSnapshot: (conversationId) => input.store.readSnapshot(conversationId),
|
|
9933
|
+
writeSnapshot: (conversationId, nextSnapshot2) => input.store.writeSnapshot(conversationId, nextSnapshot2),
|
|
9934
|
+
appendEvent: async (conversationId, event) => ({
|
|
9935
|
+
seq: 0,
|
|
9936
|
+
conversation_id: conversationId,
|
|
9937
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9938
|
+
...event
|
|
9939
|
+
}),
|
|
9940
|
+
writeBlob: (conversationId, blob) => writeConversationBlob(input.paths, conversationId, blob, {
|
|
9941
|
+
maxBytes: MAX_IMPORTED_HERMES_MEDIA_BYTES
|
|
9942
|
+
})
|
|
9943
|
+
},
|
|
9944
|
+
{
|
|
9945
|
+
conversationId: input.conversationId,
|
|
9946
|
+
runId: IMPORTED_MEDIA_PLACEHOLDER_RUN_ID,
|
|
9947
|
+
messageId: item.messageId,
|
|
9948
|
+
references: item.references
|
|
9857
9949
|
}
|
|
9858
|
-
|
|
9859
|
-
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9950
|
+
);
|
|
9951
|
+
if (result.imported_count > 0 || result.failed_count > 0) {
|
|
9952
|
+
outcomes.set(item.messageId, {
|
|
9953
|
+
imported: result.imported_count > 0,
|
|
9954
|
+
failed: result.failed_count > 0
|
|
9863
9955
|
});
|
|
9864
9956
|
}
|
|
9865
9957
|
}
|
|
9866
|
-
if (
|
|
9867
|
-
return
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9958
|
+
if (outcomes.size === 0) {
|
|
9959
|
+
return;
|
|
9960
|
+
}
|
|
9961
|
+
const nextSnapshot = await input.store.readSnapshot(input.conversationId);
|
|
9962
|
+
let changed = false;
|
|
9963
|
+
for (const message of nextSnapshot.messages) {
|
|
9964
|
+
const outcome = outcomes.get(message.id);
|
|
9965
|
+
if (!outcome) {
|
|
9966
|
+
continue;
|
|
9967
|
+
}
|
|
9968
|
+
if (outcome.imported) {
|
|
9969
|
+
cleanMessageTextParts(message);
|
|
9970
|
+
changed = true;
|
|
9971
|
+
}
|
|
9972
|
+
if (outcome.failed) {
|
|
9973
|
+
changed = appendImportedMediaImportFailureNotice(message) || changed;
|
|
9974
|
+
}
|
|
9975
|
+
}
|
|
9976
|
+
if (changed) {
|
|
9977
|
+
await input.store.writeSnapshot(input.conversationId, nextSnapshot);
|
|
9978
|
+
}
|
|
9979
|
+
}
|
|
9980
|
+
function collectMessageMediaReferences(message) {
|
|
9981
|
+
return message.parts.flatMap(
|
|
9982
|
+
(part) => part.type === "text" && part.text ? collectMediaTags(part.text) : []
|
|
9983
|
+
);
|
|
9984
|
+
}
|
|
9985
|
+
function appendImportedMediaImportFailureNotice(message) {
|
|
9986
|
+
const hermes = toRecord8(message.hermes);
|
|
9987
|
+
if (hermes.media_import_failure_notice_appended === true) {
|
|
9988
|
+
return false;
|
|
9989
|
+
}
|
|
9990
|
+
const failures = readMediaImportFailures(message);
|
|
9991
|
+
if (failures.length === 0) {
|
|
9992
|
+
return false;
|
|
9993
|
+
}
|
|
9994
|
+
const notice = formatImportedMediaImportFailureNotice(failures);
|
|
9995
|
+
const textPart = message.parts.find((part) => part.type === "text");
|
|
9996
|
+
if (textPart) {
|
|
9997
|
+
const currentText = textPart.text ?? "";
|
|
9998
|
+
const separator = currentText.trim().length > 0 ? "\n\n" : "";
|
|
9999
|
+
textPart.text = `${currentText.trimEnd()}${separator}${notice}`;
|
|
10000
|
+
} else {
|
|
10001
|
+
message.parts.unshift({ type: "text", text: notice });
|
|
10002
|
+
}
|
|
10003
|
+
message.hermes = {
|
|
10004
|
+
...hermes,
|
|
10005
|
+
media_import_failure_notice_appended: true
|
|
10006
|
+
};
|
|
10007
|
+
return true;
|
|
10008
|
+
}
|
|
10009
|
+
function formatImportedMediaImportFailureNotice(failures) {
|
|
10010
|
+
const filenames = failures.map((failure) => failure.filename);
|
|
10011
|
+
const target = filenames.length === 1 ? `\u6587\u4EF6\u201C${filenames[0]}\u201D` : `${filenames.length} \u4E2A\u6587\u4EF6\uFF08${formatImportedFilenameList(filenames)}\uFF09`;
|
|
10012
|
+
const permissionDenied = failures.some((failure) => {
|
|
10013
|
+
const code = failure.code?.toUpperCase();
|
|
10014
|
+
return code === "EPERM" || code === "EACCES" || /operation not permitted|permission denied/iu.test(failure.reason);
|
|
10015
|
+
});
|
|
10016
|
+
if (permissionDenied) {
|
|
10017
|
+
return `${target}\u6CA1\u80FD\u4F5C\u4E3A\u9644\u4EF6\u5BFC\u5165\uFF1AHermes Link \u8BFB\u53D6\u6587\u4EF6\u65F6\u88AB macOS \u62D2\u7EDD\u4E86\u3002\u8BF7\u5728\u201C\u7CFB\u7EDF\u8BBE\u7F6E > \u9690\u79C1\u4E0E\u5B89\u5168\u6027 > \u5B8C\u5168\u78C1\u76D8\u8BBF\u95EE\u6743\u9650\u201D\u91CC\u7ED9\u8FD0\u884C Link \u7684\u7EC8\u7AEF\u6216 Node \u6388\u6743\u540E\u91CD\u8BD5\u3002`;
|
|
10018
|
+
}
|
|
10019
|
+
return `${target}\u6CA1\u80FD\u4F5C\u4E3A\u9644\u4EF6\u5BFC\u5165\uFF1AHermes Link \u8BFB\u53D6\u672C\u673A\u6587\u4EF6\u5931\u8D25\u4E86\uFF0C\u8BF7\u786E\u8BA4\u6587\u4EF6\u8FD8\u5728\u539F\u4F4D\u7F6E\u5E76\u7A0D\u540E\u91CD\u8BD5\u3002`;
|
|
10020
|
+
}
|
|
10021
|
+
function formatImportedFilenameList(filenames) {
|
|
10022
|
+
const preview = filenames.slice(0, 3).map((filename) => `\u201C${filename}\u201D`);
|
|
10023
|
+
const remaining = filenames.length - preview.length;
|
|
10024
|
+
return remaining > 0 ? `${preview.join("\u3001")} \u7B49 ${filenames.length} \u4E2A` : preview.join("\u3001");
|
|
10025
|
+
}
|
|
10026
|
+
function toLinkMessage(input) {
|
|
10027
|
+
const role = normalizeMessageRole(input.message.role);
|
|
10028
|
+
const text = normalizeContent(input.message.content);
|
|
10029
|
+
const createdAt = isoFromHermesTime(input.message.timestamp) ?? new Date(Date.now() + input.index).toISOString();
|
|
10030
|
+
return {
|
|
10031
|
+
id: `msg_${randomUUID6().replaceAll("-", "")}`,
|
|
10032
|
+
schema_version: 1,
|
|
10033
|
+
conversation_id: input.conversationId,
|
|
10034
|
+
role,
|
|
10035
|
+
status: "completed",
|
|
10036
|
+
created_at: createdAt,
|
|
10037
|
+
updated_at: createdAt,
|
|
10038
|
+
sender: senderForRole({
|
|
10039
|
+
role,
|
|
10040
|
+
profileName: input.profileName,
|
|
10041
|
+
profileUid: input.profileUid,
|
|
10042
|
+
profileDisplayName: input.profileDisplayName
|
|
10043
|
+
}),
|
|
10044
|
+
parts: text ? [{ type: "text", text }] : [],
|
|
10045
|
+
attachments: [],
|
|
10046
|
+
hermes: {
|
|
10047
|
+
session_id: input.sessionId,
|
|
10048
|
+
message_id: input.message.id,
|
|
10049
|
+
imported_from: "hermes",
|
|
10050
|
+
import_projection: HERMES_IMPORT_PROJECTION_VERSION
|
|
10051
|
+
},
|
|
10052
|
+
raw: {
|
|
10053
|
+
format: "hermes-message",
|
|
10054
|
+
payload: input.message
|
|
10055
|
+
}
|
|
10056
|
+
};
|
|
10057
|
+
}
|
|
10058
|
+
function senderForRole(input) {
|
|
10059
|
+
switch (input.role) {
|
|
10060
|
+
case "user":
|
|
10061
|
+
return { id: "hermes_user", type: "human", display_name: "Me" };
|
|
10062
|
+
case "assistant":
|
|
10063
|
+
return {
|
|
10064
|
+
id: `agent_${input.profileName}`,
|
|
10065
|
+
type: "agent",
|
|
10066
|
+
display_name: input.profileDisplayName,
|
|
10067
|
+
profile_uid: input.profileUid,
|
|
10068
|
+
profile: input.profileName
|
|
10069
|
+
};
|
|
10070
|
+
case "tool":
|
|
10071
|
+
return { id: "hermes_tool", type: "tool", display_name: "Tool" };
|
|
10072
|
+
case "system":
|
|
10073
|
+
return { id: "hermes_system", type: "system", display_name: "System" };
|
|
10074
|
+
}
|
|
10075
|
+
}
|
|
10076
|
+
function firstUserText(snapshot) {
|
|
10077
|
+
return snapshot.messages.find((message) => message.role === "user")?.parts.find((part) => part.type === "text")?.text?.slice(0, 80);
|
|
10078
|
+
}
|
|
10079
|
+
function normalizeTitle(value) {
|
|
10080
|
+
const normalized = value?.replace(/\s+/gu, " ").trim();
|
|
10081
|
+
return normalized || DEFAULT_CONVERSATION_TITLE;
|
|
10082
|
+
}
|
|
10083
|
+
function normalizeMessageRole(value) {
|
|
10084
|
+
switch (value?.trim().toLowerCase()) {
|
|
10085
|
+
case "user":
|
|
10086
|
+
return "user";
|
|
10087
|
+
case "assistant":
|
|
10088
|
+
return "assistant";
|
|
10089
|
+
case "tool":
|
|
10090
|
+
return "tool";
|
|
10091
|
+
case "system":
|
|
10092
|
+
return "system";
|
|
10093
|
+
default:
|
|
10094
|
+
return "system";
|
|
10095
|
+
}
|
|
10096
|
+
}
|
|
10097
|
+
function normalizeContent(value) {
|
|
10098
|
+
if (typeof value === "string") {
|
|
10099
|
+
return value;
|
|
9872
10100
|
}
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
let lastEventSeq;
|
|
9884
|
-
if (importedParts.length > 0) {
|
|
9885
|
-
const event = await deps.appendEvent(input.conversationId, {
|
|
9886
|
-
type: "message.parts.created",
|
|
9887
|
-
message_id: input.messageId,
|
|
9888
|
-
run_id: input.runId,
|
|
9889
|
-
payload: { parts: importedParts }
|
|
9890
|
-
});
|
|
9891
|
-
lastEventSeq = event.seq;
|
|
10101
|
+
if (Array.isArray(value)) {
|
|
10102
|
+
return value.map((item) => {
|
|
10103
|
+
if (typeof item === "string") {
|
|
10104
|
+
return item;
|
|
10105
|
+
}
|
|
10106
|
+
if (typeof item === "object" && item !== null) {
|
|
10107
|
+
return readString9(item, "text") ?? "";
|
|
10108
|
+
}
|
|
10109
|
+
return "";
|
|
10110
|
+
}).filter(Boolean).join("");
|
|
9892
10111
|
}
|
|
9893
|
-
return
|
|
9894
|
-
conversation_id: input.conversationId,
|
|
9895
|
-
run_id: input.runId,
|
|
9896
|
-
message_id: input.messageId,
|
|
9897
|
-
discovered_count: references.length,
|
|
9898
|
-
imported_count: importedParts.length,
|
|
9899
|
-
skipped_count: skippedCount,
|
|
9900
|
-
failed_count: newFailures.length,
|
|
9901
|
-
parts: importedParts,
|
|
9902
|
-
...lastEventSeq ? { last_event_seq: lastEventSeq } : {}
|
|
9903
|
-
};
|
|
10112
|
+
return "";
|
|
9904
10113
|
}
|
|
9905
|
-
function
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
10114
|
+
function parseJsonValue(value) {
|
|
10115
|
+
if (typeof value !== "string") {
|
|
10116
|
+
return void 0;
|
|
10117
|
+
}
|
|
10118
|
+
const trimmed = value.trim();
|
|
10119
|
+
if (!trimmed) {
|
|
10120
|
+
return void 0;
|
|
10121
|
+
}
|
|
10122
|
+
try {
|
|
10123
|
+
return JSON.parse(trimmed);
|
|
10124
|
+
} catch {
|
|
10125
|
+
return void 0;
|
|
9910
10126
|
}
|
|
9911
|
-
return failures.flatMap((item) => {
|
|
9912
|
-
const record = toRecord8(item);
|
|
9913
|
-
const key = readString9(record, "key");
|
|
9914
|
-
const filename = readString9(record, "filename");
|
|
9915
|
-
const reason = readString9(record, "reason");
|
|
9916
|
-
if (!key || !filename || !reason) {
|
|
9917
|
-
return [];
|
|
9918
|
-
}
|
|
9919
|
-
return [
|
|
9920
|
-
{
|
|
9921
|
-
key,
|
|
9922
|
-
filename,
|
|
9923
|
-
reason,
|
|
9924
|
-
...readString9(record, "code") ? { code: readString9(record, "code") } : {}
|
|
9925
|
-
}
|
|
9926
|
-
];
|
|
9927
|
-
});
|
|
9928
10127
|
}
|
|
9929
|
-
function
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
10128
|
+
function toRecord8(value) {
|
|
10129
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
10130
|
+
}
|
|
10131
|
+
function isDeletedSession(session) {
|
|
10132
|
+
return readBoolean(session.deleted) || readBoolean(session.is_deleted) || Boolean(readString9(session, "deleted_at")) || ["deleted", "removed"].includes(readString9(session, "status") ?? "");
|
|
10133
|
+
}
|
|
10134
|
+
function isHiddenSession(session) {
|
|
10135
|
+
const source = readString9(session, "source")?.toLowerCase();
|
|
10136
|
+
const status = readString9(session, "status")?.toLowerCase();
|
|
10137
|
+
const visibility = readString9(session, "visibility")?.toLowerCase();
|
|
10138
|
+
return Boolean(source && HIDDEN_SESSION_SOURCES.has(source)) || readBoolean(session.hidden) || readBoolean(session.archived) || Boolean(readString9(session, "archived_at")) || status === "hidden" || status === "archived" || visibility === "hidden" || visibility === "hide";
|
|
10139
|
+
}
|
|
10140
|
+
function readTableColumns(db, tableName) {
|
|
10141
|
+
try {
|
|
10142
|
+
const rows = db.prepare(`PRAGMA table_info(${quoteIdentifier(tableName)})`).all();
|
|
10143
|
+
return new Set(
|
|
10144
|
+
rows.map((row) => typeof row.name === "string" ? row.name : "").filter(Boolean)
|
|
10145
|
+
);
|
|
10146
|
+
} catch {
|
|
9933
10147
|
return /* @__PURE__ */ new Set();
|
|
9934
10148
|
}
|
|
9935
|
-
return new Set(
|
|
9936
|
-
keys.filter(
|
|
9937
|
-
(key) => typeof key === "string" && key.length > 0
|
|
9938
|
-
)
|
|
9939
|
-
);
|
|
9940
10149
|
}
|
|
9941
|
-
function
|
|
9942
|
-
return {
|
|
9943
|
-
conversation_id: input.conversationId,
|
|
9944
|
-
run_id: input.runId,
|
|
9945
|
-
message_id: input.messageId,
|
|
9946
|
-
discovered_count: 0,
|
|
9947
|
-
imported_count: 0,
|
|
9948
|
-
skipped_count: 0,
|
|
9949
|
-
failed_count: 0,
|
|
9950
|
-
parts: []
|
|
9951
|
-
};
|
|
10150
|
+
function quoteIdentifier(value) {
|
|
10151
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
9952
10152
|
}
|
|
9953
|
-
async function
|
|
9954
|
-
|
|
9955
|
-
const fileStat = await stat8(sourcePath).catch((error) => {
|
|
10153
|
+
async function isFile(filePath) {
|
|
10154
|
+
return stat8(filePath).then((value) => value.isFile()).catch((error) => {
|
|
9956
10155
|
if (isNodeError10(error, "ENOENT")) {
|
|
9957
|
-
|
|
9958
|
-
404,
|
|
9959
|
-
"media_source_not_found",
|
|
9960
|
-
"Hermes output file was not found"
|
|
9961
|
-
);
|
|
10156
|
+
return false;
|
|
9962
10157
|
}
|
|
9963
10158
|
throw error;
|
|
9964
10159
|
});
|
|
9965
|
-
if (!fileStat.isFile()) {
|
|
9966
|
-
throw new LinkHttpError(
|
|
9967
|
-
400,
|
|
9968
|
-
"media_source_not_file",
|
|
9969
|
-
"Hermes output media source is not a file"
|
|
9970
|
-
);
|
|
9971
|
-
}
|
|
9972
|
-
if (fileStat.size > MAX_IMPORTED_BLOB_BYTES) {
|
|
9973
|
-
throw new LinkHttpError(
|
|
9974
|
-
413,
|
|
9975
|
-
"media_source_too_large",
|
|
9976
|
-
"Hermes output media source is too large"
|
|
9977
|
-
);
|
|
9978
|
-
}
|
|
9979
|
-
return deps.writeBlob(conversationId, {
|
|
9980
|
-
bytes: await readFile9(sourcePath),
|
|
9981
|
-
filename: path14.basename(sourcePath),
|
|
9982
|
-
mime: source.mime ?? inferMimeType(sourcePath)
|
|
9983
|
-
});
|
|
9984
10160
|
}
|
|
9985
|
-
function
|
|
9986
|
-
return {
|
|
9987
|
-
key: sourceKey,
|
|
9988
|
-
filename: sanitizeFilename(reference.path, "attachment"),
|
|
9989
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
9990
|
-
...isNodeError10(error) && error.code ? { code: error.code } : {}
|
|
9991
|
-
};
|
|
10161
|
+
function createConversationId() {
|
|
10162
|
+
return `conv_${randomUUID6().replaceAll("-", "")}`;
|
|
9992
10163
|
}
|
|
9993
|
-
function
|
|
9994
|
-
|
|
10164
|
+
function isoFromHermesTime(value) {
|
|
10165
|
+
const numeric = readNumber2(value);
|
|
10166
|
+
if (!numeric || numeric <= 0) {
|
|
10167
|
+
return void 0;
|
|
10168
|
+
}
|
|
10169
|
+
const millis = numeric > 1e10 ? numeric : numeric * 1e3;
|
|
10170
|
+
return new Date(millis).toISOString();
|
|
9995
10171
|
}
|
|
9996
10172
|
function readString9(payload, key) {
|
|
9997
10173
|
const value = payload[key];
|
|
9998
10174
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
9999
10175
|
}
|
|
10000
|
-
function
|
|
10001
|
-
return typeof value === "
|
|
10176
|
+
function readNumber2(value) {
|
|
10177
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
10178
|
+
}
|
|
10179
|
+
function readBoolean(value) {
|
|
10180
|
+
if (value === true || value === 1) {
|
|
10181
|
+
return true;
|
|
10182
|
+
}
|
|
10183
|
+
if (typeof value === "string") {
|
|
10184
|
+
return ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
|
|
10185
|
+
}
|
|
10186
|
+
return false;
|
|
10002
10187
|
}
|
|
10003
10188
|
function isNodeError10(error, code) {
|
|
10004
|
-
return typeof error === "object" && error !== null && "code" in error &&
|
|
10189
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
10005
10190
|
}
|
|
10006
10191
|
|
|
10007
10192
|
// src/conversations/run-lifecycle.ts
|
|
@@ -11278,8 +11463,9 @@ function readChatCompletionUsage(payload) {
|
|
|
11278
11463
|
const input = readInteger2(usage, "prompt_tokens") ?? readInteger2(usage, "input_tokens");
|
|
11279
11464
|
const output = readInteger2(usage, "completion_tokens") ?? readInteger2(usage, "output_tokens");
|
|
11280
11465
|
const total = readInteger2(usage, "total_tokens");
|
|
11281
|
-
const contextWindow = readInteger2(usage, "context_window") ?? readInteger2(usage, "context_max");
|
|
11466
|
+
const contextWindow = readInteger2(usage, "context_window") ?? readInteger2(usage, "context_max") ?? readInteger2(usage, "context_length");
|
|
11282
11467
|
const explicitContextTokens = readInteger2(usage, "context_tokens") ?? readInteger2(usage, "context_used") ?? readInteger2(usage, "current_context_tokens") ?? readInteger2(usage, "last_prompt_tokens");
|
|
11468
|
+
const explicitUsagePercent = readInteger2(usage, "usage_percent") ?? readInteger2(usage, "context_percent");
|
|
11283
11469
|
if (input === void 0 && output === void 0 && total === void 0) {
|
|
11284
11470
|
return void 0;
|
|
11285
11471
|
}
|
|
@@ -11290,7 +11476,7 @@ function readChatCompletionUsage(payload) {
|
|
|
11290
11476
|
...explicitContextTokens !== void 0 ? { context_tokens: explicitContextTokens } : {},
|
|
11291
11477
|
...contextWindow !== void 0 ? { context_window: contextWindow } : {},
|
|
11292
11478
|
...explicitContextTokens !== void 0 && contextWindow ? {
|
|
11293
|
-
usage_percent: Math.min(
|
|
11479
|
+
usage_percent: explicitUsagePercent !== void 0 ? Math.min(100, explicitUsagePercent) : Math.min(
|
|
11294
11480
|
100,
|
|
11295
11481
|
Math.round(explicitContextTokens / contextWindow * 100)
|
|
11296
11482
|
)
|
|
@@ -11437,10 +11623,20 @@ var ConversationRunLifecycle = class {
|
|
|
11437
11623
|
});
|
|
11438
11624
|
return void 0;
|
|
11439
11625
|
});
|
|
11626
|
+
const instructions = buildRunInstructions(run, deliveryStagingDir);
|
|
11627
|
+
const estimatedUsage = estimateContextUsage({
|
|
11628
|
+
conversationHistory: conversationHistory.messages,
|
|
11629
|
+
currentInput: resolvedInput,
|
|
11630
|
+
instructions,
|
|
11631
|
+
contextWindow: run.context_window
|
|
11632
|
+
});
|
|
11633
|
+
if (estimatedUsage) {
|
|
11634
|
+
await this.updateRun(conversationId, runId, { usage: estimatedUsage });
|
|
11635
|
+
}
|
|
11440
11636
|
const response = await streamHermesResponses(
|
|
11441
11637
|
{
|
|
11442
11638
|
input: resolvedInput,
|
|
11443
|
-
instructions
|
|
11639
|
+
instructions,
|
|
11444
11640
|
session_id: hermesSessionId,
|
|
11445
11641
|
model: run.model,
|
|
11446
11642
|
...previousResponseId ? { previous_response_id: previousResponseId } : {},
|
|
@@ -11943,7 +12139,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
11943
12139
|
run.hermes_response_id = responseId;
|
|
11944
12140
|
}
|
|
11945
12141
|
if (usage) {
|
|
11946
|
-
run.usage = usage;
|
|
12142
|
+
run.usage = mergeRunUsage(run.usage, usage);
|
|
11947
12143
|
}
|
|
11948
12144
|
if (assistant) {
|
|
11949
12145
|
assistant.status = "completed";
|
|
@@ -11995,7 +12191,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
11995
12191
|
const visibleMessage = formatFailureMessage(message, run.error_detail);
|
|
11996
12192
|
const usage = readUsage(source?.payload);
|
|
11997
12193
|
if (usage) {
|
|
11998
|
-
run.usage = usage;
|
|
12194
|
+
run.usage = mergeRunUsage(run.usage, usage);
|
|
11999
12195
|
}
|
|
12000
12196
|
const assistant = snapshot.messages.find(
|
|
12001
12197
|
(item) => item.id === run.assistant_message_id
|
|
@@ -12014,6 +12210,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
12014
12210
|
}
|
|
12015
12211
|
}
|
|
12016
12212
|
await this.deps.writeSnapshot(conversationId, snapshot);
|
|
12213
|
+
const contextUsage = contextUsagePayload(run);
|
|
12017
12214
|
await this.deps.appendEvent(conversationId, {
|
|
12018
12215
|
type: "run.failed",
|
|
12019
12216
|
message_id: assistant?.id,
|
|
@@ -12022,6 +12219,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
12022
12219
|
error: { message },
|
|
12023
12220
|
...run.error_detail ? { error_detail: run.error_detail } : {},
|
|
12024
12221
|
run,
|
|
12222
|
+
...contextUsage ? { context: contextUsage } : {},
|
|
12025
12223
|
...source ? { hermes: source.payload } : {}
|
|
12026
12224
|
},
|
|
12027
12225
|
...source ? { raw: { format: "hermes-run-event", payload: source.rawPayload } } : {}
|
|
@@ -12353,6 +12551,7 @@ function contextUsagePayload(run) {
|
|
|
12353
12551
|
source: "unknown"
|
|
12354
12552
|
};
|
|
12355
12553
|
}
|
|
12554
|
+
const contextSource = usage.context_source ?? "explicit";
|
|
12356
12555
|
return {
|
|
12357
12556
|
input_tokens: contextTokens,
|
|
12358
12557
|
output_tokens: usage?.output_tokens ?? 0,
|
|
@@ -12360,7 +12559,7 @@ function contextUsagePayload(run) {
|
|
|
12360
12559
|
...contextWindow ? { context_window: contextWindow } : {},
|
|
12361
12560
|
used_tokens: contextTokens,
|
|
12362
12561
|
...contextWindow ? { window_tokens: contextWindow } : {},
|
|
12363
|
-
source:
|
|
12562
|
+
source: contextSource,
|
|
12364
12563
|
...usage?.usage_percent !== void 0 ? { usage_percent: usage.usage_percent } : contextWindow ? {
|
|
12365
12564
|
usage_percent: Math.min(
|
|
12366
12565
|
100,
|
|
@@ -12369,6 +12568,41 @@ function contextUsagePayload(run) {
|
|
|
12369
12568
|
} : {}
|
|
12370
12569
|
};
|
|
12371
12570
|
}
|
|
12571
|
+
function mergeRunUsage(previous, next) {
|
|
12572
|
+
const nextContextWindow = next.context_window ?? previous?.context_window;
|
|
12573
|
+
const nextContextTokens = next.context_tokens ?? refineEstimatedContextTokens(
|
|
12574
|
+
previous,
|
|
12575
|
+
next.input_tokens,
|
|
12576
|
+
nextContextWindow
|
|
12577
|
+
);
|
|
12578
|
+
const nextContextSource = next.context_tokens !== void 0 ? next.context_source ?? "explicit" : nextContextTokens !== void 0 ? previous?.context_source : void 0;
|
|
12579
|
+
return {
|
|
12580
|
+
...next,
|
|
12581
|
+
...nextContextTokens !== void 0 ? { context_tokens: nextContextTokens } : {},
|
|
12582
|
+
...nextContextWindow !== void 0 ? { context_window: nextContextWindow } : {},
|
|
12583
|
+
...nextContextSource ? { context_source: nextContextSource } : {},
|
|
12584
|
+
...nextContextTokens !== void 0 && nextContextWindow ? {
|
|
12585
|
+
usage_percent: next.usage_percent ?? previous?.usage_percent ?? Math.min(
|
|
12586
|
+
100,
|
|
12587
|
+
Math.round(nextContextTokens / nextContextWindow * 100)
|
|
12588
|
+
)
|
|
12589
|
+
} : {}
|
|
12590
|
+
};
|
|
12591
|
+
}
|
|
12592
|
+
function refineEstimatedContextTokens(previous, inputTokens, contextWindow) {
|
|
12593
|
+
if (previous?.context_source !== "estimated") {
|
|
12594
|
+
return void 0;
|
|
12595
|
+
}
|
|
12596
|
+
const currentEstimate = previous.context_tokens;
|
|
12597
|
+
if (currentEstimate === void 0) {
|
|
12598
|
+
return void 0;
|
|
12599
|
+
}
|
|
12600
|
+
const upperBound = inputTokens > 0 ? contextWindow ? Math.min(inputTokens, contextWindow) : inputTokens : contextWindow;
|
|
12601
|
+
if (upperBound === void 0) {
|
|
12602
|
+
return currentEstimate;
|
|
12603
|
+
}
|
|
12604
|
+
return Math.min(currentEstimate, upperBound);
|
|
12605
|
+
}
|
|
12372
12606
|
function findPreviousHermesResponseId(snapshot, run) {
|
|
12373
12607
|
const currentProfile = normalizeRunProfileForCompare(run.profile);
|
|
12374
12608
|
if (!currentProfile) {
|
|
@@ -13972,13 +14206,84 @@ function isLanHost(hostname) {
|
|
|
13972
14206
|
}
|
|
13973
14207
|
|
|
13974
14208
|
// src/http/sse.ts
|
|
14209
|
+
var DEFAULT_SSE_RETRY_MS = 1e3;
|
|
14210
|
+
var DEFAULT_SSE_HEARTBEAT_MS = 15e3;
|
|
14211
|
+
function beginSseStream(request, response, options = {}) {
|
|
14212
|
+
const retryMs = normalizeRetryMs(options.retryMs);
|
|
14213
|
+
const heartbeatMs = Math.max(1e3, options.heartbeatMs ?? DEFAULT_SSE_HEARTBEAT_MS);
|
|
14214
|
+
response.statusCode = 200;
|
|
14215
|
+
response.setHeader("content-type", "text/event-stream; charset=utf-8");
|
|
14216
|
+
response.setHeader("cache-control", "no-store");
|
|
14217
|
+
response.setHeader("connection", "keep-alive");
|
|
14218
|
+
response.flushHeaders();
|
|
14219
|
+
writeSseRetry(response, retryMs);
|
|
14220
|
+
writeSseComment(response, options.initialComment ?? "connected");
|
|
14221
|
+
let closed = false;
|
|
14222
|
+
let heartbeat = null;
|
|
14223
|
+
const cleanup = () => {
|
|
14224
|
+
if (closed) {
|
|
14225
|
+
return;
|
|
14226
|
+
}
|
|
14227
|
+
closed = true;
|
|
14228
|
+
if (heartbeat != null) {
|
|
14229
|
+
clearInterval(heartbeat);
|
|
14230
|
+
heartbeat = null;
|
|
14231
|
+
}
|
|
14232
|
+
request.off("close", cleanup);
|
|
14233
|
+
response.off("close", cleanup);
|
|
14234
|
+
options.onClose?.();
|
|
14235
|
+
if (!response.writableEnded && !response.destroyed) {
|
|
14236
|
+
response.end();
|
|
14237
|
+
}
|
|
14238
|
+
};
|
|
14239
|
+
heartbeat = setInterval(() => {
|
|
14240
|
+
if (response.writableEnded || response.destroyed) {
|
|
14241
|
+
cleanup();
|
|
14242
|
+
return;
|
|
14243
|
+
}
|
|
14244
|
+
writeSseComment(response);
|
|
14245
|
+
}, heartbeatMs);
|
|
14246
|
+
heartbeat.unref();
|
|
14247
|
+
request.once("close", cleanup);
|
|
14248
|
+
response.once("close", cleanup);
|
|
14249
|
+
return cleanup;
|
|
14250
|
+
}
|
|
13975
14251
|
function writeSseEvent(response, event) {
|
|
13976
|
-
response
|
|
14252
|
+
writeJsonSseEvent(response, {
|
|
14253
|
+
event: event.type,
|
|
14254
|
+
data: event,
|
|
14255
|
+
id: event.seq
|
|
14256
|
+
});
|
|
14257
|
+
}
|
|
14258
|
+
function writeJsonSseEvent(response, event) {
|
|
14259
|
+
if (event.retryMs != null) {
|
|
14260
|
+
response.write(`retry: ${normalizeRetryMs(event.retryMs)}
|
|
14261
|
+
`);
|
|
14262
|
+
}
|
|
14263
|
+
if (event.id != null && event.id !== "") {
|
|
14264
|
+
response.write(`id: ${event.id}
|
|
14265
|
+
`);
|
|
14266
|
+
}
|
|
14267
|
+
response.write(`event: ${event.event}
|
|
14268
|
+
`);
|
|
14269
|
+
response.write(`data: ${JSON.stringify(event.data)}
|
|
14270
|
+
|
|
14271
|
+
`);
|
|
14272
|
+
}
|
|
14273
|
+
function writeSseComment(response, comment = "keep-alive") {
|
|
14274
|
+
response.write(`: ${comment}
|
|
14275
|
+
|
|
13977
14276
|
`);
|
|
13978
|
-
|
|
14277
|
+
}
|
|
14278
|
+
function writeSseRetry(response, retryMs) {
|
|
14279
|
+
response.write(`retry: ${normalizeRetryMs(retryMs)}
|
|
13979
14280
|
|
|
13980
14281
|
`);
|
|
13981
14282
|
}
|
|
14283
|
+
function normalizeRetryMs(retryMs) {
|
|
14284
|
+
const parsed = Number.isFinite(retryMs) ? Math.trunc(retryMs) : DEFAULT_SSE_RETRY_MS;
|
|
14285
|
+
return parsed >= 0 ? parsed : DEFAULT_SSE_RETRY_MS;
|
|
14286
|
+
}
|
|
13982
14287
|
|
|
13983
14288
|
// src/http/routes/conversations.ts
|
|
13984
14289
|
function registerConversationRoutes(router, options) {
|
|
@@ -14022,49 +14327,44 @@ function registerConversationRoutes(router, options) {
|
|
|
14022
14327
|
const notificationOnly = mode === "notifications";
|
|
14023
14328
|
ctx.respond = false;
|
|
14024
14329
|
const response = ctx.res;
|
|
14025
|
-
|
|
14026
|
-
|
|
14027
|
-
|
|
14028
|
-
|
|
14029
|
-
|
|
14330
|
+
let unsubscribe = () => {
|
|
14331
|
+
};
|
|
14332
|
+
beginSseStream(ctx.req, response, {
|
|
14333
|
+
onClose: () => unsubscribe()
|
|
14334
|
+
});
|
|
14335
|
+
unsubscribe = conversations.subscribeAll((event) => {
|
|
14030
14336
|
if (notificationOnly && !isConversationNotificationEvent(event)) {
|
|
14031
14337
|
return;
|
|
14032
14338
|
}
|
|
14033
14339
|
writeSseEvent(response, event);
|
|
14034
14340
|
});
|
|
14035
|
-
const cleanup = () => {
|
|
14036
|
-
unsubscribe();
|
|
14037
|
-
response.end();
|
|
14038
|
-
};
|
|
14039
|
-
ctx.req.on("close", cleanup);
|
|
14040
14341
|
});
|
|
14041
14342
|
router.get("/api/v1/conversations/:conversationId/events", async (ctx) => {
|
|
14042
14343
|
await authenticateRequest(ctx, paths);
|
|
14043
|
-
const after =
|
|
14344
|
+
const after = resolveConversationEventCursor({
|
|
14345
|
+
queryAfter: ctx.query.after,
|
|
14346
|
+
lastEventIdHeader: ctx.req.headers["last-event-id"]
|
|
14347
|
+
});
|
|
14044
14348
|
const history = await conversations.listEvents(
|
|
14045
14349
|
ctx.params.conversationId,
|
|
14046
14350
|
after
|
|
14047
14351
|
);
|
|
14048
14352
|
ctx.respond = false;
|
|
14049
14353
|
const response = ctx.res;
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14354
|
+
let unsubscribe = () => {
|
|
14355
|
+
};
|
|
14356
|
+
beginSseStream(ctx.req, response, {
|
|
14357
|
+
onClose: () => unsubscribe()
|
|
14358
|
+
});
|
|
14054
14359
|
for (const event of history) {
|
|
14055
14360
|
writeSseEvent(response, event);
|
|
14056
14361
|
}
|
|
14057
|
-
|
|
14362
|
+
unsubscribe = conversations.subscribe(
|
|
14058
14363
|
ctx.params.conversationId,
|
|
14059
14364
|
(event) => {
|
|
14060
14365
|
writeSseEvent(response, event);
|
|
14061
14366
|
}
|
|
14062
14367
|
);
|
|
14063
|
-
const cleanup = () => {
|
|
14064
|
-
unsubscribe();
|
|
14065
|
-
response.end();
|
|
14066
|
-
};
|
|
14067
|
-
ctx.req.on("close", cleanup);
|
|
14068
14368
|
});
|
|
14069
14369
|
router.post("/api/v1/conversations/:conversationId/messages", async (ctx) => {
|
|
14070
14370
|
await authenticateRequest(ctx, paths);
|
|
@@ -14267,6 +14567,19 @@ function registerConversationRoutes(router, options) {
|
|
|
14267
14567
|
}
|
|
14268
14568
|
);
|
|
14269
14569
|
}
|
|
14570
|
+
function resolveConversationEventCursor(input) {
|
|
14571
|
+
const queryAfter = readInteger3(input.queryAfter) ?? 0;
|
|
14572
|
+
const headerAfter = readNonNegativeIntegerHeader(input.lastEventIdHeader) ?? 0;
|
|
14573
|
+
return Math.max(queryAfter, headerAfter);
|
|
14574
|
+
}
|
|
14575
|
+
function readNonNegativeIntegerHeader(value) {
|
|
14576
|
+
const raw = Array.isArray(value) ? value[0] : value;
|
|
14577
|
+
if (!raw) {
|
|
14578
|
+
return null;
|
|
14579
|
+
}
|
|
14580
|
+
const parsed = Number.parseInt(raw, 10);
|
|
14581
|
+
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null;
|
|
14582
|
+
}
|
|
14270
14583
|
function contentDispositionInline(filename) {
|
|
14271
14584
|
const fallback = asciiFilenameFallback(filename);
|
|
14272
14585
|
return `inline; filename="${fallback}"; filename*=UTF-8''${encodeRfc5987Value(filename)}`;
|
|
@@ -18260,22 +18573,18 @@ function registerProfileRoutes(router, options) {
|
|
|
18260
18573
|
await authenticateRequest(ctx, paths);
|
|
18261
18574
|
ctx.respond = false;
|
|
18262
18575
|
const response = ctx.res;
|
|
18263
|
-
|
|
18264
|
-
|
|
18265
|
-
|
|
18266
|
-
|
|
18576
|
+
let unsubscribe = () => {
|
|
18577
|
+
};
|
|
18578
|
+
beginSseStream(ctx.req, response, {
|
|
18579
|
+
onClose: () => unsubscribe()
|
|
18580
|
+
});
|
|
18267
18581
|
writeProfileCreationSseEvent(
|
|
18268
18582
|
response,
|
|
18269
18583
|
await readHermesProfileCreationStatus(paths)
|
|
18270
18584
|
);
|
|
18271
|
-
|
|
18585
|
+
unsubscribe = subscribeHermesProfileCreationStatus((status) => {
|
|
18272
18586
|
writeProfileCreationSseEvent(response, status);
|
|
18273
18587
|
});
|
|
18274
|
-
const cleanup = () => {
|
|
18275
|
-
unsubscribe();
|
|
18276
|
-
response.end();
|
|
18277
|
-
};
|
|
18278
|
-
ctx.req.on("close", cleanup);
|
|
18279
18588
|
});
|
|
18280
18589
|
router.get("/api/v1/profiles/:name/status", async (ctx) => {
|
|
18281
18590
|
await authenticateRequest(ctx, paths);
|
|
@@ -18491,10 +18800,10 @@ function isProfileAvatarUrl(value) {
|
|
|
18491
18800
|
return isHttpUrl(value) || /^data:image\/[a-z0-9.+-]+;base64,/iu.test(value);
|
|
18492
18801
|
}
|
|
18493
18802
|
function writeProfileCreationSseEvent(response, status) {
|
|
18494
|
-
response
|
|
18495
|
-
|
|
18496
|
-
|
|
18497
|
-
|
|
18803
|
+
writeJsonSseEvent(response, {
|
|
18804
|
+
event: "profile.creation.status",
|
|
18805
|
+
data: status
|
|
18806
|
+
});
|
|
18498
18807
|
}
|
|
18499
18808
|
|
|
18500
18809
|
// src/http/routes/runs.ts
|
|
@@ -21646,26 +21955,22 @@ function registerHermesUpdateRoutes(router, options) {
|
|
|
21646
21955
|
await authenticateRequest(ctx, paths);
|
|
21647
21956
|
ctx.respond = false;
|
|
21648
21957
|
const response = ctx.res;
|
|
21649
|
-
|
|
21650
|
-
|
|
21651
|
-
|
|
21652
|
-
|
|
21958
|
+
let unsubscribe = () => {
|
|
21959
|
+
};
|
|
21960
|
+
beginSseStream(ctx.req, response, {
|
|
21961
|
+
onClose: () => unsubscribe()
|
|
21962
|
+
});
|
|
21653
21963
|
writeUpdateSseEvent(response, await readHermesUpdateStatus(paths));
|
|
21654
|
-
|
|
21964
|
+
unsubscribe = subscribeHermesUpdateStatus((status) => {
|
|
21655
21965
|
writeUpdateSseEvent(response, status);
|
|
21656
21966
|
});
|
|
21657
|
-
const cleanup = () => {
|
|
21658
|
-
unsubscribe();
|
|
21659
|
-
response.end();
|
|
21660
|
-
};
|
|
21661
|
-
ctx.req.on("close", cleanup);
|
|
21662
21967
|
});
|
|
21663
21968
|
}
|
|
21664
21969
|
function writeUpdateSseEvent(response, status) {
|
|
21665
|
-
response
|
|
21666
|
-
|
|
21667
|
-
|
|
21668
|
-
|
|
21970
|
+
writeJsonSseEvent(response, {
|
|
21971
|
+
event: "hermes.update.status",
|
|
21972
|
+
data: status
|
|
21973
|
+
});
|
|
21669
21974
|
}
|
|
21670
21975
|
|
|
21671
21976
|
// src/http/routes/link-updates.ts
|
|
@@ -21695,26 +22000,22 @@ function registerLinkUpdateRoutes(router, options) {
|
|
|
21695
22000
|
await authenticateRequest(ctx, paths);
|
|
21696
22001
|
ctx.respond = false;
|
|
21697
22002
|
const response = ctx.res;
|
|
21698
|
-
|
|
21699
|
-
|
|
21700
|
-
|
|
21701
|
-
|
|
22003
|
+
let unsubscribe = () => {
|
|
22004
|
+
};
|
|
22005
|
+
beginSseStream(ctx.req, response, {
|
|
22006
|
+
onClose: () => unsubscribe()
|
|
22007
|
+
});
|
|
21702
22008
|
writeUpdateSseEvent2(response, await readLinkUpdateStatus(paths));
|
|
21703
|
-
|
|
22009
|
+
unsubscribe = subscribeLinkUpdateStatus((status) => {
|
|
21704
22010
|
writeUpdateSseEvent2(response, status);
|
|
21705
22011
|
});
|
|
21706
|
-
const cleanup = () => {
|
|
21707
|
-
unsubscribe();
|
|
21708
|
-
response.end();
|
|
21709
|
-
};
|
|
21710
|
-
ctx.req.on("close", cleanup);
|
|
21711
22012
|
});
|
|
21712
22013
|
}
|
|
21713
22014
|
function writeUpdateSseEvent2(response, status) {
|
|
21714
|
-
response
|
|
21715
|
-
|
|
21716
|
-
|
|
21717
|
-
|
|
22015
|
+
writeJsonSseEvent(response, {
|
|
22016
|
+
event: "link.update.status",
|
|
22017
|
+
data: status
|
|
22018
|
+
});
|
|
21718
22019
|
}
|
|
21719
22020
|
|
|
21720
22021
|
// src/http/routes/pairing.ts
|