@hermespilot/link 0.3.9 → 0.4.0
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-7OVDWXR7.js → chunk-FWPHQZP6.js} +900 -206
- package/dist/cli/index.js +60 -43
- package/dist/http/app.d.ts +17 -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 randomUUID8 } from "crypto";
|
|
8
8
|
|
|
9
9
|
// src/database/link-database.ts
|
|
10
10
|
import { mkdir } from "fs/promises";
|
|
@@ -3883,7 +3883,7 @@ async function listCronOutputFiles(profileName, jobId) {
|
|
|
3883
3883
|
mtimeMs: fileStat.mtimeMs
|
|
3884
3884
|
});
|
|
3885
3885
|
}
|
|
3886
|
-
return files.sort((left, right) => left.mtimeMs - right.mtimeMs).map(({ path:
|
|
3886
|
+
return files.sort((left, right) => left.mtimeMs - right.mtimeMs).map(({ path: path26, mtime }) => ({ path: path26, mtime }));
|
|
3887
3887
|
}
|
|
3888
3888
|
async function readCronOutput(outputPath) {
|
|
3889
3889
|
const content = await readFile3(outputPath, "utf8");
|
|
@@ -3970,7 +3970,7 @@ import os2 from "os";
|
|
|
3970
3970
|
import path5 from "path";
|
|
3971
3971
|
|
|
3972
3972
|
// src/constants.ts
|
|
3973
|
-
var LINK_VERSION = "0.
|
|
3973
|
+
var LINK_VERSION = "0.4.0";
|
|
3974
3974
|
var LINK_COMMAND = "hermeslink";
|
|
3975
3975
|
var LINK_DEFAULT_PORT = 52379;
|
|
3976
3976
|
var LINK_RUNTIME_DIR_NAME = ".hermeslink";
|
|
@@ -6760,6 +6760,70 @@ function safePathSegment(value, fallback) {
|
|
|
6760
6760
|
return safe.length > 0 ? safe.slice(0, 120) : fallback;
|
|
6761
6761
|
}
|
|
6762
6762
|
|
|
6763
|
+
// src/conversations/conversation-clear-plans.ts
|
|
6764
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
6765
|
+
import { mkdir as mkdir7 } from "fs/promises";
|
|
6766
|
+
import path11 from "path";
|
|
6767
|
+
var PLAN_ID_PATTERN = /^clear_[a-f0-9]{32}$/u;
|
|
6768
|
+
var ConversationClearPlanStore = class {
|
|
6769
|
+
constructor(paths) {
|
|
6770
|
+
this.paths = paths;
|
|
6771
|
+
}
|
|
6772
|
+
paths;
|
|
6773
|
+
async create(conversationIds) {
|
|
6774
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6775
|
+
const plan = {
|
|
6776
|
+
id: `clear_${randomUUID5().replaceAll("-", "")}`,
|
|
6777
|
+
status: "prepared",
|
|
6778
|
+
created_at: now,
|
|
6779
|
+
updated_at: now,
|
|
6780
|
+
total_count: conversationIds.length,
|
|
6781
|
+
deleted_count: 0,
|
|
6782
|
+
failed_count: 0,
|
|
6783
|
+
conversation_ids: conversationIds,
|
|
6784
|
+
conversations: []
|
|
6785
|
+
};
|
|
6786
|
+
await this.write(plan);
|
|
6787
|
+
return plan;
|
|
6788
|
+
}
|
|
6789
|
+
async read(planId) {
|
|
6790
|
+
const normalizedPlanId = normalizePlanId(planId);
|
|
6791
|
+
const plan = await readJsonFile(
|
|
6792
|
+
this.planPath(normalizedPlanId)
|
|
6793
|
+
);
|
|
6794
|
+
if (!plan) {
|
|
6795
|
+
throw new LinkHttpError(
|
|
6796
|
+
404,
|
|
6797
|
+
"conversation_clear_plan_not_found",
|
|
6798
|
+
"Conversation clear plan was not found"
|
|
6799
|
+
);
|
|
6800
|
+
}
|
|
6801
|
+
return plan;
|
|
6802
|
+
}
|
|
6803
|
+
async write(plan) {
|
|
6804
|
+
const normalizedPlanId = normalizePlanId(plan.id);
|
|
6805
|
+
await mkdir7(this.plansDir(), { recursive: true, mode: 448 });
|
|
6806
|
+
await writeJsonFile(this.planPath(normalizedPlanId), plan);
|
|
6807
|
+
}
|
|
6808
|
+
plansDir() {
|
|
6809
|
+
return path11.join(this.paths.indexesDir, "conversation-clear-plans");
|
|
6810
|
+
}
|
|
6811
|
+
planPath(planId) {
|
|
6812
|
+
return path11.join(this.plansDir(), `${planId}.json`);
|
|
6813
|
+
}
|
|
6814
|
+
};
|
|
6815
|
+
function normalizePlanId(planId) {
|
|
6816
|
+
const normalized = planId.trim();
|
|
6817
|
+
if (!PLAN_ID_PATTERN.test(normalized)) {
|
|
6818
|
+
throw new LinkHttpError(
|
|
6819
|
+
400,
|
|
6820
|
+
"conversation_clear_plan_id_invalid",
|
|
6821
|
+
"Conversation clear plan id is invalid"
|
|
6822
|
+
);
|
|
6823
|
+
}
|
|
6824
|
+
return normalized;
|
|
6825
|
+
}
|
|
6826
|
+
|
|
6763
6827
|
// src/conversations/conversation-session-ids.ts
|
|
6764
6828
|
function normalizeHermesSessionIds(values) {
|
|
6765
6829
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6810,8 +6874,134 @@ var MAX_UPLOADED_BLOB_BYTES = 50 * 1024 * 1024;
|
|
|
6810
6874
|
var ConversationMaintenanceCoordinator = class {
|
|
6811
6875
|
constructor(deps) {
|
|
6812
6876
|
this.deps = deps;
|
|
6877
|
+
this.clearPlans = new ConversationClearPlanStore(deps.paths);
|
|
6813
6878
|
}
|
|
6814
6879
|
deps;
|
|
6880
|
+
clearPlans;
|
|
6881
|
+
async prepareClearAllConversationPlan() {
|
|
6882
|
+
const targets = [];
|
|
6883
|
+
for (const conversationId of await this.deps.store.listConversationIds()) {
|
|
6884
|
+
const manifest = await this.deps.store.readManifest(conversationId).catch(() => null);
|
|
6885
|
+
if (manifest?.status !== "active") {
|
|
6886
|
+
continue;
|
|
6887
|
+
}
|
|
6888
|
+
targets.push({
|
|
6889
|
+
id: conversationId,
|
|
6890
|
+
updatedAt: manifest.updated_at
|
|
6891
|
+
});
|
|
6892
|
+
}
|
|
6893
|
+
targets.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
6894
|
+
return this.clearPlans.create(
|
|
6895
|
+
targets.map((target) => target.id)
|
|
6896
|
+
);
|
|
6897
|
+
}
|
|
6898
|
+
async readClearAllConversationPlan(planId) {
|
|
6899
|
+
return this.clearPlans.read(planId);
|
|
6900
|
+
}
|
|
6901
|
+
async executeClearAllConversationPlan(planId) {
|
|
6902
|
+
let plan = await this.clearPlans.read(planId);
|
|
6903
|
+
if (plan.status === "completed") {
|
|
6904
|
+
return plan;
|
|
6905
|
+
}
|
|
6906
|
+
if (plan.status === "failed") {
|
|
6907
|
+
throw new LinkHttpError(
|
|
6908
|
+
409,
|
|
6909
|
+
"conversation_clear_plan_already_failed",
|
|
6910
|
+
"Conversation clear plan has already failed"
|
|
6911
|
+
);
|
|
6912
|
+
}
|
|
6913
|
+
const results = [];
|
|
6914
|
+
if (plan.status !== "executing") {
|
|
6915
|
+
plan = await this.writeClearPlan({
|
|
6916
|
+
...plan,
|
|
6917
|
+
status: "executing",
|
|
6918
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6919
|
+
conversations: results,
|
|
6920
|
+
deleted_count: 0,
|
|
6921
|
+
failed_count: 0
|
|
6922
|
+
});
|
|
6923
|
+
}
|
|
6924
|
+
for (const conversationId of plan.conversation_ids) {
|
|
6925
|
+
try {
|
|
6926
|
+
const deleted = await this.deleteConversation(conversationId);
|
|
6927
|
+
results.push({ ...deleted, status: "deleted" });
|
|
6928
|
+
} catch (error) {
|
|
6929
|
+
if (error instanceof LinkHttpError && error.code === "conversation_not_found") {
|
|
6930
|
+
results.push({
|
|
6931
|
+
conversation_id: conversationId,
|
|
6932
|
+
status: "deleted",
|
|
6933
|
+
hermes_deleted: false,
|
|
6934
|
+
deleted_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6935
|
+
});
|
|
6936
|
+
} else {
|
|
6937
|
+
results.push({
|
|
6938
|
+
conversation_id: conversationId,
|
|
6939
|
+
status: "failed",
|
|
6940
|
+
error: {
|
|
6941
|
+
code: error instanceof LinkHttpError ? error.code : "internal_error",
|
|
6942
|
+
message: error instanceof Error ? error.message : "Internal error"
|
|
6943
|
+
}
|
|
6944
|
+
});
|
|
6945
|
+
}
|
|
6946
|
+
}
|
|
6947
|
+
plan = await this.writeClearPlan({
|
|
6948
|
+
...plan,
|
|
6949
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6950
|
+
conversations: [...results],
|
|
6951
|
+
deleted_count: results.filter((result) => result.status === "deleted").length,
|
|
6952
|
+
failed_count: results.filter((result) => result.status === "failed").length
|
|
6953
|
+
});
|
|
6954
|
+
}
|
|
6955
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6956
|
+
return this.writeClearPlan({
|
|
6957
|
+
...plan,
|
|
6958
|
+
status: plan.failed_count === 0 ? "completed" : "failed",
|
|
6959
|
+
updated_at: completedAt,
|
|
6960
|
+
completed_at: completedAt
|
|
6961
|
+
});
|
|
6962
|
+
}
|
|
6963
|
+
async startClearAllConversationPlan(planId) {
|
|
6964
|
+
const plan = await this.clearPlans.read(planId);
|
|
6965
|
+
if (plan.status === "completed" || plan.status === "executing") {
|
|
6966
|
+
return plan;
|
|
6967
|
+
}
|
|
6968
|
+
if (plan.status === "failed") {
|
|
6969
|
+
throw new LinkHttpError(
|
|
6970
|
+
409,
|
|
6971
|
+
"conversation_clear_plan_already_failed",
|
|
6972
|
+
"Conversation clear plan has already failed"
|
|
6973
|
+
);
|
|
6974
|
+
}
|
|
6975
|
+
const started = await this.writeClearPlan({
|
|
6976
|
+
...plan,
|
|
6977
|
+
status: "executing",
|
|
6978
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6979
|
+
conversations: [],
|
|
6980
|
+
deleted_count: 0,
|
|
6981
|
+
failed_count: 0
|
|
6982
|
+
});
|
|
6983
|
+
void this.executeClearAllConversationPlan(started.id).catch(
|
|
6984
|
+
async (error) => {
|
|
6985
|
+
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6986
|
+
await this.writeClearPlan({
|
|
6987
|
+
...started,
|
|
6988
|
+
status: "failed",
|
|
6989
|
+
updated_at: failedAt,
|
|
6990
|
+
completed_at: failedAt,
|
|
6991
|
+
failed_count: started.total_count,
|
|
6992
|
+
conversations: started.conversation_ids.map((conversationId) => ({
|
|
6993
|
+
conversation_id: conversationId,
|
|
6994
|
+
status: "failed",
|
|
6995
|
+
error: {
|
|
6996
|
+
code: error instanceof LinkHttpError ? error.code : "internal_error",
|
|
6997
|
+
message: error instanceof Error ? error.message : "Internal error"
|
|
6998
|
+
}
|
|
6999
|
+
}))
|
|
7000
|
+
}).catch(() => void 0);
|
|
7001
|
+
}
|
|
7002
|
+
);
|
|
7003
|
+
return started;
|
|
7004
|
+
}
|
|
6815
7005
|
async deleteConversation(conversationId) {
|
|
6816
7006
|
return this.deps.withConversationLock(
|
|
6817
7007
|
conversationId,
|
|
@@ -7017,6 +7207,10 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
7017
7207
|
async listConversationBlobIds(conversationId) {
|
|
7018
7208
|
return listConversationBlobIds(this.deps.paths, conversationId);
|
|
7019
7209
|
}
|
|
7210
|
+
async writeClearPlan(plan) {
|
|
7211
|
+
await this.clearPlans.write(plan);
|
|
7212
|
+
return plan;
|
|
7213
|
+
}
|
|
7020
7214
|
};
|
|
7021
7215
|
function isVoiceAttachmentInput(attachment) {
|
|
7022
7216
|
return attachment.kind === "voice" || attachment.type === "voice" || attachment.is_voice_note === true || attachment.isVoiceNote === true;
|
|
@@ -7096,14 +7290,14 @@ function isUsableLanIpv4(value) {
|
|
|
7096
7290
|
|
|
7097
7291
|
// src/hermes/session-title.ts
|
|
7098
7292
|
import { stat as stat7 } from "fs/promises";
|
|
7099
|
-
import
|
|
7293
|
+
import path12 from "path";
|
|
7100
7294
|
async function readHermesSessionTitle(sessionId, paths, profileName) {
|
|
7101
7295
|
const trimmedSessionId = sessionId.trim();
|
|
7102
7296
|
if (!trimmedSessionId) {
|
|
7103
7297
|
return void 0;
|
|
7104
7298
|
}
|
|
7105
7299
|
const resolvedProfileName = isValidProfileName(profileName) ? profileName : "default";
|
|
7106
|
-
const dbPath =
|
|
7300
|
+
const dbPath = path12.join(
|
|
7107
7301
|
resolveHermesProfileDir(resolvedProfileName),
|
|
7108
7302
|
"state.db"
|
|
7109
7303
|
);
|
|
@@ -7124,7 +7318,7 @@ async function readHermesCompressionTip(sessionId, paths, profileName) {
|
|
|
7124
7318
|
return void 0;
|
|
7125
7319
|
}
|
|
7126
7320
|
const resolvedProfileName = isValidProfileName(profileName) ? profileName : "default";
|
|
7127
|
-
const dbPath =
|
|
7321
|
+
const dbPath = path12.join(
|
|
7128
7322
|
resolveHermesProfileDir(resolvedProfileName),
|
|
7129
7323
|
"state.db"
|
|
7130
7324
|
);
|
|
@@ -7219,11 +7413,19 @@ var ConversationMetadataCoordinator = class {
|
|
|
7219
7413
|
const snapshot = options.snapshot ?? await this.deps.store.readSnapshot(manifest.id).catch(
|
|
7220
7414
|
() => createEmptySnapshot()
|
|
7221
7415
|
);
|
|
7222
|
-
const
|
|
7416
|
+
const profileName = latestRuntimeRun(snapshot)?.profile ?? manifest.profile_name_snapshot ?? manifest.profile;
|
|
7417
|
+
const compressionRootSessionId = manifest.hermes_lineage?.kind === "compression" ? manifest.hermes_lineage.root_session_id : void 0;
|
|
7418
|
+
const rootTitle = compressionRootSessionId ? await readHermesSessionTitle(
|
|
7419
|
+
compressionRootSessionId,
|
|
7420
|
+
this.deps.paths,
|
|
7421
|
+
profileName
|
|
7422
|
+
) : void 0;
|
|
7423
|
+
const tipTitle = await readHermesSessionTitle(
|
|
7223
7424
|
manifest.hermes_session_id,
|
|
7224
7425
|
this.deps.paths,
|
|
7225
|
-
|
|
7426
|
+
profileName
|
|
7226
7427
|
);
|
|
7428
|
+
const title = rootTitle ?? (compressionRootSessionId ? stripCompressionTitleSuffix(tipTitle) : tipTitle);
|
|
7227
7429
|
if (!title || title === manifest.title) {
|
|
7228
7430
|
return manifest;
|
|
7229
7431
|
}
|
|
@@ -7537,9 +7739,18 @@ function canAutoGenerateTitle(manifest) {
|
|
|
7537
7739
|
function normalizeConversationTitleSource(title) {
|
|
7538
7740
|
return isDefaultConversationTitle(title) ? "default" : "hermes";
|
|
7539
7741
|
}
|
|
7742
|
+
function stripCompressionTitleSuffix(value) {
|
|
7743
|
+
const normalized = value?.replace(/\s+/gu, " ").trim();
|
|
7744
|
+
if (!normalized) {
|
|
7745
|
+
return void 0;
|
|
7746
|
+
}
|
|
7747
|
+
const match = /^(.*?) #\d+$/u.exec(normalized);
|
|
7748
|
+
const stripped = match?.[1]?.trim();
|
|
7749
|
+
return stripped || normalized;
|
|
7750
|
+
}
|
|
7540
7751
|
|
|
7541
7752
|
// src/conversations/conversation-turns.ts
|
|
7542
|
-
import { randomUUID as
|
|
7753
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
7543
7754
|
var MESSAGE_ORDER_STEP_MS = 10;
|
|
7544
7755
|
var ASSISTANT_ORDER_OFFSET_MS = 1;
|
|
7545
7756
|
function createAgentTurnDraft(input) {
|
|
@@ -7773,10 +7984,10 @@ function createAssistantMessage(input) {
|
|
|
7773
7984
|
};
|
|
7774
7985
|
}
|
|
7775
7986
|
function createMessageId() {
|
|
7776
|
-
return `msg_${
|
|
7987
|
+
return `msg_${randomUUID6().replaceAll("-", "")}`;
|
|
7777
7988
|
}
|
|
7778
7989
|
function createRunId() {
|
|
7779
|
-
return `run_${
|
|
7990
|
+
return `run_${randomUUID6().replaceAll("-", "")}`;
|
|
7780
7991
|
}
|
|
7781
7992
|
function hasActiveOrQueuedRuns(snapshot) {
|
|
7782
7993
|
return snapshot.runs.some(
|
|
@@ -8891,20 +9102,20 @@ function hydrateAgentEventBlocks(blocks, agentEvents) {
|
|
|
8891
9102
|
// src/conversations/conversation-store.ts
|
|
8892
9103
|
import {
|
|
8893
9104
|
appendFile as appendFile2,
|
|
8894
|
-
mkdir as
|
|
9105
|
+
mkdir as mkdir8,
|
|
8895
9106
|
readdir as readdir5,
|
|
8896
9107
|
readFile as readFile7,
|
|
8897
9108
|
rm as rm5,
|
|
8898
9109
|
writeFile as writeFile2
|
|
8899
9110
|
} from "fs/promises";
|
|
8900
|
-
import
|
|
9111
|
+
import path13 from "path";
|
|
8901
9112
|
var ConversationStore = class {
|
|
8902
9113
|
constructor(paths) {
|
|
8903
9114
|
this.paths = paths;
|
|
8904
9115
|
}
|
|
8905
9116
|
paths;
|
|
8906
9117
|
async ensureConversationsDir() {
|
|
8907
|
-
await
|
|
9118
|
+
await mkdir8(this.paths.conversationsDir, { recursive: true, mode: 448 });
|
|
8908
9119
|
}
|
|
8909
9120
|
async listConversationIds() {
|
|
8910
9121
|
await this.ensureConversationsDir();
|
|
@@ -8919,7 +9130,7 @@ var ConversationStore = class {
|
|
|
8919
9130
|
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
8920
9131
|
}
|
|
8921
9132
|
async createConversation(manifest, snapshot = createEmptySnapshot2()) {
|
|
8922
|
-
await
|
|
9133
|
+
await mkdir8(this.conversationDir(manifest.id), {
|
|
8923
9134
|
recursive: true,
|
|
8924
9135
|
mode: 448
|
|
8925
9136
|
});
|
|
@@ -8959,7 +9170,7 @@ var ConversationStore = class {
|
|
|
8959
9170
|
conversation_id: conversationId,
|
|
8960
9171
|
created_at: now
|
|
8961
9172
|
};
|
|
8962
|
-
await
|
|
9173
|
+
await mkdir8(this.conversationDir(conversationId), {
|
|
8963
9174
|
recursive: true,
|
|
8964
9175
|
mode: 448
|
|
8965
9176
|
});
|
|
@@ -9013,23 +9224,23 @@ var ConversationStore = class {
|
|
|
9013
9224
|
return manifest?.status === "active";
|
|
9014
9225
|
}
|
|
9015
9226
|
removeConversationAttachments(conversationId) {
|
|
9016
|
-
return rm5(
|
|
9227
|
+
return rm5(path13.join(this.conversationDir(conversationId), "attachments"), {
|
|
9017
9228
|
recursive: true,
|
|
9018
9229
|
force: true
|
|
9019
9230
|
});
|
|
9020
9231
|
}
|
|
9021
9232
|
conversationDir(conversationId) {
|
|
9022
9233
|
assertValidConversationId(conversationId);
|
|
9023
|
-
return
|
|
9234
|
+
return path13.join(this.paths.conversationsDir, conversationId);
|
|
9024
9235
|
}
|
|
9025
9236
|
manifestPath(conversationId) {
|
|
9026
|
-
return
|
|
9237
|
+
return path13.join(this.conversationDir(conversationId), "manifest.json");
|
|
9027
9238
|
}
|
|
9028
9239
|
snapshotPath(conversationId) {
|
|
9029
|
-
return
|
|
9240
|
+
return path13.join(this.conversationDir(conversationId), "snapshot.json");
|
|
9030
9241
|
}
|
|
9031
9242
|
eventsPath(conversationId) {
|
|
9032
|
-
return
|
|
9243
|
+
return path13.join(this.conversationDir(conversationId), "events.ndjson");
|
|
9033
9244
|
}
|
|
9034
9245
|
};
|
|
9035
9246
|
function createEmptySnapshot2() {
|
|
@@ -9040,14 +9251,14 @@ function isNodeError9(error, code) {
|
|
|
9040
9251
|
}
|
|
9041
9252
|
|
|
9042
9253
|
// src/conversations/hermes-session-sync.ts
|
|
9043
|
-
import { randomUUID as
|
|
9254
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
9044
9255
|
import { readdir as readdir7, readFile as readFile9, stat as stat9 } from "fs/promises";
|
|
9045
9256
|
import os4 from "os";
|
|
9046
|
-
import
|
|
9257
|
+
import path15 from "path";
|
|
9047
9258
|
|
|
9048
9259
|
// src/conversations/delivery-import.ts
|
|
9049
9260
|
import { lstat as lstat2, readFile as readFile8, readdir as readdir6, stat as stat8 } from "fs/promises";
|
|
9050
|
-
import
|
|
9261
|
+
import path14 from "path";
|
|
9051
9262
|
var MAX_IMPORTED_BLOB_BYTES = 100 * 1024 * 1024;
|
|
9052
9263
|
var MAX_MEDIA_IMPORT_FAILURES = 20;
|
|
9053
9264
|
var MAX_DELIVERY_FILES = 50;
|
|
@@ -9118,16 +9329,16 @@ var SUPPORTED_DELIVERY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
9118
9329
|
".m4a"
|
|
9119
9330
|
]);
|
|
9120
9331
|
function resolveDeliveryStagingTarget(paths, stagingDir) {
|
|
9121
|
-
const resolvedDir =
|
|
9122
|
-
const relative =
|
|
9123
|
-
if (!relative || relative.startsWith("..") ||
|
|
9332
|
+
const resolvedDir = path14.resolve(stagingDir);
|
|
9333
|
+
const relative = path14.relative(path14.resolve(paths.conversationsDir), resolvedDir);
|
|
9334
|
+
if (!relative || relative.startsWith("..") || path14.isAbsolute(relative)) {
|
|
9124
9335
|
throw new LinkHttpError(
|
|
9125
9336
|
400,
|
|
9126
9337
|
"delivery_staging_invalid",
|
|
9127
9338
|
"delivery staging directory must be inside Hermes Link conversations"
|
|
9128
9339
|
);
|
|
9129
9340
|
}
|
|
9130
|
-
const segments = relative.split(
|
|
9341
|
+
const segments = relative.split(path14.sep);
|
|
9131
9342
|
if (segments.length !== 3 || segments[1] !== DELIVERY_STAGING_SEGMENT || !segments[0] || !segments[2]) {
|
|
9132
9343
|
throw new LinkHttpError(
|
|
9133
9344
|
400,
|
|
@@ -9163,7 +9374,7 @@ async function collectStagedDeliveryReferences(stagingDir) {
|
|
|
9163
9374
|
return entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).filter((entry) => isSupportedDeliveryFilename(entry.name)).sort(
|
|
9164
9375
|
(left, right) => left.name.localeCompare(right.name, "en", { numeric: true })
|
|
9165
9376
|
).slice(0, MAX_DELIVERY_FILES).map((entry) => {
|
|
9166
|
-
const sourcePath =
|
|
9377
|
+
const sourcePath = path14.join(stagingDir, entry.name);
|
|
9167
9378
|
const mime = inferMimeType(sourcePath);
|
|
9168
9379
|
return {
|
|
9169
9380
|
path: sourcePath,
|
|
@@ -9349,7 +9560,7 @@ async function writeBlobFromFile(deps, conversationId, source) {
|
|
|
9349
9560
|
}
|
|
9350
9561
|
return deps.writeBlob(conversationId, {
|
|
9351
9562
|
bytes: await readFile8(sourcePath),
|
|
9352
|
-
filename:
|
|
9563
|
+
filename: path14.basename(sourcePath),
|
|
9353
9564
|
mime: source.mime ?? inferMimeType(sourcePath)
|
|
9354
9565
|
});
|
|
9355
9566
|
}
|
|
@@ -9362,7 +9573,7 @@ function describeMediaImportFailure(reference, sourceKey, error) {
|
|
|
9362
9573
|
};
|
|
9363
9574
|
}
|
|
9364
9575
|
function isSupportedDeliveryFilename(filename) {
|
|
9365
|
-
return SUPPORTED_DELIVERY_EXTENSIONS.has(
|
|
9576
|
+
return SUPPORTED_DELIVERY_EXTENSIONS.has(path14.extname(filename).toLowerCase());
|
|
9366
9577
|
}
|
|
9367
9578
|
function readString8(payload, key) {
|
|
9368
9579
|
const value = payload[key];
|
|
@@ -9419,7 +9630,7 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
|
|
|
9419
9630
|
const candidates = [];
|
|
9420
9631
|
for (const profileName of profileNames) {
|
|
9421
9632
|
const profileDir = resolveHermesProfileDir(profileName);
|
|
9422
|
-
const dbPath =
|
|
9633
|
+
const dbPath = path15.join(profileDir, "state.db");
|
|
9423
9634
|
const sessions = await listProfileSessions(dbPath).catch((error) => {
|
|
9424
9635
|
result.errors.push({
|
|
9425
9636
|
profile: profileName,
|
|
@@ -9449,16 +9660,19 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
|
|
|
9449
9660
|
const importableCandidates = candidates.slice(0, maxImports);
|
|
9450
9661
|
result.skipped_over_limit = Math.max(0, candidates.length - maxImports);
|
|
9451
9662
|
for (const candidate of importableCandidates) {
|
|
9452
|
-
|
|
9663
|
+
const candidateSessionIds = lineageSessionIds(candidate);
|
|
9664
|
+
const knownConversationIds = findKnownConversationIdsForCandidate(
|
|
9665
|
+
knownHermesSessions,
|
|
9666
|
+
candidate
|
|
9667
|
+
);
|
|
9668
|
+
if (knownConversationIds.length > 0) {
|
|
9453
9669
|
result.skipped_existing += 1;
|
|
9454
|
-
const reprojected = await
|
|
9670
|
+
const reprojected = await mergeExistingHermesConversation({
|
|
9455
9671
|
paths,
|
|
9456
9672
|
store,
|
|
9457
9673
|
logger,
|
|
9458
9674
|
candidate,
|
|
9459
|
-
conversationIds:
|
|
9460
|
-
candidate.session.id
|
|
9461
|
-
) ?? []
|
|
9675
|
+
conversationIds: knownConversationIds
|
|
9462
9676
|
}).catch((error) => {
|
|
9463
9677
|
result.errors.push({
|
|
9464
9678
|
profile: candidate.profileName,
|
|
@@ -9469,14 +9683,22 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
|
|
|
9469
9683
|
if (reprojected) {
|
|
9470
9684
|
result.reprojected_count += 1;
|
|
9471
9685
|
}
|
|
9686
|
+
for (const sessionId of candidateSessionIds) {
|
|
9687
|
+
knownHermesSessions.sessionIds.add(sessionId);
|
|
9688
|
+
}
|
|
9689
|
+
continue;
|
|
9690
|
+
}
|
|
9691
|
+
if (candidateSessionIds.some(
|
|
9692
|
+
(sessionId) => knownHermesSessions.sessionIds.has(sessionId)
|
|
9693
|
+
)) {
|
|
9694
|
+
result.skipped_existing += 1;
|
|
9472
9695
|
continue;
|
|
9473
9696
|
}
|
|
9474
9697
|
const imported = await importHermesSession({
|
|
9475
9698
|
paths,
|
|
9476
9699
|
store,
|
|
9477
9700
|
logger,
|
|
9478
|
-
candidate
|
|
9479
|
-
existingHermesSessionIds: knownHermesSessions.ids
|
|
9701
|
+
candidate
|
|
9480
9702
|
}).catch((error) => {
|
|
9481
9703
|
result.errors.push({
|
|
9482
9704
|
profile: candidate.profileName,
|
|
@@ -9486,6 +9708,9 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
|
|
|
9486
9708
|
});
|
|
9487
9709
|
if (imported) {
|
|
9488
9710
|
result.imported_count += 1;
|
|
9711
|
+
for (const sessionId of candidateSessionIds) {
|
|
9712
|
+
knownHermesSessions.sessionIds.add(sessionId);
|
|
9713
|
+
}
|
|
9489
9714
|
}
|
|
9490
9715
|
}
|
|
9491
9716
|
if (result.imported_count > 0 || result.reprojected_count > 0 || result.errors.length > 0) {
|
|
@@ -9496,13 +9721,14 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
|
|
|
9496
9721
|
return result;
|
|
9497
9722
|
}
|
|
9498
9723
|
async function importHermesSession(input) {
|
|
9499
|
-
const { paths, store, logger, candidate
|
|
9724
|
+
const { paths, store, logger, candidate } = input;
|
|
9500
9725
|
const profile = await resolveConversationProfileTarget(
|
|
9501
9726
|
paths,
|
|
9502
9727
|
candidate.profileName
|
|
9503
9728
|
);
|
|
9504
9729
|
const sessionId = candidate.session.id;
|
|
9505
|
-
const
|
|
9730
|
+
const hermesSessionIds = lineageSessionIds(candidate);
|
|
9731
|
+
const messages = await readHermesLineageMessages(candidate);
|
|
9506
9732
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9507
9733
|
const createdAt = isoFromHermesTime(candidate.session.started_at) ?? now;
|
|
9508
9734
|
const updatedAt = isoFromHermesTime(candidate.session.last_active) ?? isoFromHermesTime(messages.at(-1)?.timestamp) ?? createdAt;
|
|
@@ -9519,7 +9745,14 @@ async function importHermesSession(input) {
|
|
|
9519
9745
|
}),
|
|
9520
9746
|
runs: []
|
|
9521
9747
|
};
|
|
9522
|
-
const title = readString9(candidate.session, "title") ?? firstUserText(snapshot);
|
|
9748
|
+
const title = lineageTitle(candidate) ?? readString9(candidate.session, "title") ?? firstUserText(snapshot);
|
|
9749
|
+
const importedStats = buildImportedHermesStats({
|
|
9750
|
+
candidate,
|
|
9751
|
+
snapshot,
|
|
9752
|
+
profileUid: profile.profileUid,
|
|
9753
|
+
profileName: profile.profileName,
|
|
9754
|
+
updatedAt
|
|
9755
|
+
});
|
|
9523
9756
|
const manifest = {
|
|
9524
9757
|
id: conversationId,
|
|
9525
9758
|
schema_version: 1,
|
|
@@ -9528,13 +9761,15 @@ async function importHermesSession(input) {
|
|
|
9528
9761
|
title_source: title ? "hermes" : "default",
|
|
9529
9762
|
status: "active",
|
|
9530
9763
|
hermes_session_id: sessionId,
|
|
9531
|
-
hermes_session_ids:
|
|
9764
|
+
hermes_session_ids: hermesSessionIds,
|
|
9765
|
+
...lineageManifestPatch(candidate),
|
|
9532
9766
|
profile_uid: profile.profileUid,
|
|
9533
9767
|
profile_name_snapshot: profile.profileName,
|
|
9534
9768
|
profile: profile.profileName,
|
|
9535
9769
|
created_at: createdAt,
|
|
9536
9770
|
updated_at: updatedAt,
|
|
9537
|
-
last_event_seq: 0
|
|
9771
|
+
last_event_seq: 0,
|
|
9772
|
+
stats: importedStats
|
|
9538
9773
|
};
|
|
9539
9774
|
await store.createConversation(manifest, snapshot);
|
|
9540
9775
|
await hydrateImportedConversationMedia({
|
|
@@ -9577,59 +9812,288 @@ async function importHermesSession(input) {
|
|
|
9577
9812
|
paths,
|
|
9578
9813
|
toStatsIndexRecord(await store.readManifest(conversationId), stats)
|
|
9579
9814
|
);
|
|
9580
|
-
existingHermesSessionIds.add(sessionId);
|
|
9581
9815
|
return true;
|
|
9582
9816
|
}
|
|
9583
|
-
async function
|
|
9817
|
+
async function mergeExistingHermesConversation(input) {
|
|
9818
|
+
const conversations = await readExistingHermesConversations(
|
|
9819
|
+
input.store,
|
|
9820
|
+
input.conversationIds
|
|
9821
|
+
);
|
|
9822
|
+
const active = conversations.filter((item) => item.manifest.status === "active");
|
|
9823
|
+
const canonical = selectCanonicalHermesConversation(active);
|
|
9824
|
+
if (!canonical) {
|
|
9825
|
+
return false;
|
|
9826
|
+
}
|
|
9584
9827
|
let changed = false;
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
|
|
9591
|
-
|
|
9828
|
+
const candidateMessages = await readHermesLineageMessages(input.candidate);
|
|
9829
|
+
const nextCanonical = await mergeHermesCandidateIntoConversation({
|
|
9830
|
+
...input,
|
|
9831
|
+
existing: canonical,
|
|
9832
|
+
candidateMessages
|
|
9833
|
+
});
|
|
9834
|
+
changed = nextCanonical || changed;
|
|
9835
|
+
for (const duplicate of active) {
|
|
9836
|
+
if (duplicate.conversationId === canonical.conversationId) {
|
|
9592
9837
|
continue;
|
|
9593
9838
|
}
|
|
9594
|
-
|
|
9595
|
-
if (!prefix?.needsUpgrade || prefix.messages.length === 0) {
|
|
9839
|
+
if (!isSafeHermesImportConversation(duplicate) || duplicate.manifest.status !== "active") {
|
|
9596
9840
|
continue;
|
|
9597
9841
|
}
|
|
9598
|
-
|
|
9599
|
-
input.paths,
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9842
|
+
await softDeleteMergedHermesDuplicate({
|
|
9843
|
+
paths: input.paths,
|
|
9844
|
+
store: input.store,
|
|
9845
|
+
duplicate,
|
|
9846
|
+
candidate: input.candidate
|
|
9847
|
+
});
|
|
9848
|
+
changed = true;
|
|
9849
|
+
}
|
|
9850
|
+
return changed;
|
|
9851
|
+
}
|
|
9852
|
+
async function mergeHermesCandidateIntoConversation(input) {
|
|
9853
|
+
const { candidate, existing } = input;
|
|
9854
|
+
const profile = await resolveConversationProfileTarget(
|
|
9855
|
+
input.paths,
|
|
9856
|
+
existing.manifest.profile_name_snapshot ?? existing.manifest.profile ?? candidate.profileName
|
|
9857
|
+
);
|
|
9858
|
+
let nextSnapshot = existing.snapshot;
|
|
9859
|
+
const pureImport = isSafeHermesImportConversation(existing);
|
|
9860
|
+
const prefix = collectImportedHermesPrefix(existing.snapshot);
|
|
9861
|
+
if (pureImport || prefix?.needsUpgrade && prefix.messages.length > 0) {
|
|
9862
|
+
const messages = pureImport ? input.candidateMessages : prefix.messages;
|
|
9863
|
+
nextSnapshot = {
|
|
9864
|
+
...existing.snapshot,
|
|
9604
9865
|
messages: [
|
|
9605
9866
|
...toLinkMessages({
|
|
9606
|
-
conversationId,
|
|
9867
|
+
conversationId: existing.conversationId,
|
|
9607
9868
|
profileName: profile.profileName,
|
|
9608
9869
|
profileUid: profile.profileUid,
|
|
9609
9870
|
profileDisplayName: profile.profileDisplayName,
|
|
9610
|
-
sessionId:
|
|
9611
|
-
messages
|
|
9871
|
+
sessionId: candidate.session.id,
|
|
9872
|
+
messages
|
|
9612
9873
|
}),
|
|
9613
|
-
...snapshot.messages.slice(prefix.endIndex)
|
|
9874
|
+
...pureImport ? [] : existing.snapshot.messages.slice(prefix.endIndex)
|
|
9614
9875
|
]
|
|
9615
9876
|
};
|
|
9616
|
-
await input.store.writeSnapshot(conversationId, nextSnapshot);
|
|
9877
|
+
await input.store.writeSnapshot(existing.conversationId, nextSnapshot);
|
|
9617
9878
|
await hydrateImportedConversationMedia({
|
|
9618
9879
|
paths: input.paths,
|
|
9619
9880
|
store: input.store,
|
|
9620
9881
|
logger: input.logger,
|
|
9621
|
-
conversationId
|
|
9882
|
+
conversationId: existing.conversationId
|
|
9622
9883
|
});
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
|
|
9629
|
-
|
|
9630
|
-
|
|
9884
|
+
nextSnapshot = await input.store.readSnapshot(existing.conversationId);
|
|
9885
|
+
}
|
|
9886
|
+
const updatedAt = isoFromHermesTime(candidate.session.last_active) ?? existing.manifest.updated_at;
|
|
9887
|
+
const nextManifest = mergeHermesLineageIntoManifest({
|
|
9888
|
+
manifest: existing.manifest,
|
|
9889
|
+
candidate,
|
|
9890
|
+
snapshot: nextSnapshot,
|
|
9891
|
+
profileUid: profile.profileUid,
|
|
9892
|
+
profileName: profile.profileName,
|
|
9893
|
+
updatedAt
|
|
9894
|
+
});
|
|
9895
|
+
if (manifestEquivalent(existing.manifest, nextManifest)) {
|
|
9896
|
+
return nextSnapshot !== existing.snapshot;
|
|
9631
9897
|
}
|
|
9632
|
-
|
|
9898
|
+
await input.store.writeManifest(nextManifest);
|
|
9899
|
+
await upsertConversationStats(
|
|
9900
|
+
input.paths,
|
|
9901
|
+
toStatsIndexRecord(nextManifest, nextManifest.stats)
|
|
9902
|
+
);
|
|
9903
|
+
return true;
|
|
9904
|
+
}
|
|
9905
|
+
function findKnownConversationIdsForCandidate(known, candidate) {
|
|
9906
|
+
const conversationIds = [];
|
|
9907
|
+
for (const sessionId of lineageSessionIds(candidate)) {
|
|
9908
|
+
for (const conversationId of known.conversationIdsBySessionId.get(sessionId) ?? []) {
|
|
9909
|
+
if (!conversationIds.includes(conversationId)) {
|
|
9910
|
+
conversationIds.push(conversationId);
|
|
9911
|
+
}
|
|
9912
|
+
}
|
|
9913
|
+
}
|
|
9914
|
+
return conversationIds;
|
|
9915
|
+
}
|
|
9916
|
+
function lineageSessionIds(candidate) {
|
|
9917
|
+
return normalizeSessionIds([
|
|
9918
|
+
candidate.session._lineage_root_id,
|
|
9919
|
+
...candidate.session._lineage_session_ids ?? [],
|
|
9920
|
+
candidate.session.id
|
|
9921
|
+
]);
|
|
9922
|
+
}
|
|
9923
|
+
function lineageTitle(candidate) {
|
|
9924
|
+
return candidate.session._lineage_title ?? stripCompressionTitleSuffix2(readString9(candidate.session, "title") ?? "");
|
|
9925
|
+
}
|
|
9926
|
+
function lineageManifestPatch(candidate) {
|
|
9927
|
+
const sessionIds = lineageSessionIds(candidate);
|
|
9928
|
+
const rootSessionId = candidate.session._lineage_root_id ?? sessionIds[0];
|
|
9929
|
+
const currentSessionId = candidate.session.id;
|
|
9930
|
+
if (!rootSessionId || sessionIds.length <= 1 || rootSessionId === currentSessionId) {
|
|
9931
|
+
return {};
|
|
9932
|
+
}
|
|
9933
|
+
return {
|
|
9934
|
+
hermes_lineage: {
|
|
9935
|
+
kind: "compression",
|
|
9936
|
+
root_session_id: rootSessionId,
|
|
9937
|
+
current_session_id: currentSessionId,
|
|
9938
|
+
session_ids: sessionIds
|
|
9939
|
+
}
|
|
9940
|
+
};
|
|
9941
|
+
}
|
|
9942
|
+
function buildImportedHermesStats(input) {
|
|
9943
|
+
const usage = input.candidate.session._lineage_usage;
|
|
9944
|
+
const base = input.baseStats;
|
|
9945
|
+
const usageInput = usage?.input_tokens ?? 0;
|
|
9946
|
+
const usageOutput = usage?.output_tokens ?? 0;
|
|
9947
|
+
const usageTotal = usage?.total_tokens ?? usageInput + usageOutput;
|
|
9948
|
+
const inputTokens = Math.max(base?.input_tokens ?? 0, usageInput);
|
|
9949
|
+
const outputTokens = Math.max(base?.output_tokens ?? 0, usageOutput);
|
|
9950
|
+
const totalTokens = Math.max(
|
|
9951
|
+
base?.total_tokens ?? 0,
|
|
9952
|
+
usageTotal,
|
|
9953
|
+
inputTokens + outputTokens
|
|
9954
|
+
);
|
|
9955
|
+
const updatedAt = base?.updated_at ? latestTimestamp(base.updated_at, input.updatedAt) : input.updatedAt;
|
|
9956
|
+
return {
|
|
9957
|
+
input_tokens: inputTokens,
|
|
9958
|
+
output_tokens: outputTokens,
|
|
9959
|
+
total_tokens: totalTokens,
|
|
9960
|
+
message_count: Math.max(
|
|
9961
|
+
base?.message_count ?? 0,
|
|
9962
|
+
input.snapshot.messages.length
|
|
9963
|
+
),
|
|
9964
|
+
run_count: Math.max(base?.run_count ?? 0, usage?.api_call_count ?? 0),
|
|
9965
|
+
profile_uid: base?.profile_uid ?? input.profileUid,
|
|
9966
|
+
profile_name_snapshot: base?.profile_name_snapshot ?? base?.profile ?? input.profileName,
|
|
9967
|
+
profile: base?.profile ?? base?.profile_name_snapshot ?? input.profileName,
|
|
9968
|
+
...base?.model ?? usage?.model ? { model: base?.model ?? usage?.model } : {},
|
|
9969
|
+
...base?.provider ?? usage?.provider ? { provider: base?.provider ?? usage?.provider } : {},
|
|
9970
|
+
...base?.context_window ? { context_window: base.context_window } : {},
|
|
9971
|
+
updated_at: updatedAt
|
|
9972
|
+
};
|
|
9973
|
+
}
|
|
9974
|
+
async function readExistingHermesConversations(store, conversationIds) {
|
|
9975
|
+
const conversations = [];
|
|
9976
|
+
for (const conversationId of [...new Set(conversationIds)]) {
|
|
9977
|
+
const manifest = await store.readManifest(conversationId).catch(() => null);
|
|
9978
|
+
if (!manifest) {
|
|
9979
|
+
continue;
|
|
9980
|
+
}
|
|
9981
|
+
const snapshot = await store.readSnapshot(conversationId).catch(() => ({
|
|
9982
|
+
schema_version: 1,
|
|
9983
|
+
messages: [],
|
|
9984
|
+
runs: []
|
|
9985
|
+
}));
|
|
9986
|
+
conversations.push({ conversationId, manifest, snapshot });
|
|
9987
|
+
}
|
|
9988
|
+
return conversations;
|
|
9989
|
+
}
|
|
9990
|
+
function selectCanonicalHermesConversation(conversations) {
|
|
9991
|
+
const ranked = [...conversations].sort((left, right) => {
|
|
9992
|
+
const rightScore = canonicalConversationScore(right);
|
|
9993
|
+
const leftScore = canonicalConversationScore(left);
|
|
9994
|
+
if (rightScore !== leftScore) {
|
|
9995
|
+
return rightScore - leftScore;
|
|
9996
|
+
}
|
|
9997
|
+
return Date.parse(left.manifest.created_at) - Date.parse(right.manifest.created_at) || left.conversationId.localeCompare(right.conversationId);
|
|
9998
|
+
});
|
|
9999
|
+
return ranked[0] ?? null;
|
|
10000
|
+
}
|
|
10001
|
+
function canonicalConversationScore(item) {
|
|
10002
|
+
let score = 0;
|
|
10003
|
+
if (!isSafeHermesImportConversation(item)) {
|
|
10004
|
+
score += 1e3;
|
|
10005
|
+
}
|
|
10006
|
+
if (item.snapshot.runs.length > 0) {
|
|
10007
|
+
score += 500;
|
|
10008
|
+
}
|
|
10009
|
+
if (item.manifest.title_source === "manual") {
|
|
10010
|
+
score += 200;
|
|
10011
|
+
}
|
|
10012
|
+
if (!hasCompressionTitleSuffix(item.manifest.title)) {
|
|
10013
|
+
score += 100;
|
|
10014
|
+
}
|
|
10015
|
+
if (item.manifest.hermes_lineage?.root_session_id && item.manifest.hermes_session_id === item.manifest.hermes_lineage.root_session_id) {
|
|
10016
|
+
score += 50;
|
|
10017
|
+
}
|
|
10018
|
+
return score;
|
|
10019
|
+
}
|
|
10020
|
+
function isSafeHermesImportConversation(item) {
|
|
10021
|
+
const snapshot = item.snapshot;
|
|
10022
|
+
if (snapshot.runs.length > 0) {
|
|
10023
|
+
return false;
|
|
10024
|
+
}
|
|
10025
|
+
if (item.manifest.title_source === "manual" || item.manifest.title_source === "generated") {
|
|
10026
|
+
return false;
|
|
10027
|
+
}
|
|
10028
|
+
return snapshot.messages.length === 0 || snapshot.messages.every((message) => isHermesImportedMessage(message));
|
|
10029
|
+
}
|
|
10030
|
+
async function softDeleteMergedHermesDuplicate(input) {
|
|
10031
|
+
const deletedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10032
|
+
const emptySnapshot3 = {
|
|
10033
|
+
schema_version: 1,
|
|
10034
|
+
messages: [],
|
|
10035
|
+
runs: []
|
|
10036
|
+
};
|
|
10037
|
+
const hermesSessionIds = normalizeSessionIds([
|
|
10038
|
+
input.duplicate.manifest.hermes_session_id,
|
|
10039
|
+
...input.duplicate.manifest.hermes_session_ids ?? [],
|
|
10040
|
+
...input.duplicate.manifest.hermes_lineage?.session_ids ?? [],
|
|
10041
|
+
...lineageSessionIds(input.candidate)
|
|
10042
|
+
]);
|
|
10043
|
+
const nextManifest = {
|
|
10044
|
+
...input.duplicate.manifest,
|
|
10045
|
+
status: "deleted_soft",
|
|
10046
|
+
hermes_session_ids: hermesSessionIds,
|
|
10047
|
+
...lineageManifestPatch(input.candidate),
|
|
10048
|
+
updated_at: deletedAt,
|
|
10049
|
+
deleted_at: deletedAt
|
|
10050
|
+
};
|
|
10051
|
+
const stats = buildConversationStats(
|
|
10052
|
+
{ ...nextManifest, stats: input.duplicate.manifest.stats },
|
|
10053
|
+
emptySnapshot3
|
|
10054
|
+
);
|
|
10055
|
+
const tombstone = { ...nextManifest, stats };
|
|
10056
|
+
await input.store.writeManifest(tombstone);
|
|
10057
|
+
await input.store.writeSnapshot(input.duplicate.conversationId, emptySnapshot3);
|
|
10058
|
+
await upsertConversationStats(input.paths, toStatsIndexRecord(tombstone, stats));
|
|
10059
|
+
}
|
|
10060
|
+
function mergeHermesLineageIntoManifest(input) {
|
|
10061
|
+
const hermesSessionIds = normalizeSessionIds([
|
|
10062
|
+
input.manifest.hermes_session_id,
|
|
10063
|
+
...input.manifest.hermes_session_ids ?? [],
|
|
10064
|
+
...input.manifest.hermes_lineage?.session_ids ?? [],
|
|
10065
|
+
...lineageSessionIds(input.candidate)
|
|
10066
|
+
]);
|
|
10067
|
+
const nextBase = {
|
|
10068
|
+
...input.manifest,
|
|
10069
|
+
hermes_session_id: input.candidate.session.id,
|
|
10070
|
+
hermes_session_ids: hermesSessionIds,
|
|
10071
|
+
...lineageManifestPatch(input.candidate),
|
|
10072
|
+
profile_uid: input.manifest.profile_uid ?? input.profileUid,
|
|
10073
|
+
profile_name_snapshot: input.manifest.profile_name_snapshot ?? input.manifest.profile ?? input.profileName,
|
|
10074
|
+
profile: input.manifest.profile ?? input.manifest.profile_name_snapshot ?? input.profileName,
|
|
10075
|
+
updated_at: latestTimestamp(input.manifest.updated_at, input.updatedAt)
|
|
10076
|
+
};
|
|
10077
|
+
const title = lineageTitle(input.candidate);
|
|
10078
|
+
if (title && canSyncHermesTitle(input.manifest)) {
|
|
10079
|
+
nextBase.title = normalizeTitle(title);
|
|
10080
|
+
nextBase.title_source = "hermes";
|
|
10081
|
+
}
|
|
10082
|
+
const baseStats = buildConversationStats(nextBase, input.snapshot);
|
|
10083
|
+
return {
|
|
10084
|
+
...nextBase,
|
|
10085
|
+
stats: buildImportedHermesStats({
|
|
10086
|
+
candidate: input.candidate,
|
|
10087
|
+
snapshot: input.snapshot,
|
|
10088
|
+
profileUid: input.profileUid,
|
|
10089
|
+
profileName: input.profileName,
|
|
10090
|
+
updatedAt: input.updatedAt,
|
|
10091
|
+
baseStats
|
|
10092
|
+
})
|
|
10093
|
+
};
|
|
10094
|
+
}
|
|
10095
|
+
function manifestEquivalent(left, right) {
|
|
10096
|
+
return stableJson(left) === stableJson(right);
|
|
9633
10097
|
}
|
|
9634
10098
|
function collectImportedHermesPrefix(snapshot) {
|
|
9635
10099
|
const rows = [];
|
|
@@ -10103,7 +10567,7 @@ function latestTimestamp(left, right) {
|
|
|
10103
10567
|
return leftTime >= rightTime ? left : right;
|
|
10104
10568
|
}
|
|
10105
10569
|
async function readKnownHermesSessions(store) {
|
|
10106
|
-
const
|
|
10570
|
+
const sessionIds = /* @__PURE__ */ new Set();
|
|
10107
10571
|
const conversationIdsBySessionId = /* @__PURE__ */ new Map();
|
|
10108
10572
|
for (const conversationId of await store.listConversationIds()) {
|
|
10109
10573
|
const manifest = await store.readManifest(conversationId).catch(() => null);
|
|
@@ -10111,7 +10575,7 @@ async function readKnownHermesSessions(store) {
|
|
|
10111
10575
|
continue;
|
|
10112
10576
|
}
|
|
10113
10577
|
if (manifest.hermes_session_id) {
|
|
10114
|
-
|
|
10578
|
+
sessionIds.add(manifest.hermes_session_id);
|
|
10115
10579
|
rememberKnownHermesConversation(
|
|
10116
10580
|
conversationIdsBySessionId,
|
|
10117
10581
|
manifest.hermes_session_id,
|
|
@@ -10119,7 +10583,22 @@ async function readKnownHermesSessions(store) {
|
|
|
10119
10583
|
);
|
|
10120
10584
|
}
|
|
10121
10585
|
for (const sessionId of manifest.hermes_session_ids ?? []) {
|
|
10122
|
-
|
|
10586
|
+
sessionIds.add(sessionId);
|
|
10587
|
+
rememberKnownHermesConversation(
|
|
10588
|
+
conversationIdsBySessionId,
|
|
10589
|
+
sessionId,
|
|
10590
|
+
conversationId
|
|
10591
|
+
);
|
|
10592
|
+
}
|
|
10593
|
+
for (const sessionId of [
|
|
10594
|
+
manifest.hermes_lineage?.root_session_id,
|
|
10595
|
+
manifest.hermes_lineage?.current_session_id,
|
|
10596
|
+
...manifest.hermes_lineage?.session_ids ?? []
|
|
10597
|
+
]) {
|
|
10598
|
+
if (!sessionId) {
|
|
10599
|
+
continue;
|
|
10600
|
+
}
|
|
10601
|
+
sessionIds.add(sessionId);
|
|
10123
10602
|
rememberKnownHermesConversation(
|
|
10124
10603
|
conversationIdsBySessionId,
|
|
10125
10604
|
sessionId,
|
|
@@ -10127,7 +10606,7 @@ async function readKnownHermesSessions(store) {
|
|
|
10127
10606
|
);
|
|
10128
10607
|
}
|
|
10129
10608
|
}
|
|
10130
|
-
return {
|
|
10609
|
+
return { sessionIds, conversationIdsBySessionId };
|
|
10131
10610
|
}
|
|
10132
10611
|
function rememberKnownHermesConversation(map, sessionId, conversationId) {
|
|
10133
10612
|
const current = map.get(sessionId) ?? [];
|
|
@@ -10137,7 +10616,7 @@ function rememberKnownHermesConversation(map, sessionId, conversationId) {
|
|
|
10137
10616
|
}
|
|
10138
10617
|
async function discoverHermesProfileNames() {
|
|
10139
10618
|
const names = /* @__PURE__ */ new Set([DEFAULT_PROFILE_NAME]);
|
|
10140
|
-
const profilesDir =
|
|
10619
|
+
const profilesDir = path15.join(os4.homedir(), ".hermes", "profiles");
|
|
10141
10620
|
const entries = await readdir7(profilesDir, { withFileTypes: true }).catch(
|
|
10142
10621
|
(error) => {
|
|
10143
10622
|
if (isNodeError11(error, "ENOENT")) {
|
|
@@ -10245,14 +10724,12 @@ function joinImportedText(left, right) {
|
|
|
10245
10724
|
${right}`;
|
|
10246
10725
|
}
|
|
10247
10726
|
function projectCompressionTips(rows) {
|
|
10248
|
-
const byId = /* @__PURE__ */ new Map();
|
|
10249
10727
|
const childrenByParent = /* @__PURE__ */ new Map();
|
|
10250
10728
|
for (const row of rows) {
|
|
10251
10729
|
const id = readString9(row, "id");
|
|
10252
10730
|
if (!id) {
|
|
10253
10731
|
continue;
|
|
10254
10732
|
}
|
|
10255
|
-
byId.set(id, row);
|
|
10256
10733
|
const parentId = readString9(row, "parent_session_id");
|
|
10257
10734
|
if (parentId) {
|
|
10258
10735
|
const children = childrenByParent.get(parentId) ?? [];
|
|
@@ -10268,13 +10745,14 @@ function projectCompressionTips(rows) {
|
|
|
10268
10745
|
}
|
|
10269
10746
|
let tip = row;
|
|
10270
10747
|
const visited = /* @__PURE__ */ new Set([id]);
|
|
10748
|
+
const chain = [row];
|
|
10271
10749
|
while (readString9(tip, "end_reason") === "compression") {
|
|
10272
10750
|
const tipId2 = readString9(tip, "id");
|
|
10273
10751
|
if (!tipId2) {
|
|
10274
10752
|
break;
|
|
10275
10753
|
}
|
|
10276
|
-
const next = (childrenByParent.get(tipId2) ?? []).filter((child) =>
|
|
10277
|
-
(left, right) => (readNumber2(right.last_active) ?? 0) - (readNumber2(left.last_active) ?? 0)
|
|
10754
|
+
const next = (childrenByParent.get(tipId2) ?? []).filter((child) => isCompressionContinuation(tip, child)).sort(
|
|
10755
|
+
(left, right) => (readNumber2(right.started_at) ?? 0) - (readNumber2(left.started_at) ?? 0) || (readNumber2(right.last_active) ?? 0) - (readNumber2(left.last_active) ?? 0)
|
|
10278
10756
|
)[0];
|
|
10279
10757
|
const nextId = next ? readString9(next, "id") : null;
|
|
10280
10758
|
if (!next || !nextId || visited.has(nextId)) {
|
|
@@ -10282,25 +10760,75 @@ function projectCompressionTips(rows) {
|
|
|
10282
10760
|
}
|
|
10283
10761
|
tip = next;
|
|
10284
10762
|
visited.add(nextId);
|
|
10763
|
+
chain.push(next);
|
|
10285
10764
|
}
|
|
10286
10765
|
const tipId = readString9(tip, "id");
|
|
10287
10766
|
if (tipId) {
|
|
10767
|
+
const sessionIds = chain.map((item) => readString9(item, "id")).filter((item) => Boolean(item));
|
|
10288
10768
|
projected.push({
|
|
10289
10769
|
...tip,
|
|
10290
10770
|
id: tipId,
|
|
10291
10771
|
_lineage_root_id: id,
|
|
10772
|
+
_lineage_session_ids: sessionIds,
|
|
10773
|
+
_lineage_title: readString9(row, "title") ?? stripCompressionTitleSuffix2(readString9(tip, "title") ?? ""),
|
|
10774
|
+
_lineage_usage: aggregateHermesSessionUsage(chain),
|
|
10292
10775
|
started_at: readNumber2(row.started_at) ?? readNumber2(tip.started_at)
|
|
10293
10776
|
});
|
|
10294
10777
|
}
|
|
10295
10778
|
}
|
|
10296
10779
|
return projected;
|
|
10297
10780
|
}
|
|
10781
|
+
function isCompressionContinuation(parent, child) {
|
|
10782
|
+
const childId = readString9(child, "id");
|
|
10783
|
+
const parentEndedAt = readNumber2(parent.ended_at);
|
|
10784
|
+
const childStartedAt = readNumber2(child.started_at);
|
|
10785
|
+
return Boolean(childId) && readString9(parent, "end_reason") === "compression" && parentEndedAt !== null && childStartedAt !== null && childStartedAt >= parentEndedAt;
|
|
10786
|
+
}
|
|
10787
|
+
function aggregateHermesSessionUsage(rows) {
|
|
10788
|
+
let inputTokens = 0;
|
|
10789
|
+
let outputTokens = 0;
|
|
10790
|
+
let apiCallCount = 0;
|
|
10791
|
+
let model;
|
|
10792
|
+
let provider;
|
|
10793
|
+
for (const row of rows) {
|
|
10794
|
+
inputTokens += readNumber2(row.input_tokens) ?? 0;
|
|
10795
|
+
outputTokens += readNumber2(row.output_tokens) ?? 0;
|
|
10796
|
+
apiCallCount += readNumber2(row.api_call_count) ?? 0;
|
|
10797
|
+
model = readString9(row, "model") ?? model;
|
|
10798
|
+
provider = readString9(row, "billing_provider") ?? readString9(row, "provider") ?? provider;
|
|
10799
|
+
}
|
|
10800
|
+
return {
|
|
10801
|
+
input_tokens: inputTokens,
|
|
10802
|
+
output_tokens: outputTokens,
|
|
10803
|
+
total_tokens: inputTokens + outputTokens,
|
|
10804
|
+
api_call_count: apiCallCount,
|
|
10805
|
+
...model ? { model } : {},
|
|
10806
|
+
...provider ? { provider } : {}
|
|
10807
|
+
};
|
|
10808
|
+
}
|
|
10809
|
+
async function readHermesLineageMessages(candidate) {
|
|
10810
|
+
const rows = [];
|
|
10811
|
+
for (const sessionId of lineageSessionIds(candidate)) {
|
|
10812
|
+
const messages = await readHermesSessionMessages({
|
|
10813
|
+
...candidate,
|
|
10814
|
+
session: {
|
|
10815
|
+
...candidate.session,
|
|
10816
|
+
id: sessionId
|
|
10817
|
+
}
|
|
10818
|
+
});
|
|
10819
|
+
rows.push(...messages);
|
|
10820
|
+
}
|
|
10821
|
+
return rows;
|
|
10822
|
+
}
|
|
10298
10823
|
async function readHermesSessionMessages(candidate) {
|
|
10299
10824
|
const [dbMessages, jsonlMessages] = await Promise.all([
|
|
10300
10825
|
readStateDbMessages(candidate.dbPath, candidate.session.id),
|
|
10301
10826
|
readJsonlMessages(candidate.profileName, candidate.session.id)
|
|
10302
10827
|
]);
|
|
10303
|
-
|
|
10828
|
+
const selected = jsonlMessages.length > dbMessages.length ? jsonlMessages : dbMessages;
|
|
10829
|
+
return selected.map(
|
|
10830
|
+
(message) => readString9(message, "session_id") ? message : { ...message, session_id: candidate.session.id }
|
|
10831
|
+
);
|
|
10304
10832
|
}
|
|
10305
10833
|
async function readStateDbMessages(dbPath, sessionId) {
|
|
10306
10834
|
if (!await isFile(dbPath)) {
|
|
@@ -10338,8 +10866,8 @@ async function readJsonlMessages(profileName, sessionId) {
|
|
|
10338
10866
|
return [];
|
|
10339
10867
|
}
|
|
10340
10868
|
const profileDir = resolveHermesProfileDir(profileName);
|
|
10341
|
-
const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() =>
|
|
10342
|
-
const transcriptPath =
|
|
10869
|
+
const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path15.join(profileDir, "sessions"));
|
|
10870
|
+
const transcriptPath = path15.join(sessionsDir, `${sessionId}.jsonl`);
|
|
10343
10871
|
const raw = await readFile9(transcriptPath, "utf8").catch((error) => {
|
|
10344
10872
|
if (isNodeError11(error, "ENOENT")) {
|
|
10345
10873
|
return "";
|
|
@@ -10491,9 +11019,10 @@ function formatImportedFilenameList(filenames) {
|
|
|
10491
11019
|
function toLinkMessage(input) {
|
|
10492
11020
|
const role = normalizeMessageRole(input.message.role);
|
|
10493
11021
|
const text = normalizeContent(input.message.content);
|
|
11022
|
+
const sessionId = readString9(input.message, "session_id") ?? input.sessionId;
|
|
10494
11023
|
const createdAt = isoFromHermesTime(input.message.timestamp) ?? new Date(Date.now() + input.index).toISOString();
|
|
10495
11024
|
return {
|
|
10496
|
-
id: `msg_${
|
|
11025
|
+
id: `msg_${randomUUID7().replaceAll("-", "")}`,
|
|
10497
11026
|
schema_version: 1,
|
|
10498
11027
|
conversation_id: input.conversationId,
|
|
10499
11028
|
role,
|
|
@@ -10509,7 +11038,7 @@ function toLinkMessage(input) {
|
|
|
10509
11038
|
parts: text ? [{ type: "text", text }] : [],
|
|
10510
11039
|
attachments: [],
|
|
10511
11040
|
hermes: {
|
|
10512
|
-
session_id:
|
|
11041
|
+
session_id: sessionId,
|
|
10513
11042
|
message_id: input.message.id,
|
|
10514
11043
|
imported_from: "hermes",
|
|
10515
11044
|
import_projection: HERMES_IMPORT_PROJECTION_VERSION
|
|
@@ -10545,6 +11074,47 @@ function normalizeTitle(value) {
|
|
|
10545
11074
|
const normalized = value?.replace(/\s+/gu, " ").trim();
|
|
10546
11075
|
return normalized || DEFAULT_CONVERSATION_TITLE;
|
|
10547
11076
|
}
|
|
11077
|
+
function canSyncHermesTitle(manifest) {
|
|
11078
|
+
return manifest.title_source !== "manual" && manifest.title_source !== "generated" && manifest.title_source !== "temporary_user_message" && manifest.title_source !== "temporary_fallback";
|
|
11079
|
+
}
|
|
11080
|
+
function stripCompressionTitleSuffix2(value) {
|
|
11081
|
+
const normalized = value.replace(/\s+/gu, " ").trim();
|
|
11082
|
+
if (!normalized) {
|
|
11083
|
+
return void 0;
|
|
11084
|
+
}
|
|
11085
|
+
const match = /^(.*?) #\d+$/u.exec(normalized);
|
|
11086
|
+
const stripped = match?.[1]?.trim();
|
|
11087
|
+
return stripped || normalized;
|
|
11088
|
+
}
|
|
11089
|
+
function hasCompressionTitleSuffix(value) {
|
|
11090
|
+
return / #\d+$/u.test(value.trim());
|
|
11091
|
+
}
|
|
11092
|
+
function normalizeSessionIds(values) {
|
|
11093
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11094
|
+
for (const value of values) {
|
|
11095
|
+
const sessionId = value?.trim();
|
|
11096
|
+
if (!sessionId || seen.has(sessionId)) {
|
|
11097
|
+
continue;
|
|
11098
|
+
}
|
|
11099
|
+
seen.add(sessionId);
|
|
11100
|
+
}
|
|
11101
|
+
return [...seen];
|
|
11102
|
+
}
|
|
11103
|
+
function stableJson(value) {
|
|
11104
|
+
return JSON.stringify(stabilizeJsonValue(value));
|
|
11105
|
+
}
|
|
11106
|
+
function stabilizeJsonValue(value) {
|
|
11107
|
+
if (Array.isArray(value)) {
|
|
11108
|
+
return value.map((item) => stabilizeJsonValue(item));
|
|
11109
|
+
}
|
|
11110
|
+
if (!value || typeof value !== "object") {
|
|
11111
|
+
return value;
|
|
11112
|
+
}
|
|
11113
|
+
const record = value;
|
|
11114
|
+
return Object.fromEntries(
|
|
11115
|
+
Object.keys(record).sort().map((key) => [key, stabilizeJsonValue(record[key])])
|
|
11116
|
+
);
|
|
11117
|
+
}
|
|
10548
11118
|
function normalizeMessageRole(value) {
|
|
10549
11119
|
switch (value?.trim().toLowerCase()) {
|
|
10550
11120
|
case "user":
|
|
@@ -10624,7 +11194,7 @@ async function isFile(filePath) {
|
|
|
10624
11194
|
});
|
|
10625
11195
|
}
|
|
10626
11196
|
function createConversationId() {
|
|
10627
|
-
return `conv_${
|
|
11197
|
+
return `conv_${randomUUID7().replaceAll("-", "")}`;
|
|
10628
11198
|
}
|
|
10629
11199
|
function isoFromHermesTime(value) {
|
|
10630
11200
|
const numeric = readNumber2(value);
|
|
@@ -10891,10 +11461,10 @@ async function cancelHermesRun(runId, options = {}) {
|
|
|
10891
11461
|
);
|
|
10892
11462
|
}
|
|
10893
11463
|
}
|
|
10894
|
-
async function callHermesApi(
|
|
11464
|
+
async function callHermesApi(path26, init, options) {
|
|
10895
11465
|
const method = init.method ?? "GET";
|
|
10896
11466
|
const startedAt = Date.now();
|
|
10897
|
-
void options.logger?.debug("hermes_api_request_started", { method, path:
|
|
11467
|
+
void options.logger?.debug("hermes_api_request_started", { method, path: path26 });
|
|
10898
11468
|
const availability = await ensureHermesApiServerAvailable({
|
|
10899
11469
|
fetchImpl: options.fetchImpl,
|
|
10900
11470
|
logger: options.logger,
|
|
@@ -10902,21 +11472,21 @@ async function callHermesApi(path25, init, options) {
|
|
|
10902
11472
|
});
|
|
10903
11473
|
let config = availability.configResult.apiServer;
|
|
10904
11474
|
const fetcher = options.fetchImpl ?? fetch;
|
|
10905
|
-
const request = () => fetchHermesApi(fetcher, config,
|
|
11475
|
+
const request = () => fetchHermesApi(fetcher, config, path26, init, options);
|
|
10906
11476
|
let response;
|
|
10907
11477
|
try {
|
|
10908
11478
|
response = await request();
|
|
10909
11479
|
} catch (error) {
|
|
10910
|
-
logHermesApiError(options.logger, method,
|
|
11480
|
+
logHermesApiError(options.logger, method, path26, startedAt, error);
|
|
10911
11481
|
throw error;
|
|
10912
11482
|
}
|
|
10913
11483
|
if (response.status !== 401) {
|
|
10914
|
-
logHermesApiResponse(options.logger, method,
|
|
11484
|
+
logHermesApiResponse(options.logger, method, path26, startedAt, response);
|
|
10915
11485
|
return response;
|
|
10916
11486
|
}
|
|
10917
11487
|
void options.logger?.warn("hermes_api_request_retrying_after_401", {
|
|
10918
11488
|
method,
|
|
10919
|
-
path:
|
|
11489
|
+
path: path26,
|
|
10920
11490
|
duration_ms: Date.now() - startedAt
|
|
10921
11491
|
});
|
|
10922
11492
|
const refreshedAvailability = await ensureHermesApiServerAvailable({
|
|
@@ -10929,20 +11499,20 @@ async function callHermesApi(path25, init, options) {
|
|
|
10929
11499
|
try {
|
|
10930
11500
|
response = await request();
|
|
10931
11501
|
} catch (error) {
|
|
10932
|
-
logHermesApiError(options.logger, method,
|
|
11502
|
+
logHermesApiError(options.logger, method, path26, startedAt, error);
|
|
10933
11503
|
throw error;
|
|
10934
11504
|
}
|
|
10935
|
-
logHermesApiResponse(options.logger, method,
|
|
11505
|
+
logHermesApiResponse(options.logger, method, path26, startedAt, response);
|
|
10936
11506
|
return response;
|
|
10937
11507
|
}
|
|
10938
|
-
async function fetchHermesApi(fetcher, config,
|
|
11508
|
+
async function fetchHermesApi(fetcher, config, path26, init, options) {
|
|
10939
11509
|
const headers = new Headers(init.headers);
|
|
10940
11510
|
headers.set("accept", headers.get("accept") ?? "application/json");
|
|
10941
11511
|
if (config.key) {
|
|
10942
11512
|
headers.set("x-api-key", config.key);
|
|
10943
11513
|
headers.set("authorization", `Bearer ${config.key}`);
|
|
10944
11514
|
}
|
|
10945
|
-
return await fetcher(`http://127.0.0.1:${config.port}${
|
|
11515
|
+
return await fetcher(`http://127.0.0.1:${config.port}${path26}`, {
|
|
10946
11516
|
...init,
|
|
10947
11517
|
headers
|
|
10948
11518
|
}).catch((error) => {
|
|
@@ -10950,7 +11520,7 @@ async function fetchHermesApi(fetcher, config, path25, init, options) {
|
|
|
10950
11520
|
throw error;
|
|
10951
11521
|
}
|
|
10952
11522
|
void options.logger?.warn("hermes_api_server_connect_failed", {
|
|
10953
|
-
path:
|
|
11523
|
+
path: path26,
|
|
10954
11524
|
port: config.port ?? null,
|
|
10955
11525
|
error: error instanceof Error ? error.message : String(error)
|
|
10956
11526
|
});
|
|
@@ -10961,10 +11531,10 @@ async function fetchHermesApi(fetcher, config, path25, init, options) {
|
|
|
10961
11531
|
);
|
|
10962
11532
|
});
|
|
10963
11533
|
}
|
|
10964
|
-
function logHermesApiResponse(logger, method,
|
|
11534
|
+
function logHermesApiResponse(logger, method, path26, startedAt, response) {
|
|
10965
11535
|
const fields = {
|
|
10966
11536
|
method,
|
|
10967
|
-
path:
|
|
11537
|
+
path: path26,
|
|
10968
11538
|
status: response.status,
|
|
10969
11539
|
duration_ms: Date.now() - startedAt
|
|
10970
11540
|
};
|
|
@@ -10984,10 +11554,10 @@ async function logHermesApiFailureResponse(logger, fields, response) {
|
|
|
10984
11554
|
...upstreamError ? { upstream_error: upstreamError } : {}
|
|
10985
11555
|
});
|
|
10986
11556
|
}
|
|
10987
|
-
function logHermesApiError(logger, method,
|
|
11557
|
+
function logHermesApiError(logger, method, path26, startedAt, error) {
|
|
10988
11558
|
void logger?.warn("hermes_api_request_failed", {
|
|
10989
11559
|
method,
|
|
10990
|
-
path:
|
|
11560
|
+
path: path26,
|
|
10991
11561
|
duration_ms: Date.now() - startedAt,
|
|
10992
11562
|
...error instanceof LinkHttpError ? { status: error.status, code: error.code } : {},
|
|
10993
11563
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -11050,7 +11620,7 @@ function readString10(payload, key) {
|
|
|
11050
11620
|
|
|
11051
11621
|
// src/conversations/history-builder.ts
|
|
11052
11622
|
import { readFile as readFile10, stat as stat10 } from "fs/promises";
|
|
11053
|
-
import
|
|
11623
|
+
import path16 from "path";
|
|
11054
11624
|
var HISTORY_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
11055
11625
|
var HERMES_HISTORY_COLUMNS = [
|
|
11056
11626
|
"role",
|
|
@@ -11111,13 +11681,13 @@ async function readHermesTranscriptHistory(sessionId, profileName) {
|
|
|
11111
11681
|
}
|
|
11112
11682
|
const normalizedProfileName = isValidProfileName2(profileName) ? profileName : "default";
|
|
11113
11683
|
const profileDir = resolveHermesProfileDir(normalizedProfileName);
|
|
11114
|
-
const dbPath =
|
|
11684
|
+
const dbPath = path16.join(profileDir, "state.db");
|
|
11115
11685
|
const sessionsDirConfig = await readHermesSessionsDir(normalizedProfileName).then((value) => ({
|
|
11116
11686
|
sessionsDir: value.sessionsDir,
|
|
11117
11687
|
configured: value.configured,
|
|
11118
11688
|
configError: false
|
|
11119
11689
|
})).catch(() => ({
|
|
11120
|
-
sessionsDir:
|
|
11690
|
+
sessionsDir: path16.join(profileDir, "sessions"),
|
|
11121
11691
|
configured: false,
|
|
11122
11692
|
configError: true
|
|
11123
11693
|
}));
|
|
@@ -11175,7 +11745,7 @@ async function readHermesJsonlHistory(sessionsDir, sessionId) {
|
|
|
11175
11745
|
if (!isValidSessionFileStem(sessionId)) {
|
|
11176
11746
|
return empty;
|
|
11177
11747
|
}
|
|
11178
|
-
const transcriptPath =
|
|
11748
|
+
const transcriptPath = path16.join(sessionsDir, `${sessionId}.jsonl`);
|
|
11179
11749
|
const raw = await readFile10(transcriptPath, "utf8").catch((error) => {
|
|
11180
11750
|
if (isNodeError12(error, "ENOENT")) {
|
|
11181
11751
|
return "";
|
|
@@ -11418,7 +11988,7 @@ function normalizeProfileForCompare(value) {
|
|
|
11418
11988
|
// src/hermes/stt.ts
|
|
11419
11989
|
import { execFile as execFile3 } from "child_process";
|
|
11420
11990
|
import { access as access2, readFile as readFile11, stat as stat11 } from "fs/promises";
|
|
11421
|
-
import
|
|
11991
|
+
import path17 from "path";
|
|
11422
11992
|
import { promisify as promisify3 } from "util";
|
|
11423
11993
|
var execFileAsync3 = promisify3(execFile3);
|
|
11424
11994
|
var STT_RESULT_PREFIX = "__HERMES_LINK_STT__";
|
|
@@ -11513,7 +12083,7 @@ async function buildHermesSttEnv(profileName) {
|
|
|
11513
12083
|
};
|
|
11514
12084
|
const devSource = await findDevHermesAgentSource();
|
|
11515
12085
|
if (devSource) {
|
|
11516
|
-
env.PYTHONPATH = [devSource, env.PYTHONPATH].filter(Boolean).join(
|
|
12086
|
+
env.PYTHONPATH = [devSource, env.PYTHONPATH].filter(Boolean).join(path17.delimiter);
|
|
11517
12087
|
}
|
|
11518
12088
|
return env;
|
|
11519
12089
|
}
|
|
@@ -11560,14 +12130,14 @@ async function resolveHermesPythonCommand() {
|
|
|
11560
12130
|
};
|
|
11561
12131
|
}
|
|
11562
12132
|
async function resolveExecutablePath(command) {
|
|
11563
|
-
if (
|
|
12133
|
+
if (path17.isAbsolute(command)) {
|
|
11564
12134
|
return await isExecutableFile(command) ? command : null;
|
|
11565
12135
|
}
|
|
11566
12136
|
const pathEnv = process.env.PATH ?? "";
|
|
11567
12137
|
const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
|
|
11568
|
-
for (const dir of pathEnv.split(
|
|
12138
|
+
for (const dir of pathEnv.split(path17.delimiter)) {
|
|
11569
12139
|
for (const extension of extensions) {
|
|
11570
|
-
const candidate =
|
|
12140
|
+
const candidate = path17.join(dir, `${command}${extension}`);
|
|
11571
12141
|
if (await isExecutableFile(candidate)) {
|
|
11572
12142
|
return candidate;
|
|
11573
12143
|
}
|
|
@@ -11607,8 +12177,8 @@ function shebangToPythonCommand(shebang) {
|
|
|
11607
12177
|
}
|
|
11608
12178
|
async function findDevHermesAgentSource() {
|
|
11609
12179
|
const candidates = [
|
|
11610
|
-
|
|
11611
|
-
|
|
12180
|
+
path17.resolve(process.cwd(), "reference/hermes-agent"),
|
|
12181
|
+
path17.resolve(process.cwd(), "../../reference/hermes-agent")
|
|
11612
12182
|
];
|
|
11613
12183
|
for (const candidate of candidates) {
|
|
11614
12184
|
if (await isDirectory(candidate)) {
|
|
@@ -13249,7 +13819,7 @@ var ConversationService = class {
|
|
|
13249
13819
|
async createConversation(input = {}) {
|
|
13250
13820
|
await this.store.ensureConversationsDir();
|
|
13251
13821
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13252
|
-
const id = `conv_${
|
|
13822
|
+
const id = `conv_${randomUUID8().replaceAll("-", "")}`;
|
|
13253
13823
|
const title = input.title?.trim() || DEFAULT_CONVERSATION_TITLE;
|
|
13254
13824
|
const profile = await resolveConversationProfileTarget(
|
|
13255
13825
|
this.paths,
|
|
@@ -13336,7 +13906,7 @@ var ConversationService = class {
|
|
|
13336
13906
|
manifest.profile_name_snapshot ?? manifest.profile ?? input.profileName
|
|
13337
13907
|
);
|
|
13338
13908
|
const message = {
|
|
13339
|
-
id: `msg_${
|
|
13909
|
+
id: `msg_${randomUUID8().replaceAll("-", "")}`,
|
|
13340
13910
|
schema_version: 1,
|
|
13341
13911
|
conversation_id: manifest.id,
|
|
13342
13912
|
role: "assistant",
|
|
@@ -13800,6 +14370,18 @@ var ConversationService = class {
|
|
|
13800
14370
|
async deleteConversation(conversationId) {
|
|
13801
14371
|
return this.maintenance.deleteConversation(conversationId);
|
|
13802
14372
|
}
|
|
14373
|
+
prepareClearAllConversationPlan() {
|
|
14374
|
+
return this.maintenance.prepareClearAllConversationPlan();
|
|
14375
|
+
}
|
|
14376
|
+
readClearAllConversationPlan(planId) {
|
|
14377
|
+
return this.maintenance.readClearAllConversationPlan(planId);
|
|
14378
|
+
}
|
|
14379
|
+
executeClearAllConversationPlan(planId) {
|
|
14380
|
+
return this.maintenance.executeClearAllConversationPlan(planId);
|
|
14381
|
+
}
|
|
14382
|
+
startClearAllConversationPlan(planId) {
|
|
14383
|
+
return this.maintenance.startClearAllConversationPlan(planId);
|
|
14384
|
+
}
|
|
13803
14385
|
async deleteConversations(conversationIds) {
|
|
13804
14386
|
return this.maintenance.deleteConversations(conversationIds);
|
|
13805
14387
|
}
|
|
@@ -13928,8 +14510,8 @@ function findApproval(snapshot, approvalId) {
|
|
|
13928
14510
|
}
|
|
13929
14511
|
|
|
13930
14512
|
// src/identity/identity.ts
|
|
13931
|
-
import { generateKeyPairSync, randomUUID as
|
|
13932
|
-
import { mkdir as
|
|
14513
|
+
import { generateKeyPairSync, randomUUID as randomUUID9, sign } from "crypto";
|
|
14514
|
+
import { mkdir as mkdir9, chmod as chmod2 } from "fs/promises";
|
|
13933
14515
|
import { z } from "zod";
|
|
13934
14516
|
var linkIdentitySchema = z.object({
|
|
13935
14517
|
install_id: z.string().min(1),
|
|
@@ -13951,12 +14533,12 @@ async function ensureIdentity(paths = resolveRuntimePaths()) {
|
|
|
13951
14533
|
if (existing) {
|
|
13952
14534
|
return existing;
|
|
13953
14535
|
}
|
|
13954
|
-
await
|
|
14536
|
+
await mkdir9(paths.homeDir, { recursive: true, mode: 448 });
|
|
13955
14537
|
await chmod2(paths.homeDir, 448).catch(() => void 0);
|
|
13956
14538
|
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
13957
14539
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13958
14540
|
const identity = {
|
|
13959
|
-
install_id: `install_${
|
|
14541
|
+
install_id: `install_${randomUUID9().replaceAll("-", "")}`,
|
|
13960
14542
|
link_id: null,
|
|
13961
14543
|
public_key_pem: publicKey.export({ type: "spki", format: "pem" }).toString(),
|
|
13962
14544
|
private_key_pem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
|
|
@@ -13993,7 +14575,7 @@ function getIdentityStatus(identity) {
|
|
|
13993
14575
|
}
|
|
13994
14576
|
|
|
13995
14577
|
// src/security/devices.ts
|
|
13996
|
-
import { randomBytes as randomBytes2, randomUUID as
|
|
14578
|
+
import { randomBytes as randomBytes2, randomUUID as randomUUID10, timingSafeEqual, createHash as createHash4 } from "crypto";
|
|
13997
14579
|
var ACCESS_TOKEN_TTL_MS = 15 * 60 * 1e3;
|
|
13998
14580
|
var REFRESH_TOKEN_TTL_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
13999
14581
|
var DEVICE_SEEN_WRITE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
@@ -14011,7 +14593,7 @@ async function createDeviceSession(input, paths = resolveRuntimePaths()) {
|
|
|
14011
14593
|
}
|
|
14012
14594
|
}
|
|
14013
14595
|
const device = {
|
|
14014
|
-
id: `dev_${
|
|
14596
|
+
id: `dev_${randomUUID10().replaceAll("-", "")}`,
|
|
14015
14597
|
label: normalizeDeviceLabel(input.label),
|
|
14016
14598
|
platform: normalizeDevicePlatform(input.platform),
|
|
14017
14599
|
model: normalizeDeviceModel(input.model),
|
|
@@ -14925,6 +15507,37 @@ function registerConversationRoutes(router, options) {
|
|
|
14925
15507
|
await authenticateRequest(ctx, paths);
|
|
14926
15508
|
ctx.body = { ok: true };
|
|
14927
15509
|
});
|
|
15510
|
+
router.post("/api/v1/conversations/clear-plans", async (ctx) => {
|
|
15511
|
+
await authenticateRequest(ctx, paths);
|
|
15512
|
+
const plan = await conversations.prepareClearAllConversationPlan();
|
|
15513
|
+
ctx.status = 201;
|
|
15514
|
+
ctx.body = {
|
|
15515
|
+
ok: true,
|
|
15516
|
+
plan
|
|
15517
|
+
};
|
|
15518
|
+
});
|
|
15519
|
+
router.get("/api/v1/conversations/clear-plans/:planId", async (ctx) => {
|
|
15520
|
+
await authenticateRequest(ctx, paths);
|
|
15521
|
+
ctx.set("cache-control", "no-store");
|
|
15522
|
+
ctx.body = {
|
|
15523
|
+
ok: true,
|
|
15524
|
+
plan: await conversations.readClearAllConversationPlan(ctx.params.planId)
|
|
15525
|
+
};
|
|
15526
|
+
});
|
|
15527
|
+
router.post(
|
|
15528
|
+
"/api/v1/conversations/clear-plans/:planId/execute",
|
|
15529
|
+
async (ctx) => {
|
|
15530
|
+
await authenticateRequest(ctx, paths);
|
|
15531
|
+
const plan = await conversations.startClearAllConversationPlan(
|
|
15532
|
+
ctx.params.planId
|
|
15533
|
+
);
|
|
15534
|
+
ctx.status = plan.status === "completed" ? 200 : 202;
|
|
15535
|
+
ctx.body = {
|
|
15536
|
+
ok: true,
|
|
15537
|
+
plan
|
|
15538
|
+
};
|
|
15539
|
+
}
|
|
15540
|
+
);
|
|
14928
15541
|
router.delete("/api/v1/conversations", async (ctx) => {
|
|
14929
15542
|
await authenticateRequest(ctx, paths);
|
|
14930
15543
|
const body = await readJsonBody(ctx.req);
|
|
@@ -15148,14 +15761,14 @@ function createHttpErrorMiddleware(logger) {
|
|
|
15148
15761
|
// src/hermes/profiles.ts
|
|
15149
15762
|
import { readdir as readdir9, readFile as readFile12, rename as rename3, rm as rm6, stat as stat12 } from "fs/promises";
|
|
15150
15763
|
import os5 from "os";
|
|
15151
|
-
import
|
|
15764
|
+
import path18 from "path";
|
|
15152
15765
|
import YAML2 from "yaml";
|
|
15153
15766
|
var DEFAULT_PROFILE = "default";
|
|
15154
15767
|
var PROFILE_NAME_PATTERN4 = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
15155
15768
|
async function listHermesProfiles(paths = resolveRuntimePaths()) {
|
|
15156
15769
|
const profiles = /* @__PURE__ */ new Map();
|
|
15157
15770
|
profiles.set(DEFAULT_PROFILE, await profileInfo(DEFAULT_PROFILE, paths));
|
|
15158
|
-
const profilesDir =
|
|
15771
|
+
const profilesDir = path18.join(os5.homedir(), ".hermes", "profiles");
|
|
15159
15772
|
const entries = await readdir9(profilesDir, { withFileTypes: true }).catch(
|
|
15160
15773
|
(error) => {
|
|
15161
15774
|
if (isNodeError14(error, "ENOENT")) {
|
|
@@ -15250,7 +15863,7 @@ async function readHermesProfileCapabilities(name) {
|
|
|
15250
15863
|
return {
|
|
15251
15864
|
defaultModel: listedModels?.defaultModel ?? null,
|
|
15252
15865
|
modelCount: listedModels?.models.length ?? 0,
|
|
15253
|
-
skillCount: await countSkills(
|
|
15866
|
+
skillCount: await countSkills(path18.join(profileDir, "skills")).catch(
|
|
15254
15867
|
() => 0
|
|
15255
15868
|
),
|
|
15256
15869
|
toolCount: await countConfiguredTools(name).catch(() => 0)
|
|
@@ -15306,7 +15919,7 @@ async function countSkills(root) {
|
|
|
15306
15919
|
);
|
|
15307
15920
|
let count = 0;
|
|
15308
15921
|
for (const entry of entries) {
|
|
15309
|
-
const entryPath =
|
|
15922
|
+
const entryPath = path18.join(root, entry.name);
|
|
15310
15923
|
if (entry.name === ".git" || entry.name === ".hub") {
|
|
15311
15924
|
continue;
|
|
15312
15925
|
}
|
|
@@ -16003,12 +16616,12 @@ import { spawn as spawn2 } from "child_process";
|
|
|
16003
16616
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
16004
16617
|
import {
|
|
16005
16618
|
cp,
|
|
16006
|
-
mkdir as
|
|
16619
|
+
mkdir as mkdir10,
|
|
16007
16620
|
readFile as readFile13,
|
|
16008
16621
|
rm as rm7,
|
|
16009
16622
|
stat as stat13
|
|
16010
16623
|
} from "fs/promises";
|
|
16011
|
-
import
|
|
16624
|
+
import path19 from "path";
|
|
16012
16625
|
import YAML3 from "yaml";
|
|
16013
16626
|
var PROFILE_CREATE_LOG_FILE = "profile-create.log";
|
|
16014
16627
|
var PROFILE_CREATE_LOG_MAX_FILES = 3;
|
|
@@ -16056,7 +16669,7 @@ async function startHermesProfileCreation(input, options) {
|
|
|
16056
16669
|
signal: null,
|
|
16057
16670
|
error: null
|
|
16058
16671
|
};
|
|
16059
|
-
await
|
|
16672
|
+
await mkdir10(options.paths.runDir, { recursive: true, mode: 448 });
|
|
16060
16673
|
await writeProfileCreationState(options.paths, started);
|
|
16061
16674
|
await writer.write(`
|
|
16062
16675
|
=== profile creation started ${startedAt} ===
|
|
@@ -16459,7 +17072,7 @@ function collectEnvKeys(value, keys = /* @__PURE__ */ new Set()) {
|
|
|
16459
17072
|
return keys;
|
|
16460
17073
|
}
|
|
16461
17074
|
async function writeEnvValues(profileName, values) {
|
|
16462
|
-
const envPath =
|
|
17075
|
+
const envPath = path19.join(resolveHermesProfileDir(profileName), ".env");
|
|
16463
17076
|
const existingRaw = await readFile13(envPath, "utf8").catch((error) => {
|
|
16464
17077
|
if (isNodeError15(error, "ENOENT")) {
|
|
16465
17078
|
return "";
|
|
@@ -16496,8 +17109,8 @@ async function writeEnvValues(profileName, values) {
|
|
|
16496
17109
|
await atomicWriteFilePreservingMetadata(envPath, nextRaw);
|
|
16497
17110
|
}
|
|
16498
17111
|
async function copySkills(sourceProfile, targetProfile) {
|
|
16499
|
-
const sourceSkills =
|
|
16500
|
-
const targetSkills =
|
|
17112
|
+
const sourceSkills = path19.join(resolveHermesProfileDir(sourceProfile), "skills");
|
|
17113
|
+
const targetSkills = path19.join(resolveHermesProfileDir(targetProfile), "skills");
|
|
16501
17114
|
if (!await pathExists(sourceSkills)) {
|
|
16502
17115
|
return;
|
|
16503
17116
|
}
|
|
@@ -16592,10 +17205,10 @@ async function readProfileCreationLogLines(paths) {
|
|
|
16592
17205
|
);
|
|
16593
17206
|
}
|
|
16594
17207
|
function profileCreationStatePath(paths) {
|
|
16595
|
-
return
|
|
17208
|
+
return path19.join(paths.runDir, "profile-create-state.json");
|
|
16596
17209
|
}
|
|
16597
17210
|
function profileCreationLogPath(paths) {
|
|
16598
|
-
return
|
|
17211
|
+
return path19.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
|
|
16599
17212
|
}
|
|
16600
17213
|
async function clearProfileCreationLogFiles(paths) {
|
|
16601
17214
|
const primary = profileCreationLogPath(paths);
|
|
@@ -16869,7 +17482,7 @@ import {
|
|
|
16869
17482
|
readFile as readFile14,
|
|
16870
17483
|
stat as stat14
|
|
16871
17484
|
} from "fs/promises";
|
|
16872
|
-
import
|
|
17485
|
+
import path20 from "path";
|
|
16873
17486
|
import YAML4 from "yaml";
|
|
16874
17487
|
var ENTRY_DELIMITER = "\n\xA7\n";
|
|
16875
17488
|
var DEFAULT_MEMORY_LIMIT = 2200;
|
|
@@ -17086,7 +17699,7 @@ async function saveProviderSettings(profileName, provider, patch) {
|
|
|
17086
17699
|
if (provider === "hindsight") {
|
|
17087
17700
|
await patchJsonProviderConfig(
|
|
17088
17701
|
profileName,
|
|
17089
|
-
|
|
17702
|
+
path20.join("hindsight", "config.json"),
|
|
17090
17703
|
{
|
|
17091
17704
|
mode: patch.mode,
|
|
17092
17705
|
api_url: patch.apiUrl,
|
|
@@ -17253,7 +17866,7 @@ async function patchHermesMemoryProvider(profileName, provider) {
|
|
|
17253
17866
|
await atomicWriteFilePreservingMetadata(configPath, document.toString());
|
|
17254
17867
|
}
|
|
17255
17868
|
function resolveMemoryDir(profileName) {
|
|
17256
|
-
return
|
|
17869
|
+
return path20.join(resolveHermesProfileDir(profileName), "memories");
|
|
17257
17870
|
}
|
|
17258
17871
|
async function readMemoryStore(profileName, target, limits) {
|
|
17259
17872
|
const filePath = memoryFilePath(profileName, target);
|
|
@@ -17314,7 +17927,7 @@ async function writeMemoryEntries(profileName, target, entries) {
|
|
|
17314
17927
|
);
|
|
17315
17928
|
}
|
|
17316
17929
|
function memoryFilePath(profileName, target) {
|
|
17317
|
-
return
|
|
17930
|
+
return path20.join(
|
|
17318
17931
|
resolveMemoryDir(profileName),
|
|
17319
17932
|
target === "user" ? "USER.md" : "MEMORY.md"
|
|
17320
17933
|
);
|
|
@@ -17374,7 +17987,7 @@ async function readCustomProviderSetupSummary(profileName) {
|
|
|
17374
17987
|
configurable: true,
|
|
17375
17988
|
configured: true,
|
|
17376
17989
|
configurationIssue: null,
|
|
17377
|
-
providerConfigPath:
|
|
17990
|
+
providerConfigPath: path20.join(
|
|
17378
17991
|
resolveHermesProfileDir(profileName),
|
|
17379
17992
|
"<provider>.json"
|
|
17380
17993
|
),
|
|
@@ -17728,7 +18341,7 @@ async function readProviderSettings(profileName, provider) {
|
|
|
17728
18341
|
stringSetting(
|
|
17729
18342
|
"dbPath",
|
|
17730
18343
|
"SQLite \u6570\u636E\u5E93\u8DEF\u5F84",
|
|
17731
|
-
config.db_path ??
|
|
18344
|
+
config.db_path ?? path20.join(resolveHermesProfileDir(profileName), "memory_store.db")
|
|
17732
18345
|
),
|
|
17733
18346
|
booleanSetting("autoExtract", "\u4F1A\u8BDD\u7ED3\u675F\u81EA\u52A8\u62BD\u53D6", config.auto_extract ?? false),
|
|
17734
18347
|
numberSetting("defaultTrust", "\u9ED8\u8BA4\u4FE1\u4EFB\u5206", config.default_trust ?? 0.5),
|
|
@@ -17751,7 +18364,7 @@ async function readProviderSettings(profileName, provider) {
|
|
|
17751
18364
|
stringSetting(
|
|
17752
18365
|
"workingDirectory",
|
|
17753
18366
|
"\u5DE5\u4F5C\u76EE\u5F55",
|
|
17754
|
-
|
|
18367
|
+
path20.join(resolveHermesProfileDir(profileName), "byterover"),
|
|
17755
18368
|
false
|
|
17756
18369
|
)
|
|
17757
18370
|
];
|
|
@@ -17760,16 +18373,16 @@ async function readProviderSettings(profileName, provider) {
|
|
|
17760
18373
|
}
|
|
17761
18374
|
function memoryProviderConfigPath(profileName, provider) {
|
|
17762
18375
|
if (provider === "honcho") {
|
|
17763
|
-
return
|
|
18376
|
+
return path20.join(resolveHermesProfileDir(profileName), "honcho.json");
|
|
17764
18377
|
}
|
|
17765
18378
|
if (provider === "mem0") {
|
|
17766
|
-
return
|
|
18379
|
+
return path20.join(resolveHermesProfileDir(profileName), "mem0.json");
|
|
17767
18380
|
}
|
|
17768
18381
|
if (provider === "supermemory") {
|
|
17769
|
-
return
|
|
18382
|
+
return path20.join(resolveHermesProfileDir(profileName), "supermemory.json");
|
|
17770
18383
|
}
|
|
17771
18384
|
if (provider === "hindsight") {
|
|
17772
|
-
return
|
|
18385
|
+
return path20.join(
|
|
17773
18386
|
resolveHermesProfileDir(profileName),
|
|
17774
18387
|
"hindsight",
|
|
17775
18388
|
"config.json"
|
|
@@ -17778,13 +18391,13 @@ function memoryProviderConfigPath(profileName, provider) {
|
|
|
17778
18391
|
return null;
|
|
17779
18392
|
}
|
|
17780
18393
|
function customProviderConfigPath(profileName, provider) {
|
|
17781
|
-
return
|
|
18394
|
+
return path20.join(
|
|
17782
18395
|
resolveHermesProfileDir(profileName),
|
|
17783
18396
|
`${normalizeCustomProviderId(provider)}.json`
|
|
17784
18397
|
);
|
|
17785
18398
|
}
|
|
17786
18399
|
function customProviderRegistryPath(profileName) {
|
|
17787
|
-
return
|
|
18400
|
+
return path20.join(
|
|
17788
18401
|
resolveHermesProfileDir(profileName),
|
|
17789
18402
|
CUSTOM_PROVIDER_REGISTRY_FILE
|
|
17790
18403
|
);
|
|
@@ -17836,7 +18449,7 @@ async function saveCustomProviderRegistryEntry(profileName, provider) {
|
|
|
17836
18449
|
);
|
|
17837
18450
|
}
|
|
17838
18451
|
async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
17839
|
-
const pluginsDir =
|
|
18452
|
+
const pluginsDir = path20.join(resolveHermesProfileDir(profileName), "plugins");
|
|
17840
18453
|
const entries = await readdir10(pluginsDir, { withFileTypes: true }).catch(
|
|
17841
18454
|
(error) => {
|
|
17842
18455
|
if (isNodeError16(error, "ENOENT")) {
|
|
@@ -17856,7 +18469,7 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
|
17856
18469
|
} catch {
|
|
17857
18470
|
continue;
|
|
17858
18471
|
}
|
|
17859
|
-
const providerDir =
|
|
18472
|
+
const providerDir = path20.join(pluginsDir, entry.name);
|
|
17860
18473
|
if (!await isMemoryProviderPluginDir(providerDir)) {
|
|
17861
18474
|
continue;
|
|
17862
18475
|
}
|
|
@@ -17870,7 +18483,7 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
|
17870
18483
|
return descriptors;
|
|
17871
18484
|
}
|
|
17872
18485
|
async function isUserMemoryProviderInstalled(profileName, provider) {
|
|
17873
|
-
const providerDir =
|
|
18486
|
+
const providerDir = path20.join(
|
|
17874
18487
|
resolveHermesProfileDir(profileName),
|
|
17875
18488
|
"plugins",
|
|
17876
18489
|
normalizeCustomProviderId(provider)
|
|
@@ -17878,7 +18491,7 @@ async function isUserMemoryProviderInstalled(profileName, provider) {
|
|
|
17878
18491
|
return isMemoryProviderPluginDir(providerDir);
|
|
17879
18492
|
}
|
|
17880
18493
|
async function isMemoryProviderPluginDir(providerDir) {
|
|
17881
|
-
const source = await readFile14(
|
|
18494
|
+
const source = await readFile14(path20.join(providerDir, "__init__.py"), "utf8").catch(
|
|
17882
18495
|
(error) => {
|
|
17883
18496
|
if (isNodeError16(error, "ENOENT")) {
|
|
17884
18497
|
return "";
|
|
@@ -17890,7 +18503,7 @@ async function isMemoryProviderPluginDir(providerDir) {
|
|
|
17890
18503
|
return sample.includes("register_memory_provider") || sample.includes("MemoryProvider");
|
|
17891
18504
|
}
|
|
17892
18505
|
async function readPluginMetadata(providerDir) {
|
|
17893
|
-
const raw = await readFile14(
|
|
18506
|
+
const raw = await readFile14(path20.join(providerDir, "plugin.yaml"), "utf8").catch(
|
|
17894
18507
|
(error) => {
|
|
17895
18508
|
if (isNodeError16(error, "ENOENT")) {
|
|
17896
18509
|
return "";
|
|
@@ -17902,10 +18515,10 @@ async function readPluginMetadata(providerDir) {
|
|
|
17902
18515
|
}
|
|
17903
18516
|
async function resolveByteRoverCli() {
|
|
17904
18517
|
const candidates = [
|
|
17905
|
-
...(process.env.PATH ?? "").split(
|
|
17906
|
-
|
|
18518
|
+
...(process.env.PATH ?? "").split(path20.delimiter).filter(Boolean).map((dir) => path20.join(dir, "brv")),
|
|
18519
|
+
path20.join(process.env.HOME ?? "", ".brv-cli", "bin", "brv"),
|
|
17907
18520
|
"/usr/local/bin/brv",
|
|
17908
|
-
|
|
18521
|
+
path20.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
|
|
17909
18522
|
].filter(Boolean);
|
|
17910
18523
|
for (const candidate of candidates) {
|
|
17911
18524
|
const found = await access3(candidate).then(() => true).catch(() => false);
|
|
@@ -17966,7 +18579,7 @@ async function patchHermesMemoryEnv(profileName, patch) {
|
|
|
17966
18579
|
if (entries.length === 0) {
|
|
17967
18580
|
return;
|
|
17968
18581
|
}
|
|
17969
|
-
const envPath =
|
|
18582
|
+
const envPath = path20.join(resolveHermesProfileDir(profileName), ".env");
|
|
17970
18583
|
const existingRaw = await readFile14(envPath, "utf8").catch((error) => {
|
|
17971
18584
|
if (isNodeError16(error, "ENOENT")) {
|
|
17972
18585
|
return "";
|
|
@@ -18040,7 +18653,7 @@ async function readActiveMemoryProvider(profileName) {
|
|
|
18040
18653
|
return provider;
|
|
18041
18654
|
}
|
|
18042
18655
|
async function patchJsonProviderConfig(profileName, relativePath, patch) {
|
|
18043
|
-
const configPath =
|
|
18656
|
+
const configPath = path20.join(
|
|
18044
18657
|
resolveHermesProfileDir(profileName),
|
|
18045
18658
|
relativePath
|
|
18046
18659
|
);
|
|
@@ -18069,7 +18682,7 @@ async function readJsonObject(filePath) {
|
|
|
18069
18682
|
} catch {
|
|
18070
18683
|
throw new HermesMemoryError(
|
|
18071
18684
|
"memory_provider_config_invalid",
|
|
18072
|
-
`${
|
|
18685
|
+
`${path20.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
|
|
18073
18686
|
);
|
|
18074
18687
|
}
|
|
18075
18688
|
}
|
|
@@ -18615,7 +19228,7 @@ function toMemoryHttpError(error) {
|
|
|
18615
19228
|
|
|
18616
19229
|
// src/hermes/skills.ts
|
|
18617
19230
|
import { readFile as readFile15, readdir as readdir11 } from "fs/promises";
|
|
18618
|
-
import
|
|
19231
|
+
import path21 from "path";
|
|
18619
19232
|
import YAML5 from "yaml";
|
|
18620
19233
|
var HermesSkillNotFoundError = class extends Error {
|
|
18621
19234
|
constructor(skillName) {
|
|
@@ -18629,7 +19242,7 @@ var EXCLUDED_SKILL_DIRS = /* @__PURE__ */ new Set([".git", ".github", ".hub"]);
|
|
|
18629
19242
|
async function listHermesProfileSkills(profileName, paths = resolveRuntimePaths()) {
|
|
18630
19243
|
const profile = await readExistingProfile(profileName, paths);
|
|
18631
19244
|
const profileDir = resolveHermesProfileDir(profile.name);
|
|
18632
|
-
const skillsRoot =
|
|
19245
|
+
const skillsRoot = path21.join(profileDir, "skills");
|
|
18633
19246
|
const [skillFiles, disabled, provenance] = await Promise.all([
|
|
18634
19247
|
findSkillFiles(skillsRoot),
|
|
18635
19248
|
readDisabledSkillNames(resolveHermesConfigPath(profile.name)),
|
|
@@ -18720,7 +19333,7 @@ async function collectSkillFiles(directory, results) {
|
|
|
18720
19333
|
if (EXCLUDED_SKILL_DIRS.has(entry.name)) {
|
|
18721
19334
|
continue;
|
|
18722
19335
|
}
|
|
18723
|
-
const entryPath =
|
|
19336
|
+
const entryPath = path21.join(directory, entry.name);
|
|
18724
19337
|
if (entry.isDirectory()) {
|
|
18725
19338
|
await collectSkillFiles(entryPath, results);
|
|
18726
19339
|
continue;
|
|
@@ -18742,10 +19355,10 @@ async function readSkillMetadata(input) {
|
|
|
18742
19355
|
if (raw === null) {
|
|
18743
19356
|
return null;
|
|
18744
19357
|
}
|
|
18745
|
-
const skillDir =
|
|
19358
|
+
const skillDir = path21.dirname(input.skillFile);
|
|
18746
19359
|
const { frontmatter, body } = parseSkillDocument(raw.slice(0, 4e3));
|
|
18747
19360
|
const name = normalizeSkillName(
|
|
18748
|
-
readString16(frontmatter.name) ??
|
|
19361
|
+
readString16(frontmatter.name) ?? path21.basename(skillDir)
|
|
18749
19362
|
);
|
|
18750
19363
|
if (!name) {
|
|
18751
19364
|
return null;
|
|
@@ -18764,7 +19377,7 @@ async function readSkillMetadata(input) {
|
|
|
18764
19377
|
enabled: !input.disabled.has(name),
|
|
18765
19378
|
source: provenance.source,
|
|
18766
19379
|
trust: provenance.trust,
|
|
18767
|
-
relativePath:
|
|
19380
|
+
relativePath: path21.relative(input.skillsRoot, skillDir)
|
|
18768
19381
|
};
|
|
18769
19382
|
}
|
|
18770
19383
|
function parseSkillDocument(raw) {
|
|
@@ -18785,8 +19398,8 @@ function parseSkillDocument(raw) {
|
|
|
18785
19398
|
}
|
|
18786
19399
|
}
|
|
18787
19400
|
function categoryFromPath(skillsRoot, skillFile) {
|
|
18788
|
-
const relative =
|
|
18789
|
-
const parts = relative.split(
|
|
19401
|
+
const relative = path21.relative(skillsRoot, skillFile);
|
|
19402
|
+
const parts = relative.split(path21.sep).filter(Boolean);
|
|
18790
19403
|
return parts.length >= 3 ? parts[0] : null;
|
|
18791
19404
|
}
|
|
18792
19405
|
function firstBodyDescription(body) {
|
|
@@ -18833,7 +19446,7 @@ async function readSkillProvenance(root) {
|
|
|
18833
19446
|
return provenance;
|
|
18834
19447
|
}
|
|
18835
19448
|
async function readBundledSkillNames(root) {
|
|
18836
|
-
const raw = await readFile15(
|
|
19449
|
+
const raw = await readFile15(path21.join(root, ".bundled_manifest"), "utf8").catch(
|
|
18837
19450
|
(error) => {
|
|
18838
19451
|
if (isNodeError17(error, "ENOENT")) {
|
|
18839
19452
|
return "";
|
|
@@ -18856,7 +19469,7 @@ async function readBundledSkillNames(root) {
|
|
|
18856
19469
|
return names;
|
|
18857
19470
|
}
|
|
18858
19471
|
async function readHubInstalledSkills(root) {
|
|
18859
|
-
const raw = await readFile15(
|
|
19472
|
+
const raw = await readFile15(path21.join(root, ".hub", "lock.json"), "utf8").catch(
|
|
18860
19473
|
(error) => {
|
|
18861
19474
|
if (isNodeError17(error, "ENOENT")) {
|
|
18862
19475
|
return "";
|
|
@@ -19459,8 +20072,8 @@ function readModelList(payload) {
|
|
|
19459
20072
|
// src/hermes/updates.ts
|
|
19460
20073
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
19461
20074
|
import { spawn as spawn3 } from "child_process";
|
|
19462
|
-
import { mkdir as
|
|
19463
|
-
import
|
|
20075
|
+
import { mkdir as mkdir11, readFile as readFile16, rm as rm8 } from "fs/promises";
|
|
20076
|
+
import path22 from "path";
|
|
19464
20077
|
var SERVER_HERMES_RELEASES_LATEST_PATH = "/api/v1/hermes-agent/releases/latest";
|
|
19465
20078
|
var RELEASE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
19466
20079
|
var RELEASE_FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -19523,7 +20136,7 @@ async function startHermesUpdate(options) {
|
|
|
19523
20136
|
signal: null,
|
|
19524
20137
|
error: null
|
|
19525
20138
|
};
|
|
19526
|
-
await
|
|
20139
|
+
await mkdir11(options.paths.runDir, { recursive: true, mode: 448 });
|
|
19527
20140
|
await writer.write(`
|
|
19528
20141
|
=== hermes update started ${startedAt} ===
|
|
19529
20142
|
`);
|
|
@@ -19729,13 +20342,13 @@ async function readUpdateLogLines(paths) {
|
|
|
19729
20342
|
);
|
|
19730
20343
|
}
|
|
19731
20344
|
function releaseCachePath(paths) {
|
|
19732
|
-
return
|
|
20345
|
+
return path22.join(paths.indexesDir, "hermes-release-check.json");
|
|
19733
20346
|
}
|
|
19734
20347
|
function updateStatePath(paths) {
|
|
19735
|
-
return
|
|
20348
|
+
return path22.join(paths.runDir, "hermes-update-state.json");
|
|
19736
20349
|
}
|
|
19737
20350
|
function updateLogPath(paths) {
|
|
19738
|
-
return
|
|
20351
|
+
return path22.join(paths.logsDir, UPDATE_LOG_FILE);
|
|
19739
20352
|
}
|
|
19740
20353
|
async function clearUpdateLogFiles(paths) {
|
|
19741
20354
|
const primary = updateLogPath(paths);
|
|
@@ -19827,17 +20440,17 @@ function readString17(payload, key) {
|
|
|
19827
20440
|
// src/link/updates.ts
|
|
19828
20441
|
import { spawn as spawn5 } from "child_process";
|
|
19829
20442
|
import { EventEmitter as EventEmitter4 } from "events";
|
|
19830
|
-
import { mkdir as
|
|
19831
|
-
import
|
|
20443
|
+
import { mkdir as mkdir14, readFile as readFile18, rm as rm11 } from "fs/promises";
|
|
20444
|
+
import path24 from "path";
|
|
19832
20445
|
|
|
19833
20446
|
// src/daemon/process.ts
|
|
19834
20447
|
import { spawn as spawn4 } from "child_process";
|
|
19835
|
-
import { mkdir as
|
|
19836
|
-
import
|
|
20448
|
+
import { mkdir as mkdir13, readFile as readFile17, rm as rm10 } from "fs/promises";
|
|
20449
|
+
import path23 from "path";
|
|
19837
20450
|
|
|
19838
20451
|
// src/daemon/service.ts
|
|
19839
20452
|
import { createServer } from "http";
|
|
19840
|
-
import { mkdir as
|
|
20453
|
+
import { mkdir as mkdir12, rm as rm9, writeFile as writeFile3 } from "fs/promises";
|
|
19841
20454
|
|
|
19842
20455
|
// src/relay/control-client.ts
|
|
19843
20456
|
import WebSocket from "ws";
|
|
@@ -20716,6 +21329,7 @@ function startHermesSessionSyncScheduler(options) {
|
|
|
20716
21329
|
}
|
|
20717
21330
|
|
|
20718
21331
|
// src/daemon/service.ts
|
|
21332
|
+
var DEFAULT_RELAY_READY_TIMEOUT_MS = 2e3;
|
|
20719
21333
|
async function startLinkService(options = {}) {
|
|
20720
21334
|
const paths = options.paths ?? resolveRuntimePaths();
|
|
20721
21335
|
const logger = createFileLogger({ paths });
|
|
@@ -20781,6 +21395,10 @@ async function startLinkService(options = {}) {
|
|
|
20781
21395
|
});
|
|
20782
21396
|
let relay = null;
|
|
20783
21397
|
if (identity?.link_id) {
|
|
21398
|
+
let resolveRelayReady = null;
|
|
21399
|
+
const relayReady = new Promise((resolve) => {
|
|
21400
|
+
resolveRelayReady = resolve;
|
|
21401
|
+
});
|
|
20784
21402
|
relay = connectRelayControl({
|
|
20785
21403
|
relayBaseUrl: config.relayBaseUrl,
|
|
20786
21404
|
linkId: identity.link_id,
|
|
@@ -20790,8 +21408,22 @@ async function startLinkService(options = {}) {
|
|
|
20790
21408
|
backoffMaxMs: 3e4,
|
|
20791
21409
|
onStatus: (status) => {
|
|
20792
21410
|
void logger.info("relay_status", status);
|
|
21411
|
+
if (status.state === "connected") {
|
|
21412
|
+
resolveRelayReady?.(true);
|
|
21413
|
+
resolveRelayReady = null;
|
|
21414
|
+
} else if (status.state === "failed") {
|
|
21415
|
+
resolveRelayReady?.(false);
|
|
21416
|
+
resolveRelayReady = null;
|
|
21417
|
+
}
|
|
20793
21418
|
}
|
|
20794
21419
|
});
|
|
21420
|
+
if (options.waitForRelayReady) {
|
|
21421
|
+
await Promise.race([
|
|
21422
|
+
relayReady,
|
|
21423
|
+
waitForRelayReadyTimeout(options.relayReadyTimeoutMs)
|
|
21424
|
+
]);
|
|
21425
|
+
resolveRelayReady = null;
|
|
21426
|
+
}
|
|
20795
21427
|
} else {
|
|
20796
21428
|
void logger.info("relay_skipped", { reason: "link_not_paired" });
|
|
20797
21429
|
}
|
|
@@ -20826,11 +21458,20 @@ async function startLinkService(options = {}) {
|
|
|
20826
21458
|
}
|
|
20827
21459
|
};
|
|
20828
21460
|
}
|
|
21461
|
+
function waitForRelayReadyTimeout(timeoutMs) {
|
|
21462
|
+
return new Promise((resolve) => {
|
|
21463
|
+
const timer = setTimeout(
|
|
21464
|
+
() => resolve(false),
|
|
21465
|
+
timeoutMs ?? DEFAULT_RELAY_READY_TIMEOUT_MS
|
|
21466
|
+
);
|
|
21467
|
+
timer.unref?.();
|
|
21468
|
+
});
|
|
21469
|
+
}
|
|
20829
21470
|
function pidFilePath(paths = resolveRuntimePaths()) {
|
|
20830
21471
|
return `${paths.runDir}/hermeslink.pid`;
|
|
20831
21472
|
}
|
|
20832
21473
|
async function writePidFile(paths) {
|
|
20833
|
-
await
|
|
21474
|
+
await mkdir12(paths.runDir, { recursive: true, mode: 448 });
|
|
20834
21475
|
await writeFile3(pidFilePath(paths), `${process.pid}
|
|
20835
21476
|
`, { mode: 384 });
|
|
20836
21477
|
}
|
|
@@ -20905,8 +21546,8 @@ async function startDaemonProcess(paths = resolveRuntimePaths()) {
|
|
|
20905
21546
|
return status;
|
|
20906
21547
|
}
|
|
20907
21548
|
}
|
|
20908
|
-
await
|
|
20909
|
-
await
|
|
21549
|
+
await mkdir13(paths.logsDir, { recursive: true, mode: 448 });
|
|
21550
|
+
await mkdir13(paths.runDir, { recursive: true, mode: 448 });
|
|
20910
21551
|
const scriptPath = currentCliScriptPath();
|
|
20911
21552
|
const child = spawn4(process.execPath, [scriptPath, "daemon-supervisor"], {
|
|
20912
21553
|
detached: true,
|
|
@@ -20924,10 +21565,10 @@ async function startDaemonProcess(paths = resolveRuntimePaths()) {
|
|
|
20924
21565
|
return await getDaemonStatus(paths);
|
|
20925
21566
|
}
|
|
20926
21567
|
async function runDaemonSupervisor(paths = resolveRuntimePaths()) {
|
|
20927
|
-
await
|
|
21568
|
+
await mkdir13(paths.logsDir, { recursive: true, mode: 448 });
|
|
20928
21569
|
const log = createRotatingTextLogWriter({
|
|
20929
21570
|
paths,
|
|
20930
|
-
fileName:
|
|
21571
|
+
fileName: path23.basename(daemonLogFile(paths))
|
|
20931
21572
|
});
|
|
20932
21573
|
const scriptPath = currentCliScriptPath();
|
|
20933
21574
|
const child = spawn4(process.execPath, [scriptPath, "daemon", "--foreground"], {
|
|
@@ -21168,7 +21809,7 @@ async function startLinkUpdate(options) {
|
|
|
21168
21809
|
error: null,
|
|
21169
21810
|
manual_command: manualCommand
|
|
21170
21811
|
};
|
|
21171
|
-
await
|
|
21812
|
+
await mkdir14(options.paths.runDir, { recursive: true, mode: 448 });
|
|
21172
21813
|
await writer.write(
|
|
21173
21814
|
`
|
|
21174
21815
|
=== link update started ${startedAt} target=${targetVersion} ===
|
|
@@ -21466,10 +22107,10 @@ async function readUpdateLogLines2(paths) {
|
|
|
21466
22107
|
);
|
|
21467
22108
|
}
|
|
21468
22109
|
function updateStatePath2(paths) {
|
|
21469
|
-
return
|
|
22110
|
+
return path24.join(paths.runDir, "link-update-state.json");
|
|
21470
22111
|
}
|
|
21471
22112
|
function updateLogPath2(paths) {
|
|
21472
|
-
return
|
|
22113
|
+
return path24.join(paths.logsDir, UPDATE_LOG_FILE2);
|
|
21473
22114
|
}
|
|
21474
22115
|
async function clearUpdateLogFiles2(paths) {
|
|
21475
22116
|
const primary = updateLogPath2(paths);
|
|
@@ -21533,7 +22174,7 @@ function readString18(payload, key) {
|
|
|
21533
22174
|
}
|
|
21534
22175
|
|
|
21535
22176
|
// src/pairing/pairing.ts
|
|
21536
|
-
import
|
|
22177
|
+
import path25 from "path";
|
|
21537
22178
|
import { rm as rm12 } from "fs/promises";
|
|
21538
22179
|
|
|
21539
22180
|
// src/relay/bootstrap.ts
|
|
@@ -21856,10 +22497,10 @@ async function loadRequiredIdentity2(paths) {
|
|
|
21856
22497
|
}
|
|
21857
22498
|
return identity;
|
|
21858
22499
|
}
|
|
21859
|
-
async function postServerJson(serverBaseUrl,
|
|
22500
|
+
async function postServerJson(serverBaseUrl, path26, body, options) {
|
|
21860
22501
|
let response;
|
|
21861
22502
|
try {
|
|
21862
|
-
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${
|
|
22503
|
+
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path26}`, {
|
|
21863
22504
|
method: "POST",
|
|
21864
22505
|
headers: {
|
|
21865
22506
|
accept: "application/json",
|
|
@@ -21907,10 +22548,10 @@ function pairingErrorSnapshot(stage, error) {
|
|
|
21907
22548
|
occurred_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
21908
22549
|
};
|
|
21909
22550
|
}
|
|
21910
|
-
async function patchServerJson(serverBaseUrl,
|
|
22551
|
+
async function patchServerJson(serverBaseUrl, path26, token, body, options) {
|
|
21911
22552
|
let response;
|
|
21912
22553
|
try {
|
|
21913
|
-
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${
|
|
22554
|
+
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path26}`, {
|
|
21914
22555
|
method: "PATCH",
|
|
21915
22556
|
headers: {
|
|
21916
22557
|
accept: "application/json",
|
|
@@ -21958,10 +22599,10 @@ function createPairingNetworkError(input) {
|
|
|
21958
22599
|
);
|
|
21959
22600
|
}
|
|
21960
22601
|
function pairingClaimPath(sessionId, paths) {
|
|
21961
|
-
return
|
|
22602
|
+
return path25.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
|
|
21962
22603
|
}
|
|
21963
22604
|
function pairingSessionPath(sessionId, paths) {
|
|
21964
|
-
return
|
|
22605
|
+
return path25.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
|
|
21965
22606
|
}
|
|
21966
22607
|
function qrPreferredUrls(routes) {
|
|
21967
22608
|
return routes.preferredUrls.filter((url) => !url.includes("/api/v1/relay/links/")).slice(0, 1);
|
|
@@ -22015,6 +22656,7 @@ function registerSystemRoutes(router, options) {
|
|
|
22015
22656
|
conversation_events: true,
|
|
22016
22657
|
conversation_delete: true,
|
|
22017
22658
|
conversation_bulk_delete: true,
|
|
22659
|
+
conversation_clear_plan: true,
|
|
22018
22660
|
conversation_cancel: true,
|
|
22019
22661
|
conversation_rename: true,
|
|
22020
22662
|
blobs: true,
|
|
@@ -22608,12 +23250,16 @@ function registerPairingRoutes(router, options) {
|
|
|
22608
23250
|
if (!session) {
|
|
22609
23251
|
throw new LinkHttpError(404, "pairing_session_not_found", "Pairing session was not found");
|
|
22610
23252
|
}
|
|
23253
|
+
const state = await readPairingState(sessionId, paths);
|
|
23254
|
+
if (!state.claimed && isExpiredAt(session.expires_at)) {
|
|
23255
|
+
throw new LinkHttpError(404, "pairing_session_expired", "Pairing session has expired");
|
|
23256
|
+
}
|
|
22611
23257
|
ctx.set("cache-control", "no-store");
|
|
22612
23258
|
ctx.body = {
|
|
22613
23259
|
ok: true,
|
|
22614
23260
|
session: {
|
|
22615
23261
|
...session,
|
|
22616
|
-
claimed:
|
|
23262
|
+
claimed: state.claimed
|
|
22617
23263
|
}
|
|
22618
23264
|
};
|
|
22619
23265
|
});
|
|
@@ -22629,6 +23275,8 @@ async function readPairingState(sessionId, paths) {
|
|
|
22629
23275
|
}
|
|
22630
23276
|
async function renderPairingPage(input) {
|
|
22631
23277
|
const session = input.session;
|
|
23278
|
+
const expiresAtMs = Date.parse(session.expires_at);
|
|
23279
|
+
const isExpired = !input.state.claimed && Number.isFinite(expiresAtMs) && Date.now() >= expiresAtMs;
|
|
22632
23280
|
const qrPayload = JSON.stringify({
|
|
22633
23281
|
kind: "hermes_link_pairing",
|
|
22634
23282
|
version: 1,
|
|
@@ -22648,8 +23296,9 @@ async function renderPairingPage(input) {
|
|
|
22648
23296
|
const currentUrl = session.local_api_url.replace(/\/+$/u, "");
|
|
22649
23297
|
const linkIdLabel = escapeHtml(input.linkId ?? session.link_id);
|
|
22650
23298
|
const expiresLabel = escapeHtml(formatDate(session.expires_at));
|
|
22651
|
-
const statusLabel = input.state.claimed ? "\u5DF2\u5B8C\u6210\u914D\u5BF9" : "\u7B49\u5F85 App \u626B\u7801";
|
|
22652
|
-
const statusHint = input.state.claimed ? "App \u5DF2\u5B8C\u6210\u914D\u5BF9\uFF0C\u8FD9\u4E2A\u9875\u9762\u53EF\u4EE5\u5173\u95ED\u3002" : "\u6253\u5F00 App \u626B\u7801\uFF0C\u6216\u8005\u590D\u5236\u914D\u5BF9\u7801\u624B\u52A8\u8F93\u5165\u3002";
|
|
23299
|
+
const statusLabel = input.state.claimed ? "\u5DF2\u5B8C\u6210\u914D\u5BF9" : isExpired ? "\u914D\u5BF9\u5DF2\u8FC7\u671F" : "\u7B49\u5F85 App \u626B\u7801";
|
|
23300
|
+
const statusHint = input.state.claimed ? "App \u5DF2\u5B8C\u6210\u914D\u5BF9\uFF0C\u8FD9\u4E2A\u9875\u9762\u53EF\u4EE5\u5173\u95ED\u3002" : isExpired ? "\u8FD9\u6B21\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C hermeslink pair\u3002" : "\u6253\u5F00 App \u626B\u7801\uFF0C\u6216\u8005\u590D\u5236\u914D\u5BF9\u7801\u624B\u52A8\u8F93\u5165\u3002";
|
|
23301
|
+
const statusPillLabel = input.state.claimed ? "\u5DF2\u626B\u7801" : isExpired ? "\u5DF2\u8FC7\u671F" : "\u7B49\u5F85\u4E2D";
|
|
22653
23302
|
return `<!doctype html>
|
|
22654
23303
|
<html lang="zh-CN">
|
|
22655
23304
|
<head>
|
|
@@ -22954,7 +23603,7 @@ async function renderPairingPage(input) {
|
|
|
22954
23603
|
<div class="card">
|
|
22955
23604
|
<div class="status">
|
|
22956
23605
|
<h2 class="status-title" id="statusTitle">${escapeHtml(statusLabel)}</h2>
|
|
22957
|
-
<span class="pill" id="statusPill">${
|
|
23606
|
+
<span class="pill" id="statusPill">${escapeHtml(statusPillLabel)}</span>
|
|
22958
23607
|
</div>
|
|
22959
23608
|
<div class="qr-frame">
|
|
22960
23609
|
<img src="${qrDataUri}" alt="Hermes Link pairing QR code" />
|
|
@@ -22971,22 +23620,62 @@ async function renderPairingPage(input) {
|
|
|
22971
23620
|
</main>
|
|
22972
23621
|
<script>
|
|
22973
23622
|
const sessionId = ${JSON.stringify(session.session_id)};
|
|
23623
|
+
const expiresAtMs = ${Number.isFinite(expiresAtMs) ? String(expiresAtMs) : "Number.NaN"};
|
|
23624
|
+
const initialClaimed = ${JSON.stringify(input.state.claimed)};
|
|
23625
|
+
const statusTitle = document.querySelector('#statusTitle');
|
|
23626
|
+
const statusPill = document.querySelector('#statusPill');
|
|
23627
|
+
const statusHint = document.querySelector('#statusHint');
|
|
23628
|
+
let refreshTimer = null;
|
|
23629
|
+
|
|
23630
|
+
const stopPolling = () => {
|
|
23631
|
+
if (refreshTimer !== null) {
|
|
23632
|
+
clearInterval(refreshTimer);
|
|
23633
|
+
refreshTimer = null;
|
|
23634
|
+
}
|
|
23635
|
+
};
|
|
23636
|
+
|
|
23637
|
+
const markClaimed = () => {
|
|
23638
|
+
statusTitle.textContent = '\u5DF2\u5B8C\u6210\u914D\u5BF9';
|
|
23639
|
+
statusPill.textContent = '\u5DF2\u626B\u7801';
|
|
23640
|
+
statusHint.textContent = 'App \u5DF2\u5B8C\u6210\u914D\u5BF9\uFF0C\u8FD9\u4E2A\u9875\u9762\u53EF\u4EE5\u5173\u95ED\u3002';
|
|
23641
|
+
stopPolling();
|
|
23642
|
+
};
|
|
23643
|
+
|
|
23644
|
+
const markExpired = () => {
|
|
23645
|
+
statusTitle.textContent = '\u914D\u5BF9\u5DF2\u8FC7\u671F';
|
|
23646
|
+
statusPill.textContent = '\u5DF2\u8FC7\u671F';
|
|
23647
|
+
statusHint.textContent = '\u8FD9\u6B21\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C hermeslink pair\u3002';
|
|
23648
|
+
stopPolling();
|
|
23649
|
+
};
|
|
23650
|
+
|
|
22974
23651
|
const refresh = async () => {
|
|
23652
|
+
if (Number.isFinite(expiresAtMs) && Date.now() >= expiresAtMs) {
|
|
23653
|
+
markExpired();
|
|
23654
|
+
return;
|
|
23655
|
+
}
|
|
22975
23656
|
try {
|
|
22976
23657
|
const response = await fetch('/api/v1/pairing/session?session_id=' + encodeURIComponent(sessionId), {
|
|
22977
23658
|
headers: { accept: 'application/json' },
|
|
22978
23659
|
});
|
|
23660
|
+
if (response.status === 404) {
|
|
23661
|
+
markExpired();
|
|
23662
|
+
return;
|
|
23663
|
+
}
|
|
22979
23664
|
if (!response.ok) return;
|
|
22980
23665
|
const payload = await response.json();
|
|
22981
23666
|
if (payload?.session?.claimed) {
|
|
22982
|
-
|
|
22983
|
-
document.querySelector('#statusTitle').textContent = '\u5DF2\u5B8C\u6210\u914D\u5BF9';
|
|
22984
|
-
document.querySelector('#statusHint').textContent = 'App \u5DF2\u5B8C\u6210\u914D\u5BF9\uFF0C\u8FD9\u4E2A\u9875\u9762\u53EF\u4EE5\u5173\u95ED\u3002';
|
|
23667
|
+
markClaimed();
|
|
22985
23668
|
}
|
|
22986
23669
|
} catch (_) {}
|
|
22987
23670
|
};
|
|
22988
|
-
|
|
22989
|
-
|
|
23671
|
+
if (initialClaimed) {
|
|
23672
|
+
stopPolling();
|
|
23673
|
+
} else if (Number.isFinite(expiresAtMs) && Date.now() >= expiresAtMs) {
|
|
23674
|
+
markExpired();
|
|
23675
|
+
} else {
|
|
23676
|
+
refreshTimer = window.setInterval(refresh, 1500);
|
|
23677
|
+
refresh();
|
|
23678
|
+
}
|
|
22990
23679
|
</script>
|
|
22991
23680
|
</body>
|
|
22992
23681
|
</html>`;
|
|
@@ -22998,6 +23687,10 @@ function formatDate(value) {
|
|
|
22998
23687
|
const date = new Date(value);
|
|
22999
23688
|
return Number.isNaN(date.getTime()) ? value : date.toLocaleString("zh-CN", { hour12: false });
|
|
23000
23689
|
}
|
|
23690
|
+
function isExpiredAt(value) {
|
|
23691
|
+
const expiresAtMs = Date.parse(value);
|
|
23692
|
+
return Number.isFinite(expiresAtMs) && Date.now() >= expiresAtMs;
|
|
23693
|
+
}
|
|
23001
23694
|
|
|
23002
23695
|
// src/http/routes/internal.ts
|
|
23003
23696
|
function registerInternalRoutes(router, options) {
|
|
@@ -23106,6 +23799,7 @@ export {
|
|
|
23106
23799
|
readPairingClaim,
|
|
23107
23800
|
clearPairingClaim,
|
|
23108
23801
|
createApp,
|
|
23802
|
+
connectRelayControl,
|
|
23109
23803
|
reportLinkStatusToServer,
|
|
23110
23804
|
startLinkService,
|
|
23111
23805
|
startDaemonProcess,
|