@hermespilot/link 0.1.6 → 0.1.7
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 +8 -1
- package/dist/{chunk-VCQJ5DSN.js → chunk-SCIAZZ4C.js} +336 -138
- package/dist/chunk-SCIAZZ4C.js.map +1 -0
- package/dist/cli/index.js +16 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/http/app.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-VCQJ5DSN.js.map +0 -1
|
@@ -4,7 +4,7 @@ import Router from "@koa/router";
|
|
|
4
4
|
import { Readable } from "stream";
|
|
5
5
|
|
|
6
6
|
// src/constants.ts
|
|
7
|
-
var LINK_VERSION = "0.1.
|
|
7
|
+
var LINK_VERSION = "0.1.7";
|
|
8
8
|
var LINK_COMMAND = "hermeslink";
|
|
9
9
|
var LINK_DEFAULT_PORT = 52379;
|
|
10
10
|
var LINK_RUNTIME_DIR_NAME = ".hermeslink";
|
|
@@ -96,12 +96,30 @@ function normalizeConfiguredLanguage(language) {
|
|
|
96
96
|
|
|
97
97
|
// src/conversations/conversation-service.ts
|
|
98
98
|
import { EventEmitter } from "events";
|
|
99
|
-
import { appendFile, mkdir as
|
|
100
|
-
import
|
|
101
|
-
import { createHash, randomUUID
|
|
99
|
+
import { appendFile, mkdir as mkdir4, readdir, readFile as readFile3, rm as rm2, stat, writeFile as writeFile2 } from "fs/promises";
|
|
100
|
+
import path5 from "path";
|
|
101
|
+
import { createHash, randomUUID } from "crypto";
|
|
102
102
|
|
|
103
|
-
// src/
|
|
104
|
-
|
|
103
|
+
// src/http/errors.ts
|
|
104
|
+
var LinkHttpError = class extends Error {
|
|
105
|
+
constructor(status, code, message) {
|
|
106
|
+
super(message);
|
|
107
|
+
this.status = status;
|
|
108
|
+
this.code = code;
|
|
109
|
+
}
|
|
110
|
+
status;
|
|
111
|
+
code;
|
|
112
|
+
};
|
|
113
|
+
function isLinkHttpError(error) {
|
|
114
|
+
return error instanceof LinkHttpError;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/hermes/gateway.ts
|
|
118
|
+
import { execFile as execFile2, spawn } from "child_process";
|
|
119
|
+
import { mkdir as mkdir3, open as open2 } from "fs/promises";
|
|
120
|
+
import path4 from "path";
|
|
121
|
+
import { setTimeout as delay } from "timers/promises";
|
|
122
|
+
import { promisify as promisify2 } from "util";
|
|
105
123
|
|
|
106
124
|
// src/hermes/config.ts
|
|
107
125
|
import { randomBytes } from "crypto";
|
|
@@ -109,6 +127,8 @@ import { copyFile, mkdir as mkdir2, readFile as readFile2, writeFile } from "fs/
|
|
|
109
127
|
import os2 from "os";
|
|
110
128
|
import path3 from "path";
|
|
111
129
|
import YAML from "yaml";
|
|
130
|
+
var DEFAULT_HERMES_API_SERVER_HOST = "127.0.0.1";
|
|
131
|
+
var DEFAULT_HERMES_API_SERVER_PORT = 8642;
|
|
112
132
|
function resolveHermesProfileDir(profileName = "default") {
|
|
113
133
|
if (profileName === "default") {
|
|
114
134
|
return path3.join(os2.homedir(), ".hermes");
|
|
@@ -131,9 +151,12 @@ async function readHermesApiServerConfig(profileName = "default", configPath = r
|
|
|
131
151
|
const config = toRecord(YAML.parse(existingRaw));
|
|
132
152
|
const platforms = toRecord(config.platforms);
|
|
133
153
|
const apiServer = toRecord(platforms.api_server);
|
|
134
|
-
return readApiServerConfig(
|
|
154
|
+
return readApiServerConfig(apiServer, true);
|
|
135
155
|
}
|
|
136
156
|
async function ensureHermesApiServerKey(profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
|
|
157
|
+
return ensureHermesApiServerConfig(profileName, configPath);
|
|
158
|
+
}
|
|
159
|
+
async function ensureHermesApiServerConfig(profileName = "default", configPath = resolveHermesConfigPath(profileName)) {
|
|
137
160
|
const existingRaw = await readFile2(configPath, "utf8").catch((error) => {
|
|
138
161
|
if (isNodeError2(error, "ENOENT")) {
|
|
139
162
|
return null;
|
|
@@ -146,7 +169,28 @@ async function ensureHermesApiServerKey(profileName = "default", configPath = re
|
|
|
146
169
|
const apiServer = ensureRecord(platforms, "api_server");
|
|
147
170
|
const extra = ensureRecord(apiServer, "extra");
|
|
148
171
|
const beforeKey = typeof extra.key === "string" && extra.key.length > 0 ? extra.key : null;
|
|
172
|
+
const beforeEnabled = apiServer.enabled === true;
|
|
173
|
+
const beforeHost = typeof extra.host === "string" && extra.host.trim() ? extra.host : null;
|
|
174
|
+
const beforePort = typeof extra.port === "number" && Number.isFinite(extra.port) ? extra.port : null;
|
|
149
175
|
let changed = false;
|
|
176
|
+
let enabledAdded = false;
|
|
177
|
+
let hostAdded = false;
|
|
178
|
+
let portAdded = false;
|
|
179
|
+
if (!beforeEnabled) {
|
|
180
|
+
apiServer.enabled = true;
|
|
181
|
+
enabledAdded = true;
|
|
182
|
+
changed = true;
|
|
183
|
+
}
|
|
184
|
+
if (!beforeHost) {
|
|
185
|
+
extra.host = DEFAULT_HERMES_API_SERVER_HOST;
|
|
186
|
+
hostAdded = true;
|
|
187
|
+
changed = true;
|
|
188
|
+
}
|
|
189
|
+
if (!beforePort) {
|
|
190
|
+
extra.port = DEFAULT_HERMES_API_SERVER_PORT;
|
|
191
|
+
portAdded = true;
|
|
192
|
+
changed = true;
|
|
193
|
+
}
|
|
150
194
|
if (!beforeKey) {
|
|
151
195
|
extra.key = randomBytes(32).toString("base64url");
|
|
152
196
|
changed = true;
|
|
@@ -154,9 +198,12 @@ async function ensureHermesApiServerKey(profileName = "default", configPath = re
|
|
|
154
198
|
if (!changed) {
|
|
155
199
|
return {
|
|
156
200
|
configPath,
|
|
157
|
-
apiServer: readApiServerConfig(
|
|
201
|
+
apiServer: readApiServerConfig(apiServer, true),
|
|
158
202
|
changed: false,
|
|
159
203
|
keyAdded: false,
|
|
204
|
+
enabledAdded: false,
|
|
205
|
+
hostAdded: false,
|
|
206
|
+
portAdded: false,
|
|
160
207
|
backupPath: null,
|
|
161
208
|
notice: null
|
|
162
209
|
};
|
|
@@ -170,20 +217,44 @@ async function ensureHermesApiServerKey(profileName = "default", configPath = re
|
|
|
170
217
|
await writeFile(configPath, document.toString(), { mode: 384 });
|
|
171
218
|
return {
|
|
172
219
|
configPath,
|
|
173
|
-
apiServer: readApiServerConfig(
|
|
220
|
+
apiServer: readApiServerConfig(apiServer, true),
|
|
174
221
|
changed: true,
|
|
175
|
-
keyAdded:
|
|
222
|
+
keyAdded: !beforeKey,
|
|
223
|
+
enabledAdded,
|
|
224
|
+
hostAdded,
|
|
225
|
+
portAdded,
|
|
176
226
|
backupPath,
|
|
177
|
-
notice:
|
|
227
|
+
notice: buildNotice({ keyAdded: !beforeKey, enabledAdded, hostAdded, portAdded })
|
|
178
228
|
};
|
|
179
229
|
}
|
|
180
|
-
function readApiServerConfig(
|
|
230
|
+
function readApiServerConfig(apiServerOrExtra, withDefaults = false) {
|
|
231
|
+
const apiServer = "extra" in apiServerOrExtra ? apiServerOrExtra : {};
|
|
232
|
+
const extra = toRecord("extra" in apiServerOrExtra ? apiServerOrExtra.extra : apiServerOrExtra);
|
|
233
|
+
const port = typeof extra.port === "number" ? extra.port : void 0;
|
|
234
|
+
const host = typeof extra.host === "string" ? extra.host : void 0;
|
|
181
235
|
return {
|
|
182
|
-
|
|
183
|
-
|
|
236
|
+
enabled: apiServer.enabled === true,
|
|
237
|
+
host: withDefaults ? host ?? DEFAULT_HERMES_API_SERVER_HOST : host,
|
|
238
|
+
port: withDefaults ? port ?? DEFAULT_HERMES_API_SERVER_PORT : port,
|
|
184
239
|
key: typeof extra.key === "string" ? extra.key : void 0
|
|
185
240
|
};
|
|
186
241
|
}
|
|
242
|
+
function buildNotice(flags) {
|
|
243
|
+
const fields = [];
|
|
244
|
+
if (flags.enabledAdded) {
|
|
245
|
+
fields.push("enabled");
|
|
246
|
+
}
|
|
247
|
+
if (flags.hostAdded) {
|
|
248
|
+
fields.push("host=127.0.0.1");
|
|
249
|
+
}
|
|
250
|
+
if (flags.portAdded) {
|
|
251
|
+
fields.push("port=8642");
|
|
252
|
+
}
|
|
253
|
+
if (flags.keyAdded) {
|
|
254
|
+
fields.push("key");
|
|
255
|
+
}
|
|
256
|
+
return `\u5DF2\u4E3A Hermes API Server \u81EA\u52A8\u8865\u5145 ${fields.join("\u3001")}\uFF1B\u672A\u8986\u76D6\u5DF2\u6709 port/host/key\u3002`;
|
|
257
|
+
}
|
|
187
258
|
function toRecord(value) {
|
|
188
259
|
return typeof value === "object" && value !== null ? value : {};
|
|
189
260
|
}
|
|
@@ -200,22 +271,199 @@ function isNodeError2(error, code) {
|
|
|
200
271
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
201
272
|
}
|
|
202
273
|
|
|
203
|
-
// src/
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
274
|
+
// src/hermes/cli.ts
|
|
275
|
+
import { execFile } from "child_process";
|
|
276
|
+
import { promisify } from "util";
|
|
277
|
+
var execFileAsync = promisify(execFile);
|
|
278
|
+
async function deleteHermesSession(sessionId) {
|
|
279
|
+
if (!sessionId.trim()) {
|
|
280
|
+
throw new LinkHttpError(400, "hermes_session_id_required", "Hermes session id is required");
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
await execFileAsync(resolveHermesBin(), ["sessions", "delete", sessionId, "--yes"], {
|
|
284
|
+
timeout: 1e4,
|
|
285
|
+
windowsHide: true
|
|
286
|
+
});
|
|
287
|
+
} catch (error) {
|
|
288
|
+
throw new LinkHttpError(
|
|
289
|
+
502,
|
|
290
|
+
"hermes_session_delete_failed",
|
|
291
|
+
error instanceof Error ? error.message : "Hermes session delete failed"
|
|
292
|
+
);
|
|
209
293
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
294
|
+
}
|
|
295
|
+
function resolveHermesBin() {
|
|
296
|
+
return process.env.HERMES_BIN?.trim() || "hermes";
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/hermes/gateway.ts
|
|
300
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
301
|
+
var DEFAULT_START_TIMEOUT_MS = 12e3;
|
|
302
|
+
var HEALTH_TIMEOUT_MS = 1500;
|
|
303
|
+
var MIN_API_SERVER_VERSION = "0.4.0";
|
|
304
|
+
var gatewayStartInFlight = null;
|
|
305
|
+
async function ensureHermesApiServerAvailable(options = {}) {
|
|
306
|
+
const configResult = await ensureHermesApiServerConfig();
|
|
307
|
+
const fetcher = options.fetchImpl ?? fetch;
|
|
308
|
+
if (await isHermesApiHealthy(configResult.apiServer, fetcher)) {
|
|
309
|
+
return { available: true, configResult, started: false };
|
|
310
|
+
}
|
|
311
|
+
if (!shouldAutoStart(options.autoStart)) {
|
|
312
|
+
throw new LinkHttpError(503, "hermes_api_server_unavailable", unavailableMessage());
|
|
313
|
+
}
|
|
314
|
+
const start = await startHermesGatewayOnce(options.paths ?? resolveRuntimePaths());
|
|
315
|
+
const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_START_TIMEOUT_MS);
|
|
316
|
+
while (Date.now() < deadline) {
|
|
317
|
+
if (await isHermesApiHealthy(configResult.apiServer, fetcher)) {
|
|
318
|
+
return { available: true, configResult, started: true, start };
|
|
319
|
+
}
|
|
320
|
+
await delay(400);
|
|
321
|
+
}
|
|
322
|
+
throw new LinkHttpError(
|
|
323
|
+
503,
|
|
324
|
+
"hermes_api_server_unavailable",
|
|
325
|
+
`${unavailableMessage()} Link tried to start it with \`${resolveHermesBin()} gateway run --replace\`, but it did not become ready. Gateway log: ${start.logPath}`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
async function readHermesVersion() {
|
|
329
|
+
const { stdout } = await execHermesVersion();
|
|
330
|
+
const raw = stdout.trim();
|
|
331
|
+
const version = parseHermesVersion(raw);
|
|
332
|
+
return {
|
|
333
|
+
raw,
|
|
334
|
+
version,
|
|
335
|
+
supportsApiServer: version ? compareSemver(version, MIN_API_SERVER_VERSION) >= 0 : null
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
async function execHermesVersion() {
|
|
339
|
+
try {
|
|
340
|
+
return await execFileAsync2(resolveHermesBin(), ["version"], {
|
|
341
|
+
timeout: 5e3,
|
|
342
|
+
windowsHide: true
|
|
343
|
+
});
|
|
344
|
+
} catch {
|
|
345
|
+
return await execFileAsync2(resolveHermesBin(), ["--version"], {
|
|
346
|
+
timeout: 5e3,
|
|
347
|
+
windowsHide: true
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function assertHermesRunsApiSupported(version, status) {
|
|
352
|
+
if (status !== 404) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const versionText = version?.version ? `\u5F53\u524D\u68C0\u6D4B\u5230 Hermes Agent ${version.version}\u3002` : "";
|
|
356
|
+
throw new LinkHttpError(
|
|
357
|
+
502,
|
|
358
|
+
"hermes_runs_api_unsupported",
|
|
359
|
+
`${versionText}\u5F53\u524D Hermes Agent API Server \u4E0D\u652F\u6301 HermesPilot \u9700\u8981\u7684 /v1/runs \u63A5\u53E3\uFF0C\u8BF7\u5148\u8FD0\u884C \`hermes update\` \u5347\u7EA7 Hermes Agent\u3002`
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
async function startHermesGatewayOnce(paths) {
|
|
363
|
+
if (!gatewayStartInFlight) {
|
|
364
|
+
gatewayStartInFlight = startHermesGateway(paths).finally(() => {
|
|
365
|
+
gatewayStartInFlight = null;
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
return await gatewayStartInFlight;
|
|
369
|
+
}
|
|
370
|
+
async function startHermesGateway(paths) {
|
|
371
|
+
const version = await readHermesVersion().catch((error) => {
|
|
372
|
+
throw new LinkHttpError(
|
|
373
|
+
503,
|
|
374
|
+
"hermes_agent_not_available",
|
|
375
|
+
`\u6CA1\u6709\u627E\u5230\u53EF\u7528\u7684 Hermes Agent CLI\u3002\u8BF7\u5148\u5B89\u88C5 Hermes Agent\uFF0C\u6216\u8BBE\u7F6E HERMES_BIN\u3002${formatErrorSuffix(error)}`
|
|
376
|
+
);
|
|
377
|
+
});
|
|
378
|
+
if (version.supportsApiServer === false) {
|
|
379
|
+
throw new LinkHttpError(
|
|
380
|
+
503,
|
|
381
|
+
"hermes_agent_too_old",
|
|
382
|
+
`\u5F53\u524D Hermes Agent ${version.version} \u592A\u65E7\uFF0C\u53EF\u80FD\u4E0D\u652F\u6301 Hermes API Server\u3002\u8BF7\u5148\u8FD0\u884C \`hermes update\` \u5347\u7EA7\u3002`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
await mkdir3(paths.logsDir, { recursive: true, mode: 448 });
|
|
386
|
+
const logPath = path4.join(paths.logsDir, "hermes-gateway.log");
|
|
387
|
+
const logFile = await open2(logPath, "a", 384);
|
|
388
|
+
try {
|
|
389
|
+
const child = spawn(resolveHermesBin(), ["gateway", "run", "--replace"], {
|
|
390
|
+
detached: true,
|
|
391
|
+
env: process.env,
|
|
392
|
+
stdio: ["ignore", logFile.fd, logFile.fd],
|
|
393
|
+
windowsHide: true
|
|
394
|
+
});
|
|
395
|
+
await new Promise((resolve, reject) => {
|
|
396
|
+
let settled = false;
|
|
397
|
+
const finish = (callback) => {
|
|
398
|
+
if (settled) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
settled = true;
|
|
402
|
+
callback();
|
|
403
|
+
};
|
|
404
|
+
child.once("spawn", () => finish(resolve));
|
|
405
|
+
child.once("error", (error) => finish(() => reject(error)));
|
|
406
|
+
});
|
|
407
|
+
child.unref();
|
|
408
|
+
return { pid: child.pid ?? null, logPath, version };
|
|
409
|
+
} finally {
|
|
410
|
+
await logFile.close().catch(() => void 0);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function isHermesApiHealthy(config, fetcher) {
|
|
414
|
+
const response = await fetchWithTimeout(`http://127.0.0.1:${config.port}/health`, {
|
|
415
|
+
method: "GET",
|
|
416
|
+
headers: authHeaders(config)
|
|
417
|
+
}, fetcher);
|
|
418
|
+
return Boolean(response?.ok);
|
|
419
|
+
}
|
|
420
|
+
function fetchWithTimeout(input, init, fetcher) {
|
|
421
|
+
const controller = new AbortController();
|
|
422
|
+
const timer = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS);
|
|
423
|
+
return fetcher(input, { ...init, signal: controller.signal }).catch(() => null).finally(() => clearTimeout(timer));
|
|
424
|
+
}
|
|
425
|
+
function authHeaders(config) {
|
|
426
|
+
const headers = new Headers();
|
|
427
|
+
headers.set("accept", "application/json");
|
|
428
|
+
if (config.key) {
|
|
429
|
+
headers.set("x-api-key", config.key);
|
|
430
|
+
headers.set("authorization", `Bearer ${config.key}`);
|
|
431
|
+
}
|
|
432
|
+
return headers;
|
|
433
|
+
}
|
|
434
|
+
function shouldAutoStart(value) {
|
|
435
|
+
if (value !== void 0) {
|
|
436
|
+
return value;
|
|
437
|
+
}
|
|
438
|
+
const raw = process.env.HERMESLINK_GATEWAY_AUTOSTART?.trim().toLowerCase();
|
|
439
|
+
return raw !== "0" && raw !== "false" && raw !== "off";
|
|
440
|
+
}
|
|
441
|
+
function unavailableMessage() {
|
|
442
|
+
return "Hermes API Server \u5F53\u524D\u4E0D\u53EF\u7528\u3002\u8BF7\u786E\u8BA4 Hermes Agent \u5DF2\u5B89\u88C5\uFF0C\u5E76\u53EF\u901A\u8FC7 `hermes gateway run` \u542F\u52A8\uFF1BHermesPilot Link \u9700\u8981 Hermes Agent \u7684\u672C\u673A API Server\u3002";
|
|
443
|
+
}
|
|
444
|
+
function parseHermesVersion(value) {
|
|
445
|
+
const match = /\bv?(\d+\.\d+\.\d+)\b/u.exec(value);
|
|
446
|
+
return match?.[1] ?? null;
|
|
447
|
+
}
|
|
448
|
+
function compareSemver(left, right) {
|
|
449
|
+
const leftParts = left.split(".").map((part) => Number.parseInt(part, 10));
|
|
450
|
+
const rightParts = right.split(".").map((part) => Number.parseInt(part, 10));
|
|
451
|
+
for (let index = 0; index < 3; index += 1) {
|
|
452
|
+
const diff = (leftParts[index] || 0) - (rightParts[index] || 0);
|
|
453
|
+
if (diff !== 0) {
|
|
454
|
+
return diff;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return 0;
|
|
458
|
+
}
|
|
459
|
+
function formatErrorSuffix(error) {
|
|
460
|
+
if (!(error instanceof Error) || !error.message) {
|
|
461
|
+
return "";
|
|
462
|
+
}
|
|
463
|
+
return ` \u539F\u59CB\u9519\u8BEF\uFF1A${error.message}`;
|
|
215
464
|
}
|
|
216
465
|
|
|
217
466
|
// src/hermes/api-server.ts
|
|
218
|
-
var fallbackRuns = /* @__PURE__ */ new Map();
|
|
219
467
|
async function listHermesModels(options = {}) {
|
|
220
468
|
const response = await callHermesApi("/v1/models", { method: "GET" }, options);
|
|
221
469
|
if (response.status === 404) {
|
|
@@ -234,9 +482,8 @@ async function createHermesRun(input, options = {}) {
|
|
|
234
482
|
options
|
|
235
483
|
);
|
|
236
484
|
if (response.status === 404 || response.status === 503) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
return { run_id: runId2, fallback: true };
|
|
485
|
+
assertHermesRunsApiSupported(await readHermesVersion().catch(() => null), response.status);
|
|
486
|
+
throw new LinkHttpError(503, "hermes_api_server_unavailable", "Hermes API Server is unavailable");
|
|
240
487
|
}
|
|
241
488
|
const payload = await readJsonResponse(response);
|
|
242
489
|
const runId = readString(payload, "run_id") ?? readString(payload, "runId") ?? readString(payload, "id");
|
|
@@ -246,17 +493,8 @@ async function createHermesRun(input, options = {}) {
|
|
|
246
493
|
return { run_id: runId, fallback: false };
|
|
247
494
|
}
|
|
248
495
|
async function streamHermesRunEvents(runId, options = {}) {
|
|
249
|
-
const fallback = fallbackRuns.get(runId);
|
|
250
|
-
if (fallback) {
|
|
251
|
-
fallbackRuns.delete(runId);
|
|
252
|
-
return new Response(createFallbackSseStream(fallback.input), {
|
|
253
|
-
headers: {
|
|
254
|
-
"content-type": "text/event-stream; charset=utf-8",
|
|
255
|
-
"cache-control": "no-store"
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
496
|
const response = await callHermesApi(`/v1/runs/${encodeURIComponent(runId)}/events`, { method: "GET" }, options);
|
|
497
|
+
assertHermesRunsApiSupported(await readHermesVersion().catch(() => null), response.status);
|
|
260
498
|
if (!response.ok || !response.body) {
|
|
261
499
|
throw new LinkHttpError(502, "hermes_events_unavailable", "Hermes run event stream is unavailable");
|
|
262
500
|
}
|
|
@@ -277,22 +515,23 @@ async function cancelHermesRun(runId, options = {}) {
|
|
|
277
515
|
if (!response.ok && response.status !== 404) {
|
|
278
516
|
throw new LinkHttpError(502, "hermes_cancel_failed", "Hermes run cancel failed");
|
|
279
517
|
}
|
|
280
|
-
fallbackRuns.delete(runId);
|
|
281
518
|
}
|
|
282
|
-
async function callHermesApi(
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
return new Response(null, { status: 503 });
|
|
286
|
-
}
|
|
519
|
+
async function callHermesApi(path9, init, options) {
|
|
520
|
+
const availability = await ensureHermesApiServerAvailable({ fetchImpl: options.fetchImpl });
|
|
521
|
+
const config = availability.configResult.apiServer;
|
|
287
522
|
const fetcher = options.fetchImpl ?? fetch;
|
|
288
523
|
const headers = new Headers(init.headers);
|
|
289
524
|
headers.set("accept", headers.get("accept") ?? "application/json");
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
525
|
+
if (config.key) {
|
|
526
|
+
headers.set("x-api-key", config.key);
|
|
527
|
+
headers.set("authorization", `Bearer ${config.key}`);
|
|
528
|
+
}
|
|
529
|
+
return await fetcher(`http://127.0.0.1:${config.port}${path9}`, {
|
|
293
530
|
...init,
|
|
294
531
|
headers
|
|
295
|
-
}).catch(() =>
|
|
532
|
+
}).catch(() => {
|
|
533
|
+
throw new LinkHttpError(503, "hermes_api_server_unavailable", "Hermes API Server is unavailable");
|
|
534
|
+
});
|
|
296
535
|
}
|
|
297
536
|
async function readJsonResponse(response) {
|
|
298
537
|
const payload = await response.json().catch(() => null);
|
|
@@ -301,53 +540,11 @@ async function readJsonResponse(response) {
|
|
|
301
540
|
}
|
|
302
541
|
return payload;
|
|
303
542
|
}
|
|
304
|
-
function createFallbackSseStream(input) {
|
|
305
|
-
const encoder = new TextEncoder();
|
|
306
|
-
return new ReadableStream({
|
|
307
|
-
start(controller) {
|
|
308
|
-
const message = `Hermes API Server is not running yet. Link received: ${input}`;
|
|
309
|
-
controller.enqueue(encoder.encode(`event: message.delta
|
|
310
|
-
data: ${JSON.stringify({ type: "message.delta", delta: message })}
|
|
311
|
-
|
|
312
|
-
`));
|
|
313
|
-
controller.enqueue(encoder.encode(`event: run.completed
|
|
314
|
-
data: ${JSON.stringify({ type: "run.completed" })}
|
|
315
|
-
|
|
316
|
-
`));
|
|
317
|
-
controller.close();
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
543
|
function readString(payload, key) {
|
|
322
544
|
const value = payload[key];
|
|
323
545
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
324
546
|
}
|
|
325
547
|
|
|
326
|
-
// src/hermes/cli.ts
|
|
327
|
-
import { execFile } from "child_process";
|
|
328
|
-
import { promisify } from "util";
|
|
329
|
-
var execFileAsync = promisify(execFile);
|
|
330
|
-
async function deleteHermesSession(sessionId) {
|
|
331
|
-
if (!sessionId.trim()) {
|
|
332
|
-
throw new LinkHttpError(400, "hermes_session_id_required", "Hermes session id is required");
|
|
333
|
-
}
|
|
334
|
-
try {
|
|
335
|
-
await execFileAsync(hermesBin(), ["sessions", "delete", sessionId, "--yes"], {
|
|
336
|
-
timeout: 1e4,
|
|
337
|
-
windowsHide: true
|
|
338
|
-
});
|
|
339
|
-
} catch (error) {
|
|
340
|
-
throw new LinkHttpError(
|
|
341
|
-
502,
|
|
342
|
-
"hermes_session_delete_failed",
|
|
343
|
-
error instanceof Error ? error.message : "Hermes session delete failed"
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
function hermesBin() {
|
|
348
|
-
return process.env.HERMES_BIN?.trim() || "hermes";
|
|
349
|
-
}
|
|
350
|
-
|
|
351
548
|
// src/conversations/conversation-service.ts
|
|
352
549
|
var MAX_IMPORTED_BLOB_BYTES = 100 * 1024 * 1024;
|
|
353
550
|
var MAX_UPLOADED_BLOB_BYTES = 50 * 1024 * 1024;
|
|
@@ -364,7 +561,7 @@ var ConversationService = class {
|
|
|
364
561
|
logger;
|
|
365
562
|
emitter = new EventEmitter();
|
|
366
563
|
async listConversations() {
|
|
367
|
-
await
|
|
564
|
+
await mkdir4(this.paths.conversationsDir, { recursive: true, mode: 448 });
|
|
368
565
|
const entries = await readdir(this.paths.conversationsDir, { withFileTypes: true }).catch((error) => {
|
|
369
566
|
if (isNodeError3(error, "ENOENT")) {
|
|
370
567
|
return [];
|
|
@@ -386,9 +583,9 @@ var ConversationService = class {
|
|
|
386
583
|
return summaries.sort((left, right) => Date.parse(right.updated_at) - Date.parse(left.updated_at));
|
|
387
584
|
}
|
|
388
585
|
async createConversation(input = {}) {
|
|
389
|
-
await
|
|
586
|
+
await mkdir4(this.paths.conversationsDir, { recursive: true, mode: 448 });
|
|
390
587
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
391
|
-
const id = `conv_${
|
|
588
|
+
const id = `conv_${randomUUID().replaceAll("-", "")}`;
|
|
392
589
|
const title = input.title?.trim() || "Untitled";
|
|
393
590
|
const manifest = {
|
|
394
591
|
id,
|
|
@@ -401,7 +598,7 @@ var ConversationService = class {
|
|
|
401
598
|
updated_at: now,
|
|
402
599
|
last_event_seq: 0
|
|
403
600
|
};
|
|
404
|
-
await
|
|
601
|
+
await mkdir4(this.conversationDir(id), { recursive: true, mode: 448 });
|
|
405
602
|
await this.writeManifest(manifest);
|
|
406
603
|
await this.writeSnapshot(id, emptySnapshot());
|
|
407
604
|
const event = await this.appendEvent(manifest.id, {
|
|
@@ -462,7 +659,7 @@ var ConversationService = class {
|
|
|
462
659
|
}
|
|
463
660
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
464
661
|
const userMessage = {
|
|
465
|
-
id: `msg_${
|
|
662
|
+
id: `msg_${randomUUID().replaceAll("-", "")}`,
|
|
466
663
|
schema_version: 1,
|
|
467
664
|
conversation_id: manifest.id,
|
|
468
665
|
role: "user",
|
|
@@ -481,7 +678,7 @@ var ConversationService = class {
|
|
|
481
678
|
raw: { format: "hermes-link-user-message", payload: { content, attachments: input.attachments ?? [] } }
|
|
482
679
|
};
|
|
483
680
|
const assistantMessage = {
|
|
484
|
-
id: `msg_${
|
|
681
|
+
id: `msg_${randomUUID().replaceAll("-", "")}`,
|
|
485
682
|
schema_version: 1,
|
|
486
683
|
conversation_id: manifest.id,
|
|
487
684
|
role: "assistant",
|
|
@@ -493,7 +690,7 @@ var ConversationService = class {
|
|
|
493
690
|
attachments: []
|
|
494
691
|
};
|
|
495
692
|
const run = {
|
|
496
|
-
id: `run_${
|
|
693
|
+
id: `run_${randomUUID().replaceAll("-", "")}`,
|
|
497
694
|
conversation_id: manifest.id,
|
|
498
695
|
trigger_message_id: userMessage.id,
|
|
499
696
|
assistant_message_id: assistantMessage.id,
|
|
@@ -563,9 +760,9 @@ var ConversationService = class {
|
|
|
563
760
|
if (input.bytes.byteLength > MAX_UPLOADED_BLOB_BYTES) {
|
|
564
761
|
throw new LinkHttpError(413, "blob_too_large", "Blob is too large");
|
|
565
762
|
}
|
|
566
|
-
const id = `blob_${
|
|
763
|
+
const id = `blob_${randomUUID().replaceAll("-", "")}`;
|
|
567
764
|
const filePath = this.blobPath(id);
|
|
568
|
-
await
|
|
765
|
+
await mkdir4(path5.dirname(filePath), { recursive: true, mode: 448 });
|
|
569
766
|
await writeFile2(filePath, input.bytes, { mode: 384 });
|
|
570
767
|
const manifestPath = `${filePath}.json`;
|
|
571
768
|
const blob = {
|
|
@@ -665,7 +862,7 @@ ${attachmentLines.join("\n")}`;
|
|
|
665
862
|
}
|
|
666
863
|
return this.writeBlob(conversationId, {
|
|
667
864
|
bytes: await readFile3(sourcePath),
|
|
668
|
-
filename:
|
|
865
|
+
filename: path5.basename(sourcePath),
|
|
669
866
|
mime: source.mime ?? inferMimeType(sourcePath)
|
|
670
867
|
});
|
|
671
868
|
}
|
|
@@ -916,7 +1113,7 @@ ${attachmentLines.join("\n")}`;
|
|
|
916
1113
|
conversation_id: conversationId,
|
|
917
1114
|
created_at: now
|
|
918
1115
|
};
|
|
919
|
-
await
|
|
1116
|
+
await mkdir4(this.conversationDir(conversationId), { recursive: true, mode: 448 });
|
|
920
1117
|
await appendFile(this.eventsPath(conversationId), `${JSON.stringify(event)}
|
|
921
1118
|
`, { mode: 384 });
|
|
922
1119
|
await this.writeManifest({
|
|
@@ -956,20 +1153,20 @@ ${attachmentLines.join("\n")}`;
|
|
|
956
1153
|
}
|
|
957
1154
|
conversationDir(conversationId) {
|
|
958
1155
|
assertValidConversationId(conversationId);
|
|
959
|
-
return
|
|
1156
|
+
return path5.join(this.paths.conversationsDir, conversationId);
|
|
960
1157
|
}
|
|
961
1158
|
manifestPath(conversationId) {
|
|
962
|
-
return
|
|
1159
|
+
return path5.join(this.conversationDir(conversationId), "manifest.json");
|
|
963
1160
|
}
|
|
964
1161
|
snapshotPath(conversationId) {
|
|
965
|
-
return
|
|
1162
|
+
return path5.join(this.conversationDir(conversationId), "snapshot.json");
|
|
966
1163
|
}
|
|
967
1164
|
eventsPath(conversationId) {
|
|
968
|
-
return
|
|
1165
|
+
return path5.join(this.conversationDir(conversationId), "events.ndjson");
|
|
969
1166
|
}
|
|
970
1167
|
blobPath(blobId) {
|
|
971
1168
|
assertValidBlobId(blobId);
|
|
972
|
-
return
|
|
1169
|
+
return path5.join(this.paths.blobsDir, `${blobId}.bin`);
|
|
973
1170
|
}
|
|
974
1171
|
liveEventName(conversationId) {
|
|
975
1172
|
return `conversation:${conversationId}`;
|
|
@@ -996,7 +1193,7 @@ ${attachmentLines.join("\n")}`;
|
|
|
996
1193
|
}
|
|
997
1194
|
}
|
|
998
1195
|
async listConversationBlobIds(conversationId) {
|
|
999
|
-
await
|
|
1196
|
+
await mkdir4(this.paths.blobsDir, { recursive: true, mode: 448 });
|
|
1000
1197
|
const entries = await readdir(this.paths.blobsDir, { withFileTypes: true }).catch((error) => {
|
|
1001
1198
|
if (isNodeError3(error, "ENOENT")) {
|
|
1002
1199
|
return [];
|
|
@@ -1013,7 +1210,7 @@ ${attachmentLines.join("\n")}`;
|
|
|
1013
1210
|
continue;
|
|
1014
1211
|
}
|
|
1015
1212
|
const manifest = await readJsonFile(
|
|
1016
|
-
|
|
1213
|
+
path5.join(this.paths.blobsDir, entry.name)
|
|
1017
1214
|
).catch(() => null);
|
|
1018
1215
|
if (manifest?.conversation_ids?.includes(conversationId)) {
|
|
1019
1216
|
blobIds.push(blobId);
|
|
@@ -1199,7 +1396,7 @@ function isExplicitMediaPathKey(key) {
|
|
|
1199
1396
|
].includes(key);
|
|
1200
1397
|
}
|
|
1201
1398
|
function inferMimeType(filePath) {
|
|
1202
|
-
const extension =
|
|
1399
|
+
const extension = path5.extname(filePath).toLowerCase();
|
|
1203
1400
|
return {
|
|
1204
1401
|
".png": "image/png",
|
|
1205
1402
|
".jpg": "image/jpeg",
|
|
@@ -1261,15 +1458,15 @@ function mediaSourceKey(sourcePath) {
|
|
|
1261
1458
|
return createHash("sha256").update(resolveMediaSourcePath(sourcePath)).digest("hex").slice(0, 32);
|
|
1262
1459
|
}
|
|
1263
1460
|
function sanitizeFilename(value, fallback) {
|
|
1264
|
-
const base =
|
|
1461
|
+
const base = path5.basename((value ?? "").replace(/[\r\n\t]/gu, " ").trim());
|
|
1265
1462
|
const safe = base.replace(/[/:\\]/gu, "_").slice(0, 200).trim();
|
|
1266
1463
|
return safe || fallback;
|
|
1267
1464
|
}
|
|
1268
1465
|
function resolveMediaSourcePath(sourcePath) {
|
|
1269
1466
|
const trimmed = sourcePath.trim();
|
|
1270
|
-
const expanded = trimmed.startsWith("~/") ?
|
|
1271
|
-
const resolved =
|
|
1272
|
-
if (!
|
|
1467
|
+
const expanded = trimmed.startsWith("~/") ? path5.join(process.env.HOME ?? "", trimmed.slice(2)) : trimmed;
|
|
1468
|
+
const resolved = path5.resolve(expanded);
|
|
1469
|
+
if (!path5.isAbsolute(expanded)) {
|
|
1273
1470
|
throw new LinkHttpError(400, "media_source_path_not_absolute", "Hermes output media source must be an absolute path");
|
|
1274
1471
|
}
|
|
1275
1472
|
return resolved;
|
|
@@ -1296,16 +1493,16 @@ function isNodeError3(error, code) {
|
|
|
1296
1493
|
}
|
|
1297
1494
|
|
|
1298
1495
|
// src/hermes/profiles.ts
|
|
1299
|
-
import { mkdir as
|
|
1496
|
+
import { mkdir as mkdir5, readdir as readdir2, rename as rename2, rm as rm3, stat as stat2 } from "fs/promises";
|
|
1300
1497
|
import os3 from "os";
|
|
1301
|
-
import
|
|
1498
|
+
import path6 from "path";
|
|
1302
1499
|
var DEFAULT_PROFILE = "default";
|
|
1303
1500
|
var PROFILE_NAME_PATTERN = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
1304
1501
|
async function listHermesProfiles(paths = resolveRuntimePaths()) {
|
|
1305
1502
|
const activeProfile = await getActiveProfile(paths);
|
|
1306
1503
|
const profiles = /* @__PURE__ */ new Map();
|
|
1307
1504
|
profiles.set(DEFAULT_PROFILE, profileInfo(DEFAULT_PROFILE, activeProfile));
|
|
1308
|
-
const profilesDir =
|
|
1505
|
+
const profilesDir = path6.join(os3.homedir(), ".hermes", "profiles");
|
|
1309
1506
|
const entries = await readdir2(profilesDir, { withFileTypes: true }).catch((error) => {
|
|
1310
1507
|
if (isNodeError4(error, "ENOENT")) {
|
|
1311
1508
|
return [];
|
|
@@ -1352,14 +1549,14 @@ async function createHermesProfile(name) {
|
|
|
1352
1549
|
if (await pathExists(profile.path)) {
|
|
1353
1550
|
throw new Error("profile already exists");
|
|
1354
1551
|
}
|
|
1355
|
-
await
|
|
1552
|
+
await mkdir5(profile.path, { recursive: true, mode: 448 });
|
|
1356
1553
|
await ensureHermesApiServerKey(name, profile.configPath);
|
|
1357
1554
|
return profile;
|
|
1358
1555
|
}
|
|
1359
1556
|
async function useHermesProfile(name, paths = resolveRuntimePaths()) {
|
|
1360
1557
|
assertProfileName(name);
|
|
1361
1558
|
const profile = profileInfo(name, name);
|
|
1362
|
-
await
|
|
1559
|
+
await mkdir5(profile.path, { recursive: true, mode: 448 });
|
|
1363
1560
|
const current = await readJsonFile(paths.stateFile) ?? {};
|
|
1364
1561
|
await writeJsonFile(paths.stateFile, { ...current, activeProfile: name });
|
|
1365
1562
|
return profile;
|
|
@@ -1412,8 +1609,8 @@ function isNodeError4(error, code) {
|
|
|
1412
1609
|
}
|
|
1413
1610
|
|
|
1414
1611
|
// src/identity/identity.ts
|
|
1415
|
-
import { generateKeyPairSync, randomUUID as
|
|
1416
|
-
import { mkdir as
|
|
1612
|
+
import { generateKeyPairSync, randomUUID as randomUUID2, sign } from "crypto";
|
|
1613
|
+
import { mkdir as mkdir6, chmod } from "fs/promises";
|
|
1417
1614
|
import { z } from "zod";
|
|
1418
1615
|
var linkIdentitySchema = z.object({
|
|
1419
1616
|
install_id: z.string().min(1),
|
|
@@ -1435,12 +1632,12 @@ async function ensureIdentity(paths = resolveRuntimePaths()) {
|
|
|
1435
1632
|
if (existing) {
|
|
1436
1633
|
return existing;
|
|
1437
1634
|
}
|
|
1438
|
-
await
|
|
1635
|
+
await mkdir6(paths.homeDir, { recursive: true, mode: 448 });
|
|
1439
1636
|
await chmod(paths.homeDir, 448).catch(() => void 0);
|
|
1440
1637
|
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
1441
1638
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1442
1639
|
const identity = {
|
|
1443
|
-
install_id: `install_${
|
|
1640
|
+
install_id: `install_${randomUUID2().replaceAll("-", "")}`,
|
|
1444
1641
|
link_id: null,
|
|
1445
1642
|
public_key_pem: publicKey.export({ type: "spki", format: "pem" }).toString(),
|
|
1446
1643
|
private_key_pem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
|
|
@@ -1474,11 +1671,11 @@ function getIdentityStatus(identity) {
|
|
|
1474
1671
|
}
|
|
1475
1672
|
|
|
1476
1673
|
// src/pairing/pairing.ts
|
|
1477
|
-
import
|
|
1674
|
+
import path7 from "path";
|
|
1478
1675
|
import { rm as rm4 } from "fs/promises";
|
|
1479
1676
|
|
|
1480
1677
|
// src/security/devices.ts
|
|
1481
|
-
import { randomBytes as randomBytes2, randomUUID as
|
|
1678
|
+
import { randomBytes as randomBytes2, randomUUID as randomUUID3, timingSafeEqual, createHash as createHash2 } from "crypto";
|
|
1482
1679
|
var ACCESS_TOKEN_TTL_MS = 15 * 60 * 1e3;
|
|
1483
1680
|
var REFRESH_TOKEN_TTL_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
1484
1681
|
async function createDeviceSession(input, paths = resolveRuntimePaths()) {
|
|
@@ -1487,7 +1684,7 @@ async function createDeviceSession(input, paths = resolveRuntimePaths()) {
|
|
|
1487
1684
|
const accessToken = randomToken("hpat_");
|
|
1488
1685
|
const refreshToken = randomToken("hprt_");
|
|
1489
1686
|
const device = {
|
|
1490
|
-
id: `dev_${
|
|
1687
|
+
id: `dev_${randomUUID3().replaceAll("-", "")}`,
|
|
1491
1688
|
label: input.label,
|
|
1492
1689
|
platform: input.platform,
|
|
1493
1690
|
scope: "admin",
|
|
@@ -1942,8 +2139,8 @@ async function loadRequiredIdentity(paths) {
|
|
|
1942
2139
|
}
|
|
1943
2140
|
return identity;
|
|
1944
2141
|
}
|
|
1945
|
-
async function postServerJson(serverBaseUrl,
|
|
1946
|
-
const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${
|
|
2142
|
+
async function postServerJson(serverBaseUrl, path9, body) {
|
|
2143
|
+
const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path9}`, {
|
|
1947
2144
|
method: "POST",
|
|
1948
2145
|
headers: {
|
|
1949
2146
|
accept: "application/json",
|
|
@@ -1953,8 +2150,8 @@ async function postServerJson(serverBaseUrl, path8, body) {
|
|
|
1953
2150
|
});
|
|
1954
2151
|
return readJsonResponse2(response);
|
|
1955
2152
|
}
|
|
1956
|
-
async function patchServerJson(serverBaseUrl,
|
|
1957
|
-
const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${
|
|
2153
|
+
async function patchServerJson(serverBaseUrl, path9, token, body) {
|
|
2154
|
+
const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path9}`, {
|
|
1958
2155
|
method: "PATCH",
|
|
1959
2156
|
headers: {
|
|
1960
2157
|
accept: "application/json",
|
|
@@ -1988,7 +2185,7 @@ function defaultDisplayName() {
|
|
|
1988
2185
|
return `Hermes Link ${process.platform}`;
|
|
1989
2186
|
}
|
|
1990
2187
|
function pairingClaimPath(sessionId, paths) {
|
|
1991
|
-
return
|
|
2188
|
+
return path7.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
|
|
1992
2189
|
}
|
|
1993
2190
|
function qrPreferredUrls(routes) {
|
|
1994
2191
|
return routes.preferredUrls.filter((url) => !url.includes("/api/v1/relay/links/")).slice(0, 1);
|
|
@@ -2064,8 +2261,8 @@ function base64UrlToBase64(value) {
|
|
|
2064
2261
|
}
|
|
2065
2262
|
|
|
2066
2263
|
// src/runtime/logger.ts
|
|
2067
|
-
import { appendFile as appendFile2, mkdir as
|
|
2068
|
-
import
|
|
2264
|
+
import { appendFile as appendFile2, mkdir as mkdir7, open as open3, readFile as readFile4, rename as rename3, rm as rm5, stat as stat3 } from "fs/promises";
|
|
2265
|
+
import path8 from "path";
|
|
2069
2266
|
var DEFAULT_LOG_FILE = "hermeslink.log";
|
|
2070
2267
|
var DEFAULT_MAX_FILE_BYTES = 1024 * 1024;
|
|
2071
2268
|
var DEFAULT_MAX_FILES = 5;
|
|
@@ -2113,7 +2310,7 @@ var FileLogger = class {
|
|
|
2113
2310
|
return this.queue;
|
|
2114
2311
|
}
|
|
2115
2312
|
async appendEntry(entry) {
|
|
2116
|
-
await
|
|
2313
|
+
await mkdir7(this.paths.logsDir, { recursive: true, mode: 448 });
|
|
2117
2314
|
const line = `${JSON.stringify(entry)}
|
|
2118
2315
|
`;
|
|
2119
2316
|
await this.rotateIfNeeded(Buffer.byteLength(line, "utf8"));
|
|
@@ -2139,7 +2336,7 @@ function createFileLogger(options = {}) {
|
|
|
2139
2336
|
return new FileLogger(options);
|
|
2140
2337
|
}
|
|
2141
2338
|
function getLinkLogFile(paths = resolveRuntimePaths(), fileName = DEFAULT_LOG_FILE) {
|
|
2142
|
-
return
|
|
2339
|
+
return path8.join(paths.logsDir, fileName);
|
|
2143
2340
|
}
|
|
2144
2341
|
async function readRecentLogEntries(options = {}) {
|
|
2145
2342
|
const paths = options.paths ?? resolveRuntimePaths();
|
|
@@ -2241,7 +2438,7 @@ async function readTail(filePath, maxBytes) {
|
|
|
2241
2438
|
if (info.size <= maxBytes) {
|
|
2242
2439
|
return await readFile4(filePath, "utf8").catch(() => null);
|
|
2243
2440
|
}
|
|
2244
|
-
const handle = await
|
|
2441
|
+
const handle = await open3(filePath, "r").catch(() => null);
|
|
2245
2442
|
if (!handle) {
|
|
2246
2443
|
return null;
|
|
2247
2444
|
}
|
|
@@ -2780,7 +2977,8 @@ export {
|
|
|
2780
2977
|
LINK_COMMAND,
|
|
2781
2978
|
resolveRuntimePaths,
|
|
2782
2979
|
loadConfig,
|
|
2783
|
-
|
|
2980
|
+
ensureHermesApiServerConfig,
|
|
2981
|
+
ensureHermesApiServerAvailable,
|
|
2784
2982
|
loadIdentity,
|
|
2785
2983
|
ensureIdentity,
|
|
2786
2984
|
getIdentityStatus,
|
|
@@ -2791,4 +2989,4 @@ export {
|
|
|
2791
2989
|
getLinkLogFile,
|
|
2792
2990
|
createApp
|
|
2793
2991
|
};
|
|
2794
|
-
//# sourceMappingURL=chunk-
|
|
2992
|
+
//# sourceMappingURL=chunk-SCIAZZ4C.js.map
|