@hermespilot/link 0.4.9 → 0.5.1
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-XQNMUEOZ.js → chunk-PULX22HX.js} +1118 -420
- package/dist/cli/index.js +1 -1
- package/dist/http/app.d.ts +53 -1
- package/dist/http/app.js +1 -1
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import Router from "@koa/router";
|
|
|
4
4
|
|
|
5
5
|
// src/conversations/conversation-service.ts
|
|
6
6
|
import { EventEmitter } from "events";
|
|
7
|
-
import { randomUUID as
|
|
7
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
8
8
|
|
|
9
9
|
// src/database/link-database.ts
|
|
10
10
|
import { mkdir } from "fs/promises";
|
|
@@ -150,7 +150,10 @@ var MIGRATIONS = [
|
|
|
150
150
|
}
|
|
151
151
|
];
|
|
152
152
|
async function migrateLinkDatabase(paths) {
|
|
153
|
-
await mkdir(path.dirname(paths.databaseFile), {
|
|
153
|
+
await mkdir(path.dirname(paths.databaseFile), {
|
|
154
|
+
recursive: true,
|
|
155
|
+
mode: 448
|
|
156
|
+
});
|
|
154
157
|
const db = openDatabase(paths);
|
|
155
158
|
const appliedVersions = [];
|
|
156
159
|
try {
|
|
@@ -169,11 +172,9 @@ async function migrateLinkDatabase(paths) {
|
|
|
169
172
|
db.exec("BEGIN IMMEDIATE");
|
|
170
173
|
try {
|
|
171
174
|
db.exec(migration.sql);
|
|
172
|
-
db.prepare(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
(/* @__PURE__ */ new Date()).toISOString()
|
|
176
|
-
);
|
|
175
|
+
db.prepare(
|
|
176
|
+
"INSERT INTO schema_migrations (version, name, applied_at) VALUES (?, ?, ?)"
|
|
177
|
+
).run(migration.version, migration.name, (/* @__PURE__ */ new Date()).toISOString());
|
|
177
178
|
db.exec("COMMIT");
|
|
178
179
|
applied.add(migration.version);
|
|
179
180
|
appliedVersions.push(migration.version);
|
|
@@ -217,7 +218,9 @@ async function upsertConversationStats(paths, record) {
|
|
|
217
218
|
await migrateLinkDatabase(paths);
|
|
218
219
|
const db = openDatabase(paths);
|
|
219
220
|
try {
|
|
220
|
-
db.prepare(conversationStatsUpsertSql()).run(
|
|
221
|
+
db.prepare(conversationStatsUpsertSql()).run(
|
|
222
|
+
...conversationStatsParams(record)
|
|
223
|
+
);
|
|
221
224
|
} finally {
|
|
222
225
|
db.close();
|
|
223
226
|
}
|
|
@@ -226,10 +229,11 @@ async function listConversationStatsPage(paths, input) {
|
|
|
226
229
|
await migrateLinkDatabase(paths);
|
|
227
230
|
const rawLimit = Number.isFinite(input.limit) ? Math.trunc(input.limit) : 25;
|
|
228
231
|
const limit = Math.max(1, Math.min(100, rawLimit));
|
|
232
|
+
const status = input.status ?? "active";
|
|
229
233
|
const db = openDatabase(paths);
|
|
230
234
|
try {
|
|
231
235
|
const conditions = ["status = ?"];
|
|
232
|
-
const params = [
|
|
236
|
+
const params = [status];
|
|
233
237
|
if (input.cursor) {
|
|
234
238
|
conditions.push(`(
|
|
235
239
|
updated_at < ?
|
|
@@ -241,13 +245,15 @@ async function listConversationStatsPage(paths, input) {
|
|
|
241
245
|
input.cursor.conversationId
|
|
242
246
|
);
|
|
243
247
|
}
|
|
244
|
-
const rows = db.prepare(
|
|
248
|
+
const rows = db.prepare(
|
|
249
|
+
`
|
|
245
250
|
SELECT conversation_id, updated_at
|
|
246
251
|
FROM conversation_stats
|
|
247
252
|
WHERE ${conditions.join(" AND ")}
|
|
248
253
|
ORDER BY updated_at DESC, conversation_id DESC
|
|
249
254
|
LIMIT ?
|
|
250
|
-
`
|
|
255
|
+
`
|
|
256
|
+
).all(...params, limit + 1);
|
|
251
257
|
const records = rows.slice(0, limit).map((row) => ({
|
|
252
258
|
conversationId: readString(row, "conversation_id") ?? "",
|
|
253
259
|
updatedAt: readString(row, "updated_at") ?? ""
|
|
@@ -264,18 +270,20 @@ async function searchConversationStatsPage(paths, input) {
|
|
|
264
270
|
await migrateLinkDatabase(paths);
|
|
265
271
|
const rawLimit = Number.isFinite(input.limit) ? Math.trunc(input.limit) : 25;
|
|
266
272
|
const limit = Math.max(1, Math.min(100, rawLimit));
|
|
273
|
+
const status = input.status ?? "active";
|
|
267
274
|
const query = input.query.trim();
|
|
268
275
|
if (!query) {
|
|
269
276
|
return listConversationStatsPage(paths, {
|
|
270
277
|
limit,
|
|
271
|
-
cursor: input.cursor
|
|
278
|
+
cursor: input.cursor,
|
|
279
|
+
status
|
|
272
280
|
});
|
|
273
281
|
}
|
|
274
282
|
const db = openDatabase(paths);
|
|
275
283
|
try {
|
|
276
284
|
const conditions = ["status = ?", "LOWER(title) LIKE ? ESCAPE '\\'"];
|
|
277
285
|
const params = [
|
|
278
|
-
|
|
286
|
+
status,
|
|
279
287
|
`%${escapeSqlLike(query.toLowerCase())}%`
|
|
280
288
|
];
|
|
281
289
|
if (input.cursor) {
|
|
@@ -289,13 +297,15 @@ async function searchConversationStatsPage(paths, input) {
|
|
|
289
297
|
input.cursor.conversationId
|
|
290
298
|
);
|
|
291
299
|
}
|
|
292
|
-
const rows = db.prepare(
|
|
300
|
+
const rows = db.prepare(
|
|
301
|
+
`
|
|
293
302
|
SELECT conversation_id, updated_at
|
|
294
303
|
FROM conversation_stats
|
|
295
304
|
WHERE ${conditions.join(" AND ")}
|
|
296
305
|
ORDER BY updated_at DESC, conversation_id DESC
|
|
297
306
|
LIMIT ?
|
|
298
|
-
`
|
|
307
|
+
`
|
|
308
|
+
).all(...params, limit + 1);
|
|
299
309
|
const records = rows.slice(0, limit).map((row) => ({
|
|
300
310
|
conversationId: readString(row, "conversation_id") ?? "",
|
|
301
311
|
updatedAt: readString(row, "updated_at") ?? ""
|
|
@@ -336,11 +346,13 @@ async function readLinkStatistics(paths, filter = {}) {
|
|
|
336
346
|
try {
|
|
337
347
|
const conversationWhere = statisticsWhereClause(filter);
|
|
338
348
|
const usageWhere = runUsageWhereClause(filter);
|
|
339
|
-
const conversationRow = db.prepare(
|
|
349
|
+
const conversationRow = db.prepare(
|
|
350
|
+
`
|
|
340
351
|
SELECT
|
|
341
352
|
COUNT(*) AS total_conversations,
|
|
342
353
|
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) AS active_conversations,
|
|
343
|
-
SUM(CASE WHEN status
|
|
354
|
+
SUM(CASE WHEN status = 'archived' THEN 1 ELSE 0 END) AS archived_conversations,
|
|
355
|
+
SUM(CASE WHEN status = 'deleted_soft' THEN 1 ELSE 0 END) AS deleted_conversations,
|
|
344
356
|
COUNT(DISTINCT CASE
|
|
345
357
|
WHEN profile_uid IS NOT NULL AND profile_uid <> '' THEN profile_uid
|
|
346
358
|
WHEN profile_name_snapshot IS NOT NULL AND profile_name_snapshot <> '' THEN profile_name_snapshot
|
|
@@ -349,8 +361,10 @@ async function readLinkStatistics(paths, filter = {}) {
|
|
|
349
361
|
MAX(stats_updated_at) AS updated_at
|
|
350
362
|
FROM conversation_stats
|
|
351
363
|
${conversationWhere.sql}
|
|
352
|
-
`
|
|
353
|
-
|
|
364
|
+
`
|
|
365
|
+
).get(...conversationWhere.params);
|
|
366
|
+
const usageRow = db.prepare(
|
|
367
|
+
`
|
|
354
368
|
SELECT
|
|
355
369
|
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
|
356
370
|
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
|
@@ -366,12 +380,14 @@ async function readLinkStatistics(paths, filter = {}) {
|
|
|
366
380
|
MAX(updated_at) AS updated_at
|
|
367
381
|
FROM run_usage_facts
|
|
368
382
|
${usageWhere.sql}
|
|
369
|
-
`
|
|
383
|
+
`
|
|
384
|
+
).get(...usageWhere.params);
|
|
370
385
|
const updatedAt = readString(usageRow, "updated_at") ?? readString(conversationRow, "updated_at");
|
|
371
386
|
return {
|
|
372
387
|
conversations: {
|
|
373
388
|
total: readNumber(conversationRow, "total_conversations"),
|
|
374
389
|
active: readNumber(conversationRow, "active_conversations"),
|
|
390
|
+
archived: readNumber(conversationRow, "archived_conversations"),
|
|
375
391
|
deleted: readNumber(conversationRow, "deleted_conversations")
|
|
376
392
|
},
|
|
377
393
|
tokens: {
|
|
@@ -413,7 +429,8 @@ async function readLinkUsageStatistics(paths, filter = {}) {
|
|
|
413
429
|
from: range.fromInclusive,
|
|
414
430
|
to: range.toExclusive
|
|
415
431
|
});
|
|
416
|
-
const totalsRow = db.prepare(
|
|
432
|
+
const totalsRow = db.prepare(
|
|
433
|
+
`
|
|
417
434
|
SELECT
|
|
418
435
|
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
|
419
436
|
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
|
@@ -425,8 +442,10 @@ async function readLinkUsageStatistics(paths, filter = {}) {
|
|
|
425
442
|
MAX(updated_at) AS updated_at
|
|
426
443
|
FROM run_usage_facts
|
|
427
444
|
${where.sql}
|
|
428
|
-
`
|
|
429
|
-
|
|
445
|
+
`
|
|
446
|
+
).get(...where.params);
|
|
447
|
+
const dailyRows = db.prepare(
|
|
448
|
+
`
|
|
430
449
|
SELECT
|
|
431
450
|
substr(completed_at, 1, 10) AS date,
|
|
432
451
|
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
|
@@ -438,8 +457,10 @@ async function readLinkUsageStatistics(paths, filter = {}) {
|
|
|
438
457
|
${where.sql}
|
|
439
458
|
GROUP BY substr(completed_at, 1, 10)
|
|
440
459
|
ORDER BY date ASC
|
|
441
|
-
`
|
|
442
|
-
|
|
460
|
+
`
|
|
461
|
+
).all(...where.params);
|
|
462
|
+
const modelRows = db.prepare(
|
|
463
|
+
`
|
|
443
464
|
SELECT
|
|
444
465
|
COALESCE(NULLIF(model, ''), 'unknown') AS model,
|
|
445
466
|
provider,
|
|
@@ -454,8 +475,10 @@ async function readLinkUsageStatistics(paths, filter = {}) {
|
|
|
454
475
|
HAVING SUM(total_tokens) > 0
|
|
455
476
|
ORDER BY total_tokens DESC, run_count DESC, model ASC
|
|
456
477
|
LIMIT 12
|
|
457
|
-
`
|
|
458
|
-
|
|
478
|
+
`
|
|
479
|
+
).all(...where.params);
|
|
480
|
+
const profileRows = db.prepare(
|
|
481
|
+
`
|
|
459
482
|
SELECT
|
|
460
483
|
MAX(NULLIF(profile_uid, '')) AS profile_uid,
|
|
461
484
|
COALESCE(
|
|
@@ -480,7 +503,8 @@ async function readLinkUsageStatistics(paths, filter = {}) {
|
|
|
480
503
|
HAVING SUM(total_tokens) > 0
|
|
481
504
|
ORDER BY total_tokens DESC, run_count DESC, profile ASC
|
|
482
505
|
LIMIT 12
|
|
483
|
-
`
|
|
506
|
+
`
|
|
507
|
+
).all(...where.params);
|
|
484
508
|
const dailyByDate = new Map(
|
|
485
509
|
dailyRows.map((row) => [readString(row, "date") ?? "", row])
|
|
486
510
|
);
|
|
@@ -539,17 +563,21 @@ async function ensureProfileIdentity(paths, input) {
|
|
|
539
563
|
const db = openDatabase(paths);
|
|
540
564
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
541
565
|
try {
|
|
542
|
-
const existing = db.prepare(
|
|
566
|
+
const existing = db.prepare(
|
|
567
|
+
`
|
|
543
568
|
${profileRegistrySelectSql()}
|
|
544
569
|
FROM profile_registry
|
|
545
570
|
WHERE profile_name = ?
|
|
546
|
-
`
|
|
571
|
+
`
|
|
572
|
+
).get(input.profileName);
|
|
547
573
|
if (existing) {
|
|
548
|
-
db.prepare(
|
|
574
|
+
db.prepare(
|
|
575
|
+
`
|
|
549
576
|
UPDATE profile_registry
|
|
550
577
|
SET profile_path = ?, updated_at = ?
|
|
551
578
|
WHERE profile_uid = ?
|
|
552
|
-
`
|
|
579
|
+
`
|
|
580
|
+
).run(input.profilePath, now, readString(existing, "profile_uid") ?? "");
|
|
553
581
|
return profileIdentityFromRow({
|
|
554
582
|
...existing,
|
|
555
583
|
profile_path: input.profilePath,
|
|
@@ -557,7 +585,8 @@ async function ensureProfileIdentity(paths, input) {
|
|
|
557
585
|
});
|
|
558
586
|
}
|
|
559
587
|
const profileUid = `prof_${randomUUID().replaceAll("-", "")}`;
|
|
560
|
-
db.prepare(
|
|
588
|
+
db.prepare(
|
|
589
|
+
`
|
|
561
590
|
INSERT INTO profile_registry (
|
|
562
591
|
profile_uid,
|
|
563
592
|
profile_name,
|
|
@@ -565,7 +594,8 @@ async function ensureProfileIdentity(paths, input) {
|
|
|
565
594
|
created_at,
|
|
566
595
|
updated_at
|
|
567
596
|
) VALUES (?, ?, ?, ?, ?)
|
|
568
|
-
`
|
|
597
|
+
`
|
|
598
|
+
).run(profileUid, input.profileName, input.profilePath, now, now);
|
|
569
599
|
return {
|
|
570
600
|
profileUid,
|
|
571
601
|
profileName: input.profileName,
|
|
@@ -583,11 +613,13 @@ async function updateProfileMetadata(paths, input) {
|
|
|
583
613
|
const db = openDatabase(paths);
|
|
584
614
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
585
615
|
try {
|
|
586
|
-
const existing = db.prepare(
|
|
616
|
+
const existing = db.prepare(
|
|
617
|
+
`
|
|
587
618
|
${profileRegistrySelectSql()}
|
|
588
619
|
FROM profile_registry
|
|
589
620
|
WHERE profile_name = ?
|
|
590
|
-
`
|
|
621
|
+
`
|
|
622
|
+
).get(input.profileName);
|
|
591
623
|
if (!existing) {
|
|
592
624
|
throw new Error("profile identity not found");
|
|
593
625
|
}
|
|
@@ -614,16 +646,20 @@ async function updateProfileMetadata(paths, input) {
|
|
|
614
646
|
if (updates.length === 0) {
|
|
615
647
|
return profileIdentityFromRow(existing);
|
|
616
648
|
}
|
|
617
|
-
db.prepare(
|
|
649
|
+
db.prepare(
|
|
650
|
+
`
|
|
618
651
|
UPDATE profile_registry
|
|
619
652
|
SET ${updates.join(", ")}, updated_at = ?
|
|
620
653
|
WHERE profile_name = ?
|
|
621
|
-
`
|
|
622
|
-
|
|
654
|
+
`
|
|
655
|
+
).run(...params, now, input.profileName);
|
|
656
|
+
const updated = db.prepare(
|
|
657
|
+
`
|
|
623
658
|
${profileRegistrySelectSql()}
|
|
624
659
|
FROM profile_registry
|
|
625
660
|
WHERE profile_name = ?
|
|
626
|
-
`
|
|
661
|
+
`
|
|
662
|
+
).get(input.profileName);
|
|
627
663
|
return profileIdentityFromRow(updated ?? existing);
|
|
628
664
|
} finally {
|
|
629
665
|
db.close();
|
|
@@ -635,18 +671,22 @@ async function renameProfileIdentity(paths, input) {
|
|
|
635
671
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
636
672
|
let result = null;
|
|
637
673
|
try {
|
|
638
|
-
const existing = db.prepare(
|
|
674
|
+
const existing = db.prepare(
|
|
675
|
+
`
|
|
639
676
|
${profileRegistrySelectSql()}
|
|
640
677
|
FROM profile_registry
|
|
641
678
|
WHERE profile_name = ?
|
|
642
|
-
`
|
|
679
|
+
`
|
|
680
|
+
).get(input.oldProfileName);
|
|
643
681
|
if (existing) {
|
|
644
682
|
const profileUid = readString(existing, "profile_uid") ?? `prof_${randomUUID().replaceAll("-", "")}`;
|
|
645
|
-
db.prepare(
|
|
683
|
+
db.prepare(
|
|
684
|
+
`
|
|
646
685
|
UPDATE profile_registry
|
|
647
686
|
SET profile_name = ?, profile_path = ?, updated_at = ?
|
|
648
687
|
WHERE profile_uid = ?
|
|
649
|
-
`
|
|
688
|
+
`
|
|
689
|
+
).run(input.newProfileName, input.newProfilePath, now, profileUid);
|
|
650
690
|
result = {
|
|
651
691
|
profileUid,
|
|
652
692
|
profileName: input.newProfileName,
|
|
@@ -692,7 +732,8 @@ async function deleteConversationStatsForProfile(paths, input) {
|
|
|
692
732
|
const db = openDatabase(paths);
|
|
693
733
|
try {
|
|
694
734
|
if (input.profileUid?.trim()) {
|
|
695
|
-
db.prepare(
|
|
735
|
+
db.prepare(
|
|
736
|
+
`
|
|
696
737
|
DELETE FROM conversation_stats
|
|
697
738
|
WHERE profile_uid = ?
|
|
698
739
|
OR profile_name_snapshot = ?
|
|
@@ -700,17 +741,20 @@ async function deleteConversationStatsForProfile(paths, input) {
|
|
|
700
741
|
(profile_name_snapshot IS NULL OR profile_name_snapshot = '')
|
|
701
742
|
AND profile = ?
|
|
702
743
|
)
|
|
703
|
-
`
|
|
744
|
+
`
|
|
745
|
+
).run(input.profileUid.trim(), input.profileName, input.profileName);
|
|
704
746
|
return;
|
|
705
747
|
}
|
|
706
|
-
db.prepare(
|
|
748
|
+
db.prepare(
|
|
749
|
+
`
|
|
707
750
|
DELETE FROM conversation_stats
|
|
708
751
|
WHERE profile_name_snapshot = ?
|
|
709
752
|
OR (
|
|
710
753
|
(profile_name_snapshot IS NULL OR profile_name_snapshot = '')
|
|
711
754
|
AND profile = ?
|
|
712
755
|
)
|
|
713
|
-
`
|
|
756
|
+
`
|
|
757
|
+
).run(input.profileName, input.profileName);
|
|
714
758
|
} finally {
|
|
715
759
|
db.close();
|
|
716
760
|
}
|
|
@@ -728,7 +772,9 @@ function openDatabase(paths) {
|
|
|
728
772
|
}
|
|
729
773
|
function readAppliedVersions(db) {
|
|
730
774
|
const rows = db.prepare("SELECT version FROM schema_migrations").all();
|
|
731
|
-
return new Set(
|
|
775
|
+
return new Set(
|
|
776
|
+
rows.map((row) => readNumber(row, "version")).filter((value) => value > 0)
|
|
777
|
+
);
|
|
732
778
|
}
|
|
733
779
|
function ensureProfileIdentitySchema(db) {
|
|
734
780
|
db.exec(`
|
|
@@ -754,7 +800,9 @@ function ensureProfileIdentitySchema(db) {
|
|
|
754
800
|
db.exec("ALTER TABLE profile_registry ADD COLUMN description TEXT;");
|
|
755
801
|
}
|
|
756
802
|
if (!profileColumns.has("avatar_type")) {
|
|
757
|
-
db.exec(
|
|
803
|
+
db.exec(
|
|
804
|
+
"ALTER TABLE profile_registry ADD COLUMN avatar_type TEXT NOT NULL DEFAULT 'default';"
|
|
805
|
+
);
|
|
758
806
|
}
|
|
759
807
|
if (!profileColumns.has("avatar_url")) {
|
|
760
808
|
db.exec("ALTER TABLE profile_registry ADD COLUMN avatar_url TEXT;");
|
|
@@ -766,7 +814,9 @@ function ensureProfileIdentitySchema(db) {
|
|
|
766
814
|
db.exec("ALTER TABLE conversation_stats ADD COLUMN profile_uid TEXT;");
|
|
767
815
|
}
|
|
768
816
|
if (!conversationColumns.has("profile_name_snapshot")) {
|
|
769
|
-
db.exec(
|
|
817
|
+
db.exec(
|
|
818
|
+
"ALTER TABLE conversation_stats ADD COLUMN profile_name_snapshot TEXT;"
|
|
819
|
+
);
|
|
770
820
|
}
|
|
771
821
|
db.exec(`
|
|
772
822
|
CREATE INDEX IF NOT EXISTS idx_conversation_stats_profile_uid
|
|
@@ -4057,7 +4107,7 @@ async function listCronOutputFiles(profileName, jobId) {
|
|
|
4057
4107
|
mtimeMs: fileStat.mtimeMs
|
|
4058
4108
|
});
|
|
4059
4109
|
}
|
|
4060
|
-
return files.sort((left, right) => left.mtimeMs - right.mtimeMs).map(({ path:
|
|
4110
|
+
return files.sort((left, right) => left.mtimeMs - right.mtimeMs).map(({ path: path27, mtime }) => ({ path: path27, mtime }));
|
|
4061
4111
|
}
|
|
4062
4112
|
async function readCronOutput(outputPath) {
|
|
4063
4113
|
const content = await readFile3(outputPath, "utf8");
|
|
@@ -4134,7 +4184,7 @@ import os2 from "os";
|
|
|
4134
4184
|
import path5 from "path";
|
|
4135
4185
|
|
|
4136
4186
|
// src/constants.ts
|
|
4137
|
-
var LINK_VERSION = "0.
|
|
4187
|
+
var LINK_VERSION = "0.5.1";
|
|
4138
4188
|
var LINK_COMMAND = "hermeslink";
|
|
4139
4189
|
var LINK_DEFAULT_PORT = 52379;
|
|
4140
4190
|
var LINK_RUNTIME_DIR_NAME = ".hermeslink";
|
|
@@ -7387,12 +7437,12 @@ function safePathSegment(value, fallback) {
|
|
|
7387
7437
|
return safe.length > 0 ? safe.slice(0, 120) : fallback;
|
|
7388
7438
|
}
|
|
7389
7439
|
|
|
7390
|
-
// src/conversations/conversation-
|
|
7440
|
+
// src/conversations/conversation-archive-plans.ts
|
|
7391
7441
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
7392
7442
|
import { mkdir as mkdir7 } from "fs/promises";
|
|
7393
7443
|
import path11 from "path";
|
|
7394
|
-
var PLAN_ID_PATTERN = /^
|
|
7395
|
-
var
|
|
7444
|
+
var PLAN_ID_PATTERN = /^archive_[a-f0-9]{32}$/u;
|
|
7445
|
+
var ConversationArchivePlanStore = class {
|
|
7396
7446
|
constructor(paths) {
|
|
7397
7447
|
this.paths = paths;
|
|
7398
7448
|
}
|
|
@@ -7400,12 +7450,12 @@ var ConversationClearPlanStore = class {
|
|
|
7400
7450
|
async create(conversationIds) {
|
|
7401
7451
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7402
7452
|
const plan = {
|
|
7403
|
-
id: `
|
|
7453
|
+
id: `archive_${randomUUID5().replaceAll("-", "")}`,
|
|
7404
7454
|
status: "prepared",
|
|
7405
7455
|
created_at: now,
|
|
7406
7456
|
updated_at: now,
|
|
7407
7457
|
total_count: conversationIds.length,
|
|
7408
|
-
|
|
7458
|
+
archived_count: 0,
|
|
7409
7459
|
failed_count: 0,
|
|
7410
7460
|
conversation_ids: conversationIds,
|
|
7411
7461
|
conversations: []
|
|
@@ -7421,8 +7471,8 @@ var ConversationClearPlanStore = class {
|
|
|
7421
7471
|
if (!plan) {
|
|
7422
7472
|
throw new LinkHttpError(
|
|
7423
7473
|
404,
|
|
7424
|
-
"
|
|
7425
|
-
"Conversation
|
|
7474
|
+
"conversation_archive_plan_not_found",
|
|
7475
|
+
"Conversation archive plan was not found"
|
|
7426
7476
|
);
|
|
7427
7477
|
}
|
|
7428
7478
|
return plan;
|
|
@@ -7433,7 +7483,7 @@ var ConversationClearPlanStore = class {
|
|
|
7433
7483
|
await writeJsonFile(this.planPath(normalizedPlanId), plan);
|
|
7434
7484
|
}
|
|
7435
7485
|
plansDir() {
|
|
7436
|
-
return path11.join(this.paths.indexesDir, "conversation-
|
|
7486
|
+
return path11.join(this.paths.indexesDir, "conversation-archive-plans");
|
|
7437
7487
|
}
|
|
7438
7488
|
planPath(planId) {
|
|
7439
7489
|
return path11.join(this.plansDir(), `${planId}.json`);
|
|
@@ -7442,6 +7492,71 @@ var ConversationClearPlanStore = class {
|
|
|
7442
7492
|
function normalizePlanId(planId) {
|
|
7443
7493
|
const normalized = planId.trim();
|
|
7444
7494
|
if (!PLAN_ID_PATTERN.test(normalized)) {
|
|
7495
|
+
throw new LinkHttpError(
|
|
7496
|
+
400,
|
|
7497
|
+
"conversation_archive_plan_id_invalid",
|
|
7498
|
+
"Conversation archive plan id is invalid"
|
|
7499
|
+
);
|
|
7500
|
+
}
|
|
7501
|
+
return normalized;
|
|
7502
|
+
}
|
|
7503
|
+
|
|
7504
|
+
// src/conversations/conversation-clear-plans.ts
|
|
7505
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
7506
|
+
import { mkdir as mkdir8 } from "fs/promises";
|
|
7507
|
+
import path12 from "path";
|
|
7508
|
+
var PLAN_ID_PATTERN2 = /^clear_[a-f0-9]{32}$/u;
|
|
7509
|
+
var ConversationClearPlanStore = class {
|
|
7510
|
+
constructor(paths) {
|
|
7511
|
+
this.paths = paths;
|
|
7512
|
+
}
|
|
7513
|
+
paths;
|
|
7514
|
+
async create(conversationIds, targetStatus = "active") {
|
|
7515
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7516
|
+
const plan = {
|
|
7517
|
+
id: `clear_${randomUUID6().replaceAll("-", "")}`,
|
|
7518
|
+
status: "prepared",
|
|
7519
|
+
target_status: targetStatus,
|
|
7520
|
+
created_at: now,
|
|
7521
|
+
updated_at: now,
|
|
7522
|
+
total_count: conversationIds.length,
|
|
7523
|
+
deleted_count: 0,
|
|
7524
|
+
failed_count: 0,
|
|
7525
|
+
conversation_ids: conversationIds,
|
|
7526
|
+
conversations: []
|
|
7527
|
+
};
|
|
7528
|
+
await this.write(plan);
|
|
7529
|
+
return plan;
|
|
7530
|
+
}
|
|
7531
|
+
async read(planId) {
|
|
7532
|
+
const normalizedPlanId = normalizePlanId2(planId);
|
|
7533
|
+
const plan = await readJsonFile(
|
|
7534
|
+
this.planPath(normalizedPlanId)
|
|
7535
|
+
);
|
|
7536
|
+
if (!plan) {
|
|
7537
|
+
throw new LinkHttpError(
|
|
7538
|
+
404,
|
|
7539
|
+
"conversation_clear_plan_not_found",
|
|
7540
|
+
"Conversation clear plan was not found"
|
|
7541
|
+
);
|
|
7542
|
+
}
|
|
7543
|
+
return plan;
|
|
7544
|
+
}
|
|
7545
|
+
async write(plan) {
|
|
7546
|
+
const normalizedPlanId = normalizePlanId2(plan.id);
|
|
7547
|
+
await mkdir8(this.plansDir(), { recursive: true, mode: 448 });
|
|
7548
|
+
await writeJsonFile(this.planPath(normalizedPlanId), plan);
|
|
7549
|
+
}
|
|
7550
|
+
plansDir() {
|
|
7551
|
+
return path12.join(this.paths.indexesDir, "conversation-clear-plans");
|
|
7552
|
+
}
|
|
7553
|
+
planPath(planId) {
|
|
7554
|
+
return path12.join(this.plansDir(), `${planId}.json`);
|
|
7555
|
+
}
|
|
7556
|
+
};
|
|
7557
|
+
function normalizePlanId2(planId) {
|
|
7558
|
+
const normalized = planId.trim();
|
|
7559
|
+
if (!PLAN_ID_PATTERN2.test(normalized)) {
|
|
7445
7560
|
throw new LinkHttpError(
|
|
7446
7561
|
400,
|
|
7447
7562
|
"conversation_clear_plan_id_invalid",
|
|
@@ -7502,14 +7617,16 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
7502
7617
|
constructor(deps) {
|
|
7503
7618
|
this.deps = deps;
|
|
7504
7619
|
this.clearPlans = new ConversationClearPlanStore(deps.paths);
|
|
7620
|
+
this.archivePlans = new ConversationArchivePlanStore(deps.paths);
|
|
7505
7621
|
}
|
|
7506
7622
|
deps;
|
|
7507
7623
|
clearPlans;
|
|
7508
|
-
|
|
7624
|
+
archivePlans;
|
|
7625
|
+
async prepareClearAllConversationPlan(targetStatus = "active") {
|
|
7509
7626
|
const targets = [];
|
|
7510
7627
|
for (const conversationId of await this.deps.store.listConversationIds()) {
|
|
7511
7628
|
const manifest = await this.deps.store.readManifest(conversationId).catch(() => null);
|
|
7512
|
-
if (manifest?.status !==
|
|
7629
|
+
if (manifest?.status !== targetStatus) {
|
|
7513
7630
|
continue;
|
|
7514
7631
|
}
|
|
7515
7632
|
targets.push({
|
|
@@ -7519,7 +7636,8 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
7519
7636
|
}
|
|
7520
7637
|
targets.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
7521
7638
|
return this.clearPlans.create(
|
|
7522
|
-
targets.map((target) => target.id)
|
|
7639
|
+
targets.map((target) => target.id),
|
|
7640
|
+
targetStatus
|
|
7523
7641
|
);
|
|
7524
7642
|
}
|
|
7525
7643
|
async readClearAllConversationPlan(planId) {
|
|
@@ -7629,6 +7747,133 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
7629
7747
|
);
|
|
7630
7748
|
return started;
|
|
7631
7749
|
}
|
|
7750
|
+
async prepareArchiveAllConversationPlan(input = {}) {
|
|
7751
|
+
const excluded = new Set(
|
|
7752
|
+
normalizeConversationIds(input.excludeConversationIds ?? [])
|
|
7753
|
+
);
|
|
7754
|
+
const targets = [];
|
|
7755
|
+
for (const conversationId of await this.deps.store.listConversationIds()) {
|
|
7756
|
+
if (excluded.has(conversationId)) {
|
|
7757
|
+
continue;
|
|
7758
|
+
}
|
|
7759
|
+
const manifest = await this.deps.store.readManifest(conversationId).catch(() => null);
|
|
7760
|
+
if (manifest?.status !== "active") {
|
|
7761
|
+
continue;
|
|
7762
|
+
}
|
|
7763
|
+
targets.push({
|
|
7764
|
+
id: conversationId,
|
|
7765
|
+
updatedAt: manifest.updated_at
|
|
7766
|
+
});
|
|
7767
|
+
}
|
|
7768
|
+
targets.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
7769
|
+
return this.archivePlans.create(targets.map((target) => target.id));
|
|
7770
|
+
}
|
|
7771
|
+
async readArchiveAllConversationPlan(planId) {
|
|
7772
|
+
return this.archivePlans.read(planId);
|
|
7773
|
+
}
|
|
7774
|
+
async executeArchiveAllConversationPlan(planId) {
|
|
7775
|
+
let plan = await this.archivePlans.read(planId);
|
|
7776
|
+
if (plan.status === "completed") {
|
|
7777
|
+
return plan;
|
|
7778
|
+
}
|
|
7779
|
+
if (plan.status === "failed") {
|
|
7780
|
+
throw new LinkHttpError(
|
|
7781
|
+
409,
|
|
7782
|
+
"conversation_archive_plan_already_failed",
|
|
7783
|
+
"Conversation archive plan has already failed"
|
|
7784
|
+
);
|
|
7785
|
+
}
|
|
7786
|
+
const results = [];
|
|
7787
|
+
if (plan.status !== "executing") {
|
|
7788
|
+
plan = await this.writeArchivePlan({
|
|
7789
|
+
...plan,
|
|
7790
|
+
status: "executing",
|
|
7791
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7792
|
+
conversations: results,
|
|
7793
|
+
archived_count: 0,
|
|
7794
|
+
failed_count: 0
|
|
7795
|
+
});
|
|
7796
|
+
}
|
|
7797
|
+
for (const conversationId of plan.conversation_ids) {
|
|
7798
|
+
try {
|
|
7799
|
+
const archived = await this.archiveConversation(conversationId);
|
|
7800
|
+
results.push({ ...archived, status: "archived" });
|
|
7801
|
+
} catch (error) {
|
|
7802
|
+
if (error instanceof LinkHttpError && error.code === "conversation_not_found") {
|
|
7803
|
+
results.push({
|
|
7804
|
+
conversation_id: conversationId,
|
|
7805
|
+
status: "archived",
|
|
7806
|
+
archived_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
7807
|
+
});
|
|
7808
|
+
} else {
|
|
7809
|
+
results.push({
|
|
7810
|
+
conversation_id: conversationId,
|
|
7811
|
+
status: "failed",
|
|
7812
|
+
error: {
|
|
7813
|
+
code: error instanceof LinkHttpError ? error.code : "internal_error",
|
|
7814
|
+
message: error instanceof Error ? error.message : "Internal error"
|
|
7815
|
+
}
|
|
7816
|
+
});
|
|
7817
|
+
}
|
|
7818
|
+
}
|
|
7819
|
+
plan = await this.writeArchivePlan({
|
|
7820
|
+
...plan,
|
|
7821
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7822
|
+
conversations: [...results],
|
|
7823
|
+
archived_count: results.filter((result) => result.status === "archived").length,
|
|
7824
|
+
failed_count: results.filter((result) => result.status === "failed").length
|
|
7825
|
+
});
|
|
7826
|
+
}
|
|
7827
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7828
|
+
return this.writeArchivePlan({
|
|
7829
|
+
...plan,
|
|
7830
|
+
status: plan.failed_count === 0 ? "completed" : "failed",
|
|
7831
|
+
updated_at: completedAt,
|
|
7832
|
+
completed_at: completedAt
|
|
7833
|
+
});
|
|
7834
|
+
}
|
|
7835
|
+
async startArchiveAllConversationPlan(planId) {
|
|
7836
|
+
const plan = await this.archivePlans.read(planId);
|
|
7837
|
+
if (plan.status === "completed" || plan.status === "executing") {
|
|
7838
|
+
return plan;
|
|
7839
|
+
}
|
|
7840
|
+
if (plan.status === "failed") {
|
|
7841
|
+
throw new LinkHttpError(
|
|
7842
|
+
409,
|
|
7843
|
+
"conversation_archive_plan_already_failed",
|
|
7844
|
+
"Conversation archive plan has already failed"
|
|
7845
|
+
);
|
|
7846
|
+
}
|
|
7847
|
+
const started = await this.writeArchivePlan({
|
|
7848
|
+
...plan,
|
|
7849
|
+
status: "executing",
|
|
7850
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7851
|
+
conversations: [],
|
|
7852
|
+
archived_count: 0,
|
|
7853
|
+
failed_count: 0
|
|
7854
|
+
});
|
|
7855
|
+
void this.executeArchiveAllConversationPlan(started.id).catch(
|
|
7856
|
+
async (error) => {
|
|
7857
|
+
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7858
|
+
await this.writeArchivePlan({
|
|
7859
|
+
...started,
|
|
7860
|
+
status: "failed",
|
|
7861
|
+
updated_at: failedAt,
|
|
7862
|
+
completed_at: failedAt,
|
|
7863
|
+
failed_count: started.total_count,
|
|
7864
|
+
conversations: started.conversation_ids.map((conversationId) => ({
|
|
7865
|
+
conversation_id: conversationId,
|
|
7866
|
+
status: "failed",
|
|
7867
|
+
error: {
|
|
7868
|
+
code: error instanceof LinkHttpError ? error.code : "internal_error",
|
|
7869
|
+
message: error instanceof Error ? error.message : "Internal error"
|
|
7870
|
+
}
|
|
7871
|
+
}))
|
|
7872
|
+
}).catch(() => void 0);
|
|
7873
|
+
}
|
|
7874
|
+
);
|
|
7875
|
+
return started;
|
|
7876
|
+
}
|
|
7632
7877
|
async deleteConversation(conversationId) {
|
|
7633
7878
|
return this.deps.withConversationLock(
|
|
7634
7879
|
conversationId,
|
|
@@ -7665,8 +7910,20 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
7665
7910
|
conversations
|
|
7666
7911
|
};
|
|
7667
7912
|
}
|
|
7913
|
+
async archiveConversation(conversationId) {
|
|
7914
|
+
return this.deps.withConversationLock(
|
|
7915
|
+
conversationId,
|
|
7916
|
+
() => this.archiveConversationLocked(conversationId)
|
|
7917
|
+
);
|
|
7918
|
+
}
|
|
7919
|
+
async unarchiveConversation(conversationId) {
|
|
7920
|
+
return this.deps.withConversationLock(
|
|
7921
|
+
conversationId,
|
|
7922
|
+
() => this.unarchiveConversationLocked(conversationId)
|
|
7923
|
+
);
|
|
7924
|
+
}
|
|
7668
7925
|
async writeBlob(conversationId, input) {
|
|
7669
|
-
await this.deps.store.
|
|
7926
|
+
await this.deps.store.readRunnableManifest(conversationId);
|
|
7670
7927
|
const blob = await writeConversationBlob(
|
|
7671
7928
|
this.deps.paths,
|
|
7672
7929
|
conversationId,
|
|
@@ -7682,11 +7939,11 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
7682
7939
|
return blob;
|
|
7683
7940
|
}
|
|
7684
7941
|
async readBlob(conversationId, blobId) {
|
|
7685
|
-
await this.deps.store.
|
|
7942
|
+
await this.deps.store.readRunnableManifest(conversationId);
|
|
7686
7943
|
return readConversationBlob(this.deps.paths, conversationId, blobId);
|
|
7687
7944
|
}
|
|
7688
7945
|
async deleteUnreferencedBlob(conversationId, blobId) {
|
|
7689
|
-
await this.deps.store.
|
|
7946
|
+
await this.deps.store.readRunnableManifest(conversationId);
|
|
7690
7947
|
const snapshot = await this.deps.store.readSnapshot(conversationId);
|
|
7691
7948
|
return deleteConversationBlobIfUnreferenced(
|
|
7692
7949
|
this.deps.paths,
|
|
@@ -7735,8 +7992,96 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
7735
7992
|
}
|
|
7736
7993
|
return parts;
|
|
7737
7994
|
}
|
|
7995
|
+
async archiveConversationLocked(conversationId) {
|
|
7996
|
+
const manifest = await this.deps.store.readManifest(conversationId);
|
|
7997
|
+
if (manifest.status === "deleted_soft") {
|
|
7998
|
+
throw new LinkHttpError(
|
|
7999
|
+
404,
|
|
8000
|
+
"conversation_not_found",
|
|
8001
|
+
"Conversation was not found"
|
|
8002
|
+
);
|
|
8003
|
+
}
|
|
8004
|
+
if (manifest.status === "archived") {
|
|
8005
|
+
return {
|
|
8006
|
+
conversation_id: conversationId,
|
|
8007
|
+
archived_at: manifest.archived_at ?? manifest.updated_at
|
|
8008
|
+
};
|
|
8009
|
+
}
|
|
8010
|
+
const archivedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8011
|
+
const snapshot = await this.deps.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
8012
|
+
await this.deps.store.writeManifest({
|
|
8013
|
+
...manifest,
|
|
8014
|
+
status: "archived",
|
|
8015
|
+
updated_at: archivedAt,
|
|
8016
|
+
archived_at: archivedAt
|
|
8017
|
+
});
|
|
8018
|
+
await this.deps.appendEvent(conversationId, {
|
|
8019
|
+
type: "conversation.archived",
|
|
8020
|
+
payload: {
|
|
8021
|
+
archived_at: archivedAt
|
|
8022
|
+
}
|
|
8023
|
+
});
|
|
8024
|
+
const updatedManifest = await this.deps.store.readManifest(conversationId);
|
|
8025
|
+
await this.deps.metadata.persistConversationStats(conversationId, snapshot);
|
|
8026
|
+
await this.deps.store.writeManifest({
|
|
8027
|
+
...updatedManifest,
|
|
8028
|
+
stats: buildConversationStats(updatedManifest, snapshot)
|
|
8029
|
+
});
|
|
8030
|
+
return {
|
|
8031
|
+
conversation_id: conversationId,
|
|
8032
|
+
archived_at: archivedAt
|
|
8033
|
+
};
|
|
8034
|
+
}
|
|
8035
|
+
async unarchiveConversationLocked(conversationId) {
|
|
8036
|
+
const manifest = await this.deps.store.readManifest(conversationId);
|
|
8037
|
+
if (manifest.status === "deleted_soft") {
|
|
8038
|
+
throw new LinkHttpError(
|
|
8039
|
+
404,
|
|
8040
|
+
"conversation_not_found",
|
|
8041
|
+
"Conversation was not found"
|
|
8042
|
+
);
|
|
8043
|
+
}
|
|
8044
|
+
if (manifest.status === "active") {
|
|
8045
|
+
return {
|
|
8046
|
+
conversation_id: conversationId,
|
|
8047
|
+
unarchived_at: manifest.updated_at
|
|
8048
|
+
};
|
|
8049
|
+
}
|
|
8050
|
+
const unarchivedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8051
|
+
const snapshot = await this.deps.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
8052
|
+
const next = {
|
|
8053
|
+
...manifest,
|
|
8054
|
+
status: "active",
|
|
8055
|
+
updated_at: unarchivedAt
|
|
8056
|
+
};
|
|
8057
|
+
delete next.archived_at;
|
|
8058
|
+
await this.deps.store.writeManifest(next);
|
|
8059
|
+
await this.deps.appendEvent(conversationId, {
|
|
8060
|
+
type: "conversation.unarchived",
|
|
8061
|
+
payload: {
|
|
8062
|
+
unarchived_at: unarchivedAt
|
|
8063
|
+
}
|
|
8064
|
+
});
|
|
8065
|
+
const updatedManifest = await this.deps.store.readManifest(conversationId);
|
|
8066
|
+
await this.deps.metadata.persistConversationStats(conversationId, snapshot);
|
|
8067
|
+
await this.deps.store.writeManifest({
|
|
8068
|
+
...updatedManifest,
|
|
8069
|
+
stats: buildConversationStats(updatedManifest, snapshot)
|
|
8070
|
+
});
|
|
8071
|
+
return {
|
|
8072
|
+
conversation_id: conversationId,
|
|
8073
|
+
unarchived_at: unarchivedAt
|
|
8074
|
+
};
|
|
8075
|
+
}
|
|
7738
8076
|
async deleteConversationLocked(conversationId) {
|
|
7739
|
-
const manifest = await this.deps.store.
|
|
8077
|
+
const manifest = await this.deps.store.readManifest(conversationId);
|
|
8078
|
+
if (manifest.status === "deleted_soft") {
|
|
8079
|
+
throw new LinkHttpError(
|
|
8080
|
+
404,
|
|
8081
|
+
"conversation_not_found",
|
|
8082
|
+
"Conversation was not found"
|
|
8083
|
+
);
|
|
8084
|
+
}
|
|
7740
8085
|
const snapshot = await this.deps.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
7741
8086
|
const referencedBlobIds = /* @__PURE__ */ new Set([
|
|
7742
8087
|
...collectBlobIds(snapshot),
|
|
@@ -7747,7 +8092,7 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
7747
8092
|
hermesSessionIds,
|
|
7748
8093
|
manifest.profile_name_snapshot ?? manifest.profile ?? "default"
|
|
7749
8094
|
);
|
|
7750
|
-
if (
|
|
8095
|
+
if (hermesDeleteResults.some((result) => result.status === "unknown")) {
|
|
7751
8096
|
throw new LinkHttpError(
|
|
7752
8097
|
502,
|
|
7753
8098
|
"hermes_session_delete_not_confirmed",
|
|
@@ -7838,6 +8183,10 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
7838
8183
|
await this.clearPlans.write(plan);
|
|
7839
8184
|
return plan;
|
|
7840
8185
|
}
|
|
8186
|
+
async writeArchivePlan(plan) {
|
|
8187
|
+
await this.archivePlans.write(plan);
|
|
8188
|
+
return plan;
|
|
8189
|
+
}
|
|
7841
8190
|
};
|
|
7842
8191
|
function isVoiceAttachmentInput(attachment) {
|
|
7843
8192
|
return attachment.kind === "voice" || attachment.type === "voice" || attachment.is_voice_note === true || attachment.isVoiceNote === true;
|
|
@@ -7938,14 +8287,14 @@ function isUsableLanIpv4(value) {
|
|
|
7938
8287
|
|
|
7939
8288
|
// src/hermes/session-title.ts
|
|
7940
8289
|
import { stat as stat7 } from "fs/promises";
|
|
7941
|
-
import
|
|
8290
|
+
import path13 from "path";
|
|
7942
8291
|
async function readHermesSessionTitle(sessionId, paths, profileName) {
|
|
7943
8292
|
const trimmedSessionId = sessionId.trim();
|
|
7944
8293
|
if (!trimmedSessionId) {
|
|
7945
8294
|
return void 0;
|
|
7946
8295
|
}
|
|
7947
8296
|
const resolvedProfileName = isValidProfileName(profileName) ? profileName : "default";
|
|
7948
|
-
const dbPath =
|
|
8297
|
+
const dbPath = path13.join(
|
|
7949
8298
|
resolveHermesProfileDir(resolvedProfileName),
|
|
7950
8299
|
"state.db"
|
|
7951
8300
|
);
|
|
@@ -7966,7 +8315,7 @@ async function readHermesCompressionTip(sessionId, paths, profileName) {
|
|
|
7966
8315
|
return void 0;
|
|
7967
8316
|
}
|
|
7968
8317
|
const resolvedProfileName = isValidProfileName(profileName) ? profileName : "default";
|
|
7969
|
-
const dbPath =
|
|
8318
|
+
const dbPath = path13.join(
|
|
7970
8319
|
resolveHermesProfileDir(resolvedProfileName),
|
|
7971
8320
|
"state.db"
|
|
7972
8321
|
);
|
|
@@ -8398,7 +8747,7 @@ function stripCompressionTitleSuffix(value) {
|
|
|
8398
8747
|
}
|
|
8399
8748
|
|
|
8400
8749
|
// src/conversations/conversation-turns.ts
|
|
8401
|
-
import { randomUUID as
|
|
8750
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
8402
8751
|
var MESSAGE_ORDER_STEP_MS = 10;
|
|
8403
8752
|
var ASSISTANT_ORDER_OFFSET_MS = 1;
|
|
8404
8753
|
function createAgentTurnDraft(input) {
|
|
@@ -8632,10 +8981,10 @@ function createAssistantMessage(input) {
|
|
|
8632
8981
|
};
|
|
8633
8982
|
}
|
|
8634
8983
|
function createMessageId() {
|
|
8635
|
-
return `msg_${
|
|
8984
|
+
return `msg_${randomUUID7().replaceAll("-", "")}`;
|
|
8636
8985
|
}
|
|
8637
8986
|
function createRunId() {
|
|
8638
|
-
return `run_${
|
|
8987
|
+
return `run_${randomUUID7().replaceAll("-", "")}`;
|
|
8639
8988
|
}
|
|
8640
8989
|
function hasActiveOrQueuedRuns(snapshot) {
|
|
8641
8990
|
return snapshot.runs.some(
|
|
@@ -8699,10 +9048,7 @@ var ConversationOrchestrationCoordinator = class {
|
|
|
8699
9048
|
});
|
|
8700
9049
|
}
|
|
8701
9050
|
async startNextQueuedRunLocked(conversationId) {
|
|
8702
|
-
|
|
8703
|
-
return null;
|
|
8704
|
-
}
|
|
8705
|
-
const manifest = await this.deps.store.readActiveManifest(conversationId);
|
|
9051
|
+
const manifest = await this.deps.store.readRunnableManifest(conversationId);
|
|
8706
9052
|
const snapshot = await this.deps.store.readSnapshot(conversationId);
|
|
8707
9053
|
if (hasRunningRuns(snapshot)) {
|
|
8708
9054
|
return null;
|
|
@@ -8774,7 +9120,9 @@ var ConversationOrchestrationCoordinator = class {
|
|
|
8774
9120
|
};
|
|
8775
9121
|
}
|
|
8776
9122
|
async sendMessageLocked(input) {
|
|
8777
|
-
let manifest = await this.deps.
|
|
9123
|
+
let manifest = await this.deps.restoreArchivedConversationForUserContinuation(
|
|
9124
|
+
input.conversationId
|
|
9125
|
+
);
|
|
8778
9126
|
let content = input.content.trim();
|
|
8779
9127
|
const userAttachmentParts = await this.deps.resolveMessageAttachmentParts(
|
|
8780
9128
|
manifest.id,
|
|
@@ -9502,29 +9850,45 @@ var ConversationQueryCoordinator = class {
|
|
|
9502
9850
|
return this.listConversationsFromStore();
|
|
9503
9851
|
}
|
|
9504
9852
|
async listConversationPage(options = {}) {
|
|
9853
|
+
return this.listConversationPageForStatus("active", options);
|
|
9854
|
+
}
|
|
9855
|
+
async listArchivedConversationPage(options = {}) {
|
|
9856
|
+
return this.listConversationPageForStatus("archived", options);
|
|
9857
|
+
}
|
|
9858
|
+
async listConversationPageForStatus(status, options = {}) {
|
|
9505
9859
|
const limit = normalizeConversationListPageLimit(options.limit);
|
|
9506
9860
|
const cursor = decodeConversationListCursor(options.cursor);
|
|
9507
9861
|
return this.listIndexedConversationPage({
|
|
9862
|
+
status,
|
|
9508
9863
|
limit,
|
|
9509
9864
|
cursor,
|
|
9510
|
-
fallback: () => this.listConversationPageFromStore({ limit, cursor }),
|
|
9865
|
+
fallback: () => this.listConversationPageFromStore({ status, limit, cursor }),
|
|
9511
9866
|
listPage: (pageCursor) => listConversationStatsPage(this.deps.paths, {
|
|
9867
|
+
status,
|
|
9512
9868
|
limit,
|
|
9513
9869
|
cursor: pageCursor
|
|
9514
9870
|
})
|
|
9515
9871
|
});
|
|
9516
9872
|
}
|
|
9517
9873
|
async searchConversationPage(options = {}) {
|
|
9874
|
+
return this.searchConversationPageForStatus("active", options);
|
|
9875
|
+
}
|
|
9876
|
+
async searchArchivedConversationPage(options = {}) {
|
|
9877
|
+
return this.searchConversationPageForStatus("archived", options);
|
|
9878
|
+
}
|
|
9879
|
+
async searchConversationPageForStatus(status, options = {}) {
|
|
9518
9880
|
const query = normalizeConversationSearchQuery(options.query);
|
|
9519
9881
|
if (!query) {
|
|
9520
|
-
return this.
|
|
9882
|
+
return this.listConversationPageForStatus(status, options);
|
|
9521
9883
|
}
|
|
9522
9884
|
const limit = normalizeConversationListPageLimit(options.limit);
|
|
9523
9885
|
const cursor = decodeConversationListCursor(options.cursor);
|
|
9524
9886
|
return this.listIndexedConversationPage({
|
|
9887
|
+
status,
|
|
9525
9888
|
limit,
|
|
9526
9889
|
cursor,
|
|
9527
9890
|
listPage: (pageCursor) => searchConversationStatsPage(this.deps.paths, {
|
|
9891
|
+
status,
|
|
9528
9892
|
limit,
|
|
9529
9893
|
cursor: pageCursor,
|
|
9530
9894
|
query
|
|
@@ -9550,6 +9914,7 @@ var ConversationQueryCoordinator = class {
|
|
|
9550
9914
|
}
|
|
9551
9915
|
usedIndex = true;
|
|
9552
9916
|
const summaries = await this.summarizeIndexedConversations(
|
|
9917
|
+
input.status,
|
|
9553
9918
|
indexedPage.records
|
|
9554
9919
|
);
|
|
9555
9920
|
for (const summary of summaries) {
|
|
@@ -9590,18 +9955,14 @@ var ConversationQueryCoordinator = class {
|
|
|
9590
9955
|
}
|
|
9591
9956
|
};
|
|
9592
9957
|
}
|
|
9593
|
-
async listConversationsFromStore() {
|
|
9958
|
+
async listConversationsFromStore(status = "active") {
|
|
9594
9959
|
const summaries = [];
|
|
9595
9960
|
for (const conversationId of await this.deps.store.listConversationIds()) {
|
|
9596
|
-
const manifest = await this.deps.store.readManifest(conversationId).catch(
|
|
9597
|
-
|
|
9598
|
-
);
|
|
9599
|
-
if (!manifest || manifest.status !== "active") {
|
|
9961
|
+
const manifest = await this.deps.store.readManifest(conversationId).catch(() => null);
|
|
9962
|
+
if (!manifest || manifest.status !== status) {
|
|
9600
9963
|
continue;
|
|
9601
9964
|
}
|
|
9602
|
-
const snapshot = await this.deps.store.readSnapshot(conversationId).catch(
|
|
9603
|
-
() => emptySnapshot2()
|
|
9604
|
-
);
|
|
9965
|
+
const snapshot = await this.deps.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
9605
9966
|
const refreshed = await this.deps.metadata.refreshTitleFromHermes(manifest, { snapshot }).catch(() => manifest);
|
|
9606
9967
|
summaries.push(await this.summarizeConversation(refreshed, snapshot));
|
|
9607
9968
|
}
|
|
@@ -9610,10 +9971,15 @@ var ConversationQueryCoordinator = class {
|
|
|
9610
9971
|
);
|
|
9611
9972
|
}
|
|
9612
9973
|
async listConversationPageFromStore(input) {
|
|
9613
|
-
const all = await this.listConversationsFromStore();
|
|
9614
|
-
const startIndex = input.cursor ? all.findIndex(
|
|
9974
|
+
const all = await this.listConversationsFromStore(input.status);
|
|
9975
|
+
const startIndex = input.cursor ? all.findIndex(
|
|
9976
|
+
(summary) => isAfterConversationListCursor(summary, input.cursor)
|
|
9977
|
+
) : 0;
|
|
9615
9978
|
const safeStartIndex = startIndex < 0 ? all.length : startIndex;
|
|
9616
|
-
const conversations = all.slice(
|
|
9979
|
+
const conversations = all.slice(
|
|
9980
|
+
safeStartIndex,
|
|
9981
|
+
safeStartIndex + input.limit
|
|
9982
|
+
);
|
|
9617
9983
|
const hasMore = safeStartIndex + input.limit < all.length;
|
|
9618
9984
|
return {
|
|
9619
9985
|
conversations,
|
|
@@ -9624,18 +9990,14 @@ var ConversationQueryCoordinator = class {
|
|
|
9624
9990
|
}
|
|
9625
9991
|
};
|
|
9626
9992
|
}
|
|
9627
|
-
async summarizeIndexedConversations(records) {
|
|
9993
|
+
async summarizeIndexedConversations(status, records) {
|
|
9628
9994
|
const summaries = [];
|
|
9629
9995
|
for (const record of records) {
|
|
9630
|
-
const manifest = await this.deps.store.readManifest(record.conversationId).catch(
|
|
9631
|
-
|
|
9632
|
-
);
|
|
9633
|
-
if (!manifest || manifest.status !== "active") {
|
|
9996
|
+
const manifest = await this.deps.store.readManifest(record.conversationId).catch(() => null);
|
|
9997
|
+
if (!manifest || manifest.status !== status) {
|
|
9634
9998
|
continue;
|
|
9635
9999
|
}
|
|
9636
|
-
const snapshot = await this.deps.store.readSnapshot(record.conversationId).catch(
|
|
9637
|
-
() => emptySnapshot2()
|
|
9638
|
-
);
|
|
10000
|
+
const snapshot = await this.deps.store.readSnapshot(record.conversationId).catch(() => emptySnapshot2());
|
|
9639
10001
|
const refreshed = await this.deps.metadata.refreshTitleFromHermes(manifest, { snapshot }).catch(() => manifest);
|
|
9640
10002
|
summaries.push(await this.summarizeConversation(refreshed, snapshot));
|
|
9641
10003
|
}
|
|
@@ -9812,20 +10174,20 @@ function hydrateAgentEventBlocks(blocks, agentEvents) {
|
|
|
9812
10174
|
// src/conversations/conversation-store.ts
|
|
9813
10175
|
import {
|
|
9814
10176
|
appendFile as appendFile2,
|
|
9815
|
-
mkdir as
|
|
10177
|
+
mkdir as mkdir9,
|
|
9816
10178
|
readdir as readdir5,
|
|
9817
10179
|
readFile as readFile7,
|
|
9818
10180
|
rm as rm5,
|
|
9819
10181
|
writeFile as writeFile2
|
|
9820
10182
|
} from "fs/promises";
|
|
9821
|
-
import
|
|
10183
|
+
import path14 from "path";
|
|
9822
10184
|
var ConversationStore = class {
|
|
9823
10185
|
constructor(paths) {
|
|
9824
10186
|
this.paths = paths;
|
|
9825
10187
|
}
|
|
9826
10188
|
paths;
|
|
9827
10189
|
async ensureConversationsDir() {
|
|
9828
|
-
await
|
|
10190
|
+
await mkdir9(this.paths.conversationsDir, { recursive: true, mode: 448 });
|
|
9829
10191
|
}
|
|
9830
10192
|
async listConversationIds() {
|
|
9831
10193
|
await this.ensureConversationsDir();
|
|
@@ -9840,7 +10202,7 @@ var ConversationStore = class {
|
|
|
9840
10202
|
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
9841
10203
|
}
|
|
9842
10204
|
async createConversation(manifest, snapshot = createEmptySnapshot2()) {
|
|
9843
|
-
await
|
|
10205
|
+
await mkdir9(this.conversationDir(manifest.id), {
|
|
9844
10206
|
recursive: true,
|
|
9845
10207
|
mode: 448
|
|
9846
10208
|
});
|
|
@@ -9880,7 +10242,7 @@ var ConversationStore = class {
|
|
|
9880
10242
|
conversation_id: conversationId,
|
|
9881
10243
|
created_at: now
|
|
9882
10244
|
};
|
|
9883
|
-
await
|
|
10245
|
+
await mkdir9(this.conversationDir(conversationId), {
|
|
9884
10246
|
recursive: true,
|
|
9885
10247
|
mode: 448
|
|
9886
10248
|
});
|
|
@@ -9929,28 +10291,43 @@ var ConversationStore = class {
|
|
|
9929
10291
|
}
|
|
9930
10292
|
return manifest;
|
|
9931
10293
|
}
|
|
10294
|
+
async readRunnableManifest(conversationId) {
|
|
10295
|
+
const manifest = await this.readManifest(conversationId);
|
|
10296
|
+
if (manifest.status === "deleted_soft") {
|
|
10297
|
+
throw new LinkHttpError(
|
|
10298
|
+
404,
|
|
10299
|
+
"conversation_not_found",
|
|
10300
|
+
"Conversation was not found"
|
|
10301
|
+
);
|
|
10302
|
+
}
|
|
10303
|
+
return manifest;
|
|
10304
|
+
}
|
|
9932
10305
|
async isConversationActive(conversationId) {
|
|
9933
10306
|
const manifest = await this.readManifest(conversationId).catch(() => null);
|
|
9934
10307
|
return manifest?.status === "active";
|
|
9935
10308
|
}
|
|
10309
|
+
async isConversationRunnable(conversationId) {
|
|
10310
|
+
const manifest = await this.readManifest(conversationId).catch(() => null);
|
|
10311
|
+
return manifest != null && manifest.status !== "deleted_soft";
|
|
10312
|
+
}
|
|
9936
10313
|
removeConversationAttachments(conversationId) {
|
|
9937
|
-
return rm5(
|
|
10314
|
+
return rm5(path14.join(this.conversationDir(conversationId), "attachments"), {
|
|
9938
10315
|
recursive: true,
|
|
9939
10316
|
force: true
|
|
9940
10317
|
});
|
|
9941
10318
|
}
|
|
9942
10319
|
conversationDir(conversationId) {
|
|
9943
10320
|
assertValidConversationId(conversationId);
|
|
9944
|
-
return
|
|
10321
|
+
return path14.join(this.paths.conversationsDir, conversationId);
|
|
9945
10322
|
}
|
|
9946
10323
|
manifestPath(conversationId) {
|
|
9947
|
-
return
|
|
10324
|
+
return path14.join(this.conversationDir(conversationId), "manifest.json");
|
|
9948
10325
|
}
|
|
9949
10326
|
snapshotPath(conversationId) {
|
|
9950
|
-
return
|
|
10327
|
+
return path14.join(this.conversationDir(conversationId), "snapshot.json");
|
|
9951
10328
|
}
|
|
9952
10329
|
eventsPath(conversationId) {
|
|
9953
|
-
return
|
|
10330
|
+
return path14.join(this.conversationDir(conversationId), "events.ndjson");
|
|
9954
10331
|
}
|
|
9955
10332
|
};
|
|
9956
10333
|
function createEmptySnapshot2() {
|
|
@@ -9961,13 +10338,13 @@ function isNodeError9(error, code) {
|
|
|
9961
10338
|
}
|
|
9962
10339
|
|
|
9963
10340
|
// src/conversations/hermes-session-sync.ts
|
|
9964
|
-
import { randomUUID as
|
|
10341
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
9965
10342
|
import { readdir as readdir7, readFile as readFile9, stat as stat9 } from "fs/promises";
|
|
9966
|
-
import
|
|
10343
|
+
import path16 from "path";
|
|
9967
10344
|
|
|
9968
10345
|
// src/conversations/delivery-import.ts
|
|
9969
10346
|
import { lstat as lstat2, readFile as readFile8, readdir as readdir6, stat as stat8 } from "fs/promises";
|
|
9970
|
-
import
|
|
10347
|
+
import path15 from "path";
|
|
9971
10348
|
var MAX_IMPORTED_BLOB_BYTES = 100 * 1024 * 1024;
|
|
9972
10349
|
var MAX_MEDIA_IMPORT_FAILURES = 20;
|
|
9973
10350
|
var MAX_DELIVERY_FILES = 50;
|
|
@@ -10038,16 +10415,16 @@ var SUPPORTED_DELIVERY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
10038
10415
|
".m4a"
|
|
10039
10416
|
]);
|
|
10040
10417
|
function resolveDeliveryStagingTarget(paths, stagingDir) {
|
|
10041
|
-
const resolvedDir =
|
|
10042
|
-
const relative =
|
|
10043
|
-
if (!relative || relative.startsWith("..") ||
|
|
10418
|
+
const resolvedDir = path15.resolve(stagingDir);
|
|
10419
|
+
const relative = path15.relative(path15.resolve(paths.conversationsDir), resolvedDir);
|
|
10420
|
+
if (!relative || relative.startsWith("..") || path15.isAbsolute(relative)) {
|
|
10044
10421
|
throw new LinkHttpError(
|
|
10045
10422
|
400,
|
|
10046
10423
|
"delivery_staging_invalid",
|
|
10047
10424
|
"delivery staging directory must be inside Hermes Link conversations"
|
|
10048
10425
|
);
|
|
10049
10426
|
}
|
|
10050
|
-
const segments = relative.split(
|
|
10427
|
+
const segments = relative.split(path15.sep);
|
|
10051
10428
|
if (segments.length !== 3 || segments[1] !== DELIVERY_STAGING_SEGMENT || !segments[0] || !segments[2]) {
|
|
10052
10429
|
throw new LinkHttpError(
|
|
10053
10430
|
400,
|
|
@@ -10083,7 +10460,7 @@ async function collectStagedDeliveryReferences(stagingDir) {
|
|
|
10083
10460
|
return entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).filter((entry) => isSupportedDeliveryFilename(entry.name)).sort(
|
|
10084
10461
|
(left, right) => left.name.localeCompare(right.name, "en", { numeric: true })
|
|
10085
10462
|
).slice(0, MAX_DELIVERY_FILES).map((entry) => {
|
|
10086
|
-
const sourcePath =
|
|
10463
|
+
const sourcePath = path15.join(stagingDir, entry.name);
|
|
10087
10464
|
const mime = inferMimeType(sourcePath);
|
|
10088
10465
|
return {
|
|
10089
10466
|
path: sourcePath,
|
|
@@ -10269,7 +10646,7 @@ async function writeBlobFromFile(deps, conversationId, source) {
|
|
|
10269
10646
|
}
|
|
10270
10647
|
return deps.writeBlob(conversationId, {
|
|
10271
10648
|
bytes: await readFile8(sourcePath),
|
|
10272
|
-
filename:
|
|
10649
|
+
filename: path15.basename(sourcePath),
|
|
10273
10650
|
mime: source.mime ?? inferMimeType(sourcePath)
|
|
10274
10651
|
});
|
|
10275
10652
|
}
|
|
@@ -10282,7 +10659,7 @@ function describeMediaImportFailure(reference, sourceKey, error) {
|
|
|
10282
10659
|
};
|
|
10283
10660
|
}
|
|
10284
10661
|
function isSupportedDeliveryFilename(filename) {
|
|
10285
|
-
return SUPPORTED_DELIVERY_EXTENSIONS.has(
|
|
10662
|
+
return SUPPORTED_DELIVERY_EXTENSIONS.has(path15.extname(filename).toLowerCase());
|
|
10286
10663
|
}
|
|
10287
10664
|
function readString8(payload, key) {
|
|
10288
10665
|
const value = payload[key];
|
|
@@ -10339,7 +10716,7 @@ async function syncHermesSessionsIntoConversations(paths, logger, options = {})
|
|
|
10339
10716
|
const candidates = [];
|
|
10340
10717
|
for (const profileName of profileNames) {
|
|
10341
10718
|
const profileDir = resolveHermesProfileDir(profileName);
|
|
10342
|
-
const dbPath =
|
|
10719
|
+
const dbPath = path16.join(profileDir, "state.db");
|
|
10343
10720
|
const sessions = await listProfileSessions(dbPath).catch((error) => {
|
|
10344
10721
|
result.errors.push({
|
|
10345
10722
|
profile: profileName,
|
|
@@ -11575,8 +11952,8 @@ async function readJsonlMessages(profileName, sessionId) {
|
|
|
11575
11952
|
return [];
|
|
11576
11953
|
}
|
|
11577
11954
|
const profileDir = resolveHermesProfileDir(profileName);
|
|
11578
|
-
const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() =>
|
|
11579
|
-
const transcriptPath =
|
|
11955
|
+
const sessionsDir = await readHermesSessionsDir(profileName).then((value) => value.sessionsDir).catch(() => path16.join(profileDir, "sessions"));
|
|
11956
|
+
const transcriptPath = path16.join(sessionsDir, `${sessionId}.jsonl`);
|
|
11580
11957
|
const raw = await readFile9(transcriptPath, "utf8").catch((error) => {
|
|
11581
11958
|
if (isNodeError11(error, "ENOENT")) {
|
|
11582
11959
|
return "";
|
|
@@ -11731,7 +12108,7 @@ function toLinkMessage(input) {
|
|
|
11731
12108
|
const sessionId = readString9(input.message, "session_id") ?? input.sessionId;
|
|
11732
12109
|
const createdAt = isoFromHermesTime(input.message.timestamp) ?? new Date(Date.now() + input.index).toISOString();
|
|
11733
12110
|
return {
|
|
11734
|
-
id: `msg_${
|
|
12111
|
+
id: `msg_${randomUUID8().replaceAll("-", "")}`,
|
|
11735
12112
|
schema_version: 1,
|
|
11736
12113
|
conversation_id: input.conversationId,
|
|
11737
12114
|
role,
|
|
@@ -11903,7 +12280,7 @@ async function isFile(filePath) {
|
|
|
11903
12280
|
});
|
|
11904
12281
|
}
|
|
11905
12282
|
function createConversationId() {
|
|
11906
|
-
return `conv_${
|
|
12283
|
+
return `conv_${randomUUID8().replaceAll("-", "")}`;
|
|
11907
12284
|
}
|
|
11908
12285
|
function isoFromHermesTime(value) {
|
|
11909
12286
|
const numeric = readNumber2(value);
|
|
@@ -11934,9 +12311,32 @@ function isNodeError11(error, code) {
|
|
|
11934
12311
|
}
|
|
11935
12312
|
|
|
11936
12313
|
// src/conversations/run-lifecycle.ts
|
|
12314
|
+
import { createHash as createHash4 } from "crypto";
|
|
11937
12315
|
import { readdir as readdir8 } from "fs/promises";
|
|
11938
12316
|
|
|
11939
12317
|
// src/hermes/api-server.ts
|
|
12318
|
+
var CAPABILITIES_CACHE_TTL_MS = 6e4;
|
|
12319
|
+
var capabilitiesCache = /* @__PURE__ */ new Map();
|
|
12320
|
+
async function readHermesApiCapabilities(options = {}) {
|
|
12321
|
+
const cacheKey = options.profileName ?? "default";
|
|
12322
|
+
const cached = options.fetchImpl ? null : capabilitiesCache.get(cacheKey) ?? null;
|
|
12323
|
+
if (!options.forceRefresh && cached && cached.expiresAt > Date.now()) {
|
|
12324
|
+
return cached.value;
|
|
12325
|
+
}
|
|
12326
|
+
const response = await callHermesApi(
|
|
12327
|
+
"/v1/capabilities",
|
|
12328
|
+
{ method: "GET" },
|
|
12329
|
+
options
|
|
12330
|
+
);
|
|
12331
|
+
const capabilities = response.status === 404 ? legacyHermesApiCapabilities() : parseHermesApiCapabilities(await readJsonResponse(response));
|
|
12332
|
+
if (!options.fetchImpl) {
|
|
12333
|
+
capabilitiesCache.set(cacheKey, {
|
|
12334
|
+
expiresAt: Date.now() + CAPABILITIES_CACHE_TTL_MS,
|
|
12335
|
+
value: capabilities
|
|
12336
|
+
});
|
|
12337
|
+
}
|
|
12338
|
+
return capabilities;
|
|
12339
|
+
}
|
|
11940
12340
|
async function listHermesModels(options = {}) {
|
|
11941
12341
|
const response = await callHermesApi("/v1/models", { method: "GET" }, options);
|
|
11942
12342
|
if (response.status === 404) {
|
|
@@ -12036,12 +12436,14 @@ async function runHermesCronJobAction(jobId, action, options = {}) {
|
|
|
12036
12436
|
return { ...payload.job };
|
|
12037
12437
|
}
|
|
12038
12438
|
async function createHermesRun(input, options = {}) {
|
|
12439
|
+
const headers = new Headers({ "content-type": "application/json" });
|
|
12440
|
+
await applySessionKeyHeader(headers, input.session_key, options);
|
|
12039
12441
|
const response = await callHermesApi(
|
|
12040
12442
|
"/v1/runs",
|
|
12041
12443
|
{
|
|
12042
12444
|
method: "POST",
|
|
12043
12445
|
body: JSON.stringify(input),
|
|
12044
|
-
headers
|
|
12446
|
+
headers
|
|
12045
12447
|
},
|
|
12046
12448
|
options
|
|
12047
12449
|
);
|
|
@@ -12093,6 +12495,15 @@ async function streamHermesRunEvents(runId, options = {}) {
|
|
|
12093
12495
|
});
|
|
12094
12496
|
}
|
|
12095
12497
|
async function streamHermesResponses(input, options = {}) {
|
|
12498
|
+
const requestHeaders = new Headers({
|
|
12499
|
+
accept: "text/event-stream",
|
|
12500
|
+
"content-type": "application/json"
|
|
12501
|
+
});
|
|
12502
|
+
const sessionKeyHeader = await applySessionKeyHeader(
|
|
12503
|
+
requestHeaders,
|
|
12504
|
+
input.session_key,
|
|
12505
|
+
options
|
|
12506
|
+
);
|
|
12096
12507
|
const requestBody = {
|
|
12097
12508
|
input: input.input,
|
|
12098
12509
|
stream: true,
|
|
@@ -12102,19 +12513,32 @@ async function streamHermesResponses(input, options = {}) {
|
|
|
12102
12513
|
...input.session_id ? { session_id: input.session_id } : {},
|
|
12103
12514
|
...input.previous_response_id ? { previous_response_id: input.previous_response_id } : input.conversation_history && input.conversation_history.length > 0 ? { conversation_history: input.conversation_history } : {}
|
|
12104
12515
|
};
|
|
12105
|
-
|
|
12516
|
+
let response = await callHermesApi(
|
|
12106
12517
|
"/v1/responses",
|
|
12107
12518
|
{
|
|
12108
12519
|
method: "POST",
|
|
12109
12520
|
body: JSON.stringify(requestBody),
|
|
12110
|
-
headers:
|
|
12111
|
-
accept: "text/event-stream",
|
|
12112
|
-
"content-type": "application/json"
|
|
12113
|
-
},
|
|
12521
|
+
headers: requestHeaders,
|
|
12114
12522
|
signal: options.signal
|
|
12115
12523
|
},
|
|
12116
12524
|
options
|
|
12117
12525
|
);
|
|
12526
|
+
if (response.status === 403 && sessionKeyHeader && await isSessionKeyRejected(response.clone())) {
|
|
12527
|
+
void options.logger?.warn("hermes_session_key_rejected_downgrading", {
|
|
12528
|
+
profile: options.profileName ?? "default"
|
|
12529
|
+
});
|
|
12530
|
+
requestHeaders.delete(sessionKeyHeader);
|
|
12531
|
+
response = await callHermesApi(
|
|
12532
|
+
"/v1/responses",
|
|
12533
|
+
{
|
|
12534
|
+
method: "POST",
|
|
12535
|
+
body: JSON.stringify(requestBody),
|
|
12536
|
+
headers: requestHeaders,
|
|
12537
|
+
signal: options.signal
|
|
12538
|
+
},
|
|
12539
|
+
options
|
|
12540
|
+
);
|
|
12541
|
+
}
|
|
12118
12542
|
if (response.status === 404 || response.status === 503) {
|
|
12119
12543
|
assertHermesRunsApiSupported(
|
|
12120
12544
|
await readHermesVersion({ logger: options.logger }).catch(() => null),
|
|
@@ -12145,21 +12569,30 @@ async function streamHermesResponses(input, options = {}) {
|
|
|
12145
12569
|
"x-hermes-session-id": response.headers.get(
|
|
12146
12570
|
"x-hermes-session-id"
|
|
12147
12571
|
)
|
|
12572
|
+
} : {},
|
|
12573
|
+
...response.headers.get("x-hermes-session-key") ? {
|
|
12574
|
+
"x-hermes-session-key": response.headers.get(
|
|
12575
|
+
"x-hermes-session-key"
|
|
12576
|
+
)
|
|
12148
12577
|
} : {}
|
|
12149
12578
|
}
|
|
12150
12579
|
});
|
|
12151
12580
|
}
|
|
12152
12581
|
async function cancelHermesRun(runId, options = {}) {
|
|
12153
|
-
const
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12582
|
+
const stopPath = await resolveHermesRunStopPath(runId, options);
|
|
12583
|
+
if (!stopPath) {
|
|
12584
|
+
throw new LinkHttpError(
|
|
12585
|
+
501,
|
|
12586
|
+
"hermes_cancel_unsupported",
|
|
12587
|
+
"Hermes Agent does not expose a run stop endpoint; only Link-managed conversation runs can be cancelled."
|
|
12588
|
+
);
|
|
12589
|
+
}
|
|
12590
|
+
const response = await callHermesApi(stopPath, { method: "POST" }, options);
|
|
12158
12591
|
if (response.status === 404 || response.status === 405 || response.status === 501) {
|
|
12159
12592
|
throw new LinkHttpError(
|
|
12160
12593
|
501,
|
|
12161
12594
|
"hermes_cancel_unsupported",
|
|
12162
|
-
"Hermes Agent does not expose a run
|
|
12595
|
+
"Hermes Agent does not expose a run stop endpoint; only Link-managed conversation runs can be cancelled."
|
|
12163
12596
|
);
|
|
12164
12597
|
}
|
|
12165
12598
|
if (!response.ok) {
|
|
@@ -12170,10 +12603,84 @@ async function cancelHermesRun(runId, options = {}) {
|
|
|
12170
12603
|
);
|
|
12171
12604
|
}
|
|
12172
12605
|
}
|
|
12173
|
-
async function
|
|
12606
|
+
async function resolveHermesRunStopPath(runId, options) {
|
|
12607
|
+
const encodedRunId = encodeURIComponent(runId);
|
|
12608
|
+
try {
|
|
12609
|
+
const capabilities = await readHermesApiCapabilities(options);
|
|
12610
|
+
if (capabilities.runStopPath) {
|
|
12611
|
+
return capabilities.runStopPath.replace("{run_id}", encodedRunId);
|
|
12612
|
+
}
|
|
12613
|
+
return null;
|
|
12614
|
+
} catch (error) {
|
|
12615
|
+
void options.logger?.debug("hermes_capabilities_probe_failed_for_stop", {
|
|
12616
|
+
profile: options.profileName ?? "default",
|
|
12617
|
+
error: error instanceof Error ? error.message : String(error)
|
|
12618
|
+
});
|
|
12619
|
+
return `/v1/runs/${encodedRunId}/stop`;
|
|
12620
|
+
}
|
|
12621
|
+
}
|
|
12622
|
+
async function applySessionKeyHeader(headers, sessionKey, options) {
|
|
12623
|
+
const normalized = sessionKey?.trim();
|
|
12624
|
+
if (!normalized) {
|
|
12625
|
+
return null;
|
|
12626
|
+
}
|
|
12627
|
+
let capabilities;
|
|
12628
|
+
try {
|
|
12629
|
+
capabilities = await readHermesApiCapabilities(options);
|
|
12630
|
+
} catch (error) {
|
|
12631
|
+
void options.logger?.debug("hermes_session_key_capabilities_unavailable", {
|
|
12632
|
+
profile: options.profileName ?? "default",
|
|
12633
|
+
error: error instanceof Error ? error.message : String(error)
|
|
12634
|
+
});
|
|
12635
|
+
return null;
|
|
12636
|
+
}
|
|
12637
|
+
if (capabilities.source !== "reported" || capabilities.authRequired !== true || !capabilities.sessionKeyHeader) {
|
|
12638
|
+
void options.logger?.debug("hermes_session_key_skipped", {
|
|
12639
|
+
profile: options.profileName ?? "default",
|
|
12640
|
+
source: capabilities.source,
|
|
12641
|
+
auth_required: capabilities.authRequired,
|
|
12642
|
+
supported: Boolean(capabilities.sessionKeyHeader)
|
|
12643
|
+
});
|
|
12644
|
+
return null;
|
|
12645
|
+
}
|
|
12646
|
+
headers.set(capabilities.sessionKeyHeader, normalized);
|
|
12647
|
+
return capabilities.sessionKeyHeader;
|
|
12648
|
+
}
|
|
12649
|
+
async function isSessionKeyRejected(response) {
|
|
12650
|
+
const message = await readUpstreamErrorMessage(response, "");
|
|
12651
|
+
return /X-Hermes-Session-Key|session key/iu.test(message);
|
|
12652
|
+
}
|
|
12653
|
+
function legacyHermesApiCapabilities() {
|
|
12654
|
+
return {
|
|
12655
|
+
source: "legacy",
|
|
12656
|
+
authRequired: null,
|
|
12657
|
+
responsesStreaming: null,
|
|
12658
|
+
runStopPath: null,
|
|
12659
|
+
sessionContinuityHeader: null,
|
|
12660
|
+
sessionKeyHeader: null
|
|
12661
|
+
};
|
|
12662
|
+
}
|
|
12663
|
+
function parseHermesApiCapabilities(payload) {
|
|
12664
|
+
const auth = isRecord(payload.auth) ? payload.auth : {};
|
|
12665
|
+
const features = isRecord(payload.features) ? payload.features : {};
|
|
12666
|
+
const endpoints = isRecord(payload.endpoints) ? payload.endpoints : {};
|
|
12667
|
+
const runStop = isRecord(endpoints.run_stop) ? endpoints.run_stop : {};
|
|
12668
|
+
return {
|
|
12669
|
+
source: "reported",
|
|
12670
|
+
authRequired: readBoolean2(auth, "required"),
|
|
12671
|
+
responsesStreaming: readBoolean2(features, "responses_streaming"),
|
|
12672
|
+
runStopPath: readBoolean2(features, "run_stop") === false ? null : readString10(runStop, "path"),
|
|
12673
|
+
sessionContinuityHeader: readString10(
|
|
12674
|
+
features,
|
|
12675
|
+
"session_continuity_header"
|
|
12676
|
+
),
|
|
12677
|
+
sessionKeyHeader: readString10(features, "session_key_header")
|
|
12678
|
+
};
|
|
12679
|
+
}
|
|
12680
|
+
async function callHermesApi(path27, init, options) {
|
|
12174
12681
|
const method = init.method ?? "GET";
|
|
12175
12682
|
const startedAt = Date.now();
|
|
12176
|
-
void options.logger?.debug("hermes_api_request_started", { method, path:
|
|
12683
|
+
void options.logger?.debug("hermes_api_request_started", { method, path: path27 });
|
|
12177
12684
|
const availability = await ensureHermesApiServerAvailable({
|
|
12178
12685
|
fetchImpl: options.fetchImpl,
|
|
12179
12686
|
logger: options.logger,
|
|
@@ -12181,7 +12688,7 @@ async function callHermesApi(path26, init, options) {
|
|
|
12181
12688
|
});
|
|
12182
12689
|
let config = availability.configResult.apiServer;
|
|
12183
12690
|
const fetcher = options.fetchImpl ?? fetch;
|
|
12184
|
-
const request = () => fetchHermesApi(fetcher, config,
|
|
12691
|
+
const request = () => fetchHermesApi(fetcher, config, path27, init, options);
|
|
12185
12692
|
let response;
|
|
12186
12693
|
try {
|
|
12187
12694
|
response = await request();
|
|
@@ -12189,7 +12696,7 @@ async function callHermesApi(path26, init, options) {
|
|
|
12189
12696
|
logHermesApiError(
|
|
12190
12697
|
options.logger,
|
|
12191
12698
|
method,
|
|
12192
|
-
|
|
12699
|
+
path27,
|
|
12193
12700
|
options.profileName,
|
|
12194
12701
|
startedAt,
|
|
12195
12702
|
error
|
|
@@ -12200,7 +12707,7 @@ async function callHermesApi(path26, init, options) {
|
|
|
12200
12707
|
logHermesApiResponse(
|
|
12201
12708
|
options.logger,
|
|
12202
12709
|
method,
|
|
12203
|
-
|
|
12710
|
+
path27,
|
|
12204
12711
|
options.profileName,
|
|
12205
12712
|
startedAt,
|
|
12206
12713
|
response
|
|
@@ -12209,7 +12716,7 @@ async function callHermesApi(path26, init, options) {
|
|
|
12209
12716
|
}
|
|
12210
12717
|
void options.logger?.warn("hermes_api_request_retrying_after_401", {
|
|
12211
12718
|
method,
|
|
12212
|
-
path:
|
|
12719
|
+
path: path27,
|
|
12213
12720
|
profile: options.profileName ?? "default",
|
|
12214
12721
|
port: config.port ?? null,
|
|
12215
12722
|
duration_ms: Date.now() - startedAt
|
|
@@ -12227,7 +12734,7 @@ async function callHermesApi(path26, init, options) {
|
|
|
12227
12734
|
logHermesApiError(
|
|
12228
12735
|
options.logger,
|
|
12229
12736
|
method,
|
|
12230
|
-
|
|
12737
|
+
path27,
|
|
12231
12738
|
options.profileName,
|
|
12232
12739
|
startedAt,
|
|
12233
12740
|
error
|
|
@@ -12237,7 +12744,7 @@ async function callHermesApi(path26, init, options) {
|
|
|
12237
12744
|
logHermesApiResponse(
|
|
12238
12745
|
options.logger,
|
|
12239
12746
|
method,
|
|
12240
|
-
|
|
12747
|
+
path27,
|
|
12241
12748
|
options.profileName,
|
|
12242
12749
|
startedAt,
|
|
12243
12750
|
response
|
|
@@ -12247,7 +12754,7 @@ async function callHermesApi(path26, init, options) {
|
|
|
12247
12754
|
}
|
|
12248
12755
|
void options.logger?.warn("hermes_api_request_repairing_after_401", {
|
|
12249
12756
|
method,
|
|
12250
|
-
path:
|
|
12757
|
+
path: path27,
|
|
12251
12758
|
profile: options.profileName ?? "default",
|
|
12252
12759
|
port: config.port ?? null,
|
|
12253
12760
|
duration_ms: Date.now() - startedAt
|
|
@@ -12267,7 +12774,7 @@ async function callHermesApi(path26, init, options) {
|
|
|
12267
12774
|
logHermesApiError(
|
|
12268
12775
|
options.logger,
|
|
12269
12776
|
method,
|
|
12270
|
-
|
|
12777
|
+
path27,
|
|
12271
12778
|
options.profileName,
|
|
12272
12779
|
startedAt,
|
|
12273
12780
|
error
|
|
@@ -12277,21 +12784,21 @@ async function callHermesApi(path26, init, options) {
|
|
|
12277
12784
|
logHermesApiResponse(
|
|
12278
12785
|
options.logger,
|
|
12279
12786
|
method,
|
|
12280
|
-
|
|
12787
|
+
path27,
|
|
12281
12788
|
options.profileName,
|
|
12282
12789
|
startedAt,
|
|
12283
12790
|
response
|
|
12284
12791
|
);
|
|
12285
12792
|
return response;
|
|
12286
12793
|
}
|
|
12287
|
-
async function fetchHermesApi(fetcher, config,
|
|
12794
|
+
async function fetchHermesApi(fetcher, config, path27, init, options) {
|
|
12288
12795
|
const headers = new Headers(init.headers);
|
|
12289
12796
|
headers.set("accept", headers.get("accept") ?? "application/json");
|
|
12290
12797
|
if (config.key) {
|
|
12291
12798
|
headers.set("x-api-key", config.key);
|
|
12292
12799
|
headers.set("authorization", `Bearer ${config.key}`);
|
|
12293
12800
|
}
|
|
12294
|
-
return await fetcher(`http://127.0.0.1:${config.port}${
|
|
12801
|
+
return await fetcher(`http://127.0.0.1:${config.port}${path27}`, {
|
|
12295
12802
|
...init,
|
|
12296
12803
|
headers
|
|
12297
12804
|
}).catch((error) => {
|
|
@@ -12300,10 +12807,10 @@ async function fetchHermesApi(fetcher, config, path26, init, options) {
|
|
|
12300
12807
|
}
|
|
12301
12808
|
void options.logger?.warn("hermes_api_server_connect_failed", {
|
|
12302
12809
|
method: String(init.method ?? "GET").toUpperCase(),
|
|
12303
|
-
path:
|
|
12810
|
+
path: path27,
|
|
12304
12811
|
profile: options.profileName ?? "default",
|
|
12305
12812
|
port: config.port ?? null,
|
|
12306
|
-
url: `http://127.0.0.1:${config.port}${
|
|
12813
|
+
url: `http://127.0.0.1:${config.port}${path27}`,
|
|
12307
12814
|
error: error instanceof Error ? error.message : String(error)
|
|
12308
12815
|
});
|
|
12309
12816
|
throw new LinkHttpError(
|
|
@@ -12313,10 +12820,10 @@ async function fetchHermesApi(fetcher, config, path26, init, options) {
|
|
|
12313
12820
|
);
|
|
12314
12821
|
});
|
|
12315
12822
|
}
|
|
12316
|
-
function logHermesApiResponse(logger, method,
|
|
12823
|
+
function logHermesApiResponse(logger, method, path27, profileName, startedAt, response) {
|
|
12317
12824
|
const fields = {
|
|
12318
12825
|
method,
|
|
12319
|
-
path:
|
|
12826
|
+
path: path27,
|
|
12320
12827
|
profile: profileName ?? "default",
|
|
12321
12828
|
status: response.status,
|
|
12322
12829
|
duration_ms: Date.now() - startedAt
|
|
@@ -12337,10 +12844,10 @@ async function logHermesApiFailureResponse(logger, fields, response) {
|
|
|
12337
12844
|
...upstreamError ? { upstream_error: upstreamError } : {}
|
|
12338
12845
|
});
|
|
12339
12846
|
}
|
|
12340
|
-
function logHermesApiError(logger, method,
|
|
12847
|
+
function logHermesApiError(logger, method, path27, profileName, startedAt, error) {
|
|
12341
12848
|
void logger?.warn("hermes_api_request_failed", {
|
|
12342
12849
|
method,
|
|
12343
|
-
path:
|
|
12850
|
+
path: path27,
|
|
12344
12851
|
profile: profileName ?? "default",
|
|
12345
12852
|
duration_ms: Date.now() - startedAt,
|
|
12346
12853
|
...error instanceof LinkHttpError ? { status: error.status, code: error.code } : {},
|
|
@@ -12401,10 +12908,14 @@ function readString10(payload, key) {
|
|
|
12401
12908
|
const value = payload[key];
|
|
12402
12909
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
12403
12910
|
}
|
|
12911
|
+
function readBoolean2(payload, key) {
|
|
12912
|
+
const value = payload[key];
|
|
12913
|
+
return typeof value === "boolean" ? value : null;
|
|
12914
|
+
}
|
|
12404
12915
|
|
|
12405
12916
|
// src/conversations/history-builder.ts
|
|
12406
12917
|
import { readFile as readFile10, stat as stat10 } from "fs/promises";
|
|
12407
|
-
import
|
|
12918
|
+
import path17 from "path";
|
|
12408
12919
|
var HISTORY_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
12409
12920
|
var HERMES_HISTORY_COLUMNS = [
|
|
12410
12921
|
"role",
|
|
@@ -12465,13 +12976,13 @@ async function readHermesTranscriptHistory(sessionId, profileName) {
|
|
|
12465
12976
|
}
|
|
12466
12977
|
const normalizedProfileName = isValidProfileName2(profileName) ? profileName : "default";
|
|
12467
12978
|
const profileDir = resolveHermesProfileDir(normalizedProfileName);
|
|
12468
|
-
const dbPath =
|
|
12979
|
+
const dbPath = path17.join(profileDir, "state.db");
|
|
12469
12980
|
const sessionsDirConfig = await readHermesSessionsDir(normalizedProfileName).then((value) => ({
|
|
12470
12981
|
sessionsDir: value.sessionsDir,
|
|
12471
12982
|
configured: value.configured,
|
|
12472
12983
|
configError: false
|
|
12473
12984
|
})).catch(() => ({
|
|
12474
|
-
sessionsDir:
|
|
12985
|
+
sessionsDir: path17.join(profileDir, "sessions"),
|
|
12475
12986
|
configured: false,
|
|
12476
12987
|
configError: true
|
|
12477
12988
|
}));
|
|
@@ -12529,7 +13040,7 @@ async function readHermesJsonlHistory(sessionsDir, sessionId) {
|
|
|
12529
13040
|
if (!isValidSessionFileStem(sessionId)) {
|
|
12530
13041
|
return empty;
|
|
12531
13042
|
}
|
|
12532
|
-
const transcriptPath =
|
|
13043
|
+
const transcriptPath = path17.join(sessionsDir, `${sessionId}.jsonl`);
|
|
12533
13044
|
const raw = await readFile10(transcriptPath, "utf8").catch((error) => {
|
|
12534
13045
|
if (isNodeError12(error, "ENOENT")) {
|
|
12535
13046
|
return "";
|
|
@@ -12772,7 +13283,7 @@ function normalizeProfileForCompare(value) {
|
|
|
12772
13283
|
// src/hermes/stt.ts
|
|
12773
13284
|
import { execFile as execFile3 } from "child_process";
|
|
12774
13285
|
import { access as access2, readFile as readFile11, stat as stat11 } from "fs/promises";
|
|
12775
|
-
import
|
|
13286
|
+
import path18 from "path";
|
|
12776
13287
|
import { promisify as promisify3 } from "util";
|
|
12777
13288
|
var execFileAsync3 = promisify3(execFile3);
|
|
12778
13289
|
var STT_RESULT_PREFIX = "__HERMES_LINK_STT__";
|
|
@@ -12867,7 +13378,7 @@ async function buildHermesSttEnv(profileName) {
|
|
|
12867
13378
|
};
|
|
12868
13379
|
const devSource = await findDevHermesAgentSource();
|
|
12869
13380
|
if (devSource) {
|
|
12870
|
-
env.PYTHONPATH = [devSource, env.PYTHONPATH].filter(Boolean).join(
|
|
13381
|
+
env.PYTHONPATH = [devSource, env.PYTHONPATH].filter(Boolean).join(path18.delimiter);
|
|
12871
13382
|
}
|
|
12872
13383
|
return env;
|
|
12873
13384
|
}
|
|
@@ -12914,14 +13425,14 @@ async function resolveHermesPythonCommand() {
|
|
|
12914
13425
|
};
|
|
12915
13426
|
}
|
|
12916
13427
|
async function resolveExecutablePath2(command) {
|
|
12917
|
-
if (
|
|
13428
|
+
if (path18.isAbsolute(command)) {
|
|
12918
13429
|
return await isExecutableFile(command) ? command : null;
|
|
12919
13430
|
}
|
|
12920
13431
|
const pathEnv = process.env.PATH ?? "";
|
|
12921
13432
|
const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";") : [""];
|
|
12922
|
-
for (const dir of pathEnv.split(
|
|
13433
|
+
for (const dir of pathEnv.split(path18.delimiter)) {
|
|
12923
13434
|
for (const extension of extensions) {
|
|
12924
|
-
const candidate =
|
|
13435
|
+
const candidate = path18.join(dir, `${command}${extension}`);
|
|
12925
13436
|
if (await isExecutableFile(candidate)) {
|
|
12926
13437
|
return candidate;
|
|
12927
13438
|
}
|
|
@@ -12961,8 +13472,8 @@ function shebangToPythonCommand(shebang) {
|
|
|
12961
13472
|
}
|
|
12962
13473
|
async function findDevHermesAgentSource() {
|
|
12963
13474
|
const candidates = [
|
|
12964
|
-
|
|
12965
|
-
|
|
13475
|
+
path18.resolve(process.cwd(), "reference/hermes-agent"),
|
|
13476
|
+
path18.resolve(process.cwd(), "../../reference/hermes-agent")
|
|
12966
13477
|
];
|
|
12967
13478
|
for (const candidate of candidates) {
|
|
12968
13479
|
if (await isDirectory(candidate)) {
|
|
@@ -12979,6 +13490,71 @@ function compactProcessOutput(value) {
|
|
|
12979
13490
|
return compact || null;
|
|
12980
13491
|
}
|
|
12981
13492
|
|
|
13493
|
+
// src/identity/identity.ts
|
|
13494
|
+
import { generateKeyPairSync, randomUUID as randomUUID9, sign } from "crypto";
|
|
13495
|
+
import { mkdir as mkdir10, chmod as chmod2 } from "fs/promises";
|
|
13496
|
+
import { z } from "zod";
|
|
13497
|
+
var linkIdentitySchema = z.object({
|
|
13498
|
+
install_id: z.string().min(1),
|
|
13499
|
+
link_id: z.string().min(1).nullable().optional(),
|
|
13500
|
+
public_key_pem: z.string().min(1),
|
|
13501
|
+
private_key_pem: z.string().min(1),
|
|
13502
|
+
created_at: z.string().min(1),
|
|
13503
|
+
updated_at: z.string().min(1)
|
|
13504
|
+
});
|
|
13505
|
+
async function loadIdentity(paths = resolveRuntimePaths()) {
|
|
13506
|
+
const value = await readJsonFile(paths.identityFile);
|
|
13507
|
+
if (value === null) {
|
|
13508
|
+
return null;
|
|
13509
|
+
}
|
|
13510
|
+
return linkIdentitySchema.parse(value);
|
|
13511
|
+
}
|
|
13512
|
+
async function ensureIdentity(paths = resolveRuntimePaths()) {
|
|
13513
|
+
const existing = await loadIdentity(paths);
|
|
13514
|
+
if (existing) {
|
|
13515
|
+
return existing;
|
|
13516
|
+
}
|
|
13517
|
+
await mkdir10(paths.homeDir, { recursive: true, mode: 448 });
|
|
13518
|
+
await chmod2(paths.homeDir, 448).catch(() => void 0);
|
|
13519
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
13520
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13521
|
+
const identity = {
|
|
13522
|
+
install_id: `install_${randomUUID9().replaceAll("-", "")}`,
|
|
13523
|
+
link_id: null,
|
|
13524
|
+
public_key_pem: publicKey.export({ type: "spki", format: "pem" }).toString(),
|
|
13525
|
+
private_key_pem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
|
|
13526
|
+
created_at: now,
|
|
13527
|
+
updated_at: now
|
|
13528
|
+
};
|
|
13529
|
+
await writeJsonFile(paths.identityFile, identity);
|
|
13530
|
+
return identity;
|
|
13531
|
+
}
|
|
13532
|
+
async function saveAssignedLinkId(linkId, paths = resolveRuntimePaths()) {
|
|
13533
|
+
const identity = await ensureIdentity(paths);
|
|
13534
|
+
const next = {
|
|
13535
|
+
...identity,
|
|
13536
|
+
link_id: linkId,
|
|
13537
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
13538
|
+
};
|
|
13539
|
+
await writeJsonFile(paths.identityFile, next);
|
|
13540
|
+
return next;
|
|
13541
|
+
}
|
|
13542
|
+
function signRelayNonce(identity, nonce) {
|
|
13543
|
+
return signIdentityPayload(identity, nonce);
|
|
13544
|
+
}
|
|
13545
|
+
function signIdentityPayload(identity, payload) {
|
|
13546
|
+
const signature = sign(null, Buffer.from(payload, "utf8"), identity.private_key_pem);
|
|
13547
|
+
return signature.toString("base64url");
|
|
13548
|
+
}
|
|
13549
|
+
function getIdentityStatus(identity) {
|
|
13550
|
+
return {
|
|
13551
|
+
installId: identity.install_id,
|
|
13552
|
+
linkId: identity.link_id ?? null,
|
|
13553
|
+
hasPrivateKey: identity.private_key_pem.trim().length > 0,
|
|
13554
|
+
publicKeyPem: identity.public_key_pem
|
|
13555
|
+
};
|
|
13556
|
+
}
|
|
13557
|
+
|
|
12982
13558
|
// src/conversations/hermes-sse.ts
|
|
12983
13559
|
async function* parseSseResponse(response) {
|
|
12984
13560
|
if (!response.body) {
|
|
@@ -13546,6 +14122,10 @@ var ConversationRunLifecycle = class {
|
|
|
13546
14122
|
input: resolvedInput,
|
|
13547
14123
|
instructions,
|
|
13548
14124
|
session_id: hermesSessionId,
|
|
14125
|
+
session_key: await this.buildHermesSessionKey(
|
|
14126
|
+
conversationId,
|
|
14127
|
+
run.profile ?? "default"
|
|
14128
|
+
),
|
|
13549
14129
|
model: run.model,
|
|
13550
14130
|
...previousResponseId ? { previous_response_id: previousResponseId } : {},
|
|
13551
14131
|
...conversationHistory.messages.length > 0 ? { conversation_history: conversationHistory.messages } : {}
|
|
@@ -13575,7 +14155,7 @@ var ConversationRunLifecycle = class {
|
|
|
13575
14155
|
);
|
|
13576
14156
|
return;
|
|
13577
14157
|
}
|
|
13578
|
-
if (!await this.deps.
|
|
14158
|
+
if (!await this.deps.isConversationRunnable(conversationId)) {
|
|
13579
14159
|
return;
|
|
13580
14160
|
}
|
|
13581
14161
|
const event = normalizeHermesStreamEvent(rawEvent);
|
|
@@ -13766,7 +14346,7 @@ ${attachmentLines.join("\n")}`
|
|
|
13766
14346
|
}
|
|
13767
14347
|
run.hermes_session_id = normalizedSessionId;
|
|
13768
14348
|
await this.deps.writeSnapshot(conversationId, snapshot);
|
|
13769
|
-
const manifest = await this.deps.
|
|
14349
|
+
const manifest = await this.deps.readRunnableManifest(conversationId);
|
|
13770
14350
|
const nextManifest = addHermesSessionIdToManifest(
|
|
13771
14351
|
manifest,
|
|
13772
14352
|
normalizedSessionId
|
|
@@ -13850,7 +14430,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
13850
14430
|
);
|
|
13851
14431
|
}
|
|
13852
14432
|
async persistHermesEventLocked(conversationId, runId, event) {
|
|
13853
|
-
if (!await this.deps.
|
|
14433
|
+
if (!await this.deps.isConversationRunnable(conversationId)) {
|
|
13854
14434
|
return;
|
|
13855
14435
|
}
|
|
13856
14436
|
if (event.payloadType === "message.delta") {
|
|
@@ -14178,7 +14758,7 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
14178
14758
|
});
|
|
14179
14759
|
}
|
|
14180
14760
|
async cancelRunLocked(conversationId, runId, options) {
|
|
14181
|
-
const manifest = await this.deps.
|
|
14761
|
+
const manifest = await this.deps.readRunnableManifest(conversationId);
|
|
14182
14762
|
const snapshot = await this.deps.readSnapshot(conversationId);
|
|
14183
14763
|
const run = snapshot.runs.find((item) => item.id === runId);
|
|
14184
14764
|
if (!run) {
|
|
@@ -14250,6 +14830,15 @@ ${details.join("\n")}` : emptyHermesResponseMessage();
|
|
|
14250
14830
|
last_event_seq: event.seq
|
|
14251
14831
|
};
|
|
14252
14832
|
}
|
|
14833
|
+
async buildHermesSessionKey(conversationId, profileName) {
|
|
14834
|
+
const identity = await loadIdentity(this.deps.paths).catch(() => null);
|
|
14835
|
+
const linkScope = identity?.link_id?.trim() || identity?.install_id?.trim() || "local";
|
|
14836
|
+
const raw = `hermespilot:${linkScope}:profile:${profileName}:conversation:${conversationId}`;
|
|
14837
|
+
if (raw.length <= 200) {
|
|
14838
|
+
return raw;
|
|
14839
|
+
}
|
|
14840
|
+
return `hermespilot:${createHash4("sha256").update(raw).digest("hex")}`;
|
|
14841
|
+
}
|
|
14253
14842
|
async assistantMessageIdForRun(conversationId, runId) {
|
|
14254
14843
|
const snapshot = await this.deps.readSnapshot(conversationId).catch(() => null);
|
|
14255
14844
|
return snapshot?.runs.find((item) => item.id === runId)?.assistant_message_id;
|
|
@@ -14611,6 +15200,20 @@ function isNodeError13(error, code) {
|
|
|
14611
15200
|
|
|
14612
15201
|
// src/conversations/conversation-service.ts
|
|
14613
15202
|
var ALL_CONVERSATION_EVENTS = "conversation:*";
|
|
15203
|
+
function isConversationNotificationEvent(event) {
|
|
15204
|
+
const type = event.type.toLowerCase();
|
|
15205
|
+
return type === "conversation.created" || type === "conversation.updated" || isAlwaysPublishedNotificationEvent(type) || type === "message.created" || type === "message.completed" || type === "message.failed" || type === "run.completed" || type === "run.failed" || type === "run.cancelled" || type === "run.canceled" || type === "approval.requested" || readPayloadBool(event.payload, "requires_action") || readPayloadBool(event.payload, "requires_user_action") || readPayloadBool(event.payload, "requires_approval");
|
|
15206
|
+
}
|
|
15207
|
+
function isAlwaysPublishedNotificationEvent(type) {
|
|
15208
|
+
const normalized = type.toLowerCase();
|
|
15209
|
+
return normalized === "conversation.archived" || normalized === "conversation.unarchived" || normalized === "conversation.deleted";
|
|
15210
|
+
}
|
|
15211
|
+
function readPayloadBool(payload, key) {
|
|
15212
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
15213
|
+
return false;
|
|
15214
|
+
}
|
|
15215
|
+
return payload[key] === true;
|
|
15216
|
+
}
|
|
14614
15217
|
var ConversationService = class {
|
|
14615
15218
|
constructor(paths, logger) {
|
|
14616
15219
|
this.paths = paths;
|
|
@@ -14659,8 +15262,10 @@ var ConversationService = class {
|
|
|
14659
15262
|
statsOverride
|
|
14660
15263
|
),
|
|
14661
15264
|
readActiveManifest: (conversationId) => this.store.readActiveManifest(conversationId),
|
|
15265
|
+
readRunnableManifest: (conversationId) => this.store.readRunnableManifest(conversationId),
|
|
14662
15266
|
writeManifest: (manifest) => this.store.writeManifest(manifest),
|
|
14663
15267
|
isConversationActive: (conversationId) => this.store.isConversationActive(conversationId),
|
|
15268
|
+
isConversationRunnable: (conversationId) => this.store.isConversationRunnable(conversationId),
|
|
14664
15269
|
writeBlob: (conversationId, input) => this.maintenance.writeBlob(conversationId, input),
|
|
14665
15270
|
syncCronDeliveries: () => this.syncCronDeliveries(),
|
|
14666
15271
|
scheduleTitleRefresh: (conversationId) => this.metadata.scheduleGeneratedTitleRefresh(conversationId)
|
|
@@ -14676,6 +15281,9 @@ var ConversationService = class {
|
|
|
14676
15281
|
resolveMessageAttachmentParts: (conversationId, attachments) => this.maintenance.resolveMessageAttachmentParts(
|
|
14677
15282
|
conversationId,
|
|
14678
15283
|
attachments
|
|
15284
|
+
),
|
|
15285
|
+
restoreArchivedConversationForUserContinuation: (conversationId) => this.restoreArchivedConversationForUserContinuationLocked(
|
|
15286
|
+
conversationId
|
|
14679
15287
|
)
|
|
14680
15288
|
});
|
|
14681
15289
|
}
|
|
@@ -14710,6 +15318,30 @@ var ConversationService = class {
|
|
|
14710
15318
|
}
|
|
14711
15319
|
}
|
|
14712
15320
|
}
|
|
15321
|
+
async restoreArchivedConversationForUserContinuationLocked(conversationId) {
|
|
15322
|
+
const manifest = await this.store.readRunnableManifest(conversationId);
|
|
15323
|
+
if (manifest.status === "active") {
|
|
15324
|
+
return manifest;
|
|
15325
|
+
}
|
|
15326
|
+
const snapshot = await this.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
15327
|
+
const unarchivedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
15328
|
+
const next = {
|
|
15329
|
+
...manifest,
|
|
15330
|
+
status: "active",
|
|
15331
|
+
updated_at: unarchivedAt
|
|
15332
|
+
};
|
|
15333
|
+
delete next.archived_at;
|
|
15334
|
+
await this.store.writeManifest(next);
|
|
15335
|
+
await this.appendEvent(conversationId, {
|
|
15336
|
+
type: "conversation.unarchived",
|
|
15337
|
+
payload: {
|
|
15338
|
+
unarchived_at: unarchivedAt,
|
|
15339
|
+
reason: "user_continuation"
|
|
15340
|
+
}
|
|
15341
|
+
});
|
|
15342
|
+
await this.metadata.persistConversationStats(conversationId, snapshot);
|
|
15343
|
+
return this.store.readActiveManifest(conversationId);
|
|
15344
|
+
}
|
|
14713
15345
|
async listConversations() {
|
|
14714
15346
|
return this.queries.listConversations();
|
|
14715
15347
|
}
|
|
@@ -14719,6 +15351,12 @@ var ConversationService = class {
|
|
|
14719
15351
|
searchConversationPage(input = {}) {
|
|
14720
15352
|
return this.queries.searchConversationPage(input);
|
|
14721
15353
|
}
|
|
15354
|
+
listArchivedConversationPage(input = {}) {
|
|
15355
|
+
return this.queries.listArchivedConversationPage(input);
|
|
15356
|
+
}
|
|
15357
|
+
searchArchivedConversationPage(input = {}) {
|
|
15358
|
+
return this.queries.searchArchivedConversationPage(input);
|
|
15359
|
+
}
|
|
14722
15360
|
async getStatistics(filter = {}) {
|
|
14723
15361
|
return readLinkStatistics(this.paths, filter);
|
|
14724
15362
|
}
|
|
@@ -14726,15 +15364,11 @@ var ConversationService = class {
|
|
|
14726
15364
|
const records = [];
|
|
14727
15365
|
const usageFacts = [];
|
|
14728
15366
|
for (const conversationId of await this.store.listConversationIds()) {
|
|
14729
|
-
const manifest = await this.store.readManifest(conversationId).catch(
|
|
14730
|
-
() => null
|
|
14731
|
-
);
|
|
15367
|
+
const manifest = await this.store.readManifest(conversationId).catch(() => null);
|
|
14732
15368
|
if (!manifest) {
|
|
14733
15369
|
continue;
|
|
14734
15370
|
}
|
|
14735
|
-
const snapshot = await this.store.readSnapshot(conversationId).catch(
|
|
14736
|
-
() => emptySnapshot2()
|
|
14737
|
-
);
|
|
15371
|
+
const snapshot = await this.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
14738
15372
|
records.push(
|
|
14739
15373
|
toStatsIndexRecord(
|
|
14740
15374
|
manifest,
|
|
@@ -14758,7 +15392,7 @@ var ConversationService = class {
|
|
|
14758
15392
|
async createConversation(input = {}) {
|
|
14759
15393
|
await this.store.ensureConversationsDir();
|
|
14760
15394
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14761
|
-
const id = `conv_${
|
|
15395
|
+
const id = `conv_${randomUUID10().replaceAll("-", "")}`;
|
|
14762
15396
|
const title = input.title?.trim() || DEFAULT_CONVERSATION_TITLE;
|
|
14763
15397
|
const profile = await resolveConversationProfileTarget(
|
|
14764
15398
|
this.paths,
|
|
@@ -14811,9 +15445,7 @@ var ConversationService = class {
|
|
|
14811
15445
|
async ensureCronInboxConversation(input = {}) {
|
|
14812
15446
|
const profileName = normalizeProfileName2(input.profileName);
|
|
14813
15447
|
for (const conversationId of await this.store.listConversationIds()) {
|
|
14814
|
-
const manifest = await this.store.readManifest(conversationId).catch(
|
|
14815
|
-
() => null
|
|
14816
|
-
);
|
|
15448
|
+
const manifest = await this.store.readManifest(conversationId).catch(() => null);
|
|
14817
15449
|
if (manifest?.status === "active" && manifest.title === "HermesLink \u5B9A\u65F6\u4EFB\u52A1" && normalizeProfileName2(
|
|
14818
15450
|
manifest.profile_name_snapshot ?? manifest.profile
|
|
14819
15451
|
) === profileName) {
|
|
@@ -14845,7 +15477,7 @@ var ConversationService = class {
|
|
|
14845
15477
|
manifest.profile_name_snapshot ?? manifest.profile ?? input.profileName
|
|
14846
15478
|
);
|
|
14847
15479
|
const message = {
|
|
14848
|
-
id: `msg_${
|
|
15480
|
+
id: `msg_${randomUUID10().replaceAll("-", "")}`,
|
|
14849
15481
|
schema_version: 1,
|
|
14850
15482
|
conversation_id: manifest.id,
|
|
14851
15483
|
role: "assistant",
|
|
@@ -14915,7 +15547,7 @@ var ConversationService = class {
|
|
|
14915
15547
|
async deliverStagedFiles(stagingDir) {
|
|
14916
15548
|
const target = resolveDeliveryStagingTarget(this.paths, stagingDir);
|
|
14917
15549
|
return this.withConversationLock(target.conversationId, async () => {
|
|
14918
|
-
await this.store.
|
|
15550
|
+
await this.store.readRunnableManifest(target.conversationId);
|
|
14919
15551
|
const snapshot = await this.store.readSnapshot(target.conversationId);
|
|
14920
15552
|
const run = snapshot.runs.find((item) => item.id === target.runId);
|
|
14921
15553
|
if (!run) {
|
|
@@ -14961,7 +15593,9 @@ var ConversationService = class {
|
|
|
14961
15593
|
);
|
|
14962
15594
|
}
|
|
14963
15595
|
return this.withConversationLock(conversationId, async () => {
|
|
14964
|
-
const manifest = await this.
|
|
15596
|
+
const manifest = await this.restoreArchivedConversationForUserContinuationLocked(
|
|
15597
|
+
conversationId
|
|
15598
|
+
);
|
|
14965
15599
|
const snapshot = await this.store.readSnapshot(conversationId);
|
|
14966
15600
|
const nextManifest = {
|
|
14967
15601
|
...manifest,
|
|
@@ -14995,7 +15629,7 @@ var ConversationService = class {
|
|
|
14995
15629
|
}
|
|
14996
15630
|
async setConversationProfile(conversationId, profileName) {
|
|
14997
15631
|
return this.withConversationLock(conversationId, async () => {
|
|
14998
|
-
|
|
15632
|
+
let manifest = await this.store.readRunnableManifest(conversationId);
|
|
14999
15633
|
const snapshot = await this.store.readSnapshot(conversationId);
|
|
15000
15634
|
if (hasRunningRuns(snapshot) || hasQueuedRuns(snapshot) || this.hasActiveRunControllerForConversation(conversationId)) {
|
|
15001
15635
|
throw new LinkHttpError(
|
|
@@ -15004,6 +15638,9 @@ var ConversationService = class {
|
|
|
15004
15638
|
"This conversation has an active or queued run. Wait for it to finish before switching profile."
|
|
15005
15639
|
);
|
|
15006
15640
|
}
|
|
15641
|
+
manifest = await this.restoreArchivedConversationForUserContinuationLocked(
|
|
15642
|
+
conversationId
|
|
15643
|
+
);
|
|
15007
15644
|
const profile = await resolveConversationProfileTarget(
|
|
15008
15645
|
this.paths,
|
|
15009
15646
|
profileName
|
|
@@ -15098,7 +15735,9 @@ var ConversationService = class {
|
|
|
15098
15735
|
}
|
|
15099
15736
|
async renameConversation(conversationId, title) {
|
|
15100
15737
|
return this.withConversationLock(conversationId, async () => {
|
|
15101
|
-
const manifest = await this.
|
|
15738
|
+
const manifest = await this.restoreArchivedConversationForUserContinuationLocked(
|
|
15739
|
+
conversationId
|
|
15740
|
+
);
|
|
15102
15741
|
const snapshot = await this.store.readSnapshot(conversationId);
|
|
15103
15742
|
return this.renameConversationLocked(conversationId, title, {
|
|
15104
15743
|
manifest,
|
|
@@ -15136,6 +15775,16 @@ var ConversationService = class {
|
|
|
15136
15775
|
this.emitter.on(ALL_CONVERSATION_EVENTS, listener);
|
|
15137
15776
|
return () => this.emitter.off(ALL_CONVERSATION_EVENTS, listener);
|
|
15138
15777
|
}
|
|
15778
|
+
async shouldPublishNotificationEvent(event) {
|
|
15779
|
+
if (!isConversationNotificationEvent(event)) {
|
|
15780
|
+
return false;
|
|
15781
|
+
}
|
|
15782
|
+
if (isAlwaysPublishedNotificationEvent(event.type)) {
|
|
15783
|
+
return true;
|
|
15784
|
+
}
|
|
15785
|
+
const manifest = await this.store.readManifest(event.conversation_id).catch(() => null);
|
|
15786
|
+
return manifest?.status === "active";
|
|
15787
|
+
}
|
|
15139
15788
|
async sendMessage(input) {
|
|
15140
15789
|
return this.orchestration.sendMessage(input);
|
|
15141
15790
|
}
|
|
@@ -15154,9 +15803,7 @@ var ConversationService = class {
|
|
|
15154
15803
|
if (active) {
|
|
15155
15804
|
return this.cancelRun(active.conversationId, runId);
|
|
15156
15805
|
}
|
|
15157
|
-
const conversationId = await this.runLifecycle.findConversationIdForRun(
|
|
15158
|
-
runId
|
|
15159
|
-
);
|
|
15806
|
+
const conversationId = await this.runLifecycle.findConversationIdForRun(runId);
|
|
15160
15807
|
if (!conversationId) {
|
|
15161
15808
|
throw new LinkHttpError(404, "run_not_found", "Run was not found");
|
|
15162
15809
|
}
|
|
@@ -15179,7 +15826,7 @@ var ConversationService = class {
|
|
|
15179
15826
|
);
|
|
15180
15827
|
}
|
|
15181
15828
|
return this.withConversationLock(input.conversationId, async () => {
|
|
15182
|
-
|
|
15829
|
+
let manifest = await this.store.readRunnableManifest(input.conversationId);
|
|
15183
15830
|
const snapshot = await this.store.readSnapshot(input.conversationId);
|
|
15184
15831
|
const match = findApproval(snapshot, input.approvalId);
|
|
15185
15832
|
if (!match) {
|
|
@@ -15201,6 +15848,9 @@ var ConversationService = class {
|
|
|
15201
15848
|
last_event_seq: manifest.last_event_seq
|
|
15202
15849
|
};
|
|
15203
15850
|
}
|
|
15851
|
+
manifest = await this.restoreArchivedConversationForUserContinuationLocked(
|
|
15852
|
+
input.conversationId
|
|
15853
|
+
);
|
|
15204
15854
|
const resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
15205
15855
|
let commandAllowlistUpdated = false;
|
|
15206
15856
|
let configPath;
|
|
@@ -15307,10 +15957,17 @@ var ConversationService = class {
|
|
|
15307
15957
|
});
|
|
15308
15958
|
}
|
|
15309
15959
|
async deleteConversation(conversationId) {
|
|
15960
|
+
this.abortActiveRunsForConversation(conversationId);
|
|
15310
15961
|
return this.maintenance.deleteConversation(conversationId);
|
|
15311
15962
|
}
|
|
15312
|
-
|
|
15313
|
-
return this.maintenance.
|
|
15963
|
+
async archiveConversation(conversationId) {
|
|
15964
|
+
return this.maintenance.archiveConversation(conversationId);
|
|
15965
|
+
}
|
|
15966
|
+
async unarchiveConversation(conversationId) {
|
|
15967
|
+
return this.maintenance.unarchiveConversation(conversationId);
|
|
15968
|
+
}
|
|
15969
|
+
prepareClearAllConversationPlan(targetStatus) {
|
|
15970
|
+
return this.maintenance.prepareClearAllConversationPlan(targetStatus);
|
|
15314
15971
|
}
|
|
15315
15972
|
readClearAllConversationPlan(planId) {
|
|
15316
15973
|
return this.maintenance.readClearAllConversationPlan(planId);
|
|
@@ -15321,6 +15978,18 @@ var ConversationService = class {
|
|
|
15321
15978
|
startClearAllConversationPlan(planId) {
|
|
15322
15979
|
return this.maintenance.startClearAllConversationPlan(planId);
|
|
15323
15980
|
}
|
|
15981
|
+
prepareArchiveAllConversationPlan(input = {}) {
|
|
15982
|
+
return this.maintenance.prepareArchiveAllConversationPlan(input);
|
|
15983
|
+
}
|
|
15984
|
+
readArchiveAllConversationPlan(planId) {
|
|
15985
|
+
return this.maintenance.readArchiveAllConversationPlan(planId);
|
|
15986
|
+
}
|
|
15987
|
+
executeArchiveAllConversationPlan(planId) {
|
|
15988
|
+
return this.maintenance.executeArchiveAllConversationPlan(planId);
|
|
15989
|
+
}
|
|
15990
|
+
startArchiveAllConversationPlan(planId) {
|
|
15991
|
+
return this.maintenance.startArchiveAllConversationPlan(planId);
|
|
15992
|
+
}
|
|
15324
15993
|
async deleteConversations(conversationIds) {
|
|
15325
15994
|
return this.maintenance.deleteConversations(conversationIds);
|
|
15326
15995
|
}
|
|
@@ -15329,50 +15998,47 @@ var ConversationService = class {
|
|
|
15329
15998
|
const profileUid = input.profileUid?.trim() || null;
|
|
15330
15999
|
const deletedConversationIds = [];
|
|
15331
16000
|
for (const conversationId of await this.store.listConversationIds()) {
|
|
15332
|
-
const manifest = await this.store.readManifest(conversationId).catch(
|
|
15333
|
-
() => null
|
|
15334
|
-
);
|
|
16001
|
+
const manifest = await this.store.readManifest(conversationId).catch(() => null);
|
|
15335
16002
|
if (!manifest || !conversationMatchesProfile(manifest, profileName, profileUid)) {
|
|
15336
16003
|
continue;
|
|
15337
16004
|
}
|
|
15338
|
-
const deleted = await this.withConversationLock(
|
|
15339
|
-
|
|
15340
|
-
|
|
15341
|
-
|
|
15342
|
-
|
|
15343
|
-
|
|
15344
|
-
|
|
15345
|
-
|
|
15346
|
-
|
|
15347
|
-
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
)
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
|
|
15357
|
-
|
|
15358
|
-
|
|
15359
|
-
|
|
15360
|
-
|
|
15361
|
-
|
|
15362
|
-
|
|
15363
|
-
|
|
15364
|
-
|
|
15365
|
-
error: error instanceof Error ? error.message : String(error)
|
|
16005
|
+
const deleted = await this.withConversationLock(
|
|
16006
|
+
conversationId,
|
|
16007
|
+
async () => {
|
|
16008
|
+
const lockedManifest = await this.store.readManifest(conversationId).catch(() => null);
|
|
16009
|
+
if (!lockedManifest || !conversationMatchesProfile(lockedManifest, profileName, profileUid)) {
|
|
16010
|
+
return false;
|
|
16011
|
+
}
|
|
16012
|
+
this.abortActiveRunsForConversation(conversationId);
|
|
16013
|
+
const snapshot = await this.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
16014
|
+
const blobIds = /* @__PURE__ */ new Set([
|
|
16015
|
+
...collectBlobIds(snapshot),
|
|
16016
|
+
...await listConversationBlobIds(this.paths, conversationId).catch(
|
|
16017
|
+
() => []
|
|
16018
|
+
)
|
|
16019
|
+
]);
|
|
16020
|
+
for (const blobId of blobIds) {
|
|
16021
|
+
await pruneConversationBlobReference(
|
|
16022
|
+
this.paths,
|
|
16023
|
+
conversationId,
|
|
16024
|
+
blobId
|
|
16025
|
+
).catch((error) => {
|
|
16026
|
+
void this.logger.warn("profile_delete_blob_gc_failed", {
|
|
16027
|
+
profile: profileName,
|
|
16028
|
+
conversation_id: conversationId,
|
|
16029
|
+
blob_id: blobId,
|
|
16030
|
+
error: error instanceof Error ? error.message : String(error)
|
|
16031
|
+
});
|
|
15366
16032
|
});
|
|
15367
|
-
}
|
|
16033
|
+
}
|
|
16034
|
+
await removeConversationFiles(this.paths, conversationId);
|
|
16035
|
+
await removeConversationDeliveryStaging(
|
|
16036
|
+
this.paths,
|
|
16037
|
+
conversationId
|
|
16038
|
+
).catch(() => void 0);
|
|
16039
|
+
return true;
|
|
15368
16040
|
}
|
|
15369
|
-
|
|
15370
|
-
await removeConversationDeliveryStaging(
|
|
15371
|
-
this.paths,
|
|
15372
|
-
conversationId
|
|
15373
|
-
).catch(() => void 0);
|
|
15374
|
-
return true;
|
|
15375
|
-
});
|
|
16041
|
+
);
|
|
15376
16042
|
if (deleted) {
|
|
15377
16043
|
deletedConversationIds.push(conversationId);
|
|
15378
16044
|
}
|
|
@@ -15448,73 +16114,8 @@ function findApproval(snapshot, approvalId) {
|
|
|
15448
16114
|
return null;
|
|
15449
16115
|
}
|
|
15450
16116
|
|
|
15451
|
-
// src/identity/identity.ts
|
|
15452
|
-
import { generateKeyPairSync, randomUUID as randomUUID9, sign } from "crypto";
|
|
15453
|
-
import { mkdir as mkdir9, chmod as chmod2 } from "fs/promises";
|
|
15454
|
-
import { z } from "zod";
|
|
15455
|
-
var linkIdentitySchema = z.object({
|
|
15456
|
-
install_id: z.string().min(1),
|
|
15457
|
-
link_id: z.string().min(1).nullable().optional(),
|
|
15458
|
-
public_key_pem: z.string().min(1),
|
|
15459
|
-
private_key_pem: z.string().min(1),
|
|
15460
|
-
created_at: z.string().min(1),
|
|
15461
|
-
updated_at: z.string().min(1)
|
|
15462
|
-
});
|
|
15463
|
-
async function loadIdentity(paths = resolveRuntimePaths()) {
|
|
15464
|
-
const value = await readJsonFile(paths.identityFile);
|
|
15465
|
-
if (value === null) {
|
|
15466
|
-
return null;
|
|
15467
|
-
}
|
|
15468
|
-
return linkIdentitySchema.parse(value);
|
|
15469
|
-
}
|
|
15470
|
-
async function ensureIdentity(paths = resolveRuntimePaths()) {
|
|
15471
|
-
const existing = await loadIdentity(paths);
|
|
15472
|
-
if (existing) {
|
|
15473
|
-
return existing;
|
|
15474
|
-
}
|
|
15475
|
-
await mkdir9(paths.homeDir, { recursive: true, mode: 448 });
|
|
15476
|
-
await chmod2(paths.homeDir, 448).catch(() => void 0);
|
|
15477
|
-
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
15478
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15479
|
-
const identity = {
|
|
15480
|
-
install_id: `install_${randomUUID9().replaceAll("-", "")}`,
|
|
15481
|
-
link_id: null,
|
|
15482
|
-
public_key_pem: publicKey.export({ type: "spki", format: "pem" }).toString(),
|
|
15483
|
-
private_key_pem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
|
|
15484
|
-
created_at: now,
|
|
15485
|
-
updated_at: now
|
|
15486
|
-
};
|
|
15487
|
-
await writeJsonFile(paths.identityFile, identity);
|
|
15488
|
-
return identity;
|
|
15489
|
-
}
|
|
15490
|
-
async function saveAssignedLinkId(linkId, paths = resolveRuntimePaths()) {
|
|
15491
|
-
const identity = await ensureIdentity(paths);
|
|
15492
|
-
const next = {
|
|
15493
|
-
...identity,
|
|
15494
|
-
link_id: linkId,
|
|
15495
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
15496
|
-
};
|
|
15497
|
-
await writeJsonFile(paths.identityFile, next);
|
|
15498
|
-
return next;
|
|
15499
|
-
}
|
|
15500
|
-
function signRelayNonce(identity, nonce) {
|
|
15501
|
-
return signIdentityPayload(identity, nonce);
|
|
15502
|
-
}
|
|
15503
|
-
function signIdentityPayload(identity, payload) {
|
|
15504
|
-
const signature = sign(null, Buffer.from(payload, "utf8"), identity.private_key_pem);
|
|
15505
|
-
return signature.toString("base64url");
|
|
15506
|
-
}
|
|
15507
|
-
function getIdentityStatus(identity) {
|
|
15508
|
-
return {
|
|
15509
|
-
installId: identity.install_id,
|
|
15510
|
-
linkId: identity.link_id ?? null,
|
|
15511
|
-
hasPrivateKey: identity.private_key_pem.trim().length > 0,
|
|
15512
|
-
publicKeyPem: identity.public_key_pem
|
|
15513
|
-
};
|
|
15514
|
-
}
|
|
15515
|
-
|
|
15516
16117
|
// src/security/devices.ts
|
|
15517
|
-
import { randomBytes as randomBytes2, randomUUID as
|
|
16118
|
+
import { randomBytes as randomBytes2, randomUUID as randomUUID11, timingSafeEqual, createHash as createHash5 } from "crypto";
|
|
15518
16119
|
var ACCESS_TOKEN_TTL_MS = 15 * 60 * 1e3;
|
|
15519
16120
|
var REFRESH_TOKEN_TTL_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
15520
16121
|
var DEVICE_SEEN_WRITE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
@@ -15532,7 +16133,7 @@ async function createDeviceSession(input, paths = resolveRuntimePaths()) {
|
|
|
15532
16133
|
}
|
|
15533
16134
|
}
|
|
15534
16135
|
const device = {
|
|
15535
|
-
id: `dev_${
|
|
16136
|
+
id: `dev_${randomUUID11().replaceAll("-", "")}`,
|
|
15536
16137
|
label: normalizeDeviceLabel(input.label),
|
|
15537
16138
|
platform: normalizeDevicePlatform(input.platform),
|
|
15538
16139
|
model: normalizeDeviceModel(input.model),
|
|
@@ -15810,7 +16411,7 @@ function randomToken(prefix) {
|
|
|
15810
16411
|
return `${prefix}${randomBytes2(24).toString("base64url")}`;
|
|
15811
16412
|
}
|
|
15812
16413
|
function sha256(value) {
|
|
15813
|
-
return
|
|
16414
|
+
return createHash5("sha256").update(value).digest("hex");
|
|
15814
16415
|
}
|
|
15815
16416
|
function safeEqual(left, right) {
|
|
15816
16417
|
const leftBytes = Buffer.from(left);
|
|
@@ -16024,7 +16625,7 @@ function readPositiveInteger2(value) {
|
|
|
16024
16625
|
}
|
|
16025
16626
|
return void 0;
|
|
16026
16627
|
}
|
|
16027
|
-
function
|
|
16628
|
+
function readBoolean3(value) {
|
|
16028
16629
|
if (typeof value === "boolean") {
|
|
16029
16630
|
return value;
|
|
16030
16631
|
}
|
|
@@ -16145,7 +16746,7 @@ function readMessageAttachments(value) {
|
|
|
16145
16746
|
}
|
|
16146
16747
|
const kind = readAttachmentString(record.kind);
|
|
16147
16748
|
const type = readAttachmentString(record.type);
|
|
16148
|
-
const isVoiceNote =
|
|
16749
|
+
const isVoiceNote = readBoolean3(record.is_voice_note) ?? readBoolean3(record.isVoiceNote);
|
|
16149
16750
|
const durationMs = readPositiveInteger2(record.duration_ms) ?? readPositiveInteger2(record.durationMs);
|
|
16150
16751
|
const waveform = readAttachmentWaveform2(
|
|
16151
16752
|
record.waveform ?? record.waveform_samples ?? record.waveformSamples
|
|
@@ -16320,6 +16921,33 @@ function registerConversationRoutes(router, options) {
|
|
|
16320
16921
|
page: result.page
|
|
16321
16922
|
};
|
|
16322
16923
|
});
|
|
16924
|
+
router.get("/api/v1/conversations/archived", async (ctx) => {
|
|
16925
|
+
await authenticateRequest(ctx, paths);
|
|
16926
|
+
ctx.set("cache-control", "no-store");
|
|
16927
|
+
const result = await conversations.listArchivedConversationPage({
|
|
16928
|
+
limit: readLimit(ctx.query.limit),
|
|
16929
|
+
cursor: readQueryString(ctx.query.cursor) ?? readQueryString(ctx.query.after) ?? readQueryString(ctx.query.page_cursor)
|
|
16930
|
+
});
|
|
16931
|
+
ctx.body = {
|
|
16932
|
+
ok: true,
|
|
16933
|
+
conversations: result.conversations,
|
|
16934
|
+
page: result.page
|
|
16935
|
+
};
|
|
16936
|
+
});
|
|
16937
|
+
router.get("/api/v1/conversations/archived/search", async (ctx) => {
|
|
16938
|
+
await authenticateRequest(ctx, paths);
|
|
16939
|
+
ctx.set("cache-control", "no-store");
|
|
16940
|
+
const result = await conversations.searchArchivedConversationPage({
|
|
16941
|
+
limit: readLimit(ctx.query.limit),
|
|
16942
|
+
cursor: readQueryString(ctx.query.cursor) ?? readQueryString(ctx.query.after) ?? readQueryString(ctx.query.page_cursor),
|
|
16943
|
+
query: readQueryString(ctx.query.query) ?? readQueryString(ctx.query.q) ?? readQueryString(ctx.query.keyword) ?? ""
|
|
16944
|
+
});
|
|
16945
|
+
ctx.body = {
|
|
16946
|
+
ok: true,
|
|
16947
|
+
conversations: result.conversations,
|
|
16948
|
+
page: result.page
|
|
16949
|
+
};
|
|
16950
|
+
});
|
|
16323
16951
|
router.post("/api/v1/conversations", async (ctx) => {
|
|
16324
16952
|
await authenticateRequest(ctx, paths);
|
|
16325
16953
|
const body = await readJsonBody(ctx.req);
|
|
@@ -16353,14 +16981,20 @@ function registerConversationRoutes(router, options) {
|
|
|
16353
16981
|
const response = ctx.res;
|
|
16354
16982
|
let unsubscribe = () => {
|
|
16355
16983
|
};
|
|
16984
|
+
let notificationChain = Promise.resolve();
|
|
16356
16985
|
beginSseStream(ctx.req, response, {
|
|
16357
16986
|
onClose: () => unsubscribe()
|
|
16358
16987
|
});
|
|
16359
16988
|
unsubscribe = conversations.subscribeAll((event) => {
|
|
16360
|
-
if (notificationOnly
|
|
16989
|
+
if (!notificationOnly) {
|
|
16990
|
+
writeSseEvent(response, event);
|
|
16361
16991
|
return;
|
|
16362
16992
|
}
|
|
16363
|
-
|
|
16993
|
+
notificationChain = notificationChain.then(async () => {
|
|
16994
|
+
if (await conversations.shouldPublishNotificationEvent(event)) {
|
|
16995
|
+
writeSseEvent(response, event);
|
|
16996
|
+
}
|
|
16997
|
+
}).catch(() => void 0);
|
|
16364
16998
|
});
|
|
16365
16999
|
});
|
|
16366
17000
|
router.get("/api/v1/conversations/:conversationId/events", async (ctx) => {
|
|
@@ -16466,7 +17100,11 @@ function registerConversationRoutes(router, options) {
|
|
|
16466
17100
|
});
|
|
16467
17101
|
router.post("/api/v1/conversations/clear-plans", async (ctx) => {
|
|
16468
17102
|
await authenticateRequest(ctx, paths);
|
|
16469
|
-
const
|
|
17103
|
+
const body = await readJsonBody(ctx.req);
|
|
17104
|
+
const targetStatus = readConversationClearPlanTargetStatus(body);
|
|
17105
|
+
const plan = await conversations.prepareClearAllConversationPlan(
|
|
17106
|
+
targetStatus
|
|
17107
|
+
);
|
|
16470
17108
|
ctx.status = 201;
|
|
16471
17109
|
ctx.body = {
|
|
16472
17110
|
ok: true,
|
|
@@ -16495,6 +17133,46 @@ function registerConversationRoutes(router, options) {
|
|
|
16495
17133
|
};
|
|
16496
17134
|
}
|
|
16497
17135
|
);
|
|
17136
|
+
router.post("/api/v1/conversations/archive-plans", async (ctx) => {
|
|
17137
|
+
await authenticateRequest(ctx, paths);
|
|
17138
|
+
const body = await readJsonBody(ctx.req);
|
|
17139
|
+
const plan = await conversations.prepareArchiveAllConversationPlan({
|
|
17140
|
+
excludeConversationIds: readStringArray(
|
|
17141
|
+
body,
|
|
17142
|
+
"exclude_conversation_ids",
|
|
17143
|
+
"excludeConversationIds"
|
|
17144
|
+
) ?? []
|
|
17145
|
+
});
|
|
17146
|
+
ctx.status = 201;
|
|
17147
|
+
ctx.body = {
|
|
17148
|
+
ok: true,
|
|
17149
|
+
plan
|
|
17150
|
+
};
|
|
17151
|
+
});
|
|
17152
|
+
router.get("/api/v1/conversations/archive-plans/:planId", async (ctx) => {
|
|
17153
|
+
await authenticateRequest(ctx, paths);
|
|
17154
|
+
ctx.set("cache-control", "no-store");
|
|
17155
|
+
ctx.body = {
|
|
17156
|
+
ok: true,
|
|
17157
|
+
plan: await conversations.readArchiveAllConversationPlan(
|
|
17158
|
+
ctx.params.planId
|
|
17159
|
+
)
|
|
17160
|
+
};
|
|
17161
|
+
});
|
|
17162
|
+
router.post(
|
|
17163
|
+
"/api/v1/conversations/archive-plans/:planId/execute",
|
|
17164
|
+
async (ctx) => {
|
|
17165
|
+
await authenticateRequest(ctx, paths);
|
|
17166
|
+
const plan = await conversations.startArchiveAllConversationPlan(
|
|
17167
|
+
ctx.params.planId
|
|
17168
|
+
);
|
|
17169
|
+
ctx.status = plan.status === "completed" ? 200 : 202;
|
|
17170
|
+
ctx.body = {
|
|
17171
|
+
ok: true,
|
|
17172
|
+
plan
|
|
17173
|
+
};
|
|
17174
|
+
}
|
|
17175
|
+
);
|
|
16498
17176
|
router.delete("/api/v1/conversations", async (ctx) => {
|
|
16499
17177
|
await authenticateRequest(ctx, paths);
|
|
16500
17178
|
const body = await readJsonBody(ctx.req);
|
|
@@ -16568,6 +17246,25 @@ function registerConversationRoutes(router, options) {
|
|
|
16568
17246
|
};
|
|
16569
17247
|
}
|
|
16570
17248
|
);
|
|
17249
|
+
router.post("/api/v1/conversations/:conversationId/archive", async (ctx) => {
|
|
17250
|
+
await authenticateRequest(ctx, paths);
|
|
17251
|
+
ctx.body = {
|
|
17252
|
+
ok: true,
|
|
17253
|
+
...await conversations.archiveConversation(ctx.params.conversationId)
|
|
17254
|
+
};
|
|
17255
|
+
});
|
|
17256
|
+
router.post(
|
|
17257
|
+
"/api/v1/conversations/:conversationId/unarchive",
|
|
17258
|
+
async (ctx) => {
|
|
17259
|
+
await authenticateRequest(ctx, paths);
|
|
17260
|
+
ctx.body = {
|
|
17261
|
+
ok: true,
|
|
17262
|
+
...await conversations.unarchiveConversation(
|
|
17263
|
+
ctx.params.conversationId
|
|
17264
|
+
)
|
|
17265
|
+
};
|
|
17266
|
+
}
|
|
17267
|
+
);
|
|
16571
17268
|
router.delete("/api/v1/conversations/:conversationId", async (ctx) => {
|
|
16572
17269
|
await authenticateRequest(ctx, paths);
|
|
16573
17270
|
ctx.body = {
|
|
@@ -16601,10 +17298,7 @@ function registerConversationRoutes(router, options) {
|
|
|
16601
17298
|
ctx.set("content-type", blob.mime);
|
|
16602
17299
|
ctx.set("content-length", String(blob.size));
|
|
16603
17300
|
ctx.set("cache-control", "private, max-age=86400");
|
|
16604
|
-
ctx.set(
|
|
16605
|
-
"content-disposition",
|
|
16606
|
-
contentDispositionInline(blob.filename)
|
|
16607
|
-
);
|
|
17301
|
+
ctx.set("content-disposition", contentDispositionInline(blob.filename));
|
|
16608
17302
|
ctx.body = blob.bytes;
|
|
16609
17303
|
}
|
|
16610
17304
|
);
|
|
@@ -16627,6 +17321,17 @@ function resolveConversationEventCursor(input) {
|
|
|
16627
17321
|
const headerAfter = readNonNegativeIntegerHeader(input.lastEventIdHeader) ?? 0;
|
|
16628
17322
|
return Math.max(queryAfter, headerAfter);
|
|
16629
17323
|
}
|
|
17324
|
+
function readConversationClearPlanTargetStatus(body) {
|
|
17325
|
+
const raw = readString14(body, "target_status") ?? readString14(body, "targetStatus") ?? "active";
|
|
17326
|
+
if (raw === "active" || raw === "archived") {
|
|
17327
|
+
return raw;
|
|
17328
|
+
}
|
|
17329
|
+
throw new LinkHttpError(
|
|
17330
|
+
400,
|
|
17331
|
+
"conversation_clear_plan_target_invalid",
|
|
17332
|
+
"Conversation clear plan target status is invalid"
|
|
17333
|
+
);
|
|
17334
|
+
}
|
|
16630
17335
|
function readNonNegativeIntegerHeader(value) {
|
|
16631
17336
|
const raw = Array.isArray(value) ? value[0] : value;
|
|
16632
17337
|
if (!raw) {
|
|
@@ -16663,16 +17368,6 @@ function encodeRfc5987Value(value) {
|
|
|
16663
17368
|
(char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`
|
|
16664
17369
|
);
|
|
16665
17370
|
}
|
|
16666
|
-
function isConversationNotificationEvent(event) {
|
|
16667
|
-
const type = event.type.toLowerCase();
|
|
16668
|
-
return type === "conversation.created" || type === "conversation.updated" || type === "conversation.deleted" || type === "message.created" || type === "message.completed" || type === "message.failed" || type === "run.completed" || type === "run.failed" || type === "run.cancelled" || type === "run.canceled" || type === "approval.requested" || readPayloadBool(event.payload, "requires_action") || readPayloadBool(event.payload, "requires_user_action") || readPayloadBool(event.payload, "requires_approval");
|
|
16669
|
-
}
|
|
16670
|
-
function readPayloadBool(payload, key) {
|
|
16671
|
-
if (!payload || typeof payload !== "object") {
|
|
16672
|
-
return false;
|
|
16673
|
-
}
|
|
16674
|
-
return payload[key] === true;
|
|
16675
|
-
}
|
|
16676
17371
|
|
|
16677
17372
|
// src/http/middleware/error-handler.ts
|
|
16678
17373
|
function createHttpErrorMiddleware(logger) {
|
|
@@ -16719,7 +17414,7 @@ function createHttpErrorMiddleware(logger) {
|
|
|
16719
17414
|
// src/hermes/profiles.ts
|
|
16720
17415
|
import { execFile as execFile4 } from "child_process";
|
|
16721
17416
|
import { readdir as readdir9, readFile as readFile12, rename as rename3, stat as stat12 } from "fs/promises";
|
|
16722
|
-
import
|
|
17417
|
+
import path19 from "path";
|
|
16723
17418
|
import { setTimeout as delay2 } from "timers/promises";
|
|
16724
17419
|
import { promisify as promisify4 } from "util";
|
|
16725
17420
|
import YAML2 from "yaml";
|
|
@@ -16830,7 +17525,7 @@ async function readHermesProfileCapabilities(name) {
|
|
|
16830
17525
|
return {
|
|
16831
17526
|
defaultModel: listedModels?.defaultModel ?? null,
|
|
16832
17527
|
modelCount: listedModels?.models.length ?? 0,
|
|
16833
|
-
skillCount: await countSkills(
|
|
17528
|
+
skillCount: await countSkills(path19.join(profileDir, "skills")).catch(
|
|
16834
17529
|
() => 0
|
|
16835
17530
|
),
|
|
16836
17531
|
toolCount: await countConfiguredTools(name).catch(() => 0)
|
|
@@ -17045,7 +17740,7 @@ async function countSkills(root) {
|
|
|
17045
17740
|
);
|
|
17046
17741
|
let count = 0;
|
|
17047
17742
|
for (const entry of entries) {
|
|
17048
|
-
const entryPath =
|
|
17743
|
+
const entryPath = path19.join(root, entry.name);
|
|
17049
17744
|
if (entry.name === ".git" || entry.name === ".hub") {
|
|
17050
17745
|
continue;
|
|
17051
17746
|
}
|
|
@@ -17388,7 +18083,7 @@ function readCronJobUpdateInput(body) {
|
|
|
17388
18083
|
if (repeat !== void 0) {
|
|
17389
18084
|
input.repeat = repeat;
|
|
17390
18085
|
}
|
|
17391
|
-
const enabled =
|
|
18086
|
+
const enabled = readBoolean3(body.enabled);
|
|
17392
18087
|
if (enabled !== void 0) {
|
|
17393
18088
|
input.enabled = enabled;
|
|
17394
18089
|
}
|
|
@@ -17652,7 +18347,7 @@ function readModelConfigInput(body) {
|
|
|
17652
18347
|
body.context_length ?? body.contextLength
|
|
17653
18348
|
),
|
|
17654
18349
|
keyEnv: readString14(body, "key_env") ?? readString14(body, "keyEnv") ?? void 0,
|
|
17655
|
-
setDefault:
|
|
18350
|
+
setDefault: readBoolean3(body.set_default ?? body.setDefault),
|
|
17656
18351
|
reasoningEffort: readString14(body, "reasoning_effort") ?? readString14(body, "reasoningEffort") ?? void 0
|
|
17657
18352
|
};
|
|
17658
18353
|
}
|
|
@@ -17663,7 +18358,7 @@ function readModelDefaultsInput(body) {
|
|
|
17663
18358
|
};
|
|
17664
18359
|
}
|
|
17665
18360
|
function shouldReloadGatewayAfterModelConfigChange(body) {
|
|
17666
|
-
const explicit =
|
|
18361
|
+
const explicit = readBoolean3(body.reload_gateway ?? body.reloadGateway) ?? (readBoolean3(body.skip_gateway_reload ?? body.skipGatewayReload) === true ? false : void 0);
|
|
17667
18362
|
return explicit ?? true;
|
|
17668
18363
|
}
|
|
17669
18364
|
function markModelConfigAppliedWithoutGatewayReload(result) {
|
|
@@ -17742,12 +18437,12 @@ import { spawn as spawn2 } from "child_process";
|
|
|
17742
18437
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
17743
18438
|
import {
|
|
17744
18439
|
cp,
|
|
17745
|
-
mkdir as
|
|
18440
|
+
mkdir as mkdir11,
|
|
17746
18441
|
readFile as readFile13,
|
|
17747
18442
|
rm as rm6,
|
|
17748
18443
|
stat as stat13
|
|
17749
18444
|
} from "fs/promises";
|
|
17750
|
-
import
|
|
18445
|
+
import path20 from "path";
|
|
17751
18446
|
import YAML3 from "yaml";
|
|
17752
18447
|
var PROFILE_CREATE_LOG_FILE = "profile-create.log";
|
|
17753
18448
|
var PROFILE_CREATE_LOG_MAX_FILES = 3;
|
|
@@ -17795,7 +18490,7 @@ async function startHermesProfileCreation(input, options) {
|
|
|
17795
18490
|
signal: null,
|
|
17796
18491
|
error: null
|
|
17797
18492
|
};
|
|
17798
|
-
await
|
|
18493
|
+
await mkdir11(options.paths.runDir, { recursive: true, mode: 448 });
|
|
17799
18494
|
await writeProfileCreationState(options.paths, started);
|
|
17800
18495
|
await writer.write(`
|
|
17801
18496
|
=== profile creation started ${startedAt} ===
|
|
@@ -18198,7 +18893,7 @@ function collectEnvKeys(value, keys = /* @__PURE__ */ new Set()) {
|
|
|
18198
18893
|
return keys;
|
|
18199
18894
|
}
|
|
18200
18895
|
async function writeEnvValues(profileName, values) {
|
|
18201
|
-
const envPath =
|
|
18896
|
+
const envPath = path20.join(resolveHermesProfileDir(profileName), ".env");
|
|
18202
18897
|
const existingRaw = await readFile13(envPath, "utf8").catch((error) => {
|
|
18203
18898
|
if (isNodeError15(error, "ENOENT")) {
|
|
18204
18899
|
return "";
|
|
@@ -18235,8 +18930,8 @@ async function writeEnvValues(profileName, values) {
|
|
|
18235
18930
|
await atomicWriteFilePreservingMetadata(envPath, nextRaw);
|
|
18236
18931
|
}
|
|
18237
18932
|
async function copySkills(sourceProfile, targetProfile) {
|
|
18238
|
-
const sourceSkills =
|
|
18239
|
-
const targetSkills =
|
|
18933
|
+
const sourceSkills = path20.join(resolveHermesProfileDir(sourceProfile), "skills");
|
|
18934
|
+
const targetSkills = path20.join(resolveHermesProfileDir(targetProfile), "skills");
|
|
18240
18935
|
if (!await pathExists2(sourceSkills)) {
|
|
18241
18936
|
return;
|
|
18242
18937
|
}
|
|
@@ -18331,10 +19026,10 @@ async function readProfileCreationLogLines(paths) {
|
|
|
18331
19026
|
);
|
|
18332
19027
|
}
|
|
18333
19028
|
function profileCreationStatePath(paths) {
|
|
18334
|
-
return
|
|
19029
|
+
return path20.join(paths.runDir, "profile-create-state.json");
|
|
18335
19030
|
}
|
|
18336
19031
|
function profileCreationLogPath(paths) {
|
|
18337
|
-
return
|
|
19032
|
+
return path20.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
|
|
18338
19033
|
}
|
|
18339
19034
|
async function clearProfileCreationLogFiles(paths) {
|
|
18340
19035
|
const primary = profileCreationLogPath(paths);
|
|
@@ -18499,7 +19194,7 @@ function readProfilePermissionsInput(body) {
|
|
|
18499
19194
|
containerDisk: readPositiveInteger2(
|
|
18500
19195
|
terminal.container_disk ?? terminal.containerDisk
|
|
18501
19196
|
),
|
|
18502
|
-
containerPersistent:
|
|
19197
|
+
containerPersistent: readBoolean3(
|
|
18503
19198
|
terminal.container_persistent ?? terminal.containerPersistent
|
|
18504
19199
|
)
|
|
18505
19200
|
};
|
|
@@ -18511,7 +19206,7 @@ function readProfilePermissionsInput(body) {
|
|
|
18511
19206
|
toolsets.enabled_toolsets ?? toolsets.enabledToolsets ?? toolsets.enabled,
|
|
18512
19207
|
"toolsets.enabled"
|
|
18513
19208
|
) ?? void 0,
|
|
18514
|
-
mcpEnabled:
|
|
19209
|
+
mcpEnabled: readBoolean3(toolsets.mcp_enabled ?? toolsets.mcpEnabled)
|
|
18515
19210
|
};
|
|
18516
19211
|
}
|
|
18517
19212
|
if (Object.keys(input).length === 0) {
|
|
@@ -18608,7 +19303,7 @@ import {
|
|
|
18608
19303
|
readFile as readFile14,
|
|
18609
19304
|
stat as stat14
|
|
18610
19305
|
} from "fs/promises";
|
|
18611
|
-
import
|
|
19306
|
+
import path21 from "path";
|
|
18612
19307
|
import YAML4 from "yaml";
|
|
18613
19308
|
var ENTRY_DELIMITER = "\n\xA7\n";
|
|
18614
19309
|
var DEFAULT_MEMORY_LIMIT = 2200;
|
|
@@ -18825,7 +19520,7 @@ async function saveProviderSettings(profileName, provider, patch) {
|
|
|
18825
19520
|
if (provider === "hindsight") {
|
|
18826
19521
|
await patchJsonProviderConfig(
|
|
18827
19522
|
profileName,
|
|
18828
|
-
|
|
19523
|
+
path21.join("hindsight", "config.json"),
|
|
18829
19524
|
{
|
|
18830
19525
|
mode: patch.mode,
|
|
18831
19526
|
api_url: patch.apiUrl,
|
|
@@ -18992,7 +19687,7 @@ async function patchHermesMemoryProvider(profileName, provider) {
|
|
|
18992
19687
|
await atomicWriteFilePreservingMetadata(configPath, document.toString());
|
|
18993
19688
|
}
|
|
18994
19689
|
function resolveMemoryDir(profileName) {
|
|
18995
|
-
return
|
|
19690
|
+
return path21.join(resolveHermesProfileDir(profileName), "memories");
|
|
18996
19691
|
}
|
|
18997
19692
|
async function readMemoryStore(profileName, target, limits) {
|
|
18998
19693
|
const filePath = memoryFilePath(profileName, target);
|
|
@@ -19053,7 +19748,7 @@ async function writeMemoryEntries(profileName, target, entries) {
|
|
|
19053
19748
|
);
|
|
19054
19749
|
}
|
|
19055
19750
|
function memoryFilePath(profileName, target) {
|
|
19056
|
-
return
|
|
19751
|
+
return path21.join(
|
|
19057
19752
|
resolveMemoryDir(profileName),
|
|
19058
19753
|
target === "user" ? "USER.md" : "MEMORY.md"
|
|
19059
19754
|
);
|
|
@@ -19113,7 +19808,7 @@ async function readCustomProviderSetupSummary(profileName) {
|
|
|
19113
19808
|
configurable: true,
|
|
19114
19809
|
configured: true,
|
|
19115
19810
|
configurationIssue: null,
|
|
19116
|
-
providerConfigPath:
|
|
19811
|
+
providerConfigPath: path21.join(
|
|
19117
19812
|
resolveHermesProfileDir(profileName),
|
|
19118
19813
|
"<provider>.json"
|
|
19119
19814
|
),
|
|
@@ -19467,7 +20162,7 @@ async function readProviderSettings(profileName, provider) {
|
|
|
19467
20162
|
stringSetting(
|
|
19468
20163
|
"dbPath",
|
|
19469
20164
|
"SQLite \u6570\u636E\u5E93\u8DEF\u5F84",
|
|
19470
|
-
config.db_path ??
|
|
20165
|
+
config.db_path ?? path21.join(resolveHermesProfileDir(profileName), "memory_store.db")
|
|
19471
20166
|
),
|
|
19472
20167
|
booleanSetting("autoExtract", "\u4F1A\u8BDD\u7ED3\u675F\u81EA\u52A8\u62BD\u53D6", config.auto_extract ?? false),
|
|
19473
20168
|
numberSetting("defaultTrust", "\u9ED8\u8BA4\u4FE1\u4EFB\u5206", config.default_trust ?? 0.5),
|
|
@@ -19490,7 +20185,7 @@ async function readProviderSettings(profileName, provider) {
|
|
|
19490
20185
|
stringSetting(
|
|
19491
20186
|
"workingDirectory",
|
|
19492
20187
|
"\u5DE5\u4F5C\u76EE\u5F55",
|
|
19493
|
-
|
|
20188
|
+
path21.join(resolveHermesProfileDir(profileName), "byterover"),
|
|
19494
20189
|
false
|
|
19495
20190
|
)
|
|
19496
20191
|
];
|
|
@@ -19499,16 +20194,16 @@ async function readProviderSettings(profileName, provider) {
|
|
|
19499
20194
|
}
|
|
19500
20195
|
function memoryProviderConfigPath(profileName, provider) {
|
|
19501
20196
|
if (provider === "honcho") {
|
|
19502
|
-
return
|
|
20197
|
+
return path21.join(resolveHermesProfileDir(profileName), "honcho.json");
|
|
19503
20198
|
}
|
|
19504
20199
|
if (provider === "mem0") {
|
|
19505
|
-
return
|
|
20200
|
+
return path21.join(resolveHermesProfileDir(profileName), "mem0.json");
|
|
19506
20201
|
}
|
|
19507
20202
|
if (provider === "supermemory") {
|
|
19508
|
-
return
|
|
20203
|
+
return path21.join(resolveHermesProfileDir(profileName), "supermemory.json");
|
|
19509
20204
|
}
|
|
19510
20205
|
if (provider === "hindsight") {
|
|
19511
|
-
return
|
|
20206
|
+
return path21.join(
|
|
19512
20207
|
resolveHermesProfileDir(profileName),
|
|
19513
20208
|
"hindsight",
|
|
19514
20209
|
"config.json"
|
|
@@ -19517,13 +20212,13 @@ function memoryProviderConfigPath(profileName, provider) {
|
|
|
19517
20212
|
return null;
|
|
19518
20213
|
}
|
|
19519
20214
|
function customProviderConfigPath(profileName, provider) {
|
|
19520
|
-
return
|
|
20215
|
+
return path21.join(
|
|
19521
20216
|
resolveHermesProfileDir(profileName),
|
|
19522
20217
|
`${normalizeCustomProviderId(provider)}.json`
|
|
19523
20218
|
);
|
|
19524
20219
|
}
|
|
19525
20220
|
function customProviderRegistryPath(profileName) {
|
|
19526
|
-
return
|
|
20221
|
+
return path21.join(
|
|
19527
20222
|
resolveHermesProfileDir(profileName),
|
|
19528
20223
|
CUSTOM_PROVIDER_REGISTRY_FILE
|
|
19529
20224
|
);
|
|
@@ -19575,7 +20270,7 @@ async function saveCustomProviderRegistryEntry(profileName, provider) {
|
|
|
19575
20270
|
);
|
|
19576
20271
|
}
|
|
19577
20272
|
async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
19578
|
-
const pluginsDir =
|
|
20273
|
+
const pluginsDir = path21.join(resolveHermesProfileDir(profileName), "plugins");
|
|
19579
20274
|
const entries = await readdir10(pluginsDir, { withFileTypes: true }).catch(
|
|
19580
20275
|
(error) => {
|
|
19581
20276
|
if (isNodeError16(error, "ENOENT")) {
|
|
@@ -19595,7 +20290,7 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
|
19595
20290
|
} catch {
|
|
19596
20291
|
continue;
|
|
19597
20292
|
}
|
|
19598
|
-
const providerDir =
|
|
20293
|
+
const providerDir = path21.join(pluginsDir, entry.name);
|
|
19599
20294
|
if (!await isMemoryProviderPluginDir(providerDir)) {
|
|
19600
20295
|
continue;
|
|
19601
20296
|
}
|
|
@@ -19609,7 +20304,7 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
|
19609
20304
|
return descriptors;
|
|
19610
20305
|
}
|
|
19611
20306
|
async function isUserMemoryProviderInstalled(profileName, provider) {
|
|
19612
|
-
const providerDir =
|
|
20307
|
+
const providerDir = path21.join(
|
|
19613
20308
|
resolveHermesProfileDir(profileName),
|
|
19614
20309
|
"plugins",
|
|
19615
20310
|
normalizeCustomProviderId(provider)
|
|
@@ -19617,7 +20312,7 @@ async function isUserMemoryProviderInstalled(profileName, provider) {
|
|
|
19617
20312
|
return isMemoryProviderPluginDir(providerDir);
|
|
19618
20313
|
}
|
|
19619
20314
|
async function isMemoryProviderPluginDir(providerDir) {
|
|
19620
|
-
const source = await readFile14(
|
|
20315
|
+
const source = await readFile14(path21.join(providerDir, "__init__.py"), "utf8").catch(
|
|
19621
20316
|
(error) => {
|
|
19622
20317
|
if (isNodeError16(error, "ENOENT")) {
|
|
19623
20318
|
return "";
|
|
@@ -19629,7 +20324,7 @@ async function isMemoryProviderPluginDir(providerDir) {
|
|
|
19629
20324
|
return sample.includes("register_memory_provider") || sample.includes("MemoryProvider");
|
|
19630
20325
|
}
|
|
19631
20326
|
async function readPluginMetadata(providerDir) {
|
|
19632
|
-
const raw = await readFile14(
|
|
20327
|
+
const raw = await readFile14(path21.join(providerDir, "plugin.yaml"), "utf8").catch(
|
|
19633
20328
|
(error) => {
|
|
19634
20329
|
if (isNodeError16(error, "ENOENT")) {
|
|
19635
20330
|
return "";
|
|
@@ -19641,10 +20336,10 @@ async function readPluginMetadata(providerDir) {
|
|
|
19641
20336
|
}
|
|
19642
20337
|
async function resolveByteRoverCli() {
|
|
19643
20338
|
const candidates = [
|
|
19644
|
-
...(process.env.PATH ?? "").split(
|
|
19645
|
-
|
|
20339
|
+
...(process.env.PATH ?? "").split(path21.delimiter).filter(Boolean).map((dir) => path21.join(dir, "brv")),
|
|
20340
|
+
path21.join(process.env.HOME ?? "", ".brv-cli", "bin", "brv"),
|
|
19646
20341
|
"/usr/local/bin/brv",
|
|
19647
|
-
|
|
20342
|
+
path21.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
|
|
19648
20343
|
].filter(Boolean);
|
|
19649
20344
|
for (const candidate of candidates) {
|
|
19650
20345
|
const found = await access3(candidate).then(() => true).catch(() => false);
|
|
@@ -19705,7 +20400,7 @@ async function patchHermesMemoryEnv(profileName, patch) {
|
|
|
19705
20400
|
if (entries.length === 0) {
|
|
19706
20401
|
return;
|
|
19707
20402
|
}
|
|
19708
|
-
const envPath =
|
|
20403
|
+
const envPath = path21.join(resolveHermesProfileDir(profileName), ".env");
|
|
19709
20404
|
const existingRaw = await readFile14(envPath, "utf8").catch((error) => {
|
|
19710
20405
|
if (isNodeError16(error, "ENOENT")) {
|
|
19711
20406
|
return "";
|
|
@@ -19779,7 +20474,7 @@ async function readActiveMemoryProvider(profileName) {
|
|
|
19779
20474
|
return provider;
|
|
19780
20475
|
}
|
|
19781
20476
|
async function patchJsonProviderConfig(profileName, relativePath, patch) {
|
|
19782
|
-
const configPath =
|
|
20477
|
+
const configPath = path21.join(
|
|
19783
20478
|
resolveHermesProfileDir(profileName),
|
|
19784
20479
|
relativePath
|
|
19785
20480
|
);
|
|
@@ -19808,7 +20503,7 @@ async function readJsonObject(filePath) {
|
|
|
19808
20503
|
} catch {
|
|
19809
20504
|
throw new HermesMemoryError(
|
|
19810
20505
|
"memory_provider_config_invalid",
|
|
19811
|
-
`${
|
|
20506
|
+
`${path21.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
|
|
19812
20507
|
);
|
|
19813
20508
|
}
|
|
19814
20509
|
}
|
|
@@ -19816,7 +20511,7 @@ function booleanSetting(key, label, value) {
|
|
|
19816
20511
|
return {
|
|
19817
20512
|
key,
|
|
19818
20513
|
label,
|
|
19819
|
-
value:
|
|
20514
|
+
value: readBoolean4(value) ?? false,
|
|
19820
20515
|
editable: true,
|
|
19821
20516
|
kind: "boolean"
|
|
19822
20517
|
};
|
|
@@ -19924,7 +20619,7 @@ function readPositiveInteger3(value) {
|
|
|
19924
20619
|
const numberValue = typeof value === "number" ? value : typeof value === "string" ? Number(value.trim()) : NaN;
|
|
19925
20620
|
return Number.isFinite(numberValue) && numberValue > 0 ? Math.floor(numberValue) : void 0;
|
|
19926
20621
|
}
|
|
19927
|
-
function
|
|
20622
|
+
function readBoolean4(value) {
|
|
19928
20623
|
if (typeof value === "boolean") {
|
|
19929
20624
|
return value;
|
|
19930
20625
|
}
|
|
@@ -20143,15 +20838,15 @@ function readMemorySettingsPatch(body) {
|
|
|
20143
20838
|
if (containerTag !== void 0) {
|
|
20144
20839
|
input.containerTag = containerTag;
|
|
20145
20840
|
}
|
|
20146
|
-
const autoRecall =
|
|
20841
|
+
const autoRecall = readBoolean3(body.auto_recall ?? body.autoRecall);
|
|
20147
20842
|
if (autoRecall !== void 0) {
|
|
20148
20843
|
input.autoRecall = autoRecall;
|
|
20149
20844
|
}
|
|
20150
|
-
const autoCapture =
|
|
20845
|
+
const autoCapture = readBoolean3(body.auto_capture ?? body.autoCapture);
|
|
20151
20846
|
if (autoCapture !== void 0) {
|
|
20152
20847
|
input.autoCapture = autoCapture;
|
|
20153
20848
|
}
|
|
20154
|
-
const autoRetain =
|
|
20849
|
+
const autoRetain = readBoolean3(body.auto_retain ?? body.autoRetain);
|
|
20155
20850
|
if (autoRetain !== void 0) {
|
|
20156
20851
|
input.autoRetain = autoRetain;
|
|
20157
20852
|
}
|
|
@@ -20221,7 +20916,7 @@ function readMemorySettingsPatch(body) {
|
|
|
20221
20916
|
if (writeFrequency) {
|
|
20222
20917
|
input.writeFrequency = writeFrequency;
|
|
20223
20918
|
}
|
|
20224
|
-
const saveMessages =
|
|
20919
|
+
const saveMessages = readBoolean3(body.save_messages ?? body.saveMessages);
|
|
20225
20920
|
if (saveMessages !== void 0) {
|
|
20226
20921
|
input.saveMessages = saveMessages;
|
|
20227
20922
|
}
|
|
@@ -20261,7 +20956,7 @@ function readMemorySettingsPatch(body) {
|
|
|
20261
20956
|
if (agentId !== void 0) {
|
|
20262
20957
|
input.agentId = agentId;
|
|
20263
20958
|
}
|
|
20264
|
-
const rerank =
|
|
20959
|
+
const rerank = readBoolean3(body.rerank);
|
|
20265
20960
|
if (rerank !== void 0) {
|
|
20266
20961
|
input.rerank = rerank;
|
|
20267
20962
|
}
|
|
@@ -20285,7 +20980,7 @@ function readMemorySettingsPatch(body) {
|
|
|
20285
20980
|
if (dbPath !== void 0) {
|
|
20286
20981
|
input.dbPath = dbPath;
|
|
20287
20982
|
}
|
|
20288
|
-
const autoExtract =
|
|
20983
|
+
const autoExtract = readBoolean3(body.auto_extract ?? body.autoExtract);
|
|
20289
20984
|
if (autoExtract !== void 0) {
|
|
20290
20985
|
input.autoExtract = autoExtract;
|
|
20291
20986
|
}
|
|
@@ -20354,7 +21049,7 @@ function toMemoryHttpError(error) {
|
|
|
20354
21049
|
|
|
20355
21050
|
// src/hermes/skills.ts
|
|
20356
21051
|
import { readFile as readFile15, readdir as readdir11 } from "fs/promises";
|
|
20357
|
-
import
|
|
21052
|
+
import path22 from "path";
|
|
20358
21053
|
import YAML5 from "yaml";
|
|
20359
21054
|
var HermesSkillNotFoundError = class extends Error {
|
|
20360
21055
|
constructor(skillName) {
|
|
@@ -20368,7 +21063,7 @@ var EXCLUDED_SKILL_DIRS = /* @__PURE__ */ new Set([".git", ".github", ".hub"]);
|
|
|
20368
21063
|
async function listHermesProfileSkills(profileName, paths = resolveRuntimePaths()) {
|
|
20369
21064
|
const profile = await readExistingProfile(profileName, paths);
|
|
20370
21065
|
const profileDir = resolveHermesProfileDir(profile.name);
|
|
20371
|
-
const skillsRoot =
|
|
21066
|
+
const skillsRoot = path22.join(profileDir, "skills");
|
|
20372
21067
|
const [skillFiles, disabled, provenance] = await Promise.all([
|
|
20373
21068
|
findSkillFiles(skillsRoot),
|
|
20374
21069
|
readDisabledSkillNames(resolveHermesConfigPath(profile.name)),
|
|
@@ -20459,7 +21154,7 @@ async function collectSkillFiles(directory, results) {
|
|
|
20459
21154
|
if (EXCLUDED_SKILL_DIRS.has(entry.name)) {
|
|
20460
21155
|
continue;
|
|
20461
21156
|
}
|
|
20462
|
-
const entryPath =
|
|
21157
|
+
const entryPath = path22.join(directory, entry.name);
|
|
20463
21158
|
if (entry.isDirectory()) {
|
|
20464
21159
|
await collectSkillFiles(entryPath, results);
|
|
20465
21160
|
continue;
|
|
@@ -20481,10 +21176,10 @@ async function readSkillMetadata(input) {
|
|
|
20481
21176
|
if (raw === null) {
|
|
20482
21177
|
return null;
|
|
20483
21178
|
}
|
|
20484
|
-
const skillDir =
|
|
21179
|
+
const skillDir = path22.dirname(input.skillFile);
|
|
20485
21180
|
const { frontmatter, body } = parseSkillDocument(raw.slice(0, 4e3));
|
|
20486
21181
|
const name = normalizeSkillName(
|
|
20487
|
-
readString16(frontmatter.name) ??
|
|
21182
|
+
readString16(frontmatter.name) ?? path22.basename(skillDir)
|
|
20488
21183
|
);
|
|
20489
21184
|
if (!name) {
|
|
20490
21185
|
return null;
|
|
@@ -20503,7 +21198,7 @@ async function readSkillMetadata(input) {
|
|
|
20503
21198
|
enabled: !input.disabled.has(name),
|
|
20504
21199
|
source: provenance.source,
|
|
20505
21200
|
trust: provenance.trust,
|
|
20506
|
-
relativePath:
|
|
21201
|
+
relativePath: path22.relative(input.skillsRoot, skillDir)
|
|
20507
21202
|
};
|
|
20508
21203
|
}
|
|
20509
21204
|
function parseSkillDocument(raw) {
|
|
@@ -20524,8 +21219,8 @@ function parseSkillDocument(raw) {
|
|
|
20524
21219
|
}
|
|
20525
21220
|
}
|
|
20526
21221
|
function categoryFromPath(skillsRoot, skillFile) {
|
|
20527
|
-
const relative =
|
|
20528
|
-
const parts = relative.split(
|
|
21222
|
+
const relative = path22.relative(skillsRoot, skillFile);
|
|
21223
|
+
const parts = relative.split(path22.sep).filter(Boolean);
|
|
20529
21224
|
return parts.length >= 3 ? parts[0] : null;
|
|
20530
21225
|
}
|
|
20531
21226
|
function firstBodyDescription(body) {
|
|
@@ -20572,7 +21267,7 @@ async function readSkillProvenance(root) {
|
|
|
20572
21267
|
return provenance;
|
|
20573
21268
|
}
|
|
20574
21269
|
async function readBundledSkillNames(root) {
|
|
20575
|
-
const raw = await readFile15(
|
|
21270
|
+
const raw = await readFile15(path22.join(root, ".bundled_manifest"), "utf8").catch(
|
|
20576
21271
|
(error) => {
|
|
20577
21272
|
if (isNodeError17(error, "ENOENT")) {
|
|
20578
21273
|
return "";
|
|
@@ -20595,7 +21290,7 @@ async function readBundledSkillNames(root) {
|
|
|
20595
21290
|
return names;
|
|
20596
21291
|
}
|
|
20597
21292
|
async function readHubInstalledSkills(root) {
|
|
20598
|
-
const raw = await readFile15(
|
|
21293
|
+
const raw = await readFile15(path22.join(root, ".hub", "lock.json"), "utf8").catch(
|
|
20599
21294
|
(error) => {
|
|
20600
21295
|
if (isNodeError17(error, "ENOENT")) {
|
|
20601
21296
|
return "";
|
|
@@ -20731,7 +21426,7 @@ function registerProfileSkillRoutes(router, options) {
|
|
|
20731
21426
|
router.patch("/api/v1/profiles/:name/skills/:skillName", async (ctx) => {
|
|
20732
21427
|
await authenticateRequest(ctx, paths);
|
|
20733
21428
|
const body = await readJsonBody(ctx.req);
|
|
20734
|
-
const enabled =
|
|
21429
|
+
const enabled = readBoolean3(body.enabled);
|
|
20735
21430
|
if (enabled === void 0) {
|
|
20736
21431
|
throw new LinkHttpError(
|
|
20737
21432
|
400,
|
|
@@ -21027,7 +21722,8 @@ function registerRunRoutes(router, options) {
|
|
|
21027
21722
|
conversation_history: readConversationHistory(
|
|
21028
21723
|
body.conversation_history ?? body.conversationHistory
|
|
21029
21724
|
),
|
|
21030
|
-
session_id: readString14(body, "session_id") ?? readString14(body, "sessionId") ?? void 0
|
|
21725
|
+
session_id: readString14(body, "session_id") ?? readString14(body, "sessionId") ?? void 0,
|
|
21726
|
+
session_key: readString14(body, "session_key") ?? readString14(body, "sessionKey") ?? void 0
|
|
21031
21727
|
},
|
|
21032
21728
|
{ logger, profileName: readOptionalProfileName(body) }
|
|
21033
21729
|
);
|
|
@@ -21198,8 +21894,8 @@ function readModelList(payload) {
|
|
|
21198
21894
|
// src/hermes/updates.ts
|
|
21199
21895
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
21200
21896
|
import { spawn as spawn3 } from "child_process";
|
|
21201
|
-
import { mkdir as
|
|
21202
|
-
import
|
|
21897
|
+
import { mkdir as mkdir12, readFile as readFile16, rm as rm7 } from "fs/promises";
|
|
21898
|
+
import path23 from "path";
|
|
21203
21899
|
var SERVER_HERMES_RELEASES_LATEST_PATH = "/api/v1/hermes-agent/releases/latest";
|
|
21204
21900
|
var RELEASE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
21205
21901
|
var RELEASE_FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -21262,7 +21958,7 @@ async function startHermesUpdate(options) {
|
|
|
21262
21958
|
signal: null,
|
|
21263
21959
|
error: null
|
|
21264
21960
|
};
|
|
21265
|
-
await
|
|
21961
|
+
await mkdir12(options.paths.runDir, { recursive: true, mode: 448 });
|
|
21266
21962
|
await writer.write(`
|
|
21267
21963
|
=== hermes update started ${startedAt} ===
|
|
21268
21964
|
`);
|
|
@@ -21477,13 +22173,13 @@ async function readUpdateLogLines(paths) {
|
|
|
21477
22173
|
);
|
|
21478
22174
|
}
|
|
21479
22175
|
function releaseCachePath(paths) {
|
|
21480
|
-
return
|
|
22176
|
+
return path23.join(paths.indexesDir, "hermes-release-check.json");
|
|
21481
22177
|
}
|
|
21482
22178
|
function updateStatePath(paths) {
|
|
21483
|
-
return
|
|
22179
|
+
return path23.join(paths.runDir, "hermes-update-state.json");
|
|
21484
22180
|
}
|
|
21485
22181
|
function updateLogPath(paths) {
|
|
21486
|
-
return
|
|
22182
|
+
return path23.join(paths.logsDir, UPDATE_LOG_FILE);
|
|
21487
22183
|
}
|
|
21488
22184
|
async function clearUpdateLogFiles(paths) {
|
|
21489
22185
|
const primary = updateLogPath(paths);
|
|
@@ -21583,17 +22279,17 @@ function readString17(payload, key) {
|
|
|
21583
22279
|
// src/link/updates.ts
|
|
21584
22280
|
import { spawn as spawn5 } from "child_process";
|
|
21585
22281
|
import { EventEmitter as EventEmitter4 } from "events";
|
|
21586
|
-
import { mkdir as
|
|
21587
|
-
import
|
|
22282
|
+
import { mkdir as mkdir15, readFile as readFile18, rm as rm10 } from "fs/promises";
|
|
22283
|
+
import path25 from "path";
|
|
21588
22284
|
|
|
21589
22285
|
// src/daemon/process.ts
|
|
21590
22286
|
import { spawn as spawn4 } from "child_process";
|
|
21591
|
-
import { mkdir as
|
|
21592
|
-
import
|
|
22287
|
+
import { mkdir as mkdir14, readFile as readFile17, rm as rm9 } from "fs/promises";
|
|
22288
|
+
import path24 from "path";
|
|
21593
22289
|
|
|
21594
22290
|
// src/daemon/service.ts
|
|
21595
22291
|
import { createServer } from "http";
|
|
21596
|
-
import { mkdir as
|
|
22292
|
+
import { mkdir as mkdir13, rm as rm8, writeFile as writeFile3 } from "fs/promises";
|
|
21597
22293
|
|
|
21598
22294
|
// src/relay/control-client.ts
|
|
21599
22295
|
import WebSocket from "ws";
|
|
@@ -22724,7 +23420,7 @@ function pidFilePath(paths = resolveRuntimePaths()) {
|
|
|
22724
23420
|
return `${paths.runDir}/hermeslink.pid`;
|
|
22725
23421
|
}
|
|
22726
23422
|
async function writePidFile(paths) {
|
|
22727
|
-
await
|
|
23423
|
+
await mkdir13(paths.runDir, { recursive: true, mode: 448 });
|
|
22728
23424
|
await writeFile3(pidFilePath(paths), `${process.pid}
|
|
22729
23425
|
`, { mode: 384 });
|
|
22730
23426
|
}
|
|
@@ -22799,8 +23495,8 @@ async function startDaemonProcess(paths = resolveRuntimePaths()) {
|
|
|
22799
23495
|
return status;
|
|
22800
23496
|
}
|
|
22801
23497
|
}
|
|
22802
|
-
await
|
|
22803
|
-
await
|
|
23498
|
+
await mkdir14(paths.logsDir, { recursive: true, mode: 448 });
|
|
23499
|
+
await mkdir14(paths.runDir, { recursive: true, mode: 448 });
|
|
22804
23500
|
const scriptPath = currentCliScriptPath();
|
|
22805
23501
|
const child = spawn4(process.execPath, [scriptPath, "daemon-supervisor"], {
|
|
22806
23502
|
detached: true,
|
|
@@ -22818,10 +23514,10 @@ async function startDaemonProcess(paths = resolveRuntimePaths()) {
|
|
|
22818
23514
|
return await getDaemonStatus(paths);
|
|
22819
23515
|
}
|
|
22820
23516
|
async function runDaemonSupervisor(paths = resolveRuntimePaths()) {
|
|
22821
|
-
await
|
|
23517
|
+
await mkdir14(paths.logsDir, { recursive: true, mode: 448 });
|
|
22822
23518
|
const log = createRotatingTextLogWriter({
|
|
22823
23519
|
paths,
|
|
22824
|
-
fileName:
|
|
23520
|
+
fileName: path24.basename(daemonLogFile(paths))
|
|
22825
23521
|
});
|
|
22826
23522
|
const scriptPath = currentCliScriptPath();
|
|
22827
23523
|
const child = spawn4(process.execPath, [scriptPath, "daemon", "--foreground"], {
|
|
@@ -23062,7 +23758,7 @@ async function startLinkUpdate(options) {
|
|
|
23062
23758
|
error: null,
|
|
23063
23759
|
manual_command: manualCommand
|
|
23064
23760
|
};
|
|
23065
|
-
await
|
|
23761
|
+
await mkdir15(options.paths.runDir, { recursive: true, mode: 448 });
|
|
23066
23762
|
await writer.write(
|
|
23067
23763
|
`
|
|
23068
23764
|
=== link update started ${startedAt} target=${targetVersion} ===
|
|
@@ -23375,10 +24071,10 @@ async function readUpdateLogLines2(paths) {
|
|
|
23375
24071
|
);
|
|
23376
24072
|
}
|
|
23377
24073
|
function updateStatePath2(paths) {
|
|
23378
|
-
return
|
|
24074
|
+
return path25.join(paths.runDir, "link-update-state.json");
|
|
23379
24075
|
}
|
|
23380
24076
|
function updateLogPath2(paths) {
|
|
23381
|
-
return
|
|
24077
|
+
return path25.join(paths.logsDir, UPDATE_LOG_FILE2);
|
|
23382
24078
|
}
|
|
23383
24079
|
async function clearUpdateLogFiles2(paths) {
|
|
23384
24080
|
const primary = updateLogPath2(paths);
|
|
@@ -23442,7 +24138,7 @@ function readString18(payload, key) {
|
|
|
23442
24138
|
}
|
|
23443
24139
|
|
|
23444
24140
|
// src/pairing/pairing.ts
|
|
23445
|
-
import
|
|
24141
|
+
import path26 from "path";
|
|
23446
24142
|
import { rm as rm11 } from "fs/promises";
|
|
23447
24143
|
|
|
23448
24144
|
// src/relay/bootstrap.ts
|
|
@@ -23782,10 +24478,10 @@ async function loadRequiredIdentity2(paths) {
|
|
|
23782
24478
|
}
|
|
23783
24479
|
return identity;
|
|
23784
24480
|
}
|
|
23785
|
-
async function postServerJson(serverBaseUrl,
|
|
24481
|
+
async function postServerJson(serverBaseUrl, path27, body, options) {
|
|
23786
24482
|
let response;
|
|
23787
24483
|
try {
|
|
23788
|
-
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${
|
|
24484
|
+
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path27}`, {
|
|
23789
24485
|
method: "POST",
|
|
23790
24486
|
headers: {
|
|
23791
24487
|
accept: "application/json",
|
|
@@ -23833,10 +24529,10 @@ function pairingErrorSnapshot(stage, error) {
|
|
|
23833
24529
|
occurred_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
23834
24530
|
};
|
|
23835
24531
|
}
|
|
23836
|
-
async function patchServerJson(serverBaseUrl,
|
|
24532
|
+
async function patchServerJson(serverBaseUrl, path27, token, body, options) {
|
|
23837
24533
|
let response;
|
|
23838
24534
|
try {
|
|
23839
|
-
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${
|
|
24535
|
+
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path27}`, {
|
|
23840
24536
|
method: "PATCH",
|
|
23841
24537
|
headers: {
|
|
23842
24538
|
accept: "application/json",
|
|
@@ -23884,10 +24580,10 @@ function createPairingNetworkError(input) {
|
|
|
23884
24580
|
);
|
|
23885
24581
|
}
|
|
23886
24582
|
function pairingClaimPath(sessionId, paths) {
|
|
23887
|
-
return
|
|
24583
|
+
return path26.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
|
|
23888
24584
|
}
|
|
23889
24585
|
function pairingSessionPath(sessionId, paths) {
|
|
23890
|
-
return
|
|
24586
|
+
return path26.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
|
|
23891
24587
|
}
|
|
23892
24588
|
function qrPreferredUrls(routes) {
|
|
23893
24589
|
return routes.preferredUrls.filter((url) => !url.includes("/api/v1/relay/links/")).slice(0, 1);
|
|
@@ -23939,6 +24635,10 @@ function registerSystemRoutes(router, options) {
|
|
|
23939
24635
|
statistics: true,
|
|
23940
24636
|
conversations: true,
|
|
23941
24637
|
conversation_events: true,
|
|
24638
|
+
conversation_archive: true,
|
|
24639
|
+
conversation_unarchive: true,
|
|
24640
|
+
conversation_archive_plan: true,
|
|
24641
|
+
conversation_archived_list: true,
|
|
23942
24642
|
conversation_delete: true,
|
|
23943
24643
|
conversation_bulk_delete: true,
|
|
23944
24644
|
conversation_clear_plan: true,
|
|
@@ -24142,19 +24842,17 @@ function registerSystemRoutes(router, options) {
|
|
|
24142
24842
|
})),
|
|
24143
24843
|
readDeviceSummary(paths),
|
|
24144
24844
|
listHermesProfiles(paths).catch(() => []),
|
|
24145
|
-
readHermesUpdateCheck({ paths, logger }).catch(
|
|
24146
|
-
|
|
24147
|
-
|
|
24148
|
-
|
|
24149
|
-
|
|
24150
|
-
|
|
24151
|
-
|
|
24152
|
-
|
|
24153
|
-
|
|
24154
|
-
|
|
24155
|
-
|
|
24156
|
-
})
|
|
24157
|
-
),
|
|
24845
|
+
readHermesUpdateCheck({ paths, logger }).catch((error) => ({
|
|
24846
|
+
ok: true,
|
|
24847
|
+
local: {
|
|
24848
|
+
version: null,
|
|
24849
|
+
raw: error instanceof Error ? error.message : String(error)
|
|
24850
|
+
},
|
|
24851
|
+
remote: null,
|
|
24852
|
+
update_available: false,
|
|
24853
|
+
check_state: "unavailable",
|
|
24854
|
+
issue: error instanceof Error ? error.message : String(error)
|
|
24855
|
+
})),
|
|
24158
24856
|
readHermesUpdateStatus(paths).catch((error) => ({
|
|
24159
24857
|
ok: true,
|
|
24160
24858
|
state: "failed",
|
|
@@ -24378,7 +25076,7 @@ async function readActiveCronJobCount(profiles, logger) {
|
|
|
24378
25076
|
}, 0);
|
|
24379
25077
|
}
|
|
24380
25078
|
function isActiveCronJob(job) {
|
|
24381
|
-
const enabled =
|
|
25079
|
+
const enabled = readBoolean3(job.enabled) ?? true;
|
|
24382
25080
|
if (!enabled) {
|
|
24383
25081
|
return false;
|
|
24384
25082
|
}
|
|
@@ -25074,10 +25772,10 @@ export {
|
|
|
25074
25772
|
saveConfig,
|
|
25075
25773
|
parseLogLevel,
|
|
25076
25774
|
normalizeLanHost,
|
|
25077
|
-
ConversationService,
|
|
25078
25775
|
loadIdentity,
|
|
25079
25776
|
ensureIdentity,
|
|
25080
25777
|
getIdentityStatus,
|
|
25778
|
+
ConversationService,
|
|
25081
25779
|
hasActiveDevices,
|
|
25082
25780
|
detectRuntimeEnvironment,
|
|
25083
25781
|
preparePairing,
|