@hermespilot/link 0.3.6 → 0.3.8
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/README.md +1 -1
- package/dist/{chunk-773L37RF.js → chunk-RJB5VUT4.js} +690 -314
- package/dist/cli/index.js +1 -1
- package/dist/http/app.d.ts +95 -77
- package/dist/http/app.js +1 -1
- package/package.json +6 -4
- package/scripts/check-node-version.mjs +7 -7
|
@@ -9,8 +9,21 @@ import { randomUUID as randomUUID7 } from "crypto";
|
|
|
9
9
|
// src/database/link-database.ts
|
|
10
10
|
import { mkdir } from "fs/promises";
|
|
11
11
|
import { randomUUID } from "crypto";
|
|
12
|
-
import { createRequire } from "module";
|
|
13
12
|
import path from "path";
|
|
13
|
+
|
|
14
|
+
// src/database/sqlite.ts
|
|
15
|
+
import Database from "better-sqlite3";
|
|
16
|
+
function openSqliteDatabase(filePath, options = {}) {
|
|
17
|
+
return new Database(filePath, {
|
|
18
|
+
...options.readonly === void 0 ? {} : {
|
|
19
|
+
readonly: options.readonly,
|
|
20
|
+
fileMustExist: options.readonly
|
|
21
|
+
},
|
|
22
|
+
...options.timeout === void 0 ? {} : { timeout: options.timeout }
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/database/link-database.ts
|
|
14
27
|
var MIGRATIONS = [
|
|
15
28
|
{
|
|
16
29
|
version: 1,
|
|
@@ -136,7 +149,6 @@ var MIGRATIONS = [
|
|
|
136
149
|
`
|
|
137
150
|
}
|
|
138
151
|
];
|
|
139
|
-
var nodeRequire = createRequire(import.meta.url);
|
|
140
152
|
async function migrateLinkDatabase(paths) {
|
|
141
153
|
await mkdir(path.dirname(paths.databaseFile), { recursive: true, mode: 448 });
|
|
142
154
|
const db = openDatabase(paths);
|
|
@@ -210,6 +222,92 @@ async function upsertConversationStats(paths, record) {
|
|
|
210
222
|
db.close();
|
|
211
223
|
}
|
|
212
224
|
}
|
|
225
|
+
async function listConversationStatsPage(paths, input) {
|
|
226
|
+
await migrateLinkDatabase(paths);
|
|
227
|
+
const rawLimit = Number.isFinite(input.limit) ? Math.trunc(input.limit) : 25;
|
|
228
|
+
const limit = Math.max(1, Math.min(100, rawLimit));
|
|
229
|
+
const db = openDatabase(paths);
|
|
230
|
+
try {
|
|
231
|
+
const conditions = ["status = ?"];
|
|
232
|
+
const params = ["active"];
|
|
233
|
+
if (input.cursor) {
|
|
234
|
+
conditions.push(`(
|
|
235
|
+
updated_at < ?
|
|
236
|
+
OR (updated_at = ? AND conversation_id < ?)
|
|
237
|
+
)`);
|
|
238
|
+
params.push(
|
|
239
|
+
input.cursor.updatedAt,
|
|
240
|
+
input.cursor.updatedAt,
|
|
241
|
+
input.cursor.conversationId
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
const rows = db.prepare(`
|
|
245
|
+
SELECT conversation_id, updated_at
|
|
246
|
+
FROM conversation_stats
|
|
247
|
+
WHERE ${conditions.join(" AND ")}
|
|
248
|
+
ORDER BY updated_at DESC, conversation_id DESC
|
|
249
|
+
LIMIT ?
|
|
250
|
+
`).all(...params, limit + 1);
|
|
251
|
+
const records = rows.slice(0, limit).map((row) => ({
|
|
252
|
+
conversationId: readString(row, "conversation_id") ?? "",
|
|
253
|
+
updatedAt: readString(row, "updated_at") ?? ""
|
|
254
|
+
})).filter((row) => row.conversationId && row.updatedAt);
|
|
255
|
+
return {
|
|
256
|
+
records,
|
|
257
|
+
hasMore: rows.length > limit
|
|
258
|
+
};
|
|
259
|
+
} finally {
|
|
260
|
+
db.close();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async function searchConversationStatsPage(paths, input) {
|
|
264
|
+
await migrateLinkDatabase(paths);
|
|
265
|
+
const rawLimit = Number.isFinite(input.limit) ? Math.trunc(input.limit) : 25;
|
|
266
|
+
const limit = Math.max(1, Math.min(100, rawLimit));
|
|
267
|
+
const query = input.query.trim();
|
|
268
|
+
if (!query) {
|
|
269
|
+
return listConversationStatsPage(paths, {
|
|
270
|
+
limit,
|
|
271
|
+
cursor: input.cursor
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
const db = openDatabase(paths);
|
|
275
|
+
try {
|
|
276
|
+
const conditions = ["status = ?", "LOWER(title) LIKE ? ESCAPE '\\'"];
|
|
277
|
+
const params = [
|
|
278
|
+
"active",
|
|
279
|
+
`%${escapeSqlLike(query.toLowerCase())}%`
|
|
280
|
+
];
|
|
281
|
+
if (input.cursor) {
|
|
282
|
+
conditions.push(`(
|
|
283
|
+
updated_at < ?
|
|
284
|
+
OR (updated_at = ? AND conversation_id < ?)
|
|
285
|
+
)`);
|
|
286
|
+
params.push(
|
|
287
|
+
input.cursor.updatedAt,
|
|
288
|
+
input.cursor.updatedAt,
|
|
289
|
+
input.cursor.conversationId
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
const rows = db.prepare(`
|
|
293
|
+
SELECT conversation_id, updated_at
|
|
294
|
+
FROM conversation_stats
|
|
295
|
+
WHERE ${conditions.join(" AND ")}
|
|
296
|
+
ORDER BY updated_at DESC, conversation_id DESC
|
|
297
|
+
LIMIT ?
|
|
298
|
+
`).all(...params, limit + 1);
|
|
299
|
+
const records = rows.slice(0, limit).map((row) => ({
|
|
300
|
+
conversationId: readString(row, "conversation_id") ?? "",
|
|
301
|
+
updatedAt: readString(row, "updated_at") ?? ""
|
|
302
|
+
})).filter((row) => row.conversationId && row.updatedAt);
|
|
303
|
+
return {
|
|
304
|
+
records,
|
|
305
|
+
hasMore: rows.length > limit
|
|
306
|
+
};
|
|
307
|
+
} finally {
|
|
308
|
+
db.close();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
213
311
|
async function replaceRunUsageFactsForConversation(paths, conversationId, records) {
|
|
214
312
|
await migrateLinkDatabase(paths);
|
|
215
313
|
const db = openDatabase(paths);
|
|
@@ -618,8 +716,7 @@ async function deleteConversationStatsForProfile(paths, input) {
|
|
|
618
716
|
}
|
|
619
717
|
}
|
|
620
718
|
function openDatabase(paths) {
|
|
621
|
-
const
|
|
622
|
-
const db = new DatabaseSync(paths.databaseFile, {
|
|
719
|
+
const db = openSqliteDatabase(paths.databaseFile, {
|
|
623
720
|
timeout: 5e3
|
|
624
721
|
});
|
|
625
722
|
db.exec(`
|
|
@@ -826,6 +923,9 @@ function readString(row, key) {
|
|
|
826
923
|
const value = row?.[key];
|
|
827
924
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
828
925
|
}
|
|
926
|
+
function escapeSqlLike(value) {
|
|
927
|
+
return value.replace(/[\\%_]/gu, (match) => `\\${match}`);
|
|
928
|
+
}
|
|
829
929
|
function statisticsWhereClause(filter) {
|
|
830
930
|
const profileUid = filter.profileUid?.trim();
|
|
831
931
|
const profileName = filter.profileName?.trim();
|
|
@@ -999,58 +1099,200 @@ function readProfileAvatarType(row) {
|
|
|
999
1099
|
}
|
|
1000
1100
|
|
|
1001
1101
|
// src/hermes/cron-link-delivery.ts
|
|
1002
|
-
import { mkdir as
|
|
1102
|
+
import { mkdir as mkdir3, readdir as readdir3, readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
1003
1103
|
import path4 from "path";
|
|
1004
1104
|
|
|
1005
1105
|
// src/storage/atomic-json.ts
|
|
1006
|
-
import {
|
|
1106
|
+
import { readFile } from "fs/promises";
|
|
1107
|
+
|
|
1108
|
+
// src/storage/atomic-file.ts
|
|
1007
1109
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1110
|
+
import {
|
|
1111
|
+
chmod,
|
|
1112
|
+
chown,
|
|
1113
|
+
lstat,
|
|
1114
|
+
mkdir as mkdir2,
|
|
1115
|
+
open,
|
|
1116
|
+
readdir,
|
|
1117
|
+
rename,
|
|
1118
|
+
rm,
|
|
1119
|
+
stat
|
|
1120
|
+
} from "fs/promises";
|
|
1008
1121
|
import path2 from "path";
|
|
1009
|
-
async function
|
|
1122
|
+
async function atomicWriteFilePreservingMetadata(filePath, value, options = {}) {
|
|
1123
|
+
const resolvedPath = path2.resolve(filePath);
|
|
1124
|
+
const directory = path2.dirname(resolvedPath);
|
|
1125
|
+
await ensureDirectoryWithInheritedMetadata(
|
|
1126
|
+
directory,
|
|
1127
|
+
options.directoryMode ?? 448
|
|
1128
|
+
);
|
|
1129
|
+
const existingMetadata = await readExistingFileMetadata(resolvedPath) ?? (options.metadataSourcePath ? await readExistingFileMetadata(path2.resolve(options.metadataSourcePath)) : null);
|
|
1130
|
+
const directoryMetadata = await readPathMetadata(directory);
|
|
1131
|
+
const metadata = {
|
|
1132
|
+
uid: existingMetadata?.uid ?? directoryMetadata.uid,
|
|
1133
|
+
gid: existingMetadata?.gid ?? directoryMetadata.gid,
|
|
1134
|
+
mode: existingMetadata?.mode ?? options.mode ?? 384
|
|
1135
|
+
};
|
|
1136
|
+
const tempPath = path2.join(
|
|
1137
|
+
directory,
|
|
1138
|
+
`.${path2.basename(resolvedPath)}.${process.pid}.${Date.now()}.${randomUUID2()}.tmp`
|
|
1139
|
+
);
|
|
1010
1140
|
try {
|
|
1011
|
-
const
|
|
1012
|
-
|
|
1141
|
+
const handle = await open(tempPath, "wx", metadata.mode);
|
|
1142
|
+
try {
|
|
1143
|
+
if (typeof value === "string") {
|
|
1144
|
+
await handle.writeFile(value, options.encoding ?? "utf8");
|
|
1145
|
+
} else {
|
|
1146
|
+
await handle.writeFile(value);
|
|
1147
|
+
}
|
|
1148
|
+
await handle.sync();
|
|
1149
|
+
} finally {
|
|
1150
|
+
await handle.close();
|
|
1151
|
+
}
|
|
1152
|
+
await applyMetadata(tempPath, metadata);
|
|
1153
|
+
await rename(tempPath, resolvedPath);
|
|
1013
1154
|
} catch (error) {
|
|
1155
|
+
await rm(tempPath, { force: true });
|
|
1156
|
+
throw error;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
async function ensureDirectoryWithInheritedMetadata(directory, mode) {
|
|
1160
|
+
const { source, missing } = await findExistingAncestor(directory);
|
|
1161
|
+
await mkdir2(directory, { recursive: true, mode });
|
|
1162
|
+
for (const missingDirectory of missing) {
|
|
1163
|
+
await applyMetadata(missingDirectory, {
|
|
1164
|
+
uid: source.uid,
|
|
1165
|
+
gid: source.gid,
|
|
1166
|
+
mode
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
async function inheritOwnerRecursively(targetPath, sourcePath) {
|
|
1171
|
+
if (process.platform === "win32") {
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
const source = await readPathMetadata(sourcePath);
|
|
1175
|
+
await applyOwnerRecursively(targetPath, source);
|
|
1176
|
+
}
|
|
1177
|
+
async function findExistingAncestor(directory) {
|
|
1178
|
+
const missing = [];
|
|
1179
|
+
let current = path2.resolve(directory);
|
|
1180
|
+
while (true) {
|
|
1181
|
+
const currentStat = await stat(current).catch((error) => {
|
|
1182
|
+
if (isNodeError(error, "ENOENT")) {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
throw error;
|
|
1186
|
+
});
|
|
1187
|
+
if (currentStat) {
|
|
1188
|
+
if (!currentStat.isDirectory()) {
|
|
1189
|
+
throw new Error(`${current} is not a directory`);
|
|
1190
|
+
}
|
|
1191
|
+
return {
|
|
1192
|
+
source: metadataFromStats(currentStat),
|
|
1193
|
+
missing: missing.reverse()
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
missing.push(current);
|
|
1197
|
+
const parent = path2.dirname(current);
|
|
1198
|
+
if (parent === current) {
|
|
1199
|
+
throw new Error(`No existing parent directory for ${directory}`);
|
|
1200
|
+
}
|
|
1201
|
+
current = parent;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
async function readExistingFileMetadata(filePath) {
|
|
1205
|
+
const fileStat = await stat(filePath).catch((error) => {
|
|
1014
1206
|
if (isNodeError(error, "ENOENT")) {
|
|
1015
1207
|
return null;
|
|
1016
1208
|
}
|
|
1017
1209
|
throw error;
|
|
1210
|
+
});
|
|
1211
|
+
if (!fileStat) {
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
if (!fileStat.isFile()) {
|
|
1215
|
+
throw new Error(`${filePath} is not a file`);
|
|
1018
1216
|
}
|
|
1217
|
+
return metadataFromStats(fileStat);
|
|
1019
1218
|
}
|
|
1020
|
-
async function
|
|
1021
|
-
await
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1219
|
+
async function readPathMetadata(filePath) {
|
|
1220
|
+
return metadataFromStats(await stat(filePath));
|
|
1221
|
+
}
|
|
1222
|
+
async function applyMetadata(filePath, metadata) {
|
|
1223
|
+
await applyOwner(filePath, metadata);
|
|
1224
|
+
await chmod(filePath, metadata.mode);
|
|
1225
|
+
}
|
|
1226
|
+
async function applyOwnerRecursively(filePath, metadata) {
|
|
1227
|
+
const current = await lstat(filePath);
|
|
1228
|
+
if (current.isSymbolicLink()) {
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
await applyOwner(filePath, metadata);
|
|
1232
|
+
if (!current.isDirectory()) {
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
const entries = await readdir(filePath, { withFileTypes: true });
|
|
1236
|
+
await Promise.all(
|
|
1237
|
+
entries.map(
|
|
1238
|
+
(entry) => applyOwnerRecursively(path2.join(filePath, entry.name), metadata)
|
|
1239
|
+
)
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
async function applyOwner(filePath, metadata) {
|
|
1243
|
+
if (process.platform === "win32") {
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
const currentUid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
1247
|
+
const currentGid = typeof process.getgid === "function" ? process.getgid() : void 0;
|
|
1248
|
+
if (metadata.uid === currentUid && metadata.gid === currentGid) {
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1026
1251
|
try {
|
|
1027
|
-
await
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1252
|
+
await chown(filePath, metadata.uid, metadata.gid);
|
|
1253
|
+
} catch (error) {
|
|
1254
|
+
const current = await stat(filePath);
|
|
1255
|
+
if (current.uid !== metadata.uid || current.gid !== metadata.gid) {
|
|
1256
|
+
throw error;
|
|
1257
|
+
}
|
|
1031
1258
|
}
|
|
1259
|
+
}
|
|
1260
|
+
function metadataFromStats(statsValue) {
|
|
1261
|
+
return {
|
|
1262
|
+
uid: statsValue.uid,
|
|
1263
|
+
gid: statsValue.gid,
|
|
1264
|
+
mode: statsValue.mode & 511
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
function isNodeError(error, code) {
|
|
1268
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// src/storage/atomic-json.ts
|
|
1272
|
+
async function readJsonFile(filePath) {
|
|
1032
1273
|
try {
|
|
1033
|
-
await
|
|
1274
|
+
const raw = await readFile(filePath, "utf8");
|
|
1275
|
+
return JSON.parse(raw);
|
|
1034
1276
|
} catch (error) {
|
|
1035
|
-
|
|
1277
|
+
if (isNodeError2(error, "ENOENT")) {
|
|
1278
|
+
return null;
|
|
1279
|
+
}
|
|
1036
1280
|
throw error;
|
|
1037
1281
|
}
|
|
1038
1282
|
}
|
|
1039
|
-
function
|
|
1283
|
+
async function writeJsonFile(filePath, value, mode = 384) {
|
|
1284
|
+
const payload = `${JSON.stringify(value, null, 2)}
|
|
1285
|
+
`;
|
|
1286
|
+
await atomicWriteFilePreservingMetadata(filePath, payload, { mode });
|
|
1287
|
+
}
|
|
1288
|
+
function isNodeError2(error, code) {
|
|
1040
1289
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
1041
1290
|
}
|
|
1042
1291
|
|
|
1043
1292
|
// src/hermes/config.ts
|
|
1044
1293
|
import { randomBytes } from "crypto";
|
|
1045
1294
|
import net from "net";
|
|
1046
|
-
import {
|
|
1047
|
-
copyFile,
|
|
1048
|
-
mkdir as mkdir3,
|
|
1049
|
-
readFile as readFile2,
|
|
1050
|
-
readdir,
|
|
1051
|
-
rename as rename2,
|
|
1052
|
-
writeFile
|
|
1053
|
-
} from "fs/promises";
|
|
1295
|
+
import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
|
|
1054
1296
|
import os from "os";
|
|
1055
1297
|
import path3 from "path";
|
|
1056
1298
|
import YAML from "yaml";
|
|
@@ -1274,7 +1516,7 @@ async function readHermesSessionsDir(profileName = "default", configPath = resol
|
|
|
1274
1516
|
async function readHermesApiServerConfig(profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
|
|
1275
1517
|
const existingRaw = await readFile2(configPath, "utf8").catch(
|
|
1276
1518
|
(error) => {
|
|
1277
|
-
if (
|
|
1519
|
+
if (isNodeError3(error, "ENOENT")) {
|
|
1278
1520
|
return null;
|
|
1279
1521
|
}
|
|
1280
1522
|
throw error;
|
|
@@ -1299,7 +1541,7 @@ async function readHermesApiServerConfig(profileName = "default", configPath = r
|
|
|
1299
1541
|
async function readHermesModelConfig(profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
|
|
1300
1542
|
const existingRaw = await readFile2(configPath, "utf8").catch(
|
|
1301
1543
|
(error) => {
|
|
1302
|
-
if (
|
|
1544
|
+
if (isNodeError3(error, "ENOENT")) {
|
|
1303
1545
|
return null;
|
|
1304
1546
|
}
|
|
1305
1547
|
throw error;
|
|
@@ -1820,7 +2062,7 @@ async function ensureHermesApiServerConfig(profileName = "default", configPath =
|
|
|
1820
2062
|
async function ensureHermesApiServerConfigUnlocked(profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
|
|
1821
2063
|
const existingRaw = await readFile2(configPath, "utf8").catch(
|
|
1822
2064
|
(error) => {
|
|
1823
|
-
if (
|
|
2065
|
+
if (isNodeError3(error, "ENOENT")) {
|
|
1824
2066
|
return null;
|
|
1825
2067
|
}
|
|
1826
2068
|
throw error;
|
|
@@ -1886,12 +2128,13 @@ async function ensureHermesApiServerConfigUnlocked(profileName = "default", conf
|
|
|
1886
2128
|
};
|
|
1887
2129
|
}
|
|
1888
2130
|
const backupPath = existingRaw ? `${configPath}.bak.${Date.now()}` : null;
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
2131
|
+
if (backupPath && existingRaw !== null) {
|
|
2132
|
+
await atomicWriteFilePreservingMetadata(backupPath, existingRaw, {
|
|
2133
|
+
metadataSourcePath: configPath
|
|
2134
|
+
});
|
|
1892
2135
|
}
|
|
1893
2136
|
document.contents = document.createNode(config);
|
|
1894
|
-
await
|
|
2137
|
+
await atomicWriteFilePreservingMetadata(configPath, document.toString());
|
|
1895
2138
|
return {
|
|
1896
2139
|
configPath,
|
|
1897
2140
|
apiServer: applyEnvOverrides(
|
|
@@ -1917,7 +2160,7 @@ async function ensureHermesApiServerConfigUnlocked(profileName = "default", conf
|
|
|
1917
2160
|
async function readHermesConfigDocument(configPath) {
|
|
1918
2161
|
const existingRaw = await readFile2(configPath, "utf8").catch(
|
|
1919
2162
|
(error) => {
|
|
1920
|
-
if (
|
|
2163
|
+
if (isNodeError3(error, "ENOENT")) {
|
|
1921
2164
|
return null;
|
|
1922
2165
|
}
|
|
1923
2166
|
throw error;
|
|
@@ -1932,14 +2175,16 @@ async function readHermesConfigDocument(configPath) {
|
|
|
1932
2175
|
}
|
|
1933
2176
|
async function writeHermesConfigDocument(input) {
|
|
1934
2177
|
const backupPath = input.existingRaw ? `${input.configPath}.bak.${Date.now()}` : null;
|
|
1935
|
-
await mkdir3(path3.dirname(input.configPath), { recursive: true, mode: 448 });
|
|
1936
2178
|
if (backupPath) {
|
|
1937
|
-
await
|
|
2179
|
+
await atomicWriteFilePreservingMetadata(backupPath, input.existingRaw, {
|
|
2180
|
+
metadataSourcePath: input.configPath
|
|
2181
|
+
});
|
|
1938
2182
|
}
|
|
1939
2183
|
input.document.contents = input.document.createNode(input.config);
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
2184
|
+
await atomicWriteFilePreservingMetadata(
|
|
2185
|
+
input.configPath,
|
|
2186
|
+
input.document.toString()
|
|
2187
|
+
);
|
|
1943
2188
|
return backupPath;
|
|
1944
2189
|
}
|
|
1945
2190
|
function readManagedModelConfigs(config, env, defaultModel, defaultReasoningEffort) {
|
|
@@ -3261,9 +3506,9 @@ async function readConfiguredApiServerPorts(excludedProfileName) {
|
|
|
3261
3506
|
resolveDefaultHermesRoot(resolveHermesProfileDir("default")),
|
|
3262
3507
|
"profiles"
|
|
3263
3508
|
);
|
|
3264
|
-
const entries = await
|
|
3509
|
+
const entries = await readdir2(profilesRoot, { withFileTypes: true }).catch(
|
|
3265
3510
|
(error) => {
|
|
3266
|
-
if (
|
|
3511
|
+
if (isNodeError3(error, "ENOENT")) {
|
|
3267
3512
|
return [];
|
|
3268
3513
|
}
|
|
3269
3514
|
throw error;
|
|
@@ -3285,7 +3530,7 @@ async function addConfiguredApiServerPort(ports, profileName, excludedProfileNam
|
|
|
3285
3530
|
resolveHermesConfigPath(profileName),
|
|
3286
3531
|
"utf8"
|
|
3287
3532
|
).catch((error) => {
|
|
3288
|
-
if (
|
|
3533
|
+
if (isNodeError3(error, "ENOENT")) {
|
|
3289
3534
|
return "";
|
|
3290
3535
|
}
|
|
3291
3536
|
throw error;
|
|
@@ -3358,7 +3603,7 @@ async function readHermesApiServerEnvOverrides(profileName) {
|
|
|
3358
3603
|
async function readHermesEnvFile(profileName) {
|
|
3359
3604
|
const envPath = path3.join(resolveHermesProfileDir(profileName), ".env");
|
|
3360
3605
|
const raw = await readFile2(envPath, "utf8").catch((error) => {
|
|
3361
|
-
if (
|
|
3606
|
+
if (isNodeError3(error, "ENOENT")) {
|
|
3362
3607
|
return "";
|
|
3363
3608
|
}
|
|
3364
3609
|
throw error;
|
|
@@ -3383,7 +3628,7 @@ async function writeHermesEnvValue(profileName, key, value) {
|
|
|
3383
3628
|
const envPath = path3.join(resolveHermesProfileDir(profileName), ".env");
|
|
3384
3629
|
const existingRaw = await readFile2(envPath, "utf8").catch(
|
|
3385
3630
|
(error) => {
|
|
3386
|
-
if (
|
|
3631
|
+
if (isNodeError3(error, "ENOENT")) {
|
|
3387
3632
|
return "";
|
|
3388
3633
|
}
|
|
3389
3634
|
throw error;
|
|
@@ -3406,13 +3651,14 @@ async function writeHermesEnvValue(profileName, key, value) {
|
|
|
3406
3651
|
nextLines.push(`${key}=${formatEnvValue(value)}`);
|
|
3407
3652
|
}
|
|
3408
3653
|
const nextRaw = nextLines.join("\n").replace(/\n*$/u, "\n");
|
|
3409
|
-
await mkdir3(path3.dirname(envPath), { recursive: true, mode: 448 });
|
|
3410
3654
|
if (existingRaw) {
|
|
3411
|
-
await
|
|
3655
|
+
await atomicWriteFilePreservingMetadata(
|
|
3656
|
+
`${envPath}.bak.${Date.now()}`,
|
|
3657
|
+
existingRaw,
|
|
3658
|
+
{ metadataSourcePath: envPath }
|
|
3659
|
+
);
|
|
3412
3660
|
}
|
|
3413
|
-
|
|
3414
|
-
await writeFile(tempPath, nextRaw, { mode: 384 });
|
|
3415
|
-
await rename2(tempPath, envPath);
|
|
3661
|
+
await atomicWriteFilePreservingMetadata(envPath, nextRaw);
|
|
3416
3662
|
}
|
|
3417
3663
|
function applyEnvOverrides(config, env, withDefaults) {
|
|
3418
3664
|
const host = config.host ?? env.host;
|
|
@@ -3491,7 +3737,7 @@ function ensureRecord(parent, key) {
|
|
|
3491
3737
|
function resolveHermesConfiguredPath(value, baseDir) {
|
|
3492
3738
|
return path3.isAbsolute(value) ? path3.normalize(value) : path3.resolve(baseDir, value);
|
|
3493
3739
|
}
|
|
3494
|
-
function
|
|
3740
|
+
function isNodeError3(error, code) {
|
|
3495
3741
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
3496
3742
|
}
|
|
3497
3743
|
function resolveDefaultHermesRoot(hermesHome) {
|
|
@@ -3616,9 +3862,9 @@ async function listCronOutputFiles(profileName, jobId) {
|
|
|
3616
3862
|
"output",
|
|
3617
3863
|
jobId
|
|
3618
3864
|
);
|
|
3619
|
-
const entries = await
|
|
3865
|
+
const entries = await readdir3(outputDir, { withFileTypes: true }).catch(
|
|
3620
3866
|
(error) => {
|
|
3621
|
-
if (
|
|
3867
|
+
if (isNodeError4(error, "ENOENT")) {
|
|
3622
3868
|
return [];
|
|
3623
3869
|
}
|
|
3624
3870
|
throw error;
|
|
@@ -3630,7 +3876,7 @@ async function listCronOutputFiles(profileName, jobId) {
|
|
|
3630
3876
|
continue;
|
|
3631
3877
|
}
|
|
3632
3878
|
const outputPath = path4.join(outputDir, entry.name);
|
|
3633
|
-
const fileStat = await
|
|
3879
|
+
const fileStat = await stat2(outputPath);
|
|
3634
3880
|
files.push({
|
|
3635
3881
|
path: outputPath,
|
|
3636
3882
|
mtime: fileStat.mtime.toISOString(),
|
|
@@ -3663,7 +3909,7 @@ async function readRegistry(paths) {
|
|
|
3663
3909
|
return { version: REGISTRY_VERSION, bindings: [] };
|
|
3664
3910
|
}
|
|
3665
3911
|
async function writeRegistry(paths, registry) {
|
|
3666
|
-
await
|
|
3912
|
+
await mkdir3(paths.indexesDir, { recursive: true, mode: 448 });
|
|
3667
3913
|
await writeJsonFile(registryPath(paths), registry);
|
|
3668
3914
|
}
|
|
3669
3915
|
function registryPath(paths) {
|
|
@@ -3689,13 +3935,13 @@ function readString3(record, ...keys) {
|
|
|
3689
3935
|
}
|
|
3690
3936
|
return void 0;
|
|
3691
3937
|
}
|
|
3692
|
-
function
|
|
3938
|
+
function isNodeError4(error, code) {
|
|
3693
3939
|
return error instanceof Error && error.code === code;
|
|
3694
3940
|
}
|
|
3695
3941
|
|
|
3696
3942
|
// src/hermes/gateway.ts
|
|
3697
3943
|
import { execFile as execFile2, spawn } from "child_process";
|
|
3698
|
-
import { access, readFile as readFile5, stat as
|
|
3944
|
+
import { access, readFile as readFile5, stat as stat4 } from "fs/promises";
|
|
3699
3945
|
import path7 from "path";
|
|
3700
3946
|
import { setTimeout as delay } from "timers/promises";
|
|
3701
3947
|
import { promisify as promisify2 } from "util";
|
|
@@ -3715,7 +3961,7 @@ function isLinkHttpError(error) {
|
|
|
3715
3961
|
}
|
|
3716
3962
|
|
|
3717
3963
|
// src/runtime/logger.ts
|
|
3718
|
-
import { appendFile, mkdir as
|
|
3964
|
+
import { appendFile, mkdir as mkdir4, open as open2, readFile as readFile4, rename as rename2, rm as rm2, stat as stat3 } from "fs/promises";
|
|
3719
3965
|
import os3 from "os";
|
|
3720
3966
|
import path6 from "path";
|
|
3721
3967
|
|
|
@@ -3724,7 +3970,7 @@ import os2 from "os";
|
|
|
3724
3970
|
import path5 from "path";
|
|
3725
3971
|
|
|
3726
3972
|
// src/constants.ts
|
|
3727
|
-
var LINK_VERSION = "0.3.
|
|
3973
|
+
var LINK_VERSION = "0.3.8";
|
|
3728
3974
|
var LINK_COMMAND = "hermeslink";
|
|
3729
3975
|
var LINK_DEFAULT_PORT = 52379;
|
|
3730
3976
|
var LINK_RUNTIME_DIR_NAME = ".hermeslink";
|
|
@@ -3800,7 +4046,7 @@ var FileLogger = class {
|
|
|
3800
4046
|
return this.queue;
|
|
3801
4047
|
}
|
|
3802
4048
|
async appendEntry(entry) {
|
|
3803
|
-
await
|
|
4049
|
+
await mkdir4(this.paths.logsDir, { recursive: true, mode: 448 });
|
|
3804
4050
|
const line = `${JSON.stringify(entry)}
|
|
3805
4051
|
`;
|
|
3806
4052
|
await this.rotateIfNeeded(Buffer.byteLength(line, "utf8"));
|
|
@@ -3836,7 +4082,7 @@ function createRotatingTextLogWriter(options) {
|
|
|
3836
4082
|
return queue;
|
|
3837
4083
|
}
|
|
3838
4084
|
const next = queue.then(async () => {
|
|
3839
|
-
await
|
|
4085
|
+
await mkdir4(paths.logsDir, { recursive: true, mode: 448 });
|
|
3840
4086
|
await rotateLogFileIfNeeded(filePath, buffer.length, maxFileBytes, maxFiles);
|
|
3841
4087
|
await appendFile(filePath, buffer, { mode: 384 });
|
|
3842
4088
|
}).catch(() => void 0);
|
|
@@ -3937,7 +4183,7 @@ function clampLimit(value) {
|
|
|
3937
4183
|
return Math.min(MAX_READ_LIMIT, Math.max(1, Math.floor(value)));
|
|
3938
4184
|
}
|
|
3939
4185
|
async function rotateLogFileIfNeeded(filePath, nextBytes, maxFileBytes, maxFiles) {
|
|
3940
|
-
const current = await
|
|
4186
|
+
const current = await stat3(filePath).catch(() => null);
|
|
3941
4187
|
if (!current || current.size === 0 || current.size + nextBytes <= maxFileBytes) {
|
|
3942
4188
|
return;
|
|
3943
4189
|
}
|
|
@@ -4052,7 +4298,7 @@ async function readTail(filePath, maxBytes) {
|
|
|
4052
4298
|
return tail?.content ?? null;
|
|
4053
4299
|
}
|
|
4054
4300
|
async function readTailWithMetadata(filePath, maxBytes) {
|
|
4055
|
-
const info = await
|
|
4301
|
+
const info = await stat3(filePath).catch(() => null);
|
|
4056
4302
|
if (!info || info.size <= 0) {
|
|
4057
4303
|
return null;
|
|
4058
4304
|
}
|
|
@@ -4076,7 +4322,7 @@ async function readTailWithMetadata(filePath, maxBytes) {
|
|
|
4076
4322
|
}
|
|
4077
4323
|
async function moveIfExists(from, to) {
|
|
4078
4324
|
await rm2(to, { force: true }).catch(() => void 0);
|
|
4079
|
-
await
|
|
4325
|
+
await rename2(from, to).catch((error) => {
|
|
4080
4326
|
if (error.code !== "ENOENT") {
|
|
4081
4327
|
throw error;
|
|
4082
4328
|
}
|
|
@@ -4499,8 +4745,8 @@ async function assertProfileExists(profileName) {
|
|
|
4499
4745
|
if (profileName === "default") {
|
|
4500
4746
|
return;
|
|
4501
4747
|
}
|
|
4502
|
-
const exists = await
|
|
4503
|
-
if (
|
|
4748
|
+
const exists = await stat4(resolveHermesProfileDir(profileName)).then((value) => value.isDirectory()).catch((error) => {
|
|
4749
|
+
if (isNodeError5(error, "ENOENT")) {
|
|
4504
4750
|
return false;
|
|
4505
4751
|
}
|
|
4506
4752
|
throw error;
|
|
@@ -4700,7 +4946,7 @@ function isHealthyPlatformState(value) {
|
|
|
4700
4946
|
function toRecord2(value) {
|
|
4701
4947
|
return typeof value === "object" && value !== null ? value : {};
|
|
4702
4948
|
}
|
|
4703
|
-
function
|
|
4949
|
+
function isNodeError5(error, code) {
|
|
4704
4950
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
4705
4951
|
}
|
|
4706
4952
|
function readString4(payload, key) {
|
|
@@ -5068,7 +5314,7 @@ function firstRecord(...values) {
|
|
|
5068
5314
|
|
|
5069
5315
|
// src/conversations/blob-store.ts
|
|
5070
5316
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
5071
|
-
import { mkdir as
|
|
5317
|
+
import { mkdir as mkdir5, readFile as readFile6, readdir as readdir4, rm as rm3, stat as stat5, writeFile } from "fs/promises";
|
|
5072
5318
|
import path9 from "path";
|
|
5073
5319
|
|
|
5074
5320
|
// src/conversations/media.ts
|
|
@@ -5523,8 +5769,8 @@ async function writeConversationBlob(paths, conversationId, input, options) {
|
|
|
5523
5769
|
}
|
|
5524
5770
|
const id = `blob_${randomUUID3().replaceAll("-", "")}`;
|
|
5525
5771
|
const filePath = blobPath(paths, id);
|
|
5526
|
-
await
|
|
5527
|
-
await
|
|
5772
|
+
await mkdir5(path9.dirname(filePath), { recursive: true, mode: 448 });
|
|
5773
|
+
await writeFile(filePath, input.bytes, { mode: 384 });
|
|
5528
5774
|
const blob = {
|
|
5529
5775
|
id,
|
|
5530
5776
|
size: input.bytes.byteLength,
|
|
@@ -5551,7 +5797,7 @@ async function readConversationBlob(paths, conversationId, blobId) {
|
|
|
5551
5797
|
const filePath = blobPath(paths, blobId);
|
|
5552
5798
|
const manifest = await readConversationBlobManifest(paths, conversationId, blobId);
|
|
5553
5799
|
const bytes = await readFile6(filePath).catch((error) => {
|
|
5554
|
-
if (
|
|
5800
|
+
if (isNodeError6(error, "ENOENT")) {
|
|
5555
5801
|
throw new LinkHttpError(404, "blob_not_found", "Blob was not found");
|
|
5556
5802
|
}
|
|
5557
5803
|
throw error;
|
|
@@ -5593,7 +5839,7 @@ async function deleteConversationBlobIfUnreferenced(paths, conversationId, blobI
|
|
|
5593
5839
|
async function materializeConversationBlob(paths, conversationId, blobId, manifest) {
|
|
5594
5840
|
const existingPath = manifest.materialized_path;
|
|
5595
5841
|
if (existingPath) {
|
|
5596
|
-
const exists = await
|
|
5842
|
+
const exists = await stat5(existingPath).then((value) => value.isFile()).catch(() => false);
|
|
5597
5843
|
if (exists) {
|
|
5598
5844
|
return existingPath;
|
|
5599
5845
|
}
|
|
@@ -5603,8 +5849,8 @@ async function materializeConversationBlob(paths, conversationId, blobId, manife
|
|
|
5603
5849
|
targetDir,
|
|
5604
5850
|
materializedAttachmentFilename(blobId, manifest.filename ?? blobId)
|
|
5605
5851
|
);
|
|
5606
|
-
await
|
|
5607
|
-
await
|
|
5852
|
+
await mkdir5(targetDir, { recursive: true, mode: 448 });
|
|
5853
|
+
await writeFile(targetPath, await readFile6(blobPath(paths, blobId)), {
|
|
5608
5854
|
mode: 384
|
|
5609
5855
|
});
|
|
5610
5856
|
await writeJsonFile(`${blobPath(paths, blobId)}.json`, {
|
|
@@ -5633,11 +5879,11 @@ async function pruneConversationBlobReference(paths, conversationId, blobId) {
|
|
|
5633
5879
|
}
|
|
5634
5880
|
async function listConversationBlobIds(paths, conversationId) {
|
|
5635
5881
|
assertValidConversationId(conversationId);
|
|
5636
|
-
await
|
|
5637
|
-
const entries = await
|
|
5882
|
+
await mkdir5(paths.blobsDir, { recursive: true, mode: 448 });
|
|
5883
|
+
const entries = await readdir4(paths.blobsDir, {
|
|
5638
5884
|
withFileTypes: true
|
|
5639
5885
|
}).catch((error) => {
|
|
5640
|
-
if (
|
|
5886
|
+
if (isNodeError6(error, "ENOENT")) {
|
|
5641
5887
|
return [];
|
|
5642
5888
|
}
|
|
5643
5889
|
throw error;
|
|
@@ -5671,12 +5917,12 @@ function conversationAttachmentsDir(paths, conversationId) {
|
|
|
5671
5917
|
assertValidConversationId(conversationId);
|
|
5672
5918
|
return path9.join(paths.conversationsDir, conversationId, "attachments");
|
|
5673
5919
|
}
|
|
5674
|
-
function
|
|
5920
|
+
function isNodeError6(error, code) {
|
|
5675
5921
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
5676
5922
|
}
|
|
5677
5923
|
|
|
5678
5924
|
// src/conversations/profile-runtime.ts
|
|
5679
|
-
import { stat as
|
|
5925
|
+
import { stat as stat6 } from "fs/promises";
|
|
5680
5926
|
var PROFILE_NAME_PATTERN2 = /^[a-zA-Z0-9._-]{1,64}$/u;
|
|
5681
5927
|
function normalizeProfileName2(profileName) {
|
|
5682
5928
|
const value = profileName?.trim() || "default";
|
|
@@ -5817,8 +6063,8 @@ async function buildConversationRuntimeMetadata(paths, manifest, snapshot) {
|
|
|
5817
6063
|
async function ensureProfileIdentityForRuntime(paths, profileName) {
|
|
5818
6064
|
const profilePath = resolveHermesProfileDir(profileName);
|
|
5819
6065
|
if (profileName !== "default") {
|
|
5820
|
-
const exists = await
|
|
5821
|
-
if (
|
|
6066
|
+
const exists = await stat6(profilePath).then((value) => value.isDirectory()).catch((error) => {
|
|
6067
|
+
if (isNodeError7(error, "ENOENT")) {
|
|
5822
6068
|
return false;
|
|
5823
6069
|
}
|
|
5824
6070
|
throw error;
|
|
@@ -5844,7 +6090,7 @@ function displayNameForProfile(profile) {
|
|
|
5844
6090
|
const custom = profile.displayName?.trim();
|
|
5845
6091
|
return custom || fallbackProfileDisplayName(profile.profileName);
|
|
5846
6092
|
}
|
|
5847
|
-
function
|
|
6093
|
+
function isNodeError7(error, code) {
|
|
5848
6094
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
5849
6095
|
}
|
|
5850
6096
|
|
|
@@ -6389,11 +6635,11 @@ function formatContextUsageLines(runtime) {
|
|
|
6389
6635
|
}
|
|
6390
6636
|
|
|
6391
6637
|
// src/conversations/delivery-staging.ts
|
|
6392
|
-
import { mkdir as
|
|
6638
|
+
import { mkdir as mkdir6, rm as rm4 } from "fs/promises";
|
|
6393
6639
|
import path10 from "path";
|
|
6394
6640
|
async function prepareDeliveryStagingRunDir(paths, conversationId, runId) {
|
|
6395
6641
|
const directory = deliveryStagingRunDir(paths, conversationId, runId);
|
|
6396
|
-
await
|
|
6642
|
+
await mkdir6(directory, { recursive: true, mode: 448 });
|
|
6397
6643
|
return directory;
|
|
6398
6644
|
}
|
|
6399
6645
|
async function removeConversationDeliveryStaging(paths, conversationId) {
|
|
@@ -6752,10 +6998,8 @@ function isUsableLanIpv4(value) {
|
|
|
6752
6998
|
}
|
|
6753
6999
|
|
|
6754
7000
|
// src/hermes/session-title.ts
|
|
6755
|
-
import { stat as
|
|
6756
|
-
import { createRequire as createRequire2 } from "module";
|
|
7001
|
+
import { stat as stat7 } from "fs/promises";
|
|
6757
7002
|
import path11 from "path";
|
|
6758
|
-
var nodeRequire2 = createRequire2(import.meta.url);
|
|
6759
7003
|
async function readHermesSessionTitle(sessionId, paths, profileName) {
|
|
6760
7004
|
const trimmedSessionId = sessionId.trim();
|
|
6761
7005
|
if (!trimmedSessionId) {
|
|
@@ -6766,8 +7010,8 @@ async function readHermesSessionTitle(sessionId, paths, profileName) {
|
|
|
6766
7010
|
resolveHermesProfileDir(resolvedProfileName),
|
|
6767
7011
|
"state.db"
|
|
6768
7012
|
);
|
|
6769
|
-
const exists = await
|
|
6770
|
-
if (
|
|
7013
|
+
const exists = await stat7(dbPath).then((value) => value.isFile()).catch((error) => {
|
|
7014
|
+
if (isNodeError8(error, "ENOENT")) {
|
|
6771
7015
|
return false;
|
|
6772
7016
|
}
|
|
6773
7017
|
throw error;
|
|
@@ -6787,8 +7031,8 @@ async function readHermesCompressionTip(sessionId, paths, profileName) {
|
|
|
6787
7031
|
resolveHermesProfileDir(resolvedProfileName),
|
|
6788
7032
|
"state.db"
|
|
6789
7033
|
);
|
|
6790
|
-
const exists = await
|
|
6791
|
-
if (
|
|
7034
|
+
const exists = await stat7(dbPath).then((value) => value.isFile()).catch((error) => {
|
|
7035
|
+
if (isNodeError8(error, "ENOENT")) {
|
|
6792
7036
|
return false;
|
|
6793
7037
|
}
|
|
6794
7038
|
throw error;
|
|
@@ -6801,11 +7045,8 @@ async function readHermesCompressionTip(sessionId, paths, profileName) {
|
|
|
6801
7045
|
function readSessionTitleFromStateDb(dbPath, sessionId) {
|
|
6802
7046
|
let db = null;
|
|
6803
7047
|
try {
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
);
|
|
6807
|
-
db = new DatabaseSync(dbPath, {
|
|
6808
|
-
readOnly: true,
|
|
7048
|
+
db = openSqliteDatabase(dbPath, {
|
|
7049
|
+
readonly: true,
|
|
6809
7050
|
timeout: 1e3
|
|
6810
7051
|
});
|
|
6811
7052
|
const row = db.prepare("SELECT title FROM sessions WHERE id = ? LIMIT 1").get(sessionId);
|
|
@@ -6820,11 +7061,8 @@ function readSessionTitleFromStateDb(dbPath, sessionId) {
|
|
|
6820
7061
|
function readCompressionTipFromStateDb(dbPath, sessionId) {
|
|
6821
7062
|
let db = null;
|
|
6822
7063
|
try {
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
);
|
|
6826
|
-
db = new DatabaseSync(dbPath, {
|
|
6827
|
-
readOnly: true,
|
|
7064
|
+
db = openSqliteDatabase(dbPath, {
|
|
7065
|
+
readonly: true,
|
|
6828
7066
|
timeout: 1e3
|
|
6829
7067
|
});
|
|
6830
7068
|
let current = sessionId;
|
|
@@ -6857,7 +7095,7 @@ function readCompressionTipFromStateDb(dbPath, sessionId) {
|
|
|
6857
7095
|
db?.close();
|
|
6858
7096
|
}
|
|
6859
7097
|
}
|
|
6860
|
-
function
|
|
7098
|
+
function isNodeError8(error, code) {
|
|
6861
7099
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
6862
7100
|
}
|
|
6863
7101
|
function isValidProfileName(value) {
|
|
@@ -8284,12 +8522,63 @@ function toRecord6(value) {
|
|
|
8284
8522
|
}
|
|
8285
8523
|
|
|
8286
8524
|
// src/conversations/conversation-queries.ts
|
|
8525
|
+
var DEFAULT_CONVERSATION_LIST_PAGE_SIZE = 25;
|
|
8526
|
+
var MAX_CONVERSATION_LIST_PAGE_SIZE = 100;
|
|
8287
8527
|
var ConversationQueryCoordinator = class {
|
|
8288
8528
|
constructor(deps) {
|
|
8289
8529
|
this.deps = deps;
|
|
8290
8530
|
}
|
|
8291
8531
|
deps;
|
|
8292
8532
|
async listConversations() {
|
|
8533
|
+
return this.listConversationsFromStore();
|
|
8534
|
+
}
|
|
8535
|
+
async listConversationPage(options = {}) {
|
|
8536
|
+
const limit = normalizeConversationListPageLimit(options.limit);
|
|
8537
|
+
const cursor = decodeConversationListCursor(options.cursor);
|
|
8538
|
+
const indexedPage = await listConversationStatsPage(this.deps.paths, {
|
|
8539
|
+
limit,
|
|
8540
|
+
cursor
|
|
8541
|
+
});
|
|
8542
|
+
if (indexedPage.records.length === 0) {
|
|
8543
|
+
return this.listConversationPageFromStore({ limit, cursor });
|
|
8544
|
+
}
|
|
8545
|
+
const summaries = await this.summarizeIndexedConversations(
|
|
8546
|
+
indexedPage.records
|
|
8547
|
+
);
|
|
8548
|
+
return {
|
|
8549
|
+
conversations: summaries,
|
|
8550
|
+
page: {
|
|
8551
|
+
limit,
|
|
8552
|
+
has_more: indexedPage.hasMore,
|
|
8553
|
+
next_cursor: indexedPage.hasMore && indexedPage.records.length > 0 ? encodeConversationListCursor(indexedPage.records.at(-1)) : null
|
|
8554
|
+
}
|
|
8555
|
+
};
|
|
8556
|
+
}
|
|
8557
|
+
async searchConversationPage(options = {}) {
|
|
8558
|
+
const query = normalizeConversationSearchQuery(options.query);
|
|
8559
|
+
if (!query) {
|
|
8560
|
+
return this.listConversationPage(options);
|
|
8561
|
+
}
|
|
8562
|
+
const limit = normalizeConversationListPageLimit(options.limit);
|
|
8563
|
+
const cursor = decodeConversationListCursor(options.cursor);
|
|
8564
|
+
const indexedPage = await searchConversationStatsPage(this.deps.paths, {
|
|
8565
|
+
limit,
|
|
8566
|
+
cursor,
|
|
8567
|
+
query
|
|
8568
|
+
});
|
|
8569
|
+
const summaries = await this.summarizeIndexedConversations(
|
|
8570
|
+
indexedPage.records
|
|
8571
|
+
);
|
|
8572
|
+
return {
|
|
8573
|
+
conversations: summaries,
|
|
8574
|
+
page: {
|
|
8575
|
+
limit,
|
|
8576
|
+
has_more: indexedPage.hasMore,
|
|
8577
|
+
next_cursor: indexedPage.hasMore && indexedPage.records.length > 0 ? encodeConversationListCursor(indexedPage.records.at(-1)) : null
|
|
8578
|
+
}
|
|
8579
|
+
};
|
|
8580
|
+
}
|
|
8581
|
+
async listConversationsFromStore() {
|
|
8293
8582
|
const summaries = [];
|
|
8294
8583
|
for (const conversationId of await this.deps.store.listConversationIds()) {
|
|
8295
8584
|
const manifest = await this.deps.store.readManifest(conversationId).catch(
|
|
@@ -8305,9 +8594,41 @@ var ConversationQueryCoordinator = class {
|
|
|
8305
8594
|
summaries.push(await this.summarizeConversation(refreshed, snapshot));
|
|
8306
8595
|
}
|
|
8307
8596
|
return summaries.sort(
|
|
8308
|
-
(left, right) => Date.parse(right.updated_at) - Date.parse(left.updated_at)
|
|
8597
|
+
(left, right) => Date.parse(right.updated_at) - Date.parse(left.updated_at) || right.id.localeCompare(left.id)
|
|
8309
8598
|
);
|
|
8310
8599
|
}
|
|
8600
|
+
async listConversationPageFromStore(input) {
|
|
8601
|
+
const all = await this.listConversationsFromStore();
|
|
8602
|
+
const startIndex = input.cursor ? all.findIndex((summary) => isAfterConversationListCursor(summary, input.cursor)) : 0;
|
|
8603
|
+
const safeStartIndex = startIndex < 0 ? all.length : startIndex;
|
|
8604
|
+
const conversations = all.slice(safeStartIndex, safeStartIndex + input.limit);
|
|
8605
|
+
const hasMore = safeStartIndex + input.limit < all.length;
|
|
8606
|
+
return {
|
|
8607
|
+
conversations,
|
|
8608
|
+
page: {
|
|
8609
|
+
limit: input.limit,
|
|
8610
|
+
has_more: hasMore,
|
|
8611
|
+
next_cursor: hasMore && conversations.length > 0 ? encodeConversationListCursorFromSummary(conversations.at(-1)) : null
|
|
8612
|
+
}
|
|
8613
|
+
};
|
|
8614
|
+
}
|
|
8615
|
+
async summarizeIndexedConversations(records) {
|
|
8616
|
+
const summaries = [];
|
|
8617
|
+
for (const record of records) {
|
|
8618
|
+
const manifest = await this.deps.store.readManifest(record.conversationId).catch(
|
|
8619
|
+
() => null
|
|
8620
|
+
);
|
|
8621
|
+
if (!manifest || manifest.status !== "active") {
|
|
8622
|
+
continue;
|
|
8623
|
+
}
|
|
8624
|
+
const snapshot = await this.deps.store.readSnapshot(record.conversationId).catch(
|
|
8625
|
+
() => emptySnapshot2()
|
|
8626
|
+
);
|
|
8627
|
+
const refreshed = await this.deps.metadata.refreshTitleFromHermes(manifest, { snapshot }).catch(() => manifest);
|
|
8628
|
+
summaries.push(await this.summarizeConversation(refreshed, snapshot));
|
|
8629
|
+
}
|
|
8630
|
+
return summaries;
|
|
8631
|
+
}
|
|
8311
8632
|
async getMessages(conversationId, options = {}) {
|
|
8312
8633
|
const manifest = await this.deps.store.readActiveManifest(conversationId);
|
|
8313
8634
|
const snapshot = await this.deps.store.readSnapshot(conversationId);
|
|
@@ -8394,6 +8715,61 @@ var ConversationQueryCoordinator = class {
|
|
|
8394
8715
|
});
|
|
8395
8716
|
}
|
|
8396
8717
|
};
|
|
8718
|
+
function normalizeConversationListPageLimit(value) {
|
|
8719
|
+
if (value === void 0 || !Number.isFinite(value)) {
|
|
8720
|
+
return DEFAULT_CONVERSATION_LIST_PAGE_SIZE;
|
|
8721
|
+
}
|
|
8722
|
+
return Math.max(
|
|
8723
|
+
1,
|
|
8724
|
+
Math.min(MAX_CONVERSATION_LIST_PAGE_SIZE, Math.floor(value))
|
|
8725
|
+
);
|
|
8726
|
+
}
|
|
8727
|
+
function encodeConversationListCursor(record) {
|
|
8728
|
+
return Buffer.from(
|
|
8729
|
+
JSON.stringify({
|
|
8730
|
+
updated_at: record.updatedAt,
|
|
8731
|
+
conversation_id: record.conversationId
|
|
8732
|
+
}),
|
|
8733
|
+
"utf8"
|
|
8734
|
+
).toString("base64url");
|
|
8735
|
+
}
|
|
8736
|
+
function encodeConversationListCursorFromSummary(summary) {
|
|
8737
|
+
return encodeConversationListCursor({
|
|
8738
|
+
conversationId: summary.id,
|
|
8739
|
+
updatedAt: summary.updated_at
|
|
8740
|
+
});
|
|
8741
|
+
}
|
|
8742
|
+
function decodeConversationListCursor(value) {
|
|
8743
|
+
if (!value) {
|
|
8744
|
+
return null;
|
|
8745
|
+
}
|
|
8746
|
+
try {
|
|
8747
|
+
const decoded = JSON.parse(
|
|
8748
|
+
Buffer.from(value, "base64url").toString("utf8")
|
|
8749
|
+
);
|
|
8750
|
+
const updatedAt = readNonEmptyString(decoded.updated_at);
|
|
8751
|
+
const conversationId = readNonEmptyString(decoded.conversation_id);
|
|
8752
|
+
if (!updatedAt || !conversationId) {
|
|
8753
|
+
throw new Error("invalid cursor");
|
|
8754
|
+
}
|
|
8755
|
+
return { updatedAt, conversationId };
|
|
8756
|
+
} catch {
|
|
8757
|
+
throw new LinkHttpError(
|
|
8758
|
+
400,
|
|
8759
|
+
"conversation_cursor_invalid",
|
|
8760
|
+
"Conversation list cursor is invalid"
|
|
8761
|
+
);
|
|
8762
|
+
}
|
|
8763
|
+
}
|
|
8764
|
+
function readNonEmptyString(value) {
|
|
8765
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
8766
|
+
}
|
|
8767
|
+
function normalizeConversationSearchQuery(value) {
|
|
8768
|
+
return typeof value === "string" ? value.trim() : "";
|
|
8769
|
+
}
|
|
8770
|
+
function isAfterConversationListCursor(summary, cursor) {
|
|
8771
|
+
return summary.updated_at < cursor.updatedAt || summary.updated_at === cursor.updatedAt && summary.id < cursor.conversationId;
|
|
8772
|
+
}
|
|
8397
8773
|
function hydrateAgentEventBlocks(blocks, agentEvents) {
|
|
8398
8774
|
if (!blocks?.length || agentEvents.length === 0) {
|
|
8399
8775
|
return blocks;
|
|
@@ -8418,11 +8794,11 @@ function hydrateAgentEventBlocks(blocks, agentEvents) {
|
|
|
8418
8794
|
// src/conversations/conversation-store.ts
|
|
8419
8795
|
import {
|
|
8420
8796
|
appendFile as appendFile2,
|
|
8421
|
-
mkdir as
|
|
8422
|
-
readdir as
|
|
8797
|
+
mkdir as mkdir7,
|
|
8798
|
+
readdir as readdir5,
|
|
8423
8799
|
readFile as readFile7,
|
|
8424
8800
|
rm as rm5,
|
|
8425
|
-
writeFile as
|
|
8801
|
+
writeFile as writeFile2
|
|
8426
8802
|
} from "fs/promises";
|
|
8427
8803
|
import path12 from "path";
|
|
8428
8804
|
var ConversationStore = class {
|
|
@@ -8431,14 +8807,14 @@ var ConversationStore = class {
|
|
|
8431
8807
|
}
|
|
8432
8808
|
paths;
|
|
8433
8809
|
async ensureConversationsDir() {
|
|
8434
|
-
await
|
|
8810
|
+
await mkdir7(this.paths.conversationsDir, { recursive: true, mode: 448 });
|
|
8435
8811
|
}
|
|
8436
8812
|
async listConversationIds() {
|
|
8437
8813
|
await this.ensureConversationsDir();
|
|
8438
|
-
const entries = await
|
|
8814
|
+
const entries = await readdir5(this.paths.conversationsDir, {
|
|
8439
8815
|
withFileTypes: true
|
|
8440
8816
|
}).catch((error) => {
|
|
8441
|
-
if (
|
|
8817
|
+
if (isNodeError9(error, "ENOENT")) {
|
|
8442
8818
|
return [];
|
|
8443
8819
|
}
|
|
8444
8820
|
throw error;
|
|
@@ -8446,7 +8822,7 @@ var ConversationStore = class {
|
|
|
8446
8822
|
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
8447
8823
|
}
|
|
8448
8824
|
async createConversation(manifest, snapshot = createEmptySnapshot2()) {
|
|
8449
|
-
await
|
|
8825
|
+
await mkdir7(this.conversationDir(manifest.id), {
|
|
8450
8826
|
recursive: true,
|
|
8451
8827
|
mode: 448
|
|
8452
8828
|
});
|
|
@@ -8486,7 +8862,7 @@ var ConversationStore = class {
|
|
|
8486
8862
|
conversation_id: conversationId,
|
|
8487
8863
|
created_at: now
|
|
8488
8864
|
};
|
|
8489
|
-
await
|
|
8865
|
+
await mkdir7(this.conversationDir(conversationId), {
|
|
8490
8866
|
recursive: true,
|
|
8491
8867
|
mode: 448
|
|
8492
8868
|
});
|
|
@@ -8507,7 +8883,7 @@ var ConversationStore = class {
|
|
|
8507
8883
|
await this.readManifest(conversationId);
|
|
8508
8884
|
const raw = await readFile7(this.eventsPath(conversationId), "utf8").catch(
|
|
8509
8885
|
(error) => {
|
|
8510
|
-
if (
|
|
8886
|
+
if (isNodeError9(error, "ENOENT")) {
|
|
8511
8887
|
return "";
|
|
8512
8888
|
}
|
|
8513
8889
|
throw error;
|
|
@@ -8517,7 +8893,7 @@ var ConversationStore = class {
|
|
|
8517
8893
|
}
|
|
8518
8894
|
overwriteEvents(conversationId, events) {
|
|
8519
8895
|
const content = events.map((event) => JSON.stringify(event)).join("\n");
|
|
8520
|
-
return
|
|
8896
|
+
return writeFile2(
|
|
8521
8897
|
this.eventsPath(conversationId),
|
|
8522
8898
|
content ? `${content}
|
|
8523
8899
|
` : "",
|
|
@@ -8562,19 +8938,18 @@ var ConversationStore = class {
|
|
|
8562
8938
|
function createEmptySnapshot2() {
|
|
8563
8939
|
return { schema_version: 1, messages: [], runs: [] };
|
|
8564
8940
|
}
|
|
8565
|
-
function
|
|
8941
|
+
function isNodeError9(error, code) {
|
|
8566
8942
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
8567
8943
|
}
|
|
8568
8944
|
|
|
8569
8945
|
// src/conversations/hermes-session-sync.ts
|
|
8570
8946
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
8571
|
-
import { readdir as
|
|
8572
|
-
import { createRequire as createRequire3 } from "module";
|
|
8947
|
+
import { readdir as readdir7, readFile as readFile9, stat as stat9 } from "fs/promises";
|
|
8573
8948
|
import os4 from "os";
|
|
8574
8949
|
import path14 from "path";
|
|
8575
8950
|
|
|
8576
8951
|
// src/conversations/delivery-import.ts
|
|
8577
|
-
import { lstat, readFile as readFile8, readdir as
|
|
8952
|
+
import { lstat as lstat2, readFile as readFile8, readdir as readdir6, stat as stat8 } from "fs/promises";
|
|
8578
8953
|
import path13 from "path";
|
|
8579
8954
|
var MAX_IMPORTED_BLOB_BYTES = 100 * 1024 * 1024;
|
|
8580
8955
|
var MAX_MEDIA_IMPORT_FAILURES = 20;
|
|
@@ -8670,8 +9045,8 @@ function resolveDeliveryStagingTarget(paths, stagingDir) {
|
|
|
8670
9045
|
};
|
|
8671
9046
|
}
|
|
8672
9047
|
async function collectStagedDeliveryReferences(stagingDir) {
|
|
8673
|
-
const directoryStat = await
|
|
8674
|
-
if (
|
|
9048
|
+
const directoryStat = await lstat2(stagingDir).catch((error) => {
|
|
9049
|
+
if (isNodeError10(error, "ENOENT")) {
|
|
8675
9050
|
throw new LinkHttpError(
|
|
8676
9051
|
404,
|
|
8677
9052
|
"delivery_staging_not_found",
|
|
@@ -8687,7 +9062,7 @@ async function collectStagedDeliveryReferences(stagingDir) {
|
|
|
8687
9062
|
"delivery staging path is not a directory"
|
|
8688
9063
|
);
|
|
8689
9064
|
}
|
|
8690
|
-
const entries = await
|
|
9065
|
+
const entries = await readdir6(stagingDir, { withFileTypes: true });
|
|
8691
9066
|
return entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).filter((entry) => isSupportedDeliveryFilename(entry.name)).sort(
|
|
8692
9067
|
(left, right) => left.name.localeCompare(right.name, "en", { numeric: true })
|
|
8693
9068
|
).slice(0, MAX_DELIVERY_FILES).map((entry) => {
|
|
@@ -8851,8 +9226,8 @@ function emptyImportResult(input) {
|
|
|
8851
9226
|
}
|
|
8852
9227
|
async function writeBlobFromFile(deps, conversationId, source) {
|
|
8853
9228
|
const sourcePath = resolveMediaSourcePath(source.path);
|
|
8854
|
-
const fileStat = await
|
|
8855
|
-
if (
|
|
9229
|
+
const fileStat = await stat8(sourcePath).catch((error) => {
|
|
9230
|
+
if (isNodeError10(error, "ENOENT")) {
|
|
8856
9231
|
throw new LinkHttpError(
|
|
8857
9232
|
404,
|
|
8858
9233
|
"media_source_not_found",
|
|
@@ -8886,7 +9261,7 @@ function describeMediaImportFailure(reference, sourceKey, error) {
|
|
|
8886
9261
|
key: sourceKey,
|
|
8887
9262
|
filename: sanitizeFilename(reference.path, "attachment"),
|
|
8888
9263
|
reason: error instanceof Error ? error.message : String(error),
|
|
8889
|
-
...
|
|
9264
|
+
...isNodeError10(error) && error.code ? { code: error.code } : {}
|
|
8890
9265
|
};
|
|
8891
9266
|
}
|
|
8892
9267
|
function isSupportedDeliveryFilename(filename) {
|
|
@@ -8899,12 +9274,11 @@ function readString8(payload, key) {
|
|
|
8899
9274
|
function toRecord7(value) {
|
|
8900
9275
|
return typeof value === "object" && value !== null ? value : {};
|
|
8901
9276
|
}
|
|
8902
|
-
function
|
|
9277
|
+
function isNodeError10(error, code) {
|
|
8903
9278
|
return typeof error === "object" && error !== null && "code" in error && (code === void 0 || error.code === code);
|
|
8904
9279
|
}
|
|
8905
9280
|
|
|
8906
9281
|
// src/conversations/hermes-session-sync.ts
|
|
8907
|
-
var nodeRequire3 = createRequire3(import.meta.url);
|
|
8908
9282
|
var PROFILE_NAME_PATTERN3 = /^[a-zA-Z0-9._-]{1,64}$/u;
|
|
8909
9283
|
var DEFAULT_PROFILE_NAME = "default";
|
|
8910
9284
|
var MAX_IMPORTABLE_SESSIONS = 100;
|
|
@@ -9667,9 +10041,9 @@ function rememberKnownHermesConversation(map, sessionId, conversationId) {
|
|
|
9667
10041
|
async function discoverHermesProfileNames() {
|
|
9668
10042
|
const names = /* @__PURE__ */ new Set([DEFAULT_PROFILE_NAME]);
|
|
9669
10043
|
const profilesDir = path14.join(os4.homedir(), ".hermes", "profiles");
|
|
9670
|
-
const entries = await
|
|
10044
|
+
const entries = await readdir7(profilesDir, { withFileTypes: true }).catch(
|
|
9671
10045
|
(error) => {
|
|
9672
|
-
if (
|
|
10046
|
+
if (isNodeError11(error, "ENOENT")) {
|
|
9673
10047
|
return [];
|
|
9674
10048
|
}
|
|
9675
10049
|
throw error;
|
|
@@ -9696,11 +10070,8 @@ async function listProfileSessions(dbPath) {
|
|
|
9696
10070
|
}
|
|
9697
10071
|
let db = null;
|
|
9698
10072
|
try {
|
|
9699
|
-
|
|
9700
|
-
|
|
9701
|
-
);
|
|
9702
|
-
db = new DatabaseSync(dbPath, {
|
|
9703
|
-
readOnly: true,
|
|
10073
|
+
db = openSqliteDatabase(dbPath, {
|
|
10074
|
+
readonly: true,
|
|
9704
10075
|
timeout: 1e3
|
|
9705
10076
|
});
|
|
9706
10077
|
const sessionColumns = readTableColumns(db, "sessions");
|
|
@@ -9840,11 +10211,8 @@ async function readStateDbMessages(dbPath, sessionId) {
|
|
|
9840
10211
|
}
|
|
9841
10212
|
let db = null;
|
|
9842
10213
|
try {
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
);
|
|
9846
|
-
db = new DatabaseSync(dbPath, {
|
|
9847
|
-
readOnly: true,
|
|
10214
|
+
db = openSqliteDatabase(dbPath, {
|
|
10215
|
+
readonly: true,
|
|
9848
10216
|
timeout: 1e3
|
|
9849
10217
|
});
|
|
9850
10218
|
const columns = readTableColumns(db, "messages");
|
|
@@ -9876,7 +10244,7 @@ async function readJsonlMessages(profileName, sessionId) {
|
|
|
9876
10244
|
const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path14.join(profileDir, "sessions"));
|
|
9877
10245
|
const transcriptPath = path14.join(sessionsDir, `${sessionId}.jsonl`);
|
|
9878
10246
|
const raw = await readFile9(transcriptPath, "utf8").catch((error) => {
|
|
9879
|
-
if (
|
|
10247
|
+
if (isNodeError11(error, "ENOENT")) {
|
|
9880
10248
|
return "";
|
|
9881
10249
|
}
|
|
9882
10250
|
throw error;
|
|
@@ -10151,8 +10519,8 @@ function quoteIdentifier(value) {
|
|
|
10151
10519
|
return `"${value.replaceAll('"', '""')}"`;
|
|
10152
10520
|
}
|
|
10153
10521
|
async function isFile(filePath) {
|
|
10154
|
-
return
|
|
10155
|
-
if (
|
|
10522
|
+
return stat9(filePath).then((value) => value.isFile()).catch((error) => {
|
|
10523
|
+
if (isNodeError11(error, "ENOENT")) {
|
|
10156
10524
|
return false;
|
|
10157
10525
|
}
|
|
10158
10526
|
throw error;
|
|
@@ -10185,12 +10553,12 @@ function readBoolean(value) {
|
|
|
10185
10553
|
}
|
|
10186
10554
|
return false;
|
|
10187
10555
|
}
|
|
10188
|
-
function
|
|
10556
|
+
function isNodeError11(error, code) {
|
|
10189
10557
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
10190
10558
|
}
|
|
10191
10559
|
|
|
10192
10560
|
// src/conversations/run-lifecycle.ts
|
|
10193
|
-
import { readdir as
|
|
10561
|
+
import { readdir as readdir8 } from "fs/promises";
|
|
10194
10562
|
|
|
10195
10563
|
// src/hermes/api-server.ts
|
|
10196
10564
|
async function listHermesModels(options = {}) {
|
|
@@ -10584,10 +10952,8 @@ function readString10(payload, key) {
|
|
|
10584
10952
|
}
|
|
10585
10953
|
|
|
10586
10954
|
// src/conversations/history-builder.ts
|
|
10587
|
-
import { readFile as readFile10, stat as
|
|
10588
|
-
import { createRequire as createRequire4 } from "module";
|
|
10955
|
+
import { readFile as readFile10, stat as stat10 } from "fs/promises";
|
|
10589
10956
|
import path15 from "path";
|
|
10590
|
-
var nodeRequire4 = createRequire4(import.meta.url);
|
|
10591
10957
|
var HISTORY_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
10592
10958
|
var HERMES_HISTORY_COLUMNS = [
|
|
10593
10959
|
"role",
|
|
@@ -10694,8 +11060,8 @@ async function readHermesTranscriptHistory(sessionId, profileName) {
|
|
|
10694
11060
|
};
|
|
10695
11061
|
}
|
|
10696
11062
|
async function readHermesStateDbHistory(dbPath, sessionId) {
|
|
10697
|
-
const exists = await
|
|
10698
|
-
if (
|
|
11063
|
+
const exists = await stat10(dbPath).then((value) => value.isFile()).catch((error) => {
|
|
11064
|
+
if (isNodeError12(error, "ENOENT")) {
|
|
10699
11065
|
return false;
|
|
10700
11066
|
}
|
|
10701
11067
|
throw error;
|
|
@@ -10714,7 +11080,7 @@ async function readHermesJsonlHistory(sessionsDir, sessionId) {
|
|
|
10714
11080
|
}
|
|
10715
11081
|
const transcriptPath = path15.join(sessionsDir, `${sessionId}.jsonl`);
|
|
10716
11082
|
const raw = await readFile10(transcriptPath, "utf8").catch((error) => {
|
|
10717
|
-
if (
|
|
11083
|
+
if (isNodeError12(error, "ENOENT")) {
|
|
10718
11084
|
return "";
|
|
10719
11085
|
}
|
|
10720
11086
|
throw error;
|
|
@@ -10749,11 +11115,8 @@ async function readHermesJsonlHistory(sessionsDir, sessionId) {
|
|
|
10749
11115
|
function readHistoryRows(dbPath, sessionId) {
|
|
10750
11116
|
let db = null;
|
|
10751
11117
|
try {
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
);
|
|
10755
|
-
db = new DatabaseSync(dbPath, {
|
|
10756
|
-
readOnly: true,
|
|
11118
|
+
db = openSqliteDatabase(dbPath, {
|
|
11119
|
+
readonly: true,
|
|
10757
11120
|
timeout: 1e3
|
|
10758
11121
|
});
|
|
10759
11122
|
const columns = readTableColumns2(db, "messages");
|
|
@@ -10939,7 +11302,7 @@ function readTableColumns2(db, table) {
|
|
|
10939
11302
|
db.prepare(`PRAGMA table_info(${table})`).all().map((row) => row.name).filter((name) => typeof name === "string")
|
|
10940
11303
|
);
|
|
10941
11304
|
}
|
|
10942
|
-
function
|
|
11305
|
+
function isNodeError12(error, code) {
|
|
10943
11306
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
10944
11307
|
}
|
|
10945
11308
|
function isValidProfileName2(value) {
|
|
@@ -10957,7 +11320,7 @@ function normalizeProfileForCompare(value) {
|
|
|
10957
11320
|
|
|
10958
11321
|
// src/hermes/stt.ts
|
|
10959
11322
|
import { execFile as execFile3 } from "child_process";
|
|
10960
|
-
import { access as access2, readFile as readFile11, stat as
|
|
11323
|
+
import { access as access2, readFile as readFile11, stat as stat11 } from "fs/promises";
|
|
10961
11324
|
import path16 from "path";
|
|
10962
11325
|
import { promisify as promisify3 } from "util";
|
|
10963
11326
|
var execFileAsync3 = promisify3(execFile3);
|
|
@@ -11117,7 +11480,7 @@ async function resolveExecutablePath(command) {
|
|
|
11117
11480
|
}
|
|
11118
11481
|
async function isExecutableFile(filePath) {
|
|
11119
11482
|
try {
|
|
11120
|
-
const info = await
|
|
11483
|
+
const info = await stat11(filePath);
|
|
11121
11484
|
if (!info.isFile()) {
|
|
11122
11485
|
return false;
|
|
11123
11486
|
}
|
|
@@ -11158,7 +11521,7 @@ async function findDevHermesAgentSource() {
|
|
|
11158
11521
|
return null;
|
|
11159
11522
|
}
|
|
11160
11523
|
async function isDirectory(candidate) {
|
|
11161
|
-
return
|
|
11524
|
+
return stat11(candidate).then((info) => info.isDirectory()).catch(() => false);
|
|
11162
11525
|
}
|
|
11163
11526
|
function compactProcessOutput(value) {
|
|
11164
11527
|
const compact = value.trim().replace(/\s+/gu, " ").slice(0, 500);
|
|
@@ -12402,8 +12765,8 @@ function formatFilenameList(filenames) {
|
|
|
12402
12765
|
return remaining > 0 ? `${preview.join("\u3001")} \u7B49 ${filenames.length} \u4E2A` : preview.join("\u3001");
|
|
12403
12766
|
}
|
|
12404
12767
|
async function readdirWithDirs(directory) {
|
|
12405
|
-
return
|
|
12406
|
-
if (
|
|
12768
|
+
return readdir8(directory, { withFileTypes: true }).catch((error) => {
|
|
12769
|
+
if (isNodeError13(error, "ENOENT")) {
|
|
12407
12770
|
return [];
|
|
12408
12771
|
}
|
|
12409
12772
|
throw error;
|
|
@@ -12633,7 +12996,7 @@ function readResponseId(payload) {
|
|
|
12633
12996
|
const response = toRecord11(payload.response);
|
|
12634
12997
|
return readString13(payload, "response_id") ?? readString13(response, "id");
|
|
12635
12998
|
}
|
|
12636
|
-
function
|
|
12999
|
+
function isNodeError13(error, code) {
|
|
12637
13000
|
if (typeof error !== "object" || error === null || !("code" in error)) {
|
|
12638
13001
|
return false;
|
|
12639
13002
|
}
|
|
@@ -12744,6 +13107,12 @@ var ConversationService = class {
|
|
|
12744
13107
|
async listConversations() {
|
|
12745
13108
|
return this.queries.listConversations();
|
|
12746
13109
|
}
|
|
13110
|
+
listConversationPage(input = {}) {
|
|
13111
|
+
return this.queries.listConversationPage(input);
|
|
13112
|
+
}
|
|
13113
|
+
searchConversationPage(input = {}) {
|
|
13114
|
+
return this.queries.searchConversationPage(input);
|
|
13115
|
+
}
|
|
12747
13116
|
async getStatistics(filter = {}) {
|
|
12748
13117
|
return readLinkStatistics(this.paths, filter);
|
|
12749
13118
|
}
|
|
@@ -13463,7 +13832,7 @@ function findApproval(snapshot, approvalId) {
|
|
|
13463
13832
|
|
|
13464
13833
|
// src/identity/identity.ts
|
|
13465
13834
|
import { generateKeyPairSync, randomUUID as randomUUID8, sign } from "crypto";
|
|
13466
|
-
import { mkdir as
|
|
13835
|
+
import { mkdir as mkdir8, chmod as chmod2 } from "fs/promises";
|
|
13467
13836
|
import { z } from "zod";
|
|
13468
13837
|
var linkIdentitySchema = z.object({
|
|
13469
13838
|
install_id: z.string().min(1),
|
|
@@ -13485,8 +13854,8 @@ async function ensureIdentity(paths = resolveRuntimePaths()) {
|
|
|
13485
13854
|
if (existing) {
|
|
13486
13855
|
return existing;
|
|
13487
13856
|
}
|
|
13488
|
-
await
|
|
13489
|
-
await
|
|
13857
|
+
await mkdir8(paths.homeDir, { recursive: true, mode: 448 });
|
|
13858
|
+
await chmod2(paths.homeDir, 448).catch(() => void 0);
|
|
13490
13859
|
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
13491
13860
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13492
13861
|
const identity = {
|
|
@@ -14291,9 +14660,28 @@ function registerConversationRoutes(router, options) {
|
|
|
14291
14660
|
router.get("/api/v1/conversations", async (ctx) => {
|
|
14292
14661
|
await authenticateRequest(ctx, paths);
|
|
14293
14662
|
ctx.set("cache-control", "no-store");
|
|
14663
|
+
const result = await conversations.listConversationPage({
|
|
14664
|
+
limit: readLimit(ctx.query.limit),
|
|
14665
|
+
cursor: readQueryString(ctx.query.cursor) ?? readQueryString(ctx.query.after) ?? readQueryString(ctx.query.page_cursor)
|
|
14666
|
+
});
|
|
14294
14667
|
ctx.body = {
|
|
14295
14668
|
ok: true,
|
|
14296
|
-
conversations:
|
|
14669
|
+
conversations: result.conversations,
|
|
14670
|
+
page: result.page
|
|
14671
|
+
};
|
|
14672
|
+
});
|
|
14673
|
+
router.get("/api/v1/conversations/search", async (ctx) => {
|
|
14674
|
+
await authenticateRequest(ctx, paths);
|
|
14675
|
+
ctx.set("cache-control", "no-store");
|
|
14676
|
+
const result = await conversations.searchConversationPage({
|
|
14677
|
+
limit: readLimit(ctx.query.limit),
|
|
14678
|
+
cursor: readQueryString(ctx.query.cursor) ?? readQueryString(ctx.query.after) ?? readQueryString(ctx.query.page_cursor),
|
|
14679
|
+
query: readQueryString(ctx.query.query) ?? readQueryString(ctx.query.q) ?? readQueryString(ctx.query.keyword) ?? ""
|
|
14680
|
+
});
|
|
14681
|
+
ctx.body = {
|
|
14682
|
+
ok: true,
|
|
14683
|
+
conversations: result.conversations,
|
|
14684
|
+
page: result.page
|
|
14297
14685
|
};
|
|
14298
14686
|
});
|
|
14299
14687
|
router.post("/api/v1/conversations", async (ctx) => {
|
|
@@ -14661,7 +15049,7 @@ function createHttpErrorMiddleware(logger) {
|
|
|
14661
15049
|
}
|
|
14662
15050
|
|
|
14663
15051
|
// src/hermes/profiles.ts
|
|
14664
|
-
import {
|
|
15052
|
+
import { readdir as readdir9, readFile as readFile12, rename as rename3, rm as rm6, stat as stat12 } from "fs/promises";
|
|
14665
15053
|
import os5 from "os";
|
|
14666
15054
|
import path17 from "path";
|
|
14667
15055
|
import YAML2 from "yaml";
|
|
@@ -14671,9 +15059,9 @@ async function listHermesProfiles(paths = resolveRuntimePaths()) {
|
|
|
14671
15059
|
const profiles = /* @__PURE__ */ new Map();
|
|
14672
15060
|
profiles.set(DEFAULT_PROFILE, await profileInfo(DEFAULT_PROFILE, paths));
|
|
14673
15061
|
const profilesDir = path17.join(os5.homedir(), ".hermes", "profiles");
|
|
14674
|
-
const entries = await
|
|
15062
|
+
const entries = await readdir9(profilesDir, { withFileTypes: true }).catch(
|
|
14675
15063
|
(error) => {
|
|
14676
|
-
if (
|
|
15064
|
+
if (isNodeError14(error, "ENOENT")) {
|
|
14677
15065
|
return [];
|
|
14678
15066
|
}
|
|
14679
15067
|
throw error;
|
|
@@ -14697,8 +15085,8 @@ async function listHermesProfiles(paths = resolveRuntimePaths()) {
|
|
|
14697
15085
|
async function getHermesProfileStatus(name, paths = resolveRuntimePaths()) {
|
|
14698
15086
|
assertProfileName(name);
|
|
14699
15087
|
const profile = await profileInfo(name, paths);
|
|
14700
|
-
const exists = await
|
|
14701
|
-
if (
|
|
15088
|
+
const exists = await stat12(profile.path).then((value) => value.isDirectory()).catch((error) => {
|
|
15089
|
+
if (isNodeError14(error, "ENOENT")) {
|
|
14702
15090
|
return false;
|
|
14703
15091
|
}
|
|
14704
15092
|
throw error;
|
|
@@ -14715,7 +15103,7 @@ async function renameHermesProfile(oldName, newName, paths = resolveRuntimePaths
|
|
|
14715
15103
|
assertMutableProfile(newName);
|
|
14716
15104
|
const oldProfile = await profileInfo(oldName, paths);
|
|
14717
15105
|
const newProfilePath = resolveHermesProfileDir(newName);
|
|
14718
|
-
await
|
|
15106
|
+
await rename3(oldProfile.path, newProfilePath);
|
|
14719
15107
|
const identity = await renameProfileIdentity(paths, {
|
|
14720
15108
|
oldProfileName: oldName,
|
|
14721
15109
|
newProfileName: newName,
|
|
@@ -14738,8 +15126,8 @@ async function updateHermesProfileMetadata(name, metadata, paths = resolveRuntim
|
|
|
14738
15126
|
async function deleteHermesProfile(name, paths = resolveRuntimePaths()) {
|
|
14739
15127
|
assertMutableProfile(name);
|
|
14740
15128
|
const profile = await profileInfo(name, paths);
|
|
14741
|
-
const exists = await
|
|
14742
|
-
if (
|
|
15129
|
+
const exists = await stat12(profile.path).then((value) => value.isDirectory()).catch((error) => {
|
|
15130
|
+
if (isNodeError14(error, "ENOENT")) {
|
|
14743
15131
|
return false;
|
|
14744
15132
|
}
|
|
14745
15133
|
throw error;
|
|
@@ -14807,13 +15195,13 @@ function assertProfileName(name) {
|
|
|
14807
15195
|
throw new LinkHttpError(400, "invalid_profile_name", "invalid profile name");
|
|
14808
15196
|
}
|
|
14809
15197
|
}
|
|
14810
|
-
function
|
|
15198
|
+
function isNodeError14(error, code) {
|
|
14811
15199
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
14812
15200
|
}
|
|
14813
15201
|
async function countSkills(root) {
|
|
14814
|
-
const entries = await
|
|
15202
|
+
const entries = await readdir9(root, { withFileTypes: true }).catch(
|
|
14815
15203
|
(error) => {
|
|
14816
|
-
if (
|
|
15204
|
+
if (isNodeError14(error, "ENOENT")) {
|
|
14817
15205
|
return [];
|
|
14818
15206
|
}
|
|
14819
15207
|
throw error;
|
|
@@ -14840,7 +15228,7 @@ async function countConfiguredTools(profileName) {
|
|
|
14840
15228
|
resolveHermesConfigPath(profileName),
|
|
14841
15229
|
"utf8"
|
|
14842
15230
|
).catch((error) => {
|
|
14843
|
-
if (
|
|
15231
|
+
if (isNodeError14(error, "ENOENT")) {
|
|
14844
15232
|
return "";
|
|
14845
15233
|
}
|
|
14846
15234
|
throw error;
|
|
@@ -15517,14 +15905,11 @@ function errorMessage(error) {
|
|
|
15517
15905
|
import { spawn as spawn2 } from "child_process";
|
|
15518
15906
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
15519
15907
|
import {
|
|
15520
|
-
copyFile as copyFile2,
|
|
15521
15908
|
cp,
|
|
15522
|
-
mkdir as
|
|
15909
|
+
mkdir as mkdir9,
|
|
15523
15910
|
readFile as readFile13,
|
|
15524
15911
|
rm as rm7,
|
|
15525
|
-
stat as
|
|
15526
|
-
writeFile as writeFile4,
|
|
15527
|
-
rename as rename5
|
|
15912
|
+
stat as stat13
|
|
15528
15913
|
} from "fs/promises";
|
|
15529
15914
|
import path18 from "path";
|
|
15530
15915
|
import YAML3 from "yaml";
|
|
@@ -15574,7 +15959,7 @@ async function startHermesProfileCreation(input, options) {
|
|
|
15574
15959
|
signal: null,
|
|
15575
15960
|
error: null
|
|
15576
15961
|
};
|
|
15577
|
-
await
|
|
15962
|
+
await mkdir9(options.paths.runDir, { recursive: true, mode: 448 });
|
|
15578
15963
|
await writeProfileCreationState(options.paths, started);
|
|
15579
15964
|
await writer.write(`
|
|
15580
15965
|
=== profile creation started ${startedAt} ===
|
|
@@ -15851,7 +16236,7 @@ function randomSuffix(attempt) {
|
|
|
15851
16236
|
async function applyProfileCreationPostSteps(input) {
|
|
15852
16237
|
const profilePath = resolveHermesProfileDir(input.profileName);
|
|
15853
16238
|
if (!await pathExists(profilePath)) {
|
|
15854
|
-
await
|
|
16239
|
+
await ensureDirectoryWithInheritedMetadata(profilePath, 448);
|
|
15855
16240
|
}
|
|
15856
16241
|
if (input.sourceProfile && input.copyScopes.length > 0) {
|
|
15857
16242
|
await copyProfileScopes({
|
|
@@ -15979,7 +16364,7 @@ function collectEnvKeys(value, keys = /* @__PURE__ */ new Set()) {
|
|
|
15979
16364
|
async function writeEnvValues(profileName, values) {
|
|
15980
16365
|
const envPath = path18.join(resolveHermesProfileDir(profileName), ".env");
|
|
15981
16366
|
const existingRaw = await readFile13(envPath, "utf8").catch((error) => {
|
|
15982
|
-
if (
|
|
16367
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15983
16368
|
return "";
|
|
15984
16369
|
}
|
|
15985
16370
|
throw error;
|
|
@@ -16004,13 +16389,14 @@ async function writeEnvValues(profileName, values) {
|
|
|
16004
16389
|
nextLines.push(`${key}=${formatEnvValue2(value)}`);
|
|
16005
16390
|
}
|
|
16006
16391
|
const nextRaw = nextLines.join("\n").replace(/\n*$/u, "\n");
|
|
16007
|
-
await mkdir11(path18.dirname(envPath), { recursive: true, mode: 448 });
|
|
16008
16392
|
if (existingRaw) {
|
|
16009
|
-
await
|
|
16393
|
+
await atomicWriteFilePreservingMetadata(
|
|
16394
|
+
`${envPath}.bak.${Date.now()}`,
|
|
16395
|
+
existingRaw,
|
|
16396
|
+
{ metadataSourcePath: envPath }
|
|
16397
|
+
);
|
|
16010
16398
|
}
|
|
16011
|
-
|
|
16012
|
-
await writeFile4(tempPath, nextRaw, { mode: 384 });
|
|
16013
|
-
await rename5(tempPath, envPath);
|
|
16399
|
+
await atomicWriteFilePreservingMetadata(envPath, nextRaw);
|
|
16014
16400
|
}
|
|
16015
16401
|
async function copySkills(sourceProfile, targetProfile) {
|
|
16016
16402
|
const sourceSkills = path18.join(resolveHermesProfileDir(sourceProfile), "skills");
|
|
@@ -16024,6 +16410,10 @@ async function copySkills(sourceProfile, targetProfile) {
|
|
|
16024
16410
|
force: true,
|
|
16025
16411
|
errorOnExist: false
|
|
16026
16412
|
});
|
|
16413
|
+
await inheritOwnerRecursively(
|
|
16414
|
+
targetSkills,
|
|
16415
|
+
resolveHermesProfileDir(targetProfile)
|
|
16416
|
+
);
|
|
16027
16417
|
}
|
|
16028
16418
|
function copyProperty(source, target, key) {
|
|
16029
16419
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
@@ -16035,7 +16425,7 @@ function copyProperty(source, target, key) {
|
|
|
16035
16425
|
async function readYamlConfig(configPath) {
|
|
16036
16426
|
const existingRaw = await readFile13(configPath, "utf8").catch(
|
|
16037
16427
|
(error) => {
|
|
16038
|
-
if (
|
|
16428
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
16039
16429
|
return null;
|
|
16040
16430
|
}
|
|
16041
16431
|
throw error;
|
|
@@ -16047,14 +16437,15 @@ async function readYamlConfig(configPath) {
|
|
|
16047
16437
|
};
|
|
16048
16438
|
}
|
|
16049
16439
|
async function writeYamlConfig(configPath, input) {
|
|
16050
|
-
await mkdir11(path18.dirname(configPath), { recursive: true, mode: 448 });
|
|
16051
16440
|
if (input.existingRaw) {
|
|
16052
|
-
await
|
|
16441
|
+
await atomicWriteFilePreservingMetadata(
|
|
16442
|
+
`${configPath}.bak.${Date.now()}`,
|
|
16443
|
+
input.existingRaw,
|
|
16444
|
+
{ metadataSourcePath: configPath }
|
|
16445
|
+
);
|
|
16053
16446
|
}
|
|
16054
16447
|
const document = new YAML3.Document(input.config);
|
|
16055
|
-
|
|
16056
|
-
await writeFile4(tempPath, document.toString(), { mode: 384 });
|
|
16057
|
-
await rename5(tempPath, configPath);
|
|
16448
|
+
await atomicWriteFilePreservingMetadata(configPath, document.toString());
|
|
16058
16449
|
}
|
|
16059
16450
|
async function failProfileCreation(input) {
|
|
16060
16451
|
if (input.rollbackProfileName) {
|
|
@@ -16120,8 +16511,8 @@ async function clearProfileCreationLogFiles(paths) {
|
|
|
16120
16511
|
]);
|
|
16121
16512
|
}
|
|
16122
16513
|
async function pathExists(targetPath) {
|
|
16123
|
-
return await
|
|
16124
|
-
if (
|
|
16514
|
+
return await stat13(targetPath).then(() => true).catch((error) => {
|
|
16515
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
16125
16516
|
return false;
|
|
16126
16517
|
}
|
|
16127
16518
|
throw error;
|
|
@@ -16168,7 +16559,7 @@ function formatEnvValue2(value) {
|
|
|
16168
16559
|
function escapeRegExp2(value) {
|
|
16169
16560
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
16170
16561
|
}
|
|
16171
|
-
function
|
|
16562
|
+
function isNodeError15(error, code) {
|
|
16172
16563
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
16173
16564
|
}
|
|
16174
16565
|
|
|
@@ -16377,13 +16768,9 @@ function toProfileToolConfigHttpError(error) {
|
|
|
16377
16768
|
// src/hermes/memory.ts
|
|
16378
16769
|
import {
|
|
16379
16770
|
access as access3,
|
|
16380
|
-
|
|
16381
|
-
mkdir as mkdir12,
|
|
16382
|
-
readdir as readdir9,
|
|
16771
|
+
readdir as readdir10,
|
|
16383
16772
|
readFile as readFile14,
|
|
16384
|
-
|
|
16385
|
-
stat as stat13,
|
|
16386
|
-
writeFile as writeFile5
|
|
16773
|
+
stat as stat14
|
|
16387
16774
|
} from "fs/promises";
|
|
16388
16775
|
import path19 from "path";
|
|
16389
16776
|
import YAML4 from "yaml";
|
|
@@ -16696,12 +17083,11 @@ function isSensitiveConfigKey(key) {
|
|
|
16696
17083
|
}
|
|
16697
17084
|
async function writeCustomProviderConfig(profileName, provider, config) {
|
|
16698
17085
|
const configPath = customProviderConfigPath(profileName, provider);
|
|
16699
|
-
await
|
|
16700
|
-
|
|
16701
|
-
|
|
16702
|
-
|
|
16703
|
-
|
|
16704
|
-
});
|
|
17086
|
+
await atomicWriteFilePreservingMetadata(
|
|
17087
|
+
configPath,
|
|
17088
|
+
`${JSON.stringify(config, null, 2)}
|
|
17089
|
+
`
|
|
17090
|
+
);
|
|
16705
17091
|
}
|
|
16706
17092
|
function normalizeConfigurableProvider(provider, patch) {
|
|
16707
17093
|
if (provider === CUSTOM_PROVIDER_CARD_ID) {
|
|
@@ -16749,7 +17135,7 @@ async function patchHermesMemoryProvider(profileName, provider) {
|
|
|
16749
17135
|
const configPath = resolveHermesConfigPath(profileName);
|
|
16750
17136
|
const existingRaw = await readFile14(configPath, "utf8").catch(
|
|
16751
17137
|
(error) => {
|
|
16752
|
-
if (
|
|
17138
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
16753
17139
|
return null;
|
|
16754
17140
|
}
|
|
16755
17141
|
throw error;
|
|
@@ -16761,17 +17147,13 @@ async function patchHermesMemoryProvider(profileName, provider) {
|
|
|
16761
17147
|
memory.provider = provider === "built-in" ? "" : provider;
|
|
16762
17148
|
config.memory = memory;
|
|
16763
17149
|
const backupPath = existingRaw ? `${configPath}.bak.${Date.now()}` : null;
|
|
16764
|
-
|
|
16765
|
-
|
|
16766
|
-
|
|
17150
|
+
if (backupPath && existingRaw !== null) {
|
|
17151
|
+
await atomicWriteFilePreservingMetadata(backupPath, existingRaw, {
|
|
17152
|
+
metadataSourcePath: configPath
|
|
17153
|
+
});
|
|
16767
17154
|
}
|
|
16768
17155
|
document.contents = document.createNode(config);
|
|
16769
|
-
|
|
16770
|
-
await writeFile5(tempPath, document.toString(), {
|
|
16771
|
-
encoding: "utf8",
|
|
16772
|
-
mode: 384
|
|
16773
|
-
});
|
|
16774
|
-
await rename6(tempPath, configPath);
|
|
17156
|
+
await atomicWriteFilePreservingMetadata(configPath, document.toString());
|
|
16775
17157
|
}
|
|
16776
17158
|
function resolveMemoryDir(profileName) {
|
|
16777
17159
|
return path19.join(resolveHermesProfileDir(profileName), "memories");
|
|
@@ -16779,8 +17161,8 @@ function resolveMemoryDir(profileName) {
|
|
|
16779
17161
|
async function readMemoryStore(profileName, target, limits) {
|
|
16780
17162
|
const filePath = memoryFilePath(profileName, target);
|
|
16781
17163
|
const entries = await readMemoryEntries(filePath);
|
|
16782
|
-
const fileStat = await
|
|
16783
|
-
if (
|
|
17164
|
+
const fileStat = await stat14(filePath).catch((error) => {
|
|
17165
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
16784
17166
|
return null;
|
|
16785
17167
|
}
|
|
16786
17168
|
throw error;
|
|
@@ -16809,7 +17191,7 @@ async function readMemoryStore(profileName, target, limits) {
|
|
|
16809
17191
|
}
|
|
16810
17192
|
async function readMemoryEntries(filePath) {
|
|
16811
17193
|
const raw = await readFile14(filePath, "utf8").catch((error) => {
|
|
16812
|
-
if (
|
|
17194
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
16813
17195
|
return "";
|
|
16814
17196
|
}
|
|
16815
17197
|
throw error;
|
|
@@ -16829,17 +17211,10 @@ async function mutateMemoryEntries(profileName, target, mutate) {
|
|
|
16829
17211
|
async function writeMemoryEntries(profileName, target, entries) {
|
|
16830
17212
|
assertWithinLimit(target, entries, await readMemoryLimits(profileName));
|
|
16831
17213
|
const filePath = memoryFilePath(profileName, target);
|
|
16832
|
-
|
|
16833
|
-
|
|
16834
|
-
|
|
16835
|
-
dir,
|
|
16836
|
-
`.mem_${process.pid}_${Date.now()}_${target}.tmp`
|
|
17214
|
+
await atomicWriteFilePreservingMetadata(
|
|
17215
|
+
filePath,
|
|
17216
|
+
entries.join(ENTRY_DELIMITER)
|
|
16837
17217
|
);
|
|
16838
|
-
await writeFile5(tempPath, entries.join(ENTRY_DELIMITER), {
|
|
16839
|
-
encoding: "utf8",
|
|
16840
|
-
mode: 384
|
|
16841
|
-
});
|
|
16842
|
-
await rename6(tempPath, filePath);
|
|
16843
17218
|
}
|
|
16844
17219
|
function memoryFilePath(profileName, target) {
|
|
16845
17220
|
return path19.join(
|
|
@@ -17320,7 +17695,7 @@ function customProviderRegistryPath(profileName) {
|
|
|
17320
17695
|
async function readCustomProviderRegistry(profileName) {
|
|
17321
17696
|
const raw = await readFile14(customProviderRegistryPath(profileName), "utf8").catch(
|
|
17322
17697
|
(error) => {
|
|
17323
|
-
if (
|
|
17698
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
17324
17699
|
return "";
|
|
17325
17700
|
}
|
|
17326
17701
|
throw error;
|
|
@@ -17357,19 +17732,17 @@ async function saveCustomProviderRegistryEntry(profileName, provider) {
|
|
|
17357
17732
|
{ id: providerId, label: providerId, description: "\u81EA\u5B9A\u4E49 memory provider\u3002" }
|
|
17358
17733
|
].sort((left, right) => left.id.localeCompare(right.id));
|
|
17359
17734
|
const registryPath2 = customProviderRegistryPath(profileName);
|
|
17360
|
-
await
|
|
17361
|
-
await writeFile5(
|
|
17735
|
+
await atomicWriteFilePreservingMetadata(
|
|
17362
17736
|
registryPath2,
|
|
17363
17737
|
`${JSON.stringify({ providers }, null, 2)}
|
|
17364
|
-
|
|
17365
|
-
{ encoding: "utf8", mode: 384 }
|
|
17738
|
+
`
|
|
17366
17739
|
);
|
|
17367
17740
|
}
|
|
17368
17741
|
async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
17369
17742
|
const pluginsDir = path19.join(resolveHermesProfileDir(profileName), "plugins");
|
|
17370
|
-
const entries = await
|
|
17743
|
+
const entries = await readdir10(pluginsDir, { withFileTypes: true }).catch(
|
|
17371
17744
|
(error) => {
|
|
17372
|
-
if (
|
|
17745
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
17373
17746
|
return [];
|
|
17374
17747
|
}
|
|
17375
17748
|
throw error;
|
|
@@ -17410,7 +17783,7 @@ async function isUserMemoryProviderInstalled(profileName, provider) {
|
|
|
17410
17783
|
async function isMemoryProviderPluginDir(providerDir) {
|
|
17411
17784
|
const source = await readFile14(path19.join(providerDir, "__init__.py"), "utf8").catch(
|
|
17412
17785
|
(error) => {
|
|
17413
|
-
if (
|
|
17786
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
17414
17787
|
return "";
|
|
17415
17788
|
}
|
|
17416
17789
|
throw error;
|
|
@@ -17422,7 +17795,7 @@ async function isMemoryProviderPluginDir(providerDir) {
|
|
|
17422
17795
|
async function readPluginMetadata(providerDir) {
|
|
17423
17796
|
const raw = await readFile14(path19.join(providerDir, "plugin.yaml"), "utf8").catch(
|
|
17424
17797
|
(error) => {
|
|
17425
|
-
if (
|
|
17798
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
17426
17799
|
return "";
|
|
17427
17800
|
}
|
|
17428
17801
|
throw error;
|
|
@@ -17448,7 +17821,7 @@ async function resolveByteRoverCli() {
|
|
|
17448
17821
|
async function readHolographicProviderConfig(profileName) {
|
|
17449
17822
|
const raw = await readFile14(resolveHermesConfigPath(profileName), "utf8").catch(
|
|
17450
17823
|
(error) => {
|
|
17451
|
-
if (
|
|
17824
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
17452
17825
|
return "";
|
|
17453
17826
|
}
|
|
17454
17827
|
throw error;
|
|
@@ -17462,7 +17835,7 @@ async function patchHolographicProviderConfig(profileName, patch) {
|
|
|
17462
17835
|
const configPath = resolveHermesConfigPath(profileName);
|
|
17463
17836
|
const existingRaw = await readFile14(configPath, "utf8").catch(
|
|
17464
17837
|
(error) => {
|
|
17465
|
-
if (
|
|
17838
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
17466
17839
|
return null;
|
|
17467
17840
|
}
|
|
17468
17841
|
throw error;
|
|
@@ -17479,17 +17852,15 @@ async function patchHolographicProviderConfig(profileName, patch) {
|
|
|
17479
17852
|
}
|
|
17480
17853
|
plugins["hermes-memory-store"] = memoryStore;
|
|
17481
17854
|
config.plugins = plugins;
|
|
17482
|
-
await mkdir12(path19.dirname(configPath), { recursive: true, mode: 448 });
|
|
17483
17855
|
if (existingRaw) {
|
|
17484
|
-
await
|
|
17856
|
+
await atomicWriteFilePreservingMetadata(
|
|
17857
|
+
`${configPath}.bak.${Date.now()}`,
|
|
17858
|
+
existingRaw,
|
|
17859
|
+
{ metadataSourcePath: configPath }
|
|
17860
|
+
);
|
|
17485
17861
|
}
|
|
17486
17862
|
document.contents = document.createNode(config);
|
|
17487
|
-
|
|
17488
|
-
await writeFile5(tempPath, document.toString(), {
|
|
17489
|
-
encoding: "utf8",
|
|
17490
|
-
mode: 384
|
|
17491
|
-
});
|
|
17492
|
-
await rename6(tempPath, configPath);
|
|
17863
|
+
await atomicWriteFilePreservingMetadata(configPath, document.toString());
|
|
17493
17864
|
}
|
|
17494
17865
|
async function patchHermesMemoryEnv(profileName, patch) {
|
|
17495
17866
|
const entries = Object.entries(patch).filter(
|
|
@@ -17500,7 +17871,7 @@ async function patchHermesMemoryEnv(profileName, patch) {
|
|
|
17500
17871
|
}
|
|
17501
17872
|
const envPath = path19.join(resolveHermesProfileDir(profileName), ".env");
|
|
17502
17873
|
const existingRaw = await readFile14(envPath, "utf8").catch((error) => {
|
|
17503
|
-
if (
|
|
17874
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
17504
17875
|
return "";
|
|
17505
17876
|
}
|
|
17506
17877
|
throw error;
|
|
@@ -17530,13 +17901,14 @@ async function patchHermesMemoryEnv(profileName, patch) {
|
|
|
17530
17901
|
}
|
|
17531
17902
|
}
|
|
17532
17903
|
const nextRaw = nextLines.join("\n").replace(/\n*$/u, "\n");
|
|
17533
|
-
await mkdir12(path19.dirname(envPath), { recursive: true, mode: 448 });
|
|
17534
17904
|
if (existingRaw) {
|
|
17535
|
-
await
|
|
17905
|
+
await atomicWriteFilePreservingMetadata(
|
|
17906
|
+
`${envPath}.bak.${Date.now()}`,
|
|
17907
|
+
existingRaw,
|
|
17908
|
+
{ metadataSourcePath: envPath }
|
|
17909
|
+
);
|
|
17536
17910
|
}
|
|
17537
|
-
|
|
17538
|
-
await writeFile5(tempPath, nextRaw, { encoding: "utf8", mode: 384 });
|
|
17539
|
-
await rename6(tempPath, envPath);
|
|
17911
|
+
await atomicWriteFilePreservingMetadata(envPath, nextRaw);
|
|
17540
17912
|
}
|
|
17541
17913
|
function isMemoryEnvKeyWritable(key) {
|
|
17542
17914
|
return [
|
|
@@ -17557,7 +17929,7 @@ async function readActiveMemoryProvider(profileName) {
|
|
|
17557
17929
|
resolveHermesConfigPath(profileName),
|
|
17558
17930
|
"utf8"
|
|
17559
17931
|
).catch((error) => {
|
|
17560
|
-
if (
|
|
17932
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
17561
17933
|
return "";
|
|
17562
17934
|
}
|
|
17563
17935
|
throw error;
|
|
@@ -17582,16 +17954,15 @@ async function patchJsonProviderConfig(profileName, relativePath, patch) {
|
|
|
17582
17954
|
next[key] = value;
|
|
17583
17955
|
}
|
|
17584
17956
|
}
|
|
17585
|
-
await
|
|
17586
|
-
|
|
17587
|
-
|
|
17588
|
-
|
|
17589
|
-
|
|
17590
|
-
});
|
|
17957
|
+
await atomicWriteFilePreservingMetadata(
|
|
17958
|
+
configPath,
|
|
17959
|
+
`${JSON.stringify(next, null, 2)}
|
|
17960
|
+
`
|
|
17961
|
+
);
|
|
17591
17962
|
}
|
|
17592
17963
|
async function readJsonObject(filePath) {
|
|
17593
17964
|
const raw = await readFile14(filePath, "utf8").catch((error) => {
|
|
17594
|
-
if (
|
|
17965
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
17595
17966
|
return "{}";
|
|
17596
17967
|
}
|
|
17597
17968
|
throw error;
|
|
@@ -17645,7 +18016,7 @@ async function readMemoryLimits(profileName) {
|
|
|
17645
18016
|
resolveHermesConfigPath(profileName),
|
|
17646
18017
|
"utf8"
|
|
17647
18018
|
).catch((error) => {
|
|
17648
|
-
if (
|
|
18019
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
17649
18020
|
return "";
|
|
17650
18021
|
}
|
|
17651
18022
|
throw error;
|
|
@@ -17738,7 +18109,7 @@ function formatEnvValue3(value) {
|
|
|
17738
18109
|
function escapeRegExp3(value) {
|
|
17739
18110
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
17740
18111
|
}
|
|
17741
|
-
function
|
|
18112
|
+
function isNodeError16(error, code) {
|
|
17742
18113
|
return error instanceof Error && "code" in error && error.code === code;
|
|
17743
18114
|
}
|
|
17744
18115
|
|
|
@@ -18146,14 +18517,7 @@ function toMemoryHttpError(error) {
|
|
|
18146
18517
|
}
|
|
18147
18518
|
|
|
18148
18519
|
// src/hermes/skills.ts
|
|
18149
|
-
import {
|
|
18150
|
-
copyFile as copyFile4,
|
|
18151
|
-
mkdir as mkdir13,
|
|
18152
|
-
readFile as readFile15,
|
|
18153
|
-
readdir as readdir10,
|
|
18154
|
-
rename as rename7,
|
|
18155
|
-
writeFile as writeFile6
|
|
18156
|
-
} from "fs/promises";
|
|
18520
|
+
import { readFile as readFile15, readdir as readdir11 } from "fs/promises";
|
|
18157
18521
|
import path20 from "path";
|
|
18158
18522
|
import YAML5 from "yaml";
|
|
18159
18523
|
var HermesSkillNotFoundError = class extends Error {
|
|
@@ -18245,9 +18609,9 @@ async function findSkillFiles(root) {
|
|
|
18245
18609
|
return results.sort((left, right) => left.localeCompare(right));
|
|
18246
18610
|
}
|
|
18247
18611
|
async function collectSkillFiles(directory, results) {
|
|
18248
|
-
const entries = await
|
|
18612
|
+
const entries = await readdir11(directory, { withFileTypes: true }).catch(
|
|
18249
18613
|
(error) => {
|
|
18250
|
-
if (
|
|
18614
|
+
if (isNodeError17(error, "ENOENT")) {
|
|
18251
18615
|
return [];
|
|
18252
18616
|
}
|
|
18253
18617
|
throw error;
|
|
@@ -18272,7 +18636,7 @@ async function collectSkillFiles(directory, results) {
|
|
|
18272
18636
|
async function readSkillMetadata(input) {
|
|
18273
18637
|
const raw = await readFile15(input.skillFile, "utf8").catch(
|
|
18274
18638
|
(error) => {
|
|
18275
|
-
if (
|
|
18639
|
+
if (isNodeError17(error, "ENOENT") || isNodeError17(error, "EACCES")) {
|
|
18276
18640
|
return null;
|
|
18277
18641
|
}
|
|
18278
18642
|
throw error;
|
|
@@ -18349,7 +18713,7 @@ function normalizeDescription(value) {
|
|
|
18349
18713
|
}
|
|
18350
18714
|
async function readDisabledSkillNames(configPath) {
|
|
18351
18715
|
const raw = await readFile15(configPath, "utf8").catch((error) => {
|
|
18352
|
-
if (
|
|
18716
|
+
if (isNodeError17(error, "ENOENT")) {
|
|
18353
18717
|
return "";
|
|
18354
18718
|
}
|
|
18355
18719
|
throw error;
|
|
@@ -18374,7 +18738,7 @@ async function readSkillProvenance(root) {
|
|
|
18374
18738
|
async function readBundledSkillNames(root) {
|
|
18375
18739
|
const raw = await readFile15(path20.join(root, ".bundled_manifest"), "utf8").catch(
|
|
18376
18740
|
(error) => {
|
|
18377
|
-
if (
|
|
18741
|
+
if (isNodeError17(error, "ENOENT")) {
|
|
18378
18742
|
return "";
|
|
18379
18743
|
}
|
|
18380
18744
|
throw error;
|
|
@@ -18397,7 +18761,7 @@ async function readBundledSkillNames(root) {
|
|
|
18397
18761
|
async function readHubInstalledSkills(root) {
|
|
18398
18762
|
const raw = await readFile15(path20.join(root, ".hub", "lock.json"), "utf8").catch(
|
|
18399
18763
|
(error) => {
|
|
18400
|
-
if (
|
|
18764
|
+
if (isNodeError17(error, "ENOENT")) {
|
|
18401
18765
|
return "";
|
|
18402
18766
|
}
|
|
18403
18767
|
throw error;
|
|
@@ -18469,7 +18833,7 @@ function compareCategoryNames(left, right) {
|
|
|
18469
18833
|
async function readHermesConfigDocument2(configPath) {
|
|
18470
18834
|
const existingRaw = await readFile15(configPath, "utf8").catch(
|
|
18471
18835
|
(error) => {
|
|
18472
|
-
if (
|
|
18836
|
+
if (isNodeError17(error, "ENOENT")) {
|
|
18473
18837
|
return null;
|
|
18474
18838
|
}
|
|
18475
18839
|
throw error;
|
|
@@ -18484,14 +18848,16 @@ async function readHermesConfigDocument2(configPath) {
|
|
|
18484
18848
|
}
|
|
18485
18849
|
async function writeHermesConfigDocument2(input) {
|
|
18486
18850
|
const backupPath = input.existingRaw ? `${input.configPath}.bak.${Date.now()}` : null;
|
|
18487
|
-
await mkdir13(path20.dirname(input.configPath), { recursive: true, mode: 448 });
|
|
18488
18851
|
if (backupPath) {
|
|
18489
|
-
await
|
|
18852
|
+
await atomicWriteFilePreservingMetadata(backupPath, input.existingRaw, {
|
|
18853
|
+
metadataSourcePath: input.configPath
|
|
18854
|
+
});
|
|
18490
18855
|
}
|
|
18491
18856
|
input.document.contents = input.document.createNode(input.config);
|
|
18492
|
-
|
|
18493
|
-
|
|
18494
|
-
|
|
18857
|
+
await atomicWriteFilePreservingMetadata(
|
|
18858
|
+
input.configPath,
|
|
18859
|
+
input.document.toString()
|
|
18860
|
+
);
|
|
18495
18861
|
return backupPath;
|
|
18496
18862
|
}
|
|
18497
18863
|
function readStringList3(value) {
|
|
@@ -18514,7 +18880,7 @@ function ensureRecord3(target, key) {
|
|
|
18514
18880
|
target[key] = current;
|
|
18515
18881
|
return current;
|
|
18516
18882
|
}
|
|
18517
|
-
function
|
|
18883
|
+
function isNodeError17(error, code) {
|
|
18518
18884
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
18519
18885
|
}
|
|
18520
18886
|
|
|
@@ -18996,7 +19362,7 @@ function readModelList(payload) {
|
|
|
18996
19362
|
// src/hermes/updates.ts
|
|
18997
19363
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
18998
19364
|
import { spawn as spawn3 } from "child_process";
|
|
18999
|
-
import { mkdir as
|
|
19365
|
+
import { mkdir as mkdir10, readFile as readFile16, rm as rm8 } from "fs/promises";
|
|
19000
19366
|
import path21 from "path";
|
|
19001
19367
|
var SERVER_HERMES_RELEASES_LATEST_PATH = "/api/v1/hermes-agent/releases/latest";
|
|
19002
19368
|
var RELEASE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
@@ -19057,7 +19423,7 @@ async function startHermesUpdate(options) {
|
|
|
19057
19423
|
signal: null,
|
|
19058
19424
|
error: null
|
|
19059
19425
|
};
|
|
19060
|
-
await
|
|
19426
|
+
await mkdir10(options.paths.runDir, { recursive: true, mode: 448 });
|
|
19061
19427
|
await writer.write(`
|
|
19062
19428
|
=== hermes update started ${startedAt} ===
|
|
19063
19429
|
`);
|
|
@@ -19361,17 +19727,17 @@ function readString17(payload, key) {
|
|
|
19361
19727
|
// src/link/updates.ts
|
|
19362
19728
|
import { spawn as spawn5 } from "child_process";
|
|
19363
19729
|
import { EventEmitter as EventEmitter4 } from "events";
|
|
19364
|
-
import { mkdir as
|
|
19730
|
+
import { mkdir as mkdir13, readFile as readFile18, rm as rm11 } from "fs/promises";
|
|
19365
19731
|
import path23 from "path";
|
|
19366
19732
|
|
|
19367
19733
|
// src/daemon/process.ts
|
|
19368
19734
|
import { spawn as spawn4 } from "child_process";
|
|
19369
|
-
import { mkdir as
|
|
19735
|
+
import { mkdir as mkdir12, readFile as readFile17, rm as rm10 } from "fs/promises";
|
|
19370
19736
|
import path22 from "path";
|
|
19371
19737
|
|
|
19372
19738
|
// src/daemon/service.ts
|
|
19373
19739
|
import { createServer } from "http";
|
|
19374
|
-
import { mkdir as
|
|
19740
|
+
import { mkdir as mkdir11, rm as rm9, writeFile as writeFile3 } from "fs/promises";
|
|
19375
19741
|
|
|
19376
19742
|
// src/relay/control-client.ts
|
|
19377
19743
|
import WebSocket from "ws";
|
|
@@ -20093,6 +20459,7 @@ var DEFAULT_STARTUP_REPORT_MIN_INTERVAL_MS = 15 * 6e4;
|
|
|
20093
20459
|
function startLanIpMonitor(options) {
|
|
20094
20460
|
let running = false;
|
|
20095
20461
|
let closed = false;
|
|
20462
|
+
let current = Promise.resolve();
|
|
20096
20463
|
const check = async (context = {}) => {
|
|
20097
20464
|
if (running || closed) {
|
|
20098
20465
|
return;
|
|
@@ -20108,15 +20475,16 @@ function startLanIpMonitor(options) {
|
|
|
20108
20475
|
running = false;
|
|
20109
20476
|
}
|
|
20110
20477
|
};
|
|
20111
|
-
|
|
20478
|
+
current = check({ forceReport: true, publishToRelay: true });
|
|
20112
20479
|
const timer = setInterval(() => {
|
|
20113
|
-
|
|
20480
|
+
current = check();
|
|
20114
20481
|
}, options.intervalMs ?? DEFAULT_INTERVAL_MS);
|
|
20115
20482
|
timer.unref?.();
|
|
20116
20483
|
return {
|
|
20117
|
-
close() {
|
|
20484
|
+
async close() {
|
|
20118
20485
|
closed = true;
|
|
20119
20486
|
clearInterval(timer);
|
|
20487
|
+
await current.catch(() => void 0);
|
|
20120
20488
|
}
|
|
20121
20489
|
};
|
|
20122
20490
|
}
|
|
@@ -20186,6 +20554,7 @@ async function checkLanIpChange(options, context = {}) {
|
|
|
20186
20554
|
// src/daemon/scheduler.ts
|
|
20187
20555
|
function startCronDeliveryScheduler(options) {
|
|
20188
20556
|
let running = false;
|
|
20557
|
+
let current = Promise.resolve();
|
|
20189
20558
|
const syncCronDeliveries = async () => {
|
|
20190
20559
|
if (running) {
|
|
20191
20560
|
return;
|
|
@@ -20206,17 +20575,19 @@ function startCronDeliveryScheduler(options) {
|
|
|
20206
20575
|
}
|
|
20207
20576
|
};
|
|
20208
20577
|
const timer = setInterval(() => {
|
|
20209
|
-
|
|
20578
|
+
current = syncCronDeliveries();
|
|
20210
20579
|
}, options.intervalMs ?? 3e4);
|
|
20211
20580
|
timer.unref?.();
|
|
20212
20581
|
return {
|
|
20213
|
-
close() {
|
|
20582
|
+
async close() {
|
|
20214
20583
|
clearInterval(timer);
|
|
20584
|
+
await current.catch(() => void 0);
|
|
20215
20585
|
}
|
|
20216
20586
|
};
|
|
20217
20587
|
}
|
|
20218
20588
|
function startHermesSessionSyncScheduler(options) {
|
|
20219
20589
|
let running = false;
|
|
20590
|
+
let current = Promise.resolve();
|
|
20220
20591
|
const syncSessions = async () => {
|
|
20221
20592
|
if (running) {
|
|
20222
20593
|
return;
|
|
@@ -20233,12 +20604,13 @@ function startHermesSessionSyncScheduler(options) {
|
|
|
20233
20604
|
}
|
|
20234
20605
|
};
|
|
20235
20606
|
const timer = setInterval(() => {
|
|
20236
|
-
|
|
20607
|
+
current = syncSessions();
|
|
20237
20608
|
}, options.intervalMs ?? 10 * 60 * 1e3);
|
|
20238
20609
|
timer.unref?.();
|
|
20239
20610
|
return {
|
|
20240
|
-
close() {
|
|
20611
|
+
async close() {
|
|
20241
20612
|
clearInterval(timer);
|
|
20613
|
+
await current.catch(() => void 0);
|
|
20242
20614
|
}
|
|
20243
20615
|
};
|
|
20244
20616
|
}
|
|
@@ -20262,8 +20634,9 @@ async function startLinkService(options = {}) {
|
|
|
20262
20634
|
}
|
|
20263
20635
|
const conversations = new ConversationService(paths, logger);
|
|
20264
20636
|
await conversations.rebuildStatisticsIndex();
|
|
20637
|
+
let hermesSessionSync = Promise.resolve();
|
|
20265
20638
|
const triggerHermesSessionSync = () => {
|
|
20266
|
-
|
|
20639
|
+
hermesSessionSync = Promise.resolve().then(() => conversations.syncHermesSessions()).then(() => void 0).catch((error) => {
|
|
20267
20640
|
void logger.warn("hermes_session_sync_failed", {
|
|
20268
20641
|
error: error instanceof Error ? error.message : String(error)
|
|
20269
20642
|
});
|
|
@@ -20337,11 +20710,14 @@ async function startLinkService(options = {}) {
|
|
|
20337
20710
|
}
|
|
20338
20711
|
return {
|
|
20339
20712
|
async close() {
|
|
20340
|
-
scheduler.close();
|
|
20341
|
-
hermesSessionSyncScheduler.close();
|
|
20342
|
-
lanIpMonitor.close();
|
|
20343
20713
|
relay?.close();
|
|
20344
20714
|
await closeServer(server);
|
|
20715
|
+
await Promise.all([
|
|
20716
|
+
scheduler.close(),
|
|
20717
|
+
hermesSessionSyncScheduler.close(),
|
|
20718
|
+
lanIpMonitor.close(),
|
|
20719
|
+
hermesSessionSync.catch(() => void 0)
|
|
20720
|
+
]);
|
|
20345
20721
|
await logger.info("service_stopped");
|
|
20346
20722
|
await logger.flush();
|
|
20347
20723
|
if (options.writePidFile) {
|
|
@@ -20354,8 +20730,8 @@ function pidFilePath(paths = resolveRuntimePaths()) {
|
|
|
20354
20730
|
return `${paths.runDir}/hermeslink.pid`;
|
|
20355
20731
|
}
|
|
20356
20732
|
async function writePidFile(paths) {
|
|
20357
|
-
await
|
|
20358
|
-
await
|
|
20733
|
+
await mkdir11(paths.runDir, { recursive: true, mode: 448 });
|
|
20734
|
+
await writeFile3(pidFilePath(paths), `${process.pid}
|
|
20359
20735
|
`, { mode: 384 });
|
|
20360
20736
|
}
|
|
20361
20737
|
async function closeServer(server) {
|
|
@@ -20429,8 +20805,8 @@ async function startDaemonProcess(paths = resolveRuntimePaths()) {
|
|
|
20429
20805
|
return status;
|
|
20430
20806
|
}
|
|
20431
20807
|
}
|
|
20432
|
-
await
|
|
20433
|
-
await
|
|
20808
|
+
await mkdir12(paths.logsDir, { recursive: true, mode: 448 });
|
|
20809
|
+
await mkdir12(paths.runDir, { recursive: true, mode: 448 });
|
|
20434
20810
|
const scriptPath = currentCliScriptPath();
|
|
20435
20811
|
const child = spawn4(process.execPath, [scriptPath, "daemon-supervisor"], {
|
|
20436
20812
|
detached: true,
|
|
@@ -20448,7 +20824,7 @@ async function startDaemonProcess(paths = resolveRuntimePaths()) {
|
|
|
20448
20824
|
return await getDaemonStatus(paths);
|
|
20449
20825
|
}
|
|
20450
20826
|
async function runDaemonSupervisor(paths = resolveRuntimePaths()) {
|
|
20451
|
-
await
|
|
20827
|
+
await mkdir12(paths.logsDir, { recursive: true, mode: 448 });
|
|
20452
20828
|
const log = createRotatingTextLogWriter({
|
|
20453
20829
|
paths,
|
|
20454
20830
|
fileName: path22.basename(daemonLogFile(paths))
|
|
@@ -20692,7 +21068,7 @@ async function startLinkUpdate(options) {
|
|
|
20692
21068
|
error: null,
|
|
20693
21069
|
manual_command: manualCommand
|
|
20694
21070
|
};
|
|
20695
|
-
await
|
|
21071
|
+
await mkdir13(options.paths.runDir, { recursive: true, mode: 448 });
|
|
20696
21072
|
await writer.write(
|
|
20697
21073
|
`
|
|
20698
21074
|
=== link update started ${startedAt} target=${targetVersion} ===
|