@hermespilot/link 0.3.4 → 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-FPMMWYXK.js → chunk-773L37RF.js} +2169 -643
- package/dist/cli/index.js +1 -1
- package/dist/http/app.d.ts +20 -1
- package/dist/http/app.js +1 -1
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import Router from "@koa/router";
|
|
|
4
4
|
|
|
5
5
|
// src/conversations/conversation-service.ts
|
|
6
6
|
import { EventEmitter } from "events";
|
|
7
|
-
import { randomUUID as
|
|
7
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
8
8
|
|
|
9
9
|
// src/database/link-database.ts
|
|
10
10
|
import { mkdir } from "fs/promises";
|
|
@@ -3637,7 +3637,7 @@ async function listCronOutputFiles(profileName, jobId) {
|
|
|
3637
3637
|
mtimeMs: fileStat.mtimeMs
|
|
3638
3638
|
});
|
|
3639
3639
|
}
|
|
3640
|
-
return files.sort((left, right) => left.mtimeMs - right.mtimeMs).map(({ path:
|
|
3640
|
+
return files.sort((left, right) => left.mtimeMs - right.mtimeMs).map(({ path: path25, mtime }) => ({ path: path25, mtime }));
|
|
3641
3641
|
}
|
|
3642
3642
|
async function readCronOutput(outputPath) {
|
|
3643
3643
|
const content = await readFile3(outputPath, "utf8");
|
|
@@ -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 [
|
|
@@ -8516,6 +8566,13 @@ function isNodeError8(error, code) {
|
|
|
8516
8566
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
8517
8567
|
}
|
|
8518
8568
|
|
|
8569
|
+
// src/conversations/hermes-session-sync.ts
|
|
8570
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
8571
|
+
import { readdir as readdir6, readFile as readFile9, stat as stat8 } from "fs/promises";
|
|
8572
|
+
import { createRequire as createRequire3 } from "module";
|
|
8573
|
+
import os4 from "os";
|
|
8574
|
+
import path14 from "path";
|
|
8575
|
+
|
|
8519
8576
|
// src/conversations/delivery-import.ts
|
|
8520
8577
|
import { lstat, readFile as readFile8, readdir as readdir5, stat as stat7 } from "fs/promises";
|
|
8521
8578
|
import path13 from "path";
|
|
@@ -8846,145 +8903,1431 @@ function isNodeError9(error, code) {
|
|
|
8846
8903
|
return typeof error === "object" && error !== null && "code" in error && (code === void 0 || error.code === code);
|
|
8847
8904
|
}
|
|
8848
8905
|
|
|
8849
|
-
// src/conversations/
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
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
|
+
}
|
|
8857
8972
|
}
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
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;
|
|
8870
9026
|
}
|
|
8871
|
-
async function
|
|
8872
|
-
const
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
9027
|
+
async function importHermesSession(input) {
|
|
9028
|
+
const { paths, store, logger, candidate, existingHermesSessionIds } = input;
|
|
9029
|
+
const profile = await resolveConversationProfileTarget(
|
|
9030
|
+
paths,
|
|
9031
|
+
candidate.profileName
|
|
8876
9032
|
);
|
|
8877
|
-
const
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
8883
|
-
|
|
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
|
+
});
|
|
8884
9096
|
}
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
const response = await callHermesApi(
|
|
8889
|
-
"/api/jobs",
|
|
8890
|
-
{
|
|
8891
|
-
method: "POST",
|
|
8892
|
-
body: JSON.stringify(input),
|
|
8893
|
-
headers: { "content-type": "application/json" }
|
|
8894
|
-
},
|
|
8895
|
-
options
|
|
9097
|
+
const stats = buildConversationStats(
|
|
9098
|
+
await store.readManifest(conversationId),
|
|
9099
|
+
hydratedSnapshot
|
|
8896
9100
|
);
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
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
|
|
8903
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;
|
|
8904
9160
|
}
|
|
8905
|
-
return
|
|
9161
|
+
return changed;
|
|
8906
9162
|
}
|
|
8907
|
-
|
|
8908
|
-
const
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
|
|
8918
|
-
|
|
8919
|
-
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
|
|
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
|
+
}
|
|
8924
9201
|
}
|
|
8925
|
-
|
|
9202
|
+
if (rows.length === 0 || endIndex === 0) {
|
|
9203
|
+
return null;
|
|
9204
|
+
}
|
|
9205
|
+
return {
|
|
9206
|
+
endIndex,
|
|
9207
|
+
messages: rows,
|
|
9208
|
+
needsUpgrade: needsProjectionVersion && (hasToolMetadata || hasMediaDeliveryMarkup)
|
|
9209
|
+
};
|
|
8926
9210
|
}
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
`/api/jobs/${encodeURIComponent(jobId)}`,
|
|
8930
|
-
{ method: "DELETE" },
|
|
8931
|
-
options
|
|
8932
|
-
);
|
|
8933
|
-
await readJsonResponse(response);
|
|
9211
|
+
function isHermesImportedMessage(message) {
|
|
9212
|
+
return message.hermes?.imported_from === "hermes" || message.raw?.format === "hermes-message" || message.raw?.format === "hermes-message-group";
|
|
8934
9213
|
}
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8938
|
-
{ method: "POST" },
|
|
8939
|
-
options
|
|
8940
|
-
);
|
|
8941
|
-
const payload = await readJsonResponse(response);
|
|
8942
|
-
if (!isRecord(payload.job)) {
|
|
8943
|
-
throw new LinkHttpError(
|
|
8944
|
-
502,
|
|
8945
|
-
"hermes_cron_job_invalid",
|
|
8946
|
-
"Hermes API Server did not return a cron job"
|
|
8947
|
-
);
|
|
9214
|
+
function readHermesRowsFromAgentEvent(event) {
|
|
9215
|
+
if (event.raw?.format !== "hermes-message" && event.raw?.format !== "hermes-message-group") {
|
|
9216
|
+
return [];
|
|
8948
9217
|
}
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
body: JSON.stringify(input),
|
|
8957
|
-
headers: { "content-type": "application/json" }
|
|
8958
|
-
},
|
|
8959
|
-
options
|
|
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"))
|
|
8960
9225
|
);
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
throw new LinkHttpError(
|
|
8967
|
-
503,
|
|
8968
|
-
"hermes_api_server_unavailable",
|
|
8969
|
-
"Hermes API Server is unavailable"
|
|
8970
|
-
);
|
|
9226
|
+
}
|
|
9227
|
+
function appendHermesRowOnce(rows, seen, row) {
|
|
9228
|
+
const key = hermesRowKey(row, rows.length);
|
|
9229
|
+
if (seen.has(key)) {
|
|
9230
|
+
return;
|
|
8971
9231
|
}
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
"Hermes API Server did not return a run id"
|
|
8979
|
-
);
|
|
9232
|
+
seen.add(key);
|
|
9233
|
+
rows.push(row);
|
|
9234
|
+
}
|
|
9235
|
+
function hermesRowKey(row, fallbackIndex) {
|
|
9236
|
+
if (row.id !== void 0 && row.id !== null) {
|
|
9237
|
+
return `id:${row.id}`;
|
|
8980
9238
|
}
|
|
8981
|
-
return {
|
|
9239
|
+
return `fallback:${fallbackIndex}:${row.role ?? ""}:${row.timestamp ?? ""}:${normalizeContent(row.content)}`;
|
|
8982
9240
|
}
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
9241
|
+
function hasHermesToolMetadata(row) {
|
|
9242
|
+
return normalizeMessageRole(row.role) === "tool" || readHermesToolCalls(row).length > 0 || Boolean(readString9(row, "tool_call_id")) || Boolean(readString9(row, "tool_name"));
|
|
9243
|
+
}
|
|
9244
|
+
function toLinkMessages(input) {
|
|
9245
|
+
const linkMessages = [];
|
|
9246
|
+
let pendingToolCalls = [];
|
|
9247
|
+
const toolCallsById = /* @__PURE__ */ new Map();
|
|
9248
|
+
let currentAssistant = null;
|
|
9249
|
+
const finishAssistantTurn = () => {
|
|
9250
|
+
if (!currentAssistant) {
|
|
9251
|
+
return;
|
|
9252
|
+
}
|
|
9253
|
+
pendingToolCalls.forEach((pending, index) => {
|
|
9254
|
+
const event = projectHermesToolCallWithoutOutputEvent({
|
|
9255
|
+
conversationId: input.conversationId,
|
|
9256
|
+
messageId: pending.message.id,
|
|
9257
|
+
pending,
|
|
9258
|
+
index
|
|
9259
|
+
});
|
|
9260
|
+
if (event) {
|
|
9261
|
+
attachAgentEventToMessage(pending.message, event, event.created_at);
|
|
9262
|
+
}
|
|
9263
|
+
});
|
|
9264
|
+
pendingToolCalls = [];
|
|
9265
|
+
toolCallsById.clear();
|
|
9266
|
+
currentAssistant = null;
|
|
9267
|
+
};
|
|
9268
|
+
input.messages.forEach((message, index) => {
|
|
9269
|
+
const role = normalizeMessageRole(message.role);
|
|
9270
|
+
if (role === "tool") {
|
|
9271
|
+
const pending = consumePendingToolCall({
|
|
9272
|
+
toolMessage: message,
|
|
9273
|
+
pendingToolCalls,
|
|
9274
|
+
toolCallsById
|
|
9275
|
+
});
|
|
9276
|
+
let target = pending?.message ?? currentAssistant;
|
|
9277
|
+
let createdSynthetic = false;
|
|
9278
|
+
if (!target) {
|
|
9279
|
+
target = createSyntheticToolAssistantMessage({
|
|
9280
|
+
...input,
|
|
9281
|
+
message,
|
|
9282
|
+
index
|
|
9283
|
+
});
|
|
9284
|
+
linkMessages.push(target);
|
|
9285
|
+
currentAssistant = target;
|
|
9286
|
+
createdSynthetic = true;
|
|
9287
|
+
}
|
|
9288
|
+
if (!createdSynthetic) {
|
|
9289
|
+
appendHermesRawMessage(target, message);
|
|
9290
|
+
rememberHermesMessageId(target, message);
|
|
9291
|
+
}
|
|
9292
|
+
const event = projectHermesToolCompletedEvent({
|
|
9293
|
+
conversationId: input.conversationId,
|
|
9294
|
+
messageId: target.id,
|
|
9295
|
+
sourceMessage: message,
|
|
9296
|
+
pending,
|
|
9297
|
+
index
|
|
9298
|
+
});
|
|
9299
|
+
if (event) {
|
|
9300
|
+
attachAgentEventToMessage(target, event, event.created_at);
|
|
9301
|
+
}
|
|
9302
|
+
return;
|
|
9303
|
+
}
|
|
9304
|
+
if (role === "assistant") {
|
|
9305
|
+
if (!currentAssistant) {
|
|
9306
|
+
currentAssistant = toLinkMessage({
|
|
9307
|
+
conversationId: input.conversationId,
|
|
9308
|
+
profileName: input.profileName,
|
|
9309
|
+
profileUid: input.profileUid,
|
|
9310
|
+
profileDisplayName: input.profileDisplayName,
|
|
9311
|
+
sessionId: input.sessionId,
|
|
9312
|
+
message,
|
|
9313
|
+
index
|
|
9314
|
+
});
|
|
9315
|
+
linkMessages.push(currentAssistant);
|
|
9316
|
+
} else {
|
|
9317
|
+
appendHermesRawMessage(currentAssistant, message);
|
|
9318
|
+
rememberHermesMessageId(currentAssistant, message);
|
|
9319
|
+
appendAssistantTextToMessage({
|
|
9320
|
+
message: currentAssistant,
|
|
9321
|
+
text: normalizeContent(message.content),
|
|
9322
|
+
updatedAt: isoFromHermesTime(message.timestamp) ?? currentAssistant.updated_at
|
|
9323
|
+
});
|
|
9324
|
+
}
|
|
9325
|
+
for (const toolCall of readHermesToolCalls(message)) {
|
|
9326
|
+
const pending = { message: currentAssistant, toolCall };
|
|
9327
|
+
pendingToolCalls.push(pending);
|
|
9328
|
+
if (toolCall.id) {
|
|
9329
|
+
toolCallsById.set(toolCall.id, pending);
|
|
9330
|
+
}
|
|
9331
|
+
const event = projectHermesToolStartedEvent({
|
|
9332
|
+
conversationId: input.conversationId,
|
|
9333
|
+
messageId: currentAssistant.id,
|
|
9334
|
+
sourceMessage: message,
|
|
9335
|
+
toolCall,
|
|
9336
|
+
index
|
|
9337
|
+
});
|
|
9338
|
+
if (event) {
|
|
9339
|
+
attachAgentEventToMessage(currentAssistant, event, event.created_at);
|
|
9340
|
+
}
|
|
9341
|
+
}
|
|
9342
|
+
return;
|
|
9343
|
+
}
|
|
9344
|
+
finishAssistantTurn();
|
|
9345
|
+
const linkMessage = toLinkMessage({
|
|
9346
|
+
conversationId: input.conversationId,
|
|
9347
|
+
profileName: input.profileName,
|
|
9348
|
+
profileUid: input.profileUid,
|
|
9349
|
+
profileDisplayName: input.profileDisplayName,
|
|
9350
|
+
sessionId: input.sessionId,
|
|
9351
|
+
message,
|
|
9352
|
+
index
|
|
9353
|
+
});
|
|
9354
|
+
linkMessages.push(linkMessage);
|
|
9355
|
+
});
|
|
9356
|
+
finishAssistantTurn();
|
|
9357
|
+
return linkMessages;
|
|
9358
|
+
}
|
|
9359
|
+
function consumePendingToolCall(input) {
|
|
9360
|
+
const toolCallId = readString9(input.toolMessage, "tool_call_id");
|
|
9361
|
+
const toolName = readString9(input.toolMessage, "tool_name");
|
|
9362
|
+
let pending = toolCallId ? input.toolCallsById.get(toolCallId) : void 0;
|
|
9363
|
+
if (!pending && toolName) {
|
|
9364
|
+
pending = input.pendingToolCalls.find(
|
|
9365
|
+
(item) => item.toolCall.name === toolName
|
|
9366
|
+
);
|
|
9367
|
+
}
|
|
9368
|
+
if (!pending && !toolCallId) {
|
|
9369
|
+
pending = input.pendingToolCalls[0];
|
|
9370
|
+
}
|
|
9371
|
+
if (!pending) {
|
|
9372
|
+
return void 0;
|
|
9373
|
+
}
|
|
9374
|
+
const index = input.pendingToolCalls.indexOf(pending);
|
|
9375
|
+
if (index >= 0) {
|
|
9376
|
+
input.pendingToolCalls.splice(index, 1);
|
|
9377
|
+
}
|
|
9378
|
+
if (pending.toolCall.id) {
|
|
9379
|
+
input.toolCallsById.delete(pending.toolCall.id);
|
|
9380
|
+
}
|
|
9381
|
+
return pending;
|
|
9382
|
+
}
|
|
9383
|
+
function createSyntheticToolAssistantMessage(input) {
|
|
9384
|
+
const message = toLinkMessage({
|
|
9385
|
+
conversationId: input.conversationId,
|
|
9386
|
+
profileName: input.profileName,
|
|
9387
|
+
profileUid: input.profileUid,
|
|
9388
|
+
profileDisplayName: input.profileDisplayName,
|
|
9389
|
+
sessionId: input.sessionId,
|
|
9390
|
+
message: {
|
|
9391
|
+
...input.message,
|
|
9392
|
+
role: "assistant",
|
|
9393
|
+
content: null
|
|
9394
|
+
},
|
|
9395
|
+
index: input.index
|
|
9396
|
+
});
|
|
9397
|
+
message.raw = {
|
|
9398
|
+
format: "hermes-message",
|
|
9399
|
+
payload: input.message
|
|
9400
|
+
};
|
|
9401
|
+
return message;
|
|
9402
|
+
}
|
|
9403
|
+
function readHermesToolCalls(message) {
|
|
9404
|
+
const decoded = parseJsonValue(message.tool_calls) ?? message.tool_calls;
|
|
9405
|
+
const values = Array.isArray(decoded) ? decoded : decoded ? [decoded] : [];
|
|
9406
|
+
return values.map((value) => normalizeHermesToolCall(value)).filter(
|
|
9407
|
+
(toolCall) => Boolean(toolCall)
|
|
9408
|
+
);
|
|
9409
|
+
}
|
|
9410
|
+
function normalizeHermesToolCall(value) {
|
|
9411
|
+
const record = toRecord8(value);
|
|
9412
|
+
if (Object.keys(record).length === 0) {
|
|
9413
|
+
return null;
|
|
9414
|
+
}
|
|
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";
|
|
9418
|
+
const rawArguments = fn.arguments ?? record.arguments ?? record.args ?? record.input;
|
|
9419
|
+
return {
|
|
9420
|
+
...id ? { id } : {},
|
|
9421
|
+
name,
|
|
9422
|
+
...rawArguments === void 0 ? {} : { arguments: parseJsonValue(rawArguments) ?? rawArguments },
|
|
9423
|
+
raw: value
|
|
9424
|
+
};
|
|
9425
|
+
}
|
|
9426
|
+
function projectHermesToolStartedEvent(input) {
|
|
9427
|
+
const createdAt = isoFromHermesTime(input.sourceMessage.timestamp) ?? new Date(Date.now() + input.index).toISOString();
|
|
9428
|
+
return projectHermesAgentEvent({
|
|
9429
|
+
conversationId: input.conversationId,
|
|
9430
|
+
messageId: input.messageId,
|
|
9431
|
+
type: "tool.started",
|
|
9432
|
+
createdAt,
|
|
9433
|
+
seq: input.index + 1,
|
|
9434
|
+
payload: {
|
|
9435
|
+
type: "tool.started",
|
|
9436
|
+
tool: input.toolCall.name,
|
|
9437
|
+
tool_name: input.toolCall.name,
|
|
9438
|
+
name: input.toolCall.name,
|
|
9439
|
+
...input.toolCall.id ? { tool_call_id: input.toolCall.id, id: input.toolCall.id } : {},
|
|
9440
|
+
...input.toolCall.arguments === void 0 ? {} : { arguments: input.toolCall.arguments },
|
|
9441
|
+
tool_call: input.toolCall.raw
|
|
9442
|
+
},
|
|
9443
|
+
raw: {
|
|
9444
|
+
format: "hermes-message",
|
|
9445
|
+
payload: {
|
|
9446
|
+
message: input.sourceMessage,
|
|
9447
|
+
tool_call: input.toolCall.raw
|
|
9448
|
+
}
|
|
9449
|
+
}
|
|
9450
|
+
});
|
|
9451
|
+
}
|
|
9452
|
+
function projectHermesToolCompletedEvent(input) {
|
|
9453
|
+
const createdAt = isoFromHermesTime(input.sourceMessage.timestamp) ?? new Date(Date.now() + input.index).toISOString();
|
|
9454
|
+
const output = normalizeContent(input.sourceMessage.content);
|
|
9455
|
+
const parsedOutput = parseJsonValue(output);
|
|
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";
|
|
9458
|
+
return projectHermesAgentEvent({
|
|
9459
|
+
conversationId: input.conversationId,
|
|
9460
|
+
messageId: input.messageId,
|
|
9461
|
+
type: "tool.completed",
|
|
9462
|
+
createdAt,
|
|
9463
|
+
seq: input.index + 1,
|
|
9464
|
+
payload: {
|
|
9465
|
+
type: "tool.completed",
|
|
9466
|
+
tool: toolName,
|
|
9467
|
+
tool_name: toolName,
|
|
9468
|
+
name: toolName,
|
|
9469
|
+
...toolCallId ? { tool_call_id: toolCallId, id: toolCallId } : {},
|
|
9470
|
+
...input.pending?.toolCall.arguments === void 0 ? {} : { arguments: input.pending.toolCall.arguments },
|
|
9471
|
+
output,
|
|
9472
|
+
content: output,
|
|
9473
|
+
result: parsedOutput ?? output,
|
|
9474
|
+
...input.pending ? { tool_call: input.pending.toolCall.raw } : {}
|
|
9475
|
+
},
|
|
9476
|
+
raw: {
|
|
9477
|
+
format: "hermes-message",
|
|
9478
|
+
payload: {
|
|
9479
|
+
message: input.sourceMessage,
|
|
9480
|
+
...input.pending ? { tool_call: input.pending.toolCall.raw } : {}
|
|
9481
|
+
}
|
|
9482
|
+
}
|
|
9483
|
+
});
|
|
9484
|
+
}
|
|
9485
|
+
function projectHermesToolCallWithoutOutputEvent(input) {
|
|
9486
|
+
return projectHermesAgentEvent({
|
|
9487
|
+
conversationId: input.conversationId,
|
|
9488
|
+
messageId: input.messageId,
|
|
9489
|
+
type: "tool.completed",
|
|
9490
|
+
createdAt: input.pending.message.updated_at,
|
|
9491
|
+
seq: input.index + 1,
|
|
9492
|
+
payload: {
|
|
9493
|
+
type: "tool.completed",
|
|
9494
|
+
tool: input.pending.toolCall.name,
|
|
9495
|
+
tool_name: input.pending.toolCall.name,
|
|
9496
|
+
name: input.pending.toolCall.name,
|
|
9497
|
+
...input.pending.toolCall.id ? {
|
|
9498
|
+
tool_call_id: input.pending.toolCall.id,
|
|
9499
|
+
id: input.pending.toolCall.id
|
|
9500
|
+
} : {},
|
|
9501
|
+
...input.pending.toolCall.arguments === void 0 ? {} : { arguments: input.pending.toolCall.arguments },
|
|
9502
|
+
tool_call: input.pending.toolCall.raw
|
|
9503
|
+
},
|
|
9504
|
+
raw: {
|
|
9505
|
+
format: "hermes-tool-call",
|
|
9506
|
+
payload: {
|
|
9507
|
+
tool_call: input.pending.toolCall.raw,
|
|
9508
|
+
note: "tool_output_not_found"
|
|
9509
|
+
}
|
|
9510
|
+
}
|
|
9511
|
+
});
|
|
9512
|
+
}
|
|
9513
|
+
function projectHermesAgentEvent(input) {
|
|
9514
|
+
const event = {
|
|
9515
|
+
seq: input.seq,
|
|
9516
|
+
type: input.type,
|
|
9517
|
+
conversation_id: input.conversationId,
|
|
9518
|
+
message_id: input.messageId,
|
|
9519
|
+
created_at: input.createdAt,
|
|
9520
|
+
payload: input.payload,
|
|
9521
|
+
raw: input.raw
|
|
9522
|
+
};
|
|
9523
|
+
return projectConversationAgentEvent(event);
|
|
9524
|
+
}
|
|
9525
|
+
function attachAgentEventToMessage(message, event, updatedAt) {
|
|
9526
|
+
ensureTextBlockForMessage(message);
|
|
9527
|
+
message.agent_events = upsertAgentEventProjection(
|
|
9528
|
+
message.agent_events ?? [],
|
|
9529
|
+
event
|
|
9530
|
+
);
|
|
9531
|
+
appendAgentEventBlock(message, event, updatedAt);
|
|
9532
|
+
message.updated_at = latestTimestamp(message.updated_at, updatedAt);
|
|
9533
|
+
}
|
|
9534
|
+
function appendAssistantTextToMessage(input) {
|
|
9535
|
+
const text = input.text;
|
|
9536
|
+
if (!text) {
|
|
9537
|
+
return;
|
|
9538
|
+
}
|
|
9539
|
+
appendTextPart(input.message, text);
|
|
9540
|
+
if (input.message.blocks?.length) {
|
|
9541
|
+
appendTextBlock(input.message, text, input.updatedAt);
|
|
9542
|
+
}
|
|
9543
|
+
input.message.updated_at = latestTimestamp(
|
|
9544
|
+
input.message.updated_at,
|
|
9545
|
+
input.updatedAt
|
|
9546
|
+
);
|
|
9547
|
+
}
|
|
9548
|
+
function appendTextPart(message, text) {
|
|
9549
|
+
const existing = message.parts.find((part) => part.type === "text");
|
|
9550
|
+
if (existing) {
|
|
9551
|
+
existing.text = joinImportedText(existing.text ?? "", text);
|
|
9552
|
+
return;
|
|
9553
|
+
}
|
|
9554
|
+
message.parts.push({ type: "text", text });
|
|
9555
|
+
}
|
|
9556
|
+
function appendTextBlock(message, text, updatedAt) {
|
|
9557
|
+
const blocks = [...message.blocks ?? []];
|
|
9558
|
+
const last = blocks.at(-1);
|
|
9559
|
+
if (last?.type === "text") {
|
|
9560
|
+
blocks[blocks.length - 1] = {
|
|
9561
|
+
...last,
|
|
9562
|
+
text: joinImportedText(last.text, text),
|
|
9563
|
+
updated_at: updatedAt
|
|
9564
|
+
};
|
|
9565
|
+
} else {
|
|
9566
|
+
blocks.push({
|
|
9567
|
+
id: `text_${blocks.length + 1}`,
|
|
9568
|
+
type: "text",
|
|
9569
|
+
text,
|
|
9570
|
+
created_at: updatedAt,
|
|
9571
|
+
updated_at: updatedAt
|
|
9572
|
+
});
|
|
9573
|
+
}
|
|
9574
|
+
message.blocks = blocks;
|
|
9575
|
+
}
|
|
9576
|
+
function ensureTextBlockForMessage(message) {
|
|
9577
|
+
if (message.blocks?.length) {
|
|
9578
|
+
return;
|
|
9579
|
+
}
|
|
9580
|
+
const text = message.parts.filter((part) => part.type === "text" && part.text).map((part) => part.text).join("");
|
|
9581
|
+
if (!text) {
|
|
9582
|
+
return;
|
|
9583
|
+
}
|
|
9584
|
+
message.blocks = [
|
|
9585
|
+
{
|
|
9586
|
+
id: "text_1",
|
|
9587
|
+
type: "text",
|
|
9588
|
+
text,
|
|
9589
|
+
created_at: message.created_at,
|
|
9590
|
+
updated_at: message.updated_at
|
|
9591
|
+
}
|
|
9592
|
+
];
|
|
9593
|
+
}
|
|
9594
|
+
function appendAgentEventBlock(message, event, updatedAt) {
|
|
9595
|
+
const blocks = [...message.blocks ?? []];
|
|
9596
|
+
const matchingIndex = blocks.findIndex((block) => {
|
|
9597
|
+
if (block.type !== "agent_events") {
|
|
9598
|
+
return false;
|
|
9599
|
+
}
|
|
9600
|
+
return upsertAgentEventProjection(block.events, event).length === block.events.length;
|
|
9601
|
+
});
|
|
9602
|
+
const targetIndex = matchingIndex >= 0 ? matchingIndex : blocks.at(-1)?.type === "agent_events" ? blocks.length - 1 : -1;
|
|
9603
|
+
if (targetIndex >= 0) {
|
|
9604
|
+
const block = blocks[targetIndex];
|
|
9605
|
+
if (block.type === "agent_events") {
|
|
9606
|
+
blocks[targetIndex] = {
|
|
9607
|
+
...block,
|
|
9608
|
+
events: upsertAgentEventProjection(block.events, event),
|
|
9609
|
+
updated_at: updatedAt
|
|
9610
|
+
};
|
|
9611
|
+
}
|
|
9612
|
+
} else {
|
|
9613
|
+
blocks.push({
|
|
9614
|
+
id: `tools_${blocks.length + 1}`,
|
|
9615
|
+
type: "agent_events",
|
|
9616
|
+
events: [event],
|
|
9617
|
+
created_at: updatedAt,
|
|
9618
|
+
updated_at: updatedAt
|
|
9619
|
+
});
|
|
9620
|
+
}
|
|
9621
|
+
message.blocks = blocks;
|
|
9622
|
+
}
|
|
9623
|
+
function latestTimestamp(left, right) {
|
|
9624
|
+
const leftTime = Date.parse(left);
|
|
9625
|
+
const rightTime = Date.parse(right);
|
|
9626
|
+
if (Number.isNaN(leftTime)) {
|
|
9627
|
+
return right;
|
|
9628
|
+
}
|
|
9629
|
+
if (Number.isNaN(rightTime)) {
|
|
9630
|
+
return left;
|
|
9631
|
+
}
|
|
9632
|
+
return leftTime >= rightTime ? left : right;
|
|
9633
|
+
}
|
|
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) {
|
|
9640
|
+
continue;
|
|
9641
|
+
}
|
|
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
|
+
);
|
|
9649
|
+
}
|
|
9650
|
+
for (const sessionId of manifest.hermes_session_ids ?? []) {
|
|
9651
|
+
ids.add(sessionId);
|
|
9652
|
+
rememberKnownHermesConversation(
|
|
9653
|
+
conversationIdsBySessionId,
|
|
9654
|
+
sessionId,
|
|
9655
|
+
conversationId
|
|
9656
|
+
);
|
|
9657
|
+
}
|
|
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 [];
|
|
9674
|
+
}
|
|
9675
|
+
throw error;
|
|
9676
|
+
}
|
|
9677
|
+
);
|
|
9678
|
+
for (const entry of entries) {
|
|
9679
|
+
if (entry.isDirectory() && PROFILE_NAME_PATTERN3.test(entry.name)) {
|
|
9680
|
+
names.add(entry.name);
|
|
9681
|
+
}
|
|
9682
|
+
}
|
|
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
|
+
});
|
|
9692
|
+
}
|
|
9693
|
+
async function listProfileSessions(dbPath) {
|
|
9694
|
+
if (!await isFile(dbPath)) {
|
|
9695
|
+
return [];
|
|
9696
|
+
}
|
|
9697
|
+
let db = null;
|
|
9698
|
+
try {
|
|
9699
|
+
const { DatabaseSync } = nodeRequire3(
|
|
9700
|
+
"node:sqlite"
|
|
9701
|
+
);
|
|
9702
|
+
db = new DatabaseSync(dbPath, {
|
|
9703
|
+
readOnly: true,
|
|
9704
|
+
timeout: 1e3
|
|
9705
|
+
});
|
|
9706
|
+
const sessionColumns = readTableColumns(db, "sessions");
|
|
9707
|
+
if (!sessionColumns.has("id")) {
|
|
9708
|
+
return [];
|
|
9709
|
+
}
|
|
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(
|
|
9718
|
+
`
|
|
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();
|
|
9727
|
+
}
|
|
9728
|
+
}
|
|
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 [];
|
|
9742
|
+
}
|
|
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
|
+
) : [];
|
|
9748
|
+
}
|
|
9749
|
+
if (raw.format === "hermes-message") {
|
|
9750
|
+
return typeof raw.payload === "object" && raw.payload !== null ? [raw.payload] : [];
|
|
9751
|
+
}
|
|
9752
|
+
return [];
|
|
9753
|
+
}
|
|
9754
|
+
function rememberHermesMessageId(message, row) {
|
|
9755
|
+
if (row.id === void 0 || row.id === null) {
|
|
9756
|
+
return;
|
|
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
|
+
};
|
|
9764
|
+
}
|
|
9765
|
+
function joinImportedText(left, right) {
|
|
9766
|
+
if (!left) {
|
|
9767
|
+
return right;
|
|
9768
|
+
}
|
|
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}`;
|
|
9778
|
+
}
|
|
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
|
+
}
|
|
9794
|
+
}
|
|
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
|
+
}
|
|
9827
|
+
}
|
|
9828
|
+
return projected;
|
|
9829
|
+
}
|
|
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;
|
|
9836
|
+
}
|
|
9837
|
+
async function readStateDbMessages(dbPath, sessionId) {
|
|
9838
|
+
if (!await isFile(dbPath)) {
|
|
9839
|
+
return [];
|
|
9840
|
+
}
|
|
9841
|
+
let db = null;
|
|
9842
|
+
try {
|
|
9843
|
+
const { DatabaseSync } = nodeRequire3(
|
|
9844
|
+
"node:sqlite"
|
|
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();
|
|
9869
|
+
}
|
|
9870
|
+
}
|
|
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) => {
|
|
9879
|
+
if (isNodeError10(error, "ENOENT")) {
|
|
9880
|
+
return "";
|
|
9881
|
+
}
|
|
9882
|
+
throw error;
|
|
9883
|
+
});
|
|
9884
|
+
if (!raw.trim()) {
|
|
9885
|
+
return [];
|
|
9886
|
+
}
|
|
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;
|
|
9903
|
+
}
|
|
9904
|
+
function normalizeJsonlMessage(row) {
|
|
9905
|
+
const role = readString9(row, "role");
|
|
9906
|
+
if (!role) {
|
|
9907
|
+
return null;
|
|
9908
|
+
}
|
|
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;
|
|
9926
|
+
}
|
|
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
|
|
9949
|
+
}
|
|
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
|
|
9955
|
+
});
|
|
9956
|
+
}
|
|
9957
|
+
}
|
|
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;
|
|
10100
|
+
}
|
|
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("");
|
|
10111
|
+
}
|
|
10112
|
+
return "";
|
|
10113
|
+
}
|
|
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;
|
|
10126
|
+
}
|
|
10127
|
+
}
|
|
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 {
|
|
10147
|
+
return /* @__PURE__ */ new Set();
|
|
10148
|
+
}
|
|
10149
|
+
}
|
|
10150
|
+
function quoteIdentifier(value) {
|
|
10151
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
10152
|
+
}
|
|
10153
|
+
async function isFile(filePath) {
|
|
10154
|
+
return stat8(filePath).then((value) => value.isFile()).catch((error) => {
|
|
10155
|
+
if (isNodeError10(error, "ENOENT")) {
|
|
10156
|
+
return false;
|
|
10157
|
+
}
|
|
10158
|
+
throw error;
|
|
10159
|
+
});
|
|
10160
|
+
}
|
|
10161
|
+
function createConversationId() {
|
|
10162
|
+
return `conv_${randomUUID6().replaceAll("-", "")}`;
|
|
10163
|
+
}
|
|
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();
|
|
10171
|
+
}
|
|
10172
|
+
function readString9(payload, key) {
|
|
10173
|
+
const value = payload[key];
|
|
10174
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
10175
|
+
}
|
|
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;
|
|
10187
|
+
}
|
|
10188
|
+
function isNodeError10(error, code) {
|
|
10189
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
10190
|
+
}
|
|
10191
|
+
|
|
10192
|
+
// src/conversations/run-lifecycle.ts
|
|
10193
|
+
import { readdir as readdir7 } from "fs/promises";
|
|
10194
|
+
|
|
10195
|
+
// src/hermes/api-server.ts
|
|
10196
|
+
async function listHermesModels(options = {}) {
|
|
10197
|
+
const response = await callHermesApi("/v1/models", { method: "GET" }, options);
|
|
10198
|
+
if (response.status === 404) {
|
|
10199
|
+
return { models: [] };
|
|
10200
|
+
}
|
|
10201
|
+
return await readJsonResponse(response);
|
|
10202
|
+
}
|
|
10203
|
+
async function listHermesCronJobs(options = {}) {
|
|
10204
|
+
const query = options.includeDisabled ? "?include_disabled=true" : "";
|
|
10205
|
+
const response = await callHermesApi(
|
|
10206
|
+
`/api/jobs${query}`,
|
|
10207
|
+
{ method: "GET" },
|
|
10208
|
+
options
|
|
10209
|
+
);
|
|
10210
|
+
const payload = await readJsonResponse(response);
|
|
10211
|
+
const jobs = payload.jobs;
|
|
10212
|
+
return Array.isArray(jobs) ? jobs.filter(isRecord).map((job) => ({ ...job })) : [];
|
|
10213
|
+
}
|
|
10214
|
+
async function getHermesCronJob(jobId, options = {}) {
|
|
10215
|
+
const response = await callHermesApi(
|
|
10216
|
+
`/api/jobs/${encodeURIComponent(jobId)}`,
|
|
10217
|
+
{ method: "GET" },
|
|
10218
|
+
options
|
|
10219
|
+
);
|
|
10220
|
+
const payload = await readJsonResponse(response);
|
|
10221
|
+
if (!isRecord(payload.job)) {
|
|
10222
|
+
throw new LinkHttpError(
|
|
10223
|
+
502,
|
|
10224
|
+
"hermes_cron_job_invalid",
|
|
10225
|
+
"Hermes API Server did not return a cron job"
|
|
10226
|
+
);
|
|
10227
|
+
}
|
|
10228
|
+
return { ...payload.job };
|
|
10229
|
+
}
|
|
10230
|
+
async function createHermesCronJob(input, options = {}) {
|
|
10231
|
+
const response = await callHermesApi(
|
|
10232
|
+
"/api/jobs",
|
|
10233
|
+
{
|
|
10234
|
+
method: "POST",
|
|
10235
|
+
body: JSON.stringify(input),
|
|
10236
|
+
headers: { "content-type": "application/json" }
|
|
10237
|
+
},
|
|
10238
|
+
options
|
|
10239
|
+
);
|
|
10240
|
+
const payload = await readJsonResponse(response);
|
|
10241
|
+
if (!isRecord(payload.job)) {
|
|
10242
|
+
throw new LinkHttpError(
|
|
10243
|
+
502,
|
|
10244
|
+
"hermes_cron_job_invalid",
|
|
10245
|
+
"Hermes API Server did not return a cron job"
|
|
10246
|
+
);
|
|
10247
|
+
}
|
|
10248
|
+
return { ...payload.job };
|
|
10249
|
+
}
|
|
10250
|
+
async function updateHermesCronJob(jobId, input, options = {}) {
|
|
10251
|
+
const response = await callHermesApi(
|
|
10252
|
+
`/api/jobs/${encodeURIComponent(jobId)}`,
|
|
10253
|
+
{
|
|
10254
|
+
method: "PATCH",
|
|
10255
|
+
body: JSON.stringify(input),
|
|
10256
|
+
headers: { "content-type": "application/json" }
|
|
10257
|
+
},
|
|
10258
|
+
options
|
|
10259
|
+
);
|
|
10260
|
+
const payload = await readJsonResponse(response);
|
|
10261
|
+
if (!isRecord(payload.job)) {
|
|
10262
|
+
throw new LinkHttpError(
|
|
10263
|
+
502,
|
|
10264
|
+
"hermes_cron_job_invalid",
|
|
10265
|
+
"Hermes API Server did not return a cron job"
|
|
10266
|
+
);
|
|
10267
|
+
}
|
|
10268
|
+
return { ...payload.job };
|
|
10269
|
+
}
|
|
10270
|
+
async function deleteHermesCronJob(jobId, options = {}) {
|
|
10271
|
+
const response = await callHermesApi(
|
|
10272
|
+
`/api/jobs/${encodeURIComponent(jobId)}`,
|
|
10273
|
+
{ method: "DELETE" },
|
|
10274
|
+
options
|
|
10275
|
+
);
|
|
10276
|
+
await readJsonResponse(response);
|
|
10277
|
+
}
|
|
10278
|
+
async function runHermesCronJobAction(jobId, action, options = {}) {
|
|
10279
|
+
const response = await callHermesApi(
|
|
10280
|
+
`/api/jobs/${encodeURIComponent(jobId)}/${action}`,
|
|
10281
|
+
{ method: "POST" },
|
|
10282
|
+
options
|
|
10283
|
+
);
|
|
10284
|
+
const payload = await readJsonResponse(response);
|
|
10285
|
+
if (!isRecord(payload.job)) {
|
|
10286
|
+
throw new LinkHttpError(
|
|
10287
|
+
502,
|
|
10288
|
+
"hermes_cron_job_invalid",
|
|
10289
|
+
"Hermes API Server did not return a cron job"
|
|
10290
|
+
);
|
|
10291
|
+
}
|
|
10292
|
+
return { ...payload.job };
|
|
10293
|
+
}
|
|
10294
|
+
async function createHermesRun(input, options = {}) {
|
|
10295
|
+
const response = await callHermesApi(
|
|
10296
|
+
"/v1/runs",
|
|
10297
|
+
{
|
|
10298
|
+
method: "POST",
|
|
10299
|
+
body: JSON.stringify(input),
|
|
10300
|
+
headers: { "content-type": "application/json" }
|
|
10301
|
+
},
|
|
10302
|
+
options
|
|
10303
|
+
);
|
|
10304
|
+
if (response.status === 404 || response.status === 503) {
|
|
10305
|
+
assertHermesRunsApiSupported(
|
|
10306
|
+
await readHermesVersion().catch(() => null),
|
|
10307
|
+
response.status
|
|
10308
|
+
);
|
|
10309
|
+
throw new LinkHttpError(
|
|
10310
|
+
503,
|
|
10311
|
+
"hermes_api_server_unavailable",
|
|
10312
|
+
"Hermes API Server is unavailable"
|
|
10313
|
+
);
|
|
10314
|
+
}
|
|
10315
|
+
const payload = await readJsonResponse(response);
|
|
10316
|
+
const runId = readString10(payload, "run_id") ?? readString10(payload, "runId") ?? readString10(payload, "id");
|
|
10317
|
+
if (!runId) {
|
|
10318
|
+
throw new LinkHttpError(
|
|
10319
|
+
502,
|
|
10320
|
+
"hermes_run_invalid",
|
|
10321
|
+
"Hermes API Server did not return a run id"
|
|
10322
|
+
);
|
|
10323
|
+
}
|
|
10324
|
+
return { run_id: runId, fallback: false };
|
|
10325
|
+
}
|
|
10326
|
+
async function streamHermesRunEvents(runId, options = {}) {
|
|
10327
|
+
const response = await callHermesApi(
|
|
10328
|
+
`/v1/runs/${encodeURIComponent(runId)}/events`,
|
|
10329
|
+
{ method: "GET", signal: options.signal },
|
|
10330
|
+
options
|
|
8988
10331
|
);
|
|
8989
10332
|
assertHermesRunsApiSupported(
|
|
8990
10333
|
await readHermesVersion().catch(() => null),
|
|
@@ -9083,10 +10426,10 @@ async function cancelHermesRun(runId, options = {}) {
|
|
|
9083
10426
|
);
|
|
9084
10427
|
}
|
|
9085
10428
|
}
|
|
9086
|
-
async function callHermesApi(
|
|
10429
|
+
async function callHermesApi(path25, init, options) {
|
|
9087
10430
|
const method = init.method ?? "GET";
|
|
9088
10431
|
const startedAt = Date.now();
|
|
9089
|
-
void options.logger?.debug("hermes_api_request_started", { method, path:
|
|
10432
|
+
void options.logger?.debug("hermes_api_request_started", { method, path: path25 });
|
|
9090
10433
|
const availability = await ensureHermesApiServerAvailable({
|
|
9091
10434
|
fetchImpl: options.fetchImpl,
|
|
9092
10435
|
logger: options.logger,
|
|
@@ -9094,21 +10437,21 @@ async function callHermesApi(path24, init, options) {
|
|
|
9094
10437
|
});
|
|
9095
10438
|
let config = availability.configResult.apiServer;
|
|
9096
10439
|
const fetcher = options.fetchImpl ?? fetch;
|
|
9097
|
-
const request = () => fetchHermesApi(fetcher, config,
|
|
10440
|
+
const request = () => fetchHermesApi(fetcher, config, path25, init, options);
|
|
9098
10441
|
let response;
|
|
9099
10442
|
try {
|
|
9100
10443
|
response = await request();
|
|
9101
10444
|
} catch (error) {
|
|
9102
|
-
logHermesApiError(options.logger, method,
|
|
10445
|
+
logHermesApiError(options.logger, method, path25, startedAt, error);
|
|
9103
10446
|
throw error;
|
|
9104
10447
|
}
|
|
9105
10448
|
if (response.status !== 401) {
|
|
9106
|
-
logHermesApiResponse(options.logger, method,
|
|
10449
|
+
logHermesApiResponse(options.logger, method, path25, startedAt, response);
|
|
9107
10450
|
return response;
|
|
9108
10451
|
}
|
|
9109
10452
|
void options.logger?.warn("hermes_api_request_retrying_after_401", {
|
|
9110
10453
|
method,
|
|
9111
|
-
path:
|
|
10454
|
+
path: path25,
|
|
9112
10455
|
duration_ms: Date.now() - startedAt
|
|
9113
10456
|
});
|
|
9114
10457
|
const refreshedAvailability = await ensureHermesApiServerAvailable({
|
|
@@ -9121,20 +10464,20 @@ async function callHermesApi(path24, init, options) {
|
|
|
9121
10464
|
try {
|
|
9122
10465
|
response = await request();
|
|
9123
10466
|
} catch (error) {
|
|
9124
|
-
logHermesApiError(options.logger, method,
|
|
10467
|
+
logHermesApiError(options.logger, method, path25, startedAt, error);
|
|
9125
10468
|
throw error;
|
|
9126
10469
|
}
|
|
9127
|
-
logHermesApiResponse(options.logger, method,
|
|
10470
|
+
logHermesApiResponse(options.logger, method, path25, startedAt, response);
|
|
9128
10471
|
return response;
|
|
9129
10472
|
}
|
|
9130
|
-
async function fetchHermesApi(fetcher, config,
|
|
10473
|
+
async function fetchHermesApi(fetcher, config, path25, init, options) {
|
|
9131
10474
|
const headers = new Headers(init.headers);
|
|
9132
10475
|
headers.set("accept", headers.get("accept") ?? "application/json");
|
|
9133
10476
|
if (config.key) {
|
|
9134
10477
|
headers.set("x-api-key", config.key);
|
|
9135
10478
|
headers.set("authorization", `Bearer ${config.key}`);
|
|
9136
10479
|
}
|
|
9137
|
-
return await fetcher(`http://127.0.0.1:${config.port}${
|
|
10480
|
+
return await fetcher(`http://127.0.0.1:${config.port}${path25}`, {
|
|
9138
10481
|
...init,
|
|
9139
10482
|
headers
|
|
9140
10483
|
}).catch((error) => {
|
|
@@ -9142,7 +10485,7 @@ async function fetchHermesApi(fetcher, config, path24, init, options) {
|
|
|
9142
10485
|
throw error;
|
|
9143
10486
|
}
|
|
9144
10487
|
void options.logger?.warn("hermes_api_server_connect_failed", {
|
|
9145
|
-
path:
|
|
10488
|
+
path: path25,
|
|
9146
10489
|
port: config.port ?? null,
|
|
9147
10490
|
error: error instanceof Error ? error.message : String(error)
|
|
9148
10491
|
});
|
|
@@ -9153,10 +10496,10 @@ async function fetchHermesApi(fetcher, config, path24, init, options) {
|
|
|
9153
10496
|
);
|
|
9154
10497
|
});
|
|
9155
10498
|
}
|
|
9156
|
-
function logHermesApiResponse(logger, method,
|
|
10499
|
+
function logHermesApiResponse(logger, method, path25, startedAt, response) {
|
|
9157
10500
|
const fields = {
|
|
9158
10501
|
method,
|
|
9159
|
-
path:
|
|
10502
|
+
path: path25,
|
|
9160
10503
|
status: response.status,
|
|
9161
10504
|
duration_ms: Date.now() - startedAt
|
|
9162
10505
|
};
|
|
@@ -9176,10 +10519,10 @@ async function logHermesApiFailureResponse(logger, fields, response) {
|
|
|
9176
10519
|
...upstreamError ? { upstream_error: upstreamError } : {}
|
|
9177
10520
|
});
|
|
9178
10521
|
}
|
|
9179
|
-
function logHermesApiError(logger, method,
|
|
10522
|
+
function logHermesApiError(logger, method, path25, startedAt, error) {
|
|
9180
10523
|
void logger?.warn("hermes_api_request_failed", {
|
|
9181
10524
|
method,
|
|
9182
|
-
path:
|
|
10525
|
+
path: path25,
|
|
9183
10526
|
duration_ms: Date.now() - startedAt,
|
|
9184
10527
|
...error instanceof LinkHttpError ? { status: error.status, code: error.code } : {},
|
|
9185
10528
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -9228,23 +10571,23 @@ function isRecord(value) {
|
|
|
9228
10571
|
}
|
|
9229
10572
|
function readUpstreamMessage(payload, raw) {
|
|
9230
10573
|
const error = typeof payload?.error === "object" && payload.error !== null ? payload.error : null;
|
|
9231
|
-
const message =
|
|
10574
|
+
const message = readString10(error ?? {}, "message") ?? readString10(payload ?? {}, "message");
|
|
9232
10575
|
if (message) {
|
|
9233
10576
|
return message;
|
|
9234
10577
|
}
|
|
9235
10578
|
const body = raw.trim().replace(/\s+/gu, " ").slice(0, 500);
|
|
9236
10579
|
return body || "empty response body";
|
|
9237
10580
|
}
|
|
9238
|
-
function
|
|
10581
|
+
function readString10(payload, key) {
|
|
9239
10582
|
const value = payload[key];
|
|
9240
10583
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
9241
10584
|
}
|
|
9242
10585
|
|
|
9243
10586
|
// src/conversations/history-builder.ts
|
|
9244
|
-
import { readFile as
|
|
9245
|
-
import { createRequire as
|
|
9246
|
-
import
|
|
9247
|
-
var
|
|
10587
|
+
import { readFile as readFile10, stat as stat9 } from "fs/promises";
|
|
10588
|
+
import { createRequire as createRequire4 } from "module";
|
|
10589
|
+
import path15 from "path";
|
|
10590
|
+
var nodeRequire4 = createRequire4(import.meta.url);
|
|
9248
10591
|
var HISTORY_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
9249
10592
|
var HERMES_HISTORY_COLUMNS = [
|
|
9250
10593
|
"role",
|
|
@@ -9305,13 +10648,13 @@ async function readHermesTranscriptHistory(sessionId, profileName) {
|
|
|
9305
10648
|
}
|
|
9306
10649
|
const normalizedProfileName = isValidProfileName2(profileName) ? profileName : "default";
|
|
9307
10650
|
const profileDir = resolveHermesProfileDir(normalizedProfileName);
|
|
9308
|
-
const dbPath =
|
|
10651
|
+
const dbPath = path15.join(profileDir, "state.db");
|
|
9309
10652
|
const sessionsDirConfig = await readHermesSessionsDir(normalizedProfileName).then((value) => ({
|
|
9310
10653
|
sessionsDir: value.sessionsDir,
|
|
9311
10654
|
configured: value.configured,
|
|
9312
10655
|
configError: false
|
|
9313
10656
|
})).catch(() => ({
|
|
9314
|
-
sessionsDir:
|
|
10657
|
+
sessionsDir: path15.join(profileDir, "sessions"),
|
|
9315
10658
|
configured: false,
|
|
9316
10659
|
configError: true
|
|
9317
10660
|
}));
|
|
@@ -9351,8 +10694,8 @@ async function readHermesTranscriptHistory(sessionId, profileName) {
|
|
|
9351
10694
|
};
|
|
9352
10695
|
}
|
|
9353
10696
|
async function readHermesStateDbHistory(dbPath, sessionId) {
|
|
9354
|
-
const exists = await
|
|
9355
|
-
if (
|
|
10697
|
+
const exists = await stat9(dbPath).then((value) => value.isFile()).catch((error) => {
|
|
10698
|
+
if (isNodeError11(error, "ENOENT")) {
|
|
9356
10699
|
return false;
|
|
9357
10700
|
}
|
|
9358
10701
|
throw error;
|
|
@@ -9369,9 +10712,9 @@ async function readHermesJsonlHistory(sessionsDir, sessionId) {
|
|
|
9369
10712
|
if (!isValidSessionFileStem(sessionId)) {
|
|
9370
10713
|
return empty;
|
|
9371
10714
|
}
|
|
9372
|
-
const transcriptPath =
|
|
9373
|
-
const raw = await
|
|
9374
|
-
if (
|
|
10715
|
+
const transcriptPath = path15.join(sessionsDir, `${sessionId}.jsonl`);
|
|
10716
|
+
const raw = await readFile10(transcriptPath, "utf8").catch((error) => {
|
|
10717
|
+
if (isNodeError11(error, "ENOENT")) {
|
|
9375
10718
|
return "";
|
|
9376
10719
|
}
|
|
9377
10720
|
throw error;
|
|
@@ -9406,14 +10749,14 @@ async function readHermesJsonlHistory(sessionsDir, sessionId) {
|
|
|
9406
10749
|
function readHistoryRows(dbPath, sessionId) {
|
|
9407
10750
|
let db = null;
|
|
9408
10751
|
try {
|
|
9409
|
-
const { DatabaseSync } =
|
|
10752
|
+
const { DatabaseSync } = nodeRequire4(
|
|
9410
10753
|
"node:sqlite"
|
|
9411
10754
|
);
|
|
9412
10755
|
db = new DatabaseSync(dbPath, {
|
|
9413
10756
|
readOnly: true,
|
|
9414
10757
|
timeout: 1e3
|
|
9415
10758
|
});
|
|
9416
|
-
const columns =
|
|
10759
|
+
const columns = readTableColumns2(db, "messages");
|
|
9417
10760
|
if (!columns.has("role") || !columns.has("content")) {
|
|
9418
10761
|
return [];
|
|
9419
10762
|
}
|
|
@@ -9518,7 +10861,7 @@ function normalizeHistoryMessage(role, content) {
|
|
|
9518
10861
|
if (role !== "user" && role !== "assistant") {
|
|
9519
10862
|
return null;
|
|
9520
10863
|
}
|
|
9521
|
-
const text =
|
|
10864
|
+
const text = normalizeContent2(content).trim();
|
|
9522
10865
|
if (!text) {
|
|
9523
10866
|
return null;
|
|
9524
10867
|
}
|
|
@@ -9529,7 +10872,7 @@ function normalizeHistoryRecord(record) {
|
|
|
9529
10872
|
if (!role) {
|
|
9530
10873
|
return null;
|
|
9531
10874
|
}
|
|
9532
|
-
const content =
|
|
10875
|
+
const content = normalizeContent2(record.content);
|
|
9533
10876
|
const message = { role, content };
|
|
9534
10877
|
assignString(message, "tool_call_id", record.tool_call_id);
|
|
9535
10878
|
assignString(message, "tool_name", record.tool_name);
|
|
@@ -9549,7 +10892,7 @@ function normalizeHistoryRecord(record) {
|
|
|
9549
10892
|
}
|
|
9550
10893
|
return message;
|
|
9551
10894
|
}
|
|
9552
|
-
function
|
|
10895
|
+
function normalizeContent2(value) {
|
|
9553
10896
|
if (typeof value === "string") {
|
|
9554
10897
|
return value;
|
|
9555
10898
|
}
|
|
@@ -9591,12 +10934,12 @@ function hasReplayMetadata(message) {
|
|
|
9591
10934
|
message.tool_call_id || message.tool_name || message.tool_calls || message.reasoning || message.reasoning_content || message.reasoning_details || message.codex_reasoning_items
|
|
9592
10935
|
);
|
|
9593
10936
|
}
|
|
9594
|
-
function
|
|
10937
|
+
function readTableColumns2(db, table) {
|
|
9595
10938
|
return new Set(
|
|
9596
10939
|
db.prepare(`PRAGMA table_info(${table})`).all().map((row) => row.name).filter((name) => typeof name === "string")
|
|
9597
10940
|
);
|
|
9598
10941
|
}
|
|
9599
|
-
function
|
|
10942
|
+
function isNodeError11(error, code) {
|
|
9600
10943
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
9601
10944
|
}
|
|
9602
10945
|
function isValidProfileName2(value) {
|
|
@@ -9614,8 +10957,8 @@ function normalizeProfileForCompare(value) {
|
|
|
9614
10957
|
|
|
9615
10958
|
// src/hermes/stt.ts
|
|
9616
10959
|
import { execFile as execFile3 } from "child_process";
|
|
9617
|
-
import { access as access2, readFile as
|
|
9618
|
-
import
|
|
10960
|
+
import { access as access2, readFile as readFile11, stat as stat10 } from "fs/promises";
|
|
10961
|
+
import path16 from "path";
|
|
9619
10962
|
import { promisify as promisify3 } from "util";
|
|
9620
10963
|
var execFileAsync3 = promisify3(execFile3);
|
|
9621
10964
|
var STT_RESULT_PREFIX = "__HERMES_LINK_STT__";
|
|
@@ -9710,7 +11053,7 @@ async function buildHermesSttEnv(profileName) {
|
|
|
9710
11053
|
};
|
|
9711
11054
|
const devSource = await findDevHermesAgentSource();
|
|
9712
11055
|
if (devSource) {
|
|
9713
|
-
env.PYTHONPATH = [devSource, env.PYTHONPATH].filter(Boolean).join(
|
|
11056
|
+
env.PYTHONPATH = [devSource, env.PYTHONPATH].filter(Boolean).join(path16.delimiter);
|
|
9714
11057
|
}
|
|
9715
11058
|
return env;
|
|
9716
11059
|
}
|
|
@@ -9757,14 +11100,14 @@ async function resolveHermesPythonCommand() {
|
|
|
9757
11100
|
};
|
|
9758
11101
|
}
|
|
9759
11102
|
async function resolveExecutablePath(command) {
|
|
9760
|
-
if (
|
|
11103
|
+
if (path16.isAbsolute(command)) {
|
|
9761
11104
|
return await isExecutableFile(command) ? command : null;
|
|
9762
11105
|
}
|
|
9763
11106
|
const pathEnv = process.env.PATH ?? "";
|
|
9764
11107
|
const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
|
|
9765
|
-
for (const dir of pathEnv.split(
|
|
11108
|
+
for (const dir of pathEnv.split(path16.delimiter)) {
|
|
9766
11109
|
for (const extension of extensions) {
|
|
9767
|
-
const candidate =
|
|
11110
|
+
const candidate = path16.join(dir, `${command}${extension}`);
|
|
9768
11111
|
if (await isExecutableFile(candidate)) {
|
|
9769
11112
|
return candidate;
|
|
9770
11113
|
}
|
|
@@ -9774,7 +11117,7 @@ async function resolveExecutablePath(command) {
|
|
|
9774
11117
|
}
|
|
9775
11118
|
async function isExecutableFile(filePath) {
|
|
9776
11119
|
try {
|
|
9777
|
-
const info = await
|
|
11120
|
+
const info = await stat10(filePath);
|
|
9778
11121
|
if (!info.isFile()) {
|
|
9779
11122
|
return false;
|
|
9780
11123
|
}
|
|
@@ -9785,7 +11128,7 @@ async function isExecutableFile(filePath) {
|
|
|
9785
11128
|
}
|
|
9786
11129
|
}
|
|
9787
11130
|
async function readShebang(filePath) {
|
|
9788
|
-
const raw = await
|
|
11131
|
+
const raw = await readFile11(filePath, "utf8").catch(() => "");
|
|
9789
11132
|
const firstLine = raw.split(/\r?\n/u)[0]?.trim() ?? "";
|
|
9790
11133
|
return firstLine.startsWith("#!") ? firstLine.slice(2).trim() : null;
|
|
9791
11134
|
}
|
|
@@ -9804,8 +11147,8 @@ function shebangToPythonCommand(shebang) {
|
|
|
9804
11147
|
}
|
|
9805
11148
|
async function findDevHermesAgentSource() {
|
|
9806
11149
|
const candidates = [
|
|
9807
|
-
|
|
9808
|
-
|
|
11150
|
+
path16.resolve(process.cwd(), "reference/hermes-agent"),
|
|
11151
|
+
path16.resolve(process.cwd(), "../../reference/hermes-agent")
|
|
9809
11152
|
];
|
|
9810
11153
|
for (const candidate of candidates) {
|
|
9811
11154
|
if (await isDirectory(candidate)) {
|
|
@@ -9815,7 +11158,7 @@ async function findDevHermesAgentSource() {
|
|
|
9815
11158
|
return null;
|
|
9816
11159
|
}
|
|
9817
11160
|
async function isDirectory(candidate) {
|
|
9818
|
-
return
|
|
11161
|
+
return stat10(candidate).then((info) => info.isDirectory()).catch(() => false);
|
|
9819
11162
|
}
|
|
9820
11163
|
function compactProcessOutput(value) {
|
|
9821
11164
|
const compact = value.trim().replace(/\s+/gu, " ").slice(0, 500);
|
|
@@ -9867,8 +11210,8 @@ function parseSseBlock(block) {
|
|
|
9867
11210
|
if (decoded === null) {
|
|
9868
11211
|
return null;
|
|
9869
11212
|
}
|
|
9870
|
-
const payload =
|
|
9871
|
-
const payloadType = (
|
|
11213
|
+
const payload = toRecord9(decoded);
|
|
11214
|
+
const payloadType = (readString11(payload, "type") ?? readString11(payload, "event") ?? readString11(payload, "object") ?? eventName) || "message";
|
|
9872
11215
|
return { eventName, payloadType, payload, rawPayload: decoded ?? raw };
|
|
9873
11216
|
}
|
|
9874
11217
|
function decodeJson(value) {
|
|
@@ -9884,11 +11227,11 @@ function decodeJson(value) {
|
|
|
9884
11227
|
return { type: "message.delta", delta: value };
|
|
9885
11228
|
}
|
|
9886
11229
|
}
|
|
9887
|
-
function
|
|
11230
|
+
function readString11(body, key) {
|
|
9888
11231
|
const value = body[key];
|
|
9889
11232
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
9890
11233
|
}
|
|
9891
|
-
function
|
|
11234
|
+
function toRecord9(value) {
|
|
9892
11235
|
return typeof value === "object" && value !== null ? value : {};
|
|
9893
11236
|
}
|
|
9894
11237
|
|
|
@@ -9912,8 +11255,8 @@ function normalizeHermesStreamEvent(event) {
|
|
|
9912
11255
|
};
|
|
9913
11256
|
}
|
|
9914
11257
|
if (event.eventName === "hermes.tool.progress") {
|
|
9915
|
-
const toolName =
|
|
9916
|
-
const preview =
|
|
11258
|
+
const toolName = readString12(event.payload, "tool") ?? readString12(event.payload, "name") ?? "tool";
|
|
11259
|
+
const preview = readString12(event.payload, "label") ?? readString12(event.payload, "preview") ?? toolName;
|
|
9917
11260
|
return {
|
|
9918
11261
|
...event,
|
|
9919
11262
|
payloadType: "tool.started",
|
|
@@ -9981,12 +11324,12 @@ function normalizeHermesResponseEvent(event) {
|
|
|
9981
11324
|
}
|
|
9982
11325
|
}
|
|
9983
11326
|
function normalizeResponseOutputItemAdded(event) {
|
|
9984
|
-
const item =
|
|
9985
|
-
if (
|
|
11327
|
+
const item = toRecord10(event.payload.item);
|
|
11328
|
+
if (readString12(item, "type") !== "function_call") {
|
|
9986
11329
|
return null;
|
|
9987
11330
|
}
|
|
9988
|
-
const toolName =
|
|
9989
|
-
const argumentsValue =
|
|
11331
|
+
const toolName = readString12(item, "name") ?? "tool";
|
|
11332
|
+
const argumentsValue = parseJsonValue2(item.arguments) ?? item.arguments;
|
|
9990
11333
|
return {
|
|
9991
11334
|
...event,
|
|
9992
11335
|
payloadType: "tool.started",
|
|
@@ -9995,60 +11338,60 @@ function normalizeResponseOutputItemAdded(event) {
|
|
|
9995
11338
|
tool: toolName,
|
|
9996
11339
|
tool_name: toolName,
|
|
9997
11340
|
name: toolName,
|
|
9998
|
-
tool_call_id:
|
|
11341
|
+
tool_call_id: readString12(item, "call_id") ?? readString12(item, "id"),
|
|
9999
11342
|
arguments: argumentsValue,
|
|
10000
11343
|
preview: toolName,
|
|
10001
|
-
response_item_id:
|
|
11344
|
+
response_item_id: readString12(item, "id") ?? void 0
|
|
10002
11345
|
}
|
|
10003
11346
|
};
|
|
10004
11347
|
}
|
|
10005
11348
|
function normalizeResponseOutputItemDone(event) {
|
|
10006
|
-
const item =
|
|
10007
|
-
if (
|
|
11349
|
+
const item = toRecord10(event.payload.item);
|
|
11350
|
+
if (readString12(item, "type") !== "function_call_output") {
|
|
10008
11351
|
return null;
|
|
10009
11352
|
}
|
|
10010
11353
|
const output = readResponseItemOutput(item.output);
|
|
10011
|
-
const parsedOutput =
|
|
11354
|
+
const parsedOutput = parseJsonValue2(output);
|
|
10012
11355
|
return {
|
|
10013
11356
|
...event,
|
|
10014
11357
|
payloadType: "tool.completed",
|
|
10015
11358
|
payload: {
|
|
10016
11359
|
type: "tool.completed",
|
|
10017
|
-
tool_call_id:
|
|
10018
|
-
status:
|
|
11360
|
+
tool_call_id: readString12(item, "call_id") ?? readString12(item, "id"),
|
|
11361
|
+
status: readString12(item, "status") ?? "completed",
|
|
10019
11362
|
output,
|
|
10020
11363
|
content: output,
|
|
10021
11364
|
result: parsedOutput ?? output,
|
|
10022
|
-
response_item_id:
|
|
11365
|
+
response_item_id: readString12(item, "id") ?? void 0
|
|
10023
11366
|
}
|
|
10024
11367
|
};
|
|
10025
11368
|
}
|
|
10026
11369
|
function normalizeResponseCompleted(event) {
|
|
10027
|
-
const response =
|
|
11370
|
+
const response = toRecord10(event.payload.response);
|
|
10028
11371
|
return {
|
|
10029
11372
|
...event,
|
|
10030
11373
|
payloadType: "run.completed",
|
|
10031
11374
|
payload: {
|
|
10032
11375
|
type: "run.completed",
|
|
10033
|
-
response_id:
|
|
10034
|
-
usage:
|
|
11376
|
+
response_id: readString12(response, "id") ?? readString12(event.payload, "id"),
|
|
11377
|
+
usage: toRecord10(response.usage),
|
|
10035
11378
|
response
|
|
10036
11379
|
}
|
|
10037
11380
|
};
|
|
10038
11381
|
}
|
|
10039
11382
|
function normalizeResponseFailed(event) {
|
|
10040
|
-
const response =
|
|
10041
|
-
const error =
|
|
11383
|
+
const response = toRecord10(event.payload.response);
|
|
11384
|
+
const error = toRecord10(response.error);
|
|
10042
11385
|
return {
|
|
10043
11386
|
...event,
|
|
10044
11387
|
payloadType: "run.failed",
|
|
10045
11388
|
payload: {
|
|
10046
11389
|
type: "run.failed",
|
|
10047
|
-
response_id:
|
|
11390
|
+
response_id: readString12(response, "id") ?? readString12(event.payload, "id"),
|
|
10048
11391
|
error: {
|
|
10049
|
-
message:
|
|
11392
|
+
message: readString12(error, "message") ?? readString12(event.payload, "message") ?? "Hermes run failed"
|
|
10050
11393
|
},
|
|
10051
|
-
usage:
|
|
11394
|
+
usage: toRecord10(response.usage),
|
|
10052
11395
|
response
|
|
10053
11396
|
}
|
|
10054
11397
|
};
|
|
@@ -10072,8 +11415,8 @@ function readErrorMessage2(payload) {
|
|
|
10072
11415
|
if (typeof payload.error === "string" && payload.error.trim()) {
|
|
10073
11416
|
return payload.error.trim();
|
|
10074
11417
|
}
|
|
10075
|
-
const error =
|
|
10076
|
-
return
|
|
11418
|
+
const error = toRecord10(payload.error);
|
|
11419
|
+
return readString12(error, "message") ?? readString12(payload, "message");
|
|
10077
11420
|
}
|
|
10078
11421
|
function readDelta(payload) {
|
|
10079
11422
|
return readText2(payload, "delta") ?? readText2(payload, "text") ?? readText2(payload, "content");
|
|
@@ -10108,20 +11451,21 @@ function isTopLevelErrorEvent(event) {
|
|
|
10108
11451
|
}
|
|
10109
11452
|
function readChatCompletionDelta(payload) {
|
|
10110
11453
|
const choice = readFirstChoice(payload);
|
|
10111
|
-
const delta =
|
|
11454
|
+
const delta = toRecord10(choice.delta);
|
|
10112
11455
|
return readText2(delta, "content");
|
|
10113
11456
|
}
|
|
10114
11457
|
function readChatCompletionFinishReason(payload) {
|
|
10115
11458
|
const choice = readFirstChoice(payload);
|
|
10116
|
-
return
|
|
11459
|
+
return readString12(choice, "finish_reason") ?? readString12(choice, "finishReason");
|
|
10117
11460
|
}
|
|
10118
11461
|
function readChatCompletionUsage(payload) {
|
|
10119
|
-
const usage =
|
|
11462
|
+
const usage = toRecord10(payload.usage);
|
|
10120
11463
|
const input = readInteger2(usage, "prompt_tokens") ?? readInteger2(usage, "input_tokens");
|
|
10121
11464
|
const output = readInteger2(usage, "completion_tokens") ?? readInteger2(usage, "output_tokens");
|
|
10122
11465
|
const total = readInteger2(usage, "total_tokens");
|
|
10123
|
-
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");
|
|
10124
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");
|
|
10125
11469
|
if (input === void 0 && output === void 0 && total === void 0) {
|
|
10126
11470
|
return void 0;
|
|
10127
11471
|
}
|
|
@@ -10132,7 +11476,7 @@ function readChatCompletionUsage(payload) {
|
|
|
10132
11476
|
...explicitContextTokens !== void 0 ? { context_tokens: explicitContextTokens } : {},
|
|
10133
11477
|
...contextWindow !== void 0 ? { context_window: contextWindow } : {},
|
|
10134
11478
|
...explicitContextTokens !== void 0 && contextWindow ? {
|
|
10135
|
-
usage_percent: Math.min(
|
|
11479
|
+
usage_percent: explicitUsagePercent !== void 0 ? Math.min(100, explicitUsagePercent) : Math.min(
|
|
10136
11480
|
100,
|
|
10137
11481
|
Math.round(explicitContextTokens / contextWindow * 100)
|
|
10138
11482
|
)
|
|
@@ -10144,7 +11488,7 @@ function readFirstChoice(payload) {
|
|
|
10144
11488
|
if (!Array.isArray(choices)) {
|
|
10145
11489
|
return {};
|
|
10146
11490
|
}
|
|
10147
|
-
return
|
|
11491
|
+
return toRecord10(choices[0]);
|
|
10148
11492
|
}
|
|
10149
11493
|
function readInteger2(payload, key) {
|
|
10150
11494
|
const value = payload[key];
|
|
@@ -10157,7 +11501,7 @@ function readInteger2(payload, key) {
|
|
|
10157
11501
|
}
|
|
10158
11502
|
return void 0;
|
|
10159
11503
|
}
|
|
10160
|
-
function
|
|
11504
|
+
function readString12(payload, key) {
|
|
10161
11505
|
const value = payload[key];
|
|
10162
11506
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
10163
11507
|
}
|
|
@@ -10172,10 +11516,10 @@ function readResponseItemOutput(value) {
|
|
|
10172
11516
|
if (!Array.isArray(value)) {
|
|
10173
11517
|
return stringifyJsonValue(value);
|
|
10174
11518
|
}
|
|
10175
|
-
const text = value.map(
|
|
11519
|
+
const text = value.map(toRecord10).map((part) => readText2(part, "text") ?? readText2(part, "content") ?? "").join("");
|
|
10176
11520
|
return text || stringifyJsonValue(value);
|
|
10177
11521
|
}
|
|
10178
|
-
function
|
|
11522
|
+
function parseJsonValue2(value) {
|
|
10179
11523
|
if (typeof value !== "string" || !value.trim()) {
|
|
10180
11524
|
return null;
|
|
10181
11525
|
}
|
|
@@ -10198,7 +11542,7 @@ function stringifyJsonValue(value) {
|
|
|
10198
11542
|
return String(value);
|
|
10199
11543
|
}
|
|
10200
11544
|
}
|
|
10201
|
-
function
|
|
11545
|
+
function toRecord10(value) {
|
|
10202
11546
|
return typeof value === "object" && value !== null ? value : {};
|
|
10203
11547
|
}
|
|
10204
11548
|
|
|
@@ -10279,10 +11623,20 @@ var ConversationRunLifecycle = class {
|
|
|
10279
11623
|
});
|
|
10280
11624
|
return void 0;
|
|
10281
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
|
+
}
|
|
10282
11636
|
const response = await streamHermesResponses(
|
|
10283
11637
|
{
|
|
10284
11638
|
input: resolvedInput,
|
|
10285
|
-
instructions
|
|
11639
|
+
instructions,
|
|
10286
11640
|
session_id: hermesSessionId,
|
|
10287
11641
|
model: run.model,
|
|
10288
11642
|
...previousResponseId ? { previous_response_id: previousResponseId } : {},
|
|
@@ -10615,7 +11969,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
10615
11969
|
assistant.agent_events ?? [],
|
|
10616
11970
|
agentEvent
|
|
10617
11971
|
);
|
|
10618
|
-
|
|
11972
|
+
appendAgentEventBlock2(assistant, agentEvent, event.created_at);
|
|
10619
11973
|
assistant.updated_at = event.created_at;
|
|
10620
11974
|
await this.deps.writeSnapshot(conversationId, snapshot);
|
|
10621
11975
|
}
|
|
@@ -10684,8 +12038,8 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
10684
12038
|
}
|
|
10685
12039
|
const textPart = assistant.parts.find((part) => part.type === "text");
|
|
10686
12040
|
const currentText = textPart?.text ?? "";
|
|
10687
|
-
const pendingDeliveryText =
|
|
10688
|
-
|
|
12041
|
+
const pendingDeliveryText = readString13(
|
|
12042
|
+
toRecord11(assistant.hermes),
|
|
10689
12043
|
"pending_media_delivery_text"
|
|
10690
12044
|
);
|
|
10691
12045
|
const normalizedDelta = normalizeStreamingTextDelta(
|
|
@@ -10700,7 +12054,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
10700
12054
|
pendingDeliveryText ?? ""
|
|
10701
12055
|
);
|
|
10702
12056
|
const nextHermes = {
|
|
10703
|
-
...
|
|
12057
|
+
...toRecord11(assistant.hermes),
|
|
10704
12058
|
...extracted.pendingText ? { pending_media_delivery_text: extracted.pendingText } : {}
|
|
10705
12059
|
};
|
|
10706
12060
|
if (!extracted.pendingText) {
|
|
@@ -10716,7 +12070,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
10716
12070
|
assistant.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
10717
12071
|
assistant.raw = { format: "hermes-run-event", payload: event.rawPayload };
|
|
10718
12072
|
if (extracted.visibleText) {
|
|
10719
|
-
|
|
12073
|
+
appendTextBlock2(assistant, extracted.visibleText, assistant.updated_at);
|
|
10720
12074
|
}
|
|
10721
12075
|
await this.deps.writeSnapshot(conversationId, snapshot);
|
|
10722
12076
|
if (extracted.visibleText) {
|
|
@@ -10785,7 +12139,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
10785
12139
|
run.hermes_response_id = responseId;
|
|
10786
12140
|
}
|
|
10787
12141
|
if (usage) {
|
|
10788
|
-
run.usage = usage;
|
|
12142
|
+
run.usage = mergeRunUsage(run.usage, usage);
|
|
10789
12143
|
}
|
|
10790
12144
|
if (assistant) {
|
|
10791
12145
|
assistant.status = "completed";
|
|
@@ -10837,7 +12191,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
10837
12191
|
const visibleMessage = formatFailureMessage(message, run.error_detail);
|
|
10838
12192
|
const usage = readUsage(source?.payload);
|
|
10839
12193
|
if (usage) {
|
|
10840
|
-
run.usage = usage;
|
|
12194
|
+
run.usage = mergeRunUsage(run.usage, usage);
|
|
10841
12195
|
}
|
|
10842
12196
|
const assistant = snapshot.messages.find(
|
|
10843
12197
|
(item) => item.id === run.assistant_message_id
|
|
@@ -10856,6 +12210,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
10856
12210
|
}
|
|
10857
12211
|
}
|
|
10858
12212
|
await this.deps.writeSnapshot(conversationId, snapshot);
|
|
12213
|
+
const contextUsage = contextUsagePayload(run);
|
|
10859
12214
|
await this.deps.appendEvent(conversationId, {
|
|
10860
12215
|
type: "run.failed",
|
|
10861
12216
|
message_id: assistant?.id,
|
|
@@ -10864,6 +12219,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
10864
12219
|
error: { message },
|
|
10865
12220
|
...run.error_detail ? { error_detail: run.error_detail } : {},
|
|
10866
12221
|
run,
|
|
12222
|
+
...contextUsage ? { context: contextUsage } : {},
|
|
10867
12223
|
...source ? { hermes: source.payload } : {}
|
|
10868
12224
|
},
|
|
10869
12225
|
...source ? { raw: { format: "hermes-run-event", payload: source.rawPayload } } : {}
|
|
@@ -10967,7 +12323,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
10967
12323
|
includeDisabled: true
|
|
10968
12324
|
});
|
|
10969
12325
|
return new Set(
|
|
10970
|
-
jobs.map((job) =>
|
|
12326
|
+
jobs.map((job) => readString13(job, "id") ?? readString13(job, "job_id")).filter((id) => Boolean(id))
|
|
10971
12327
|
);
|
|
10972
12328
|
}
|
|
10973
12329
|
async bindNewCronJobsCreatedByRun(input) {
|
|
@@ -11003,7 +12359,7 @@ function buildRunInstructions(run, deliveryStagingDir) {
|
|
|
11003
12359
|
].join("\n");
|
|
11004
12360
|
}
|
|
11005
12361
|
function appendMediaImportFailureNotice(message) {
|
|
11006
|
-
const hermes =
|
|
12362
|
+
const hermes = toRecord11(message.hermes);
|
|
11007
12363
|
if (hermes.media_import_failure_notice_appended === true) {
|
|
11008
12364
|
return;
|
|
11009
12365
|
}
|
|
@@ -11046,18 +12402,18 @@ function formatFilenameList(filenames) {
|
|
|
11046
12402
|
return remaining > 0 ? `${preview.join("\u3001")} \u7B49 ${filenames.length} \u4E2A` : preview.join("\u3001");
|
|
11047
12403
|
}
|
|
11048
12404
|
async function readdirWithDirs(directory) {
|
|
11049
|
-
return
|
|
11050
|
-
if (
|
|
12405
|
+
return readdir7(directory, { withFileTypes: true }).catch((error) => {
|
|
12406
|
+
if (isNodeError12(error, "ENOENT")) {
|
|
11051
12407
|
return [];
|
|
11052
12408
|
}
|
|
11053
12409
|
throw error;
|
|
11054
12410
|
});
|
|
11055
12411
|
}
|
|
11056
|
-
function
|
|
12412
|
+
function readString13(payload, key) {
|
|
11057
12413
|
const value = payload[key];
|
|
11058
12414
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
11059
12415
|
}
|
|
11060
|
-
function
|
|
12416
|
+
function toRecord11(value) {
|
|
11061
12417
|
return typeof value === "object" && value !== null ? value : {};
|
|
11062
12418
|
}
|
|
11063
12419
|
function formatFailureMessage(message, detail) {
|
|
@@ -11073,17 +12429,17 @@ function isFileSearchCompletion(payloadType, payload) {
|
|
|
11073
12429
|
if (payloadType !== "tool.completed") {
|
|
11074
12430
|
return false;
|
|
11075
12431
|
}
|
|
11076
|
-
const tool =
|
|
11077
|
-
const toolCall =
|
|
11078
|
-
const fn =
|
|
12432
|
+
const tool = toRecord11(payload.tool);
|
|
12433
|
+
const toolCall = toRecord11(payload.tool_call ?? payload.toolCall);
|
|
12434
|
+
const fn = toRecord11(toolCall.function ?? payload.function);
|
|
11079
12435
|
const candidates = [
|
|
11080
|
-
|
|
11081
|
-
|
|
11082
|
-
|
|
11083
|
-
|
|
11084
|
-
|
|
11085
|
-
|
|
11086
|
-
|
|
12436
|
+
readString13(payload, "tool_name"),
|
|
12437
|
+
readString13(payload, "toolName"),
|
|
12438
|
+
readString13(payload, "name"),
|
|
12439
|
+
readString13(payload, "tool"),
|
|
12440
|
+
readString13(tool, "name"),
|
|
12441
|
+
readString13(toolCall, "name"),
|
|
12442
|
+
readString13(fn, "name")
|
|
11087
12443
|
].filter((value) => Boolean(value)).map(normalizeToolName);
|
|
11088
12444
|
return candidates.some(
|
|
11089
12445
|
(name) => [
|
|
@@ -11126,7 +12482,7 @@ function isVoicePart(part) {
|
|
|
11126
12482
|
function normalizeToolName(value) {
|
|
11127
12483
|
return value.trim().toLowerCase().replace(/[\s-]+/gu, "_");
|
|
11128
12484
|
}
|
|
11129
|
-
function
|
|
12485
|
+
function appendTextBlock2(message, delta, updatedAt) {
|
|
11130
12486
|
if (!delta) {
|
|
11131
12487
|
return;
|
|
11132
12488
|
}
|
|
@@ -11149,7 +12505,7 @@ function appendTextBlock(message, delta, updatedAt) {
|
|
|
11149
12505
|
}
|
|
11150
12506
|
message.blocks = blocks;
|
|
11151
12507
|
}
|
|
11152
|
-
function
|
|
12508
|
+
function appendAgentEventBlock2(message, event, updatedAt) {
|
|
11153
12509
|
const blocks = [...message.blocks ?? []];
|
|
11154
12510
|
const matchingIndex = blocks.findIndex((block) => {
|
|
11155
12511
|
if (block.type !== "agent_events") {
|
|
@@ -11195,6 +12551,7 @@ function contextUsagePayload(run) {
|
|
|
11195
12551
|
source: "unknown"
|
|
11196
12552
|
};
|
|
11197
12553
|
}
|
|
12554
|
+
const contextSource = usage.context_source ?? "explicit";
|
|
11198
12555
|
return {
|
|
11199
12556
|
input_tokens: contextTokens,
|
|
11200
12557
|
output_tokens: usage?.output_tokens ?? 0,
|
|
@@ -11202,7 +12559,7 @@ function contextUsagePayload(run) {
|
|
|
11202
12559
|
...contextWindow ? { context_window: contextWindow } : {},
|
|
11203
12560
|
used_tokens: contextTokens,
|
|
11204
12561
|
...contextWindow ? { window_tokens: contextWindow } : {},
|
|
11205
|
-
source:
|
|
12562
|
+
source: contextSource,
|
|
11206
12563
|
...usage?.usage_percent !== void 0 ? { usage_percent: usage.usage_percent } : contextWindow ? {
|
|
11207
12564
|
usage_percent: Math.min(
|
|
11208
12565
|
100,
|
|
@@ -11211,6 +12568,41 @@ function contextUsagePayload(run) {
|
|
|
11211
12568
|
} : {}
|
|
11212
12569
|
};
|
|
11213
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
|
+
}
|
|
11214
12606
|
function findPreviousHermesResponseId(snapshot, run) {
|
|
11215
12607
|
const currentProfile = normalizeRunProfileForCompare(run.profile);
|
|
11216
12608
|
if (!currentProfile) {
|
|
@@ -11238,10 +12630,10 @@ function readResponseId(payload) {
|
|
|
11238
12630
|
if (!payload) {
|
|
11239
12631
|
return null;
|
|
11240
12632
|
}
|
|
11241
|
-
const response =
|
|
11242
|
-
return
|
|
12633
|
+
const response = toRecord11(payload.response);
|
|
12634
|
+
return readString13(payload, "response_id") ?? readString13(response, "id");
|
|
11243
12635
|
}
|
|
11244
|
-
function
|
|
12636
|
+
function isNodeError12(error, code) {
|
|
11245
12637
|
if (typeof error !== "object" || error === null || !("code" in error)) {
|
|
11246
12638
|
return false;
|
|
11247
12639
|
}
|
|
@@ -11330,6 +12722,7 @@ var ConversationService = class {
|
|
|
11330
12722
|
orchestration;
|
|
11331
12723
|
queries;
|
|
11332
12724
|
runLifecycle;
|
|
12725
|
+
hermesSessionSyncPromise = null;
|
|
11333
12726
|
async withConversationLock(conversationId, task) {
|
|
11334
12727
|
const previous = this.conversationLocks.get(conversationId) ?? Promise.resolve();
|
|
11335
12728
|
let release;
|
|
@@ -11390,7 +12783,7 @@ var ConversationService = class {
|
|
|
11390
12783
|
async createConversation(input = {}) {
|
|
11391
12784
|
await this.store.ensureConversationsDir();
|
|
11392
12785
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11393
|
-
const id = `conv_${
|
|
12786
|
+
const id = `conv_${randomUUID7().replaceAll("-", "")}`;
|
|
11394
12787
|
const title = input.title?.trim() || DEFAULT_CONVERSATION_TITLE;
|
|
11395
12788
|
const profile = await resolveConversationProfileTarget(
|
|
11396
12789
|
this.paths,
|
|
@@ -11477,7 +12870,7 @@ var ConversationService = class {
|
|
|
11477
12870
|
manifest.profile_name_snapshot ?? manifest.profile ?? input.profileName
|
|
11478
12871
|
);
|
|
11479
12872
|
const message = {
|
|
11480
|
-
id: `msg_${
|
|
12873
|
+
id: `msg_${randomUUID7().replaceAll("-", "")}`,
|
|
11481
12874
|
schema_version: 1,
|
|
11482
12875
|
conversation_id: manifest.id,
|
|
11483
12876
|
role: "assistant",
|
|
@@ -11521,6 +12914,29 @@ var ConversationService = class {
|
|
|
11521
12914
|
async syncCronDeliveries() {
|
|
11522
12915
|
await syncHermesLinkCronDeliveries(this.paths, this, this.logger);
|
|
11523
12916
|
}
|
|
12917
|
+
async syncHermesSessions() {
|
|
12918
|
+
if (this.hermesSessionSyncPromise) {
|
|
12919
|
+
return this.hermesSessionSyncPromise;
|
|
12920
|
+
}
|
|
12921
|
+
const task = (async () => {
|
|
12922
|
+
const result = await syncHermesSessionsIntoConversations(
|
|
12923
|
+
this.paths,
|
|
12924
|
+
this.logger
|
|
12925
|
+
);
|
|
12926
|
+
if (result.imported_count > 0 || result.reprojected_count > 0) {
|
|
12927
|
+
await this.rebuildStatisticsIndex();
|
|
12928
|
+
}
|
|
12929
|
+
return result;
|
|
12930
|
+
})();
|
|
12931
|
+
this.hermesSessionSyncPromise = task;
|
|
12932
|
+
try {
|
|
12933
|
+
return await task;
|
|
12934
|
+
} finally {
|
|
12935
|
+
if (this.hermesSessionSyncPromise === task) {
|
|
12936
|
+
this.hermesSessionSyncPromise = null;
|
|
12937
|
+
}
|
|
12938
|
+
}
|
|
12939
|
+
}
|
|
11524
12940
|
async deliverStagedFiles(stagingDir) {
|
|
11525
12941
|
const target = resolveDeliveryStagingTarget(this.paths, stagingDir);
|
|
11526
12942
|
return this.withConversationLock(target.conversationId, async () => {
|
|
@@ -12046,7 +13462,7 @@ function findApproval(snapshot, approvalId) {
|
|
|
12046
13462
|
}
|
|
12047
13463
|
|
|
12048
13464
|
// src/identity/identity.ts
|
|
12049
|
-
import { generateKeyPairSync, randomUUID as
|
|
13465
|
+
import { generateKeyPairSync, randomUUID as randomUUID8, sign } from "crypto";
|
|
12050
13466
|
import { mkdir as mkdir9, chmod } from "fs/promises";
|
|
12051
13467
|
import { z } from "zod";
|
|
12052
13468
|
var linkIdentitySchema = z.object({
|
|
@@ -12074,7 +13490,7 @@ async function ensureIdentity(paths = resolveRuntimePaths()) {
|
|
|
12074
13490
|
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
12075
13491
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12076
13492
|
const identity = {
|
|
12077
|
-
install_id: `install_${
|
|
13493
|
+
install_id: `install_${randomUUID8().replaceAll("-", "")}`,
|
|
12078
13494
|
link_id: null,
|
|
12079
13495
|
public_key_pem: publicKey.export({ type: "spki", format: "pem" }).toString(),
|
|
12080
13496
|
private_key_pem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
|
|
@@ -12111,7 +13527,7 @@ function getIdentityStatus(identity) {
|
|
|
12111
13527
|
}
|
|
12112
13528
|
|
|
12113
13529
|
// src/security/devices.ts
|
|
12114
|
-
import { randomBytes as randomBytes2, randomUUID as
|
|
13530
|
+
import { randomBytes as randomBytes2, randomUUID as randomUUID9, timingSafeEqual, createHash as createHash4 } from "crypto";
|
|
12115
13531
|
var ACCESS_TOKEN_TTL_MS = 15 * 60 * 1e3;
|
|
12116
13532
|
var REFRESH_TOKEN_TTL_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
12117
13533
|
var DEVICE_SEEN_WRITE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
@@ -12129,7 +13545,7 @@ async function createDeviceSession(input, paths = resolveRuntimePaths()) {
|
|
|
12129
13545
|
}
|
|
12130
13546
|
}
|
|
12131
13547
|
const device = {
|
|
12132
|
-
id: `dev_${
|
|
13548
|
+
id: `dev_${randomUUID9().replaceAll("-", "")}`,
|
|
12133
13549
|
label: normalizeDeviceLabel(input.label),
|
|
12134
13550
|
platform: normalizeDevicePlatform(input.platform),
|
|
12135
13551
|
model: normalizeDeviceModel(input.model),
|
|
@@ -12578,12 +13994,12 @@ async function readRawBody(request, maxBytes) {
|
|
|
12578
13994
|
}
|
|
12579
13995
|
return Buffer.concat(chunks);
|
|
12580
13996
|
}
|
|
12581
|
-
function
|
|
13997
|
+
function readString14(body, key) {
|
|
12582
13998
|
const value = body[key];
|
|
12583
13999
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
12584
14000
|
}
|
|
12585
14001
|
function readOptionalProfileName(body) {
|
|
12586
|
-
return
|
|
14002
|
+
return readString14(body, "profile") ?? readString14(body, "profile_name") ?? readString14(body, "profileName") ?? void 0;
|
|
12587
14003
|
}
|
|
12588
14004
|
function readStringArray(body, ...keys) {
|
|
12589
14005
|
for (const key of keys) {
|
|
@@ -12621,7 +14037,7 @@ function readPositiveInteger2(value) {
|
|
|
12621
14037
|
}
|
|
12622
14038
|
return void 0;
|
|
12623
14039
|
}
|
|
12624
|
-
function
|
|
14040
|
+
function readBoolean2(value) {
|
|
12625
14041
|
if (typeof value === "boolean") {
|
|
12626
14042
|
return value;
|
|
12627
14043
|
}
|
|
@@ -12724,7 +14140,7 @@ function readMessageAttachments(value) {
|
|
|
12724
14140
|
}
|
|
12725
14141
|
const kind = readAttachmentString(record.kind);
|
|
12726
14142
|
const type = readAttachmentString(record.type);
|
|
12727
|
-
const isVoiceNote =
|
|
14143
|
+
const isVoiceNote = readBoolean2(record.is_voice_note) ?? readBoolean2(record.isVoiceNote);
|
|
12728
14144
|
const durationMs = readPositiveInteger2(record.duration_ms) ?? readPositiveInteger2(record.durationMs);
|
|
12729
14145
|
const waveform = readAttachmentWaveform2(
|
|
12730
14146
|
record.waveform ?? record.waveform_samples ?? record.waveformSamples
|
|
@@ -12790,13 +14206,84 @@ function isLanHost(hostname) {
|
|
|
12790
14206
|
}
|
|
12791
14207
|
|
|
12792
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
|
+
}
|
|
12793
14251
|
function writeSseEvent(response, event) {
|
|
12794
|
-
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}
|
|
12795
14268
|
`);
|
|
12796
|
-
response.write(`data: ${JSON.stringify(event)}
|
|
14269
|
+
response.write(`data: ${JSON.stringify(event.data)}
|
|
12797
14270
|
|
|
12798
14271
|
`);
|
|
12799
14272
|
}
|
|
14273
|
+
function writeSseComment(response, comment = "keep-alive") {
|
|
14274
|
+
response.write(`: ${comment}
|
|
14275
|
+
|
|
14276
|
+
`);
|
|
14277
|
+
}
|
|
14278
|
+
function writeSseRetry(response, retryMs) {
|
|
14279
|
+
response.write(`retry: ${normalizeRetryMs(retryMs)}
|
|
14280
|
+
|
|
14281
|
+
`);
|
|
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
|
+
}
|
|
12800
14287
|
|
|
12801
14288
|
// src/http/routes/conversations.ts
|
|
12802
14289
|
function registerConversationRoutes(router, options) {
|
|
@@ -12816,7 +14303,7 @@ function registerConversationRoutes(router, options) {
|
|
|
12816
14303
|
ctx.body = {
|
|
12817
14304
|
ok: true,
|
|
12818
14305
|
conversation: await conversations.createConversation({
|
|
12819
|
-
title:
|
|
14306
|
+
title: readString14(body, "title") ?? void 0,
|
|
12820
14307
|
profileName: readOptionalProfileName(body)
|
|
12821
14308
|
})
|
|
12822
14309
|
};
|
|
@@ -12840,54 +14327,49 @@ function registerConversationRoutes(router, options) {
|
|
|
12840
14327
|
const notificationOnly = mode === "notifications";
|
|
12841
14328
|
ctx.respond = false;
|
|
12842
14329
|
const response = ctx.res;
|
|
12843
|
-
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
|
|
14330
|
+
let unsubscribe = () => {
|
|
14331
|
+
};
|
|
14332
|
+
beginSseStream(ctx.req, response, {
|
|
14333
|
+
onClose: () => unsubscribe()
|
|
14334
|
+
});
|
|
14335
|
+
unsubscribe = conversations.subscribeAll((event) => {
|
|
12848
14336
|
if (notificationOnly && !isConversationNotificationEvent(event)) {
|
|
12849
14337
|
return;
|
|
12850
14338
|
}
|
|
12851
14339
|
writeSseEvent(response, event);
|
|
12852
14340
|
});
|
|
12853
|
-
const cleanup = () => {
|
|
12854
|
-
unsubscribe();
|
|
12855
|
-
response.end();
|
|
12856
|
-
};
|
|
12857
|
-
ctx.req.on("close", cleanup);
|
|
12858
14341
|
});
|
|
12859
14342
|
router.get("/api/v1/conversations/:conversationId/events", async (ctx) => {
|
|
12860
14343
|
await authenticateRequest(ctx, paths);
|
|
12861
|
-
const after =
|
|
14344
|
+
const after = resolveConversationEventCursor({
|
|
14345
|
+
queryAfter: ctx.query.after,
|
|
14346
|
+
lastEventIdHeader: ctx.req.headers["last-event-id"]
|
|
14347
|
+
});
|
|
12862
14348
|
const history = await conversations.listEvents(
|
|
12863
14349
|
ctx.params.conversationId,
|
|
12864
14350
|
after
|
|
12865
14351
|
);
|
|
12866
14352
|
ctx.respond = false;
|
|
12867
14353
|
const response = ctx.res;
|
|
12868
|
-
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
|
|
14354
|
+
let unsubscribe = () => {
|
|
14355
|
+
};
|
|
14356
|
+
beginSseStream(ctx.req, response, {
|
|
14357
|
+
onClose: () => unsubscribe()
|
|
14358
|
+
});
|
|
12872
14359
|
for (const event of history) {
|
|
12873
14360
|
writeSseEvent(response, event);
|
|
12874
14361
|
}
|
|
12875
|
-
|
|
14362
|
+
unsubscribe = conversations.subscribe(
|
|
12876
14363
|
ctx.params.conversationId,
|
|
12877
14364
|
(event) => {
|
|
12878
14365
|
writeSseEvent(response, event);
|
|
12879
14366
|
}
|
|
12880
14367
|
);
|
|
12881
|
-
const cleanup = () => {
|
|
12882
|
-
unsubscribe();
|
|
12883
|
-
response.end();
|
|
12884
|
-
};
|
|
12885
|
-
ctx.req.on("close", cleanup);
|
|
12886
14368
|
});
|
|
12887
14369
|
router.post("/api/v1/conversations/:conversationId/messages", async (ctx) => {
|
|
12888
14370
|
await authenticateRequest(ctx, paths);
|
|
12889
14371
|
const body = await readJsonBody(ctx.req);
|
|
12890
|
-
const content =
|
|
14372
|
+
const content = readString14(body, "content") ?? readString14(body, "text") ?? readString14(body, "input") ?? "";
|
|
12891
14373
|
const attachments = readMessageAttachments(body.attachments ?? body.blobs);
|
|
12892
14374
|
if (!content && attachments.length === 0) {
|
|
12893
14375
|
throw new LinkHttpError(
|
|
@@ -12903,7 +14385,7 @@ function registerConversationRoutes(router, options) {
|
|
|
12903
14385
|
conversationId: ctx.params.conversationId,
|
|
12904
14386
|
content,
|
|
12905
14387
|
attachments,
|
|
12906
|
-
clientMessageId:
|
|
14388
|
+
clientMessageId: readString14(body, "client_message_id") ?? readString14(body, "clientMessageId") ?? void 0,
|
|
12907
14389
|
idempotencyKey: readHeader(ctx, "idempotency-key") ?? void 0,
|
|
12908
14390
|
profileName: readOptionalProfileName(body)
|
|
12909
14391
|
})
|
|
@@ -12912,7 +14394,7 @@ function registerConversationRoutes(router, options) {
|
|
|
12912
14394
|
router.patch("/api/v1/conversations/:conversationId/model", async (ctx) => {
|
|
12913
14395
|
await authenticateRequest(ctx, paths);
|
|
12914
14396
|
const body = await readJsonBody(ctx.req);
|
|
12915
|
-
const modelId =
|
|
14397
|
+
const modelId = readString14(body, "model_id") ?? readString14(body, "modelId") ?? readString14(body, "model");
|
|
12916
14398
|
if (!modelId) {
|
|
12917
14399
|
throw new LinkHttpError(400, "model_id_required", "model_id is required");
|
|
12918
14400
|
}
|
|
@@ -12942,7 +14424,7 @@ function registerConversationRoutes(router, options) {
|
|
|
12942
14424
|
router.patch("/api/v1/conversations/:conversationId/title", async (ctx) => {
|
|
12943
14425
|
await authenticateRequest(ctx, paths);
|
|
12944
14426
|
const body = await readJsonBody(ctx.req);
|
|
12945
|
-
const title =
|
|
14427
|
+
const title = readString14(body, "title") ?? readString14(body, "name") ?? readString14(body, "display_name");
|
|
12946
14428
|
if (!title) {
|
|
12947
14429
|
throw new LinkHttpError(400, "title_required", "title is required");
|
|
12948
14430
|
}
|
|
@@ -13006,7 +14488,7 @@ function registerConversationRoutes(router, options) {
|
|
|
13006
14488
|
async (ctx) => {
|
|
13007
14489
|
await authenticateRequest(ctx, paths);
|
|
13008
14490
|
const body = await readJsonBody(ctx.req);
|
|
13009
|
-
const scope =
|
|
14491
|
+
const scope = readString14(body, "scope") ?? "always";
|
|
13010
14492
|
ctx.body = {
|
|
13011
14493
|
ok: true,
|
|
13012
14494
|
...await conversations.resolveApproval({
|
|
@@ -13085,6 +14567,19 @@ function registerConversationRoutes(router, options) {
|
|
|
13085
14567
|
}
|
|
13086
14568
|
);
|
|
13087
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
|
+
}
|
|
13088
14583
|
function contentDispositionInline(filename) {
|
|
13089
14584
|
const fallback = asciiFilenameFallback(filename);
|
|
13090
14585
|
return `inline; filename="${fallback}"; filename*=UTF-8''${encodeRfc5987Value(filename)}`;
|
|
@@ -13166,26 +14661,26 @@ function createHttpErrorMiddleware(logger) {
|
|
|
13166
14661
|
}
|
|
13167
14662
|
|
|
13168
14663
|
// src/hermes/profiles.ts
|
|
13169
|
-
import { mkdir as mkdir10, readdir as
|
|
13170
|
-
import
|
|
13171
|
-
import
|
|
14664
|
+
import { mkdir as mkdir10, readdir as readdir8, readFile as readFile12, rename as rename4, rm as rm6, stat as stat11 } from "fs/promises";
|
|
14665
|
+
import os5 from "os";
|
|
14666
|
+
import path17 from "path";
|
|
13172
14667
|
import YAML2 from "yaml";
|
|
13173
14668
|
var DEFAULT_PROFILE = "default";
|
|
13174
|
-
var
|
|
14669
|
+
var PROFILE_NAME_PATTERN4 = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
13175
14670
|
async function listHermesProfiles(paths = resolveRuntimePaths()) {
|
|
13176
14671
|
const profiles = /* @__PURE__ */ new Map();
|
|
13177
14672
|
profiles.set(DEFAULT_PROFILE, await profileInfo(DEFAULT_PROFILE, paths));
|
|
13178
|
-
const profilesDir =
|
|
13179
|
-
const entries = await
|
|
14673
|
+
const profilesDir = path17.join(os5.homedir(), ".hermes", "profiles");
|
|
14674
|
+
const entries = await readdir8(profilesDir, { withFileTypes: true }).catch(
|
|
13180
14675
|
(error) => {
|
|
13181
|
-
if (
|
|
14676
|
+
if (isNodeError13(error, "ENOENT")) {
|
|
13182
14677
|
return [];
|
|
13183
14678
|
}
|
|
13184
14679
|
throw error;
|
|
13185
14680
|
}
|
|
13186
14681
|
);
|
|
13187
14682
|
for (const entry of entries) {
|
|
13188
|
-
if (entry.isDirectory() &&
|
|
14683
|
+
if (entry.isDirectory() && PROFILE_NAME_PATTERN4.test(entry.name)) {
|
|
13189
14684
|
profiles.set(entry.name, await profileInfo(entry.name, paths));
|
|
13190
14685
|
}
|
|
13191
14686
|
}
|
|
@@ -13202,8 +14697,8 @@ async function listHermesProfiles(paths = resolveRuntimePaths()) {
|
|
|
13202
14697
|
async function getHermesProfileStatus(name, paths = resolveRuntimePaths()) {
|
|
13203
14698
|
assertProfileName(name);
|
|
13204
14699
|
const profile = await profileInfo(name, paths);
|
|
13205
|
-
const exists = await
|
|
13206
|
-
if (
|
|
14700
|
+
const exists = await stat11(profile.path).then((value) => value.isDirectory()).catch((error) => {
|
|
14701
|
+
if (isNodeError13(error, "ENOENT")) {
|
|
13207
14702
|
return false;
|
|
13208
14703
|
}
|
|
13209
14704
|
throw error;
|
|
@@ -13243,8 +14738,8 @@ async function updateHermesProfileMetadata(name, metadata, paths = resolveRuntim
|
|
|
13243
14738
|
async function deleteHermesProfile(name, paths = resolveRuntimePaths()) {
|
|
13244
14739
|
assertMutableProfile(name);
|
|
13245
14740
|
const profile = await profileInfo(name, paths);
|
|
13246
|
-
const exists = await
|
|
13247
|
-
if (
|
|
14741
|
+
const exists = await stat11(profile.path).then((value) => value.isDirectory()).catch((error) => {
|
|
14742
|
+
if (isNodeError13(error, "ENOENT")) {
|
|
13248
14743
|
return false;
|
|
13249
14744
|
}
|
|
13250
14745
|
throw error;
|
|
@@ -13270,7 +14765,7 @@ async function readHermesProfileCapabilities(name) {
|
|
|
13270
14765
|
return {
|
|
13271
14766
|
defaultModel: listedModels?.defaultModel ?? null,
|
|
13272
14767
|
modelCount: listedModels?.models.length ?? 0,
|
|
13273
|
-
skillCount: await countSkills(
|
|
14768
|
+
skillCount: await countSkills(path17.join(profileDir, "skills")).catch(
|
|
13274
14769
|
() => 0
|
|
13275
14770
|
),
|
|
13276
14771
|
toolCount: await countConfiguredTools(name).catch(() => 0)
|
|
@@ -13308,17 +14803,17 @@ function assertMutableProfile(name) {
|
|
|
13308
14803
|
}
|
|
13309
14804
|
}
|
|
13310
14805
|
function assertProfileName(name) {
|
|
13311
|
-
if (!
|
|
14806
|
+
if (!PROFILE_NAME_PATTERN4.test(name)) {
|
|
13312
14807
|
throw new LinkHttpError(400, "invalid_profile_name", "invalid profile name");
|
|
13313
14808
|
}
|
|
13314
14809
|
}
|
|
13315
|
-
function
|
|
14810
|
+
function isNodeError13(error, code) {
|
|
13316
14811
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
13317
14812
|
}
|
|
13318
14813
|
async function countSkills(root) {
|
|
13319
|
-
const entries = await
|
|
14814
|
+
const entries = await readdir8(root, { withFileTypes: true }).catch(
|
|
13320
14815
|
(error) => {
|
|
13321
|
-
if (
|
|
14816
|
+
if (isNodeError13(error, "ENOENT")) {
|
|
13322
14817
|
return [];
|
|
13323
14818
|
}
|
|
13324
14819
|
throw error;
|
|
@@ -13326,7 +14821,7 @@ async function countSkills(root) {
|
|
|
13326
14821
|
);
|
|
13327
14822
|
let count = 0;
|
|
13328
14823
|
for (const entry of entries) {
|
|
13329
|
-
const entryPath =
|
|
14824
|
+
const entryPath = path17.join(root, entry.name);
|
|
13330
14825
|
if (entry.name === ".git" || entry.name === ".hub") {
|
|
13331
14826
|
continue;
|
|
13332
14827
|
}
|
|
@@ -13341,11 +14836,11 @@ async function countSkills(root) {
|
|
|
13341
14836
|
return count;
|
|
13342
14837
|
}
|
|
13343
14838
|
async function countConfiguredTools(profileName) {
|
|
13344
|
-
const raw = await
|
|
14839
|
+
const raw = await readFile12(
|
|
13345
14840
|
resolveHermesConfigPath(profileName),
|
|
13346
14841
|
"utf8"
|
|
13347
14842
|
).catch((error) => {
|
|
13348
|
-
if (
|
|
14843
|
+
if (isNodeError13(error, "ENOENT")) {
|
|
13349
14844
|
return "";
|
|
13350
14845
|
}
|
|
13351
14846
|
throw error;
|
|
@@ -13353,14 +14848,14 @@ async function countConfiguredTools(profileName) {
|
|
|
13353
14848
|
if (!raw.trim()) {
|
|
13354
14849
|
return 0;
|
|
13355
14850
|
}
|
|
13356
|
-
const config =
|
|
14851
|
+
const config = toRecord12(YAML2.parse(raw));
|
|
13357
14852
|
const toolsets = /* @__PURE__ */ new Set();
|
|
13358
14853
|
collectToolsetValues(config.toolsets, toolsets);
|
|
13359
|
-
const platformToolsets =
|
|
14854
|
+
const platformToolsets = toRecord12(config.platform_toolsets);
|
|
13360
14855
|
for (const value of Object.values(platformToolsets)) {
|
|
13361
14856
|
collectToolsetValues(value, toolsets);
|
|
13362
14857
|
}
|
|
13363
|
-
const mcpServers = Object.keys(
|
|
14858
|
+
const mcpServers = Object.keys(toRecord12(config.mcp_servers)).length;
|
|
13364
14859
|
return toolsets.size + mcpServers;
|
|
13365
14860
|
}
|
|
13366
14861
|
function collectToolsetValues(value, target) {
|
|
@@ -13374,7 +14869,7 @@ function collectToolsetValues(value, target) {
|
|
|
13374
14869
|
target.add(value.trim());
|
|
13375
14870
|
}
|
|
13376
14871
|
}
|
|
13377
|
-
function
|
|
14872
|
+
function toRecord12(value) {
|
|
13378
14873
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
13379
14874
|
}
|
|
13380
14875
|
|
|
@@ -13571,7 +15066,7 @@ function toHermesCronJobInput(input) {
|
|
|
13571
15066
|
};
|
|
13572
15067
|
}
|
|
13573
15068
|
async function bindAndDecorateCronJobForHermesLink(input) {
|
|
13574
|
-
const jobId =
|
|
15069
|
+
const jobId = readString14(input.job, "id") ?? readString14(input.job, "job_id");
|
|
13575
15070
|
if (!jobId) {
|
|
13576
15071
|
return input.job;
|
|
13577
15072
|
}
|
|
@@ -13588,9 +15083,9 @@ async function bindAndDecorateCronJobForHermesLink(input) {
|
|
|
13588
15083
|
}
|
|
13589
15084
|
function readCronJobCreateInput(body) {
|
|
13590
15085
|
const input = {};
|
|
13591
|
-
const name =
|
|
13592
|
-
const prompt =
|
|
13593
|
-
const schedule =
|
|
15086
|
+
const name = readString14(body, "name") ?? readString14(body, "title");
|
|
15087
|
+
const prompt = readString14(body, "prompt") ?? readString14(body, "description") ?? readString14(body, "task");
|
|
15088
|
+
const schedule = readString14(body, "schedule");
|
|
13594
15089
|
if (!name) {
|
|
13595
15090
|
throw new LinkHttpError(400, "cron_job_name_required", "name is required");
|
|
13596
15091
|
}
|
|
@@ -13611,7 +15106,7 @@ function readCronJobCreateInput(body) {
|
|
|
13611
15106
|
input.name = name;
|
|
13612
15107
|
input.prompt = prompt;
|
|
13613
15108
|
input.schedule = schedule;
|
|
13614
|
-
input.deliver =
|
|
15109
|
+
input.deliver = readString14(body, "deliver") ?? HERMES_LINK_CRON_DELIVER;
|
|
13615
15110
|
const skills = readOptionalCronSkills(body);
|
|
13616
15111
|
if (skills) {
|
|
13617
15112
|
input.skills = skills;
|
|
@@ -13669,7 +15164,7 @@ function readCronJobUpdateInput(body) {
|
|
|
13669
15164
|
if (repeat !== void 0) {
|
|
13670
15165
|
input.repeat = repeat;
|
|
13671
15166
|
}
|
|
13672
|
-
const enabled =
|
|
15167
|
+
const enabled = readBoolean2(body.enabled);
|
|
13673
15168
|
if (enabled !== void 0) {
|
|
13674
15169
|
input.enabled = enabled;
|
|
13675
15170
|
}
|
|
@@ -13839,7 +15334,7 @@ function registerModelConfigRoutes(router, options) {
|
|
|
13839
15334
|
router.delete("/api/v1/model-configs", async (ctx) => {
|
|
13840
15335
|
await authenticateRequest(ctx, paths);
|
|
13841
15336
|
const body = await readJsonBody(ctx.req);
|
|
13842
|
-
const modelId =
|
|
15337
|
+
const modelId = readString14(body, "model_id") ?? readString14(body, "modelId");
|
|
13843
15338
|
if (!modelId) {
|
|
13844
15339
|
throw new LinkHttpError(400, "model_id_required", "model_id is required");
|
|
13845
15340
|
}
|
|
@@ -13894,7 +15389,7 @@ function registerModelConfigRoutes(router, options) {
|
|
|
13894
15389
|
await authenticateRequest(ctx, paths);
|
|
13895
15390
|
await getHermesProfileStatus(ctx.params.name, paths);
|
|
13896
15391
|
const body = await readJsonBody(ctx.req);
|
|
13897
|
-
const modelId =
|
|
15392
|
+
const modelId = readString14(body, "model_id") ?? readString14(body, "modelId");
|
|
13898
15393
|
if (!modelId) {
|
|
13899
15394
|
throw new LinkHttpError(400, "model_id_required", "model_id is required");
|
|
13900
15395
|
}
|
|
@@ -13911,9 +15406,9 @@ function registerModelConfigRoutes(router, options) {
|
|
|
13911
15406
|
});
|
|
13912
15407
|
}
|
|
13913
15408
|
function readModelConfigInput(body) {
|
|
13914
|
-
const id =
|
|
13915
|
-
const provider =
|
|
13916
|
-
const baseUrl =
|
|
15409
|
+
const id = readString14(body, "id") ?? readString14(body, "model_id") ?? readString14(body, "modelId");
|
|
15410
|
+
const provider = readString14(body, "provider") ?? readString14(body, "provider_key") ?? readString14(body, "providerKey");
|
|
15411
|
+
const baseUrl = readString14(body, "base_url") ?? readString14(body, "baseUrl");
|
|
13917
15412
|
if (!id || !provider || !baseUrl) {
|
|
13918
15413
|
throw new LinkHttpError(
|
|
13919
15414
|
400,
|
|
@@ -13923,28 +15418,28 @@ function readModelConfigInput(body) {
|
|
|
13923
15418
|
}
|
|
13924
15419
|
return {
|
|
13925
15420
|
id,
|
|
13926
|
-
originalModelId:
|
|
15421
|
+
originalModelId: readString14(body, "original_model_id") ?? readString14(body, "originalModelId") ?? readString14(body, "original_id") ?? void 0,
|
|
13927
15422
|
provider,
|
|
13928
|
-
providerName:
|
|
15423
|
+
providerName: readString14(body, "provider_name") ?? readString14(body, "providerName") ?? void 0,
|
|
13929
15424
|
baseUrl,
|
|
13930
|
-
apiKey:
|
|
13931
|
-
apiMode:
|
|
15425
|
+
apiKey: readString14(body, "api_key") ?? readString14(body, "apiKey") ?? void 0,
|
|
15426
|
+
apiMode: readString14(body, "api_mode") ?? readString14(body, "apiMode") ?? void 0,
|
|
13932
15427
|
contextLength: readPositiveInteger2(
|
|
13933
15428
|
body.context_length ?? body.contextLength
|
|
13934
15429
|
),
|
|
13935
|
-
keyEnv:
|
|
13936
|
-
setDefault:
|
|
13937
|
-
reasoningEffort:
|
|
15430
|
+
keyEnv: readString14(body, "key_env") ?? readString14(body, "keyEnv") ?? void 0,
|
|
15431
|
+
setDefault: readBoolean2(body.set_default ?? body.setDefault),
|
|
15432
|
+
reasoningEffort: readString14(body, "reasoning_effort") ?? readString14(body, "reasoningEffort") ?? void 0
|
|
13938
15433
|
};
|
|
13939
15434
|
}
|
|
13940
15435
|
function readModelDefaultsInput(body) {
|
|
13941
15436
|
return {
|
|
13942
|
-
taskModelId:
|
|
13943
|
-
compressionModelId:
|
|
15437
|
+
taskModelId: readString14(body, "task_model_id") ?? readString14(body, "taskModelId") ?? readString14(body, "default_model_id") ?? readString14(body, "defaultModelId") ?? void 0,
|
|
15438
|
+
compressionModelId: readString14(body, "compression_model_id") ?? readString14(body, "compressionModelId") ?? void 0
|
|
13944
15439
|
};
|
|
13945
15440
|
}
|
|
13946
15441
|
function shouldReloadGatewayAfterModelConfigChange(body) {
|
|
13947
|
-
const explicit =
|
|
15442
|
+
const explicit = readBoolean2(body.reload_gateway ?? body.reloadGateway) ?? (readBoolean2(body.skip_gateway_reload ?? body.skipGatewayReload) === true ? false : void 0);
|
|
13948
15443
|
return explicit ?? true;
|
|
13949
15444
|
}
|
|
13950
15445
|
function markModelConfigAppliedWithoutGatewayReload(result) {
|
|
@@ -14025,19 +15520,19 @@ import {
|
|
|
14025
15520
|
copyFile as copyFile2,
|
|
14026
15521
|
cp,
|
|
14027
15522
|
mkdir as mkdir11,
|
|
14028
|
-
readFile as
|
|
15523
|
+
readFile as readFile13,
|
|
14029
15524
|
rm as rm7,
|
|
14030
|
-
stat as
|
|
15525
|
+
stat as stat12,
|
|
14031
15526
|
writeFile as writeFile4,
|
|
14032
15527
|
rename as rename5
|
|
14033
15528
|
} from "fs/promises";
|
|
14034
|
-
import
|
|
15529
|
+
import path18 from "path";
|
|
14035
15530
|
import YAML3 from "yaml";
|
|
14036
15531
|
var PROFILE_CREATE_LOG_FILE = "profile-create.log";
|
|
14037
15532
|
var PROFILE_CREATE_LOG_MAX_FILES = 3;
|
|
14038
15533
|
var MAX_PROFILE_CREATE_LOG_LINES = 260;
|
|
14039
15534
|
var MAX_OUTPUT_LINE_LENGTH = 1200;
|
|
14040
|
-
var
|
|
15535
|
+
var PROFILE_NAME_PATTERN5 = /^[a-z0-9][a-z0-9_-]{0,63}$/u;
|
|
14041
15536
|
var ALL_COPY_SCOPES = [
|
|
14042
15537
|
"models",
|
|
14043
15538
|
"skills",
|
|
@@ -14289,7 +15784,7 @@ function normalizeOptionalProfileName(value) {
|
|
|
14289
15784
|
if (!trimmed) {
|
|
14290
15785
|
return null;
|
|
14291
15786
|
}
|
|
14292
|
-
if (!
|
|
15787
|
+
if (!PROFILE_NAME_PATTERN5.test(trimmed)) {
|
|
14293
15788
|
throw new LinkHttpError(
|
|
14294
15789
|
400,
|
|
14295
15790
|
"invalid_profile_name",
|
|
@@ -14421,7 +15916,7 @@ function copyModelConfig(source, target) {
|
|
|
14421
15916
|
copied[key] = cloneJson(source[key]);
|
|
14422
15917
|
}
|
|
14423
15918
|
}
|
|
14424
|
-
const sourceAuxiliary =
|
|
15919
|
+
const sourceAuxiliary = toRecord13(source.auxiliary);
|
|
14425
15920
|
if (Object.prototype.hasOwnProperty.call(sourceAuxiliary, "compression")) {
|
|
14426
15921
|
const targetAuxiliary = ensureRecord2(target, "auxiliary");
|
|
14427
15922
|
targetAuxiliary.compression = cloneJson(sourceAuxiliary.compression);
|
|
@@ -14430,12 +15925,12 @@ function copyModelConfig(source, target) {
|
|
|
14430
15925
|
return copied;
|
|
14431
15926
|
}
|
|
14432
15927
|
function copyToolPermissionsConfig(source, target) {
|
|
14433
|
-
const sourcePlatformToolsets =
|
|
15928
|
+
const sourcePlatformToolsets = toRecord13(source.platform_toolsets);
|
|
14434
15929
|
if (Object.prototype.hasOwnProperty.call(sourcePlatformToolsets, "api_server")) {
|
|
14435
15930
|
const targetPlatformToolsets = ensureRecord2(target, "platform_toolsets");
|
|
14436
15931
|
targetPlatformToolsets.api_server = cloneJson(sourcePlatformToolsets.api_server);
|
|
14437
15932
|
}
|
|
14438
|
-
const sourceStt =
|
|
15933
|
+
const sourceStt = toRecord13(source.stt);
|
|
14439
15934
|
if (Object.prototype.hasOwnProperty.call(sourceStt, "enabled")) {
|
|
14440
15935
|
const targetStt = ensureRecord2(target, "stt");
|
|
14441
15936
|
targetStt.enabled = cloneJson(sourceStt.enabled);
|
|
@@ -14482,9 +15977,9 @@ function collectEnvKeys(value, keys = /* @__PURE__ */ new Set()) {
|
|
|
14482
15977
|
return keys;
|
|
14483
15978
|
}
|
|
14484
15979
|
async function writeEnvValues(profileName, values) {
|
|
14485
|
-
const envPath =
|
|
14486
|
-
const existingRaw = await
|
|
14487
|
-
if (
|
|
15980
|
+
const envPath = path18.join(resolveHermesProfileDir(profileName), ".env");
|
|
15981
|
+
const existingRaw = await readFile13(envPath, "utf8").catch((error) => {
|
|
15982
|
+
if (isNodeError14(error, "ENOENT")) {
|
|
14488
15983
|
return "";
|
|
14489
15984
|
}
|
|
14490
15985
|
throw error;
|
|
@@ -14509,7 +16004,7 @@ async function writeEnvValues(profileName, values) {
|
|
|
14509
16004
|
nextLines.push(`${key}=${formatEnvValue2(value)}`);
|
|
14510
16005
|
}
|
|
14511
16006
|
const nextRaw = nextLines.join("\n").replace(/\n*$/u, "\n");
|
|
14512
|
-
await mkdir11(
|
|
16007
|
+
await mkdir11(path18.dirname(envPath), { recursive: true, mode: 448 });
|
|
14513
16008
|
if (existingRaw) {
|
|
14514
16009
|
await copyFile2(envPath, `${envPath}.bak.${Date.now()}`);
|
|
14515
16010
|
}
|
|
@@ -14518,8 +16013,8 @@ async function writeEnvValues(profileName, values) {
|
|
|
14518
16013
|
await rename5(tempPath, envPath);
|
|
14519
16014
|
}
|
|
14520
16015
|
async function copySkills(sourceProfile, targetProfile) {
|
|
14521
|
-
const sourceSkills =
|
|
14522
|
-
const targetSkills =
|
|
16016
|
+
const sourceSkills = path18.join(resolveHermesProfileDir(sourceProfile), "skills");
|
|
16017
|
+
const targetSkills = path18.join(resolveHermesProfileDir(targetProfile), "skills");
|
|
14523
16018
|
if (!await pathExists(sourceSkills)) {
|
|
14524
16019
|
return;
|
|
14525
16020
|
}
|
|
@@ -14538,21 +16033,21 @@ function copyProperty(source, target, key) {
|
|
|
14538
16033
|
}
|
|
14539
16034
|
}
|
|
14540
16035
|
async function readYamlConfig(configPath) {
|
|
14541
|
-
const existingRaw = await
|
|
16036
|
+
const existingRaw = await readFile13(configPath, "utf8").catch(
|
|
14542
16037
|
(error) => {
|
|
14543
|
-
if (
|
|
16038
|
+
if (isNodeError14(error, "ENOENT")) {
|
|
14544
16039
|
return null;
|
|
14545
16040
|
}
|
|
14546
16041
|
throw error;
|
|
14547
16042
|
}
|
|
14548
16043
|
);
|
|
14549
16044
|
return {
|
|
14550
|
-
config:
|
|
16045
|
+
config: toRecord13(existingRaw ? YAML3.parse(existingRaw) : {}),
|
|
14551
16046
|
existingRaw
|
|
14552
16047
|
};
|
|
14553
16048
|
}
|
|
14554
16049
|
async function writeYamlConfig(configPath, input) {
|
|
14555
|
-
await mkdir11(
|
|
16050
|
+
await mkdir11(path18.dirname(configPath), { recursive: true, mode: 448 });
|
|
14556
16051
|
if (input.existingRaw) {
|
|
14557
16052
|
await copyFile2(configPath, `${configPath}.bak.${Date.now()}`);
|
|
14558
16053
|
}
|
|
@@ -14600,7 +16095,7 @@ async function writeProfileCreationState(paths, state) {
|
|
|
14600
16095
|
await writeJsonFile(profileCreationStatePath(paths), state);
|
|
14601
16096
|
}
|
|
14602
16097
|
async function readProfileCreationLogLines(paths) {
|
|
14603
|
-
const raw = await
|
|
16098
|
+
const raw = await readFile13(profileCreationLogPath(paths), "utf8").catch(() => "");
|
|
14604
16099
|
if (!raw.trim()) {
|
|
14605
16100
|
return [];
|
|
14606
16101
|
}
|
|
@@ -14609,10 +16104,10 @@ async function readProfileCreationLogLines(paths) {
|
|
|
14609
16104
|
);
|
|
14610
16105
|
}
|
|
14611
16106
|
function profileCreationStatePath(paths) {
|
|
14612
|
-
return
|
|
16107
|
+
return path18.join(paths.runDir, "profile-create-state.json");
|
|
14613
16108
|
}
|
|
14614
16109
|
function profileCreationLogPath(paths) {
|
|
14615
|
-
return
|
|
16110
|
+
return path18.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
|
|
14616
16111
|
}
|
|
14617
16112
|
async function clearProfileCreationLogFiles(paths) {
|
|
14618
16113
|
const primary = profileCreationLogPath(paths);
|
|
@@ -14625,8 +16120,8 @@ async function clearProfileCreationLogFiles(paths) {
|
|
|
14625
16120
|
]);
|
|
14626
16121
|
}
|
|
14627
16122
|
async function pathExists(targetPath) {
|
|
14628
|
-
return await
|
|
14629
|
-
if (
|
|
16123
|
+
return await stat12(targetPath).then(() => true).catch((error) => {
|
|
16124
|
+
if (isNodeError14(error, "ENOENT")) {
|
|
14630
16125
|
return false;
|
|
14631
16126
|
}
|
|
14632
16127
|
throw error;
|
|
@@ -14658,7 +16153,7 @@ function ensureRecord2(target, key) {
|
|
|
14658
16153
|
target[key] = next;
|
|
14659
16154
|
return next;
|
|
14660
16155
|
}
|
|
14661
|
-
function
|
|
16156
|
+
function toRecord13(value) {
|
|
14662
16157
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
14663
16158
|
}
|
|
14664
16159
|
function cloneJson(value) {
|
|
@@ -14673,7 +16168,7 @@ function formatEnvValue2(value) {
|
|
|
14673
16168
|
function escapeRegExp2(value) {
|
|
14674
16169
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
14675
16170
|
}
|
|
14676
|
-
function
|
|
16171
|
+
function isNodeError14(error, code) {
|
|
14677
16172
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
14678
16173
|
}
|
|
14679
16174
|
|
|
@@ -14758,16 +16253,16 @@ function readProfilePermissionsInput(body) {
|
|
|
14758
16253
|
const approvals = readOptionalObject(body, "approvals");
|
|
14759
16254
|
if (approvals) {
|
|
14760
16255
|
input.approvals = {
|
|
14761
|
-
mode:
|
|
16256
|
+
mode: readString14(approvals, "mode") ?? readString14(approvals, "approval_mode") ?? readString14(approvals, "approvalMode") ?? void 0,
|
|
14762
16257
|
timeout: readPositiveInteger2(approvals.timeout),
|
|
14763
|
-
cronMode:
|
|
16258
|
+
cronMode: readString14(approvals, "cron_mode") ?? readString14(approvals, "cronMode") ?? void 0
|
|
14764
16259
|
};
|
|
14765
16260
|
}
|
|
14766
16261
|
const terminal = readOptionalObject(body, "terminal");
|
|
14767
16262
|
if (terminal) {
|
|
14768
16263
|
input.terminal = {
|
|
14769
|
-
backend:
|
|
14770
|
-
cwd:
|
|
16264
|
+
backend: readString14(terminal, "backend") ?? void 0,
|
|
16265
|
+
cwd: readString14(terminal, "cwd") ?? void 0,
|
|
14771
16266
|
containerCpu: readPositiveInteger2(
|
|
14772
16267
|
terminal.container_cpu ?? terminal.containerCpu
|
|
14773
16268
|
),
|
|
@@ -14777,7 +16272,7 @@ function readProfilePermissionsInput(body) {
|
|
|
14777
16272
|
containerDisk: readPositiveInteger2(
|
|
14778
16273
|
terminal.container_disk ?? terminal.containerDisk
|
|
14779
16274
|
),
|
|
14780
|
-
containerPersistent:
|
|
16275
|
+
containerPersistent: readBoolean2(
|
|
14781
16276
|
terminal.container_persistent ?? terminal.containerPersistent
|
|
14782
16277
|
)
|
|
14783
16278
|
};
|
|
@@ -14789,7 +16284,7 @@ function readProfilePermissionsInput(body) {
|
|
|
14789
16284
|
toolsets.enabled_toolsets ?? toolsets.enabledToolsets ?? toolsets.enabled,
|
|
14790
16285
|
"toolsets.enabled"
|
|
14791
16286
|
) ?? void 0,
|
|
14792
|
-
mcpEnabled:
|
|
16287
|
+
mcpEnabled: readBoolean2(toolsets.mcp_enabled ?? toolsets.mcpEnabled)
|
|
14793
16288
|
};
|
|
14794
16289
|
}
|
|
14795
16290
|
if (Object.keys(input).length === 0) {
|
|
@@ -14884,13 +16379,13 @@ import {
|
|
|
14884
16379
|
access as access3,
|
|
14885
16380
|
copyFile as copyFile3,
|
|
14886
16381
|
mkdir as mkdir12,
|
|
14887
|
-
readdir as
|
|
14888
|
-
readFile as
|
|
16382
|
+
readdir as readdir9,
|
|
16383
|
+
readFile as readFile14,
|
|
14889
16384
|
rename as rename6,
|
|
14890
|
-
stat as
|
|
16385
|
+
stat as stat13,
|
|
14891
16386
|
writeFile as writeFile5
|
|
14892
16387
|
} from "fs/promises";
|
|
14893
|
-
import
|
|
16388
|
+
import path19 from "path";
|
|
14894
16389
|
import YAML4 from "yaml";
|
|
14895
16390
|
var ENTRY_DELIMITER = "\n\xA7\n";
|
|
14896
16391
|
var DEFAULT_MEMORY_LIMIT = 2200;
|
|
@@ -15107,7 +16602,7 @@ async function saveProviderSettings(profileName, provider, patch) {
|
|
|
15107
16602
|
if (provider === "hindsight") {
|
|
15108
16603
|
await patchJsonProviderConfig(
|
|
15109
16604
|
profileName,
|
|
15110
|
-
|
|
16605
|
+
path19.join("hindsight", "config.json"),
|
|
15111
16606
|
{
|
|
15112
16607
|
mode: patch.mode,
|
|
15113
16608
|
api_url: patch.apiUrl,
|
|
@@ -15160,7 +16655,7 @@ async function patchCustomProviderConfig(profileName, provider, patch) {
|
|
|
15160
16655
|
"\u81EA\u5B9A\u4E49 memory provider \u914D\u7F6E\u5FC5\u987B\u662F\u6709\u6548\u7684 JSON object\u3002"
|
|
15161
16656
|
);
|
|
15162
16657
|
}
|
|
15163
|
-
const config =
|
|
16658
|
+
const config = toRecord14(parsed);
|
|
15164
16659
|
if (Object.keys(config).length === 0 && parsed !== null) {
|
|
15165
16660
|
throw new HermesMemoryError(
|
|
15166
16661
|
"memory_provider_config_invalid",
|
|
@@ -15201,7 +16696,7 @@ function isSensitiveConfigKey(key) {
|
|
|
15201
16696
|
}
|
|
15202
16697
|
async function writeCustomProviderConfig(profileName, provider, config) {
|
|
15203
16698
|
const configPath = customProviderConfigPath(profileName, provider);
|
|
15204
|
-
await mkdir12(
|
|
16699
|
+
await mkdir12(path19.dirname(configPath), { recursive: true, mode: 448 });
|
|
15205
16700
|
await writeFile5(configPath, `${JSON.stringify(config, null, 2)}
|
|
15206
16701
|
`, {
|
|
15207
16702
|
encoding: "utf8",
|
|
@@ -15252,21 +16747,21 @@ function normalizeCustomProviderId(provider) {
|
|
|
15252
16747
|
}
|
|
15253
16748
|
async function patchHermesMemoryProvider(profileName, provider) {
|
|
15254
16749
|
const configPath = resolveHermesConfigPath(profileName);
|
|
15255
|
-
const existingRaw = await
|
|
16750
|
+
const existingRaw = await readFile14(configPath, "utf8").catch(
|
|
15256
16751
|
(error) => {
|
|
15257
|
-
if (
|
|
16752
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15258
16753
|
return null;
|
|
15259
16754
|
}
|
|
15260
16755
|
throw error;
|
|
15261
16756
|
}
|
|
15262
16757
|
);
|
|
15263
16758
|
const document = existingRaw ? YAML4.parseDocument(existingRaw) : new YAML4.Document({});
|
|
15264
|
-
const config =
|
|
15265
|
-
const memory =
|
|
16759
|
+
const config = toRecord14(document.toJSON());
|
|
16760
|
+
const memory = toRecord14(config.memory);
|
|
15266
16761
|
memory.provider = provider === "built-in" ? "" : provider;
|
|
15267
16762
|
config.memory = memory;
|
|
15268
16763
|
const backupPath = existingRaw ? `${configPath}.bak.${Date.now()}` : null;
|
|
15269
|
-
await mkdir12(
|
|
16764
|
+
await mkdir12(path19.dirname(configPath), { recursive: true, mode: 448 });
|
|
15270
16765
|
if (backupPath) {
|
|
15271
16766
|
await copyFile3(configPath, backupPath);
|
|
15272
16767
|
}
|
|
@@ -15279,13 +16774,13 @@ async function patchHermesMemoryProvider(profileName, provider) {
|
|
|
15279
16774
|
await rename6(tempPath, configPath);
|
|
15280
16775
|
}
|
|
15281
16776
|
function resolveMemoryDir(profileName) {
|
|
15282
|
-
return
|
|
16777
|
+
return path19.join(resolveHermesProfileDir(profileName), "memories");
|
|
15283
16778
|
}
|
|
15284
16779
|
async function readMemoryStore(profileName, target, limits) {
|
|
15285
16780
|
const filePath = memoryFilePath(profileName, target);
|
|
15286
16781
|
const entries = await readMemoryEntries(filePath);
|
|
15287
|
-
const fileStat = await
|
|
15288
|
-
if (
|
|
16782
|
+
const fileStat = await stat13(filePath).catch((error) => {
|
|
16783
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15289
16784
|
return null;
|
|
15290
16785
|
}
|
|
15291
16786
|
throw error;
|
|
@@ -15313,8 +16808,8 @@ async function readMemoryStore(profileName, target, limits) {
|
|
|
15313
16808
|
};
|
|
15314
16809
|
}
|
|
15315
16810
|
async function readMemoryEntries(filePath) {
|
|
15316
|
-
const raw = await
|
|
15317
|
-
if (
|
|
16811
|
+
const raw = await readFile14(filePath, "utf8").catch((error) => {
|
|
16812
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15318
16813
|
return "";
|
|
15319
16814
|
}
|
|
15320
16815
|
throw error;
|
|
@@ -15334,9 +16829,9 @@ async function mutateMemoryEntries(profileName, target, mutate) {
|
|
|
15334
16829
|
async function writeMemoryEntries(profileName, target, entries) {
|
|
15335
16830
|
assertWithinLimit(target, entries, await readMemoryLimits(profileName));
|
|
15336
16831
|
const filePath = memoryFilePath(profileName, target);
|
|
15337
|
-
const dir =
|
|
16832
|
+
const dir = path19.dirname(filePath);
|
|
15338
16833
|
await mkdir12(dir, { recursive: true, mode: 448 });
|
|
15339
|
-
const tempPath =
|
|
16834
|
+
const tempPath = path19.join(
|
|
15340
16835
|
dir,
|
|
15341
16836
|
`.mem_${process.pid}_${Date.now()}_${target}.tmp`
|
|
15342
16837
|
);
|
|
@@ -15347,7 +16842,7 @@ async function writeMemoryEntries(profileName, target, entries) {
|
|
|
15347
16842
|
await rename6(tempPath, filePath);
|
|
15348
16843
|
}
|
|
15349
16844
|
function memoryFilePath(profileName, target) {
|
|
15350
|
-
return
|
|
16845
|
+
return path19.join(
|
|
15351
16846
|
resolveMemoryDir(profileName),
|
|
15352
16847
|
target === "user" ? "USER.md" : "MEMORY.md"
|
|
15353
16848
|
);
|
|
@@ -15407,7 +16902,7 @@ async function readCustomProviderSetupSummary(profileName) {
|
|
|
15407
16902
|
configurable: true,
|
|
15408
16903
|
configured: true,
|
|
15409
16904
|
configurationIssue: null,
|
|
15410
|
-
providerConfigPath:
|
|
16905
|
+
providerConfigPath: path19.join(
|
|
15411
16906
|
resolveHermesProfileDir(profileName),
|
|
15412
16907
|
"<provider>.json"
|
|
15413
16908
|
),
|
|
@@ -15485,7 +16980,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
|
|
|
15485
16980
|
const config2 = await readJsonObject(
|
|
15486
16981
|
memoryProviderConfigPath(profileName, "honcho") ?? ""
|
|
15487
16982
|
);
|
|
15488
|
-
return isConfiguredEnvValue(env.HONCHO_API_KEY) || isConfiguredEnvValue(
|
|
16983
|
+
return isConfiguredEnvValue(env.HONCHO_API_KEY) || isConfiguredEnvValue(readString15(config2.apiKey)) || isConfiguredEnvValue(readString15(config2.api_key)) || isConfiguredEnvValue(readString15(config2.baseUrl)) ? { configured: true, issue: null } : {
|
|
15489
16984
|
configured: false,
|
|
15490
16985
|
issue: "Honcho \u9700\u8981\u5148\u914D\u7F6E HONCHO_API_KEY\uFF0C\u6216\u5728 honcho.json \u914D\u7F6E self-hosted baseUrl\u3002"
|
|
15491
16986
|
};
|
|
@@ -15494,7 +16989,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
|
|
|
15494
16989
|
const config2 = await readJsonObject(
|
|
15495
16990
|
memoryProviderConfigPath(profileName, "mem0") ?? ""
|
|
15496
16991
|
);
|
|
15497
|
-
return isConfiguredEnvValue(env.MEM0_API_KEY) || isConfiguredEnvValue(
|
|
16992
|
+
return isConfiguredEnvValue(env.MEM0_API_KEY) || isConfiguredEnvValue(readString15(config2.api_key)) ? { configured: true, issue: null } : {
|
|
15498
16993
|
configured: false,
|
|
15499
16994
|
issue: "Mem0 \u9700\u8981\u5148\u5728\u672C\u673A Hermes .env \u914D\u7F6E MEM0_API_KEY\u3002"
|
|
15500
16995
|
};
|
|
@@ -15536,7 +17031,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
|
|
|
15536
17031
|
memoryProviderConfigPath(profileName, provider) ?? ""
|
|
15537
17032
|
);
|
|
15538
17033
|
const mode = normalizeHindsightMode(config.mode ?? env.HINDSIGHT_MODE);
|
|
15539
|
-
const apiKey =
|
|
17034
|
+
const apiKey = readString15(config.apiKey) ?? readString15(config.api_key) ?? env.HINDSIGHT_API_KEY;
|
|
15540
17035
|
if (mode === "cloud") {
|
|
15541
17036
|
return isConfiguredEnvValue(apiKey) ? { configured: true, issue: null } : {
|
|
15542
17037
|
configured: false,
|
|
@@ -15544,15 +17039,15 @@ async function readProviderConfigurationStatus(profileName, provider) {
|
|
|
15544
17039
|
};
|
|
15545
17040
|
}
|
|
15546
17041
|
if (mode === "local_external") {
|
|
15547
|
-
const apiUrl =
|
|
17042
|
+
const apiUrl = readString15(config.api_url) ?? env.HINDSIGHT_API_URL ?? HINDSIGHT_DEFAULT_LOCAL_URL;
|
|
15548
17043
|
return isConfiguredEnvValue(apiUrl) ? { configured: true, issue: null } : {
|
|
15549
17044
|
configured: false,
|
|
15550
17045
|
issue: "Hindsight local_external \u9700\u8981\u914D\u7F6E\u53EF\u8BBF\u95EE\u7684 API URL\u3002"
|
|
15551
17046
|
};
|
|
15552
17047
|
}
|
|
15553
17048
|
if (mode === "local_embedded") {
|
|
15554
|
-
const llmProvider =
|
|
15555
|
-
const llmModel =
|
|
17049
|
+
const llmProvider = readString15(config.llm_provider) ?? "openai";
|
|
17050
|
+
const llmModel = readString15(config.llm_model);
|
|
15556
17051
|
if (!llmModel) {
|
|
15557
17052
|
return {
|
|
15558
17053
|
configured: false,
|
|
@@ -15560,7 +17055,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
|
|
|
15560
17055
|
};
|
|
15561
17056
|
}
|
|
15562
17057
|
if (llmProvider === "openai_compatible" && !isConfiguredEnvValue(
|
|
15563
|
-
|
|
17058
|
+
readString15(config.llm_base_url) ?? env.HINDSIGHT_API_LLM_BASE_URL
|
|
15564
17059
|
)) {
|
|
15565
17060
|
return {
|
|
15566
17061
|
configured: false,
|
|
@@ -15568,7 +17063,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
|
|
|
15568
17063
|
};
|
|
15569
17064
|
}
|
|
15570
17065
|
if (!["ollama", "lmstudio", "openai_compatible"].includes(llmProvider) && !isConfiguredEnvValue(
|
|
15571
|
-
|
|
17066
|
+
readString15(config.llmApiKey) ?? readString15(config.llm_api_key) ?? env.HINDSIGHT_LLM_API_KEY
|
|
15572
17067
|
)) {
|
|
15573
17068
|
return {
|
|
15574
17069
|
configured: false,
|
|
@@ -15708,8 +17203,8 @@ async function readProviderSettings(profileName, provider) {
|
|
|
15708
17203
|
const config = await readJsonObject(
|
|
15709
17204
|
memoryProviderConfigPath(profileName, provider) ?? ""
|
|
15710
17205
|
);
|
|
15711
|
-
const banks =
|
|
15712
|
-
const hermesBank =
|
|
17206
|
+
const banks = toRecord14(config.banks);
|
|
17207
|
+
const hermesBank = toRecord14(banks.hermes);
|
|
15713
17208
|
const mode = normalizeHindsightMode(config.mode);
|
|
15714
17209
|
return [
|
|
15715
17210
|
selectSetting("mode", "\u8FDE\u63A5\u6A21\u5F0F", mode, [
|
|
@@ -15761,7 +17256,7 @@ async function readProviderSettings(profileName, provider) {
|
|
|
15761
17256
|
stringSetting(
|
|
15762
17257
|
"dbPath",
|
|
15763
17258
|
"SQLite \u6570\u636E\u5E93\u8DEF\u5F84",
|
|
15764
|
-
config.db_path ??
|
|
17259
|
+
config.db_path ?? path19.join(resolveHermesProfileDir(profileName), "memory_store.db")
|
|
15765
17260
|
),
|
|
15766
17261
|
booleanSetting("autoExtract", "\u4F1A\u8BDD\u7ED3\u675F\u81EA\u52A8\u62BD\u53D6", config.auto_extract ?? false),
|
|
15767
17262
|
numberSetting("defaultTrust", "\u9ED8\u8BA4\u4FE1\u4EFB\u5206", config.default_trust ?? 0.5),
|
|
@@ -15784,7 +17279,7 @@ async function readProviderSettings(profileName, provider) {
|
|
|
15784
17279
|
stringSetting(
|
|
15785
17280
|
"workingDirectory",
|
|
15786
17281
|
"\u5DE5\u4F5C\u76EE\u5F55",
|
|
15787
|
-
|
|
17282
|
+
path19.join(resolveHermesProfileDir(profileName), "byterover"),
|
|
15788
17283
|
false
|
|
15789
17284
|
)
|
|
15790
17285
|
];
|
|
@@ -15793,16 +17288,16 @@ async function readProviderSettings(profileName, provider) {
|
|
|
15793
17288
|
}
|
|
15794
17289
|
function memoryProviderConfigPath(profileName, provider) {
|
|
15795
17290
|
if (provider === "honcho") {
|
|
15796
|
-
return
|
|
17291
|
+
return path19.join(resolveHermesProfileDir(profileName), "honcho.json");
|
|
15797
17292
|
}
|
|
15798
17293
|
if (provider === "mem0") {
|
|
15799
|
-
return
|
|
17294
|
+
return path19.join(resolveHermesProfileDir(profileName), "mem0.json");
|
|
15800
17295
|
}
|
|
15801
17296
|
if (provider === "supermemory") {
|
|
15802
|
-
return
|
|
17297
|
+
return path19.join(resolveHermesProfileDir(profileName), "supermemory.json");
|
|
15803
17298
|
}
|
|
15804
17299
|
if (provider === "hindsight") {
|
|
15805
|
-
return
|
|
17300
|
+
return path19.join(
|
|
15806
17301
|
resolveHermesProfileDir(profileName),
|
|
15807
17302
|
"hindsight",
|
|
15808
17303
|
"config.json"
|
|
@@ -15811,21 +17306,21 @@ function memoryProviderConfigPath(profileName, provider) {
|
|
|
15811
17306
|
return null;
|
|
15812
17307
|
}
|
|
15813
17308
|
function customProviderConfigPath(profileName, provider) {
|
|
15814
|
-
return
|
|
17309
|
+
return path19.join(
|
|
15815
17310
|
resolveHermesProfileDir(profileName),
|
|
15816
17311
|
`${normalizeCustomProviderId(provider)}.json`
|
|
15817
17312
|
);
|
|
15818
17313
|
}
|
|
15819
17314
|
function customProviderRegistryPath(profileName) {
|
|
15820
|
-
return
|
|
17315
|
+
return path19.join(
|
|
15821
17316
|
resolveHermesProfileDir(profileName),
|
|
15822
17317
|
CUSTOM_PROVIDER_REGISTRY_FILE
|
|
15823
17318
|
);
|
|
15824
17319
|
}
|
|
15825
17320
|
async function readCustomProviderRegistry(profileName) {
|
|
15826
|
-
const raw = await
|
|
17321
|
+
const raw = await readFile14(customProviderRegistryPath(profileName), "utf8").catch(
|
|
15827
17322
|
(error) => {
|
|
15828
|
-
if (
|
|
17323
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15829
17324
|
return "";
|
|
15830
17325
|
}
|
|
15831
17326
|
throw error;
|
|
@@ -15836,18 +17331,18 @@ async function readCustomProviderRegistry(profileName) {
|
|
|
15836
17331
|
}
|
|
15837
17332
|
try {
|
|
15838
17333
|
const parsed = JSON.parse(raw);
|
|
15839
|
-
const providers = Array.isArray(parsed) ? parsed : Array.isArray(
|
|
17334
|
+
const providers = Array.isArray(parsed) ? parsed : Array.isArray(toRecord14(parsed).providers) ? toRecord14(parsed).providers : [];
|
|
15840
17335
|
return providers.map((item) => {
|
|
15841
17336
|
if (typeof item === "string") {
|
|
15842
17337
|
const id2 = normalizeCustomProviderId(item);
|
|
15843
17338
|
return { id: id2, label: id2, description: "\u81EA\u5B9A\u4E49 memory provider\u3002" };
|
|
15844
17339
|
}
|
|
15845
|
-
const record =
|
|
15846
|
-
const id = normalizeCustomProviderId(
|
|
17340
|
+
const record = toRecord14(item);
|
|
17341
|
+
const id = normalizeCustomProviderId(readString15(record.id) ?? "");
|
|
15847
17342
|
return {
|
|
15848
17343
|
id,
|
|
15849
|
-
label:
|
|
15850
|
-
description:
|
|
17344
|
+
label: readString15(record.label) ?? id,
|
|
17345
|
+
description: readString15(record.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
|
|
15851
17346
|
};
|
|
15852
17347
|
}).filter((item) => item.id);
|
|
15853
17348
|
} catch {
|
|
@@ -15862,7 +17357,7 @@ async function saveCustomProviderRegistryEntry(profileName, provider) {
|
|
|
15862
17357
|
{ id: providerId, label: providerId, description: "\u81EA\u5B9A\u4E49 memory provider\u3002" }
|
|
15863
17358
|
].sort((left, right) => left.id.localeCompare(right.id));
|
|
15864
17359
|
const registryPath2 = customProviderRegistryPath(profileName);
|
|
15865
|
-
await mkdir12(
|
|
17360
|
+
await mkdir12(path19.dirname(registryPath2), { recursive: true, mode: 448 });
|
|
15866
17361
|
await writeFile5(
|
|
15867
17362
|
registryPath2,
|
|
15868
17363
|
`${JSON.stringify({ providers }, null, 2)}
|
|
@@ -15871,10 +17366,10 @@ async function saveCustomProviderRegistryEntry(profileName, provider) {
|
|
|
15871
17366
|
);
|
|
15872
17367
|
}
|
|
15873
17368
|
async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
15874
|
-
const pluginsDir =
|
|
15875
|
-
const entries = await
|
|
17369
|
+
const pluginsDir = path19.join(resolveHermesProfileDir(profileName), "plugins");
|
|
17370
|
+
const entries = await readdir9(pluginsDir, { withFileTypes: true }).catch(
|
|
15876
17371
|
(error) => {
|
|
15877
|
-
if (
|
|
17372
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15878
17373
|
return [];
|
|
15879
17374
|
}
|
|
15880
17375
|
throw error;
|
|
@@ -15891,21 +17386,21 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
|
15891
17386
|
} catch {
|
|
15892
17387
|
continue;
|
|
15893
17388
|
}
|
|
15894
|
-
const providerDir =
|
|
17389
|
+
const providerDir = path19.join(pluginsDir, entry.name);
|
|
15895
17390
|
if (!await isMemoryProviderPluginDir(providerDir)) {
|
|
15896
17391
|
continue;
|
|
15897
17392
|
}
|
|
15898
17393
|
const meta = await readPluginMetadata(providerDir);
|
|
15899
17394
|
descriptors.push({
|
|
15900
17395
|
id: providerId,
|
|
15901
|
-
label:
|
|
15902
|
-
description:
|
|
17396
|
+
label: readString15(meta.name) ?? providerId,
|
|
17397
|
+
description: readString15(meta.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
|
|
15903
17398
|
});
|
|
15904
17399
|
}
|
|
15905
17400
|
return descriptors;
|
|
15906
17401
|
}
|
|
15907
17402
|
async function isUserMemoryProviderInstalled(profileName, provider) {
|
|
15908
|
-
const providerDir =
|
|
17403
|
+
const providerDir = path19.join(
|
|
15909
17404
|
resolveHermesProfileDir(profileName),
|
|
15910
17405
|
"plugins",
|
|
15911
17406
|
normalizeCustomProviderId(provider)
|
|
@@ -15913,9 +17408,9 @@ async function isUserMemoryProviderInstalled(profileName, provider) {
|
|
|
15913
17408
|
return isMemoryProviderPluginDir(providerDir);
|
|
15914
17409
|
}
|
|
15915
17410
|
async function isMemoryProviderPluginDir(providerDir) {
|
|
15916
|
-
const source = await
|
|
17411
|
+
const source = await readFile14(path19.join(providerDir, "__init__.py"), "utf8").catch(
|
|
15917
17412
|
(error) => {
|
|
15918
|
-
if (
|
|
17413
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15919
17414
|
return "";
|
|
15920
17415
|
}
|
|
15921
17416
|
throw error;
|
|
@@ -15925,22 +17420,22 @@ async function isMemoryProviderPluginDir(providerDir) {
|
|
|
15925
17420
|
return sample.includes("register_memory_provider") || sample.includes("MemoryProvider");
|
|
15926
17421
|
}
|
|
15927
17422
|
async function readPluginMetadata(providerDir) {
|
|
15928
|
-
const raw = await
|
|
17423
|
+
const raw = await readFile14(path19.join(providerDir, "plugin.yaml"), "utf8").catch(
|
|
15929
17424
|
(error) => {
|
|
15930
|
-
if (
|
|
17425
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15931
17426
|
return "";
|
|
15932
17427
|
}
|
|
15933
17428
|
throw error;
|
|
15934
17429
|
}
|
|
15935
17430
|
);
|
|
15936
|
-
return raw ?
|
|
17431
|
+
return raw ? toRecord14(YAML4.parse(raw)) : {};
|
|
15937
17432
|
}
|
|
15938
17433
|
async function resolveByteRoverCli() {
|
|
15939
17434
|
const candidates = [
|
|
15940
|
-
...(process.env.PATH ?? "").split(
|
|
15941
|
-
|
|
17435
|
+
...(process.env.PATH ?? "").split(path19.delimiter).filter(Boolean).map((dir) => path19.join(dir, "brv")),
|
|
17436
|
+
path19.join(process.env.HOME ?? "", ".brv-cli", "bin", "brv"),
|
|
15942
17437
|
"/usr/local/bin/brv",
|
|
15943
|
-
|
|
17438
|
+
path19.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
|
|
15944
17439
|
].filter(Boolean);
|
|
15945
17440
|
for (const candidate of candidates) {
|
|
15946
17441
|
const found = await access3(candidate).then(() => true).catch(() => false);
|
|
@@ -15951,32 +17446,32 @@ async function resolveByteRoverCli() {
|
|
|
15951
17446
|
return null;
|
|
15952
17447
|
}
|
|
15953
17448
|
async function readHolographicProviderConfig(profileName) {
|
|
15954
|
-
const raw = await
|
|
17449
|
+
const raw = await readFile14(resolveHermesConfigPath(profileName), "utf8").catch(
|
|
15955
17450
|
(error) => {
|
|
15956
|
-
if (
|
|
17451
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15957
17452
|
return "";
|
|
15958
17453
|
}
|
|
15959
17454
|
throw error;
|
|
15960
17455
|
}
|
|
15961
17456
|
);
|
|
15962
|
-
const config = raw ?
|
|
15963
|
-
const plugins =
|
|
15964
|
-
return
|
|
17457
|
+
const config = raw ? toRecord14(YAML4.parse(raw)) : {};
|
|
17458
|
+
const plugins = toRecord14(config.plugins);
|
|
17459
|
+
return toRecord14(plugins["hermes-memory-store"]);
|
|
15965
17460
|
}
|
|
15966
17461
|
async function patchHolographicProviderConfig(profileName, patch) {
|
|
15967
17462
|
const configPath = resolveHermesConfigPath(profileName);
|
|
15968
|
-
const existingRaw = await
|
|
17463
|
+
const existingRaw = await readFile14(configPath, "utf8").catch(
|
|
15969
17464
|
(error) => {
|
|
15970
|
-
if (
|
|
17465
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15971
17466
|
return null;
|
|
15972
17467
|
}
|
|
15973
17468
|
throw error;
|
|
15974
17469
|
}
|
|
15975
17470
|
);
|
|
15976
17471
|
const document = existingRaw ? YAML4.parseDocument(existingRaw) : new YAML4.Document({});
|
|
15977
|
-
const config =
|
|
15978
|
-
const plugins =
|
|
15979
|
-
const memoryStore =
|
|
17472
|
+
const config = toRecord14(document.toJSON());
|
|
17473
|
+
const plugins = toRecord14(config.plugins);
|
|
17474
|
+
const memoryStore = toRecord14(plugins["hermes-memory-store"]);
|
|
15980
17475
|
for (const [key, value] of Object.entries(patch)) {
|
|
15981
17476
|
if (value !== void 0) {
|
|
15982
17477
|
memoryStore[key] = value;
|
|
@@ -15984,7 +17479,7 @@ async function patchHolographicProviderConfig(profileName, patch) {
|
|
|
15984
17479
|
}
|
|
15985
17480
|
plugins["hermes-memory-store"] = memoryStore;
|
|
15986
17481
|
config.plugins = plugins;
|
|
15987
|
-
await mkdir12(
|
|
17482
|
+
await mkdir12(path19.dirname(configPath), { recursive: true, mode: 448 });
|
|
15988
17483
|
if (existingRaw) {
|
|
15989
17484
|
await copyFile3(configPath, `${configPath}.bak.${Date.now()}`);
|
|
15990
17485
|
}
|
|
@@ -16003,9 +17498,9 @@ async function patchHermesMemoryEnv(profileName, patch) {
|
|
|
16003
17498
|
if (entries.length === 0) {
|
|
16004
17499
|
return;
|
|
16005
17500
|
}
|
|
16006
|
-
const envPath =
|
|
16007
|
-
const existingRaw = await
|
|
16008
|
-
if (
|
|
17501
|
+
const envPath = path19.join(resolveHermesProfileDir(profileName), ".env");
|
|
17502
|
+
const existingRaw = await readFile14(envPath, "utf8").catch((error) => {
|
|
17503
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
16009
17504
|
return "";
|
|
16010
17505
|
}
|
|
16011
17506
|
throw error;
|
|
@@ -16035,7 +17530,7 @@ async function patchHermesMemoryEnv(profileName, patch) {
|
|
|
16035
17530
|
}
|
|
16036
17531
|
}
|
|
16037
17532
|
const nextRaw = nextLines.join("\n").replace(/\n*$/u, "\n");
|
|
16038
|
-
await mkdir12(
|
|
17533
|
+
await mkdir12(path19.dirname(envPath), { recursive: true, mode: 448 });
|
|
16039
17534
|
if (existingRaw) {
|
|
16040
17535
|
await copyFile3(envPath, `${envPath}.bak.${Date.now()}`);
|
|
16041
17536
|
}
|
|
@@ -16054,29 +17549,29 @@ function isMemoryEnvKeyWritable(key) {
|
|
|
16054
17549
|
].includes(key);
|
|
16055
17550
|
}
|
|
16056
17551
|
function normalizeHindsightMode(value) {
|
|
16057
|
-
const mode =
|
|
17552
|
+
const mode = readString15(value) ?? "cloud";
|
|
16058
17553
|
return mode === "local" ? "local_embedded" : mode;
|
|
16059
17554
|
}
|
|
16060
17555
|
async function readActiveMemoryProvider(profileName) {
|
|
16061
|
-
const raw = await
|
|
17556
|
+
const raw = await readFile14(
|
|
16062
17557
|
resolveHermesConfigPath(profileName),
|
|
16063
17558
|
"utf8"
|
|
16064
17559
|
).catch((error) => {
|
|
16065
|
-
if (
|
|
17560
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
16066
17561
|
return "";
|
|
16067
17562
|
}
|
|
16068
17563
|
throw error;
|
|
16069
17564
|
});
|
|
16070
|
-
const config = raw ?
|
|
16071
|
-
const memory =
|
|
16072
|
-
const provider =
|
|
17565
|
+
const config = raw ? toRecord14(YAML4.parse(raw)) : {};
|
|
17566
|
+
const memory = toRecord14(config.memory);
|
|
17567
|
+
const provider = readString15(memory.provider);
|
|
16073
17568
|
if (!provider || provider === "built-in" || provider === "builtin" || provider === "built_in") {
|
|
16074
17569
|
return null;
|
|
16075
17570
|
}
|
|
16076
17571
|
return provider;
|
|
16077
17572
|
}
|
|
16078
17573
|
async function patchJsonProviderConfig(profileName, relativePath, patch) {
|
|
16079
|
-
const configPath =
|
|
17574
|
+
const configPath = path19.join(
|
|
16080
17575
|
resolveHermesProfileDir(profileName),
|
|
16081
17576
|
relativePath
|
|
16082
17577
|
);
|
|
@@ -16087,7 +17582,7 @@ async function patchJsonProviderConfig(profileName, relativePath, patch) {
|
|
|
16087
17582
|
next[key] = value;
|
|
16088
17583
|
}
|
|
16089
17584
|
}
|
|
16090
|
-
await mkdir12(
|
|
17585
|
+
await mkdir12(path19.dirname(configPath), { recursive: true, mode: 448 });
|
|
16091
17586
|
await writeFile5(configPath, `${JSON.stringify(next, null, 2)}
|
|
16092
17587
|
`, {
|
|
16093
17588
|
encoding: "utf8",
|
|
@@ -16095,18 +17590,18 @@ async function patchJsonProviderConfig(profileName, relativePath, patch) {
|
|
|
16095
17590
|
});
|
|
16096
17591
|
}
|
|
16097
17592
|
async function readJsonObject(filePath) {
|
|
16098
|
-
const raw = await
|
|
16099
|
-
if (
|
|
17593
|
+
const raw = await readFile14(filePath, "utf8").catch((error) => {
|
|
17594
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
16100
17595
|
return "{}";
|
|
16101
17596
|
}
|
|
16102
17597
|
throw error;
|
|
16103
17598
|
});
|
|
16104
17599
|
try {
|
|
16105
|
-
return
|
|
17600
|
+
return toRecord14(JSON.parse(raw || "{}"));
|
|
16106
17601
|
} catch {
|
|
16107
17602
|
throw new HermesMemoryError(
|
|
16108
17603
|
"memory_provider_config_invalid",
|
|
16109
|
-
`${
|
|
17604
|
+
`${path19.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
|
|
16110
17605
|
);
|
|
16111
17606
|
}
|
|
16112
17607
|
}
|
|
@@ -16114,7 +17609,7 @@ function booleanSetting(key, label, value) {
|
|
|
16114
17609
|
return {
|
|
16115
17610
|
key,
|
|
16116
17611
|
label,
|
|
16117
|
-
value:
|
|
17612
|
+
value: readBoolean3(value) ?? false,
|
|
16118
17613
|
editable: true,
|
|
16119
17614
|
kind: "boolean"
|
|
16120
17615
|
};
|
|
@@ -16127,7 +17622,7 @@ function stringSetting(key, label, value, editable = true) {
|
|
|
16127
17622
|
return {
|
|
16128
17623
|
key,
|
|
16129
17624
|
label,
|
|
16130
|
-
value:
|
|
17625
|
+
value: readString15(value) ?? "",
|
|
16131
17626
|
editable,
|
|
16132
17627
|
kind: "string"
|
|
16133
17628
|
};
|
|
@@ -16142,21 +17637,21 @@ function textSetting(key, label, value, editable = true) {
|
|
|
16142
17637
|
};
|
|
16143
17638
|
}
|
|
16144
17639
|
function selectSetting(key, label, value, options, editable = true) {
|
|
16145
|
-
const stringValue =
|
|
17640
|
+
const stringValue = readString15(value) ?? options[0] ?? null;
|
|
16146
17641
|
return { key, label, value: stringValue, editable, kind: "select", options };
|
|
16147
17642
|
}
|
|
16148
17643
|
async function readMemoryLimits(profileName) {
|
|
16149
|
-
const raw = await
|
|
17644
|
+
const raw = await readFile14(
|
|
16150
17645
|
resolveHermesConfigPath(profileName),
|
|
16151
17646
|
"utf8"
|
|
16152
17647
|
).catch((error) => {
|
|
16153
|
-
if (
|
|
17648
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
16154
17649
|
return "";
|
|
16155
17650
|
}
|
|
16156
17651
|
throw error;
|
|
16157
17652
|
});
|
|
16158
|
-
const config = raw ?
|
|
16159
|
-
const memory =
|
|
17653
|
+
const config = raw ? toRecord14(YAML4.parse(raw)) : {};
|
|
17654
|
+
const memory = toRecord14(config.memory);
|
|
16160
17655
|
return {
|
|
16161
17656
|
memory: readPositiveInteger3(memory.memory_char_limit) ?? DEFAULT_MEMORY_LIMIT,
|
|
16162
17657
|
user: readPositiveInteger3(memory.user_char_limit) ?? DEFAULT_USER_LIMIT
|
|
@@ -16212,17 +17707,17 @@ function hashString(value) {
|
|
|
16212
17707
|
}
|
|
16213
17708
|
return hash.toString(16);
|
|
16214
17709
|
}
|
|
16215
|
-
function
|
|
17710
|
+
function toRecord14(value) {
|
|
16216
17711
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
16217
17712
|
}
|
|
16218
|
-
function
|
|
17713
|
+
function readString15(value) {
|
|
16219
17714
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
16220
17715
|
}
|
|
16221
17716
|
function readPositiveInteger3(value) {
|
|
16222
17717
|
const numberValue = typeof value === "number" ? value : typeof value === "string" ? Number(value.trim()) : NaN;
|
|
16223
17718
|
return Number.isFinite(numberValue) && numberValue > 0 ? Math.floor(numberValue) : void 0;
|
|
16224
17719
|
}
|
|
16225
|
-
function
|
|
17720
|
+
function readBoolean3(value) {
|
|
16226
17721
|
if (typeof value === "boolean") {
|
|
16227
17722
|
return value;
|
|
16228
17723
|
}
|
|
@@ -16243,7 +17738,7 @@ function formatEnvValue3(value) {
|
|
|
16243
17738
|
function escapeRegExp3(value) {
|
|
16244
17739
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
16245
17740
|
}
|
|
16246
|
-
function
|
|
17741
|
+
function isNodeError15(error, code) {
|
|
16247
17742
|
return error instanceof Error && "code" in error && error.code === code;
|
|
16248
17743
|
}
|
|
16249
17744
|
|
|
@@ -16357,7 +17852,7 @@ function registerProfileMemoryRoutes(router, options) {
|
|
|
16357
17852
|
);
|
|
16358
17853
|
}
|
|
16359
17854
|
function readMemoryTarget(body) {
|
|
16360
|
-
const raw =
|
|
17855
|
+
const raw = readString14(body, "target");
|
|
16361
17856
|
if (raw === "memory" || raw === "user") {
|
|
16362
17857
|
return raw;
|
|
16363
17858
|
}
|
|
@@ -16368,7 +17863,7 @@ function readMemoryTarget(body) {
|
|
|
16368
17863
|
);
|
|
16369
17864
|
}
|
|
16370
17865
|
function readMemoryResetTarget(body) {
|
|
16371
|
-
const raw =
|
|
17866
|
+
const raw = readString14(body, "target") ?? "all";
|
|
16372
17867
|
if (raw === "all" || raw === "memory" || raw === "user") {
|
|
16373
17868
|
return raw;
|
|
16374
17869
|
}
|
|
@@ -16379,7 +17874,7 @@ function readMemoryResetTarget(body) {
|
|
|
16379
17874
|
);
|
|
16380
17875
|
}
|
|
16381
17876
|
function readRequiredMemoryContent(body) {
|
|
16382
|
-
const content =
|
|
17877
|
+
const content = readString14(body, "content") ?? readString14(body, "text");
|
|
16383
17878
|
if (!content) {
|
|
16384
17879
|
throw new LinkHttpError(
|
|
16385
17880
|
400,
|
|
@@ -16390,7 +17885,7 @@ function readRequiredMemoryContent(body) {
|
|
|
16390
17885
|
return content;
|
|
16391
17886
|
}
|
|
16392
17887
|
function readRequiredMemoryMatch(body) {
|
|
16393
|
-
const oldText =
|
|
17888
|
+
const oldText = readString14(body, "old_text") ?? readString14(body, "oldText") ?? readString14(body, "match");
|
|
16394
17889
|
if (!oldText) {
|
|
16395
17890
|
throw new LinkHttpError(
|
|
16396
17891
|
400,
|
|
@@ -16401,7 +17896,7 @@ function readRequiredMemoryMatch(body) {
|
|
|
16401
17896
|
return oldText;
|
|
16402
17897
|
}
|
|
16403
17898
|
function readRequiredMemoryProvider(body) {
|
|
16404
|
-
const provider =
|
|
17899
|
+
const provider = readString14(body, "provider") ?? readString14(body, "provider_id") ?? readString14(body, "providerId");
|
|
16405
17900
|
if (!provider) {
|
|
16406
17901
|
throw new LinkHttpError(
|
|
16407
17902
|
400,
|
|
@@ -16413,7 +17908,7 @@ function readRequiredMemoryProvider(body) {
|
|
|
16413
17908
|
}
|
|
16414
17909
|
function readMemorySettingsPatch(body) {
|
|
16415
17910
|
const input = {};
|
|
16416
|
-
const mode =
|
|
17911
|
+
const mode = readString14(body, "mode");
|
|
16417
17912
|
if (mode) {
|
|
16418
17913
|
input.mode = mode;
|
|
16419
17914
|
}
|
|
@@ -16425,7 +17920,7 @@ function readMemorySettingsPatch(body) {
|
|
|
16425
17920
|
if (bankId !== void 0) {
|
|
16426
17921
|
input.bankId = bankId;
|
|
16427
17922
|
}
|
|
16428
|
-
const llmProvider =
|
|
17923
|
+
const llmProvider = readString14(body, "llm_provider") ?? readString14(body, "llmProvider");
|
|
16429
17924
|
if (llmProvider) {
|
|
16430
17925
|
input.llmProvider = llmProvider;
|
|
16431
17926
|
}
|
|
@@ -16441,23 +17936,23 @@ function readMemorySettingsPatch(body) {
|
|
|
16441
17936
|
if (containerTag !== void 0) {
|
|
16442
17937
|
input.containerTag = containerTag;
|
|
16443
17938
|
}
|
|
16444
|
-
const autoRecall =
|
|
17939
|
+
const autoRecall = readBoolean2(body.auto_recall ?? body.autoRecall);
|
|
16445
17940
|
if (autoRecall !== void 0) {
|
|
16446
17941
|
input.autoRecall = autoRecall;
|
|
16447
17942
|
}
|
|
16448
|
-
const autoCapture =
|
|
17943
|
+
const autoCapture = readBoolean2(body.auto_capture ?? body.autoCapture);
|
|
16449
17944
|
if (autoCapture !== void 0) {
|
|
16450
17945
|
input.autoCapture = autoCapture;
|
|
16451
17946
|
}
|
|
16452
|
-
const autoRetain =
|
|
17947
|
+
const autoRetain = readBoolean2(body.auto_retain ?? body.autoRetain);
|
|
16453
17948
|
if (autoRetain !== void 0) {
|
|
16454
17949
|
input.autoRetain = autoRetain;
|
|
16455
17950
|
}
|
|
16456
|
-
const memoryMode =
|
|
17951
|
+
const memoryMode = readString14(body, "memory_mode") ?? readString14(body, "memoryMode");
|
|
16457
17952
|
if (memoryMode) {
|
|
16458
17953
|
input.memoryMode = memoryMode;
|
|
16459
17954
|
}
|
|
16460
|
-
const recallBudget =
|
|
17955
|
+
const recallBudget = readString14(body, "recall_budget") ?? readString14(body, "recallBudget");
|
|
16461
17956
|
if (recallBudget) {
|
|
16462
17957
|
input.recallBudget = recallBudget;
|
|
16463
17958
|
}
|
|
@@ -16473,11 +17968,11 @@ function readMemorySettingsPatch(body) {
|
|
|
16473
17968
|
if (profileFrequency !== void 0) {
|
|
16474
17969
|
input.profileFrequency = profileFrequency;
|
|
16475
17970
|
}
|
|
16476
|
-
const captureMode =
|
|
17971
|
+
const captureMode = readString14(body, "capture_mode") ?? readString14(body, "captureMode");
|
|
16477
17972
|
if (captureMode) {
|
|
16478
17973
|
input.captureMode = captureMode;
|
|
16479
17974
|
}
|
|
16480
|
-
const searchMode =
|
|
17975
|
+
const searchMode = readString14(body, "search_mode") ?? readString14(body, "searchMode");
|
|
16481
17976
|
if (searchMode) {
|
|
16482
17977
|
input.searchMode = searchMode;
|
|
16483
17978
|
}
|
|
@@ -16511,19 +18006,19 @@ function readMemorySettingsPatch(body) {
|
|
|
16511
18006
|
if (aiPeer !== void 0) {
|
|
16512
18007
|
input.aiPeer = aiPeer;
|
|
16513
18008
|
}
|
|
16514
|
-
const recallMode =
|
|
18009
|
+
const recallMode = readString14(body, "recall_mode") ?? readString14(body, "recallMode");
|
|
16515
18010
|
if (recallMode) {
|
|
16516
18011
|
input.recallMode = recallMode;
|
|
16517
18012
|
}
|
|
16518
|
-
const writeFrequency =
|
|
18013
|
+
const writeFrequency = readString14(body, "write_frequency") ?? readString14(body, "writeFrequency");
|
|
16519
18014
|
if (writeFrequency) {
|
|
16520
18015
|
input.writeFrequency = writeFrequency;
|
|
16521
18016
|
}
|
|
16522
|
-
const saveMessages =
|
|
18017
|
+
const saveMessages = readBoolean2(body.save_messages ?? body.saveMessages);
|
|
16523
18018
|
if (saveMessages !== void 0) {
|
|
16524
18019
|
input.saveMessages = saveMessages;
|
|
16525
18020
|
}
|
|
16526
|
-
const sessionStrategy =
|
|
18021
|
+
const sessionStrategy = readString14(body, "session_strategy") ?? readString14(body, "sessionStrategy");
|
|
16527
18022
|
if (sessionStrategy) {
|
|
16528
18023
|
input.sessionStrategy = sessionStrategy;
|
|
16529
18024
|
}
|
|
@@ -16559,7 +18054,7 @@ function readMemorySettingsPatch(body) {
|
|
|
16559
18054
|
if (agentId !== void 0) {
|
|
16560
18055
|
input.agentId = agentId;
|
|
16561
18056
|
}
|
|
16562
|
-
const rerank =
|
|
18057
|
+
const rerank = readBoolean2(body.rerank);
|
|
16563
18058
|
if (rerank !== void 0) {
|
|
16564
18059
|
input.rerank = rerank;
|
|
16565
18060
|
}
|
|
@@ -16583,7 +18078,7 @@ function readMemorySettingsPatch(body) {
|
|
|
16583
18078
|
if (dbPath !== void 0) {
|
|
16584
18079
|
input.dbPath = dbPath;
|
|
16585
18080
|
}
|
|
16586
|
-
const autoExtract =
|
|
18081
|
+
const autoExtract = readBoolean2(body.auto_extract ?? body.autoExtract);
|
|
16587
18082
|
if (autoExtract !== void 0) {
|
|
16588
18083
|
input.autoExtract = autoExtract;
|
|
16589
18084
|
}
|
|
@@ -16654,12 +18149,12 @@ function toMemoryHttpError(error) {
|
|
|
16654
18149
|
import {
|
|
16655
18150
|
copyFile as copyFile4,
|
|
16656
18151
|
mkdir as mkdir13,
|
|
16657
|
-
readFile as
|
|
16658
|
-
readdir as
|
|
18152
|
+
readFile as readFile15,
|
|
18153
|
+
readdir as readdir10,
|
|
16659
18154
|
rename as rename7,
|
|
16660
18155
|
writeFile as writeFile6
|
|
16661
18156
|
} from "fs/promises";
|
|
16662
|
-
import
|
|
18157
|
+
import path20 from "path";
|
|
16663
18158
|
import YAML5 from "yaml";
|
|
16664
18159
|
var HermesSkillNotFoundError = class extends Error {
|
|
16665
18160
|
constructor(skillName) {
|
|
@@ -16673,7 +18168,7 @@ var EXCLUDED_SKILL_DIRS = /* @__PURE__ */ new Set([".git", ".github", ".hub"]);
|
|
|
16673
18168
|
async function listHermesProfileSkills(profileName, paths = resolveRuntimePaths()) {
|
|
16674
18169
|
const profile = await readExistingProfile(profileName, paths);
|
|
16675
18170
|
const profileDir = resolveHermesProfileDir(profile.name);
|
|
16676
|
-
const skillsRoot =
|
|
18171
|
+
const skillsRoot = path20.join(profileDir, "skills");
|
|
16677
18172
|
const [skillFiles, disabled, provenance] = await Promise.all([
|
|
16678
18173
|
findSkillFiles(skillsRoot),
|
|
16679
18174
|
readDisabledSkillNames(resolveHermesConfigPath(profile.name)),
|
|
@@ -16750,9 +18245,9 @@ async function findSkillFiles(root) {
|
|
|
16750
18245
|
return results.sort((left, right) => left.localeCompare(right));
|
|
16751
18246
|
}
|
|
16752
18247
|
async function collectSkillFiles(directory, results) {
|
|
16753
|
-
const entries = await
|
|
18248
|
+
const entries = await readdir10(directory, { withFileTypes: true }).catch(
|
|
16754
18249
|
(error) => {
|
|
16755
|
-
if (
|
|
18250
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
16756
18251
|
return [];
|
|
16757
18252
|
}
|
|
16758
18253
|
throw error;
|
|
@@ -16764,7 +18259,7 @@ async function collectSkillFiles(directory, results) {
|
|
|
16764
18259
|
if (EXCLUDED_SKILL_DIRS.has(entry.name)) {
|
|
16765
18260
|
continue;
|
|
16766
18261
|
}
|
|
16767
|
-
const entryPath =
|
|
18262
|
+
const entryPath = path20.join(directory, entry.name);
|
|
16768
18263
|
if (entry.isDirectory()) {
|
|
16769
18264
|
await collectSkillFiles(entryPath, results);
|
|
16770
18265
|
continue;
|
|
@@ -16775,9 +18270,9 @@ async function collectSkillFiles(directory, results) {
|
|
|
16775
18270
|
}
|
|
16776
18271
|
}
|
|
16777
18272
|
async function readSkillMetadata(input) {
|
|
16778
|
-
const raw = await
|
|
18273
|
+
const raw = await readFile15(input.skillFile, "utf8").catch(
|
|
16779
18274
|
(error) => {
|
|
16780
|
-
if (
|
|
18275
|
+
if (isNodeError16(error, "ENOENT") || isNodeError16(error, "EACCES")) {
|
|
16781
18276
|
return null;
|
|
16782
18277
|
}
|
|
16783
18278
|
throw error;
|
|
@@ -16786,16 +18281,16 @@ async function readSkillMetadata(input) {
|
|
|
16786
18281
|
if (raw === null) {
|
|
16787
18282
|
return null;
|
|
16788
18283
|
}
|
|
16789
|
-
const skillDir =
|
|
18284
|
+
const skillDir = path20.dirname(input.skillFile);
|
|
16790
18285
|
const { frontmatter, body } = parseSkillDocument(raw.slice(0, 4e3));
|
|
16791
18286
|
const name = normalizeSkillName(
|
|
16792
|
-
|
|
18287
|
+
readString16(frontmatter.name) ?? path20.basename(skillDir)
|
|
16793
18288
|
);
|
|
16794
18289
|
if (!name) {
|
|
16795
18290
|
return null;
|
|
16796
18291
|
}
|
|
16797
18292
|
const description = normalizeDescription(
|
|
16798
|
-
|
|
18293
|
+
readString16(frontmatter.description) ?? firstBodyDescription(body)
|
|
16799
18294
|
);
|
|
16800
18295
|
const provenance = input.provenance.get(name) ?? {
|
|
16801
18296
|
source: "local",
|
|
@@ -16808,7 +18303,7 @@ async function readSkillMetadata(input) {
|
|
|
16808
18303
|
enabled: !input.disabled.has(name),
|
|
16809
18304
|
source: provenance.source,
|
|
16810
18305
|
trust: provenance.trust,
|
|
16811
|
-
relativePath:
|
|
18306
|
+
relativePath: path20.relative(input.skillsRoot, skillDir)
|
|
16812
18307
|
};
|
|
16813
18308
|
}
|
|
16814
18309
|
function parseSkillDocument(raw) {
|
|
@@ -16821,7 +18316,7 @@ function parseSkillDocument(raw) {
|
|
|
16821
18316
|
}
|
|
16822
18317
|
try {
|
|
16823
18318
|
return {
|
|
16824
|
-
frontmatter:
|
|
18319
|
+
frontmatter: toRecord15(YAML5.parse(match[1] ?? "")),
|
|
16825
18320
|
body: content.slice(match[0].length)
|
|
16826
18321
|
};
|
|
16827
18322
|
} catch {
|
|
@@ -16829,8 +18324,8 @@ function parseSkillDocument(raw) {
|
|
|
16829
18324
|
}
|
|
16830
18325
|
}
|
|
16831
18326
|
function categoryFromPath(skillsRoot, skillFile) {
|
|
16832
|
-
const relative =
|
|
16833
|
-
const parts = relative.split(
|
|
18327
|
+
const relative = path20.relative(skillsRoot, skillFile);
|
|
18328
|
+
const parts = relative.split(path20.sep).filter(Boolean);
|
|
16834
18329
|
return parts.length >= 3 ? parts[0] : null;
|
|
16835
18330
|
}
|
|
16836
18331
|
function firstBodyDescription(body) {
|
|
@@ -16853,8 +18348,8 @@ function normalizeDescription(value) {
|
|
|
16853
18348
|
return `${description.slice(0, MAX_DESCRIPTION_LENGTH - 3)}...`;
|
|
16854
18349
|
}
|
|
16855
18350
|
async function readDisabledSkillNames(configPath) {
|
|
16856
|
-
const raw = await
|
|
16857
|
-
if (
|
|
18351
|
+
const raw = await readFile15(configPath, "utf8").catch((error) => {
|
|
18352
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
16858
18353
|
return "";
|
|
16859
18354
|
}
|
|
16860
18355
|
throw error;
|
|
@@ -16862,8 +18357,8 @@ async function readDisabledSkillNames(configPath) {
|
|
|
16862
18357
|
if (!raw.trim()) {
|
|
16863
18358
|
return /* @__PURE__ */ new Set();
|
|
16864
18359
|
}
|
|
16865
|
-
const config =
|
|
16866
|
-
const skills =
|
|
18360
|
+
const config = toRecord15(YAML5.parse(raw));
|
|
18361
|
+
const skills = toRecord15(config.skills);
|
|
16867
18362
|
return new Set(readStringList3(skills.disabled));
|
|
16868
18363
|
}
|
|
16869
18364
|
async function readSkillProvenance(root) {
|
|
@@ -16877,9 +18372,9 @@ async function readSkillProvenance(root) {
|
|
|
16877
18372
|
return provenance;
|
|
16878
18373
|
}
|
|
16879
18374
|
async function readBundledSkillNames(root) {
|
|
16880
|
-
const raw = await
|
|
18375
|
+
const raw = await readFile15(path20.join(root, ".bundled_manifest"), "utf8").catch(
|
|
16881
18376
|
(error) => {
|
|
16882
|
-
if (
|
|
18377
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
16883
18378
|
return "";
|
|
16884
18379
|
}
|
|
16885
18380
|
throw error;
|
|
@@ -16900,9 +18395,9 @@ async function readBundledSkillNames(root) {
|
|
|
16900
18395
|
return names;
|
|
16901
18396
|
}
|
|
16902
18397
|
async function readHubInstalledSkills(root) {
|
|
16903
|
-
const raw = await
|
|
18398
|
+
const raw = await readFile15(path20.join(root, ".hub", "lock.json"), "utf8").catch(
|
|
16904
18399
|
(error) => {
|
|
16905
|
-
if (
|
|
18400
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
16906
18401
|
return "";
|
|
16907
18402
|
}
|
|
16908
18403
|
throw error;
|
|
@@ -16913,17 +18408,17 @@ async function readHubInstalledSkills(root) {
|
|
|
16913
18408
|
}
|
|
16914
18409
|
let lock;
|
|
16915
18410
|
try {
|
|
16916
|
-
lock =
|
|
18411
|
+
lock = toRecord15(JSON.parse(raw));
|
|
16917
18412
|
} catch {
|
|
16918
18413
|
return /* @__PURE__ */ new Map();
|
|
16919
18414
|
}
|
|
16920
|
-
const installed =
|
|
18415
|
+
const installed = toRecord15(lock.installed);
|
|
16921
18416
|
const result = /* @__PURE__ */ new Map();
|
|
16922
18417
|
for (const [name, rawEntry] of Object.entries(installed)) {
|
|
16923
|
-
const entry =
|
|
18418
|
+
const entry = toRecord15(rawEntry);
|
|
16924
18419
|
result.set(normalizeSkillName(name), {
|
|
16925
|
-
source:
|
|
16926
|
-
trust:
|
|
18420
|
+
source: readString16(entry.source) ?? "hub",
|
|
18421
|
+
trust: readString16(entry.trust_level) ?? null
|
|
16927
18422
|
});
|
|
16928
18423
|
}
|
|
16929
18424
|
return result;
|
|
@@ -16972,9 +18467,9 @@ function compareCategoryNames(left, right) {
|
|
|
16972
18467
|
return left.localeCompare(right);
|
|
16973
18468
|
}
|
|
16974
18469
|
async function readHermesConfigDocument2(configPath) {
|
|
16975
|
-
const existingRaw = await
|
|
18470
|
+
const existingRaw = await readFile15(configPath, "utf8").catch(
|
|
16976
18471
|
(error) => {
|
|
16977
|
-
if (
|
|
18472
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
16978
18473
|
return null;
|
|
16979
18474
|
}
|
|
16980
18475
|
throw error;
|
|
@@ -16983,13 +18478,13 @@ async function readHermesConfigDocument2(configPath) {
|
|
|
16983
18478
|
const document = existingRaw ? YAML5.parseDocument(existingRaw) : new YAML5.Document({});
|
|
16984
18479
|
return {
|
|
16985
18480
|
document,
|
|
16986
|
-
config:
|
|
18481
|
+
config: toRecord15(document.toJSON()),
|
|
16987
18482
|
existingRaw
|
|
16988
18483
|
};
|
|
16989
18484
|
}
|
|
16990
18485
|
async function writeHermesConfigDocument2(input) {
|
|
16991
18486
|
const backupPath = input.existingRaw ? `${input.configPath}.bak.${Date.now()}` : null;
|
|
16992
|
-
await mkdir13(
|
|
18487
|
+
await mkdir13(path20.dirname(input.configPath), { recursive: true, mode: 448 });
|
|
16993
18488
|
if (backupPath) {
|
|
16994
18489
|
await copyFile4(input.configPath, backupPath);
|
|
16995
18490
|
}
|
|
@@ -17005,21 +18500,21 @@ function readStringList3(value) {
|
|
|
17005
18500
|
}
|
|
17006
18501
|
return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
17007
18502
|
}
|
|
17008
|
-
function
|
|
18503
|
+
function readString16(value) {
|
|
17009
18504
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
17010
18505
|
}
|
|
17011
|
-
function
|
|
18506
|
+
function toRecord15(value) {
|
|
17012
18507
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
17013
18508
|
}
|
|
17014
18509
|
function ensureRecord3(target, key) {
|
|
17015
|
-
const current =
|
|
18510
|
+
const current = toRecord15(target[key]);
|
|
17016
18511
|
if (current === target[key]) {
|
|
17017
18512
|
return current;
|
|
17018
18513
|
}
|
|
17019
18514
|
target[key] = current;
|
|
17020
18515
|
return current;
|
|
17021
18516
|
}
|
|
17022
|
-
function
|
|
18517
|
+
function isNodeError16(error, code) {
|
|
17023
18518
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
17024
18519
|
}
|
|
17025
18520
|
|
|
@@ -17034,7 +18529,7 @@ function registerProfileSkillRoutes(router, options) {
|
|
|
17034
18529
|
router.patch("/api/v1/profiles/:name/skills/:skillName", async (ctx) => {
|
|
17035
18530
|
await authenticateRequest(ctx, paths);
|
|
17036
18531
|
const body = await readJsonBody(ctx.req);
|
|
17037
|
-
const enabled =
|
|
18532
|
+
const enabled = readBoolean2(body.enabled);
|
|
17038
18533
|
if (enabled === void 0) {
|
|
17039
18534
|
throw new LinkHttpError(
|
|
17040
18535
|
400,
|
|
@@ -17078,22 +18573,18 @@ function registerProfileRoutes(router, options) {
|
|
|
17078
18573
|
await authenticateRequest(ctx, paths);
|
|
17079
18574
|
ctx.respond = false;
|
|
17080
18575
|
const response = ctx.res;
|
|
17081
|
-
|
|
17082
|
-
|
|
17083
|
-
|
|
17084
|
-
|
|
18576
|
+
let unsubscribe = () => {
|
|
18577
|
+
};
|
|
18578
|
+
beginSseStream(ctx.req, response, {
|
|
18579
|
+
onClose: () => unsubscribe()
|
|
18580
|
+
});
|
|
17085
18581
|
writeProfileCreationSseEvent(
|
|
17086
18582
|
response,
|
|
17087
18583
|
await readHermesProfileCreationStatus(paths)
|
|
17088
18584
|
);
|
|
17089
|
-
|
|
18585
|
+
unsubscribe = subscribeHermesProfileCreationStatus((status) => {
|
|
17090
18586
|
writeProfileCreationSseEvent(response, status);
|
|
17091
18587
|
});
|
|
17092
|
-
const cleanup = () => {
|
|
17093
|
-
unsubscribe();
|
|
17094
|
-
response.end();
|
|
17095
|
-
};
|
|
17096
|
-
ctx.req.on("close", cleanup);
|
|
17097
18588
|
});
|
|
17098
18589
|
router.get("/api/v1/profiles/:name/status", async (ctx) => {
|
|
17099
18590
|
await authenticateRequest(ctx, paths);
|
|
@@ -17309,10 +18800,10 @@ function isProfileAvatarUrl(value) {
|
|
|
17309
18800
|
return isHttpUrl(value) || /^data:image\/[a-z0-9.+-]+;base64,/iu.test(value);
|
|
17310
18801
|
}
|
|
17311
18802
|
function writeProfileCreationSseEvent(response, status) {
|
|
17312
|
-
response
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
18803
|
+
writeJsonSseEvent(response, {
|
|
18804
|
+
event: "profile.creation.status",
|
|
18805
|
+
data: status
|
|
18806
|
+
});
|
|
17316
18807
|
}
|
|
17317
18808
|
|
|
17318
18809
|
// src/http/routes/runs.ts
|
|
@@ -17322,7 +18813,7 @@ function registerRunRoutes(router, options) {
|
|
|
17322
18813
|
router.post("/api/v1/runs", async (ctx) => {
|
|
17323
18814
|
await authenticateRequest(ctx, paths);
|
|
17324
18815
|
const body = await readJsonBody(ctx.req);
|
|
17325
|
-
const input =
|
|
18816
|
+
const input = readString14(body, "input");
|
|
17326
18817
|
if (!input) {
|
|
17327
18818
|
throw new LinkHttpError(400, "run_input_required", "input is required");
|
|
17328
18819
|
}
|
|
@@ -17330,11 +18821,11 @@ function registerRunRoutes(router, options) {
|
|
|
17330
18821
|
ctx.body = await createHermesRun(
|
|
17331
18822
|
{
|
|
17332
18823
|
input,
|
|
17333
|
-
instructions:
|
|
18824
|
+
instructions: readString14(body, "instructions") ?? void 0,
|
|
17334
18825
|
conversation_history: readConversationHistory(
|
|
17335
18826
|
body.conversation_history ?? body.conversationHistory
|
|
17336
18827
|
),
|
|
17337
|
-
session_id:
|
|
18828
|
+
session_id: readString14(body, "session_id") ?? readString14(body, "sessionId") ?? void 0
|
|
17338
18829
|
},
|
|
17339
18830
|
{ logger, profileName: readOptionalProfileName(body) }
|
|
17340
18831
|
);
|
|
@@ -17505,8 +18996,8 @@ function readModelList(payload) {
|
|
|
17505
18996
|
// src/hermes/updates.ts
|
|
17506
18997
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
17507
18998
|
import { spawn as spawn3 } from "child_process";
|
|
17508
|
-
import { mkdir as mkdir14, readFile as
|
|
17509
|
-
import
|
|
18999
|
+
import { mkdir as mkdir14, readFile as readFile16, rm as rm8 } from "fs/promises";
|
|
19000
|
+
import path21 from "path";
|
|
17510
19001
|
var SERVER_HERMES_RELEASES_LATEST_PATH = "/api/v1/hermes-agent/releases/latest";
|
|
17511
19002
|
var RELEASE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
17512
19003
|
var RELEASE_FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -17727,24 +19218,24 @@ async function readRemoteRelease(options, now) {
|
|
|
17727
19218
|
}
|
|
17728
19219
|
}
|
|
17729
19220
|
function normalizeServerReleaseSnapshot(payload) {
|
|
17730
|
-
const snapshot =
|
|
19221
|
+
const snapshot = toRecord16(payload);
|
|
17731
19222
|
const remote = toNullableRecord(snapshot.remote);
|
|
17732
19223
|
return {
|
|
17733
19224
|
remote: remote ? normalizeServerRelease(remote) : null,
|
|
17734
|
-
cacheState:
|
|
17735
|
-
issue:
|
|
19225
|
+
cacheState: readString17(snapshot, "cache_state") ?? readString17(snapshot, "cacheState"),
|
|
19226
|
+
issue: readString17(snapshot, "issue")
|
|
17736
19227
|
};
|
|
17737
19228
|
}
|
|
17738
19229
|
function normalizeServerRelease(payload) {
|
|
17739
|
-
const tag =
|
|
17740
|
-
const name =
|
|
19230
|
+
const tag = readString17(payload, "tag");
|
|
19231
|
+
const name = readString17(payload, "name");
|
|
17741
19232
|
return {
|
|
17742
|
-
version:
|
|
19233
|
+
version: readString17(payload, "version") ?? extractSemver(name) ?? extractTagSemver(tag),
|
|
17743
19234
|
tag,
|
|
17744
19235
|
name,
|
|
17745
|
-
releaseUrl:
|
|
17746
|
-
publishedAt:
|
|
17747
|
-
fetchedAt:
|
|
19236
|
+
releaseUrl: readString17(payload, "releaseUrl") ?? readString17(payload, "release_url"),
|
|
19237
|
+
publishedAt: readString17(payload, "publishedAt") ?? readString17(payload, "published_at"),
|
|
19238
|
+
fetchedAt: readString17(payload, "fetchedAt") ?? readString17(payload, "fetched_at") ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
17748
19239
|
};
|
|
17749
19240
|
}
|
|
17750
19241
|
async function readReleaseCache(paths) {
|
|
@@ -17763,7 +19254,7 @@ async function writeUpdateState(paths, state) {
|
|
|
17763
19254
|
await writeJsonFile(updateStatePath(paths), state);
|
|
17764
19255
|
}
|
|
17765
19256
|
async function readUpdateLogLines(paths) {
|
|
17766
|
-
const raw = await
|
|
19257
|
+
const raw = await readFile16(updateLogPath(paths), "utf8").catch(() => "");
|
|
17767
19258
|
if (!raw.trim()) {
|
|
17768
19259
|
return [];
|
|
17769
19260
|
}
|
|
@@ -17772,13 +19263,13 @@ async function readUpdateLogLines(paths) {
|
|
|
17772
19263
|
);
|
|
17773
19264
|
}
|
|
17774
19265
|
function releaseCachePath(paths) {
|
|
17775
|
-
return
|
|
19266
|
+
return path21.join(paths.indexesDir, "hermes-release-check.json");
|
|
17776
19267
|
}
|
|
17777
19268
|
function updateStatePath(paths) {
|
|
17778
|
-
return
|
|
19269
|
+
return path21.join(paths.runDir, "hermes-update-state.json");
|
|
17779
19270
|
}
|
|
17780
19271
|
function updateLogPath(paths) {
|
|
17781
|
-
return
|
|
19272
|
+
return path21.join(paths.logsDir, UPDATE_LOG_FILE);
|
|
17782
19273
|
}
|
|
17783
19274
|
async function clearUpdateLogFiles(paths) {
|
|
17784
19275
|
const primary = updateLogPath(paths);
|
|
@@ -17816,7 +19307,7 @@ function compareSemver2(left, right) {
|
|
|
17816
19307
|
}
|
|
17817
19308
|
return 0;
|
|
17818
19309
|
}
|
|
17819
|
-
function
|
|
19310
|
+
function toRecord16(value) {
|
|
17820
19311
|
return typeof value === "object" && value !== null ? value : {};
|
|
17821
19312
|
}
|
|
17822
19313
|
function toNullableRecord(value) {
|
|
@@ -17862,7 +19353,7 @@ function isRecentRunningState2(state) {
|
|
|
17862
19353
|
const startedAt = Date.parse(state.started_at);
|
|
17863
19354
|
return Number.isFinite(startedAt) && Date.now() - startedAt < 3e4;
|
|
17864
19355
|
}
|
|
17865
|
-
function
|
|
19356
|
+
function readString17(payload, key) {
|
|
17866
19357
|
const value = payload[key];
|
|
17867
19358
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
17868
19359
|
}
|
|
@@ -17870,13 +19361,13 @@ function readString16(payload, key) {
|
|
|
17870
19361
|
// src/link/updates.ts
|
|
17871
19362
|
import { spawn as spawn5 } from "child_process";
|
|
17872
19363
|
import { EventEmitter as EventEmitter4 } from "events";
|
|
17873
|
-
import { mkdir as mkdir17, readFile as
|
|
17874
|
-
import
|
|
19364
|
+
import { mkdir as mkdir17, readFile as readFile18, rm as rm11 } from "fs/promises";
|
|
19365
|
+
import path23 from "path";
|
|
17875
19366
|
|
|
17876
19367
|
// src/daemon/process.ts
|
|
17877
19368
|
import { spawn as spawn4 } from "child_process";
|
|
17878
|
-
import { mkdir as mkdir16, readFile as
|
|
17879
|
-
import
|
|
19369
|
+
import { mkdir as mkdir16, readFile as readFile17, rm as rm10 } from "fs/promises";
|
|
19370
|
+
import path22 from "path";
|
|
17880
19371
|
|
|
17881
19372
|
// src/daemon/service.ts
|
|
17882
19373
|
import { createServer } from "http";
|
|
@@ -18054,7 +19545,7 @@ async function handleFrame(socket, raw, localPort, abortControllers) {
|
|
|
18054
19545
|
// src/runtime/system-info.ts
|
|
18055
19546
|
import { execFileSync } from "child_process";
|
|
18056
19547
|
import { readFileSync } from "fs";
|
|
18057
|
-
import
|
|
19548
|
+
import os6 from "os";
|
|
18058
19549
|
function readLinkSystemInfo() {
|
|
18059
19550
|
const platform = process.platform;
|
|
18060
19551
|
const hostname = readHostname(platform);
|
|
@@ -18093,7 +19584,7 @@ function readHostname(platform) {
|
|
|
18093
19584
|
return computerName;
|
|
18094
19585
|
}
|
|
18095
19586
|
}
|
|
18096
|
-
return normalizeText(
|
|
19587
|
+
return normalizeText(os6.hostname());
|
|
18097
19588
|
}
|
|
18098
19589
|
function readOsLabel(platform) {
|
|
18099
19590
|
if (platform === "darwin") {
|
|
@@ -18101,12 +19592,12 @@ function readOsLabel(platform) {
|
|
|
18101
19592
|
return version ? `macOS ${version}` : "macOS";
|
|
18102
19593
|
}
|
|
18103
19594
|
if (platform === "linux") {
|
|
18104
|
-
return readLinuxOsRelease() ?? `Linux ${
|
|
19595
|
+
return readLinuxOsRelease() ?? `Linux ${os6.release()}`;
|
|
18105
19596
|
}
|
|
18106
19597
|
if (platform === "win32") {
|
|
18107
|
-
return `Windows ${
|
|
19598
|
+
return `Windows ${os6.release()}`;
|
|
18108
19599
|
}
|
|
18109
|
-
return `${
|
|
19600
|
+
return `${os6.type()} ${os6.release()}`.trim();
|
|
18110
19601
|
}
|
|
18111
19602
|
function readLinuxOsRelease() {
|
|
18112
19603
|
for (const file of ["/etc/os-release", "/usr/lib/os-release"]) {
|
|
@@ -18153,11 +19644,11 @@ function truncateText(value, maxLength) {
|
|
|
18153
19644
|
}
|
|
18154
19645
|
|
|
18155
19646
|
// src/topology/network.ts
|
|
18156
|
-
import
|
|
19647
|
+
import os8 from "os";
|
|
18157
19648
|
|
|
18158
19649
|
// src/topology/environment.ts
|
|
18159
19650
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
18160
|
-
import
|
|
19651
|
+
import os7 from "os";
|
|
18161
19652
|
function detectRuntimeEnvironment(env = process.env) {
|
|
18162
19653
|
if (isWsl(env)) {
|
|
18163
19654
|
return {
|
|
@@ -18186,7 +19677,7 @@ function isWsl(env) {
|
|
|
18186
19677
|
if (env.WSL_DISTRO_NAME || env.WSL_INTEROP) {
|
|
18187
19678
|
return true;
|
|
18188
19679
|
}
|
|
18189
|
-
const release =
|
|
19680
|
+
const release = os7.release().toLowerCase();
|
|
18190
19681
|
return release.includes("microsoft") || release.includes("wsl");
|
|
18191
19682
|
}
|
|
18192
19683
|
function isContainer(env) {
|
|
@@ -18231,7 +19722,7 @@ async function discoverRouteCandidates(options) {
|
|
|
18231
19722
|
};
|
|
18232
19723
|
}
|
|
18233
19724
|
function discoverLanIps() {
|
|
18234
|
-
return discoverLanIpsFromInterfaces(
|
|
19725
|
+
return discoverLanIpsFromInterfaces(os8.networkInterfaces());
|
|
18235
19726
|
}
|
|
18236
19727
|
function discoverLanIpsFromInterfaces(interfaces) {
|
|
18237
19728
|
const result = /* @__PURE__ */ new Set();
|
|
@@ -18724,6 +20215,33 @@ function startCronDeliveryScheduler(options) {
|
|
|
18724
20215
|
}
|
|
18725
20216
|
};
|
|
18726
20217
|
}
|
|
20218
|
+
function startHermesSessionSyncScheduler(options) {
|
|
20219
|
+
let running = false;
|
|
20220
|
+
const syncSessions = async () => {
|
|
20221
|
+
if (running) {
|
|
20222
|
+
return;
|
|
20223
|
+
}
|
|
20224
|
+
running = true;
|
|
20225
|
+
try {
|
|
20226
|
+
await options.conversations.syncHermesSessions();
|
|
20227
|
+
} catch (error) {
|
|
20228
|
+
void options.logger.warn("hermes_session_sync_failed", {
|
|
20229
|
+
error: error instanceof Error ? error.message : String(error)
|
|
20230
|
+
});
|
|
20231
|
+
} finally {
|
|
20232
|
+
running = false;
|
|
20233
|
+
}
|
|
20234
|
+
};
|
|
20235
|
+
const timer = setInterval(() => {
|
|
20236
|
+
void syncSessions();
|
|
20237
|
+
}, options.intervalMs ?? 10 * 60 * 1e3);
|
|
20238
|
+
timer.unref?.();
|
|
20239
|
+
return {
|
|
20240
|
+
close() {
|
|
20241
|
+
clearInterval(timer);
|
|
20242
|
+
}
|
|
20243
|
+
};
|
|
20244
|
+
}
|
|
18727
20245
|
|
|
18728
20246
|
// src/daemon/service.ts
|
|
18729
20247
|
async function startLinkService(options = {}) {
|
|
@@ -18744,11 +20262,21 @@ async function startLinkService(options = {}) {
|
|
|
18744
20262
|
}
|
|
18745
20263
|
const conversations = new ConversationService(paths, logger);
|
|
18746
20264
|
await conversations.rebuildStatisticsIndex();
|
|
20265
|
+
const triggerHermesSessionSync = () => {
|
|
20266
|
+
void conversations.syncHermesSessions().catch((error) => {
|
|
20267
|
+
void logger.warn("hermes_session_sync_failed", {
|
|
20268
|
+
error: error instanceof Error ? error.message : String(error)
|
|
20269
|
+
});
|
|
20270
|
+
});
|
|
20271
|
+
};
|
|
18747
20272
|
const app = await createApp({
|
|
18748
20273
|
paths,
|
|
18749
20274
|
logger,
|
|
18750
20275
|
conversations,
|
|
18751
|
-
onPairingClaimed:
|
|
20276
|
+
onPairingClaimed: async () => {
|
|
20277
|
+
triggerHermesSessionSync();
|
|
20278
|
+
await options.onPairingClaimed?.();
|
|
20279
|
+
}
|
|
18752
20280
|
});
|
|
18753
20281
|
const server = createServer(app.callback());
|
|
18754
20282
|
try {
|
|
@@ -18768,11 +20296,16 @@ async function startLinkService(options = {}) {
|
|
|
18768
20296
|
port: config.port,
|
|
18769
20297
|
link_id: identity?.link_id ?? null
|
|
18770
20298
|
});
|
|
20299
|
+
triggerHermesSessionSync();
|
|
18771
20300
|
const scheduler = startCronDeliveryScheduler({
|
|
18772
20301
|
paths,
|
|
18773
20302
|
conversations,
|
|
18774
20303
|
logger
|
|
18775
20304
|
});
|
|
20305
|
+
const hermesSessionSyncScheduler = startHermesSessionSyncScheduler({
|
|
20306
|
+
conversations,
|
|
20307
|
+
logger
|
|
20308
|
+
});
|
|
18776
20309
|
let relay = null;
|
|
18777
20310
|
if (identity?.link_id) {
|
|
18778
20311
|
relay = connectRelayControl({
|
|
@@ -18805,6 +20338,7 @@ async function startLinkService(options = {}) {
|
|
|
18805
20338
|
return {
|
|
18806
20339
|
async close() {
|
|
18807
20340
|
scheduler.close();
|
|
20341
|
+
hermesSessionSyncScheduler.close();
|
|
18808
20342
|
lanIpMonitor.close();
|
|
18809
20343
|
relay?.close();
|
|
18810
20344
|
await closeServer(server);
|
|
@@ -18917,7 +20451,7 @@ async function runDaemonSupervisor(paths = resolveRuntimePaths()) {
|
|
|
18917
20451
|
await mkdir16(paths.logsDir, { recursive: true, mode: 448 });
|
|
18918
20452
|
const log = createRotatingTextLogWriter({
|
|
18919
20453
|
paths,
|
|
18920
|
-
fileName:
|
|
20454
|
+
fileName: path22.basename(daemonLogFile(paths))
|
|
18921
20455
|
});
|
|
18922
20456
|
const scriptPath = currentCliScriptPath();
|
|
18923
20457
|
const child = spawn4(process.execPath, [scriptPath, "daemon", "--foreground"], {
|
|
@@ -19046,7 +20580,7 @@ function currentCliScriptPath() {
|
|
|
19046
20580
|
return process.argv[1];
|
|
19047
20581
|
}
|
|
19048
20582
|
async function readPid(filePath) {
|
|
19049
|
-
const raw = await
|
|
20583
|
+
const raw = await readFile17(filePath, "utf8").catch(() => null);
|
|
19050
20584
|
if (!raw) {
|
|
19051
20585
|
return null;
|
|
19052
20586
|
}
|
|
@@ -19372,21 +20906,21 @@ async function readRemoteLinkPolicy(options) {
|
|
|
19372
20906
|
}
|
|
19373
20907
|
}
|
|
19374
20908
|
function normalizeServerSnapshot(payload) {
|
|
19375
|
-
const snapshot =
|
|
20909
|
+
const snapshot = toRecord17(payload);
|
|
19376
20910
|
const policy = toNullableRecord2(snapshot.policy);
|
|
19377
20911
|
if (!policy) {
|
|
19378
20912
|
return {
|
|
19379
20913
|
remote: null,
|
|
19380
|
-
issue:
|
|
20914
|
+
issue: readString18(snapshot, "issue")
|
|
19381
20915
|
};
|
|
19382
20916
|
}
|
|
19383
20917
|
const release = toNullableRecord2(snapshot.release);
|
|
19384
|
-
const currentVersion =
|
|
19385
|
-
const minSafeVersion =
|
|
20918
|
+
const currentVersion = readString18(policy, "current_version") ?? readString18(policy, "currentVersion");
|
|
20919
|
+
const minSafeVersion = readString18(policy, "min_safe_version") ?? readString18(policy, "minSafeVersion");
|
|
19386
20920
|
if (!currentVersion) {
|
|
19387
20921
|
return {
|
|
19388
20922
|
remote: null,
|
|
19389
|
-
issue:
|
|
20923
|
+
issue: readString18(snapshot, "issue")
|
|
19390
20924
|
};
|
|
19391
20925
|
}
|
|
19392
20926
|
return {
|
|
@@ -19394,10 +20928,10 @@ function normalizeServerSnapshot(payload) {
|
|
|
19394
20928
|
current_version: currentVersion,
|
|
19395
20929
|
min_safe_version: minSafeVersion,
|
|
19396
20930
|
target_version: currentVersion,
|
|
19397
|
-
release_url: release ?
|
|
19398
|
-
published_at: release ?
|
|
20931
|
+
release_url: release ? readString18(release, "release_url") ?? readString18(release, "releaseUrl") : null,
|
|
20932
|
+
published_at: release ? readString18(release, "published_at") ?? readString18(release, "publishedAt") : null
|
|
19399
20933
|
},
|
|
19400
|
-
issue:
|
|
20934
|
+
issue: readString18(snapshot, "issue")
|
|
19401
20935
|
};
|
|
19402
20936
|
}
|
|
19403
20937
|
async function fetchCurrentLinkReleaseFromServer(options, fetcher) {
|
|
@@ -19447,7 +20981,7 @@ async function writeUpdateState2(paths, state) {
|
|
|
19447
20981
|
await writeJsonFile(updateStatePath2(paths), state);
|
|
19448
20982
|
}
|
|
19449
20983
|
async function readUpdateLogLines2(paths) {
|
|
19450
|
-
const raw = await
|
|
20984
|
+
const raw = await readFile18(updateLogPath2(paths), "utf8").catch(() => "");
|
|
19451
20985
|
if (!raw.trim()) {
|
|
19452
20986
|
return [];
|
|
19453
20987
|
}
|
|
@@ -19456,10 +20990,10 @@ async function readUpdateLogLines2(paths) {
|
|
|
19456
20990
|
);
|
|
19457
20991
|
}
|
|
19458
20992
|
function updateStatePath2(paths) {
|
|
19459
|
-
return
|
|
20993
|
+
return path23.join(paths.runDir, "link-update-state.json");
|
|
19460
20994
|
}
|
|
19461
20995
|
function updateLogPath2(paths) {
|
|
19462
|
-
return
|
|
20996
|
+
return path23.join(paths.logsDir, UPDATE_LOG_FILE2);
|
|
19463
20997
|
}
|
|
19464
20998
|
async function clearUpdateLogFiles2(paths) {
|
|
19465
20999
|
const primary = updateLogPath2(paths);
|
|
@@ -19511,19 +21045,19 @@ function isProcessAlive4(pid) {
|
|
|
19511
21045
|
return false;
|
|
19512
21046
|
}
|
|
19513
21047
|
}
|
|
19514
|
-
function
|
|
21048
|
+
function toRecord17(value) {
|
|
19515
21049
|
return typeof value === "object" && value !== null ? value : {};
|
|
19516
21050
|
}
|
|
19517
21051
|
function toNullableRecord2(value) {
|
|
19518
21052
|
return typeof value === "object" && value !== null ? value : null;
|
|
19519
21053
|
}
|
|
19520
|
-
function
|
|
21054
|
+
function readString18(payload, key) {
|
|
19521
21055
|
const value = payload[key];
|
|
19522
21056
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
19523
21057
|
}
|
|
19524
21058
|
|
|
19525
21059
|
// src/pairing/pairing.ts
|
|
19526
|
-
import
|
|
21060
|
+
import path24 from "path";
|
|
19527
21061
|
import { rm as rm12 } from "fs/promises";
|
|
19528
21062
|
|
|
19529
21063
|
// src/relay/bootstrap.ts
|
|
@@ -19799,8 +21333,8 @@ async function loadRequiredIdentity2(paths) {
|
|
|
19799
21333
|
}
|
|
19800
21334
|
return identity;
|
|
19801
21335
|
}
|
|
19802
|
-
async function postServerJson(serverBaseUrl,
|
|
19803
|
-
const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${
|
|
21336
|
+
async function postServerJson(serverBaseUrl, path25, body) {
|
|
21337
|
+
const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path25}`, {
|
|
19804
21338
|
method: "POST",
|
|
19805
21339
|
headers: {
|
|
19806
21340
|
accept: "application/json",
|
|
@@ -19840,8 +21374,8 @@ function pairingErrorSnapshot(stage, error) {
|
|
|
19840
21374
|
occurred_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
19841
21375
|
};
|
|
19842
21376
|
}
|
|
19843
|
-
async function patchServerJson(serverBaseUrl,
|
|
19844
|
-
const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${
|
|
21377
|
+
async function patchServerJson(serverBaseUrl, path25, token, body) {
|
|
21378
|
+
const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path25}`, {
|
|
19845
21379
|
method: "PATCH",
|
|
19846
21380
|
headers: {
|
|
19847
21381
|
accept: "application/json",
|
|
@@ -19872,10 +21406,10 @@ function readErrorMessage5(payload) {
|
|
|
19872
21406
|
return typeof message === "string" ? message : null;
|
|
19873
21407
|
}
|
|
19874
21408
|
function pairingClaimPath(sessionId, paths) {
|
|
19875
|
-
return
|
|
21409
|
+
return path24.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
|
|
19876
21410
|
}
|
|
19877
21411
|
function pairingSessionPath(sessionId, paths) {
|
|
19878
|
-
return
|
|
21412
|
+
return path24.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
|
|
19879
21413
|
}
|
|
19880
21414
|
function qrPreferredUrls(routes) {
|
|
19881
21415
|
return routes.preferredUrls.filter((url) => !url.includes("/api/v1/relay/links/")).slice(0, 1);
|
|
@@ -19946,8 +21480,8 @@ function registerSystemRoutes(router, options) {
|
|
|
19946
21480
|
});
|
|
19947
21481
|
router.post("/api/v1/pairing/claim", async (ctx) => {
|
|
19948
21482
|
const body = await readJsonBody(ctx.req);
|
|
19949
|
-
const sessionId =
|
|
19950
|
-
const claimToken =
|
|
21483
|
+
const sessionId = readString14(body, "session_id") ?? readString14(body, "sessionId");
|
|
21484
|
+
const claimToken = readString14(body, "claim_token") ?? readString14(body, "claimToken");
|
|
19951
21485
|
if (!sessionId || !claimToken) {
|
|
19952
21486
|
throw new LinkHttpError(
|
|
19953
21487
|
400,
|
|
@@ -19958,10 +21492,10 @@ function registerSystemRoutes(router, options) {
|
|
|
19958
21492
|
const claimed = await claimPairing({
|
|
19959
21493
|
sessionId,
|
|
19960
21494
|
claimToken,
|
|
19961
|
-
deviceLabel:
|
|
19962
|
-
devicePlatform:
|
|
19963
|
-
deviceModel:
|
|
19964
|
-
appInstanceId:
|
|
21495
|
+
deviceLabel: readString14(body, "device_label") ?? readString14(body, "deviceLabel") ?? "HermesPilot App",
|
|
21496
|
+
devicePlatform: readString14(body, "device_platform") ?? readString14(body, "devicePlatform") ?? "unknown",
|
|
21497
|
+
deviceModel: readString14(body, "device_model") ?? readString14(body, "deviceModel"),
|
|
21498
|
+
appInstanceId: readString14(body, "app_instance_id") ?? readString14(body, "appInstanceId"),
|
|
19965
21499
|
paths
|
|
19966
21500
|
});
|
|
19967
21501
|
ctx.body = claimed;
|
|
@@ -20036,9 +21570,9 @@ function registerSystemRoutes(router, options) {
|
|
|
20036
21570
|
const body = await readJsonBody(ctx.req);
|
|
20037
21571
|
const session = await createDeviceSession(
|
|
20038
21572
|
{
|
|
20039
|
-
label:
|
|
20040
|
-
platform:
|
|
20041
|
-
model:
|
|
21573
|
+
label: readString14(body, "device_label") ?? readString14(body, "deviceLabel") ?? "HermesPilot App",
|
|
21574
|
+
platform: readString14(body, "device_platform") ?? readString14(body, "devicePlatform") ?? "unknown",
|
|
21575
|
+
model: readString14(body, "device_model") ?? readString14(body, "deviceModel"),
|
|
20042
21576
|
appInstanceId: auth.appInstanceId
|
|
20043
21577
|
},
|
|
20044
21578
|
paths
|
|
@@ -20067,7 +21601,7 @@ function registerSystemRoutes(router, options) {
|
|
|
20067
21601
|
});
|
|
20068
21602
|
router.post("/api/v1/auth/refresh", async (ctx) => {
|
|
20069
21603
|
const body = await readJsonBody(ctx.req);
|
|
20070
|
-
const refreshToken =
|
|
21604
|
+
const refreshToken = readString14(body, "refresh_token") ?? readString14(body, "refreshToken");
|
|
20071
21605
|
if (!refreshToken) {
|
|
20072
21606
|
throw new LinkHttpError(
|
|
20073
21607
|
400,
|
|
@@ -20078,10 +21612,10 @@ function registerSystemRoutes(router, options) {
|
|
|
20078
21612
|
const session = await refreshDeviceSession(
|
|
20079
21613
|
refreshToken,
|
|
20080
21614
|
{
|
|
20081
|
-
appInstanceId:
|
|
20082
|
-
label:
|
|
20083
|
-
platform:
|
|
20084
|
-
model:
|
|
21615
|
+
appInstanceId: readString14(body, "app_instance_id") ?? readString14(body, "appInstanceId"),
|
|
21616
|
+
label: readString14(body, "device_label") ?? readString14(body, "deviceLabel"),
|
|
21617
|
+
platform: readString14(body, "device_platform") ?? readString14(body, "devicePlatform"),
|
|
21618
|
+
model: readString14(body, "device_model") ?? readString14(body, "deviceModel")
|
|
20085
21619
|
},
|
|
20086
21620
|
paths
|
|
20087
21621
|
);
|
|
@@ -20100,7 +21634,7 @@ function registerSystemRoutes(router, options) {
|
|
|
20100
21634
|
});
|
|
20101
21635
|
router.post("/api/v1/auth/logout", async (ctx) => {
|
|
20102
21636
|
const body = await readJsonBody(ctx.req);
|
|
20103
|
-
const refreshToken =
|
|
21637
|
+
const refreshToken = readString14(body, "refresh_token") ?? readString14(body, "refreshToken");
|
|
20104
21638
|
if (refreshToken) {
|
|
20105
21639
|
await revokeDeviceRefreshToken(refreshToken, paths);
|
|
20106
21640
|
}
|
|
@@ -20325,7 +21859,7 @@ function registerSystemRoutes(router, options) {
|
|
|
20325
21859
|
router.patch("/api/v1/devices/:deviceId", async (ctx) => {
|
|
20326
21860
|
const auth = await authenticateRequest(ctx, paths);
|
|
20327
21861
|
const body = await readJsonBody(ctx.req);
|
|
20328
|
-
const label =
|
|
21862
|
+
const label = readString14(body, "label") ?? readString14(body, "device_label");
|
|
20329
21863
|
if (!label) {
|
|
20330
21864
|
throw new LinkHttpError(
|
|
20331
21865
|
400,
|
|
@@ -20365,11 +21899,11 @@ async function readActiveCronJobCount(profiles, logger) {
|
|
|
20365
21899
|
}, 0);
|
|
20366
21900
|
}
|
|
20367
21901
|
function isActiveCronJob(job) {
|
|
20368
|
-
const enabled =
|
|
21902
|
+
const enabled = readBoolean2(job.enabled) ?? true;
|
|
20369
21903
|
if (!enabled) {
|
|
20370
21904
|
return false;
|
|
20371
21905
|
}
|
|
20372
|
-
const state =
|
|
21906
|
+
const state = readString14(job, "state")?.toLowerCase();
|
|
20373
21907
|
return !["paused", "disabled", "completed", "deleted"].includes(state ?? "");
|
|
20374
21908
|
}
|
|
20375
21909
|
function filterLogsWithinHours(logs, hours, now = Date.now()) {
|
|
@@ -20421,26 +21955,22 @@ function registerHermesUpdateRoutes(router, options) {
|
|
|
20421
21955
|
await authenticateRequest(ctx, paths);
|
|
20422
21956
|
ctx.respond = false;
|
|
20423
21957
|
const response = ctx.res;
|
|
20424
|
-
|
|
20425
|
-
|
|
20426
|
-
|
|
20427
|
-
|
|
21958
|
+
let unsubscribe = () => {
|
|
21959
|
+
};
|
|
21960
|
+
beginSseStream(ctx.req, response, {
|
|
21961
|
+
onClose: () => unsubscribe()
|
|
21962
|
+
});
|
|
20428
21963
|
writeUpdateSseEvent(response, await readHermesUpdateStatus(paths));
|
|
20429
|
-
|
|
21964
|
+
unsubscribe = subscribeHermesUpdateStatus((status) => {
|
|
20430
21965
|
writeUpdateSseEvent(response, status);
|
|
20431
21966
|
});
|
|
20432
|
-
const cleanup = () => {
|
|
20433
|
-
unsubscribe();
|
|
20434
|
-
response.end();
|
|
20435
|
-
};
|
|
20436
|
-
ctx.req.on("close", cleanup);
|
|
20437
21967
|
});
|
|
20438
21968
|
}
|
|
20439
21969
|
function writeUpdateSseEvent(response, status) {
|
|
20440
|
-
response
|
|
20441
|
-
|
|
20442
|
-
|
|
20443
|
-
|
|
21970
|
+
writeJsonSseEvent(response, {
|
|
21971
|
+
event: "hermes.update.status",
|
|
21972
|
+
data: status
|
|
21973
|
+
});
|
|
20444
21974
|
}
|
|
20445
21975
|
|
|
20446
21976
|
// src/http/routes/link-updates.ts
|
|
@@ -20463,33 +21993,29 @@ function registerLinkUpdateRoutes(router, options) {
|
|
|
20463
21993
|
ctx.body = await startLinkUpdate({
|
|
20464
21994
|
paths,
|
|
20465
21995
|
logger,
|
|
20466
|
-
targetVersion:
|
|
21996
|
+
targetVersion: readString14(body, "target_version") ?? readString14(body, "targetVersion")
|
|
20467
21997
|
});
|
|
20468
21998
|
});
|
|
20469
21999
|
router.get("/api/v1/link/update/events", async (ctx) => {
|
|
20470
22000
|
await authenticateRequest(ctx, paths);
|
|
20471
22001
|
ctx.respond = false;
|
|
20472
22002
|
const response = ctx.res;
|
|
20473
|
-
|
|
20474
|
-
|
|
20475
|
-
|
|
20476
|
-
|
|
22003
|
+
let unsubscribe = () => {
|
|
22004
|
+
};
|
|
22005
|
+
beginSseStream(ctx.req, response, {
|
|
22006
|
+
onClose: () => unsubscribe()
|
|
22007
|
+
});
|
|
20477
22008
|
writeUpdateSseEvent2(response, await readLinkUpdateStatus(paths));
|
|
20478
|
-
|
|
22009
|
+
unsubscribe = subscribeLinkUpdateStatus((status) => {
|
|
20479
22010
|
writeUpdateSseEvent2(response, status);
|
|
20480
22011
|
});
|
|
20481
|
-
const cleanup = () => {
|
|
20482
|
-
unsubscribe();
|
|
20483
|
-
response.end();
|
|
20484
|
-
};
|
|
20485
|
-
ctx.req.on("close", cleanup);
|
|
20486
22012
|
});
|
|
20487
22013
|
}
|
|
20488
22014
|
function writeUpdateSseEvent2(response, status) {
|
|
20489
|
-
response
|
|
20490
|
-
|
|
20491
|
-
|
|
20492
|
-
|
|
22015
|
+
writeJsonSseEvent(response, {
|
|
22016
|
+
event: "link.update.status",
|
|
22017
|
+
data: status
|
|
22018
|
+
});
|
|
20493
22019
|
}
|
|
20494
22020
|
|
|
20495
22021
|
// src/http/routes/pairing.ts
|
|
@@ -20497,7 +22023,7 @@ import QRCode from "qrcode";
|
|
|
20497
22023
|
function registerPairingRoutes(router, options) {
|
|
20498
22024
|
const { paths } = options;
|
|
20499
22025
|
router.get("/pair", async (ctx) => {
|
|
20500
|
-
const sessionId =
|
|
22026
|
+
const sessionId = readString14(ctx.query, "session_id");
|
|
20501
22027
|
if (!sessionId) {
|
|
20502
22028
|
throw new LinkHttpError(400, "pairing_session_required", "session_id is required");
|
|
20503
22029
|
}
|
|
@@ -20522,7 +22048,7 @@ function registerPairingRoutes(router, options) {
|
|
|
20522
22048
|
ctx.body = page;
|
|
20523
22049
|
});
|
|
20524
22050
|
router.get("/api/v1/pairing/session", async (ctx) => {
|
|
20525
|
-
const sessionId =
|
|
22051
|
+
const sessionId = readString14(ctx.query, "session_id");
|
|
20526
22052
|
if (!sessionId) {
|
|
20527
22053
|
throw new LinkHttpError(400, "pairing_session_required", "session_id is required");
|
|
20528
22054
|
}
|
|
@@ -20926,7 +22452,7 @@ function registerInternalRoutes(router, options) {
|
|
|
20926
22452
|
router.post("/internal/deliver", async (ctx) => {
|
|
20927
22453
|
assertLoopbackRequest(ctx.req);
|
|
20928
22454
|
const body = await readJsonBody(ctx.req);
|
|
20929
|
-
const stagingDir =
|
|
22455
|
+
const stagingDir = readString14(body, "staging_dir") ?? readString14(body, "stagingDir");
|
|
20930
22456
|
if (!stagingDir) {
|
|
20931
22457
|
throw new LinkHttpError(
|
|
20932
22458
|
400,
|