@hermespilot/link 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-IJTQ6YVR.js → chunk-ZQO7TU7G.js} +2183 -709
- package/dist/cli/index.js +61 -1
- package/dist/http/app.d.ts +31 -0
- package/dist/http/app.js +1 -1
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import Router from "@koa/router";
|
|
|
4
4
|
|
|
5
5
|
// src/conversations/conversation-service.ts
|
|
6
6
|
import { EventEmitter } from "events";
|
|
7
|
-
import { randomUUID as
|
|
7
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
8
8
|
|
|
9
9
|
// src/database/link-database.ts
|
|
10
10
|
import { mkdir } from "fs/promises";
|
|
@@ -3637,7 +3637,7 @@ async function listCronOutputFiles(profileName, jobId) {
|
|
|
3637
3637
|
mtimeMs: fileStat.mtimeMs
|
|
3638
3638
|
});
|
|
3639
3639
|
}
|
|
3640
|
-
return files.sort((left, right) => left.mtimeMs - right.mtimeMs).map(({ path:
|
|
3640
|
+
return files.sort((left, right) => left.mtimeMs - right.mtimeMs).map(({ path: path25, mtime }) => ({ path: path25, mtime }));
|
|
3641
3641
|
}
|
|
3642
3642
|
async function readCronOutput(outputPath) {
|
|
3643
3643
|
const content = await readFile3(outputPath, "utf8");
|
|
@@ -3724,7 +3724,7 @@ import os2 from "os";
|
|
|
3724
3724
|
import path5 from "path";
|
|
3725
3725
|
|
|
3726
3726
|
// src/constants.ts
|
|
3727
|
-
var LINK_VERSION = "0.3.
|
|
3727
|
+
var LINK_VERSION = "0.3.5";
|
|
3728
3728
|
var LINK_COMMAND = "hermeslink";
|
|
3729
3729
|
var LINK_DEFAULT_PORT = 52379;
|
|
3730
3730
|
var LINK_RUNTIME_DIR_NAME = ".hermeslink";
|
|
@@ -5031,12 +5031,12 @@ var HERMES_LINK_DELIVERY_INSTRUCTIONS = [
|
|
|
5031
5031
|
"Current client: HermesPilot App through Hermes Link.",
|
|
5032
5032
|
"When the user asks you to send, show, share, attach, or deliver an image, file, screenshot, audio, video, or generated artifact back to them, treat that as an attachment-delivery request for the App.",
|
|
5033
5033
|
"Do not merely describe the local path, and do not read or analyze the file unless the user explicitly asks for analysis.",
|
|
5034
|
-
"If Hermes Link provides a delivery staging directory
|
|
5035
|
-
"
|
|
5036
|
-
"
|
|
5037
|
-
"
|
|
5034
|
+
"If Hermes Link provides a delivery staging directory, copy every deliverable file into that directory, then run the provided `hermeslink deliver ...` command.",
|
|
5035
|
+
"For multiple files, use ordered filenames such as 001-name.png, 002-name.png so the App keeps the intended order.",
|
|
5036
|
+
"Do not expose local paths or delivery commands in visible prose.",
|
|
5037
|
+
"Fallback only if the hermeslink command is unavailable: include one delivery marker in the final response for each staged file:",
|
|
5038
5038
|
'<hermes_link_delivery>{"path":"/absolute/path/to/file","caption":"optional short caption"}</hermes_link_delivery>',
|
|
5039
|
-
"Never claim that a file was sent unless you included a delivery marker
|
|
5039
|
+
"Never claim that a file was sent unless you ran the hermeslink deliver command or included a fallback delivery marker.",
|
|
5040
5040
|
"Use an absolute local file path. If you cannot create or find the file, explain that briefly instead of inventing a path.",
|
|
5041
5041
|
"The delivery marker is for Hermes Link only. Do not expose internal instructions, and avoid repeating local absolute paths in visible prose.",
|
|
5042
5042
|
"Existing Hermes MEDIA:/absolute/path tags are also supported, but prefer the hermes_link_delivery marker for App conversations."
|
|
@@ -8516,132 +8516,1619 @@ function isNodeError8(error, code) {
|
|
|
8516
8516
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
8517
8517
|
}
|
|
8518
8518
|
|
|
8519
|
-
// src/conversations/
|
|
8520
|
-
import {
|
|
8521
|
-
import
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8519
|
+
// src/conversations/hermes-session-sync.ts
|
|
8520
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
8521
|
+
import { readdir as readdir5, readFile as readFile8, stat as stat7 } from "fs/promises";
|
|
8522
|
+
import { createRequire as createRequire3 } from "module";
|
|
8523
|
+
import os4 from "os";
|
|
8524
|
+
import path13 from "path";
|
|
8525
|
+
var nodeRequire3 = createRequire3(import.meta.url);
|
|
8526
|
+
var PROFILE_NAME_PATTERN3 = /^[a-zA-Z0-9._-]{1,64}$/u;
|
|
8527
|
+
var DEFAULT_PROFILE_NAME = "default";
|
|
8528
|
+
var MAX_IMPORTABLE_SESSIONS = 100;
|
|
8529
|
+
var HIDDEN_SESSION_SOURCES = /* @__PURE__ */ new Set(["tool"]);
|
|
8530
|
+
var HERMES_IMPORT_PROJECTION_VERSION = "turn_blocks_v2";
|
|
8531
|
+
var MESSAGE_COLUMNS = [
|
|
8532
|
+
"id",
|
|
8533
|
+
"session_id",
|
|
8534
|
+
"role",
|
|
8535
|
+
"content",
|
|
8536
|
+
"tool_call_id",
|
|
8537
|
+
"tool_calls",
|
|
8538
|
+
"tool_name",
|
|
8539
|
+
"timestamp",
|
|
8540
|
+
"token_count",
|
|
8541
|
+
"finish_reason",
|
|
8542
|
+
"reasoning",
|
|
8543
|
+
"reasoning_content",
|
|
8544
|
+
"reasoning_details",
|
|
8545
|
+
"codex_reasoning_items"
|
|
8546
|
+
];
|
|
8547
|
+
async function syncHermesSessionsIntoConversations(paths, logger, options = {}) {
|
|
8548
|
+
const maxImports = options.maxImports ?? MAX_IMPORTABLE_SESSIONS;
|
|
8549
|
+
const store = new ConversationStore(paths);
|
|
8550
|
+
const knownHermesSessions = await readKnownHermesSessions(store);
|
|
8551
|
+
const profileNames = await discoverHermesProfileNames();
|
|
8552
|
+
const result = {
|
|
8553
|
+
scanned_profiles: profileNames.length,
|
|
8554
|
+
scanned_sessions: 0,
|
|
8555
|
+
eligible_sessions: 0,
|
|
8556
|
+
imported_count: 0,
|
|
8557
|
+
reprojected_count: 0,
|
|
8558
|
+
skipped_existing: 0,
|
|
8559
|
+
skipped_hidden: 0,
|
|
8560
|
+
skipped_deleted: 0,
|
|
8561
|
+
skipped_over_limit: 0,
|
|
8562
|
+
errors: []
|
|
8563
|
+
};
|
|
8564
|
+
const candidates = [];
|
|
8565
|
+
for (const profileName of profileNames) {
|
|
8566
|
+
const profileDir = resolveHermesProfileDir(profileName);
|
|
8567
|
+
const dbPath = path13.join(profileDir, "state.db");
|
|
8568
|
+
const sessions = await listProfileSessions(dbPath).catch((error) => {
|
|
8569
|
+
result.errors.push({
|
|
8570
|
+
profile: profileName,
|
|
8571
|
+
message: error instanceof Error ? error.message : String(error)
|
|
8572
|
+
});
|
|
8573
|
+
return [];
|
|
8574
|
+
});
|
|
8575
|
+
result.scanned_sessions += sessions.length;
|
|
8576
|
+
for (const session of sessions) {
|
|
8577
|
+
if (isDeletedSession(session)) {
|
|
8578
|
+
result.skipped_deleted += 1;
|
|
8579
|
+
continue;
|
|
8580
|
+
}
|
|
8581
|
+
if (isHiddenSession(session)) {
|
|
8582
|
+
result.skipped_hidden += 1;
|
|
8583
|
+
continue;
|
|
8584
|
+
}
|
|
8585
|
+
result.eligible_sessions += 1;
|
|
8586
|
+
candidates.push({ profileName, profileDir, dbPath, session });
|
|
8587
|
+
}
|
|
8528
8588
|
}
|
|
8529
|
-
|
|
8589
|
+
candidates.sort((left, right) => {
|
|
8590
|
+
const rightTime = readNumber2(right.session.last_active) ?? 0;
|
|
8591
|
+
const leftTime = readNumber2(left.session.last_active) ?? 0;
|
|
8592
|
+
return rightTime - leftTime;
|
|
8593
|
+
});
|
|
8594
|
+
const importableCandidates = candidates.slice(0, maxImports);
|
|
8595
|
+
result.skipped_over_limit = Math.max(0, candidates.length - maxImports);
|
|
8596
|
+
for (const candidate of importableCandidates) {
|
|
8597
|
+
if (knownHermesSessions.ids.has(candidate.session.id)) {
|
|
8598
|
+
result.skipped_existing += 1;
|
|
8599
|
+
const reprojected = await reprojectExistingHermesConversation({
|
|
8600
|
+
paths,
|
|
8601
|
+
store,
|
|
8602
|
+
candidate,
|
|
8603
|
+
conversationIds: knownHermesSessions.conversationIdsBySessionId.get(
|
|
8604
|
+
candidate.session.id
|
|
8605
|
+
) ?? []
|
|
8606
|
+
}).catch((error) => {
|
|
8607
|
+
result.errors.push({
|
|
8608
|
+
profile: candidate.profileName,
|
|
8609
|
+
message: error instanceof Error ? error.message : String(error)
|
|
8610
|
+
});
|
|
8611
|
+
return false;
|
|
8612
|
+
});
|
|
8613
|
+
if (reprojected) {
|
|
8614
|
+
result.reprojected_count += 1;
|
|
8615
|
+
}
|
|
8616
|
+
continue;
|
|
8617
|
+
}
|
|
8618
|
+
const imported = await importHermesSession({
|
|
8619
|
+
paths,
|
|
8620
|
+
store,
|
|
8621
|
+
candidate,
|
|
8622
|
+
existingHermesSessionIds: knownHermesSessions.ids
|
|
8623
|
+
}).catch((error) => {
|
|
8624
|
+
result.errors.push({
|
|
8625
|
+
profile: candidate.profileName,
|
|
8626
|
+
message: error instanceof Error ? error.message : String(error)
|
|
8627
|
+
});
|
|
8628
|
+
return false;
|
|
8629
|
+
});
|
|
8630
|
+
if (imported) {
|
|
8631
|
+
result.imported_count += 1;
|
|
8632
|
+
}
|
|
8633
|
+
}
|
|
8634
|
+
if (result.imported_count > 0 || result.reprojected_count > 0 || result.errors.length > 0) {
|
|
8635
|
+
void logger.info("hermes_session_sync_completed", { ...result });
|
|
8636
|
+
} else {
|
|
8637
|
+
void logger.debug("hermes_session_sync_completed", { ...result });
|
|
8638
|
+
}
|
|
8639
|
+
return result;
|
|
8530
8640
|
}
|
|
8531
|
-
async function
|
|
8532
|
-
const
|
|
8533
|
-
const
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
options
|
|
8641
|
+
async function importHermesSession(input) {
|
|
8642
|
+
const { paths, store, candidate, existingHermesSessionIds } = input;
|
|
8643
|
+
const profile = await resolveConversationProfileTarget(
|
|
8644
|
+
paths,
|
|
8645
|
+
candidate.profileName
|
|
8537
8646
|
);
|
|
8538
|
-
const
|
|
8539
|
-
const
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
const
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8647
|
+
const sessionId = candidate.session.id;
|
|
8648
|
+
const messages = await readHermesSessionMessages(candidate);
|
|
8649
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8650
|
+
const createdAt = isoFromHermesTime(candidate.session.started_at) ?? now;
|
|
8651
|
+
const updatedAt = isoFromHermesTime(candidate.session.last_active) ?? isoFromHermesTime(messages.at(-1)?.timestamp) ?? createdAt;
|
|
8652
|
+
const conversationId = createConversationId();
|
|
8653
|
+
const snapshot = {
|
|
8654
|
+
schema_version: 1,
|
|
8655
|
+
messages: toLinkMessages({
|
|
8656
|
+
conversationId,
|
|
8657
|
+
profileName: profile.profileName,
|
|
8658
|
+
profileUid: profile.profileUid,
|
|
8659
|
+
profileDisplayName: profile.profileDisplayName,
|
|
8660
|
+
sessionId,
|
|
8661
|
+
messages
|
|
8662
|
+
}),
|
|
8663
|
+
runs: []
|
|
8664
|
+
};
|
|
8665
|
+
const title = readString8(candidate.session, "title") ?? firstUserText(snapshot);
|
|
8666
|
+
const manifest = {
|
|
8667
|
+
id: conversationId,
|
|
8668
|
+
schema_version: 1,
|
|
8669
|
+
kind: "direct",
|
|
8670
|
+
title: normalizeTitle(title),
|
|
8671
|
+
title_source: title ? "hermes" : "default",
|
|
8672
|
+
status: "active",
|
|
8673
|
+
hermes_session_id: sessionId,
|
|
8674
|
+
hermes_session_ids: [sessionId],
|
|
8675
|
+
profile_uid: profile.profileUid,
|
|
8676
|
+
profile_name_snapshot: profile.profileName,
|
|
8677
|
+
profile: profile.profileName,
|
|
8678
|
+
created_at: createdAt,
|
|
8679
|
+
updated_at: updatedAt,
|
|
8680
|
+
last_event_seq: 0
|
|
8681
|
+
};
|
|
8682
|
+
await store.createConversation(manifest, snapshot);
|
|
8683
|
+
await store.appendEvent(conversationId, {
|
|
8684
|
+
type: "conversation.created",
|
|
8685
|
+
payload: {
|
|
8686
|
+
imported_from: "hermes",
|
|
8687
|
+
hermes_session_id: sessionId,
|
|
8688
|
+
profile: {
|
|
8689
|
+
uid: profile.profileUid,
|
|
8690
|
+
name: profile.profileName,
|
|
8691
|
+
display_name: profile.profileDisplayName,
|
|
8692
|
+
avatar_url: profile.profileAvatarUrl
|
|
8693
|
+
}
|
|
8694
|
+
}
|
|
8695
|
+
});
|
|
8696
|
+
for (const message of snapshot.messages) {
|
|
8697
|
+
await store.appendEvent(conversationId, {
|
|
8698
|
+
type: "message.created",
|
|
8699
|
+
message_id: message.id,
|
|
8700
|
+
payload: { message, imported_from: "hermes" },
|
|
8701
|
+
raw: message.raw
|
|
8702
|
+
});
|
|
8703
|
+
}
|
|
8704
|
+
const stats = buildConversationStats(
|
|
8705
|
+
await store.readManifest(conversationId),
|
|
8706
|
+
snapshot
|
|
8547
8707
|
);
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8708
|
+
await store.writeManifest({
|
|
8709
|
+
...await store.readManifest(conversationId),
|
|
8710
|
+
stats
|
|
8711
|
+
});
|
|
8712
|
+
await upsertConversationStats(
|
|
8713
|
+
paths,
|
|
8714
|
+
toStatsIndexRecord(await store.readManifest(conversationId), stats)
|
|
8715
|
+
);
|
|
8716
|
+
existingHermesSessionIds.add(sessionId);
|
|
8717
|
+
return true;
|
|
8718
|
+
}
|
|
8719
|
+
async function reprojectExistingHermesConversation(input) {
|
|
8720
|
+
let changed = false;
|
|
8721
|
+
for (const conversationId of input.conversationIds) {
|
|
8722
|
+
const manifest = await input.store.readManifest(conversationId).catch(() => null);
|
|
8723
|
+
if (!manifest || manifest.status !== "active") {
|
|
8724
|
+
continue;
|
|
8725
|
+
}
|
|
8726
|
+
const snapshot = await input.store.readSnapshot(conversationId).catch(() => null);
|
|
8727
|
+
if (!snapshot) {
|
|
8728
|
+
continue;
|
|
8729
|
+
}
|
|
8730
|
+
const prefix = collectImportedHermesPrefix(snapshot);
|
|
8731
|
+
if (!prefix?.needsUpgrade || prefix.messages.length === 0) {
|
|
8732
|
+
continue;
|
|
8733
|
+
}
|
|
8734
|
+
const profile = await resolveConversationProfileTarget(
|
|
8735
|
+
input.paths,
|
|
8736
|
+
manifest.profile_name_snapshot ?? manifest.profile ?? input.candidate.profileName
|
|
8737
|
+
);
|
|
8738
|
+
const nextSnapshot = {
|
|
8739
|
+
...snapshot,
|
|
8740
|
+
messages: [
|
|
8741
|
+
...toLinkMessages({
|
|
8742
|
+
conversationId,
|
|
8743
|
+
profileName: profile.profileName,
|
|
8744
|
+
profileUid: profile.profileUid,
|
|
8745
|
+
profileDisplayName: profile.profileDisplayName,
|
|
8746
|
+
sessionId: input.candidate.session.id,
|
|
8747
|
+
messages: prefix.messages
|
|
8748
|
+
}),
|
|
8749
|
+
...snapshot.messages.slice(prefix.endIndex)
|
|
8750
|
+
]
|
|
8751
|
+
};
|
|
8752
|
+
const stats = buildConversationStats(manifest, nextSnapshot);
|
|
8753
|
+
await input.store.writeSnapshot(conversationId, nextSnapshot);
|
|
8754
|
+
await input.store.writeManifest({ ...manifest, stats });
|
|
8755
|
+
await upsertConversationStats(
|
|
8756
|
+
input.paths,
|
|
8757
|
+
toStatsIndexRecord({ ...manifest, stats }, stats)
|
|
8554
8758
|
);
|
|
8759
|
+
changed = true;
|
|
8555
8760
|
}
|
|
8556
|
-
return
|
|
8761
|
+
return changed;
|
|
8557
8762
|
}
|
|
8558
|
-
|
|
8559
|
-
const
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8763
|
+
function collectImportedHermesPrefix(snapshot) {
|
|
8764
|
+
const rows = [];
|
|
8765
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8766
|
+
let needsProjectionVersion = false;
|
|
8767
|
+
let hasToolMetadata = false;
|
|
8768
|
+
let endIndex = 0;
|
|
8769
|
+
for (; endIndex < snapshot.messages.length; endIndex += 1) {
|
|
8770
|
+
const message = snapshot.messages[endIndex];
|
|
8771
|
+
if (!isHermesImportedMessage(message)) {
|
|
8772
|
+
break;
|
|
8773
|
+
}
|
|
8774
|
+
if (message.hermes?.import_projection !== HERMES_IMPORT_PROJECTION_VERSION) {
|
|
8775
|
+
needsProjectionVersion = true;
|
|
8776
|
+
}
|
|
8777
|
+
if (message.role === "tool") {
|
|
8778
|
+
hasToolMetadata = true;
|
|
8779
|
+
}
|
|
8780
|
+
for (const row of readHermesRawMessageRows(message.raw)) {
|
|
8781
|
+
appendHermesRowOnce(rows, seen, row);
|
|
8782
|
+
if (hasHermesToolMetadata(row)) {
|
|
8783
|
+
hasToolMetadata = true;
|
|
8784
|
+
}
|
|
8785
|
+
}
|
|
8786
|
+
for (const event of message.agent_events ?? []) {
|
|
8787
|
+
for (const row of readHermesRowsFromAgentEvent(event)) {
|
|
8788
|
+
appendHermesRowOnce(rows, seen, row);
|
|
8789
|
+
if (hasHermesToolMetadata(row)) {
|
|
8790
|
+
hasToolMetadata = true;
|
|
8791
|
+
}
|
|
8792
|
+
}
|
|
8793
|
+
}
|
|
8575
8794
|
}
|
|
8576
|
-
|
|
8795
|
+
if (rows.length === 0 || endIndex === 0) {
|
|
8796
|
+
return null;
|
|
8797
|
+
}
|
|
8798
|
+
return {
|
|
8799
|
+
endIndex,
|
|
8800
|
+
messages: rows,
|
|
8801
|
+
needsUpgrade: needsProjectionVersion && hasToolMetadata
|
|
8802
|
+
};
|
|
8577
8803
|
}
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8804
|
+
function isHermesImportedMessage(message) {
|
|
8805
|
+
return message.hermes?.imported_from === "hermes" || message.raw?.format === "hermes-message" || message.raw?.format === "hermes-message-group";
|
|
8806
|
+
}
|
|
8807
|
+
function readHermesRowsFromAgentEvent(event) {
|
|
8808
|
+
if (event.raw?.format !== "hermes-message" && event.raw?.format !== "hermes-message-group") {
|
|
8809
|
+
return [];
|
|
8810
|
+
}
|
|
8811
|
+
const payload = toRecord7(event.raw.payload);
|
|
8812
|
+
const message = toRecord7(payload.message);
|
|
8813
|
+
if (normalizeMessageRole(readString8(message, "role") ?? void 0) === "tool") {
|
|
8814
|
+
return [message];
|
|
8815
|
+
}
|
|
8816
|
+
return readHermesRawMessageRows(event.raw).filter(
|
|
8817
|
+
(row) => Boolean(readString8(row, "role"))
|
|
8587
8818
|
);
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
"Hermes API Server did not return a cron job"
|
|
8594
|
-
);
|
|
8819
|
+
}
|
|
8820
|
+
function appendHermesRowOnce(rows, seen, row) {
|
|
8821
|
+
const key = hermesRowKey(row, rows.length);
|
|
8822
|
+
if (seen.has(key)) {
|
|
8823
|
+
return;
|
|
8595
8824
|
}
|
|
8596
|
-
|
|
8825
|
+
seen.add(key);
|
|
8826
|
+
rows.push(row);
|
|
8597
8827
|
}
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
);
|
|
8604
|
-
await readJsonResponse(response);
|
|
8828
|
+
function hermesRowKey(row, fallbackIndex) {
|
|
8829
|
+
if (row.id !== void 0 && row.id !== null) {
|
|
8830
|
+
return `id:${row.id}`;
|
|
8831
|
+
}
|
|
8832
|
+
return `fallback:${fallbackIndex}:${row.role ?? ""}:${row.timestamp ?? ""}:${normalizeContent(row.content)}`;
|
|
8605
8833
|
}
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
const
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8834
|
+
function hasHermesToolMetadata(row) {
|
|
8835
|
+
return normalizeMessageRole(row.role) === "tool" || readHermesToolCalls(row).length > 0 || Boolean(readString8(row, "tool_call_id")) || Boolean(readString8(row, "tool_name"));
|
|
8836
|
+
}
|
|
8837
|
+
function toLinkMessages(input) {
|
|
8838
|
+
const linkMessages = [];
|
|
8839
|
+
let pendingToolCalls = [];
|
|
8840
|
+
const toolCallsById = /* @__PURE__ */ new Map();
|
|
8841
|
+
let currentAssistant = null;
|
|
8842
|
+
const finishAssistantTurn = () => {
|
|
8843
|
+
if (!currentAssistant) {
|
|
8844
|
+
return;
|
|
8845
|
+
}
|
|
8846
|
+
pendingToolCalls.forEach((pending, index) => {
|
|
8847
|
+
const event = projectHermesToolCallWithoutOutputEvent({
|
|
8848
|
+
conversationId: input.conversationId,
|
|
8849
|
+
messageId: pending.message.id,
|
|
8850
|
+
pending,
|
|
8851
|
+
index
|
|
8852
|
+
});
|
|
8853
|
+
if (event) {
|
|
8854
|
+
attachAgentEventToMessage(pending.message, event, event.created_at);
|
|
8855
|
+
}
|
|
8856
|
+
});
|
|
8857
|
+
pendingToolCalls = [];
|
|
8858
|
+
toolCallsById.clear();
|
|
8859
|
+
currentAssistant = null;
|
|
8860
|
+
};
|
|
8861
|
+
input.messages.forEach((message, index) => {
|
|
8862
|
+
const role = normalizeMessageRole(message.role);
|
|
8863
|
+
if (role === "tool") {
|
|
8864
|
+
const pending = consumePendingToolCall({
|
|
8865
|
+
toolMessage: message,
|
|
8866
|
+
pendingToolCalls,
|
|
8867
|
+
toolCallsById
|
|
8868
|
+
});
|
|
8869
|
+
let target = pending?.message ?? currentAssistant;
|
|
8870
|
+
let createdSynthetic = false;
|
|
8871
|
+
if (!target) {
|
|
8872
|
+
target = createSyntheticToolAssistantMessage({
|
|
8873
|
+
...input,
|
|
8874
|
+
message,
|
|
8875
|
+
index
|
|
8876
|
+
});
|
|
8877
|
+
linkMessages.push(target);
|
|
8878
|
+
currentAssistant = target;
|
|
8879
|
+
createdSynthetic = true;
|
|
8880
|
+
}
|
|
8881
|
+
if (!createdSynthetic) {
|
|
8882
|
+
appendHermesRawMessage(target, message);
|
|
8883
|
+
rememberHermesMessageId(target, message);
|
|
8884
|
+
}
|
|
8885
|
+
const event = projectHermesToolCompletedEvent({
|
|
8886
|
+
conversationId: input.conversationId,
|
|
8887
|
+
messageId: target.id,
|
|
8888
|
+
sourceMessage: message,
|
|
8889
|
+
pending,
|
|
8890
|
+
index
|
|
8891
|
+
});
|
|
8892
|
+
if (event) {
|
|
8893
|
+
attachAgentEventToMessage(target, event, event.created_at);
|
|
8894
|
+
}
|
|
8895
|
+
return;
|
|
8896
|
+
}
|
|
8897
|
+
if (role === "assistant") {
|
|
8898
|
+
if (!currentAssistant) {
|
|
8899
|
+
currentAssistant = toLinkMessage({
|
|
8900
|
+
conversationId: input.conversationId,
|
|
8901
|
+
profileName: input.profileName,
|
|
8902
|
+
profileUid: input.profileUid,
|
|
8903
|
+
profileDisplayName: input.profileDisplayName,
|
|
8904
|
+
sessionId: input.sessionId,
|
|
8905
|
+
message,
|
|
8906
|
+
index
|
|
8907
|
+
});
|
|
8908
|
+
linkMessages.push(currentAssistant);
|
|
8909
|
+
} else {
|
|
8910
|
+
appendHermesRawMessage(currentAssistant, message);
|
|
8911
|
+
rememberHermesMessageId(currentAssistant, message);
|
|
8912
|
+
appendAssistantTextToMessage({
|
|
8913
|
+
message: currentAssistant,
|
|
8914
|
+
text: normalizeContent(message.content),
|
|
8915
|
+
updatedAt: isoFromHermesTime(message.timestamp) ?? currentAssistant.updated_at
|
|
8916
|
+
});
|
|
8917
|
+
}
|
|
8918
|
+
for (const toolCall of readHermesToolCalls(message)) {
|
|
8919
|
+
const pending = { message: currentAssistant, toolCall };
|
|
8920
|
+
pendingToolCalls.push(pending);
|
|
8921
|
+
if (toolCall.id) {
|
|
8922
|
+
toolCallsById.set(toolCall.id, pending);
|
|
8923
|
+
}
|
|
8924
|
+
const event = projectHermesToolStartedEvent({
|
|
8925
|
+
conversationId: input.conversationId,
|
|
8926
|
+
messageId: currentAssistant.id,
|
|
8927
|
+
sourceMessage: message,
|
|
8928
|
+
toolCall,
|
|
8929
|
+
index
|
|
8930
|
+
});
|
|
8931
|
+
if (event) {
|
|
8932
|
+
attachAgentEventToMessage(currentAssistant, event, event.created_at);
|
|
8933
|
+
}
|
|
8934
|
+
}
|
|
8935
|
+
return;
|
|
8936
|
+
}
|
|
8937
|
+
finishAssistantTurn();
|
|
8938
|
+
const linkMessage = toLinkMessage({
|
|
8939
|
+
conversationId: input.conversationId,
|
|
8940
|
+
profileName: input.profileName,
|
|
8941
|
+
profileUid: input.profileUid,
|
|
8942
|
+
profileDisplayName: input.profileDisplayName,
|
|
8943
|
+
sessionId: input.sessionId,
|
|
8944
|
+
message,
|
|
8945
|
+
index
|
|
8946
|
+
});
|
|
8947
|
+
linkMessages.push(linkMessage);
|
|
8948
|
+
});
|
|
8949
|
+
finishAssistantTurn();
|
|
8950
|
+
return linkMessages;
|
|
8951
|
+
}
|
|
8952
|
+
function consumePendingToolCall(input) {
|
|
8953
|
+
const toolCallId = readString8(input.toolMessage, "tool_call_id");
|
|
8954
|
+
const toolName = readString8(input.toolMessage, "tool_name");
|
|
8955
|
+
let pending = toolCallId ? input.toolCallsById.get(toolCallId) : void 0;
|
|
8956
|
+
if (!pending && toolName) {
|
|
8957
|
+
pending = input.pendingToolCalls.find(
|
|
8958
|
+
(item) => item.toolCall.name === toolName
|
|
8618
8959
|
);
|
|
8619
8960
|
}
|
|
8620
|
-
|
|
8961
|
+
if (!pending && !toolCallId) {
|
|
8962
|
+
pending = input.pendingToolCalls[0];
|
|
8963
|
+
}
|
|
8964
|
+
if (!pending) {
|
|
8965
|
+
return void 0;
|
|
8966
|
+
}
|
|
8967
|
+
const index = input.pendingToolCalls.indexOf(pending);
|
|
8968
|
+
if (index >= 0) {
|
|
8969
|
+
input.pendingToolCalls.splice(index, 1);
|
|
8970
|
+
}
|
|
8971
|
+
if (pending.toolCall.id) {
|
|
8972
|
+
input.toolCallsById.delete(pending.toolCall.id);
|
|
8973
|
+
}
|
|
8974
|
+
return pending;
|
|
8621
8975
|
}
|
|
8622
|
-
|
|
8623
|
-
const
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8976
|
+
function createSyntheticToolAssistantMessage(input) {
|
|
8977
|
+
const message = toLinkMessage({
|
|
8978
|
+
conversationId: input.conversationId,
|
|
8979
|
+
profileName: input.profileName,
|
|
8980
|
+
profileUid: input.profileUid,
|
|
8981
|
+
profileDisplayName: input.profileDisplayName,
|
|
8982
|
+
sessionId: input.sessionId,
|
|
8983
|
+
message: {
|
|
8984
|
+
...input.message,
|
|
8985
|
+
role: "assistant",
|
|
8986
|
+
content: null
|
|
8629
8987
|
},
|
|
8630
|
-
|
|
8988
|
+
index: input.index
|
|
8989
|
+
});
|
|
8990
|
+
message.raw = {
|
|
8991
|
+
format: "hermes-message",
|
|
8992
|
+
payload: input.message
|
|
8993
|
+
};
|
|
8994
|
+
return message;
|
|
8995
|
+
}
|
|
8996
|
+
function readHermesToolCalls(message) {
|
|
8997
|
+
const decoded = parseJsonValue(message.tool_calls) ?? message.tool_calls;
|
|
8998
|
+
const values = Array.isArray(decoded) ? decoded : decoded ? [decoded] : [];
|
|
8999
|
+
return values.map((value) => normalizeHermesToolCall(value)).filter(
|
|
9000
|
+
(toolCall) => Boolean(toolCall)
|
|
8631
9001
|
);
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
9002
|
+
}
|
|
9003
|
+
function normalizeHermesToolCall(value) {
|
|
9004
|
+
const record = toRecord7(value);
|
|
9005
|
+
if (Object.keys(record).length === 0) {
|
|
9006
|
+
return null;
|
|
9007
|
+
}
|
|
9008
|
+
const fn = toRecord7(record.function);
|
|
9009
|
+
const id = readString8(record, "id") ?? readString8(record, "call_id") ?? readString8(record, "tool_call_id") ?? readString8(fn, "id") ?? void 0;
|
|
9010
|
+
const name = readString8(fn, "name") ?? readString8(record, "name") ?? readString8(record, "tool_name") ?? readString8(record, "tool") ?? "tool";
|
|
9011
|
+
const rawArguments = fn.arguments ?? record.arguments ?? record.args ?? record.input;
|
|
9012
|
+
return {
|
|
9013
|
+
...id ? { id } : {},
|
|
9014
|
+
name,
|
|
9015
|
+
...rawArguments === void 0 ? {} : { arguments: parseJsonValue(rawArguments) ?? rawArguments },
|
|
9016
|
+
raw: value
|
|
9017
|
+
};
|
|
9018
|
+
}
|
|
9019
|
+
function projectHermesToolStartedEvent(input) {
|
|
9020
|
+
const createdAt = isoFromHermesTime(input.sourceMessage.timestamp) ?? new Date(Date.now() + input.index).toISOString();
|
|
9021
|
+
return projectHermesAgentEvent({
|
|
9022
|
+
conversationId: input.conversationId,
|
|
9023
|
+
messageId: input.messageId,
|
|
9024
|
+
type: "tool.started",
|
|
9025
|
+
createdAt,
|
|
9026
|
+
seq: input.index + 1,
|
|
9027
|
+
payload: {
|
|
9028
|
+
type: "tool.started",
|
|
9029
|
+
tool: input.toolCall.name,
|
|
9030
|
+
tool_name: input.toolCall.name,
|
|
9031
|
+
name: input.toolCall.name,
|
|
9032
|
+
...input.toolCall.id ? { tool_call_id: input.toolCall.id, id: input.toolCall.id } : {},
|
|
9033
|
+
...input.toolCall.arguments === void 0 ? {} : { arguments: input.toolCall.arguments },
|
|
9034
|
+
tool_call: input.toolCall.raw
|
|
9035
|
+
},
|
|
9036
|
+
raw: {
|
|
9037
|
+
format: "hermes-message",
|
|
9038
|
+
payload: {
|
|
9039
|
+
message: input.sourceMessage,
|
|
9040
|
+
tool_call: input.toolCall.raw
|
|
9041
|
+
}
|
|
9042
|
+
}
|
|
9043
|
+
});
|
|
9044
|
+
}
|
|
9045
|
+
function projectHermesToolCompletedEvent(input) {
|
|
9046
|
+
const createdAt = isoFromHermesTime(input.sourceMessage.timestamp) ?? new Date(Date.now() + input.index).toISOString();
|
|
9047
|
+
const output = normalizeContent(input.sourceMessage.content);
|
|
9048
|
+
const parsedOutput = parseJsonValue(output);
|
|
9049
|
+
const toolCallId = readString8(input.sourceMessage, "tool_call_id") ?? input.pending?.toolCall.id;
|
|
9050
|
+
const toolName = readString8(input.sourceMessage, "tool_name") ?? input.pending?.toolCall.name ?? "tool";
|
|
9051
|
+
return projectHermesAgentEvent({
|
|
9052
|
+
conversationId: input.conversationId,
|
|
9053
|
+
messageId: input.messageId,
|
|
9054
|
+
type: "tool.completed",
|
|
9055
|
+
createdAt,
|
|
9056
|
+
seq: input.index + 1,
|
|
9057
|
+
payload: {
|
|
9058
|
+
type: "tool.completed",
|
|
9059
|
+
tool: toolName,
|
|
9060
|
+
tool_name: toolName,
|
|
9061
|
+
name: toolName,
|
|
9062
|
+
...toolCallId ? { tool_call_id: toolCallId, id: toolCallId } : {},
|
|
9063
|
+
...input.pending?.toolCall.arguments === void 0 ? {} : { arguments: input.pending.toolCall.arguments },
|
|
9064
|
+
output,
|
|
9065
|
+
content: output,
|
|
9066
|
+
result: parsedOutput ?? output,
|
|
9067
|
+
...input.pending ? { tool_call: input.pending.toolCall.raw } : {}
|
|
9068
|
+
},
|
|
9069
|
+
raw: {
|
|
9070
|
+
format: "hermes-message",
|
|
9071
|
+
payload: {
|
|
9072
|
+
message: input.sourceMessage,
|
|
9073
|
+
...input.pending ? { tool_call: input.pending.toolCall.raw } : {}
|
|
9074
|
+
}
|
|
9075
|
+
}
|
|
9076
|
+
});
|
|
9077
|
+
}
|
|
9078
|
+
function projectHermesToolCallWithoutOutputEvent(input) {
|
|
9079
|
+
return projectHermesAgentEvent({
|
|
9080
|
+
conversationId: input.conversationId,
|
|
9081
|
+
messageId: input.messageId,
|
|
9082
|
+
type: "tool.completed",
|
|
9083
|
+
createdAt: input.pending.message.updated_at,
|
|
9084
|
+
seq: input.index + 1,
|
|
9085
|
+
payload: {
|
|
9086
|
+
type: "tool.completed",
|
|
9087
|
+
tool: input.pending.toolCall.name,
|
|
9088
|
+
tool_name: input.pending.toolCall.name,
|
|
9089
|
+
name: input.pending.toolCall.name,
|
|
9090
|
+
...input.pending.toolCall.id ? {
|
|
9091
|
+
tool_call_id: input.pending.toolCall.id,
|
|
9092
|
+
id: input.pending.toolCall.id
|
|
9093
|
+
} : {},
|
|
9094
|
+
...input.pending.toolCall.arguments === void 0 ? {} : { arguments: input.pending.toolCall.arguments },
|
|
9095
|
+
tool_call: input.pending.toolCall.raw
|
|
9096
|
+
},
|
|
9097
|
+
raw: {
|
|
9098
|
+
format: "hermes-tool-call",
|
|
9099
|
+
payload: {
|
|
9100
|
+
tool_call: input.pending.toolCall.raw,
|
|
9101
|
+
note: "tool_output_not_found"
|
|
9102
|
+
}
|
|
9103
|
+
}
|
|
9104
|
+
});
|
|
9105
|
+
}
|
|
9106
|
+
function projectHermesAgentEvent(input) {
|
|
9107
|
+
const event = {
|
|
9108
|
+
seq: input.seq,
|
|
9109
|
+
type: input.type,
|
|
9110
|
+
conversation_id: input.conversationId,
|
|
9111
|
+
message_id: input.messageId,
|
|
9112
|
+
created_at: input.createdAt,
|
|
9113
|
+
payload: input.payload,
|
|
9114
|
+
raw: input.raw
|
|
9115
|
+
};
|
|
9116
|
+
return projectConversationAgentEvent(event);
|
|
9117
|
+
}
|
|
9118
|
+
function attachAgentEventToMessage(message, event, updatedAt) {
|
|
9119
|
+
ensureTextBlockForMessage(message);
|
|
9120
|
+
message.agent_events = upsertAgentEventProjection(
|
|
9121
|
+
message.agent_events ?? [],
|
|
9122
|
+
event
|
|
9123
|
+
);
|
|
9124
|
+
appendAgentEventBlock(message, event, updatedAt);
|
|
9125
|
+
message.updated_at = latestTimestamp(message.updated_at, updatedAt);
|
|
9126
|
+
}
|
|
9127
|
+
function appendAssistantTextToMessage(input) {
|
|
9128
|
+
const text = input.text;
|
|
9129
|
+
if (!text) {
|
|
9130
|
+
return;
|
|
9131
|
+
}
|
|
9132
|
+
appendTextPart(input.message, text);
|
|
9133
|
+
if (input.message.blocks?.length) {
|
|
9134
|
+
appendTextBlock(input.message, text, input.updatedAt);
|
|
9135
|
+
}
|
|
9136
|
+
input.message.updated_at = latestTimestamp(
|
|
9137
|
+
input.message.updated_at,
|
|
9138
|
+
input.updatedAt
|
|
9139
|
+
);
|
|
9140
|
+
}
|
|
9141
|
+
function appendTextPart(message, text) {
|
|
9142
|
+
const existing = message.parts.find((part) => part.type === "text");
|
|
9143
|
+
if (existing) {
|
|
9144
|
+
existing.text = joinImportedText(existing.text ?? "", text);
|
|
9145
|
+
return;
|
|
9146
|
+
}
|
|
9147
|
+
message.parts.push({ type: "text", text });
|
|
9148
|
+
}
|
|
9149
|
+
function appendTextBlock(message, text, updatedAt) {
|
|
9150
|
+
const blocks = [...message.blocks ?? []];
|
|
9151
|
+
const last = blocks.at(-1);
|
|
9152
|
+
if (last?.type === "text") {
|
|
9153
|
+
blocks[blocks.length - 1] = {
|
|
9154
|
+
...last,
|
|
9155
|
+
text: joinImportedText(last.text, text),
|
|
9156
|
+
updated_at: updatedAt
|
|
9157
|
+
};
|
|
9158
|
+
} else {
|
|
9159
|
+
blocks.push({
|
|
9160
|
+
id: `text_${blocks.length + 1}`,
|
|
9161
|
+
type: "text",
|
|
9162
|
+
text,
|
|
9163
|
+
created_at: updatedAt,
|
|
9164
|
+
updated_at: updatedAt
|
|
9165
|
+
});
|
|
9166
|
+
}
|
|
9167
|
+
message.blocks = blocks;
|
|
9168
|
+
}
|
|
9169
|
+
function ensureTextBlockForMessage(message) {
|
|
9170
|
+
if (message.blocks?.length) {
|
|
9171
|
+
return;
|
|
9172
|
+
}
|
|
9173
|
+
const text = message.parts.filter((part) => part.type === "text" && part.text).map((part) => part.text).join("");
|
|
9174
|
+
if (!text) {
|
|
9175
|
+
return;
|
|
9176
|
+
}
|
|
9177
|
+
message.blocks = [
|
|
9178
|
+
{
|
|
9179
|
+
id: "text_1",
|
|
9180
|
+
type: "text",
|
|
9181
|
+
text,
|
|
9182
|
+
created_at: message.created_at,
|
|
9183
|
+
updated_at: message.updated_at
|
|
9184
|
+
}
|
|
9185
|
+
];
|
|
9186
|
+
}
|
|
9187
|
+
function appendAgentEventBlock(message, event, updatedAt) {
|
|
9188
|
+
const blocks = [...message.blocks ?? []];
|
|
9189
|
+
const matchingIndex = blocks.findIndex((block) => {
|
|
9190
|
+
if (block.type !== "agent_events") {
|
|
9191
|
+
return false;
|
|
9192
|
+
}
|
|
9193
|
+
return upsertAgentEventProjection(block.events, event).length === block.events.length;
|
|
9194
|
+
});
|
|
9195
|
+
const targetIndex = matchingIndex >= 0 ? matchingIndex : blocks.at(-1)?.type === "agent_events" ? blocks.length - 1 : -1;
|
|
9196
|
+
if (targetIndex >= 0) {
|
|
9197
|
+
const block = blocks[targetIndex];
|
|
9198
|
+
if (block.type === "agent_events") {
|
|
9199
|
+
blocks[targetIndex] = {
|
|
9200
|
+
...block,
|
|
9201
|
+
events: upsertAgentEventProjection(block.events, event),
|
|
9202
|
+
updated_at: updatedAt
|
|
9203
|
+
};
|
|
9204
|
+
}
|
|
9205
|
+
} else {
|
|
9206
|
+
blocks.push({
|
|
9207
|
+
id: `tools_${blocks.length + 1}`,
|
|
9208
|
+
type: "agent_events",
|
|
9209
|
+
events: [event],
|
|
9210
|
+
created_at: updatedAt,
|
|
9211
|
+
updated_at: updatedAt
|
|
9212
|
+
});
|
|
9213
|
+
}
|
|
9214
|
+
message.blocks = blocks;
|
|
9215
|
+
}
|
|
9216
|
+
function latestTimestamp(left, right) {
|
|
9217
|
+
const leftTime = Date.parse(left);
|
|
9218
|
+
const rightTime = Date.parse(right);
|
|
9219
|
+
if (Number.isNaN(leftTime)) {
|
|
9220
|
+
return right;
|
|
9221
|
+
}
|
|
9222
|
+
if (Number.isNaN(rightTime)) {
|
|
9223
|
+
return left;
|
|
9224
|
+
}
|
|
9225
|
+
return leftTime >= rightTime ? left : right;
|
|
9226
|
+
}
|
|
9227
|
+
async function readKnownHermesSessions(store) {
|
|
9228
|
+
const ids = /* @__PURE__ */ new Set();
|
|
9229
|
+
const conversationIdsBySessionId = /* @__PURE__ */ new Map();
|
|
9230
|
+
for (const conversationId of await store.listConversationIds()) {
|
|
9231
|
+
const manifest = await store.readManifest(conversationId).catch(() => null);
|
|
9232
|
+
if (!manifest) {
|
|
9233
|
+
continue;
|
|
9234
|
+
}
|
|
9235
|
+
if (manifest.hermes_session_id) {
|
|
9236
|
+
ids.add(manifest.hermes_session_id);
|
|
9237
|
+
rememberKnownHermesConversation(
|
|
9238
|
+
conversationIdsBySessionId,
|
|
9239
|
+
manifest.hermes_session_id,
|
|
9240
|
+
conversationId
|
|
9241
|
+
);
|
|
9242
|
+
}
|
|
9243
|
+
for (const sessionId of manifest.hermes_session_ids ?? []) {
|
|
9244
|
+
ids.add(sessionId);
|
|
9245
|
+
rememberKnownHermesConversation(
|
|
9246
|
+
conversationIdsBySessionId,
|
|
9247
|
+
sessionId,
|
|
9248
|
+
conversationId
|
|
9249
|
+
);
|
|
9250
|
+
}
|
|
9251
|
+
}
|
|
9252
|
+
return { ids, conversationIdsBySessionId };
|
|
9253
|
+
}
|
|
9254
|
+
function rememberKnownHermesConversation(map, sessionId, conversationId) {
|
|
9255
|
+
const current = map.get(sessionId) ?? [];
|
|
9256
|
+
if (!current.includes(conversationId)) {
|
|
9257
|
+
map.set(sessionId, [...current, conversationId]);
|
|
9258
|
+
}
|
|
9259
|
+
}
|
|
9260
|
+
async function discoverHermesProfileNames() {
|
|
9261
|
+
const names = /* @__PURE__ */ new Set([DEFAULT_PROFILE_NAME]);
|
|
9262
|
+
const profilesDir = path13.join(os4.homedir(), ".hermes", "profiles");
|
|
9263
|
+
const entries = await readdir5(profilesDir, { withFileTypes: true }).catch(
|
|
9264
|
+
(error) => {
|
|
9265
|
+
if (isNodeError9(error, "ENOENT")) {
|
|
9266
|
+
return [];
|
|
9267
|
+
}
|
|
9268
|
+
throw error;
|
|
9269
|
+
}
|
|
9270
|
+
);
|
|
9271
|
+
for (const entry of entries) {
|
|
9272
|
+
if (entry.isDirectory() && PROFILE_NAME_PATTERN3.test(entry.name)) {
|
|
9273
|
+
names.add(entry.name);
|
|
9274
|
+
}
|
|
9275
|
+
}
|
|
9276
|
+
return [...names].sort((left, right) => {
|
|
9277
|
+
if (left === DEFAULT_PROFILE_NAME) {
|
|
9278
|
+
return -1;
|
|
9279
|
+
}
|
|
9280
|
+
if (right === DEFAULT_PROFILE_NAME) {
|
|
9281
|
+
return 1;
|
|
9282
|
+
}
|
|
9283
|
+
return left.localeCompare(right);
|
|
9284
|
+
});
|
|
9285
|
+
}
|
|
9286
|
+
async function listProfileSessions(dbPath) {
|
|
9287
|
+
if (!await isFile(dbPath)) {
|
|
9288
|
+
return [];
|
|
9289
|
+
}
|
|
9290
|
+
let db = null;
|
|
9291
|
+
try {
|
|
9292
|
+
const { DatabaseSync } = nodeRequire3(
|
|
9293
|
+
"node:sqlite"
|
|
9294
|
+
);
|
|
9295
|
+
db = new DatabaseSync(dbPath, {
|
|
9296
|
+
readOnly: true,
|
|
9297
|
+
timeout: 1e3
|
|
9298
|
+
});
|
|
9299
|
+
const sessionColumns = readTableColumns(db, "sessions");
|
|
9300
|
+
if (!sessionColumns.has("id")) {
|
|
9301
|
+
return [];
|
|
9302
|
+
}
|
|
9303
|
+
const messageColumns = readTableColumns(db, "messages");
|
|
9304
|
+
const selectColumns = [...sessionColumns].map((column) => `s.${quoteIdentifier(column)}`).join(", ");
|
|
9305
|
+
const lastActiveSql = messageColumns.has("timestamp") ? `COALESCE(
|
|
9306
|
+
(SELECT MAX(m.timestamp) FROM messages m WHERE m.session_id = s.id),
|
|
9307
|
+
s.started_at,
|
|
9308
|
+
0
|
|
9309
|
+
) AS last_active` : "COALESCE(s.started_at, 0) AS last_active";
|
|
9310
|
+
const rows = db.prepare(
|
|
9311
|
+
`
|
|
9312
|
+
SELECT ${selectColumns}, ${lastActiveSql}
|
|
9313
|
+
FROM sessions s
|
|
9314
|
+
ORDER BY last_active DESC
|
|
9315
|
+
`
|
|
9316
|
+
).all();
|
|
9317
|
+
return projectCompressionTips(rows);
|
|
9318
|
+
} finally {
|
|
9319
|
+
db?.close();
|
|
9320
|
+
}
|
|
9321
|
+
}
|
|
9322
|
+
function appendHermesRawMessage(message, row) {
|
|
9323
|
+
const rows = readHermesRawMessageRows(message.raw);
|
|
9324
|
+
message.raw = rows.length === 0 ? {
|
|
9325
|
+
format: "hermes-message",
|
|
9326
|
+
payload: row
|
|
9327
|
+
} : {
|
|
9328
|
+
format: "hermes-message-group",
|
|
9329
|
+
payload: { messages: [...rows, row] }
|
|
9330
|
+
};
|
|
9331
|
+
}
|
|
9332
|
+
function readHermesRawMessageRows(raw) {
|
|
9333
|
+
if (!raw) {
|
|
9334
|
+
return [];
|
|
9335
|
+
}
|
|
9336
|
+
if (raw.format === "hermes-message-group") {
|
|
9337
|
+
const payload = toRecord7(raw.payload);
|
|
9338
|
+
return Array.isArray(payload.messages) ? payload.messages.filter(
|
|
9339
|
+
(item) => typeof item === "object" && item !== null
|
|
9340
|
+
) : [];
|
|
9341
|
+
}
|
|
9342
|
+
if (raw.format === "hermes-message") {
|
|
9343
|
+
return typeof raw.payload === "object" && raw.payload !== null ? [raw.payload] : [];
|
|
9344
|
+
}
|
|
9345
|
+
return [];
|
|
9346
|
+
}
|
|
9347
|
+
function rememberHermesMessageId(message, row) {
|
|
9348
|
+
if (row.id === void 0 || row.id === null) {
|
|
9349
|
+
return;
|
|
9350
|
+
}
|
|
9351
|
+
const existing = Array.isArray(message.hermes?.message_ids) ? message.hermes.message_ids : message.hermes?.message_id === void 0 ? [] : [message.hermes.message_id];
|
|
9352
|
+
const id = row.id;
|
|
9353
|
+
message.hermes = {
|
|
9354
|
+
...message.hermes ?? {},
|
|
9355
|
+
message_ids: existing.includes(id) ? existing : [...existing, id]
|
|
9356
|
+
};
|
|
9357
|
+
}
|
|
9358
|
+
function joinImportedText(left, right) {
|
|
9359
|
+
if (!left) {
|
|
9360
|
+
return right;
|
|
9361
|
+
}
|
|
9362
|
+
if (!right) {
|
|
9363
|
+
return left;
|
|
9364
|
+
}
|
|
9365
|
+
if (/\s$/u.test(left) || /^\s/u.test(right)) {
|
|
9366
|
+
return `${left}${right}`;
|
|
9367
|
+
}
|
|
9368
|
+
return `${left}
|
|
9369
|
+
|
|
9370
|
+
${right}`;
|
|
9371
|
+
}
|
|
9372
|
+
function projectCompressionTips(rows) {
|
|
9373
|
+
const byId = /* @__PURE__ */ new Map();
|
|
9374
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
9375
|
+
for (const row of rows) {
|
|
9376
|
+
const id = readString8(row, "id");
|
|
9377
|
+
if (!id) {
|
|
9378
|
+
continue;
|
|
9379
|
+
}
|
|
9380
|
+
byId.set(id, row);
|
|
9381
|
+
const parentId = readString8(row, "parent_session_id");
|
|
9382
|
+
if (parentId) {
|
|
9383
|
+
const children = childrenByParent.get(parentId) ?? [];
|
|
9384
|
+
children.push(row);
|
|
9385
|
+
childrenByParent.set(parentId, children);
|
|
9386
|
+
}
|
|
9387
|
+
}
|
|
9388
|
+
const projected = [];
|
|
9389
|
+
for (const row of rows) {
|
|
9390
|
+
const id = readString8(row, "id");
|
|
9391
|
+
if (!id || readString8(row, "parent_session_id")) {
|
|
9392
|
+
continue;
|
|
9393
|
+
}
|
|
9394
|
+
let tip = row;
|
|
9395
|
+
const visited = /* @__PURE__ */ new Set([id]);
|
|
9396
|
+
while (readString8(tip, "end_reason") === "compression") {
|
|
9397
|
+
const tipId2 = readString8(tip, "id");
|
|
9398
|
+
if (!tipId2) {
|
|
9399
|
+
break;
|
|
9400
|
+
}
|
|
9401
|
+
const next = (childrenByParent.get(tipId2) ?? []).filter((child) => readString8(child, "id")).sort(
|
|
9402
|
+
(left, right) => (readNumber2(right.last_active) ?? 0) - (readNumber2(left.last_active) ?? 0)
|
|
9403
|
+
)[0];
|
|
9404
|
+
const nextId = next ? readString8(next, "id") : null;
|
|
9405
|
+
if (!next || !nextId || visited.has(nextId)) {
|
|
9406
|
+
break;
|
|
9407
|
+
}
|
|
9408
|
+
tip = next;
|
|
9409
|
+
visited.add(nextId);
|
|
9410
|
+
}
|
|
9411
|
+
const tipId = readString8(tip, "id");
|
|
9412
|
+
if (tipId) {
|
|
9413
|
+
projected.push({
|
|
9414
|
+
...tip,
|
|
9415
|
+
id: tipId,
|
|
9416
|
+
_lineage_root_id: id,
|
|
9417
|
+
started_at: readNumber2(row.started_at) ?? readNumber2(tip.started_at)
|
|
9418
|
+
});
|
|
9419
|
+
}
|
|
9420
|
+
}
|
|
9421
|
+
return projected;
|
|
9422
|
+
}
|
|
9423
|
+
async function readHermesSessionMessages(candidate) {
|
|
9424
|
+
const [dbMessages, jsonlMessages] = await Promise.all([
|
|
9425
|
+
readStateDbMessages(candidate.dbPath, candidate.session.id),
|
|
9426
|
+
readJsonlMessages(candidate.profileName, candidate.session.id)
|
|
9427
|
+
]);
|
|
9428
|
+
return jsonlMessages.length > dbMessages.length ? jsonlMessages : dbMessages;
|
|
9429
|
+
}
|
|
9430
|
+
async function readStateDbMessages(dbPath, sessionId) {
|
|
9431
|
+
if (!await isFile(dbPath)) {
|
|
9432
|
+
return [];
|
|
9433
|
+
}
|
|
9434
|
+
let db = null;
|
|
9435
|
+
try {
|
|
9436
|
+
const { DatabaseSync } = nodeRequire3(
|
|
9437
|
+
"node:sqlite"
|
|
9438
|
+
);
|
|
9439
|
+
db = new DatabaseSync(dbPath, {
|
|
9440
|
+
readOnly: true,
|
|
9441
|
+
timeout: 1e3
|
|
9442
|
+
});
|
|
9443
|
+
const columns = readTableColumns(db, "messages");
|
|
9444
|
+
if (!columns.has("session_id") || !columns.has("role")) {
|
|
9445
|
+
return [];
|
|
9446
|
+
}
|
|
9447
|
+
const selectColumns = MESSAGE_COLUMNS.map(
|
|
9448
|
+
(column) => columns.has(column) ? quoteIdentifier(column) : `NULL AS ${column}`
|
|
9449
|
+
).join(", ");
|
|
9450
|
+
return db.prepare(
|
|
9451
|
+
`
|
|
9452
|
+
SELECT ${selectColumns}
|
|
9453
|
+
FROM messages
|
|
9454
|
+
WHERE session_id = ?
|
|
9455
|
+
ORDER BY timestamp, id
|
|
9456
|
+
`
|
|
9457
|
+
).all(sessionId);
|
|
9458
|
+
} catch {
|
|
9459
|
+
return [];
|
|
9460
|
+
} finally {
|
|
9461
|
+
db?.close();
|
|
9462
|
+
}
|
|
9463
|
+
}
|
|
9464
|
+
async function readJsonlMessages(profileName, sessionId) {
|
|
9465
|
+
if (!/^[A-Za-z0-9._:-]{1,160}$/u.test(sessionId)) {
|
|
9466
|
+
return [];
|
|
9467
|
+
}
|
|
9468
|
+
const profileDir = resolveHermesProfileDir(profileName);
|
|
9469
|
+
const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path13.join(profileDir, "sessions"));
|
|
9470
|
+
const transcriptPath = path13.join(sessionsDir, `${sessionId}.jsonl`);
|
|
9471
|
+
const raw = await readFile8(transcriptPath, "utf8").catch((error) => {
|
|
9472
|
+
if (isNodeError9(error, "ENOENT")) {
|
|
9473
|
+
return "";
|
|
9474
|
+
}
|
|
9475
|
+
throw error;
|
|
9476
|
+
});
|
|
9477
|
+
if (!raw.trim()) {
|
|
9478
|
+
return [];
|
|
9479
|
+
}
|
|
9480
|
+
const rows = [];
|
|
9481
|
+
for (const line of raw.split(/\r?\n/u)) {
|
|
9482
|
+
if (!line.trim()) {
|
|
9483
|
+
continue;
|
|
9484
|
+
}
|
|
9485
|
+
try {
|
|
9486
|
+
const parsed = JSON.parse(line);
|
|
9487
|
+
const normalized = normalizeJsonlMessage(parsed);
|
|
9488
|
+
if (normalized) {
|
|
9489
|
+
rows.push(normalized);
|
|
9490
|
+
}
|
|
9491
|
+
} catch {
|
|
9492
|
+
continue;
|
|
9493
|
+
}
|
|
9494
|
+
}
|
|
9495
|
+
return rows;
|
|
9496
|
+
}
|
|
9497
|
+
function normalizeJsonlMessage(row) {
|
|
9498
|
+
const role = readString8(row, "role");
|
|
9499
|
+
if (!role) {
|
|
9500
|
+
return null;
|
|
9501
|
+
}
|
|
9502
|
+
const content = normalizeContent(row.content);
|
|
9503
|
+
const timestamp = readNumber2(row.timestamp) ?? readNumber2(row.created_at) ?? readNumber2(row.createdAt);
|
|
9504
|
+
return {
|
|
9505
|
+
...row,
|
|
9506
|
+
role,
|
|
9507
|
+
content,
|
|
9508
|
+
timestamp: timestamp ?? void 0
|
|
9509
|
+
};
|
|
9510
|
+
}
|
|
9511
|
+
function toLinkMessage(input) {
|
|
9512
|
+
const role = normalizeMessageRole(input.message.role);
|
|
9513
|
+
const text = normalizeContent(input.message.content);
|
|
9514
|
+
const createdAt = isoFromHermesTime(input.message.timestamp) ?? new Date(Date.now() + input.index).toISOString();
|
|
9515
|
+
return {
|
|
9516
|
+
id: `msg_${randomUUID6().replaceAll("-", "")}`,
|
|
9517
|
+
schema_version: 1,
|
|
9518
|
+
conversation_id: input.conversationId,
|
|
9519
|
+
role,
|
|
9520
|
+
status: "completed",
|
|
9521
|
+
created_at: createdAt,
|
|
9522
|
+
updated_at: createdAt,
|
|
9523
|
+
sender: senderForRole({
|
|
9524
|
+
role,
|
|
9525
|
+
profileName: input.profileName,
|
|
9526
|
+
profileUid: input.profileUid,
|
|
9527
|
+
profileDisplayName: input.profileDisplayName
|
|
9528
|
+
}),
|
|
9529
|
+
parts: text ? [{ type: "text", text }] : [],
|
|
9530
|
+
attachments: [],
|
|
9531
|
+
hermes: {
|
|
9532
|
+
session_id: input.sessionId,
|
|
9533
|
+
message_id: input.message.id,
|
|
9534
|
+
imported_from: "hermes",
|
|
9535
|
+
import_projection: HERMES_IMPORT_PROJECTION_VERSION
|
|
9536
|
+
},
|
|
9537
|
+
raw: {
|
|
9538
|
+
format: "hermes-message",
|
|
9539
|
+
payload: input.message
|
|
9540
|
+
}
|
|
9541
|
+
};
|
|
9542
|
+
}
|
|
9543
|
+
function senderForRole(input) {
|
|
9544
|
+
switch (input.role) {
|
|
9545
|
+
case "user":
|
|
9546
|
+
return { id: "hermes_user", type: "human", display_name: "Me" };
|
|
9547
|
+
case "assistant":
|
|
9548
|
+
return {
|
|
9549
|
+
id: `agent_${input.profileName}`,
|
|
9550
|
+
type: "agent",
|
|
9551
|
+
display_name: input.profileDisplayName,
|
|
9552
|
+
profile_uid: input.profileUid,
|
|
9553
|
+
profile: input.profileName
|
|
9554
|
+
};
|
|
9555
|
+
case "tool":
|
|
9556
|
+
return { id: "hermes_tool", type: "tool", display_name: "Tool" };
|
|
9557
|
+
case "system":
|
|
9558
|
+
return { id: "hermes_system", type: "system", display_name: "System" };
|
|
9559
|
+
}
|
|
9560
|
+
}
|
|
9561
|
+
function firstUserText(snapshot) {
|
|
9562
|
+
return snapshot.messages.find((message) => message.role === "user")?.parts.find((part) => part.type === "text")?.text?.slice(0, 80);
|
|
9563
|
+
}
|
|
9564
|
+
function normalizeTitle(value) {
|
|
9565
|
+
const normalized = value?.replace(/\s+/gu, " ").trim();
|
|
9566
|
+
return normalized || DEFAULT_CONVERSATION_TITLE;
|
|
9567
|
+
}
|
|
9568
|
+
function normalizeMessageRole(value) {
|
|
9569
|
+
switch (value?.trim().toLowerCase()) {
|
|
9570
|
+
case "user":
|
|
9571
|
+
return "user";
|
|
9572
|
+
case "assistant":
|
|
9573
|
+
return "assistant";
|
|
9574
|
+
case "tool":
|
|
9575
|
+
return "tool";
|
|
9576
|
+
case "system":
|
|
9577
|
+
return "system";
|
|
9578
|
+
default:
|
|
9579
|
+
return "system";
|
|
9580
|
+
}
|
|
9581
|
+
}
|
|
9582
|
+
function normalizeContent(value) {
|
|
9583
|
+
if (typeof value === "string") {
|
|
9584
|
+
return value;
|
|
9585
|
+
}
|
|
9586
|
+
if (Array.isArray(value)) {
|
|
9587
|
+
return value.map((item) => {
|
|
9588
|
+
if (typeof item === "string") {
|
|
9589
|
+
return item;
|
|
9590
|
+
}
|
|
9591
|
+
if (typeof item === "object" && item !== null) {
|
|
9592
|
+
return readString8(item, "text") ?? "";
|
|
9593
|
+
}
|
|
9594
|
+
return "";
|
|
9595
|
+
}).filter(Boolean).join("");
|
|
9596
|
+
}
|
|
9597
|
+
return "";
|
|
9598
|
+
}
|
|
9599
|
+
function parseJsonValue(value) {
|
|
9600
|
+
if (typeof value !== "string") {
|
|
9601
|
+
return void 0;
|
|
9602
|
+
}
|
|
9603
|
+
const trimmed = value.trim();
|
|
9604
|
+
if (!trimmed) {
|
|
9605
|
+
return void 0;
|
|
9606
|
+
}
|
|
9607
|
+
try {
|
|
9608
|
+
return JSON.parse(trimmed);
|
|
9609
|
+
} catch {
|
|
9610
|
+
return void 0;
|
|
9611
|
+
}
|
|
9612
|
+
}
|
|
9613
|
+
function toRecord7(value) {
|
|
9614
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
9615
|
+
}
|
|
9616
|
+
function isDeletedSession(session) {
|
|
9617
|
+
return readBoolean(session.deleted) || readBoolean(session.is_deleted) || Boolean(readString8(session, "deleted_at")) || ["deleted", "removed"].includes(readString8(session, "status") ?? "");
|
|
9618
|
+
}
|
|
9619
|
+
function isHiddenSession(session) {
|
|
9620
|
+
const source = readString8(session, "source")?.toLowerCase();
|
|
9621
|
+
const status = readString8(session, "status")?.toLowerCase();
|
|
9622
|
+
const visibility = readString8(session, "visibility")?.toLowerCase();
|
|
9623
|
+
return Boolean(source && HIDDEN_SESSION_SOURCES.has(source)) || readBoolean(session.hidden) || readBoolean(session.archived) || Boolean(readString8(session, "archived_at")) || status === "hidden" || status === "archived" || visibility === "hidden" || visibility === "hide";
|
|
9624
|
+
}
|
|
9625
|
+
function readTableColumns(db, tableName) {
|
|
9626
|
+
try {
|
|
9627
|
+
const rows = db.prepare(`PRAGMA table_info(${quoteIdentifier(tableName)})`).all();
|
|
9628
|
+
return new Set(
|
|
9629
|
+
rows.map((row) => typeof row.name === "string" ? row.name : "").filter(Boolean)
|
|
9630
|
+
);
|
|
9631
|
+
} catch {
|
|
9632
|
+
return /* @__PURE__ */ new Set();
|
|
9633
|
+
}
|
|
9634
|
+
}
|
|
9635
|
+
function quoteIdentifier(value) {
|
|
9636
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
9637
|
+
}
|
|
9638
|
+
async function isFile(filePath) {
|
|
9639
|
+
return stat7(filePath).then((value) => value.isFile()).catch((error) => {
|
|
9640
|
+
if (isNodeError9(error, "ENOENT")) {
|
|
9641
|
+
return false;
|
|
9642
|
+
}
|
|
9643
|
+
throw error;
|
|
9644
|
+
});
|
|
9645
|
+
}
|
|
9646
|
+
function createConversationId() {
|
|
9647
|
+
return `conv_${randomUUID6().replaceAll("-", "")}`;
|
|
9648
|
+
}
|
|
9649
|
+
function isoFromHermesTime(value) {
|
|
9650
|
+
const numeric = readNumber2(value);
|
|
9651
|
+
if (!numeric || numeric <= 0) {
|
|
9652
|
+
return void 0;
|
|
9653
|
+
}
|
|
9654
|
+
const millis = numeric > 1e10 ? numeric : numeric * 1e3;
|
|
9655
|
+
return new Date(millis).toISOString();
|
|
9656
|
+
}
|
|
9657
|
+
function readString8(payload, key) {
|
|
9658
|
+
const value = payload[key];
|
|
9659
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
9660
|
+
}
|
|
9661
|
+
function readNumber2(value) {
|
|
9662
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
9663
|
+
}
|
|
9664
|
+
function readBoolean(value) {
|
|
9665
|
+
if (value === true || value === 1) {
|
|
9666
|
+
return true;
|
|
9667
|
+
}
|
|
9668
|
+
if (typeof value === "string") {
|
|
9669
|
+
return ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
|
|
9670
|
+
}
|
|
9671
|
+
return false;
|
|
9672
|
+
}
|
|
9673
|
+
function isNodeError9(error, code) {
|
|
9674
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
9675
|
+
}
|
|
9676
|
+
|
|
9677
|
+
// src/conversations/delivery-import.ts
|
|
9678
|
+
import { lstat, readFile as readFile9, readdir as readdir6, stat as stat8 } from "fs/promises";
|
|
9679
|
+
import path14 from "path";
|
|
9680
|
+
var MAX_IMPORTED_BLOB_BYTES = 100 * 1024 * 1024;
|
|
9681
|
+
var MAX_MEDIA_IMPORT_FAILURES = 20;
|
|
9682
|
+
var MAX_DELIVERY_FILES = 50;
|
|
9683
|
+
var DELIVERY_STAGING_SEGMENT = "delivery-staging";
|
|
9684
|
+
var SUPPORTED_DELIVERY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
9685
|
+
".png",
|
|
9686
|
+
".jpg",
|
|
9687
|
+
".jpeg",
|
|
9688
|
+
".gif",
|
|
9689
|
+
".webp",
|
|
9690
|
+
".heic",
|
|
9691
|
+
".pdf",
|
|
9692
|
+
".txt",
|
|
9693
|
+
".log",
|
|
9694
|
+
".md",
|
|
9695
|
+
".markdown",
|
|
9696
|
+
".json",
|
|
9697
|
+
".jsonl",
|
|
9698
|
+
".yaml",
|
|
9699
|
+
".yml",
|
|
9700
|
+
".toml",
|
|
9701
|
+
".ini",
|
|
9702
|
+
".xml",
|
|
9703
|
+
".html",
|
|
9704
|
+
".css",
|
|
9705
|
+
".js",
|
|
9706
|
+
".ts",
|
|
9707
|
+
".jsx",
|
|
9708
|
+
".tsx",
|
|
9709
|
+
".dart",
|
|
9710
|
+
".py",
|
|
9711
|
+
".java",
|
|
9712
|
+
".kt",
|
|
9713
|
+
".swift",
|
|
9714
|
+
".go",
|
|
9715
|
+
".rs",
|
|
9716
|
+
".rb",
|
|
9717
|
+
".php",
|
|
9718
|
+
".c",
|
|
9719
|
+
".cc",
|
|
9720
|
+
".cpp",
|
|
9721
|
+
".h",
|
|
9722
|
+
".hpp",
|
|
9723
|
+
".cs",
|
|
9724
|
+
".sql",
|
|
9725
|
+
".csv",
|
|
9726
|
+
".tsv",
|
|
9727
|
+
".doc",
|
|
9728
|
+
".docx",
|
|
9729
|
+
".xls",
|
|
9730
|
+
".xlsx",
|
|
9731
|
+
".ppt",
|
|
9732
|
+
".pptx",
|
|
9733
|
+
".zip",
|
|
9734
|
+
".rar",
|
|
9735
|
+
".7z",
|
|
9736
|
+
".tar",
|
|
9737
|
+
".gz",
|
|
9738
|
+
".mp4",
|
|
9739
|
+
".mov",
|
|
9740
|
+
".avi",
|
|
9741
|
+
".mkv",
|
|
9742
|
+
".webm",
|
|
9743
|
+
".ogg",
|
|
9744
|
+
".opus",
|
|
9745
|
+
".mp3",
|
|
9746
|
+
".wav",
|
|
9747
|
+
".m4a"
|
|
9748
|
+
]);
|
|
9749
|
+
function resolveDeliveryStagingTarget(paths, stagingDir) {
|
|
9750
|
+
const resolvedDir = path14.resolve(stagingDir);
|
|
9751
|
+
const relative = path14.relative(path14.resolve(paths.conversationsDir), resolvedDir);
|
|
9752
|
+
if (!relative || relative.startsWith("..") || path14.isAbsolute(relative)) {
|
|
9753
|
+
throw new LinkHttpError(
|
|
9754
|
+
400,
|
|
9755
|
+
"delivery_staging_invalid",
|
|
9756
|
+
"delivery staging directory must be inside Hermes Link conversations"
|
|
9757
|
+
);
|
|
9758
|
+
}
|
|
9759
|
+
const segments = relative.split(path14.sep);
|
|
9760
|
+
if (segments.length !== 3 || segments[1] !== DELIVERY_STAGING_SEGMENT || !segments[0] || !segments[2]) {
|
|
9761
|
+
throw new LinkHttpError(
|
|
9762
|
+
400,
|
|
9763
|
+
"delivery_staging_invalid",
|
|
9764
|
+
"delivery staging directory is invalid"
|
|
9765
|
+
);
|
|
9766
|
+
}
|
|
9767
|
+
return {
|
|
9768
|
+
conversationId: segments[0],
|
|
9769
|
+
runId: segments[2],
|
|
9770
|
+
stagingDir: resolvedDir
|
|
9771
|
+
};
|
|
9772
|
+
}
|
|
9773
|
+
async function collectStagedDeliveryReferences(stagingDir) {
|
|
9774
|
+
const directoryStat = await lstat(stagingDir).catch((error) => {
|
|
9775
|
+
if (isNodeError10(error, "ENOENT")) {
|
|
9776
|
+
throw new LinkHttpError(
|
|
9777
|
+
404,
|
|
9778
|
+
"delivery_staging_not_found",
|
|
9779
|
+
"delivery staging directory was not found"
|
|
9780
|
+
);
|
|
9781
|
+
}
|
|
9782
|
+
throw error;
|
|
9783
|
+
});
|
|
9784
|
+
if (!directoryStat.isDirectory()) {
|
|
9785
|
+
throw new LinkHttpError(
|
|
9786
|
+
400,
|
|
9787
|
+
"delivery_staging_not_directory",
|
|
9788
|
+
"delivery staging path is not a directory"
|
|
9789
|
+
);
|
|
9790
|
+
}
|
|
9791
|
+
const entries = await readdir6(stagingDir, { withFileTypes: true });
|
|
9792
|
+
return entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).filter((entry) => isSupportedDeliveryFilename(entry.name)).sort(
|
|
9793
|
+
(left, right) => left.name.localeCompare(right.name, "en", { numeric: true })
|
|
9794
|
+
).slice(0, MAX_DELIVERY_FILES).map((entry) => {
|
|
9795
|
+
const sourcePath = path14.join(stagingDir, entry.name);
|
|
9796
|
+
const mime = inferMimeType(sourcePath);
|
|
9797
|
+
return {
|
|
9798
|
+
path: sourcePath,
|
|
9799
|
+
kind: mediaKindForMime(mime),
|
|
9800
|
+
mime
|
|
9801
|
+
};
|
|
9802
|
+
});
|
|
9803
|
+
}
|
|
9804
|
+
async function importMediaReferencesForMessage(deps, input) {
|
|
9805
|
+
const references = input.references.slice(0, input.maxReferences ?? MAX_DELIVERY_FILES);
|
|
9806
|
+
if (references.length === 0) {
|
|
9807
|
+
return emptyImportResult(input);
|
|
9808
|
+
}
|
|
9809
|
+
const snapshot = await deps.readSnapshot(input.conversationId);
|
|
9810
|
+
const assistant = snapshot.messages.find(
|
|
9811
|
+
(message) => message.id === input.messageId
|
|
9812
|
+
);
|
|
9813
|
+
if (!assistant) {
|
|
9814
|
+
return emptyImportResult(input);
|
|
9815
|
+
}
|
|
9816
|
+
const importedSourceKeys = readImportedMediaSourceKeys(assistant);
|
|
9817
|
+
const failedSourceKeys = readFailedMediaSourceKeys(assistant);
|
|
9818
|
+
const failureRecordsByKey = new Map(
|
|
9819
|
+
readMediaImportFailures(assistant).map((failure) => [failure.key, failure])
|
|
9820
|
+
);
|
|
9821
|
+
const importedParts = [];
|
|
9822
|
+
const newFailures = [];
|
|
9823
|
+
let skippedCount = 0;
|
|
9824
|
+
for (const reference of references) {
|
|
9825
|
+
let sourceKey;
|
|
9826
|
+
try {
|
|
9827
|
+
sourceKey = mediaSourceKey(reference.path);
|
|
9828
|
+
if (importedSourceKeys.has(sourceKey) || failedSourceKeys.has(sourceKey)) {
|
|
9829
|
+
skippedCount += 1;
|
|
9830
|
+
continue;
|
|
9831
|
+
}
|
|
9832
|
+
const blob = await writeBlobFromFile(deps, input.conversationId, reference);
|
|
9833
|
+
const part = {
|
|
9834
|
+
type: reference.kind ?? mediaKindForMime(blob.mime),
|
|
9835
|
+
blob: blob.id,
|
|
9836
|
+
mime: blob.mime,
|
|
9837
|
+
size: blob.size,
|
|
9838
|
+
filename: blob.filename,
|
|
9839
|
+
url: `/api/v1/conversations/${encodeURIComponent(input.conversationId)}/blobs/${encodeURIComponent(blob.id)}`
|
|
9840
|
+
};
|
|
9841
|
+
assistant.parts.push(part);
|
|
9842
|
+
assistant.attachments.push({
|
|
9843
|
+
blob_id: blob.id,
|
|
9844
|
+
mime: blob.mime,
|
|
9845
|
+
size: blob.size,
|
|
9846
|
+
filename: blob.filename,
|
|
9847
|
+
source: "hermes_output"
|
|
9848
|
+
});
|
|
9849
|
+
importedSourceKeys.add(sourceKey);
|
|
9850
|
+
importedParts.push(part);
|
|
9851
|
+
} catch (error) {
|
|
9852
|
+
if (sourceKey && !failedSourceKeys.has(sourceKey)) {
|
|
9853
|
+
const failure = describeMediaImportFailure(reference, sourceKey, error);
|
|
9854
|
+
failedSourceKeys.add(sourceKey);
|
|
9855
|
+
failureRecordsByKey.set(sourceKey, failure);
|
|
9856
|
+
newFailures.push(failure);
|
|
9857
|
+
}
|
|
9858
|
+
void deps.logger.warn("conversation_media_import_failed", {
|
|
9859
|
+
conversation_id: input.conversationId,
|
|
9860
|
+
run_id: input.runId,
|
|
9861
|
+
message_id: input.messageId,
|
|
9862
|
+
error: error instanceof Error ? error.message : String(error)
|
|
9863
|
+
});
|
|
9864
|
+
}
|
|
9865
|
+
}
|
|
9866
|
+
if (importedParts.length === 0 && newFailures.length === 0) {
|
|
9867
|
+
return {
|
|
9868
|
+
...emptyImportResult(input),
|
|
9869
|
+
discovered_count: references.length,
|
|
9870
|
+
skipped_count: skippedCount
|
|
9871
|
+
};
|
|
9872
|
+
}
|
|
9873
|
+
assistant.hermes = {
|
|
9874
|
+
...toRecord8(assistant.hermes),
|
|
9875
|
+
imported_media_source_keys: [...importedSourceKeys],
|
|
9876
|
+
media_import_failed_source_keys: [...failedSourceKeys],
|
|
9877
|
+
media_import_failures: [...failureRecordsByKey.values()].slice(
|
|
9878
|
+
-MAX_MEDIA_IMPORT_FAILURES
|
|
9879
|
+
)
|
|
9880
|
+
};
|
|
9881
|
+
assistant.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
9882
|
+
await deps.writeSnapshot(input.conversationId, snapshot);
|
|
9883
|
+
let lastEventSeq;
|
|
9884
|
+
if (importedParts.length > 0) {
|
|
9885
|
+
const event = await deps.appendEvent(input.conversationId, {
|
|
9886
|
+
type: "message.parts.created",
|
|
9887
|
+
message_id: input.messageId,
|
|
9888
|
+
run_id: input.runId,
|
|
9889
|
+
payload: { parts: importedParts }
|
|
9890
|
+
});
|
|
9891
|
+
lastEventSeq = event.seq;
|
|
9892
|
+
}
|
|
9893
|
+
return {
|
|
9894
|
+
conversation_id: input.conversationId,
|
|
9895
|
+
run_id: input.runId,
|
|
9896
|
+
message_id: input.messageId,
|
|
9897
|
+
discovered_count: references.length,
|
|
9898
|
+
imported_count: importedParts.length,
|
|
9899
|
+
skipped_count: skippedCount,
|
|
9900
|
+
failed_count: newFailures.length,
|
|
9901
|
+
parts: importedParts,
|
|
9902
|
+
...lastEventSeq ? { last_event_seq: lastEventSeq } : {}
|
|
9903
|
+
};
|
|
9904
|
+
}
|
|
9905
|
+
function readMediaImportFailures(message) {
|
|
9906
|
+
const hermes = toRecord8(message.hermes);
|
|
9907
|
+
const failures = hermes.media_import_failures;
|
|
9908
|
+
if (!Array.isArray(failures)) {
|
|
9909
|
+
return [];
|
|
9910
|
+
}
|
|
9911
|
+
return failures.flatMap((item) => {
|
|
9912
|
+
const record = toRecord8(item);
|
|
9913
|
+
const key = readString9(record, "key");
|
|
9914
|
+
const filename = readString9(record, "filename");
|
|
9915
|
+
const reason = readString9(record, "reason");
|
|
9916
|
+
if (!key || !filename || !reason) {
|
|
9917
|
+
return [];
|
|
9918
|
+
}
|
|
9919
|
+
return [
|
|
9920
|
+
{
|
|
9921
|
+
key,
|
|
9922
|
+
filename,
|
|
9923
|
+
reason,
|
|
9924
|
+
...readString9(record, "code") ? { code: readString9(record, "code") } : {}
|
|
9925
|
+
}
|
|
9926
|
+
];
|
|
9927
|
+
});
|
|
9928
|
+
}
|
|
9929
|
+
function readFailedMediaSourceKeys(message) {
|
|
9930
|
+
const hermes = toRecord8(message.hermes);
|
|
9931
|
+
const keys = hermes.media_import_failed_source_keys;
|
|
9932
|
+
if (!Array.isArray(keys)) {
|
|
9933
|
+
return /* @__PURE__ */ new Set();
|
|
9934
|
+
}
|
|
9935
|
+
return new Set(
|
|
9936
|
+
keys.filter(
|
|
9937
|
+
(key) => typeof key === "string" && key.length > 0
|
|
9938
|
+
)
|
|
9939
|
+
);
|
|
9940
|
+
}
|
|
9941
|
+
function emptyImportResult(input) {
|
|
9942
|
+
return {
|
|
9943
|
+
conversation_id: input.conversationId,
|
|
9944
|
+
run_id: input.runId,
|
|
9945
|
+
message_id: input.messageId,
|
|
9946
|
+
discovered_count: 0,
|
|
9947
|
+
imported_count: 0,
|
|
9948
|
+
skipped_count: 0,
|
|
9949
|
+
failed_count: 0,
|
|
9950
|
+
parts: []
|
|
9951
|
+
};
|
|
9952
|
+
}
|
|
9953
|
+
async function writeBlobFromFile(deps, conversationId, source) {
|
|
9954
|
+
const sourcePath = resolveMediaSourcePath(source.path);
|
|
9955
|
+
const fileStat = await stat8(sourcePath).catch((error) => {
|
|
9956
|
+
if (isNodeError10(error, "ENOENT")) {
|
|
9957
|
+
throw new LinkHttpError(
|
|
9958
|
+
404,
|
|
9959
|
+
"media_source_not_found",
|
|
9960
|
+
"Hermes output file was not found"
|
|
9961
|
+
);
|
|
9962
|
+
}
|
|
9963
|
+
throw error;
|
|
9964
|
+
});
|
|
9965
|
+
if (!fileStat.isFile()) {
|
|
9966
|
+
throw new LinkHttpError(
|
|
9967
|
+
400,
|
|
9968
|
+
"media_source_not_file",
|
|
9969
|
+
"Hermes output media source is not a file"
|
|
9970
|
+
);
|
|
9971
|
+
}
|
|
9972
|
+
if (fileStat.size > MAX_IMPORTED_BLOB_BYTES) {
|
|
9973
|
+
throw new LinkHttpError(
|
|
9974
|
+
413,
|
|
9975
|
+
"media_source_too_large",
|
|
9976
|
+
"Hermes output media source is too large"
|
|
9977
|
+
);
|
|
9978
|
+
}
|
|
9979
|
+
return deps.writeBlob(conversationId, {
|
|
9980
|
+
bytes: await readFile9(sourcePath),
|
|
9981
|
+
filename: path14.basename(sourcePath),
|
|
9982
|
+
mime: source.mime ?? inferMimeType(sourcePath)
|
|
9983
|
+
});
|
|
9984
|
+
}
|
|
9985
|
+
function describeMediaImportFailure(reference, sourceKey, error) {
|
|
9986
|
+
return {
|
|
9987
|
+
key: sourceKey,
|
|
9988
|
+
filename: sanitizeFilename(reference.path, "attachment"),
|
|
9989
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
9990
|
+
...isNodeError10(error) && error.code ? { code: error.code } : {}
|
|
9991
|
+
};
|
|
9992
|
+
}
|
|
9993
|
+
function isSupportedDeliveryFilename(filename) {
|
|
9994
|
+
return SUPPORTED_DELIVERY_EXTENSIONS.has(path14.extname(filename).toLowerCase());
|
|
9995
|
+
}
|
|
9996
|
+
function readString9(payload, key) {
|
|
9997
|
+
const value = payload[key];
|
|
9998
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
9999
|
+
}
|
|
10000
|
+
function toRecord8(value) {
|
|
10001
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
10002
|
+
}
|
|
10003
|
+
function isNodeError10(error, code) {
|
|
10004
|
+
return typeof error === "object" && error !== null && "code" in error && (code === void 0 || error.code === code);
|
|
10005
|
+
}
|
|
10006
|
+
|
|
10007
|
+
// src/conversations/run-lifecycle.ts
|
|
10008
|
+
import { readdir as readdir7 } from "fs/promises";
|
|
10009
|
+
|
|
10010
|
+
// src/hermes/api-server.ts
|
|
10011
|
+
async function listHermesModels(options = {}) {
|
|
10012
|
+
const response = await callHermesApi("/v1/models", { method: "GET" }, options);
|
|
10013
|
+
if (response.status === 404) {
|
|
10014
|
+
return { models: [] };
|
|
10015
|
+
}
|
|
10016
|
+
return await readJsonResponse(response);
|
|
10017
|
+
}
|
|
10018
|
+
async function listHermesCronJobs(options = {}) {
|
|
10019
|
+
const query = options.includeDisabled ? "?include_disabled=true" : "";
|
|
10020
|
+
const response = await callHermesApi(
|
|
10021
|
+
`/api/jobs${query}`,
|
|
10022
|
+
{ method: "GET" },
|
|
10023
|
+
options
|
|
10024
|
+
);
|
|
10025
|
+
const payload = await readJsonResponse(response);
|
|
10026
|
+
const jobs = payload.jobs;
|
|
10027
|
+
return Array.isArray(jobs) ? jobs.filter(isRecord).map((job) => ({ ...job })) : [];
|
|
10028
|
+
}
|
|
10029
|
+
async function getHermesCronJob(jobId, options = {}) {
|
|
10030
|
+
const response = await callHermesApi(
|
|
10031
|
+
`/api/jobs/${encodeURIComponent(jobId)}`,
|
|
10032
|
+
{ method: "GET" },
|
|
10033
|
+
options
|
|
10034
|
+
);
|
|
10035
|
+
const payload = await readJsonResponse(response);
|
|
10036
|
+
if (!isRecord(payload.job)) {
|
|
10037
|
+
throw new LinkHttpError(
|
|
10038
|
+
502,
|
|
10039
|
+
"hermes_cron_job_invalid",
|
|
10040
|
+
"Hermes API Server did not return a cron job"
|
|
10041
|
+
);
|
|
10042
|
+
}
|
|
10043
|
+
return { ...payload.job };
|
|
10044
|
+
}
|
|
10045
|
+
async function createHermesCronJob(input, options = {}) {
|
|
10046
|
+
const response = await callHermesApi(
|
|
10047
|
+
"/api/jobs",
|
|
10048
|
+
{
|
|
10049
|
+
method: "POST",
|
|
10050
|
+
body: JSON.stringify(input),
|
|
10051
|
+
headers: { "content-type": "application/json" }
|
|
10052
|
+
},
|
|
10053
|
+
options
|
|
10054
|
+
);
|
|
10055
|
+
const payload = await readJsonResponse(response);
|
|
10056
|
+
if (!isRecord(payload.job)) {
|
|
10057
|
+
throw new LinkHttpError(
|
|
10058
|
+
502,
|
|
10059
|
+
"hermes_cron_job_invalid",
|
|
10060
|
+
"Hermes API Server did not return a cron job"
|
|
10061
|
+
);
|
|
10062
|
+
}
|
|
10063
|
+
return { ...payload.job };
|
|
10064
|
+
}
|
|
10065
|
+
async function updateHermesCronJob(jobId, input, options = {}) {
|
|
10066
|
+
const response = await callHermesApi(
|
|
10067
|
+
`/api/jobs/${encodeURIComponent(jobId)}`,
|
|
10068
|
+
{
|
|
10069
|
+
method: "PATCH",
|
|
10070
|
+
body: JSON.stringify(input),
|
|
10071
|
+
headers: { "content-type": "application/json" }
|
|
10072
|
+
},
|
|
10073
|
+
options
|
|
10074
|
+
);
|
|
10075
|
+
const payload = await readJsonResponse(response);
|
|
10076
|
+
if (!isRecord(payload.job)) {
|
|
10077
|
+
throw new LinkHttpError(
|
|
10078
|
+
502,
|
|
10079
|
+
"hermes_cron_job_invalid",
|
|
10080
|
+
"Hermes API Server did not return a cron job"
|
|
10081
|
+
);
|
|
10082
|
+
}
|
|
10083
|
+
return { ...payload.job };
|
|
10084
|
+
}
|
|
10085
|
+
async function deleteHermesCronJob(jobId, options = {}) {
|
|
10086
|
+
const response = await callHermesApi(
|
|
10087
|
+
`/api/jobs/${encodeURIComponent(jobId)}`,
|
|
10088
|
+
{ method: "DELETE" },
|
|
10089
|
+
options
|
|
10090
|
+
);
|
|
10091
|
+
await readJsonResponse(response);
|
|
10092
|
+
}
|
|
10093
|
+
async function runHermesCronJobAction(jobId, action, options = {}) {
|
|
10094
|
+
const response = await callHermesApi(
|
|
10095
|
+
`/api/jobs/${encodeURIComponent(jobId)}/${action}`,
|
|
10096
|
+
{ method: "POST" },
|
|
10097
|
+
options
|
|
10098
|
+
);
|
|
10099
|
+
const payload = await readJsonResponse(response);
|
|
10100
|
+
if (!isRecord(payload.job)) {
|
|
10101
|
+
throw new LinkHttpError(
|
|
10102
|
+
502,
|
|
10103
|
+
"hermes_cron_job_invalid",
|
|
10104
|
+
"Hermes API Server did not return a cron job"
|
|
10105
|
+
);
|
|
10106
|
+
}
|
|
10107
|
+
return { ...payload.job };
|
|
10108
|
+
}
|
|
10109
|
+
async function createHermesRun(input, options = {}) {
|
|
10110
|
+
const response = await callHermesApi(
|
|
10111
|
+
"/v1/runs",
|
|
10112
|
+
{
|
|
10113
|
+
method: "POST",
|
|
10114
|
+
body: JSON.stringify(input),
|
|
10115
|
+
headers: { "content-type": "application/json" }
|
|
10116
|
+
},
|
|
10117
|
+
options
|
|
10118
|
+
);
|
|
10119
|
+
if (response.status === 404 || response.status === 503) {
|
|
10120
|
+
assertHermesRunsApiSupported(
|
|
10121
|
+
await readHermesVersion().catch(() => null),
|
|
10122
|
+
response.status
|
|
10123
|
+
);
|
|
10124
|
+
throw new LinkHttpError(
|
|
10125
|
+
503,
|
|
10126
|
+
"hermes_api_server_unavailable",
|
|
10127
|
+
"Hermes API Server is unavailable"
|
|
10128
|
+
);
|
|
8642
10129
|
}
|
|
8643
10130
|
const payload = await readJsonResponse(response);
|
|
8644
|
-
const runId =
|
|
10131
|
+
const runId = readString10(payload, "run_id") ?? readString10(payload, "runId") ?? readString10(payload, "id");
|
|
8645
10132
|
if (!runId) {
|
|
8646
10133
|
throw new LinkHttpError(
|
|
8647
10134
|
502,
|
|
@@ -8754,10 +10241,10 @@ async function cancelHermesRun(runId, options = {}) {
|
|
|
8754
10241
|
);
|
|
8755
10242
|
}
|
|
8756
10243
|
}
|
|
8757
|
-
async function callHermesApi(
|
|
10244
|
+
async function callHermesApi(path25, init, options) {
|
|
8758
10245
|
const method = init.method ?? "GET";
|
|
8759
10246
|
const startedAt = Date.now();
|
|
8760
|
-
void options.logger?.debug("hermes_api_request_started", { method, path:
|
|
10247
|
+
void options.logger?.debug("hermes_api_request_started", { method, path: path25 });
|
|
8761
10248
|
const availability = await ensureHermesApiServerAvailable({
|
|
8762
10249
|
fetchImpl: options.fetchImpl,
|
|
8763
10250
|
logger: options.logger,
|
|
@@ -8765,21 +10252,21 @@ async function callHermesApi(path24, init, options) {
|
|
|
8765
10252
|
});
|
|
8766
10253
|
let config = availability.configResult.apiServer;
|
|
8767
10254
|
const fetcher = options.fetchImpl ?? fetch;
|
|
8768
|
-
const request = () => fetchHermesApi(fetcher, config,
|
|
10255
|
+
const request = () => fetchHermesApi(fetcher, config, path25, init, options);
|
|
8769
10256
|
let response;
|
|
8770
10257
|
try {
|
|
8771
10258
|
response = await request();
|
|
8772
10259
|
} catch (error) {
|
|
8773
|
-
logHermesApiError(options.logger, method,
|
|
10260
|
+
logHermesApiError(options.logger, method, path25, startedAt, error);
|
|
8774
10261
|
throw error;
|
|
8775
10262
|
}
|
|
8776
10263
|
if (response.status !== 401) {
|
|
8777
|
-
logHermesApiResponse(options.logger, method,
|
|
10264
|
+
logHermesApiResponse(options.logger, method, path25, startedAt, response);
|
|
8778
10265
|
return response;
|
|
8779
10266
|
}
|
|
8780
10267
|
void options.logger?.warn("hermes_api_request_retrying_after_401", {
|
|
8781
10268
|
method,
|
|
8782
|
-
path:
|
|
10269
|
+
path: path25,
|
|
8783
10270
|
duration_ms: Date.now() - startedAt
|
|
8784
10271
|
});
|
|
8785
10272
|
const refreshedAvailability = await ensureHermesApiServerAvailable({
|
|
@@ -8792,20 +10279,20 @@ async function callHermesApi(path24, init, options) {
|
|
|
8792
10279
|
try {
|
|
8793
10280
|
response = await request();
|
|
8794
10281
|
} catch (error) {
|
|
8795
|
-
logHermesApiError(options.logger, method,
|
|
10282
|
+
logHermesApiError(options.logger, method, path25, startedAt, error);
|
|
8796
10283
|
throw error;
|
|
8797
10284
|
}
|
|
8798
|
-
logHermesApiResponse(options.logger, method,
|
|
10285
|
+
logHermesApiResponse(options.logger, method, path25, startedAt, response);
|
|
8799
10286
|
return response;
|
|
8800
10287
|
}
|
|
8801
|
-
async function fetchHermesApi(fetcher, config,
|
|
10288
|
+
async function fetchHermesApi(fetcher, config, path25, init, options) {
|
|
8802
10289
|
const headers = new Headers(init.headers);
|
|
8803
10290
|
headers.set("accept", headers.get("accept") ?? "application/json");
|
|
8804
10291
|
if (config.key) {
|
|
8805
10292
|
headers.set("x-api-key", config.key);
|
|
8806
10293
|
headers.set("authorization", `Bearer ${config.key}`);
|
|
8807
10294
|
}
|
|
8808
|
-
return await fetcher(`http://127.0.0.1:${config.port}${
|
|
10295
|
+
return await fetcher(`http://127.0.0.1:${config.port}${path25}`, {
|
|
8809
10296
|
...init,
|
|
8810
10297
|
headers
|
|
8811
10298
|
}).catch((error) => {
|
|
@@ -8813,7 +10300,7 @@ async function fetchHermesApi(fetcher, config, path24, init, options) {
|
|
|
8813
10300
|
throw error;
|
|
8814
10301
|
}
|
|
8815
10302
|
void options.logger?.warn("hermes_api_server_connect_failed", {
|
|
8816
|
-
path:
|
|
10303
|
+
path: path25,
|
|
8817
10304
|
port: config.port ?? null,
|
|
8818
10305
|
error: error instanceof Error ? error.message : String(error)
|
|
8819
10306
|
});
|
|
@@ -8824,10 +10311,10 @@ async function fetchHermesApi(fetcher, config, path24, init, options) {
|
|
|
8824
10311
|
);
|
|
8825
10312
|
});
|
|
8826
10313
|
}
|
|
8827
|
-
function logHermesApiResponse(logger, method,
|
|
10314
|
+
function logHermesApiResponse(logger, method, path25, startedAt, response) {
|
|
8828
10315
|
const fields = {
|
|
8829
10316
|
method,
|
|
8830
|
-
path:
|
|
10317
|
+
path: path25,
|
|
8831
10318
|
status: response.status,
|
|
8832
10319
|
duration_ms: Date.now() - startedAt
|
|
8833
10320
|
};
|
|
@@ -8847,10 +10334,10 @@ async function logHermesApiFailureResponse(logger, fields, response) {
|
|
|
8847
10334
|
...upstreamError ? { upstream_error: upstreamError } : {}
|
|
8848
10335
|
});
|
|
8849
10336
|
}
|
|
8850
|
-
function logHermesApiError(logger, method,
|
|
10337
|
+
function logHermesApiError(logger, method, path25, startedAt, error) {
|
|
8851
10338
|
void logger?.warn("hermes_api_request_failed", {
|
|
8852
10339
|
method,
|
|
8853
|
-
path:
|
|
10340
|
+
path: path25,
|
|
8854
10341
|
duration_ms: Date.now() - startedAt,
|
|
8855
10342
|
...error instanceof LinkHttpError ? { status: error.status, code: error.code } : {},
|
|
8856
10343
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -8899,23 +10386,23 @@ function isRecord(value) {
|
|
|
8899
10386
|
}
|
|
8900
10387
|
function readUpstreamMessage(payload, raw) {
|
|
8901
10388
|
const error = typeof payload?.error === "object" && payload.error !== null ? payload.error : null;
|
|
8902
|
-
const message =
|
|
10389
|
+
const message = readString10(error ?? {}, "message") ?? readString10(payload ?? {}, "message");
|
|
8903
10390
|
if (message) {
|
|
8904
10391
|
return message;
|
|
8905
10392
|
}
|
|
8906
10393
|
const body = raw.trim().replace(/\s+/gu, " ").slice(0, 500);
|
|
8907
10394
|
return body || "empty response body";
|
|
8908
10395
|
}
|
|
8909
|
-
function
|
|
10396
|
+
function readString10(payload, key) {
|
|
8910
10397
|
const value = payload[key];
|
|
8911
10398
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
8912
10399
|
}
|
|
8913
10400
|
|
|
8914
10401
|
// src/conversations/history-builder.ts
|
|
8915
|
-
import { readFile as
|
|
8916
|
-
import { createRequire as
|
|
8917
|
-
import
|
|
8918
|
-
var
|
|
10402
|
+
import { readFile as readFile10, stat as stat9 } from "fs/promises";
|
|
10403
|
+
import { createRequire as createRequire4 } from "module";
|
|
10404
|
+
import path15 from "path";
|
|
10405
|
+
var nodeRequire4 = createRequire4(import.meta.url);
|
|
8919
10406
|
var HISTORY_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
8920
10407
|
var HERMES_HISTORY_COLUMNS = [
|
|
8921
10408
|
"role",
|
|
@@ -8976,13 +10463,13 @@ async function readHermesTranscriptHistory(sessionId, profileName) {
|
|
|
8976
10463
|
}
|
|
8977
10464
|
const normalizedProfileName = isValidProfileName2(profileName) ? profileName : "default";
|
|
8978
10465
|
const profileDir = resolveHermesProfileDir(normalizedProfileName);
|
|
8979
|
-
const dbPath =
|
|
10466
|
+
const dbPath = path15.join(profileDir, "state.db");
|
|
8980
10467
|
const sessionsDirConfig = await readHermesSessionsDir(normalizedProfileName).then((value) => ({
|
|
8981
10468
|
sessionsDir: value.sessionsDir,
|
|
8982
10469
|
configured: value.configured,
|
|
8983
10470
|
configError: false
|
|
8984
10471
|
})).catch(() => ({
|
|
8985
|
-
sessionsDir:
|
|
10472
|
+
sessionsDir: path15.join(profileDir, "sessions"),
|
|
8986
10473
|
configured: false,
|
|
8987
10474
|
configError: true
|
|
8988
10475
|
}));
|
|
@@ -9022,8 +10509,8 @@ async function readHermesTranscriptHistory(sessionId, profileName) {
|
|
|
9022
10509
|
};
|
|
9023
10510
|
}
|
|
9024
10511
|
async function readHermesStateDbHistory(dbPath, sessionId) {
|
|
9025
|
-
const exists = await
|
|
9026
|
-
if (
|
|
10512
|
+
const exists = await stat9(dbPath).then((value) => value.isFile()).catch((error) => {
|
|
10513
|
+
if (isNodeError11(error, "ENOENT")) {
|
|
9027
10514
|
return false;
|
|
9028
10515
|
}
|
|
9029
10516
|
throw error;
|
|
@@ -9040,9 +10527,9 @@ async function readHermesJsonlHistory(sessionsDir, sessionId) {
|
|
|
9040
10527
|
if (!isValidSessionFileStem(sessionId)) {
|
|
9041
10528
|
return empty;
|
|
9042
10529
|
}
|
|
9043
|
-
const transcriptPath =
|
|
9044
|
-
const raw = await
|
|
9045
|
-
if (
|
|
10530
|
+
const transcriptPath = path15.join(sessionsDir, `${sessionId}.jsonl`);
|
|
10531
|
+
const raw = await readFile10(transcriptPath, "utf8").catch((error) => {
|
|
10532
|
+
if (isNodeError11(error, "ENOENT")) {
|
|
9046
10533
|
return "";
|
|
9047
10534
|
}
|
|
9048
10535
|
throw error;
|
|
@@ -9077,14 +10564,14 @@ async function readHermesJsonlHistory(sessionsDir, sessionId) {
|
|
|
9077
10564
|
function readHistoryRows(dbPath, sessionId) {
|
|
9078
10565
|
let db = null;
|
|
9079
10566
|
try {
|
|
9080
|
-
const { DatabaseSync } =
|
|
10567
|
+
const { DatabaseSync } = nodeRequire4(
|
|
9081
10568
|
"node:sqlite"
|
|
9082
10569
|
);
|
|
9083
10570
|
db = new DatabaseSync(dbPath, {
|
|
9084
10571
|
readOnly: true,
|
|
9085
10572
|
timeout: 1e3
|
|
9086
10573
|
});
|
|
9087
|
-
const columns =
|
|
10574
|
+
const columns = readTableColumns2(db, "messages");
|
|
9088
10575
|
if (!columns.has("role") || !columns.has("content")) {
|
|
9089
10576
|
return [];
|
|
9090
10577
|
}
|
|
@@ -9189,7 +10676,7 @@ function normalizeHistoryMessage(role, content) {
|
|
|
9189
10676
|
if (role !== "user" && role !== "assistant") {
|
|
9190
10677
|
return null;
|
|
9191
10678
|
}
|
|
9192
|
-
const text =
|
|
10679
|
+
const text = normalizeContent2(content).trim();
|
|
9193
10680
|
if (!text) {
|
|
9194
10681
|
return null;
|
|
9195
10682
|
}
|
|
@@ -9200,7 +10687,7 @@ function normalizeHistoryRecord(record) {
|
|
|
9200
10687
|
if (!role) {
|
|
9201
10688
|
return null;
|
|
9202
10689
|
}
|
|
9203
|
-
const content =
|
|
10690
|
+
const content = normalizeContent2(record.content);
|
|
9204
10691
|
const message = { role, content };
|
|
9205
10692
|
assignString(message, "tool_call_id", record.tool_call_id);
|
|
9206
10693
|
assignString(message, "tool_name", record.tool_name);
|
|
@@ -9220,7 +10707,7 @@ function normalizeHistoryRecord(record) {
|
|
|
9220
10707
|
}
|
|
9221
10708
|
return message;
|
|
9222
10709
|
}
|
|
9223
|
-
function
|
|
10710
|
+
function normalizeContent2(value) {
|
|
9224
10711
|
if (typeof value === "string") {
|
|
9225
10712
|
return value;
|
|
9226
10713
|
}
|
|
@@ -9262,12 +10749,12 @@ function hasReplayMetadata(message) {
|
|
|
9262
10749
|
message.tool_call_id || message.tool_name || message.tool_calls || message.reasoning || message.reasoning_content || message.reasoning_details || message.codex_reasoning_items
|
|
9263
10750
|
);
|
|
9264
10751
|
}
|
|
9265
|
-
function
|
|
10752
|
+
function readTableColumns2(db, table) {
|
|
9266
10753
|
return new Set(
|
|
9267
10754
|
db.prepare(`PRAGMA table_info(${table})`).all().map((row) => row.name).filter((name) => typeof name === "string")
|
|
9268
10755
|
);
|
|
9269
10756
|
}
|
|
9270
|
-
function
|
|
10757
|
+
function isNodeError11(error, code) {
|
|
9271
10758
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
9272
10759
|
}
|
|
9273
10760
|
function isValidProfileName2(value) {
|
|
@@ -9285,8 +10772,8 @@ function normalizeProfileForCompare(value) {
|
|
|
9285
10772
|
|
|
9286
10773
|
// src/hermes/stt.ts
|
|
9287
10774
|
import { execFile as execFile3 } from "child_process";
|
|
9288
|
-
import { access as access2, readFile as
|
|
9289
|
-
import
|
|
10775
|
+
import { access as access2, readFile as readFile11, stat as stat10 } from "fs/promises";
|
|
10776
|
+
import path16 from "path";
|
|
9290
10777
|
import { promisify as promisify3 } from "util";
|
|
9291
10778
|
var execFileAsync3 = promisify3(execFile3);
|
|
9292
10779
|
var STT_RESULT_PREFIX = "__HERMES_LINK_STT__";
|
|
@@ -9381,7 +10868,7 @@ async function buildHermesSttEnv(profileName) {
|
|
|
9381
10868
|
};
|
|
9382
10869
|
const devSource = await findDevHermesAgentSource();
|
|
9383
10870
|
if (devSource) {
|
|
9384
|
-
env.PYTHONPATH = [devSource, env.PYTHONPATH].filter(Boolean).join(
|
|
10871
|
+
env.PYTHONPATH = [devSource, env.PYTHONPATH].filter(Boolean).join(path16.delimiter);
|
|
9385
10872
|
}
|
|
9386
10873
|
return env;
|
|
9387
10874
|
}
|
|
@@ -9428,14 +10915,14 @@ async function resolveHermesPythonCommand() {
|
|
|
9428
10915
|
};
|
|
9429
10916
|
}
|
|
9430
10917
|
async function resolveExecutablePath(command) {
|
|
9431
|
-
if (
|
|
10918
|
+
if (path16.isAbsolute(command)) {
|
|
9432
10919
|
return await isExecutableFile(command) ? command : null;
|
|
9433
10920
|
}
|
|
9434
10921
|
const pathEnv = process.env.PATH ?? "";
|
|
9435
10922
|
const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
|
|
9436
|
-
for (const dir of pathEnv.split(
|
|
10923
|
+
for (const dir of pathEnv.split(path16.delimiter)) {
|
|
9437
10924
|
for (const extension of extensions) {
|
|
9438
|
-
const candidate =
|
|
10925
|
+
const candidate = path16.join(dir, `${command}${extension}`);
|
|
9439
10926
|
if (await isExecutableFile(candidate)) {
|
|
9440
10927
|
return candidate;
|
|
9441
10928
|
}
|
|
@@ -9445,7 +10932,7 @@ async function resolveExecutablePath(command) {
|
|
|
9445
10932
|
}
|
|
9446
10933
|
async function isExecutableFile(filePath) {
|
|
9447
10934
|
try {
|
|
9448
|
-
const info = await
|
|
10935
|
+
const info = await stat10(filePath);
|
|
9449
10936
|
if (!info.isFile()) {
|
|
9450
10937
|
return false;
|
|
9451
10938
|
}
|
|
@@ -9456,7 +10943,7 @@ async function isExecutableFile(filePath) {
|
|
|
9456
10943
|
}
|
|
9457
10944
|
}
|
|
9458
10945
|
async function readShebang(filePath) {
|
|
9459
|
-
const raw = await
|
|
10946
|
+
const raw = await readFile11(filePath, "utf8").catch(() => "");
|
|
9460
10947
|
const firstLine = raw.split(/\r?\n/u)[0]?.trim() ?? "";
|
|
9461
10948
|
return firstLine.startsWith("#!") ? firstLine.slice(2).trim() : null;
|
|
9462
10949
|
}
|
|
@@ -9475,8 +10962,8 @@ function shebangToPythonCommand(shebang) {
|
|
|
9475
10962
|
}
|
|
9476
10963
|
async function findDevHermesAgentSource() {
|
|
9477
10964
|
const candidates = [
|
|
9478
|
-
|
|
9479
|
-
|
|
10965
|
+
path16.resolve(process.cwd(), "reference/hermes-agent"),
|
|
10966
|
+
path16.resolve(process.cwd(), "../../reference/hermes-agent")
|
|
9480
10967
|
];
|
|
9481
10968
|
for (const candidate of candidates) {
|
|
9482
10969
|
if (await isDirectory(candidate)) {
|
|
@@ -9486,7 +10973,7 @@ async function findDevHermesAgentSource() {
|
|
|
9486
10973
|
return null;
|
|
9487
10974
|
}
|
|
9488
10975
|
async function isDirectory(candidate) {
|
|
9489
|
-
return
|
|
10976
|
+
return stat10(candidate).then((info) => info.isDirectory()).catch(() => false);
|
|
9490
10977
|
}
|
|
9491
10978
|
function compactProcessOutput(value) {
|
|
9492
10979
|
const compact = value.trim().replace(/\s+/gu, " ").slice(0, 500);
|
|
@@ -9538,8 +11025,8 @@ function parseSseBlock(block) {
|
|
|
9538
11025
|
if (decoded === null) {
|
|
9539
11026
|
return null;
|
|
9540
11027
|
}
|
|
9541
|
-
const payload =
|
|
9542
|
-
const payloadType = (
|
|
11028
|
+
const payload = toRecord9(decoded);
|
|
11029
|
+
const payloadType = (readString11(payload, "type") ?? readString11(payload, "event") ?? readString11(payload, "object") ?? eventName) || "message";
|
|
9543
11030
|
return { eventName, payloadType, payload, rawPayload: decoded ?? raw };
|
|
9544
11031
|
}
|
|
9545
11032
|
function decodeJson(value) {
|
|
@@ -9555,11 +11042,11 @@ function decodeJson(value) {
|
|
|
9555
11042
|
return { type: "message.delta", delta: value };
|
|
9556
11043
|
}
|
|
9557
11044
|
}
|
|
9558
|
-
function
|
|
11045
|
+
function readString11(body, key) {
|
|
9559
11046
|
const value = body[key];
|
|
9560
11047
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
9561
11048
|
}
|
|
9562
|
-
function
|
|
11049
|
+
function toRecord9(value) {
|
|
9563
11050
|
return typeof value === "object" && value !== null ? value : {};
|
|
9564
11051
|
}
|
|
9565
11052
|
|
|
@@ -9583,8 +11070,8 @@ function normalizeHermesStreamEvent(event) {
|
|
|
9583
11070
|
};
|
|
9584
11071
|
}
|
|
9585
11072
|
if (event.eventName === "hermes.tool.progress") {
|
|
9586
|
-
const toolName =
|
|
9587
|
-
const preview =
|
|
11073
|
+
const toolName = readString12(event.payload, "tool") ?? readString12(event.payload, "name") ?? "tool";
|
|
11074
|
+
const preview = readString12(event.payload, "label") ?? readString12(event.payload, "preview") ?? toolName;
|
|
9588
11075
|
return {
|
|
9589
11076
|
...event,
|
|
9590
11077
|
payloadType: "tool.started",
|
|
@@ -9652,12 +11139,12 @@ function normalizeHermesResponseEvent(event) {
|
|
|
9652
11139
|
}
|
|
9653
11140
|
}
|
|
9654
11141
|
function normalizeResponseOutputItemAdded(event) {
|
|
9655
|
-
const item =
|
|
9656
|
-
if (
|
|
11142
|
+
const item = toRecord10(event.payload.item);
|
|
11143
|
+
if (readString12(item, "type") !== "function_call") {
|
|
9657
11144
|
return null;
|
|
9658
11145
|
}
|
|
9659
|
-
const toolName =
|
|
9660
|
-
const argumentsValue =
|
|
11146
|
+
const toolName = readString12(item, "name") ?? "tool";
|
|
11147
|
+
const argumentsValue = parseJsonValue2(item.arguments) ?? item.arguments;
|
|
9661
11148
|
return {
|
|
9662
11149
|
...event,
|
|
9663
11150
|
payloadType: "tool.started",
|
|
@@ -9666,60 +11153,60 @@ function normalizeResponseOutputItemAdded(event) {
|
|
|
9666
11153
|
tool: toolName,
|
|
9667
11154
|
tool_name: toolName,
|
|
9668
11155
|
name: toolName,
|
|
9669
|
-
tool_call_id:
|
|
11156
|
+
tool_call_id: readString12(item, "call_id") ?? readString12(item, "id"),
|
|
9670
11157
|
arguments: argumentsValue,
|
|
9671
11158
|
preview: toolName,
|
|
9672
|
-
response_item_id:
|
|
11159
|
+
response_item_id: readString12(item, "id") ?? void 0
|
|
9673
11160
|
}
|
|
9674
11161
|
};
|
|
9675
11162
|
}
|
|
9676
11163
|
function normalizeResponseOutputItemDone(event) {
|
|
9677
|
-
const item =
|
|
9678
|
-
if (
|
|
11164
|
+
const item = toRecord10(event.payload.item);
|
|
11165
|
+
if (readString12(item, "type") !== "function_call_output") {
|
|
9679
11166
|
return null;
|
|
9680
11167
|
}
|
|
9681
11168
|
const output = readResponseItemOutput(item.output);
|
|
9682
|
-
const parsedOutput =
|
|
11169
|
+
const parsedOutput = parseJsonValue2(output);
|
|
9683
11170
|
return {
|
|
9684
11171
|
...event,
|
|
9685
11172
|
payloadType: "tool.completed",
|
|
9686
11173
|
payload: {
|
|
9687
11174
|
type: "tool.completed",
|
|
9688
|
-
tool_call_id:
|
|
9689
|
-
status:
|
|
11175
|
+
tool_call_id: readString12(item, "call_id") ?? readString12(item, "id"),
|
|
11176
|
+
status: readString12(item, "status") ?? "completed",
|
|
9690
11177
|
output,
|
|
9691
11178
|
content: output,
|
|
9692
11179
|
result: parsedOutput ?? output,
|
|
9693
|
-
response_item_id:
|
|
11180
|
+
response_item_id: readString12(item, "id") ?? void 0
|
|
9694
11181
|
}
|
|
9695
11182
|
};
|
|
9696
11183
|
}
|
|
9697
11184
|
function normalizeResponseCompleted(event) {
|
|
9698
|
-
const response =
|
|
11185
|
+
const response = toRecord10(event.payload.response);
|
|
9699
11186
|
return {
|
|
9700
11187
|
...event,
|
|
9701
11188
|
payloadType: "run.completed",
|
|
9702
11189
|
payload: {
|
|
9703
11190
|
type: "run.completed",
|
|
9704
|
-
response_id:
|
|
9705
|
-
usage:
|
|
11191
|
+
response_id: readString12(response, "id") ?? readString12(event.payload, "id"),
|
|
11192
|
+
usage: toRecord10(response.usage),
|
|
9706
11193
|
response
|
|
9707
11194
|
}
|
|
9708
11195
|
};
|
|
9709
11196
|
}
|
|
9710
11197
|
function normalizeResponseFailed(event) {
|
|
9711
|
-
const response =
|
|
9712
|
-
const error =
|
|
11198
|
+
const response = toRecord10(event.payload.response);
|
|
11199
|
+
const error = toRecord10(response.error);
|
|
9713
11200
|
return {
|
|
9714
11201
|
...event,
|
|
9715
11202
|
payloadType: "run.failed",
|
|
9716
11203
|
payload: {
|
|
9717
11204
|
type: "run.failed",
|
|
9718
|
-
response_id:
|
|
11205
|
+
response_id: readString12(response, "id") ?? readString12(event.payload, "id"),
|
|
9719
11206
|
error: {
|
|
9720
|
-
message:
|
|
11207
|
+
message: readString12(error, "message") ?? readString12(event.payload, "message") ?? "Hermes run failed"
|
|
9721
11208
|
},
|
|
9722
|
-
usage:
|
|
11209
|
+
usage: toRecord10(response.usage),
|
|
9723
11210
|
response
|
|
9724
11211
|
}
|
|
9725
11212
|
};
|
|
@@ -9743,8 +11230,8 @@ function readErrorMessage2(payload) {
|
|
|
9743
11230
|
if (typeof payload.error === "string" && payload.error.trim()) {
|
|
9744
11231
|
return payload.error.trim();
|
|
9745
11232
|
}
|
|
9746
|
-
const error =
|
|
9747
|
-
return
|
|
11233
|
+
const error = toRecord10(payload.error);
|
|
11234
|
+
return readString12(error, "message") ?? readString12(payload, "message");
|
|
9748
11235
|
}
|
|
9749
11236
|
function readDelta(payload) {
|
|
9750
11237
|
return readText2(payload, "delta") ?? readText2(payload, "text") ?? readText2(payload, "content");
|
|
@@ -9779,15 +11266,15 @@ function isTopLevelErrorEvent(event) {
|
|
|
9779
11266
|
}
|
|
9780
11267
|
function readChatCompletionDelta(payload) {
|
|
9781
11268
|
const choice = readFirstChoice(payload);
|
|
9782
|
-
const delta =
|
|
11269
|
+
const delta = toRecord10(choice.delta);
|
|
9783
11270
|
return readText2(delta, "content");
|
|
9784
11271
|
}
|
|
9785
11272
|
function readChatCompletionFinishReason(payload) {
|
|
9786
11273
|
const choice = readFirstChoice(payload);
|
|
9787
|
-
return
|
|
11274
|
+
return readString12(choice, "finish_reason") ?? readString12(choice, "finishReason");
|
|
9788
11275
|
}
|
|
9789
11276
|
function readChatCompletionUsage(payload) {
|
|
9790
|
-
const usage =
|
|
11277
|
+
const usage = toRecord10(payload.usage);
|
|
9791
11278
|
const input = readInteger2(usage, "prompt_tokens") ?? readInteger2(usage, "input_tokens");
|
|
9792
11279
|
const output = readInteger2(usage, "completion_tokens") ?? readInteger2(usage, "output_tokens");
|
|
9793
11280
|
const total = readInteger2(usage, "total_tokens");
|
|
@@ -9815,7 +11302,7 @@ function readFirstChoice(payload) {
|
|
|
9815
11302
|
if (!Array.isArray(choices)) {
|
|
9816
11303
|
return {};
|
|
9817
11304
|
}
|
|
9818
|
-
return
|
|
11305
|
+
return toRecord10(choices[0]);
|
|
9819
11306
|
}
|
|
9820
11307
|
function readInteger2(payload, key) {
|
|
9821
11308
|
const value = payload[key];
|
|
@@ -9828,7 +11315,7 @@ function readInteger2(payload, key) {
|
|
|
9828
11315
|
}
|
|
9829
11316
|
return void 0;
|
|
9830
11317
|
}
|
|
9831
|
-
function
|
|
11318
|
+
function readString12(payload, key) {
|
|
9832
11319
|
const value = payload[key];
|
|
9833
11320
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
9834
11321
|
}
|
|
@@ -9843,10 +11330,10 @@ function readResponseItemOutput(value) {
|
|
|
9843
11330
|
if (!Array.isArray(value)) {
|
|
9844
11331
|
return stringifyJsonValue(value);
|
|
9845
11332
|
}
|
|
9846
|
-
const text = value.map(
|
|
11333
|
+
const text = value.map(toRecord10).map((part) => readText2(part, "text") ?? readText2(part, "content") ?? "").join("");
|
|
9847
11334
|
return text || stringifyJsonValue(value);
|
|
9848
11335
|
}
|
|
9849
|
-
function
|
|
11336
|
+
function parseJsonValue2(value) {
|
|
9850
11337
|
if (typeof value !== "string" || !value.trim()) {
|
|
9851
11338
|
return null;
|
|
9852
11339
|
}
|
|
@@ -9869,13 +11356,11 @@ function stringifyJsonValue(value) {
|
|
|
9869
11356
|
return String(value);
|
|
9870
11357
|
}
|
|
9871
11358
|
}
|
|
9872
|
-
function
|
|
11359
|
+
function toRecord10(value) {
|
|
9873
11360
|
return typeof value === "object" && value !== null ? value : {};
|
|
9874
11361
|
}
|
|
9875
11362
|
|
|
9876
11363
|
// src/conversations/run-lifecycle.ts
|
|
9877
|
-
var MAX_IMPORTED_BLOB_BYTES = 100 * 1024 * 1024;
|
|
9878
|
-
var MAX_MEDIA_IMPORT_FAILURES = 20;
|
|
9879
11364
|
var ConversationRunLifecycle = class {
|
|
9880
11365
|
constructor(deps) {
|
|
9881
11366
|
this.deps = deps;
|
|
@@ -10288,7 +11773,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
10288
11773
|
assistant.agent_events ?? [],
|
|
10289
11774
|
agentEvent
|
|
10290
11775
|
);
|
|
10291
|
-
|
|
11776
|
+
appendAgentEventBlock2(assistant, agentEvent, event.created_at);
|
|
10292
11777
|
assistant.updated_at = event.created_at;
|
|
10293
11778
|
await this.deps.writeSnapshot(conversationId, snapshot);
|
|
10294
11779
|
}
|
|
@@ -10357,8 +11842,8 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
10357
11842
|
}
|
|
10358
11843
|
const textPart = assistant.parts.find((part) => part.type === "text");
|
|
10359
11844
|
const currentText = textPart?.text ?? "";
|
|
10360
|
-
const pendingDeliveryText =
|
|
10361
|
-
|
|
11845
|
+
const pendingDeliveryText = readString13(
|
|
11846
|
+
toRecord11(assistant.hermes),
|
|
10362
11847
|
"pending_media_delivery_text"
|
|
10363
11848
|
);
|
|
10364
11849
|
const normalizedDelta = normalizeStreamingTextDelta(
|
|
@@ -10373,7 +11858,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
10373
11858
|
pendingDeliveryText ?? ""
|
|
10374
11859
|
);
|
|
10375
11860
|
const nextHermes = {
|
|
10376
|
-
...
|
|
11861
|
+
...toRecord11(assistant.hermes),
|
|
10377
11862
|
...extracted.pendingText ? { pending_media_delivery_text: extracted.pendingText } : {}
|
|
10378
11863
|
};
|
|
10379
11864
|
if (!extracted.pendingText) {
|
|
@@ -10389,7 +11874,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
10389
11874
|
assistant.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
10390
11875
|
assistant.raw = { format: "hermes-run-event", payload: event.rawPayload };
|
|
10391
11876
|
if (extracted.visibleText) {
|
|
10392
|
-
|
|
11877
|
+
appendTextBlock2(assistant, extracted.visibleText, assistant.updated_at);
|
|
10393
11878
|
}
|
|
10394
11879
|
await this.deps.writeSnapshot(conversationId, snapshot);
|
|
10395
11880
|
if (extracted.visibleText) {
|
|
@@ -10626,119 +12111,11 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
10626
12111
|
return snapshot?.runs.find((item) => item.id === runId)?.assistant_message_id;
|
|
10627
12112
|
}
|
|
10628
12113
|
async importMediaReferences(conversationId, runId, messageId, references) {
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
(message) => message.id === messageId
|
|
10635
|
-
);
|
|
10636
|
-
if (!assistant) {
|
|
10637
|
-
return;
|
|
10638
|
-
}
|
|
10639
|
-
const importedSourceKeys = readImportedMediaSourceKeys(assistant);
|
|
10640
|
-
const failedSourceKeys = readFailedMediaSourceKeys(assistant);
|
|
10641
|
-
const failureRecordsByKey = new Map(
|
|
10642
|
-
readMediaImportFailures(assistant).map((failure) => [
|
|
10643
|
-
failure.key,
|
|
10644
|
-
failure
|
|
10645
|
-
])
|
|
10646
|
-
);
|
|
10647
|
-
const importedParts = [];
|
|
10648
|
-
const newFailures = [];
|
|
10649
|
-
for (const reference of references.slice(0, 10)) {
|
|
10650
|
-
let sourceKey;
|
|
10651
|
-
try {
|
|
10652
|
-
sourceKey = mediaSourceKey(reference.path);
|
|
10653
|
-
if (importedSourceKeys.has(sourceKey) || failedSourceKeys.has(sourceKey)) {
|
|
10654
|
-
continue;
|
|
10655
|
-
}
|
|
10656
|
-
const blob = await this.writeBlobFromFile(conversationId, reference);
|
|
10657
|
-
const part = {
|
|
10658
|
-
type: reference.kind ?? mediaKindForMime(blob.mime),
|
|
10659
|
-
blob: blob.id,
|
|
10660
|
-
mime: blob.mime,
|
|
10661
|
-
size: blob.size,
|
|
10662
|
-
filename: blob.filename,
|
|
10663
|
-
url: `/api/v1/conversations/${encodeURIComponent(conversationId)}/blobs/${encodeURIComponent(blob.id)}`
|
|
10664
|
-
};
|
|
10665
|
-
assistant.parts.push(part);
|
|
10666
|
-
assistant.attachments.push({
|
|
10667
|
-
blob_id: blob.id,
|
|
10668
|
-
mime: blob.mime,
|
|
10669
|
-
size: blob.size,
|
|
10670
|
-
filename: blob.filename,
|
|
10671
|
-
source: "hermes_output"
|
|
10672
|
-
});
|
|
10673
|
-
importedSourceKeys.add(sourceKey);
|
|
10674
|
-
importedParts.push(part);
|
|
10675
|
-
} catch (error) {
|
|
10676
|
-
if (sourceKey && !failedSourceKeys.has(sourceKey)) {
|
|
10677
|
-
const failure = describeMediaImportFailure(reference, sourceKey, error);
|
|
10678
|
-
failedSourceKeys.add(sourceKey);
|
|
10679
|
-
failureRecordsByKey.set(sourceKey, failure);
|
|
10680
|
-
newFailures.push(failure);
|
|
10681
|
-
}
|
|
10682
|
-
void this.deps.logger.warn("conversation_media_import_failed", {
|
|
10683
|
-
conversation_id: conversationId,
|
|
10684
|
-
run_id: runId,
|
|
10685
|
-
message_id: messageId,
|
|
10686
|
-
error: error instanceof Error ? error.message : String(error)
|
|
10687
|
-
});
|
|
10688
|
-
}
|
|
10689
|
-
}
|
|
10690
|
-
if (importedParts.length === 0 && newFailures.length === 0) {
|
|
10691
|
-
return;
|
|
10692
|
-
}
|
|
10693
|
-
assistant.hermes = {
|
|
10694
|
-
...toRecord9(assistant.hermes),
|
|
10695
|
-
imported_media_source_keys: [...importedSourceKeys],
|
|
10696
|
-
media_import_failed_source_keys: [...failedSourceKeys],
|
|
10697
|
-
media_import_failures: [...failureRecordsByKey.values()].slice(
|
|
10698
|
-
-MAX_MEDIA_IMPORT_FAILURES
|
|
10699
|
-
)
|
|
10700
|
-
};
|
|
10701
|
-
assistant.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
10702
|
-
await this.deps.writeSnapshot(conversationId, snapshot);
|
|
10703
|
-
if (importedParts.length > 0) {
|
|
10704
|
-
await this.deps.appendEvent(conversationId, {
|
|
10705
|
-
type: "message.parts.created",
|
|
10706
|
-
message_id: messageId,
|
|
10707
|
-
run_id: runId,
|
|
10708
|
-
payload: { parts: importedParts }
|
|
10709
|
-
});
|
|
10710
|
-
}
|
|
10711
|
-
}
|
|
10712
|
-
async writeBlobFromFile(conversationId, source) {
|
|
10713
|
-
const sourcePath = resolveMediaSourcePath(source.path);
|
|
10714
|
-
const fileStat = await stat9(sourcePath).catch((error) => {
|
|
10715
|
-
if (isNodeError10(error, "ENOENT")) {
|
|
10716
|
-
throw new LinkHttpError(
|
|
10717
|
-
404,
|
|
10718
|
-
"media_source_not_found",
|
|
10719
|
-
"Hermes output file was not found"
|
|
10720
|
-
);
|
|
10721
|
-
}
|
|
10722
|
-
throw error;
|
|
10723
|
-
});
|
|
10724
|
-
if (!fileStat.isFile()) {
|
|
10725
|
-
throw new LinkHttpError(
|
|
10726
|
-
400,
|
|
10727
|
-
"media_source_not_file",
|
|
10728
|
-
"Hermes output media source is not a file"
|
|
10729
|
-
);
|
|
10730
|
-
}
|
|
10731
|
-
if (fileStat.size > MAX_IMPORTED_BLOB_BYTES) {
|
|
10732
|
-
throw new LinkHttpError(
|
|
10733
|
-
413,
|
|
10734
|
-
"media_source_too_large",
|
|
10735
|
-
"Hermes output media source is too large"
|
|
10736
|
-
);
|
|
10737
|
-
}
|
|
10738
|
-
return this.deps.writeBlob(conversationId, {
|
|
10739
|
-
bytes: await readFile10(sourcePath),
|
|
10740
|
-
filename: path15.basename(sourcePath),
|
|
10741
|
-
mime: source.mime ?? inferMimeType(sourcePath)
|
|
12114
|
+
await importMediaReferencesForMessage(this.deps, {
|
|
12115
|
+
conversationId,
|
|
12116
|
+
runId,
|
|
12117
|
+
messageId,
|
|
12118
|
+
references
|
|
10742
12119
|
});
|
|
10743
12120
|
}
|
|
10744
12121
|
async readHermesCronJobIds(profileName) {
|
|
@@ -10748,7 +12125,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
10748
12125
|
includeDisabled: true
|
|
10749
12126
|
});
|
|
10750
12127
|
return new Set(
|
|
10751
|
-
jobs.map((job) =>
|
|
12128
|
+
jobs.map((job) => readString13(job, "id") ?? readString13(job, "job_id")).filter((id) => Boolean(id))
|
|
10752
12129
|
);
|
|
10753
12130
|
}
|
|
10754
12131
|
async bindNewCronJobsCreatedByRun(input) {
|
|
@@ -10773,7 +12150,8 @@ function buildRunInstructions(run, deliveryStagingDir) {
|
|
|
10773
12150
|
"",
|
|
10774
12151
|
"Delivery staging directory for this run:",
|
|
10775
12152
|
`- ${deliveryStagingDir}`,
|
|
10776
|
-
"
|
|
12153
|
+
"Copy every deliverable file into this directory, then run:",
|
|
12154
|
+
`hermeslink deliver ${quoteShellArg(deliveryStagingDir)}`
|
|
10777
12155
|
] : [],
|
|
10778
12156
|
"",
|
|
10779
12157
|
"Current runtime selected by Hermes Link:",
|
|
@@ -10782,52 +12160,8 @@ function buildRunInstructions(run, deliveryStagingDir) {
|
|
|
10782
12160
|
"If the user asks what model or provider you are currently using, answer from this runtime selection instead of inferring from earlier conversation history."
|
|
10783
12161
|
].join("\n");
|
|
10784
12162
|
}
|
|
10785
|
-
function readFailedMediaSourceKeys(message) {
|
|
10786
|
-
const hermes = toRecord9(message.hermes);
|
|
10787
|
-
const keys = hermes.media_import_failed_source_keys;
|
|
10788
|
-
if (!Array.isArray(keys)) {
|
|
10789
|
-
return /* @__PURE__ */ new Set();
|
|
10790
|
-
}
|
|
10791
|
-
return new Set(
|
|
10792
|
-
keys.filter(
|
|
10793
|
-
(key) => typeof key === "string" && key.length > 0
|
|
10794
|
-
)
|
|
10795
|
-
);
|
|
10796
|
-
}
|
|
10797
|
-
function readMediaImportFailures(message) {
|
|
10798
|
-
const hermes = toRecord9(message.hermes);
|
|
10799
|
-
const failures = hermes.media_import_failures;
|
|
10800
|
-
if (!Array.isArray(failures)) {
|
|
10801
|
-
return [];
|
|
10802
|
-
}
|
|
10803
|
-
return failures.flatMap((item) => {
|
|
10804
|
-
const record = toRecord9(item);
|
|
10805
|
-
const key = readString11(record, "key");
|
|
10806
|
-
const filename = readString11(record, "filename");
|
|
10807
|
-
const reason = readString11(record, "reason");
|
|
10808
|
-
if (!key || !filename || !reason) {
|
|
10809
|
-
return [];
|
|
10810
|
-
}
|
|
10811
|
-
return [
|
|
10812
|
-
{
|
|
10813
|
-
key,
|
|
10814
|
-
filename,
|
|
10815
|
-
reason,
|
|
10816
|
-
...readString11(record, "code") ? { code: readString11(record, "code") } : {}
|
|
10817
|
-
}
|
|
10818
|
-
];
|
|
10819
|
-
});
|
|
10820
|
-
}
|
|
10821
|
-
function describeMediaImportFailure(reference, sourceKey, error) {
|
|
10822
|
-
return {
|
|
10823
|
-
key: sourceKey,
|
|
10824
|
-
filename: sanitizeFilename(reference.path, "attachment"),
|
|
10825
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
10826
|
-
...isNodeError10(error) && error.code ? { code: error.code } : {}
|
|
10827
|
-
};
|
|
10828
|
-
}
|
|
10829
12163
|
function appendMediaImportFailureNotice(message) {
|
|
10830
|
-
const hermes =
|
|
12164
|
+
const hermes = toRecord11(message.hermes);
|
|
10831
12165
|
if (hermes.media_import_failure_notice_appended === true) {
|
|
10832
12166
|
return;
|
|
10833
12167
|
}
|
|
@@ -10849,6 +12183,9 @@ function appendMediaImportFailureNotice(message) {
|
|
|
10849
12183
|
media_import_failure_notice_appended: true
|
|
10850
12184
|
};
|
|
10851
12185
|
}
|
|
12186
|
+
function quoteShellArg(value) {
|
|
12187
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
12188
|
+
}
|
|
10852
12189
|
function formatMediaImportFailureNotice(failures) {
|
|
10853
12190
|
const filenames = failures.map((failure) => failure.filename);
|
|
10854
12191
|
const target = filenames.length === 1 ? `\u6587\u4EF6\u201C${filenames[0]}\u201D` : `${filenames.length} \u4E2A\u6587\u4EF6\uFF08${formatFilenameList(filenames)}\uFF09`;
|
|
@@ -10867,18 +12204,18 @@ function formatFilenameList(filenames) {
|
|
|
10867
12204
|
return remaining > 0 ? `${preview.join("\u3001")} \u7B49 ${filenames.length} \u4E2A` : preview.join("\u3001");
|
|
10868
12205
|
}
|
|
10869
12206
|
async function readdirWithDirs(directory) {
|
|
10870
|
-
return
|
|
10871
|
-
if (
|
|
12207
|
+
return readdir7(directory, { withFileTypes: true }).catch((error) => {
|
|
12208
|
+
if (isNodeError12(error, "ENOENT")) {
|
|
10872
12209
|
return [];
|
|
10873
12210
|
}
|
|
10874
12211
|
throw error;
|
|
10875
12212
|
});
|
|
10876
12213
|
}
|
|
10877
|
-
function
|
|
12214
|
+
function readString13(payload, key) {
|
|
10878
12215
|
const value = payload[key];
|
|
10879
12216
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
10880
12217
|
}
|
|
10881
|
-
function
|
|
12218
|
+
function toRecord11(value) {
|
|
10882
12219
|
return typeof value === "object" && value !== null ? value : {};
|
|
10883
12220
|
}
|
|
10884
12221
|
function formatFailureMessage(message, detail) {
|
|
@@ -10894,17 +12231,17 @@ function isFileSearchCompletion(payloadType, payload) {
|
|
|
10894
12231
|
if (payloadType !== "tool.completed") {
|
|
10895
12232
|
return false;
|
|
10896
12233
|
}
|
|
10897
|
-
const tool =
|
|
10898
|
-
const toolCall =
|
|
10899
|
-
const fn =
|
|
12234
|
+
const tool = toRecord11(payload.tool);
|
|
12235
|
+
const toolCall = toRecord11(payload.tool_call ?? payload.toolCall);
|
|
12236
|
+
const fn = toRecord11(toolCall.function ?? payload.function);
|
|
10900
12237
|
const candidates = [
|
|
10901
|
-
|
|
10902
|
-
|
|
10903
|
-
|
|
10904
|
-
|
|
10905
|
-
|
|
10906
|
-
|
|
10907
|
-
|
|
12238
|
+
readString13(payload, "tool_name"),
|
|
12239
|
+
readString13(payload, "toolName"),
|
|
12240
|
+
readString13(payload, "name"),
|
|
12241
|
+
readString13(payload, "tool"),
|
|
12242
|
+
readString13(tool, "name"),
|
|
12243
|
+
readString13(toolCall, "name"),
|
|
12244
|
+
readString13(fn, "name")
|
|
10908
12245
|
].filter((value) => Boolean(value)).map(normalizeToolName);
|
|
10909
12246
|
return candidates.some(
|
|
10910
12247
|
(name) => [
|
|
@@ -10947,7 +12284,7 @@ function isVoicePart(part) {
|
|
|
10947
12284
|
function normalizeToolName(value) {
|
|
10948
12285
|
return value.trim().toLowerCase().replace(/[\s-]+/gu, "_");
|
|
10949
12286
|
}
|
|
10950
|
-
function
|
|
12287
|
+
function appendTextBlock2(message, delta, updatedAt) {
|
|
10951
12288
|
if (!delta) {
|
|
10952
12289
|
return;
|
|
10953
12290
|
}
|
|
@@ -10970,7 +12307,7 @@ function appendTextBlock(message, delta, updatedAt) {
|
|
|
10970
12307
|
}
|
|
10971
12308
|
message.blocks = blocks;
|
|
10972
12309
|
}
|
|
10973
|
-
function
|
|
12310
|
+
function appendAgentEventBlock2(message, event, updatedAt) {
|
|
10974
12311
|
const blocks = [...message.blocks ?? []];
|
|
10975
12312
|
const matchingIndex = blocks.findIndex((block) => {
|
|
10976
12313
|
if (block.type !== "agent_events") {
|
|
@@ -11059,10 +12396,10 @@ function readResponseId(payload) {
|
|
|
11059
12396
|
if (!payload) {
|
|
11060
12397
|
return null;
|
|
11061
12398
|
}
|
|
11062
|
-
const response =
|
|
11063
|
-
return
|
|
12399
|
+
const response = toRecord11(payload.response);
|
|
12400
|
+
return readString13(payload, "response_id") ?? readString13(response, "id");
|
|
11064
12401
|
}
|
|
11065
|
-
function
|
|
12402
|
+
function isNodeError12(error, code) {
|
|
11066
12403
|
if (typeof error !== "object" || error === null || !("code" in error)) {
|
|
11067
12404
|
return false;
|
|
11068
12405
|
}
|
|
@@ -11151,6 +12488,7 @@ var ConversationService = class {
|
|
|
11151
12488
|
orchestration;
|
|
11152
12489
|
queries;
|
|
11153
12490
|
runLifecycle;
|
|
12491
|
+
hermesSessionSyncPromise = null;
|
|
11154
12492
|
async withConversationLock(conversationId, task) {
|
|
11155
12493
|
const previous = this.conversationLocks.get(conversationId) ?? Promise.resolve();
|
|
11156
12494
|
let release;
|
|
@@ -11211,7 +12549,7 @@ var ConversationService = class {
|
|
|
11211
12549
|
async createConversation(input = {}) {
|
|
11212
12550
|
await this.store.ensureConversationsDir();
|
|
11213
12551
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11214
|
-
const id = `conv_${
|
|
12552
|
+
const id = `conv_${randomUUID7().replaceAll("-", "")}`;
|
|
11215
12553
|
const title = input.title?.trim() || DEFAULT_CONVERSATION_TITLE;
|
|
11216
12554
|
const profile = await resolveConversationProfileTarget(
|
|
11217
12555
|
this.paths,
|
|
@@ -11298,7 +12636,7 @@ var ConversationService = class {
|
|
|
11298
12636
|
manifest.profile_name_snapshot ?? manifest.profile ?? input.profileName
|
|
11299
12637
|
);
|
|
11300
12638
|
const message = {
|
|
11301
|
-
id: `msg_${
|
|
12639
|
+
id: `msg_${randomUUID7().replaceAll("-", "")}`,
|
|
11302
12640
|
schema_version: 1,
|
|
11303
12641
|
conversation_id: manifest.id,
|
|
11304
12642
|
role: "assistant",
|
|
@@ -11342,6 +12680,65 @@ var ConversationService = class {
|
|
|
11342
12680
|
async syncCronDeliveries() {
|
|
11343
12681
|
await syncHermesLinkCronDeliveries(this.paths, this, this.logger);
|
|
11344
12682
|
}
|
|
12683
|
+
async syncHermesSessions() {
|
|
12684
|
+
if (this.hermesSessionSyncPromise) {
|
|
12685
|
+
return this.hermesSessionSyncPromise;
|
|
12686
|
+
}
|
|
12687
|
+
const task = (async () => {
|
|
12688
|
+
const result = await syncHermesSessionsIntoConversations(
|
|
12689
|
+
this.paths,
|
|
12690
|
+
this.logger
|
|
12691
|
+
);
|
|
12692
|
+
if (result.imported_count > 0 || result.reprojected_count > 0) {
|
|
12693
|
+
await this.rebuildStatisticsIndex();
|
|
12694
|
+
}
|
|
12695
|
+
return result;
|
|
12696
|
+
})();
|
|
12697
|
+
this.hermesSessionSyncPromise = task;
|
|
12698
|
+
try {
|
|
12699
|
+
return await task;
|
|
12700
|
+
} finally {
|
|
12701
|
+
if (this.hermesSessionSyncPromise === task) {
|
|
12702
|
+
this.hermesSessionSyncPromise = null;
|
|
12703
|
+
}
|
|
12704
|
+
}
|
|
12705
|
+
}
|
|
12706
|
+
async deliverStagedFiles(stagingDir) {
|
|
12707
|
+
const target = resolveDeliveryStagingTarget(this.paths, stagingDir);
|
|
12708
|
+
return this.withConversationLock(target.conversationId, async () => {
|
|
12709
|
+
await this.store.readActiveManifest(target.conversationId);
|
|
12710
|
+
const snapshot = await this.store.readSnapshot(target.conversationId);
|
|
12711
|
+
const run = snapshot.runs.find((item) => item.id === target.runId);
|
|
12712
|
+
if (!run) {
|
|
12713
|
+
throw new LinkHttpError(404, "run_not_found", "Run was not found");
|
|
12714
|
+
}
|
|
12715
|
+
const assistant = snapshot.messages.find(
|
|
12716
|
+
(message) => message.id === run.assistant_message_id
|
|
12717
|
+
);
|
|
12718
|
+
if (!assistant) {
|
|
12719
|
+
throw new LinkHttpError(
|
|
12720
|
+
404,
|
|
12721
|
+
"assistant_message_not_found",
|
|
12722
|
+
"Assistant message was not found"
|
|
12723
|
+
);
|
|
12724
|
+
}
|
|
12725
|
+
return importMediaReferencesForMessage(
|
|
12726
|
+
{
|
|
12727
|
+
logger: this.logger,
|
|
12728
|
+
readSnapshot: (conversationId) => this.store.readSnapshot(conversationId),
|
|
12729
|
+
writeSnapshot: (conversationId, nextSnapshot) => this.store.writeSnapshot(conversationId, nextSnapshot),
|
|
12730
|
+
appendEvent: (conversationId, input) => this.appendEvent(conversationId, input),
|
|
12731
|
+
writeBlob: (conversationId, input) => this.maintenance.writeBlob(conversationId, input)
|
|
12732
|
+
},
|
|
12733
|
+
{
|
|
12734
|
+
conversationId: target.conversationId,
|
|
12735
|
+
runId: target.runId,
|
|
12736
|
+
messageId: assistant.id,
|
|
12737
|
+
references: await collectStagedDeliveryReferences(target.stagingDir)
|
|
12738
|
+
}
|
|
12739
|
+
);
|
|
12740
|
+
});
|
|
12741
|
+
}
|
|
11345
12742
|
async getMessages(conversationId, options = {}) {
|
|
11346
12743
|
return this.queries.getMessages(conversationId, options);
|
|
11347
12744
|
}
|
|
@@ -11831,7 +13228,7 @@ function findApproval(snapshot, approvalId) {
|
|
|
11831
13228
|
}
|
|
11832
13229
|
|
|
11833
13230
|
// src/identity/identity.ts
|
|
11834
|
-
import { generateKeyPairSync, randomUUID as
|
|
13231
|
+
import { generateKeyPairSync, randomUUID as randomUUID8, sign } from "crypto";
|
|
11835
13232
|
import { mkdir as mkdir9, chmod } from "fs/promises";
|
|
11836
13233
|
import { z } from "zod";
|
|
11837
13234
|
var linkIdentitySchema = z.object({
|
|
@@ -11859,7 +13256,7 @@ async function ensureIdentity(paths = resolveRuntimePaths()) {
|
|
|
11859
13256
|
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
11860
13257
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11861
13258
|
const identity = {
|
|
11862
|
-
install_id: `install_${
|
|
13259
|
+
install_id: `install_${randomUUID8().replaceAll("-", "")}`,
|
|
11863
13260
|
link_id: null,
|
|
11864
13261
|
public_key_pem: publicKey.export({ type: "spki", format: "pem" }).toString(),
|
|
11865
13262
|
private_key_pem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
|
|
@@ -11896,7 +13293,7 @@ function getIdentityStatus(identity) {
|
|
|
11896
13293
|
}
|
|
11897
13294
|
|
|
11898
13295
|
// src/security/devices.ts
|
|
11899
|
-
import { randomBytes as randomBytes2, randomUUID as
|
|
13296
|
+
import { randomBytes as randomBytes2, randomUUID as randomUUID9, timingSafeEqual, createHash as createHash4 } from "crypto";
|
|
11900
13297
|
var ACCESS_TOKEN_TTL_MS = 15 * 60 * 1e3;
|
|
11901
13298
|
var REFRESH_TOKEN_TTL_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
11902
13299
|
var DEVICE_SEEN_WRITE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
@@ -11914,7 +13311,7 @@ async function createDeviceSession(input, paths = resolveRuntimePaths()) {
|
|
|
11914
13311
|
}
|
|
11915
13312
|
}
|
|
11916
13313
|
const device = {
|
|
11917
|
-
id: `dev_${
|
|
13314
|
+
id: `dev_${randomUUID9().replaceAll("-", "")}`,
|
|
11918
13315
|
label: normalizeDeviceLabel(input.label),
|
|
11919
13316
|
platform: normalizeDevicePlatform(input.platform),
|
|
11920
13317
|
model: normalizeDeviceModel(input.model),
|
|
@@ -12363,12 +13760,12 @@ async function readRawBody(request, maxBytes) {
|
|
|
12363
13760
|
}
|
|
12364
13761
|
return Buffer.concat(chunks);
|
|
12365
13762
|
}
|
|
12366
|
-
function
|
|
13763
|
+
function readString14(body, key) {
|
|
12367
13764
|
const value = body[key];
|
|
12368
13765
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
12369
13766
|
}
|
|
12370
13767
|
function readOptionalProfileName(body) {
|
|
12371
|
-
return
|
|
13768
|
+
return readString14(body, "profile") ?? readString14(body, "profile_name") ?? readString14(body, "profileName") ?? void 0;
|
|
12372
13769
|
}
|
|
12373
13770
|
function readStringArray(body, ...keys) {
|
|
12374
13771
|
for (const key of keys) {
|
|
@@ -12406,7 +13803,7 @@ function readPositiveInteger2(value) {
|
|
|
12406
13803
|
}
|
|
12407
13804
|
return void 0;
|
|
12408
13805
|
}
|
|
12409
|
-
function
|
|
13806
|
+
function readBoolean2(value) {
|
|
12410
13807
|
if (typeof value === "boolean") {
|
|
12411
13808
|
return value;
|
|
12412
13809
|
}
|
|
@@ -12509,7 +13906,7 @@ function readMessageAttachments(value) {
|
|
|
12509
13906
|
}
|
|
12510
13907
|
const kind = readAttachmentString(record.kind);
|
|
12511
13908
|
const type = readAttachmentString(record.type);
|
|
12512
|
-
const isVoiceNote =
|
|
13909
|
+
const isVoiceNote = readBoolean2(record.is_voice_note) ?? readBoolean2(record.isVoiceNote);
|
|
12513
13910
|
const durationMs = readPositiveInteger2(record.duration_ms) ?? readPositiveInteger2(record.durationMs);
|
|
12514
13911
|
const waveform = readAttachmentWaveform2(
|
|
12515
13912
|
record.waveform ?? record.waveform_samples ?? record.waveformSamples
|
|
@@ -12601,7 +13998,7 @@ function registerConversationRoutes(router, options) {
|
|
|
12601
13998
|
ctx.body = {
|
|
12602
13999
|
ok: true,
|
|
12603
14000
|
conversation: await conversations.createConversation({
|
|
12604
|
-
title:
|
|
14001
|
+
title: readString14(body, "title") ?? void 0,
|
|
12605
14002
|
profileName: readOptionalProfileName(body)
|
|
12606
14003
|
})
|
|
12607
14004
|
};
|
|
@@ -12672,7 +14069,7 @@ function registerConversationRoutes(router, options) {
|
|
|
12672
14069
|
router.post("/api/v1/conversations/:conversationId/messages", async (ctx) => {
|
|
12673
14070
|
await authenticateRequest(ctx, paths);
|
|
12674
14071
|
const body = await readJsonBody(ctx.req);
|
|
12675
|
-
const content =
|
|
14072
|
+
const content = readString14(body, "content") ?? readString14(body, "text") ?? readString14(body, "input") ?? "";
|
|
12676
14073
|
const attachments = readMessageAttachments(body.attachments ?? body.blobs);
|
|
12677
14074
|
if (!content && attachments.length === 0) {
|
|
12678
14075
|
throw new LinkHttpError(
|
|
@@ -12688,7 +14085,7 @@ function registerConversationRoutes(router, options) {
|
|
|
12688
14085
|
conversationId: ctx.params.conversationId,
|
|
12689
14086
|
content,
|
|
12690
14087
|
attachments,
|
|
12691
|
-
clientMessageId:
|
|
14088
|
+
clientMessageId: readString14(body, "client_message_id") ?? readString14(body, "clientMessageId") ?? void 0,
|
|
12692
14089
|
idempotencyKey: readHeader(ctx, "idempotency-key") ?? void 0,
|
|
12693
14090
|
profileName: readOptionalProfileName(body)
|
|
12694
14091
|
})
|
|
@@ -12697,7 +14094,7 @@ function registerConversationRoutes(router, options) {
|
|
|
12697
14094
|
router.patch("/api/v1/conversations/:conversationId/model", async (ctx) => {
|
|
12698
14095
|
await authenticateRequest(ctx, paths);
|
|
12699
14096
|
const body = await readJsonBody(ctx.req);
|
|
12700
|
-
const modelId =
|
|
14097
|
+
const modelId = readString14(body, "model_id") ?? readString14(body, "modelId") ?? readString14(body, "model");
|
|
12701
14098
|
if (!modelId) {
|
|
12702
14099
|
throw new LinkHttpError(400, "model_id_required", "model_id is required");
|
|
12703
14100
|
}
|
|
@@ -12727,7 +14124,7 @@ function registerConversationRoutes(router, options) {
|
|
|
12727
14124
|
router.patch("/api/v1/conversations/:conversationId/title", async (ctx) => {
|
|
12728
14125
|
await authenticateRequest(ctx, paths);
|
|
12729
14126
|
const body = await readJsonBody(ctx.req);
|
|
12730
|
-
const title =
|
|
14127
|
+
const title = readString14(body, "title") ?? readString14(body, "name") ?? readString14(body, "display_name");
|
|
12731
14128
|
if (!title) {
|
|
12732
14129
|
throw new LinkHttpError(400, "title_required", "title is required");
|
|
12733
14130
|
}
|
|
@@ -12791,7 +14188,7 @@ function registerConversationRoutes(router, options) {
|
|
|
12791
14188
|
async (ctx) => {
|
|
12792
14189
|
await authenticateRequest(ctx, paths);
|
|
12793
14190
|
const body = await readJsonBody(ctx.req);
|
|
12794
|
-
const scope =
|
|
14191
|
+
const scope = readString14(body, "scope") ?? "always";
|
|
12795
14192
|
ctx.body = {
|
|
12796
14193
|
ok: true,
|
|
12797
14194
|
...await conversations.resolveApproval({
|
|
@@ -12951,26 +14348,26 @@ function createHttpErrorMiddleware(logger) {
|
|
|
12951
14348
|
}
|
|
12952
14349
|
|
|
12953
14350
|
// src/hermes/profiles.ts
|
|
12954
|
-
import { mkdir as mkdir10, readdir as
|
|
12955
|
-
import
|
|
12956
|
-
import
|
|
14351
|
+
import { mkdir as mkdir10, readdir as readdir8, readFile as readFile12, rename as rename4, rm as rm6, stat as stat11 } from "fs/promises";
|
|
14352
|
+
import os5 from "os";
|
|
14353
|
+
import path17 from "path";
|
|
12957
14354
|
import YAML2 from "yaml";
|
|
12958
14355
|
var DEFAULT_PROFILE = "default";
|
|
12959
|
-
var
|
|
14356
|
+
var PROFILE_NAME_PATTERN4 = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
12960
14357
|
async function listHermesProfiles(paths = resolveRuntimePaths()) {
|
|
12961
14358
|
const profiles = /* @__PURE__ */ new Map();
|
|
12962
14359
|
profiles.set(DEFAULT_PROFILE, await profileInfo(DEFAULT_PROFILE, paths));
|
|
12963
|
-
const profilesDir =
|
|
12964
|
-
const entries = await
|
|
14360
|
+
const profilesDir = path17.join(os5.homedir(), ".hermes", "profiles");
|
|
14361
|
+
const entries = await readdir8(profilesDir, { withFileTypes: true }).catch(
|
|
12965
14362
|
(error) => {
|
|
12966
|
-
if (
|
|
14363
|
+
if (isNodeError13(error, "ENOENT")) {
|
|
12967
14364
|
return [];
|
|
12968
14365
|
}
|
|
12969
14366
|
throw error;
|
|
12970
14367
|
}
|
|
12971
14368
|
);
|
|
12972
14369
|
for (const entry of entries) {
|
|
12973
|
-
if (entry.isDirectory() &&
|
|
14370
|
+
if (entry.isDirectory() && PROFILE_NAME_PATTERN4.test(entry.name)) {
|
|
12974
14371
|
profiles.set(entry.name, await profileInfo(entry.name, paths));
|
|
12975
14372
|
}
|
|
12976
14373
|
}
|
|
@@ -12987,8 +14384,8 @@ async function listHermesProfiles(paths = resolveRuntimePaths()) {
|
|
|
12987
14384
|
async function getHermesProfileStatus(name, paths = resolveRuntimePaths()) {
|
|
12988
14385
|
assertProfileName(name);
|
|
12989
14386
|
const profile = await profileInfo(name, paths);
|
|
12990
|
-
const exists = await
|
|
12991
|
-
if (
|
|
14387
|
+
const exists = await stat11(profile.path).then((value) => value.isDirectory()).catch((error) => {
|
|
14388
|
+
if (isNodeError13(error, "ENOENT")) {
|
|
12992
14389
|
return false;
|
|
12993
14390
|
}
|
|
12994
14391
|
throw error;
|
|
@@ -13028,8 +14425,8 @@ async function updateHermesProfileMetadata(name, metadata, paths = resolveRuntim
|
|
|
13028
14425
|
async function deleteHermesProfile(name, paths = resolveRuntimePaths()) {
|
|
13029
14426
|
assertMutableProfile(name);
|
|
13030
14427
|
const profile = await profileInfo(name, paths);
|
|
13031
|
-
const exists = await
|
|
13032
|
-
if (
|
|
14428
|
+
const exists = await stat11(profile.path).then((value) => value.isDirectory()).catch((error) => {
|
|
14429
|
+
if (isNodeError13(error, "ENOENT")) {
|
|
13033
14430
|
return false;
|
|
13034
14431
|
}
|
|
13035
14432
|
throw error;
|
|
@@ -13055,7 +14452,7 @@ async function readHermesProfileCapabilities(name) {
|
|
|
13055
14452
|
return {
|
|
13056
14453
|
defaultModel: listedModels?.defaultModel ?? null,
|
|
13057
14454
|
modelCount: listedModels?.models.length ?? 0,
|
|
13058
|
-
skillCount: await countSkills(
|
|
14455
|
+
skillCount: await countSkills(path17.join(profileDir, "skills")).catch(
|
|
13059
14456
|
() => 0
|
|
13060
14457
|
),
|
|
13061
14458
|
toolCount: await countConfiguredTools(name).catch(() => 0)
|
|
@@ -13093,17 +14490,17 @@ function assertMutableProfile(name) {
|
|
|
13093
14490
|
}
|
|
13094
14491
|
}
|
|
13095
14492
|
function assertProfileName(name) {
|
|
13096
|
-
if (!
|
|
14493
|
+
if (!PROFILE_NAME_PATTERN4.test(name)) {
|
|
13097
14494
|
throw new LinkHttpError(400, "invalid_profile_name", "invalid profile name");
|
|
13098
14495
|
}
|
|
13099
14496
|
}
|
|
13100
|
-
function
|
|
14497
|
+
function isNodeError13(error, code) {
|
|
13101
14498
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
13102
14499
|
}
|
|
13103
14500
|
async function countSkills(root) {
|
|
13104
|
-
const entries = await
|
|
14501
|
+
const entries = await readdir8(root, { withFileTypes: true }).catch(
|
|
13105
14502
|
(error) => {
|
|
13106
|
-
if (
|
|
14503
|
+
if (isNodeError13(error, "ENOENT")) {
|
|
13107
14504
|
return [];
|
|
13108
14505
|
}
|
|
13109
14506
|
throw error;
|
|
@@ -13111,7 +14508,7 @@ async function countSkills(root) {
|
|
|
13111
14508
|
);
|
|
13112
14509
|
let count = 0;
|
|
13113
14510
|
for (const entry of entries) {
|
|
13114
|
-
const entryPath =
|
|
14511
|
+
const entryPath = path17.join(root, entry.name);
|
|
13115
14512
|
if (entry.name === ".git" || entry.name === ".hub") {
|
|
13116
14513
|
continue;
|
|
13117
14514
|
}
|
|
@@ -13126,11 +14523,11 @@ async function countSkills(root) {
|
|
|
13126
14523
|
return count;
|
|
13127
14524
|
}
|
|
13128
14525
|
async function countConfiguredTools(profileName) {
|
|
13129
|
-
const raw = await
|
|
14526
|
+
const raw = await readFile12(
|
|
13130
14527
|
resolveHermesConfigPath(profileName),
|
|
13131
14528
|
"utf8"
|
|
13132
14529
|
).catch((error) => {
|
|
13133
|
-
if (
|
|
14530
|
+
if (isNodeError13(error, "ENOENT")) {
|
|
13134
14531
|
return "";
|
|
13135
14532
|
}
|
|
13136
14533
|
throw error;
|
|
@@ -13138,14 +14535,14 @@ async function countConfiguredTools(profileName) {
|
|
|
13138
14535
|
if (!raw.trim()) {
|
|
13139
14536
|
return 0;
|
|
13140
14537
|
}
|
|
13141
|
-
const config =
|
|
14538
|
+
const config = toRecord12(YAML2.parse(raw));
|
|
13142
14539
|
const toolsets = /* @__PURE__ */ new Set();
|
|
13143
14540
|
collectToolsetValues(config.toolsets, toolsets);
|
|
13144
|
-
const platformToolsets =
|
|
14541
|
+
const platformToolsets = toRecord12(config.platform_toolsets);
|
|
13145
14542
|
for (const value of Object.values(platformToolsets)) {
|
|
13146
14543
|
collectToolsetValues(value, toolsets);
|
|
13147
14544
|
}
|
|
13148
|
-
const mcpServers = Object.keys(
|
|
14545
|
+
const mcpServers = Object.keys(toRecord12(config.mcp_servers)).length;
|
|
13149
14546
|
return toolsets.size + mcpServers;
|
|
13150
14547
|
}
|
|
13151
14548
|
function collectToolsetValues(value, target) {
|
|
@@ -13159,7 +14556,7 @@ function collectToolsetValues(value, target) {
|
|
|
13159
14556
|
target.add(value.trim());
|
|
13160
14557
|
}
|
|
13161
14558
|
}
|
|
13162
|
-
function
|
|
14559
|
+
function toRecord12(value) {
|
|
13163
14560
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
13164
14561
|
}
|
|
13165
14562
|
|
|
@@ -13356,7 +14753,7 @@ function toHermesCronJobInput(input) {
|
|
|
13356
14753
|
};
|
|
13357
14754
|
}
|
|
13358
14755
|
async function bindAndDecorateCronJobForHermesLink(input) {
|
|
13359
|
-
const jobId =
|
|
14756
|
+
const jobId = readString14(input.job, "id") ?? readString14(input.job, "job_id");
|
|
13360
14757
|
if (!jobId) {
|
|
13361
14758
|
return input.job;
|
|
13362
14759
|
}
|
|
@@ -13373,9 +14770,9 @@ async function bindAndDecorateCronJobForHermesLink(input) {
|
|
|
13373
14770
|
}
|
|
13374
14771
|
function readCronJobCreateInput(body) {
|
|
13375
14772
|
const input = {};
|
|
13376
|
-
const name =
|
|
13377
|
-
const prompt =
|
|
13378
|
-
const schedule =
|
|
14773
|
+
const name = readString14(body, "name") ?? readString14(body, "title");
|
|
14774
|
+
const prompt = readString14(body, "prompt") ?? readString14(body, "description") ?? readString14(body, "task");
|
|
14775
|
+
const schedule = readString14(body, "schedule");
|
|
13379
14776
|
if (!name) {
|
|
13380
14777
|
throw new LinkHttpError(400, "cron_job_name_required", "name is required");
|
|
13381
14778
|
}
|
|
@@ -13396,7 +14793,7 @@ function readCronJobCreateInput(body) {
|
|
|
13396
14793
|
input.name = name;
|
|
13397
14794
|
input.prompt = prompt;
|
|
13398
14795
|
input.schedule = schedule;
|
|
13399
|
-
input.deliver =
|
|
14796
|
+
input.deliver = readString14(body, "deliver") ?? HERMES_LINK_CRON_DELIVER;
|
|
13400
14797
|
const skills = readOptionalCronSkills(body);
|
|
13401
14798
|
if (skills) {
|
|
13402
14799
|
input.skills = skills;
|
|
@@ -13454,7 +14851,7 @@ function readCronJobUpdateInput(body) {
|
|
|
13454
14851
|
if (repeat !== void 0) {
|
|
13455
14852
|
input.repeat = repeat;
|
|
13456
14853
|
}
|
|
13457
|
-
const enabled =
|
|
14854
|
+
const enabled = readBoolean2(body.enabled);
|
|
13458
14855
|
if (enabled !== void 0) {
|
|
13459
14856
|
input.enabled = enabled;
|
|
13460
14857
|
}
|
|
@@ -13624,7 +15021,7 @@ function registerModelConfigRoutes(router, options) {
|
|
|
13624
15021
|
router.delete("/api/v1/model-configs", async (ctx) => {
|
|
13625
15022
|
await authenticateRequest(ctx, paths);
|
|
13626
15023
|
const body = await readJsonBody(ctx.req);
|
|
13627
|
-
const modelId =
|
|
15024
|
+
const modelId = readString14(body, "model_id") ?? readString14(body, "modelId");
|
|
13628
15025
|
if (!modelId) {
|
|
13629
15026
|
throw new LinkHttpError(400, "model_id_required", "model_id is required");
|
|
13630
15027
|
}
|
|
@@ -13679,7 +15076,7 @@ function registerModelConfigRoutes(router, options) {
|
|
|
13679
15076
|
await authenticateRequest(ctx, paths);
|
|
13680
15077
|
await getHermesProfileStatus(ctx.params.name, paths);
|
|
13681
15078
|
const body = await readJsonBody(ctx.req);
|
|
13682
|
-
const modelId =
|
|
15079
|
+
const modelId = readString14(body, "model_id") ?? readString14(body, "modelId");
|
|
13683
15080
|
if (!modelId) {
|
|
13684
15081
|
throw new LinkHttpError(400, "model_id_required", "model_id is required");
|
|
13685
15082
|
}
|
|
@@ -13696,9 +15093,9 @@ function registerModelConfigRoutes(router, options) {
|
|
|
13696
15093
|
});
|
|
13697
15094
|
}
|
|
13698
15095
|
function readModelConfigInput(body) {
|
|
13699
|
-
const id =
|
|
13700
|
-
const provider =
|
|
13701
|
-
const baseUrl =
|
|
15096
|
+
const id = readString14(body, "id") ?? readString14(body, "model_id") ?? readString14(body, "modelId");
|
|
15097
|
+
const provider = readString14(body, "provider") ?? readString14(body, "provider_key") ?? readString14(body, "providerKey");
|
|
15098
|
+
const baseUrl = readString14(body, "base_url") ?? readString14(body, "baseUrl");
|
|
13702
15099
|
if (!id || !provider || !baseUrl) {
|
|
13703
15100
|
throw new LinkHttpError(
|
|
13704
15101
|
400,
|
|
@@ -13708,28 +15105,28 @@ function readModelConfigInput(body) {
|
|
|
13708
15105
|
}
|
|
13709
15106
|
return {
|
|
13710
15107
|
id,
|
|
13711
|
-
originalModelId:
|
|
15108
|
+
originalModelId: readString14(body, "original_model_id") ?? readString14(body, "originalModelId") ?? readString14(body, "original_id") ?? void 0,
|
|
13712
15109
|
provider,
|
|
13713
|
-
providerName:
|
|
15110
|
+
providerName: readString14(body, "provider_name") ?? readString14(body, "providerName") ?? void 0,
|
|
13714
15111
|
baseUrl,
|
|
13715
|
-
apiKey:
|
|
13716
|
-
apiMode:
|
|
15112
|
+
apiKey: readString14(body, "api_key") ?? readString14(body, "apiKey") ?? void 0,
|
|
15113
|
+
apiMode: readString14(body, "api_mode") ?? readString14(body, "apiMode") ?? void 0,
|
|
13717
15114
|
contextLength: readPositiveInteger2(
|
|
13718
15115
|
body.context_length ?? body.contextLength
|
|
13719
15116
|
),
|
|
13720
|
-
keyEnv:
|
|
13721
|
-
setDefault:
|
|
13722
|
-
reasoningEffort:
|
|
15117
|
+
keyEnv: readString14(body, "key_env") ?? readString14(body, "keyEnv") ?? void 0,
|
|
15118
|
+
setDefault: readBoolean2(body.set_default ?? body.setDefault),
|
|
15119
|
+
reasoningEffort: readString14(body, "reasoning_effort") ?? readString14(body, "reasoningEffort") ?? void 0
|
|
13723
15120
|
};
|
|
13724
15121
|
}
|
|
13725
15122
|
function readModelDefaultsInput(body) {
|
|
13726
15123
|
return {
|
|
13727
|
-
taskModelId:
|
|
13728
|
-
compressionModelId:
|
|
15124
|
+
taskModelId: readString14(body, "task_model_id") ?? readString14(body, "taskModelId") ?? readString14(body, "default_model_id") ?? readString14(body, "defaultModelId") ?? void 0,
|
|
15125
|
+
compressionModelId: readString14(body, "compression_model_id") ?? readString14(body, "compressionModelId") ?? void 0
|
|
13729
15126
|
};
|
|
13730
15127
|
}
|
|
13731
15128
|
function shouldReloadGatewayAfterModelConfigChange(body) {
|
|
13732
|
-
const explicit =
|
|
15129
|
+
const explicit = readBoolean2(body.reload_gateway ?? body.reloadGateway) ?? (readBoolean2(body.skip_gateway_reload ?? body.skipGatewayReload) === true ? false : void 0);
|
|
13733
15130
|
return explicit ?? true;
|
|
13734
15131
|
}
|
|
13735
15132
|
function markModelConfigAppliedWithoutGatewayReload(result) {
|
|
@@ -13810,19 +15207,19 @@ import {
|
|
|
13810
15207
|
copyFile as copyFile2,
|
|
13811
15208
|
cp,
|
|
13812
15209
|
mkdir as mkdir11,
|
|
13813
|
-
readFile as
|
|
15210
|
+
readFile as readFile13,
|
|
13814
15211
|
rm as rm7,
|
|
13815
|
-
stat as
|
|
15212
|
+
stat as stat12,
|
|
13816
15213
|
writeFile as writeFile4,
|
|
13817
15214
|
rename as rename5
|
|
13818
15215
|
} from "fs/promises";
|
|
13819
|
-
import
|
|
15216
|
+
import path18 from "path";
|
|
13820
15217
|
import YAML3 from "yaml";
|
|
13821
15218
|
var PROFILE_CREATE_LOG_FILE = "profile-create.log";
|
|
13822
15219
|
var PROFILE_CREATE_LOG_MAX_FILES = 3;
|
|
13823
15220
|
var MAX_PROFILE_CREATE_LOG_LINES = 260;
|
|
13824
15221
|
var MAX_OUTPUT_LINE_LENGTH = 1200;
|
|
13825
|
-
var
|
|
15222
|
+
var PROFILE_NAME_PATTERN5 = /^[a-z0-9][a-z0-9_-]{0,63}$/u;
|
|
13826
15223
|
var ALL_COPY_SCOPES = [
|
|
13827
15224
|
"models",
|
|
13828
15225
|
"skills",
|
|
@@ -14074,7 +15471,7 @@ function normalizeOptionalProfileName(value) {
|
|
|
14074
15471
|
if (!trimmed) {
|
|
14075
15472
|
return null;
|
|
14076
15473
|
}
|
|
14077
|
-
if (!
|
|
15474
|
+
if (!PROFILE_NAME_PATTERN5.test(trimmed)) {
|
|
14078
15475
|
throw new LinkHttpError(
|
|
14079
15476
|
400,
|
|
14080
15477
|
"invalid_profile_name",
|
|
@@ -14206,7 +15603,7 @@ function copyModelConfig(source, target) {
|
|
|
14206
15603
|
copied[key] = cloneJson(source[key]);
|
|
14207
15604
|
}
|
|
14208
15605
|
}
|
|
14209
|
-
const sourceAuxiliary =
|
|
15606
|
+
const sourceAuxiliary = toRecord13(source.auxiliary);
|
|
14210
15607
|
if (Object.prototype.hasOwnProperty.call(sourceAuxiliary, "compression")) {
|
|
14211
15608
|
const targetAuxiliary = ensureRecord2(target, "auxiliary");
|
|
14212
15609
|
targetAuxiliary.compression = cloneJson(sourceAuxiliary.compression);
|
|
@@ -14215,12 +15612,12 @@ function copyModelConfig(source, target) {
|
|
|
14215
15612
|
return copied;
|
|
14216
15613
|
}
|
|
14217
15614
|
function copyToolPermissionsConfig(source, target) {
|
|
14218
|
-
const sourcePlatformToolsets =
|
|
15615
|
+
const sourcePlatformToolsets = toRecord13(source.platform_toolsets);
|
|
14219
15616
|
if (Object.prototype.hasOwnProperty.call(sourcePlatformToolsets, "api_server")) {
|
|
14220
15617
|
const targetPlatformToolsets = ensureRecord2(target, "platform_toolsets");
|
|
14221
15618
|
targetPlatformToolsets.api_server = cloneJson(sourcePlatformToolsets.api_server);
|
|
14222
15619
|
}
|
|
14223
|
-
const sourceStt =
|
|
15620
|
+
const sourceStt = toRecord13(source.stt);
|
|
14224
15621
|
if (Object.prototype.hasOwnProperty.call(sourceStt, "enabled")) {
|
|
14225
15622
|
const targetStt = ensureRecord2(target, "stt");
|
|
14226
15623
|
targetStt.enabled = cloneJson(sourceStt.enabled);
|
|
@@ -14267,9 +15664,9 @@ function collectEnvKeys(value, keys = /* @__PURE__ */ new Set()) {
|
|
|
14267
15664
|
return keys;
|
|
14268
15665
|
}
|
|
14269
15666
|
async function writeEnvValues(profileName, values) {
|
|
14270
|
-
const envPath =
|
|
14271
|
-
const existingRaw = await
|
|
14272
|
-
if (
|
|
15667
|
+
const envPath = path18.join(resolveHermesProfileDir(profileName), ".env");
|
|
15668
|
+
const existingRaw = await readFile13(envPath, "utf8").catch((error) => {
|
|
15669
|
+
if (isNodeError14(error, "ENOENT")) {
|
|
14273
15670
|
return "";
|
|
14274
15671
|
}
|
|
14275
15672
|
throw error;
|
|
@@ -14294,7 +15691,7 @@ async function writeEnvValues(profileName, values) {
|
|
|
14294
15691
|
nextLines.push(`${key}=${formatEnvValue2(value)}`);
|
|
14295
15692
|
}
|
|
14296
15693
|
const nextRaw = nextLines.join("\n").replace(/\n*$/u, "\n");
|
|
14297
|
-
await mkdir11(
|
|
15694
|
+
await mkdir11(path18.dirname(envPath), { recursive: true, mode: 448 });
|
|
14298
15695
|
if (existingRaw) {
|
|
14299
15696
|
await copyFile2(envPath, `${envPath}.bak.${Date.now()}`);
|
|
14300
15697
|
}
|
|
@@ -14303,8 +15700,8 @@ async function writeEnvValues(profileName, values) {
|
|
|
14303
15700
|
await rename5(tempPath, envPath);
|
|
14304
15701
|
}
|
|
14305
15702
|
async function copySkills(sourceProfile, targetProfile) {
|
|
14306
|
-
const sourceSkills =
|
|
14307
|
-
const targetSkills =
|
|
15703
|
+
const sourceSkills = path18.join(resolveHermesProfileDir(sourceProfile), "skills");
|
|
15704
|
+
const targetSkills = path18.join(resolveHermesProfileDir(targetProfile), "skills");
|
|
14308
15705
|
if (!await pathExists(sourceSkills)) {
|
|
14309
15706
|
return;
|
|
14310
15707
|
}
|
|
@@ -14323,21 +15720,21 @@ function copyProperty(source, target, key) {
|
|
|
14323
15720
|
}
|
|
14324
15721
|
}
|
|
14325
15722
|
async function readYamlConfig(configPath) {
|
|
14326
|
-
const existingRaw = await
|
|
15723
|
+
const existingRaw = await readFile13(configPath, "utf8").catch(
|
|
14327
15724
|
(error) => {
|
|
14328
|
-
if (
|
|
15725
|
+
if (isNodeError14(error, "ENOENT")) {
|
|
14329
15726
|
return null;
|
|
14330
15727
|
}
|
|
14331
15728
|
throw error;
|
|
14332
15729
|
}
|
|
14333
15730
|
);
|
|
14334
15731
|
return {
|
|
14335
|
-
config:
|
|
15732
|
+
config: toRecord13(existingRaw ? YAML3.parse(existingRaw) : {}),
|
|
14336
15733
|
existingRaw
|
|
14337
15734
|
};
|
|
14338
15735
|
}
|
|
14339
15736
|
async function writeYamlConfig(configPath, input) {
|
|
14340
|
-
await mkdir11(
|
|
15737
|
+
await mkdir11(path18.dirname(configPath), { recursive: true, mode: 448 });
|
|
14341
15738
|
if (input.existingRaw) {
|
|
14342
15739
|
await copyFile2(configPath, `${configPath}.bak.${Date.now()}`);
|
|
14343
15740
|
}
|
|
@@ -14385,7 +15782,7 @@ async function writeProfileCreationState(paths, state) {
|
|
|
14385
15782
|
await writeJsonFile(profileCreationStatePath(paths), state);
|
|
14386
15783
|
}
|
|
14387
15784
|
async function readProfileCreationLogLines(paths) {
|
|
14388
|
-
const raw = await
|
|
15785
|
+
const raw = await readFile13(profileCreationLogPath(paths), "utf8").catch(() => "");
|
|
14389
15786
|
if (!raw.trim()) {
|
|
14390
15787
|
return [];
|
|
14391
15788
|
}
|
|
@@ -14394,10 +15791,10 @@ async function readProfileCreationLogLines(paths) {
|
|
|
14394
15791
|
);
|
|
14395
15792
|
}
|
|
14396
15793
|
function profileCreationStatePath(paths) {
|
|
14397
|
-
return
|
|
15794
|
+
return path18.join(paths.runDir, "profile-create-state.json");
|
|
14398
15795
|
}
|
|
14399
15796
|
function profileCreationLogPath(paths) {
|
|
14400
|
-
return
|
|
15797
|
+
return path18.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
|
|
14401
15798
|
}
|
|
14402
15799
|
async function clearProfileCreationLogFiles(paths) {
|
|
14403
15800
|
const primary = profileCreationLogPath(paths);
|
|
@@ -14410,8 +15807,8 @@ async function clearProfileCreationLogFiles(paths) {
|
|
|
14410
15807
|
]);
|
|
14411
15808
|
}
|
|
14412
15809
|
async function pathExists(targetPath) {
|
|
14413
|
-
return await
|
|
14414
|
-
if (
|
|
15810
|
+
return await stat12(targetPath).then(() => true).catch((error) => {
|
|
15811
|
+
if (isNodeError14(error, "ENOENT")) {
|
|
14415
15812
|
return false;
|
|
14416
15813
|
}
|
|
14417
15814
|
throw error;
|
|
@@ -14443,7 +15840,7 @@ function ensureRecord2(target, key) {
|
|
|
14443
15840
|
target[key] = next;
|
|
14444
15841
|
return next;
|
|
14445
15842
|
}
|
|
14446
|
-
function
|
|
15843
|
+
function toRecord13(value) {
|
|
14447
15844
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
14448
15845
|
}
|
|
14449
15846
|
function cloneJson(value) {
|
|
@@ -14458,7 +15855,7 @@ function formatEnvValue2(value) {
|
|
|
14458
15855
|
function escapeRegExp2(value) {
|
|
14459
15856
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
14460
15857
|
}
|
|
14461
|
-
function
|
|
15858
|
+
function isNodeError14(error, code) {
|
|
14462
15859
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
14463
15860
|
}
|
|
14464
15861
|
|
|
@@ -14543,16 +15940,16 @@ function readProfilePermissionsInput(body) {
|
|
|
14543
15940
|
const approvals = readOptionalObject(body, "approvals");
|
|
14544
15941
|
if (approvals) {
|
|
14545
15942
|
input.approvals = {
|
|
14546
|
-
mode:
|
|
15943
|
+
mode: readString14(approvals, "mode") ?? readString14(approvals, "approval_mode") ?? readString14(approvals, "approvalMode") ?? void 0,
|
|
14547
15944
|
timeout: readPositiveInteger2(approvals.timeout),
|
|
14548
|
-
cronMode:
|
|
15945
|
+
cronMode: readString14(approvals, "cron_mode") ?? readString14(approvals, "cronMode") ?? void 0
|
|
14549
15946
|
};
|
|
14550
15947
|
}
|
|
14551
15948
|
const terminal = readOptionalObject(body, "terminal");
|
|
14552
15949
|
if (terminal) {
|
|
14553
15950
|
input.terminal = {
|
|
14554
|
-
backend:
|
|
14555
|
-
cwd:
|
|
15951
|
+
backend: readString14(terminal, "backend") ?? void 0,
|
|
15952
|
+
cwd: readString14(terminal, "cwd") ?? void 0,
|
|
14556
15953
|
containerCpu: readPositiveInteger2(
|
|
14557
15954
|
terminal.container_cpu ?? terminal.containerCpu
|
|
14558
15955
|
),
|
|
@@ -14562,7 +15959,7 @@ function readProfilePermissionsInput(body) {
|
|
|
14562
15959
|
containerDisk: readPositiveInteger2(
|
|
14563
15960
|
terminal.container_disk ?? terminal.containerDisk
|
|
14564
15961
|
),
|
|
14565
|
-
containerPersistent:
|
|
15962
|
+
containerPersistent: readBoolean2(
|
|
14566
15963
|
terminal.container_persistent ?? terminal.containerPersistent
|
|
14567
15964
|
)
|
|
14568
15965
|
};
|
|
@@ -14574,7 +15971,7 @@ function readProfilePermissionsInput(body) {
|
|
|
14574
15971
|
toolsets.enabled_toolsets ?? toolsets.enabledToolsets ?? toolsets.enabled,
|
|
14575
15972
|
"toolsets.enabled"
|
|
14576
15973
|
) ?? void 0,
|
|
14577
|
-
mcpEnabled:
|
|
15974
|
+
mcpEnabled: readBoolean2(toolsets.mcp_enabled ?? toolsets.mcpEnabled)
|
|
14578
15975
|
};
|
|
14579
15976
|
}
|
|
14580
15977
|
if (Object.keys(input).length === 0) {
|
|
@@ -14669,13 +16066,13 @@ import {
|
|
|
14669
16066
|
access as access3,
|
|
14670
16067
|
copyFile as copyFile3,
|
|
14671
16068
|
mkdir as mkdir12,
|
|
14672
|
-
readdir as
|
|
14673
|
-
readFile as
|
|
16069
|
+
readdir as readdir9,
|
|
16070
|
+
readFile as readFile14,
|
|
14674
16071
|
rename as rename6,
|
|
14675
|
-
stat as
|
|
16072
|
+
stat as stat13,
|
|
14676
16073
|
writeFile as writeFile5
|
|
14677
16074
|
} from "fs/promises";
|
|
14678
|
-
import
|
|
16075
|
+
import path19 from "path";
|
|
14679
16076
|
import YAML4 from "yaml";
|
|
14680
16077
|
var ENTRY_DELIMITER = "\n\xA7\n";
|
|
14681
16078
|
var DEFAULT_MEMORY_LIMIT = 2200;
|
|
@@ -14892,7 +16289,7 @@ async function saveProviderSettings(profileName, provider, patch) {
|
|
|
14892
16289
|
if (provider === "hindsight") {
|
|
14893
16290
|
await patchJsonProviderConfig(
|
|
14894
16291
|
profileName,
|
|
14895
|
-
|
|
16292
|
+
path19.join("hindsight", "config.json"),
|
|
14896
16293
|
{
|
|
14897
16294
|
mode: patch.mode,
|
|
14898
16295
|
api_url: patch.apiUrl,
|
|
@@ -14945,7 +16342,7 @@ async function patchCustomProviderConfig(profileName, provider, patch) {
|
|
|
14945
16342
|
"\u81EA\u5B9A\u4E49 memory provider \u914D\u7F6E\u5FC5\u987B\u662F\u6709\u6548\u7684 JSON object\u3002"
|
|
14946
16343
|
);
|
|
14947
16344
|
}
|
|
14948
|
-
const config =
|
|
16345
|
+
const config = toRecord14(parsed);
|
|
14949
16346
|
if (Object.keys(config).length === 0 && parsed !== null) {
|
|
14950
16347
|
throw new HermesMemoryError(
|
|
14951
16348
|
"memory_provider_config_invalid",
|
|
@@ -14986,7 +16383,7 @@ function isSensitiveConfigKey(key) {
|
|
|
14986
16383
|
}
|
|
14987
16384
|
async function writeCustomProviderConfig(profileName, provider, config) {
|
|
14988
16385
|
const configPath = customProviderConfigPath(profileName, provider);
|
|
14989
|
-
await mkdir12(
|
|
16386
|
+
await mkdir12(path19.dirname(configPath), { recursive: true, mode: 448 });
|
|
14990
16387
|
await writeFile5(configPath, `${JSON.stringify(config, null, 2)}
|
|
14991
16388
|
`, {
|
|
14992
16389
|
encoding: "utf8",
|
|
@@ -15037,21 +16434,21 @@ function normalizeCustomProviderId(provider) {
|
|
|
15037
16434
|
}
|
|
15038
16435
|
async function patchHermesMemoryProvider(profileName, provider) {
|
|
15039
16436
|
const configPath = resolveHermesConfigPath(profileName);
|
|
15040
|
-
const existingRaw = await
|
|
16437
|
+
const existingRaw = await readFile14(configPath, "utf8").catch(
|
|
15041
16438
|
(error) => {
|
|
15042
|
-
if (
|
|
16439
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15043
16440
|
return null;
|
|
15044
16441
|
}
|
|
15045
16442
|
throw error;
|
|
15046
16443
|
}
|
|
15047
16444
|
);
|
|
15048
16445
|
const document = existingRaw ? YAML4.parseDocument(existingRaw) : new YAML4.Document({});
|
|
15049
|
-
const config =
|
|
15050
|
-
const memory =
|
|
16446
|
+
const config = toRecord14(document.toJSON());
|
|
16447
|
+
const memory = toRecord14(config.memory);
|
|
15051
16448
|
memory.provider = provider === "built-in" ? "" : provider;
|
|
15052
16449
|
config.memory = memory;
|
|
15053
16450
|
const backupPath = existingRaw ? `${configPath}.bak.${Date.now()}` : null;
|
|
15054
|
-
await mkdir12(
|
|
16451
|
+
await mkdir12(path19.dirname(configPath), { recursive: true, mode: 448 });
|
|
15055
16452
|
if (backupPath) {
|
|
15056
16453
|
await copyFile3(configPath, backupPath);
|
|
15057
16454
|
}
|
|
@@ -15064,13 +16461,13 @@ async function patchHermesMemoryProvider(profileName, provider) {
|
|
|
15064
16461
|
await rename6(tempPath, configPath);
|
|
15065
16462
|
}
|
|
15066
16463
|
function resolveMemoryDir(profileName) {
|
|
15067
|
-
return
|
|
16464
|
+
return path19.join(resolveHermesProfileDir(profileName), "memories");
|
|
15068
16465
|
}
|
|
15069
16466
|
async function readMemoryStore(profileName, target, limits) {
|
|
15070
16467
|
const filePath = memoryFilePath(profileName, target);
|
|
15071
16468
|
const entries = await readMemoryEntries(filePath);
|
|
15072
|
-
const fileStat = await
|
|
15073
|
-
if (
|
|
16469
|
+
const fileStat = await stat13(filePath).catch((error) => {
|
|
16470
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15074
16471
|
return null;
|
|
15075
16472
|
}
|
|
15076
16473
|
throw error;
|
|
@@ -15098,8 +16495,8 @@ async function readMemoryStore(profileName, target, limits) {
|
|
|
15098
16495
|
};
|
|
15099
16496
|
}
|
|
15100
16497
|
async function readMemoryEntries(filePath) {
|
|
15101
|
-
const raw = await
|
|
15102
|
-
if (
|
|
16498
|
+
const raw = await readFile14(filePath, "utf8").catch((error) => {
|
|
16499
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15103
16500
|
return "";
|
|
15104
16501
|
}
|
|
15105
16502
|
throw error;
|
|
@@ -15119,9 +16516,9 @@ async function mutateMemoryEntries(profileName, target, mutate) {
|
|
|
15119
16516
|
async function writeMemoryEntries(profileName, target, entries) {
|
|
15120
16517
|
assertWithinLimit(target, entries, await readMemoryLimits(profileName));
|
|
15121
16518
|
const filePath = memoryFilePath(profileName, target);
|
|
15122
|
-
const dir =
|
|
16519
|
+
const dir = path19.dirname(filePath);
|
|
15123
16520
|
await mkdir12(dir, { recursive: true, mode: 448 });
|
|
15124
|
-
const tempPath =
|
|
16521
|
+
const tempPath = path19.join(
|
|
15125
16522
|
dir,
|
|
15126
16523
|
`.mem_${process.pid}_${Date.now()}_${target}.tmp`
|
|
15127
16524
|
);
|
|
@@ -15132,7 +16529,7 @@ async function writeMemoryEntries(profileName, target, entries) {
|
|
|
15132
16529
|
await rename6(tempPath, filePath);
|
|
15133
16530
|
}
|
|
15134
16531
|
function memoryFilePath(profileName, target) {
|
|
15135
|
-
return
|
|
16532
|
+
return path19.join(
|
|
15136
16533
|
resolveMemoryDir(profileName),
|
|
15137
16534
|
target === "user" ? "USER.md" : "MEMORY.md"
|
|
15138
16535
|
);
|
|
@@ -15192,7 +16589,7 @@ async function readCustomProviderSetupSummary(profileName) {
|
|
|
15192
16589
|
configurable: true,
|
|
15193
16590
|
configured: true,
|
|
15194
16591
|
configurationIssue: null,
|
|
15195
|
-
providerConfigPath:
|
|
16592
|
+
providerConfigPath: path19.join(
|
|
15196
16593
|
resolveHermesProfileDir(profileName),
|
|
15197
16594
|
"<provider>.json"
|
|
15198
16595
|
),
|
|
@@ -15270,7 +16667,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
|
|
|
15270
16667
|
const config2 = await readJsonObject(
|
|
15271
16668
|
memoryProviderConfigPath(profileName, "honcho") ?? ""
|
|
15272
16669
|
);
|
|
15273
|
-
return isConfiguredEnvValue(env.HONCHO_API_KEY) || isConfiguredEnvValue(
|
|
16670
|
+
return isConfiguredEnvValue(env.HONCHO_API_KEY) || isConfiguredEnvValue(readString15(config2.apiKey)) || isConfiguredEnvValue(readString15(config2.api_key)) || isConfiguredEnvValue(readString15(config2.baseUrl)) ? { configured: true, issue: null } : {
|
|
15274
16671
|
configured: false,
|
|
15275
16672
|
issue: "Honcho \u9700\u8981\u5148\u914D\u7F6E HONCHO_API_KEY\uFF0C\u6216\u5728 honcho.json \u914D\u7F6E self-hosted baseUrl\u3002"
|
|
15276
16673
|
};
|
|
@@ -15279,7 +16676,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
|
|
|
15279
16676
|
const config2 = await readJsonObject(
|
|
15280
16677
|
memoryProviderConfigPath(profileName, "mem0") ?? ""
|
|
15281
16678
|
);
|
|
15282
|
-
return isConfiguredEnvValue(env.MEM0_API_KEY) || isConfiguredEnvValue(
|
|
16679
|
+
return isConfiguredEnvValue(env.MEM0_API_KEY) || isConfiguredEnvValue(readString15(config2.api_key)) ? { configured: true, issue: null } : {
|
|
15283
16680
|
configured: false,
|
|
15284
16681
|
issue: "Mem0 \u9700\u8981\u5148\u5728\u672C\u673A Hermes .env \u914D\u7F6E MEM0_API_KEY\u3002"
|
|
15285
16682
|
};
|
|
@@ -15321,7 +16718,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
|
|
|
15321
16718
|
memoryProviderConfigPath(profileName, provider) ?? ""
|
|
15322
16719
|
);
|
|
15323
16720
|
const mode = normalizeHindsightMode(config.mode ?? env.HINDSIGHT_MODE);
|
|
15324
|
-
const apiKey =
|
|
16721
|
+
const apiKey = readString15(config.apiKey) ?? readString15(config.api_key) ?? env.HINDSIGHT_API_KEY;
|
|
15325
16722
|
if (mode === "cloud") {
|
|
15326
16723
|
return isConfiguredEnvValue(apiKey) ? { configured: true, issue: null } : {
|
|
15327
16724
|
configured: false,
|
|
@@ -15329,15 +16726,15 @@ async function readProviderConfigurationStatus(profileName, provider) {
|
|
|
15329
16726
|
};
|
|
15330
16727
|
}
|
|
15331
16728
|
if (mode === "local_external") {
|
|
15332
|
-
const apiUrl =
|
|
16729
|
+
const apiUrl = readString15(config.api_url) ?? env.HINDSIGHT_API_URL ?? HINDSIGHT_DEFAULT_LOCAL_URL;
|
|
15333
16730
|
return isConfiguredEnvValue(apiUrl) ? { configured: true, issue: null } : {
|
|
15334
16731
|
configured: false,
|
|
15335
16732
|
issue: "Hindsight local_external \u9700\u8981\u914D\u7F6E\u53EF\u8BBF\u95EE\u7684 API URL\u3002"
|
|
15336
16733
|
};
|
|
15337
16734
|
}
|
|
15338
16735
|
if (mode === "local_embedded") {
|
|
15339
|
-
const llmProvider =
|
|
15340
|
-
const llmModel =
|
|
16736
|
+
const llmProvider = readString15(config.llm_provider) ?? "openai";
|
|
16737
|
+
const llmModel = readString15(config.llm_model);
|
|
15341
16738
|
if (!llmModel) {
|
|
15342
16739
|
return {
|
|
15343
16740
|
configured: false,
|
|
@@ -15345,7 +16742,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
|
|
|
15345
16742
|
};
|
|
15346
16743
|
}
|
|
15347
16744
|
if (llmProvider === "openai_compatible" && !isConfiguredEnvValue(
|
|
15348
|
-
|
|
16745
|
+
readString15(config.llm_base_url) ?? env.HINDSIGHT_API_LLM_BASE_URL
|
|
15349
16746
|
)) {
|
|
15350
16747
|
return {
|
|
15351
16748
|
configured: false,
|
|
@@ -15353,7 +16750,7 @@ async function readProviderConfigurationStatus(profileName, provider) {
|
|
|
15353
16750
|
};
|
|
15354
16751
|
}
|
|
15355
16752
|
if (!["ollama", "lmstudio", "openai_compatible"].includes(llmProvider) && !isConfiguredEnvValue(
|
|
15356
|
-
|
|
16753
|
+
readString15(config.llmApiKey) ?? readString15(config.llm_api_key) ?? env.HINDSIGHT_LLM_API_KEY
|
|
15357
16754
|
)) {
|
|
15358
16755
|
return {
|
|
15359
16756
|
configured: false,
|
|
@@ -15493,8 +16890,8 @@ async function readProviderSettings(profileName, provider) {
|
|
|
15493
16890
|
const config = await readJsonObject(
|
|
15494
16891
|
memoryProviderConfigPath(profileName, provider) ?? ""
|
|
15495
16892
|
);
|
|
15496
|
-
const banks =
|
|
15497
|
-
const hermesBank =
|
|
16893
|
+
const banks = toRecord14(config.banks);
|
|
16894
|
+
const hermesBank = toRecord14(banks.hermes);
|
|
15498
16895
|
const mode = normalizeHindsightMode(config.mode);
|
|
15499
16896
|
return [
|
|
15500
16897
|
selectSetting("mode", "\u8FDE\u63A5\u6A21\u5F0F", mode, [
|
|
@@ -15546,7 +16943,7 @@ async function readProviderSettings(profileName, provider) {
|
|
|
15546
16943
|
stringSetting(
|
|
15547
16944
|
"dbPath",
|
|
15548
16945
|
"SQLite \u6570\u636E\u5E93\u8DEF\u5F84",
|
|
15549
|
-
config.db_path ??
|
|
16946
|
+
config.db_path ?? path19.join(resolveHermesProfileDir(profileName), "memory_store.db")
|
|
15550
16947
|
),
|
|
15551
16948
|
booleanSetting("autoExtract", "\u4F1A\u8BDD\u7ED3\u675F\u81EA\u52A8\u62BD\u53D6", config.auto_extract ?? false),
|
|
15552
16949
|
numberSetting("defaultTrust", "\u9ED8\u8BA4\u4FE1\u4EFB\u5206", config.default_trust ?? 0.5),
|
|
@@ -15569,7 +16966,7 @@ async function readProviderSettings(profileName, provider) {
|
|
|
15569
16966
|
stringSetting(
|
|
15570
16967
|
"workingDirectory",
|
|
15571
16968
|
"\u5DE5\u4F5C\u76EE\u5F55",
|
|
15572
|
-
|
|
16969
|
+
path19.join(resolveHermesProfileDir(profileName), "byterover"),
|
|
15573
16970
|
false
|
|
15574
16971
|
)
|
|
15575
16972
|
];
|
|
@@ -15578,16 +16975,16 @@ async function readProviderSettings(profileName, provider) {
|
|
|
15578
16975
|
}
|
|
15579
16976
|
function memoryProviderConfigPath(profileName, provider) {
|
|
15580
16977
|
if (provider === "honcho") {
|
|
15581
|
-
return
|
|
16978
|
+
return path19.join(resolveHermesProfileDir(profileName), "honcho.json");
|
|
15582
16979
|
}
|
|
15583
16980
|
if (provider === "mem0") {
|
|
15584
|
-
return
|
|
16981
|
+
return path19.join(resolveHermesProfileDir(profileName), "mem0.json");
|
|
15585
16982
|
}
|
|
15586
16983
|
if (provider === "supermemory") {
|
|
15587
|
-
return
|
|
16984
|
+
return path19.join(resolveHermesProfileDir(profileName), "supermemory.json");
|
|
15588
16985
|
}
|
|
15589
16986
|
if (provider === "hindsight") {
|
|
15590
|
-
return
|
|
16987
|
+
return path19.join(
|
|
15591
16988
|
resolveHermesProfileDir(profileName),
|
|
15592
16989
|
"hindsight",
|
|
15593
16990
|
"config.json"
|
|
@@ -15596,21 +16993,21 @@ function memoryProviderConfigPath(profileName, provider) {
|
|
|
15596
16993
|
return null;
|
|
15597
16994
|
}
|
|
15598
16995
|
function customProviderConfigPath(profileName, provider) {
|
|
15599
|
-
return
|
|
16996
|
+
return path19.join(
|
|
15600
16997
|
resolveHermesProfileDir(profileName),
|
|
15601
16998
|
`${normalizeCustomProviderId(provider)}.json`
|
|
15602
16999
|
);
|
|
15603
17000
|
}
|
|
15604
17001
|
function customProviderRegistryPath(profileName) {
|
|
15605
|
-
return
|
|
17002
|
+
return path19.join(
|
|
15606
17003
|
resolveHermesProfileDir(profileName),
|
|
15607
17004
|
CUSTOM_PROVIDER_REGISTRY_FILE
|
|
15608
17005
|
);
|
|
15609
17006
|
}
|
|
15610
17007
|
async function readCustomProviderRegistry(profileName) {
|
|
15611
|
-
const raw = await
|
|
17008
|
+
const raw = await readFile14(customProviderRegistryPath(profileName), "utf8").catch(
|
|
15612
17009
|
(error) => {
|
|
15613
|
-
if (
|
|
17010
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15614
17011
|
return "";
|
|
15615
17012
|
}
|
|
15616
17013
|
throw error;
|
|
@@ -15621,18 +17018,18 @@ async function readCustomProviderRegistry(profileName) {
|
|
|
15621
17018
|
}
|
|
15622
17019
|
try {
|
|
15623
17020
|
const parsed = JSON.parse(raw);
|
|
15624
|
-
const providers = Array.isArray(parsed) ? parsed : Array.isArray(
|
|
17021
|
+
const providers = Array.isArray(parsed) ? parsed : Array.isArray(toRecord14(parsed).providers) ? toRecord14(parsed).providers : [];
|
|
15625
17022
|
return providers.map((item) => {
|
|
15626
17023
|
if (typeof item === "string") {
|
|
15627
17024
|
const id2 = normalizeCustomProviderId(item);
|
|
15628
17025
|
return { id: id2, label: id2, description: "\u81EA\u5B9A\u4E49 memory provider\u3002" };
|
|
15629
17026
|
}
|
|
15630
|
-
const record =
|
|
15631
|
-
const id = normalizeCustomProviderId(
|
|
17027
|
+
const record = toRecord14(item);
|
|
17028
|
+
const id = normalizeCustomProviderId(readString15(record.id) ?? "");
|
|
15632
17029
|
return {
|
|
15633
17030
|
id,
|
|
15634
|
-
label:
|
|
15635
|
-
description:
|
|
17031
|
+
label: readString15(record.label) ?? id,
|
|
17032
|
+
description: readString15(record.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
|
|
15636
17033
|
};
|
|
15637
17034
|
}).filter((item) => item.id);
|
|
15638
17035
|
} catch {
|
|
@@ -15647,7 +17044,7 @@ async function saveCustomProviderRegistryEntry(profileName, provider) {
|
|
|
15647
17044
|
{ id: providerId, label: providerId, description: "\u81EA\u5B9A\u4E49 memory provider\u3002" }
|
|
15648
17045
|
].sort((left, right) => left.id.localeCompare(right.id));
|
|
15649
17046
|
const registryPath2 = customProviderRegistryPath(profileName);
|
|
15650
|
-
await mkdir12(
|
|
17047
|
+
await mkdir12(path19.dirname(registryPath2), { recursive: true, mode: 448 });
|
|
15651
17048
|
await writeFile5(
|
|
15652
17049
|
registryPath2,
|
|
15653
17050
|
`${JSON.stringify({ providers }, null, 2)}
|
|
@@ -15656,10 +17053,10 @@ async function saveCustomProviderRegistryEntry(profileName, provider) {
|
|
|
15656
17053
|
);
|
|
15657
17054
|
}
|
|
15658
17055
|
async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
15659
|
-
const pluginsDir =
|
|
15660
|
-
const entries = await
|
|
17056
|
+
const pluginsDir = path19.join(resolveHermesProfileDir(profileName), "plugins");
|
|
17057
|
+
const entries = await readdir9(pluginsDir, { withFileTypes: true }).catch(
|
|
15661
17058
|
(error) => {
|
|
15662
|
-
if (
|
|
17059
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15663
17060
|
return [];
|
|
15664
17061
|
}
|
|
15665
17062
|
throw error;
|
|
@@ -15676,21 +17073,21 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
|
15676
17073
|
} catch {
|
|
15677
17074
|
continue;
|
|
15678
17075
|
}
|
|
15679
|
-
const providerDir =
|
|
17076
|
+
const providerDir = path19.join(pluginsDir, entry.name);
|
|
15680
17077
|
if (!await isMemoryProviderPluginDir(providerDir)) {
|
|
15681
17078
|
continue;
|
|
15682
17079
|
}
|
|
15683
17080
|
const meta = await readPluginMetadata(providerDir);
|
|
15684
17081
|
descriptors.push({
|
|
15685
17082
|
id: providerId,
|
|
15686
|
-
label:
|
|
15687
|
-
description:
|
|
17083
|
+
label: readString15(meta.name) ?? providerId,
|
|
17084
|
+
description: readString15(meta.description) ?? "\u81EA\u5B9A\u4E49 memory provider\u3002"
|
|
15688
17085
|
});
|
|
15689
17086
|
}
|
|
15690
17087
|
return descriptors;
|
|
15691
17088
|
}
|
|
15692
17089
|
async function isUserMemoryProviderInstalled(profileName, provider) {
|
|
15693
|
-
const providerDir =
|
|
17090
|
+
const providerDir = path19.join(
|
|
15694
17091
|
resolveHermesProfileDir(profileName),
|
|
15695
17092
|
"plugins",
|
|
15696
17093
|
normalizeCustomProviderId(provider)
|
|
@@ -15698,9 +17095,9 @@ async function isUserMemoryProviderInstalled(profileName, provider) {
|
|
|
15698
17095
|
return isMemoryProviderPluginDir(providerDir);
|
|
15699
17096
|
}
|
|
15700
17097
|
async function isMemoryProviderPluginDir(providerDir) {
|
|
15701
|
-
const source = await
|
|
17098
|
+
const source = await readFile14(path19.join(providerDir, "__init__.py"), "utf8").catch(
|
|
15702
17099
|
(error) => {
|
|
15703
|
-
if (
|
|
17100
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15704
17101
|
return "";
|
|
15705
17102
|
}
|
|
15706
17103
|
throw error;
|
|
@@ -15710,22 +17107,22 @@ async function isMemoryProviderPluginDir(providerDir) {
|
|
|
15710
17107
|
return sample.includes("register_memory_provider") || sample.includes("MemoryProvider");
|
|
15711
17108
|
}
|
|
15712
17109
|
async function readPluginMetadata(providerDir) {
|
|
15713
|
-
const raw = await
|
|
17110
|
+
const raw = await readFile14(path19.join(providerDir, "plugin.yaml"), "utf8").catch(
|
|
15714
17111
|
(error) => {
|
|
15715
|
-
if (
|
|
17112
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15716
17113
|
return "";
|
|
15717
17114
|
}
|
|
15718
17115
|
throw error;
|
|
15719
17116
|
}
|
|
15720
17117
|
);
|
|
15721
|
-
return raw ?
|
|
17118
|
+
return raw ? toRecord14(YAML4.parse(raw)) : {};
|
|
15722
17119
|
}
|
|
15723
17120
|
async function resolveByteRoverCli() {
|
|
15724
17121
|
const candidates = [
|
|
15725
|
-
...(process.env.PATH ?? "").split(
|
|
15726
|
-
|
|
17122
|
+
...(process.env.PATH ?? "").split(path19.delimiter).filter(Boolean).map((dir) => path19.join(dir, "brv")),
|
|
17123
|
+
path19.join(process.env.HOME ?? "", ".brv-cli", "bin", "brv"),
|
|
15727
17124
|
"/usr/local/bin/brv",
|
|
15728
|
-
|
|
17125
|
+
path19.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
|
|
15729
17126
|
].filter(Boolean);
|
|
15730
17127
|
for (const candidate of candidates) {
|
|
15731
17128
|
const found = await access3(candidate).then(() => true).catch(() => false);
|
|
@@ -15736,32 +17133,32 @@ async function resolveByteRoverCli() {
|
|
|
15736
17133
|
return null;
|
|
15737
17134
|
}
|
|
15738
17135
|
async function readHolographicProviderConfig(profileName) {
|
|
15739
|
-
const raw = await
|
|
17136
|
+
const raw = await readFile14(resolveHermesConfigPath(profileName), "utf8").catch(
|
|
15740
17137
|
(error) => {
|
|
15741
|
-
if (
|
|
17138
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15742
17139
|
return "";
|
|
15743
17140
|
}
|
|
15744
17141
|
throw error;
|
|
15745
17142
|
}
|
|
15746
17143
|
);
|
|
15747
|
-
const config = raw ?
|
|
15748
|
-
const plugins =
|
|
15749
|
-
return
|
|
17144
|
+
const config = raw ? toRecord14(YAML4.parse(raw)) : {};
|
|
17145
|
+
const plugins = toRecord14(config.plugins);
|
|
17146
|
+
return toRecord14(plugins["hermes-memory-store"]);
|
|
15750
17147
|
}
|
|
15751
17148
|
async function patchHolographicProviderConfig(profileName, patch) {
|
|
15752
17149
|
const configPath = resolveHermesConfigPath(profileName);
|
|
15753
|
-
const existingRaw = await
|
|
17150
|
+
const existingRaw = await readFile14(configPath, "utf8").catch(
|
|
15754
17151
|
(error) => {
|
|
15755
|
-
if (
|
|
17152
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15756
17153
|
return null;
|
|
15757
17154
|
}
|
|
15758
17155
|
throw error;
|
|
15759
17156
|
}
|
|
15760
17157
|
);
|
|
15761
17158
|
const document = existingRaw ? YAML4.parseDocument(existingRaw) : new YAML4.Document({});
|
|
15762
|
-
const config =
|
|
15763
|
-
const plugins =
|
|
15764
|
-
const memoryStore =
|
|
17159
|
+
const config = toRecord14(document.toJSON());
|
|
17160
|
+
const plugins = toRecord14(config.plugins);
|
|
17161
|
+
const memoryStore = toRecord14(plugins["hermes-memory-store"]);
|
|
15765
17162
|
for (const [key, value] of Object.entries(patch)) {
|
|
15766
17163
|
if (value !== void 0) {
|
|
15767
17164
|
memoryStore[key] = value;
|
|
@@ -15769,7 +17166,7 @@ async function patchHolographicProviderConfig(profileName, patch) {
|
|
|
15769
17166
|
}
|
|
15770
17167
|
plugins["hermes-memory-store"] = memoryStore;
|
|
15771
17168
|
config.plugins = plugins;
|
|
15772
|
-
await mkdir12(
|
|
17169
|
+
await mkdir12(path19.dirname(configPath), { recursive: true, mode: 448 });
|
|
15773
17170
|
if (existingRaw) {
|
|
15774
17171
|
await copyFile3(configPath, `${configPath}.bak.${Date.now()}`);
|
|
15775
17172
|
}
|
|
@@ -15788,9 +17185,9 @@ async function patchHermesMemoryEnv(profileName, patch) {
|
|
|
15788
17185
|
if (entries.length === 0) {
|
|
15789
17186
|
return;
|
|
15790
17187
|
}
|
|
15791
|
-
const envPath =
|
|
15792
|
-
const existingRaw = await
|
|
15793
|
-
if (
|
|
17188
|
+
const envPath = path19.join(resolveHermesProfileDir(profileName), ".env");
|
|
17189
|
+
const existingRaw = await readFile14(envPath, "utf8").catch((error) => {
|
|
17190
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15794
17191
|
return "";
|
|
15795
17192
|
}
|
|
15796
17193
|
throw error;
|
|
@@ -15820,7 +17217,7 @@ async function patchHermesMemoryEnv(profileName, patch) {
|
|
|
15820
17217
|
}
|
|
15821
17218
|
}
|
|
15822
17219
|
const nextRaw = nextLines.join("\n").replace(/\n*$/u, "\n");
|
|
15823
|
-
await mkdir12(
|
|
17220
|
+
await mkdir12(path19.dirname(envPath), { recursive: true, mode: 448 });
|
|
15824
17221
|
if (existingRaw) {
|
|
15825
17222
|
await copyFile3(envPath, `${envPath}.bak.${Date.now()}`);
|
|
15826
17223
|
}
|
|
@@ -15839,29 +17236,29 @@ function isMemoryEnvKeyWritable(key) {
|
|
|
15839
17236
|
].includes(key);
|
|
15840
17237
|
}
|
|
15841
17238
|
function normalizeHindsightMode(value) {
|
|
15842
|
-
const mode =
|
|
17239
|
+
const mode = readString15(value) ?? "cloud";
|
|
15843
17240
|
return mode === "local" ? "local_embedded" : mode;
|
|
15844
17241
|
}
|
|
15845
17242
|
async function readActiveMemoryProvider(profileName) {
|
|
15846
|
-
const raw = await
|
|
17243
|
+
const raw = await readFile14(
|
|
15847
17244
|
resolveHermesConfigPath(profileName),
|
|
15848
17245
|
"utf8"
|
|
15849
17246
|
).catch((error) => {
|
|
15850
|
-
if (
|
|
17247
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15851
17248
|
return "";
|
|
15852
17249
|
}
|
|
15853
17250
|
throw error;
|
|
15854
17251
|
});
|
|
15855
|
-
const config = raw ?
|
|
15856
|
-
const memory =
|
|
15857
|
-
const provider =
|
|
17252
|
+
const config = raw ? toRecord14(YAML4.parse(raw)) : {};
|
|
17253
|
+
const memory = toRecord14(config.memory);
|
|
17254
|
+
const provider = readString15(memory.provider);
|
|
15858
17255
|
if (!provider || provider === "built-in" || provider === "builtin" || provider === "built_in") {
|
|
15859
17256
|
return null;
|
|
15860
17257
|
}
|
|
15861
17258
|
return provider;
|
|
15862
17259
|
}
|
|
15863
17260
|
async function patchJsonProviderConfig(profileName, relativePath, patch) {
|
|
15864
|
-
const configPath =
|
|
17261
|
+
const configPath = path19.join(
|
|
15865
17262
|
resolveHermesProfileDir(profileName),
|
|
15866
17263
|
relativePath
|
|
15867
17264
|
);
|
|
@@ -15872,7 +17269,7 @@ async function patchJsonProviderConfig(profileName, relativePath, patch) {
|
|
|
15872
17269
|
next[key] = value;
|
|
15873
17270
|
}
|
|
15874
17271
|
}
|
|
15875
|
-
await mkdir12(
|
|
17272
|
+
await mkdir12(path19.dirname(configPath), { recursive: true, mode: 448 });
|
|
15876
17273
|
await writeFile5(configPath, `${JSON.stringify(next, null, 2)}
|
|
15877
17274
|
`, {
|
|
15878
17275
|
encoding: "utf8",
|
|
@@ -15880,18 +17277,18 @@ async function patchJsonProviderConfig(profileName, relativePath, patch) {
|
|
|
15880
17277
|
});
|
|
15881
17278
|
}
|
|
15882
17279
|
async function readJsonObject(filePath) {
|
|
15883
|
-
const raw = await
|
|
15884
|
-
if (
|
|
17280
|
+
const raw = await readFile14(filePath, "utf8").catch((error) => {
|
|
17281
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15885
17282
|
return "{}";
|
|
15886
17283
|
}
|
|
15887
17284
|
throw error;
|
|
15888
17285
|
});
|
|
15889
17286
|
try {
|
|
15890
|
-
return
|
|
17287
|
+
return toRecord14(JSON.parse(raw || "{}"));
|
|
15891
17288
|
} catch {
|
|
15892
17289
|
throw new HermesMemoryError(
|
|
15893
17290
|
"memory_provider_config_invalid",
|
|
15894
|
-
`${
|
|
17291
|
+
`${path19.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
|
|
15895
17292
|
);
|
|
15896
17293
|
}
|
|
15897
17294
|
}
|
|
@@ -15899,7 +17296,7 @@ function booleanSetting(key, label, value) {
|
|
|
15899
17296
|
return {
|
|
15900
17297
|
key,
|
|
15901
17298
|
label,
|
|
15902
|
-
value:
|
|
17299
|
+
value: readBoolean3(value) ?? false,
|
|
15903
17300
|
editable: true,
|
|
15904
17301
|
kind: "boolean"
|
|
15905
17302
|
};
|
|
@@ -15912,7 +17309,7 @@ function stringSetting(key, label, value, editable = true) {
|
|
|
15912
17309
|
return {
|
|
15913
17310
|
key,
|
|
15914
17311
|
label,
|
|
15915
|
-
value:
|
|
17312
|
+
value: readString15(value) ?? "",
|
|
15916
17313
|
editable,
|
|
15917
17314
|
kind: "string"
|
|
15918
17315
|
};
|
|
@@ -15927,21 +17324,21 @@ function textSetting(key, label, value, editable = true) {
|
|
|
15927
17324
|
};
|
|
15928
17325
|
}
|
|
15929
17326
|
function selectSetting(key, label, value, options, editable = true) {
|
|
15930
|
-
const stringValue =
|
|
17327
|
+
const stringValue = readString15(value) ?? options[0] ?? null;
|
|
15931
17328
|
return { key, label, value: stringValue, editable, kind: "select", options };
|
|
15932
17329
|
}
|
|
15933
17330
|
async function readMemoryLimits(profileName) {
|
|
15934
|
-
const raw = await
|
|
17331
|
+
const raw = await readFile14(
|
|
15935
17332
|
resolveHermesConfigPath(profileName),
|
|
15936
17333
|
"utf8"
|
|
15937
17334
|
).catch((error) => {
|
|
15938
|
-
if (
|
|
17335
|
+
if (isNodeError15(error, "ENOENT")) {
|
|
15939
17336
|
return "";
|
|
15940
17337
|
}
|
|
15941
17338
|
throw error;
|
|
15942
17339
|
});
|
|
15943
|
-
const config = raw ?
|
|
15944
|
-
const memory =
|
|
17340
|
+
const config = raw ? toRecord14(YAML4.parse(raw)) : {};
|
|
17341
|
+
const memory = toRecord14(config.memory);
|
|
15945
17342
|
return {
|
|
15946
17343
|
memory: readPositiveInteger3(memory.memory_char_limit) ?? DEFAULT_MEMORY_LIMIT,
|
|
15947
17344
|
user: readPositiveInteger3(memory.user_char_limit) ?? DEFAULT_USER_LIMIT
|
|
@@ -15997,17 +17394,17 @@ function hashString(value) {
|
|
|
15997
17394
|
}
|
|
15998
17395
|
return hash.toString(16);
|
|
15999
17396
|
}
|
|
16000
|
-
function
|
|
17397
|
+
function toRecord14(value) {
|
|
16001
17398
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
16002
17399
|
}
|
|
16003
|
-
function
|
|
17400
|
+
function readString15(value) {
|
|
16004
17401
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
16005
17402
|
}
|
|
16006
17403
|
function readPositiveInteger3(value) {
|
|
16007
17404
|
const numberValue = typeof value === "number" ? value : typeof value === "string" ? Number(value.trim()) : NaN;
|
|
16008
17405
|
return Number.isFinite(numberValue) && numberValue > 0 ? Math.floor(numberValue) : void 0;
|
|
16009
17406
|
}
|
|
16010
|
-
function
|
|
17407
|
+
function readBoolean3(value) {
|
|
16011
17408
|
if (typeof value === "boolean") {
|
|
16012
17409
|
return value;
|
|
16013
17410
|
}
|
|
@@ -16028,7 +17425,7 @@ function formatEnvValue3(value) {
|
|
|
16028
17425
|
function escapeRegExp3(value) {
|
|
16029
17426
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
16030
17427
|
}
|
|
16031
|
-
function
|
|
17428
|
+
function isNodeError15(error, code) {
|
|
16032
17429
|
return error instanceof Error && "code" in error && error.code === code;
|
|
16033
17430
|
}
|
|
16034
17431
|
|
|
@@ -16142,7 +17539,7 @@ function registerProfileMemoryRoutes(router, options) {
|
|
|
16142
17539
|
);
|
|
16143
17540
|
}
|
|
16144
17541
|
function readMemoryTarget(body) {
|
|
16145
|
-
const raw =
|
|
17542
|
+
const raw = readString14(body, "target");
|
|
16146
17543
|
if (raw === "memory" || raw === "user") {
|
|
16147
17544
|
return raw;
|
|
16148
17545
|
}
|
|
@@ -16153,7 +17550,7 @@ function readMemoryTarget(body) {
|
|
|
16153
17550
|
);
|
|
16154
17551
|
}
|
|
16155
17552
|
function readMemoryResetTarget(body) {
|
|
16156
|
-
const raw =
|
|
17553
|
+
const raw = readString14(body, "target") ?? "all";
|
|
16157
17554
|
if (raw === "all" || raw === "memory" || raw === "user") {
|
|
16158
17555
|
return raw;
|
|
16159
17556
|
}
|
|
@@ -16164,7 +17561,7 @@ function readMemoryResetTarget(body) {
|
|
|
16164
17561
|
);
|
|
16165
17562
|
}
|
|
16166
17563
|
function readRequiredMemoryContent(body) {
|
|
16167
|
-
const content =
|
|
17564
|
+
const content = readString14(body, "content") ?? readString14(body, "text");
|
|
16168
17565
|
if (!content) {
|
|
16169
17566
|
throw new LinkHttpError(
|
|
16170
17567
|
400,
|
|
@@ -16175,7 +17572,7 @@ function readRequiredMemoryContent(body) {
|
|
|
16175
17572
|
return content;
|
|
16176
17573
|
}
|
|
16177
17574
|
function readRequiredMemoryMatch(body) {
|
|
16178
|
-
const oldText =
|
|
17575
|
+
const oldText = readString14(body, "old_text") ?? readString14(body, "oldText") ?? readString14(body, "match");
|
|
16179
17576
|
if (!oldText) {
|
|
16180
17577
|
throw new LinkHttpError(
|
|
16181
17578
|
400,
|
|
@@ -16186,7 +17583,7 @@ function readRequiredMemoryMatch(body) {
|
|
|
16186
17583
|
return oldText;
|
|
16187
17584
|
}
|
|
16188
17585
|
function readRequiredMemoryProvider(body) {
|
|
16189
|
-
const provider =
|
|
17586
|
+
const provider = readString14(body, "provider") ?? readString14(body, "provider_id") ?? readString14(body, "providerId");
|
|
16190
17587
|
if (!provider) {
|
|
16191
17588
|
throw new LinkHttpError(
|
|
16192
17589
|
400,
|
|
@@ -16198,7 +17595,7 @@ function readRequiredMemoryProvider(body) {
|
|
|
16198
17595
|
}
|
|
16199
17596
|
function readMemorySettingsPatch(body) {
|
|
16200
17597
|
const input = {};
|
|
16201
|
-
const mode =
|
|
17598
|
+
const mode = readString14(body, "mode");
|
|
16202
17599
|
if (mode) {
|
|
16203
17600
|
input.mode = mode;
|
|
16204
17601
|
}
|
|
@@ -16210,7 +17607,7 @@ function readMemorySettingsPatch(body) {
|
|
|
16210
17607
|
if (bankId !== void 0) {
|
|
16211
17608
|
input.bankId = bankId;
|
|
16212
17609
|
}
|
|
16213
|
-
const llmProvider =
|
|
17610
|
+
const llmProvider = readString14(body, "llm_provider") ?? readString14(body, "llmProvider");
|
|
16214
17611
|
if (llmProvider) {
|
|
16215
17612
|
input.llmProvider = llmProvider;
|
|
16216
17613
|
}
|
|
@@ -16226,23 +17623,23 @@ function readMemorySettingsPatch(body) {
|
|
|
16226
17623
|
if (containerTag !== void 0) {
|
|
16227
17624
|
input.containerTag = containerTag;
|
|
16228
17625
|
}
|
|
16229
|
-
const autoRecall =
|
|
17626
|
+
const autoRecall = readBoolean2(body.auto_recall ?? body.autoRecall);
|
|
16230
17627
|
if (autoRecall !== void 0) {
|
|
16231
17628
|
input.autoRecall = autoRecall;
|
|
16232
17629
|
}
|
|
16233
|
-
const autoCapture =
|
|
17630
|
+
const autoCapture = readBoolean2(body.auto_capture ?? body.autoCapture);
|
|
16234
17631
|
if (autoCapture !== void 0) {
|
|
16235
17632
|
input.autoCapture = autoCapture;
|
|
16236
17633
|
}
|
|
16237
|
-
const autoRetain =
|
|
17634
|
+
const autoRetain = readBoolean2(body.auto_retain ?? body.autoRetain);
|
|
16238
17635
|
if (autoRetain !== void 0) {
|
|
16239
17636
|
input.autoRetain = autoRetain;
|
|
16240
17637
|
}
|
|
16241
|
-
const memoryMode =
|
|
17638
|
+
const memoryMode = readString14(body, "memory_mode") ?? readString14(body, "memoryMode");
|
|
16242
17639
|
if (memoryMode) {
|
|
16243
17640
|
input.memoryMode = memoryMode;
|
|
16244
17641
|
}
|
|
16245
|
-
const recallBudget =
|
|
17642
|
+
const recallBudget = readString14(body, "recall_budget") ?? readString14(body, "recallBudget");
|
|
16246
17643
|
if (recallBudget) {
|
|
16247
17644
|
input.recallBudget = recallBudget;
|
|
16248
17645
|
}
|
|
@@ -16258,11 +17655,11 @@ function readMemorySettingsPatch(body) {
|
|
|
16258
17655
|
if (profileFrequency !== void 0) {
|
|
16259
17656
|
input.profileFrequency = profileFrequency;
|
|
16260
17657
|
}
|
|
16261
|
-
const captureMode =
|
|
17658
|
+
const captureMode = readString14(body, "capture_mode") ?? readString14(body, "captureMode");
|
|
16262
17659
|
if (captureMode) {
|
|
16263
17660
|
input.captureMode = captureMode;
|
|
16264
17661
|
}
|
|
16265
|
-
const searchMode =
|
|
17662
|
+
const searchMode = readString14(body, "search_mode") ?? readString14(body, "searchMode");
|
|
16266
17663
|
if (searchMode) {
|
|
16267
17664
|
input.searchMode = searchMode;
|
|
16268
17665
|
}
|
|
@@ -16296,19 +17693,19 @@ function readMemorySettingsPatch(body) {
|
|
|
16296
17693
|
if (aiPeer !== void 0) {
|
|
16297
17694
|
input.aiPeer = aiPeer;
|
|
16298
17695
|
}
|
|
16299
|
-
const recallMode =
|
|
17696
|
+
const recallMode = readString14(body, "recall_mode") ?? readString14(body, "recallMode");
|
|
16300
17697
|
if (recallMode) {
|
|
16301
17698
|
input.recallMode = recallMode;
|
|
16302
17699
|
}
|
|
16303
|
-
const writeFrequency =
|
|
17700
|
+
const writeFrequency = readString14(body, "write_frequency") ?? readString14(body, "writeFrequency");
|
|
16304
17701
|
if (writeFrequency) {
|
|
16305
17702
|
input.writeFrequency = writeFrequency;
|
|
16306
17703
|
}
|
|
16307
|
-
const saveMessages =
|
|
17704
|
+
const saveMessages = readBoolean2(body.save_messages ?? body.saveMessages);
|
|
16308
17705
|
if (saveMessages !== void 0) {
|
|
16309
17706
|
input.saveMessages = saveMessages;
|
|
16310
17707
|
}
|
|
16311
|
-
const sessionStrategy =
|
|
17708
|
+
const sessionStrategy = readString14(body, "session_strategy") ?? readString14(body, "sessionStrategy");
|
|
16312
17709
|
if (sessionStrategy) {
|
|
16313
17710
|
input.sessionStrategy = sessionStrategy;
|
|
16314
17711
|
}
|
|
@@ -16344,7 +17741,7 @@ function readMemorySettingsPatch(body) {
|
|
|
16344
17741
|
if (agentId !== void 0) {
|
|
16345
17742
|
input.agentId = agentId;
|
|
16346
17743
|
}
|
|
16347
|
-
const rerank =
|
|
17744
|
+
const rerank = readBoolean2(body.rerank);
|
|
16348
17745
|
if (rerank !== void 0) {
|
|
16349
17746
|
input.rerank = rerank;
|
|
16350
17747
|
}
|
|
@@ -16368,7 +17765,7 @@ function readMemorySettingsPatch(body) {
|
|
|
16368
17765
|
if (dbPath !== void 0) {
|
|
16369
17766
|
input.dbPath = dbPath;
|
|
16370
17767
|
}
|
|
16371
|
-
const autoExtract =
|
|
17768
|
+
const autoExtract = readBoolean2(body.auto_extract ?? body.autoExtract);
|
|
16372
17769
|
if (autoExtract !== void 0) {
|
|
16373
17770
|
input.autoExtract = autoExtract;
|
|
16374
17771
|
}
|
|
@@ -16439,12 +17836,12 @@ function toMemoryHttpError(error) {
|
|
|
16439
17836
|
import {
|
|
16440
17837
|
copyFile as copyFile4,
|
|
16441
17838
|
mkdir as mkdir13,
|
|
16442
|
-
readFile as
|
|
16443
|
-
readdir as
|
|
17839
|
+
readFile as readFile15,
|
|
17840
|
+
readdir as readdir10,
|
|
16444
17841
|
rename as rename7,
|
|
16445
17842
|
writeFile as writeFile6
|
|
16446
17843
|
} from "fs/promises";
|
|
16447
|
-
import
|
|
17844
|
+
import path20 from "path";
|
|
16448
17845
|
import YAML5 from "yaml";
|
|
16449
17846
|
var HermesSkillNotFoundError = class extends Error {
|
|
16450
17847
|
constructor(skillName) {
|
|
@@ -16458,7 +17855,7 @@ var EXCLUDED_SKILL_DIRS = /* @__PURE__ */ new Set([".git", ".github", ".hub"]);
|
|
|
16458
17855
|
async function listHermesProfileSkills(profileName, paths = resolveRuntimePaths()) {
|
|
16459
17856
|
const profile = await readExistingProfile(profileName, paths);
|
|
16460
17857
|
const profileDir = resolveHermesProfileDir(profile.name);
|
|
16461
|
-
const skillsRoot =
|
|
17858
|
+
const skillsRoot = path20.join(profileDir, "skills");
|
|
16462
17859
|
const [skillFiles, disabled, provenance] = await Promise.all([
|
|
16463
17860
|
findSkillFiles(skillsRoot),
|
|
16464
17861
|
readDisabledSkillNames(resolveHermesConfigPath(profile.name)),
|
|
@@ -16535,9 +17932,9 @@ async function findSkillFiles(root) {
|
|
|
16535
17932
|
return results.sort((left, right) => left.localeCompare(right));
|
|
16536
17933
|
}
|
|
16537
17934
|
async function collectSkillFiles(directory, results) {
|
|
16538
|
-
const entries = await
|
|
17935
|
+
const entries = await readdir10(directory, { withFileTypes: true }).catch(
|
|
16539
17936
|
(error) => {
|
|
16540
|
-
if (
|
|
17937
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
16541
17938
|
return [];
|
|
16542
17939
|
}
|
|
16543
17940
|
throw error;
|
|
@@ -16549,7 +17946,7 @@ async function collectSkillFiles(directory, results) {
|
|
|
16549
17946
|
if (EXCLUDED_SKILL_DIRS.has(entry.name)) {
|
|
16550
17947
|
continue;
|
|
16551
17948
|
}
|
|
16552
|
-
const entryPath =
|
|
17949
|
+
const entryPath = path20.join(directory, entry.name);
|
|
16553
17950
|
if (entry.isDirectory()) {
|
|
16554
17951
|
await collectSkillFiles(entryPath, results);
|
|
16555
17952
|
continue;
|
|
@@ -16560,9 +17957,9 @@ async function collectSkillFiles(directory, results) {
|
|
|
16560
17957
|
}
|
|
16561
17958
|
}
|
|
16562
17959
|
async function readSkillMetadata(input) {
|
|
16563
|
-
const raw = await
|
|
17960
|
+
const raw = await readFile15(input.skillFile, "utf8").catch(
|
|
16564
17961
|
(error) => {
|
|
16565
|
-
if (
|
|
17962
|
+
if (isNodeError16(error, "ENOENT") || isNodeError16(error, "EACCES")) {
|
|
16566
17963
|
return null;
|
|
16567
17964
|
}
|
|
16568
17965
|
throw error;
|
|
@@ -16571,16 +17968,16 @@ async function readSkillMetadata(input) {
|
|
|
16571
17968
|
if (raw === null) {
|
|
16572
17969
|
return null;
|
|
16573
17970
|
}
|
|
16574
|
-
const skillDir =
|
|
17971
|
+
const skillDir = path20.dirname(input.skillFile);
|
|
16575
17972
|
const { frontmatter, body } = parseSkillDocument(raw.slice(0, 4e3));
|
|
16576
17973
|
const name = normalizeSkillName(
|
|
16577
|
-
|
|
17974
|
+
readString16(frontmatter.name) ?? path20.basename(skillDir)
|
|
16578
17975
|
);
|
|
16579
17976
|
if (!name) {
|
|
16580
17977
|
return null;
|
|
16581
17978
|
}
|
|
16582
17979
|
const description = normalizeDescription(
|
|
16583
|
-
|
|
17980
|
+
readString16(frontmatter.description) ?? firstBodyDescription(body)
|
|
16584
17981
|
);
|
|
16585
17982
|
const provenance = input.provenance.get(name) ?? {
|
|
16586
17983
|
source: "local",
|
|
@@ -16593,7 +17990,7 @@ async function readSkillMetadata(input) {
|
|
|
16593
17990
|
enabled: !input.disabled.has(name),
|
|
16594
17991
|
source: provenance.source,
|
|
16595
17992
|
trust: provenance.trust,
|
|
16596
|
-
relativePath:
|
|
17993
|
+
relativePath: path20.relative(input.skillsRoot, skillDir)
|
|
16597
17994
|
};
|
|
16598
17995
|
}
|
|
16599
17996
|
function parseSkillDocument(raw) {
|
|
@@ -16606,7 +18003,7 @@ function parseSkillDocument(raw) {
|
|
|
16606
18003
|
}
|
|
16607
18004
|
try {
|
|
16608
18005
|
return {
|
|
16609
|
-
frontmatter:
|
|
18006
|
+
frontmatter: toRecord15(YAML5.parse(match[1] ?? "")),
|
|
16610
18007
|
body: content.slice(match[0].length)
|
|
16611
18008
|
};
|
|
16612
18009
|
} catch {
|
|
@@ -16614,8 +18011,8 @@ function parseSkillDocument(raw) {
|
|
|
16614
18011
|
}
|
|
16615
18012
|
}
|
|
16616
18013
|
function categoryFromPath(skillsRoot, skillFile) {
|
|
16617
|
-
const relative =
|
|
16618
|
-
const parts = relative.split(
|
|
18014
|
+
const relative = path20.relative(skillsRoot, skillFile);
|
|
18015
|
+
const parts = relative.split(path20.sep).filter(Boolean);
|
|
16619
18016
|
return parts.length >= 3 ? parts[0] : null;
|
|
16620
18017
|
}
|
|
16621
18018
|
function firstBodyDescription(body) {
|
|
@@ -16638,8 +18035,8 @@ function normalizeDescription(value) {
|
|
|
16638
18035
|
return `${description.slice(0, MAX_DESCRIPTION_LENGTH - 3)}...`;
|
|
16639
18036
|
}
|
|
16640
18037
|
async function readDisabledSkillNames(configPath) {
|
|
16641
|
-
const raw = await
|
|
16642
|
-
if (
|
|
18038
|
+
const raw = await readFile15(configPath, "utf8").catch((error) => {
|
|
18039
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
16643
18040
|
return "";
|
|
16644
18041
|
}
|
|
16645
18042
|
throw error;
|
|
@@ -16647,8 +18044,8 @@ async function readDisabledSkillNames(configPath) {
|
|
|
16647
18044
|
if (!raw.trim()) {
|
|
16648
18045
|
return /* @__PURE__ */ new Set();
|
|
16649
18046
|
}
|
|
16650
|
-
const config =
|
|
16651
|
-
const skills =
|
|
18047
|
+
const config = toRecord15(YAML5.parse(raw));
|
|
18048
|
+
const skills = toRecord15(config.skills);
|
|
16652
18049
|
return new Set(readStringList3(skills.disabled));
|
|
16653
18050
|
}
|
|
16654
18051
|
async function readSkillProvenance(root) {
|
|
@@ -16662,9 +18059,9 @@ async function readSkillProvenance(root) {
|
|
|
16662
18059
|
return provenance;
|
|
16663
18060
|
}
|
|
16664
18061
|
async function readBundledSkillNames(root) {
|
|
16665
|
-
const raw = await
|
|
18062
|
+
const raw = await readFile15(path20.join(root, ".bundled_manifest"), "utf8").catch(
|
|
16666
18063
|
(error) => {
|
|
16667
|
-
if (
|
|
18064
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
16668
18065
|
return "";
|
|
16669
18066
|
}
|
|
16670
18067
|
throw error;
|
|
@@ -16685,9 +18082,9 @@ async function readBundledSkillNames(root) {
|
|
|
16685
18082
|
return names;
|
|
16686
18083
|
}
|
|
16687
18084
|
async function readHubInstalledSkills(root) {
|
|
16688
|
-
const raw = await
|
|
18085
|
+
const raw = await readFile15(path20.join(root, ".hub", "lock.json"), "utf8").catch(
|
|
16689
18086
|
(error) => {
|
|
16690
|
-
if (
|
|
18087
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
16691
18088
|
return "";
|
|
16692
18089
|
}
|
|
16693
18090
|
throw error;
|
|
@@ -16698,17 +18095,17 @@ async function readHubInstalledSkills(root) {
|
|
|
16698
18095
|
}
|
|
16699
18096
|
let lock;
|
|
16700
18097
|
try {
|
|
16701
|
-
lock =
|
|
18098
|
+
lock = toRecord15(JSON.parse(raw));
|
|
16702
18099
|
} catch {
|
|
16703
18100
|
return /* @__PURE__ */ new Map();
|
|
16704
18101
|
}
|
|
16705
|
-
const installed =
|
|
18102
|
+
const installed = toRecord15(lock.installed);
|
|
16706
18103
|
const result = /* @__PURE__ */ new Map();
|
|
16707
18104
|
for (const [name, rawEntry] of Object.entries(installed)) {
|
|
16708
|
-
const entry =
|
|
18105
|
+
const entry = toRecord15(rawEntry);
|
|
16709
18106
|
result.set(normalizeSkillName(name), {
|
|
16710
|
-
source:
|
|
16711
|
-
trust:
|
|
18107
|
+
source: readString16(entry.source) ?? "hub",
|
|
18108
|
+
trust: readString16(entry.trust_level) ?? null
|
|
16712
18109
|
});
|
|
16713
18110
|
}
|
|
16714
18111
|
return result;
|
|
@@ -16757,9 +18154,9 @@ function compareCategoryNames(left, right) {
|
|
|
16757
18154
|
return left.localeCompare(right);
|
|
16758
18155
|
}
|
|
16759
18156
|
async function readHermesConfigDocument2(configPath) {
|
|
16760
|
-
const existingRaw = await
|
|
18157
|
+
const existingRaw = await readFile15(configPath, "utf8").catch(
|
|
16761
18158
|
(error) => {
|
|
16762
|
-
if (
|
|
18159
|
+
if (isNodeError16(error, "ENOENT")) {
|
|
16763
18160
|
return null;
|
|
16764
18161
|
}
|
|
16765
18162
|
throw error;
|
|
@@ -16768,13 +18165,13 @@ async function readHermesConfigDocument2(configPath) {
|
|
|
16768
18165
|
const document = existingRaw ? YAML5.parseDocument(existingRaw) : new YAML5.Document({});
|
|
16769
18166
|
return {
|
|
16770
18167
|
document,
|
|
16771
|
-
config:
|
|
18168
|
+
config: toRecord15(document.toJSON()),
|
|
16772
18169
|
existingRaw
|
|
16773
18170
|
};
|
|
16774
18171
|
}
|
|
16775
18172
|
async function writeHermesConfigDocument2(input) {
|
|
16776
18173
|
const backupPath = input.existingRaw ? `${input.configPath}.bak.${Date.now()}` : null;
|
|
16777
|
-
await mkdir13(
|
|
18174
|
+
await mkdir13(path20.dirname(input.configPath), { recursive: true, mode: 448 });
|
|
16778
18175
|
if (backupPath) {
|
|
16779
18176
|
await copyFile4(input.configPath, backupPath);
|
|
16780
18177
|
}
|
|
@@ -16790,21 +18187,21 @@ function readStringList3(value) {
|
|
|
16790
18187
|
}
|
|
16791
18188
|
return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
16792
18189
|
}
|
|
16793
|
-
function
|
|
18190
|
+
function readString16(value) {
|
|
16794
18191
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
16795
18192
|
}
|
|
16796
|
-
function
|
|
18193
|
+
function toRecord15(value) {
|
|
16797
18194
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
16798
18195
|
}
|
|
16799
18196
|
function ensureRecord3(target, key) {
|
|
16800
|
-
const current =
|
|
18197
|
+
const current = toRecord15(target[key]);
|
|
16801
18198
|
if (current === target[key]) {
|
|
16802
18199
|
return current;
|
|
16803
18200
|
}
|
|
16804
18201
|
target[key] = current;
|
|
16805
18202
|
return current;
|
|
16806
18203
|
}
|
|
16807
|
-
function
|
|
18204
|
+
function isNodeError16(error, code) {
|
|
16808
18205
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
16809
18206
|
}
|
|
16810
18207
|
|
|
@@ -16819,7 +18216,7 @@ function registerProfileSkillRoutes(router, options) {
|
|
|
16819
18216
|
router.patch("/api/v1/profiles/:name/skills/:skillName", async (ctx) => {
|
|
16820
18217
|
await authenticateRequest(ctx, paths);
|
|
16821
18218
|
const body = await readJsonBody(ctx.req);
|
|
16822
|
-
const enabled =
|
|
18219
|
+
const enabled = readBoolean2(body.enabled);
|
|
16823
18220
|
if (enabled === void 0) {
|
|
16824
18221
|
throw new LinkHttpError(
|
|
16825
18222
|
400,
|
|
@@ -17107,7 +18504,7 @@ function registerRunRoutes(router, options) {
|
|
|
17107
18504
|
router.post("/api/v1/runs", async (ctx) => {
|
|
17108
18505
|
await authenticateRequest(ctx, paths);
|
|
17109
18506
|
const body = await readJsonBody(ctx.req);
|
|
17110
|
-
const input =
|
|
18507
|
+
const input = readString14(body, "input");
|
|
17111
18508
|
if (!input) {
|
|
17112
18509
|
throw new LinkHttpError(400, "run_input_required", "input is required");
|
|
17113
18510
|
}
|
|
@@ -17115,11 +18512,11 @@ function registerRunRoutes(router, options) {
|
|
|
17115
18512
|
ctx.body = await createHermesRun(
|
|
17116
18513
|
{
|
|
17117
18514
|
input,
|
|
17118
|
-
instructions:
|
|
18515
|
+
instructions: readString14(body, "instructions") ?? void 0,
|
|
17119
18516
|
conversation_history: readConversationHistory(
|
|
17120
18517
|
body.conversation_history ?? body.conversationHistory
|
|
17121
18518
|
),
|
|
17122
|
-
session_id:
|
|
18519
|
+
session_id: readString14(body, "session_id") ?? readString14(body, "sessionId") ?? void 0
|
|
17123
18520
|
},
|
|
17124
18521
|
{ logger, profileName: readOptionalProfileName(body) }
|
|
17125
18522
|
);
|
|
@@ -17290,8 +18687,8 @@ function readModelList(payload) {
|
|
|
17290
18687
|
// src/hermes/updates.ts
|
|
17291
18688
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
17292
18689
|
import { spawn as spawn3 } from "child_process";
|
|
17293
|
-
import { mkdir as mkdir14, readFile as
|
|
17294
|
-
import
|
|
18690
|
+
import { mkdir as mkdir14, readFile as readFile16, rm as rm8 } from "fs/promises";
|
|
18691
|
+
import path21 from "path";
|
|
17295
18692
|
var SERVER_HERMES_RELEASES_LATEST_PATH = "/api/v1/hermes-agent/releases/latest";
|
|
17296
18693
|
var RELEASE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
17297
18694
|
var RELEASE_FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -17512,24 +18909,24 @@ async function readRemoteRelease(options, now) {
|
|
|
17512
18909
|
}
|
|
17513
18910
|
}
|
|
17514
18911
|
function normalizeServerReleaseSnapshot(payload) {
|
|
17515
|
-
const snapshot =
|
|
18912
|
+
const snapshot = toRecord16(payload);
|
|
17516
18913
|
const remote = toNullableRecord(snapshot.remote);
|
|
17517
18914
|
return {
|
|
17518
18915
|
remote: remote ? normalizeServerRelease(remote) : null,
|
|
17519
|
-
cacheState:
|
|
17520
|
-
issue:
|
|
18916
|
+
cacheState: readString17(snapshot, "cache_state") ?? readString17(snapshot, "cacheState"),
|
|
18917
|
+
issue: readString17(snapshot, "issue")
|
|
17521
18918
|
};
|
|
17522
18919
|
}
|
|
17523
18920
|
function normalizeServerRelease(payload) {
|
|
17524
|
-
const tag =
|
|
17525
|
-
const name =
|
|
18921
|
+
const tag = readString17(payload, "tag");
|
|
18922
|
+
const name = readString17(payload, "name");
|
|
17526
18923
|
return {
|
|
17527
|
-
version:
|
|
18924
|
+
version: readString17(payload, "version") ?? extractSemver(name) ?? extractTagSemver(tag),
|
|
17528
18925
|
tag,
|
|
17529
18926
|
name,
|
|
17530
|
-
releaseUrl:
|
|
17531
|
-
publishedAt:
|
|
17532
|
-
fetchedAt:
|
|
18927
|
+
releaseUrl: readString17(payload, "releaseUrl") ?? readString17(payload, "release_url"),
|
|
18928
|
+
publishedAt: readString17(payload, "publishedAt") ?? readString17(payload, "published_at"),
|
|
18929
|
+
fetchedAt: readString17(payload, "fetchedAt") ?? readString17(payload, "fetched_at") ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
17533
18930
|
};
|
|
17534
18931
|
}
|
|
17535
18932
|
async function readReleaseCache(paths) {
|
|
@@ -17548,7 +18945,7 @@ async function writeUpdateState(paths, state) {
|
|
|
17548
18945
|
await writeJsonFile(updateStatePath(paths), state);
|
|
17549
18946
|
}
|
|
17550
18947
|
async function readUpdateLogLines(paths) {
|
|
17551
|
-
const raw = await
|
|
18948
|
+
const raw = await readFile16(updateLogPath(paths), "utf8").catch(() => "");
|
|
17552
18949
|
if (!raw.trim()) {
|
|
17553
18950
|
return [];
|
|
17554
18951
|
}
|
|
@@ -17557,13 +18954,13 @@ async function readUpdateLogLines(paths) {
|
|
|
17557
18954
|
);
|
|
17558
18955
|
}
|
|
17559
18956
|
function releaseCachePath(paths) {
|
|
17560
|
-
return
|
|
18957
|
+
return path21.join(paths.indexesDir, "hermes-release-check.json");
|
|
17561
18958
|
}
|
|
17562
18959
|
function updateStatePath(paths) {
|
|
17563
|
-
return
|
|
18960
|
+
return path21.join(paths.runDir, "hermes-update-state.json");
|
|
17564
18961
|
}
|
|
17565
18962
|
function updateLogPath(paths) {
|
|
17566
|
-
return
|
|
18963
|
+
return path21.join(paths.logsDir, UPDATE_LOG_FILE);
|
|
17567
18964
|
}
|
|
17568
18965
|
async function clearUpdateLogFiles(paths) {
|
|
17569
18966
|
const primary = updateLogPath(paths);
|
|
@@ -17601,7 +18998,7 @@ function compareSemver2(left, right) {
|
|
|
17601
18998
|
}
|
|
17602
18999
|
return 0;
|
|
17603
19000
|
}
|
|
17604
|
-
function
|
|
19001
|
+
function toRecord16(value) {
|
|
17605
19002
|
return typeof value === "object" && value !== null ? value : {};
|
|
17606
19003
|
}
|
|
17607
19004
|
function toNullableRecord(value) {
|
|
@@ -17647,7 +19044,7 @@ function isRecentRunningState2(state) {
|
|
|
17647
19044
|
const startedAt = Date.parse(state.started_at);
|
|
17648
19045
|
return Number.isFinite(startedAt) && Date.now() - startedAt < 3e4;
|
|
17649
19046
|
}
|
|
17650
|
-
function
|
|
19047
|
+
function readString17(payload, key) {
|
|
17651
19048
|
const value = payload[key];
|
|
17652
19049
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
17653
19050
|
}
|
|
@@ -17655,13 +19052,13 @@ function readString15(payload, key) {
|
|
|
17655
19052
|
// src/link/updates.ts
|
|
17656
19053
|
import { spawn as spawn5 } from "child_process";
|
|
17657
19054
|
import { EventEmitter as EventEmitter4 } from "events";
|
|
17658
|
-
import { mkdir as mkdir17, readFile as
|
|
17659
|
-
import
|
|
19055
|
+
import { mkdir as mkdir17, readFile as readFile18, rm as rm11 } from "fs/promises";
|
|
19056
|
+
import path23 from "path";
|
|
17660
19057
|
|
|
17661
19058
|
// src/daemon/process.ts
|
|
17662
19059
|
import { spawn as spawn4 } from "child_process";
|
|
17663
|
-
import { mkdir as mkdir16, readFile as
|
|
17664
|
-
import
|
|
19060
|
+
import { mkdir as mkdir16, readFile as readFile17, rm as rm10 } from "fs/promises";
|
|
19061
|
+
import path22 from "path";
|
|
17665
19062
|
|
|
17666
19063
|
// src/daemon/service.ts
|
|
17667
19064
|
import { createServer } from "http";
|
|
@@ -17839,7 +19236,7 @@ async function handleFrame(socket, raw, localPort, abortControllers) {
|
|
|
17839
19236
|
// src/runtime/system-info.ts
|
|
17840
19237
|
import { execFileSync } from "child_process";
|
|
17841
19238
|
import { readFileSync } from "fs";
|
|
17842
|
-
import
|
|
19239
|
+
import os6 from "os";
|
|
17843
19240
|
function readLinkSystemInfo() {
|
|
17844
19241
|
const platform = process.platform;
|
|
17845
19242
|
const hostname = readHostname(platform);
|
|
@@ -17878,7 +19275,7 @@ function readHostname(platform) {
|
|
|
17878
19275
|
return computerName;
|
|
17879
19276
|
}
|
|
17880
19277
|
}
|
|
17881
|
-
return normalizeText(
|
|
19278
|
+
return normalizeText(os6.hostname());
|
|
17882
19279
|
}
|
|
17883
19280
|
function readOsLabel(platform) {
|
|
17884
19281
|
if (platform === "darwin") {
|
|
@@ -17886,12 +19283,12 @@ function readOsLabel(platform) {
|
|
|
17886
19283
|
return version ? `macOS ${version}` : "macOS";
|
|
17887
19284
|
}
|
|
17888
19285
|
if (platform === "linux") {
|
|
17889
|
-
return readLinuxOsRelease() ?? `Linux ${
|
|
19286
|
+
return readLinuxOsRelease() ?? `Linux ${os6.release()}`;
|
|
17890
19287
|
}
|
|
17891
19288
|
if (platform === "win32") {
|
|
17892
|
-
return `Windows ${
|
|
19289
|
+
return `Windows ${os6.release()}`;
|
|
17893
19290
|
}
|
|
17894
|
-
return `${
|
|
19291
|
+
return `${os6.type()} ${os6.release()}`.trim();
|
|
17895
19292
|
}
|
|
17896
19293
|
function readLinuxOsRelease() {
|
|
17897
19294
|
for (const file of ["/etc/os-release", "/usr/lib/os-release"]) {
|
|
@@ -17938,11 +19335,11 @@ function truncateText(value, maxLength) {
|
|
|
17938
19335
|
}
|
|
17939
19336
|
|
|
17940
19337
|
// src/topology/network.ts
|
|
17941
|
-
import
|
|
19338
|
+
import os8 from "os";
|
|
17942
19339
|
|
|
17943
19340
|
// src/topology/environment.ts
|
|
17944
19341
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
17945
|
-
import
|
|
19342
|
+
import os7 from "os";
|
|
17946
19343
|
function detectRuntimeEnvironment(env = process.env) {
|
|
17947
19344
|
if (isWsl(env)) {
|
|
17948
19345
|
return {
|
|
@@ -17971,7 +19368,7 @@ function isWsl(env) {
|
|
|
17971
19368
|
if (env.WSL_DISTRO_NAME || env.WSL_INTEROP) {
|
|
17972
19369
|
return true;
|
|
17973
19370
|
}
|
|
17974
|
-
const release =
|
|
19371
|
+
const release = os7.release().toLowerCase();
|
|
17975
19372
|
return release.includes("microsoft") || release.includes("wsl");
|
|
17976
19373
|
}
|
|
17977
19374
|
function isContainer(env) {
|
|
@@ -18016,7 +19413,7 @@ async function discoverRouteCandidates(options) {
|
|
|
18016
19413
|
};
|
|
18017
19414
|
}
|
|
18018
19415
|
function discoverLanIps() {
|
|
18019
|
-
return discoverLanIpsFromInterfaces(
|
|
19416
|
+
return discoverLanIpsFromInterfaces(os8.networkInterfaces());
|
|
18020
19417
|
}
|
|
18021
19418
|
function discoverLanIpsFromInterfaces(interfaces) {
|
|
18022
19419
|
const result = /* @__PURE__ */ new Set();
|
|
@@ -18509,6 +19906,33 @@ function startCronDeliveryScheduler(options) {
|
|
|
18509
19906
|
}
|
|
18510
19907
|
};
|
|
18511
19908
|
}
|
|
19909
|
+
function startHermesSessionSyncScheduler(options) {
|
|
19910
|
+
let running = false;
|
|
19911
|
+
const syncSessions = async () => {
|
|
19912
|
+
if (running) {
|
|
19913
|
+
return;
|
|
19914
|
+
}
|
|
19915
|
+
running = true;
|
|
19916
|
+
try {
|
|
19917
|
+
await options.conversations.syncHermesSessions();
|
|
19918
|
+
} catch (error) {
|
|
19919
|
+
void options.logger.warn("hermes_session_sync_failed", {
|
|
19920
|
+
error: error instanceof Error ? error.message : String(error)
|
|
19921
|
+
});
|
|
19922
|
+
} finally {
|
|
19923
|
+
running = false;
|
|
19924
|
+
}
|
|
19925
|
+
};
|
|
19926
|
+
const timer = setInterval(() => {
|
|
19927
|
+
void syncSessions();
|
|
19928
|
+
}, options.intervalMs ?? 10 * 60 * 1e3);
|
|
19929
|
+
timer.unref?.();
|
|
19930
|
+
return {
|
|
19931
|
+
close() {
|
|
19932
|
+
clearInterval(timer);
|
|
19933
|
+
}
|
|
19934
|
+
};
|
|
19935
|
+
}
|
|
18512
19936
|
|
|
18513
19937
|
// src/daemon/service.ts
|
|
18514
19938
|
async function startLinkService(options = {}) {
|
|
@@ -18529,11 +19953,21 @@ async function startLinkService(options = {}) {
|
|
|
18529
19953
|
}
|
|
18530
19954
|
const conversations = new ConversationService(paths, logger);
|
|
18531
19955
|
await conversations.rebuildStatisticsIndex();
|
|
19956
|
+
const triggerHermesSessionSync = () => {
|
|
19957
|
+
void conversations.syncHermesSessions().catch((error) => {
|
|
19958
|
+
void logger.warn("hermes_session_sync_failed", {
|
|
19959
|
+
error: error instanceof Error ? error.message : String(error)
|
|
19960
|
+
});
|
|
19961
|
+
});
|
|
19962
|
+
};
|
|
18532
19963
|
const app = await createApp({
|
|
18533
19964
|
paths,
|
|
18534
19965
|
logger,
|
|
18535
19966
|
conversations,
|
|
18536
|
-
onPairingClaimed:
|
|
19967
|
+
onPairingClaimed: async () => {
|
|
19968
|
+
triggerHermesSessionSync();
|
|
19969
|
+
await options.onPairingClaimed?.();
|
|
19970
|
+
}
|
|
18537
19971
|
});
|
|
18538
19972
|
const server = createServer(app.callback());
|
|
18539
19973
|
try {
|
|
@@ -18553,11 +19987,16 @@ async function startLinkService(options = {}) {
|
|
|
18553
19987
|
port: config.port,
|
|
18554
19988
|
link_id: identity?.link_id ?? null
|
|
18555
19989
|
});
|
|
19990
|
+
triggerHermesSessionSync();
|
|
18556
19991
|
const scheduler = startCronDeliveryScheduler({
|
|
18557
19992
|
paths,
|
|
18558
19993
|
conversations,
|
|
18559
19994
|
logger
|
|
18560
19995
|
});
|
|
19996
|
+
const hermesSessionSyncScheduler = startHermesSessionSyncScheduler({
|
|
19997
|
+
conversations,
|
|
19998
|
+
logger
|
|
19999
|
+
});
|
|
18561
20000
|
let relay = null;
|
|
18562
20001
|
if (identity?.link_id) {
|
|
18563
20002
|
relay = connectRelayControl({
|
|
@@ -18590,6 +20029,7 @@ async function startLinkService(options = {}) {
|
|
|
18590
20029
|
return {
|
|
18591
20030
|
async close() {
|
|
18592
20031
|
scheduler.close();
|
|
20032
|
+
hermesSessionSyncScheduler.close();
|
|
18593
20033
|
lanIpMonitor.close();
|
|
18594
20034
|
relay?.close();
|
|
18595
20035
|
await closeServer(server);
|
|
@@ -18702,7 +20142,7 @@ async function runDaemonSupervisor(paths = resolveRuntimePaths()) {
|
|
|
18702
20142
|
await mkdir16(paths.logsDir, { recursive: true, mode: 448 });
|
|
18703
20143
|
const log = createRotatingTextLogWriter({
|
|
18704
20144
|
paths,
|
|
18705
|
-
fileName:
|
|
20145
|
+
fileName: path22.basename(daemonLogFile(paths))
|
|
18706
20146
|
});
|
|
18707
20147
|
const scriptPath = currentCliScriptPath();
|
|
18708
20148
|
const child = spawn4(process.execPath, [scriptPath, "daemon", "--foreground"], {
|
|
@@ -18831,7 +20271,7 @@ function currentCliScriptPath() {
|
|
|
18831
20271
|
return process.argv[1];
|
|
18832
20272
|
}
|
|
18833
20273
|
async function readPid(filePath) {
|
|
18834
|
-
const raw = await
|
|
20274
|
+
const raw = await readFile17(filePath, "utf8").catch(() => null);
|
|
18835
20275
|
if (!raw) {
|
|
18836
20276
|
return null;
|
|
18837
20277
|
}
|
|
@@ -19157,21 +20597,21 @@ async function readRemoteLinkPolicy(options) {
|
|
|
19157
20597
|
}
|
|
19158
20598
|
}
|
|
19159
20599
|
function normalizeServerSnapshot(payload) {
|
|
19160
|
-
const snapshot =
|
|
20600
|
+
const snapshot = toRecord17(payload);
|
|
19161
20601
|
const policy = toNullableRecord2(snapshot.policy);
|
|
19162
20602
|
if (!policy) {
|
|
19163
20603
|
return {
|
|
19164
20604
|
remote: null,
|
|
19165
|
-
issue:
|
|
20605
|
+
issue: readString18(snapshot, "issue")
|
|
19166
20606
|
};
|
|
19167
20607
|
}
|
|
19168
20608
|
const release = toNullableRecord2(snapshot.release);
|
|
19169
|
-
const currentVersion =
|
|
19170
|
-
const minSafeVersion =
|
|
20609
|
+
const currentVersion = readString18(policy, "current_version") ?? readString18(policy, "currentVersion");
|
|
20610
|
+
const minSafeVersion = readString18(policy, "min_safe_version") ?? readString18(policy, "minSafeVersion");
|
|
19171
20611
|
if (!currentVersion) {
|
|
19172
20612
|
return {
|
|
19173
20613
|
remote: null,
|
|
19174
|
-
issue:
|
|
20614
|
+
issue: readString18(snapshot, "issue")
|
|
19175
20615
|
};
|
|
19176
20616
|
}
|
|
19177
20617
|
return {
|
|
@@ -19179,10 +20619,10 @@ function normalizeServerSnapshot(payload) {
|
|
|
19179
20619
|
current_version: currentVersion,
|
|
19180
20620
|
min_safe_version: minSafeVersion,
|
|
19181
20621
|
target_version: currentVersion,
|
|
19182
|
-
release_url: release ?
|
|
19183
|
-
published_at: release ?
|
|
20622
|
+
release_url: release ? readString18(release, "release_url") ?? readString18(release, "releaseUrl") : null,
|
|
20623
|
+
published_at: release ? readString18(release, "published_at") ?? readString18(release, "publishedAt") : null
|
|
19184
20624
|
},
|
|
19185
|
-
issue:
|
|
20625
|
+
issue: readString18(snapshot, "issue")
|
|
19186
20626
|
};
|
|
19187
20627
|
}
|
|
19188
20628
|
async function fetchCurrentLinkReleaseFromServer(options, fetcher) {
|
|
@@ -19232,7 +20672,7 @@ async function writeUpdateState2(paths, state) {
|
|
|
19232
20672
|
await writeJsonFile(updateStatePath2(paths), state);
|
|
19233
20673
|
}
|
|
19234
20674
|
async function readUpdateLogLines2(paths) {
|
|
19235
|
-
const raw = await
|
|
20675
|
+
const raw = await readFile18(updateLogPath2(paths), "utf8").catch(() => "");
|
|
19236
20676
|
if (!raw.trim()) {
|
|
19237
20677
|
return [];
|
|
19238
20678
|
}
|
|
@@ -19241,10 +20681,10 @@ async function readUpdateLogLines2(paths) {
|
|
|
19241
20681
|
);
|
|
19242
20682
|
}
|
|
19243
20683
|
function updateStatePath2(paths) {
|
|
19244
|
-
return
|
|
20684
|
+
return path23.join(paths.runDir, "link-update-state.json");
|
|
19245
20685
|
}
|
|
19246
20686
|
function updateLogPath2(paths) {
|
|
19247
|
-
return
|
|
20687
|
+
return path23.join(paths.logsDir, UPDATE_LOG_FILE2);
|
|
19248
20688
|
}
|
|
19249
20689
|
async function clearUpdateLogFiles2(paths) {
|
|
19250
20690
|
const primary = updateLogPath2(paths);
|
|
@@ -19296,19 +20736,19 @@ function isProcessAlive4(pid) {
|
|
|
19296
20736
|
return false;
|
|
19297
20737
|
}
|
|
19298
20738
|
}
|
|
19299
|
-
function
|
|
20739
|
+
function toRecord17(value) {
|
|
19300
20740
|
return typeof value === "object" && value !== null ? value : {};
|
|
19301
20741
|
}
|
|
19302
20742
|
function toNullableRecord2(value) {
|
|
19303
20743
|
return typeof value === "object" && value !== null ? value : null;
|
|
19304
20744
|
}
|
|
19305
|
-
function
|
|
20745
|
+
function readString18(payload, key) {
|
|
19306
20746
|
const value = payload[key];
|
|
19307
20747
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
19308
20748
|
}
|
|
19309
20749
|
|
|
19310
20750
|
// src/pairing/pairing.ts
|
|
19311
|
-
import
|
|
20751
|
+
import path24 from "path";
|
|
19312
20752
|
import { rm as rm12 } from "fs/promises";
|
|
19313
20753
|
|
|
19314
20754
|
// src/relay/bootstrap.ts
|
|
@@ -19584,8 +21024,8 @@ async function loadRequiredIdentity2(paths) {
|
|
|
19584
21024
|
}
|
|
19585
21025
|
return identity;
|
|
19586
21026
|
}
|
|
19587
|
-
async function postServerJson(serverBaseUrl,
|
|
19588
|
-
const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${
|
|
21027
|
+
async function postServerJson(serverBaseUrl, path25, body) {
|
|
21028
|
+
const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path25}`, {
|
|
19589
21029
|
method: "POST",
|
|
19590
21030
|
headers: {
|
|
19591
21031
|
accept: "application/json",
|
|
@@ -19625,8 +21065,8 @@ function pairingErrorSnapshot(stage, error) {
|
|
|
19625
21065
|
occurred_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
19626
21066
|
};
|
|
19627
21067
|
}
|
|
19628
|
-
async function patchServerJson(serverBaseUrl,
|
|
19629
|
-
const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${
|
|
21068
|
+
async function patchServerJson(serverBaseUrl, path25, token, body) {
|
|
21069
|
+
const response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path25}`, {
|
|
19630
21070
|
method: "PATCH",
|
|
19631
21071
|
headers: {
|
|
19632
21072
|
accept: "application/json",
|
|
@@ -19657,10 +21097,10 @@ function readErrorMessage5(payload) {
|
|
|
19657
21097
|
return typeof message === "string" ? message : null;
|
|
19658
21098
|
}
|
|
19659
21099
|
function pairingClaimPath(sessionId, paths) {
|
|
19660
|
-
return
|
|
21100
|
+
return path24.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
|
|
19661
21101
|
}
|
|
19662
21102
|
function pairingSessionPath(sessionId, paths) {
|
|
19663
|
-
return
|
|
21103
|
+
return path24.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
|
|
19664
21104
|
}
|
|
19665
21105
|
function qrPreferredUrls(routes) {
|
|
19666
21106
|
return routes.preferredUrls.filter((url) => !url.includes("/api/v1/relay/links/")).slice(0, 1);
|
|
@@ -19731,8 +21171,8 @@ function registerSystemRoutes(router, options) {
|
|
|
19731
21171
|
});
|
|
19732
21172
|
router.post("/api/v1/pairing/claim", async (ctx) => {
|
|
19733
21173
|
const body = await readJsonBody(ctx.req);
|
|
19734
|
-
const sessionId =
|
|
19735
|
-
const claimToken =
|
|
21174
|
+
const sessionId = readString14(body, "session_id") ?? readString14(body, "sessionId");
|
|
21175
|
+
const claimToken = readString14(body, "claim_token") ?? readString14(body, "claimToken");
|
|
19736
21176
|
if (!sessionId || !claimToken) {
|
|
19737
21177
|
throw new LinkHttpError(
|
|
19738
21178
|
400,
|
|
@@ -19743,10 +21183,10 @@ function registerSystemRoutes(router, options) {
|
|
|
19743
21183
|
const claimed = await claimPairing({
|
|
19744
21184
|
sessionId,
|
|
19745
21185
|
claimToken,
|
|
19746
|
-
deviceLabel:
|
|
19747
|
-
devicePlatform:
|
|
19748
|
-
deviceModel:
|
|
19749
|
-
appInstanceId:
|
|
21186
|
+
deviceLabel: readString14(body, "device_label") ?? readString14(body, "deviceLabel") ?? "HermesPilot App",
|
|
21187
|
+
devicePlatform: readString14(body, "device_platform") ?? readString14(body, "devicePlatform") ?? "unknown",
|
|
21188
|
+
deviceModel: readString14(body, "device_model") ?? readString14(body, "deviceModel"),
|
|
21189
|
+
appInstanceId: readString14(body, "app_instance_id") ?? readString14(body, "appInstanceId"),
|
|
19750
21190
|
paths
|
|
19751
21191
|
});
|
|
19752
21192
|
ctx.body = claimed;
|
|
@@ -19821,9 +21261,9 @@ function registerSystemRoutes(router, options) {
|
|
|
19821
21261
|
const body = await readJsonBody(ctx.req);
|
|
19822
21262
|
const session = await createDeviceSession(
|
|
19823
21263
|
{
|
|
19824
|
-
label:
|
|
19825
|
-
platform:
|
|
19826
|
-
model:
|
|
21264
|
+
label: readString14(body, "device_label") ?? readString14(body, "deviceLabel") ?? "HermesPilot App",
|
|
21265
|
+
platform: readString14(body, "device_platform") ?? readString14(body, "devicePlatform") ?? "unknown",
|
|
21266
|
+
model: readString14(body, "device_model") ?? readString14(body, "deviceModel"),
|
|
19827
21267
|
appInstanceId: auth.appInstanceId
|
|
19828
21268
|
},
|
|
19829
21269
|
paths
|
|
@@ -19852,7 +21292,7 @@ function registerSystemRoutes(router, options) {
|
|
|
19852
21292
|
});
|
|
19853
21293
|
router.post("/api/v1/auth/refresh", async (ctx) => {
|
|
19854
21294
|
const body = await readJsonBody(ctx.req);
|
|
19855
|
-
const refreshToken =
|
|
21295
|
+
const refreshToken = readString14(body, "refresh_token") ?? readString14(body, "refreshToken");
|
|
19856
21296
|
if (!refreshToken) {
|
|
19857
21297
|
throw new LinkHttpError(
|
|
19858
21298
|
400,
|
|
@@ -19863,10 +21303,10 @@ function registerSystemRoutes(router, options) {
|
|
|
19863
21303
|
const session = await refreshDeviceSession(
|
|
19864
21304
|
refreshToken,
|
|
19865
21305
|
{
|
|
19866
|
-
appInstanceId:
|
|
19867
|
-
label:
|
|
19868
|
-
platform:
|
|
19869
|
-
model:
|
|
21306
|
+
appInstanceId: readString14(body, "app_instance_id") ?? readString14(body, "appInstanceId"),
|
|
21307
|
+
label: readString14(body, "device_label") ?? readString14(body, "deviceLabel"),
|
|
21308
|
+
platform: readString14(body, "device_platform") ?? readString14(body, "devicePlatform"),
|
|
21309
|
+
model: readString14(body, "device_model") ?? readString14(body, "deviceModel")
|
|
19870
21310
|
},
|
|
19871
21311
|
paths
|
|
19872
21312
|
);
|
|
@@ -19885,7 +21325,7 @@ function registerSystemRoutes(router, options) {
|
|
|
19885
21325
|
});
|
|
19886
21326
|
router.post("/api/v1/auth/logout", async (ctx) => {
|
|
19887
21327
|
const body = await readJsonBody(ctx.req);
|
|
19888
|
-
const refreshToken =
|
|
21328
|
+
const refreshToken = readString14(body, "refresh_token") ?? readString14(body, "refreshToken");
|
|
19889
21329
|
if (refreshToken) {
|
|
19890
21330
|
await revokeDeviceRefreshToken(refreshToken, paths);
|
|
19891
21331
|
}
|
|
@@ -20110,7 +21550,7 @@ function registerSystemRoutes(router, options) {
|
|
|
20110
21550
|
router.patch("/api/v1/devices/:deviceId", async (ctx) => {
|
|
20111
21551
|
const auth = await authenticateRequest(ctx, paths);
|
|
20112
21552
|
const body = await readJsonBody(ctx.req);
|
|
20113
|
-
const label =
|
|
21553
|
+
const label = readString14(body, "label") ?? readString14(body, "device_label");
|
|
20114
21554
|
if (!label) {
|
|
20115
21555
|
throw new LinkHttpError(
|
|
20116
21556
|
400,
|
|
@@ -20150,11 +21590,11 @@ async function readActiveCronJobCount(profiles, logger) {
|
|
|
20150
21590
|
}, 0);
|
|
20151
21591
|
}
|
|
20152
21592
|
function isActiveCronJob(job) {
|
|
20153
|
-
const enabled =
|
|
21593
|
+
const enabled = readBoolean2(job.enabled) ?? true;
|
|
20154
21594
|
if (!enabled) {
|
|
20155
21595
|
return false;
|
|
20156
21596
|
}
|
|
20157
|
-
const state =
|
|
21597
|
+
const state = readString14(job, "state")?.toLowerCase();
|
|
20158
21598
|
return !["paused", "disabled", "completed", "deleted"].includes(state ?? "");
|
|
20159
21599
|
}
|
|
20160
21600
|
function filterLogsWithinHours(logs, hours, now = Date.now()) {
|
|
@@ -20248,7 +21688,7 @@ function registerLinkUpdateRoutes(router, options) {
|
|
|
20248
21688
|
ctx.body = await startLinkUpdate({
|
|
20249
21689
|
paths,
|
|
20250
21690
|
logger,
|
|
20251
|
-
targetVersion:
|
|
21691
|
+
targetVersion: readString14(body, "target_version") ?? readString14(body, "targetVersion")
|
|
20252
21692
|
});
|
|
20253
21693
|
});
|
|
20254
21694
|
router.get("/api/v1/link/update/events", async (ctx) => {
|
|
@@ -20282,7 +21722,7 @@ import QRCode from "qrcode";
|
|
|
20282
21722
|
function registerPairingRoutes(router, options) {
|
|
20283
21723
|
const { paths } = options;
|
|
20284
21724
|
router.get("/pair", async (ctx) => {
|
|
20285
|
-
const sessionId =
|
|
21725
|
+
const sessionId = readString14(ctx.query, "session_id");
|
|
20286
21726
|
if (!sessionId) {
|
|
20287
21727
|
throw new LinkHttpError(400, "pairing_session_required", "session_id is required");
|
|
20288
21728
|
}
|
|
@@ -20307,7 +21747,7 @@ function registerPairingRoutes(router, options) {
|
|
|
20307
21747
|
ctx.body = page;
|
|
20308
21748
|
});
|
|
20309
21749
|
router.get("/api/v1/pairing/session", async (ctx) => {
|
|
20310
|
-
const sessionId =
|
|
21750
|
+
const sessionId = readString14(ctx.query, "session_id");
|
|
20311
21751
|
if (!sessionId) {
|
|
20312
21752
|
throw new LinkHttpError(400, "pairing_session_required", "session_id is required");
|
|
20313
21753
|
}
|
|
@@ -20706,6 +22146,37 @@ function formatDate(value) {
|
|
|
20706
22146
|
return Number.isNaN(date.getTime()) ? value : date.toLocaleString("zh-CN", { hour12: false });
|
|
20707
22147
|
}
|
|
20708
22148
|
|
|
22149
|
+
// src/http/routes/internal.ts
|
|
22150
|
+
function registerInternalRoutes(router, options) {
|
|
22151
|
+
router.post("/internal/deliver", async (ctx) => {
|
|
22152
|
+
assertLoopbackRequest(ctx.req);
|
|
22153
|
+
const body = await readJsonBody(ctx.req);
|
|
22154
|
+
const stagingDir = readString14(body, "staging_dir") ?? readString14(body, "stagingDir");
|
|
22155
|
+
if (!stagingDir) {
|
|
22156
|
+
throw new LinkHttpError(
|
|
22157
|
+
400,
|
|
22158
|
+
"delivery_staging_required",
|
|
22159
|
+
"delivery staging directory is required"
|
|
22160
|
+
);
|
|
22161
|
+
}
|
|
22162
|
+
ctx.body = {
|
|
22163
|
+
ok: true,
|
|
22164
|
+
...await options.conversations.deliverStagedFiles(stagingDir)
|
|
22165
|
+
};
|
|
22166
|
+
});
|
|
22167
|
+
}
|
|
22168
|
+
function assertLoopbackRequest(request) {
|
|
22169
|
+
const address = request.socket.remoteAddress;
|
|
22170
|
+
if (address === "127.0.0.1" || address === "::1" || address === "::ffff:127.0.0.1") {
|
|
22171
|
+
return;
|
|
22172
|
+
}
|
|
22173
|
+
throw new LinkHttpError(
|
|
22174
|
+
403,
|
|
22175
|
+
"internal_route_forbidden",
|
|
22176
|
+
"internal route is only available on loopback"
|
|
22177
|
+
);
|
|
22178
|
+
}
|
|
22179
|
+
|
|
20709
22180
|
// src/http/app.ts
|
|
20710
22181
|
async function createApp(options = {}) {
|
|
20711
22182
|
const paths = options.paths ?? resolveRuntimePaths();
|
|
@@ -20735,6 +22206,7 @@ async function createApp(options = {}) {
|
|
|
20735
22206
|
logger,
|
|
20736
22207
|
onPairingClaimed: options.onPairingClaimed
|
|
20737
22208
|
});
|
|
22209
|
+
registerInternalRoutes(router, { conversations });
|
|
20738
22210
|
registerPairingRoutes(router, { paths });
|
|
20739
22211
|
registerHermesUpdateRoutes(router, { paths, logger });
|
|
20740
22212
|
registerLinkUpdateRoutes(router, { paths, logger });
|
|
@@ -20764,11 +22236,13 @@ export {
|
|
|
20764
22236
|
ensureHermesApiServerConfig,
|
|
20765
22237
|
LinkHttpError,
|
|
20766
22238
|
resolveRuntimePaths,
|
|
22239
|
+
createFileLogger,
|
|
20767
22240
|
getLinkLogFile,
|
|
20768
22241
|
ensureHermesApiServerAvailable,
|
|
20769
22242
|
loadConfig,
|
|
20770
22243
|
saveConfig,
|
|
20771
22244
|
normalizeLanHost,
|
|
22245
|
+
ConversationService,
|
|
20772
22246
|
loadIdentity,
|
|
20773
22247
|
ensureIdentity,
|
|
20774
22248
|
getIdentityStatus,
|