@hermespilot/link 0.7.7-beta.0 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -4,7 +4,8 @@ import Router from "@koa/router";
|
|
|
4
4
|
|
|
5
5
|
// src/conversations/conversation-service.ts
|
|
6
6
|
import { EventEmitter } from "events";
|
|
7
|
-
import { createHash as createHash6, randomUUID as
|
|
7
|
+
import { createHash as createHash6, randomUUID as randomUUID11 } from "crypto";
|
|
8
|
+
import path24 from "path";
|
|
8
9
|
|
|
9
10
|
// src/database/link-database.ts
|
|
10
11
|
import { mkdir } from "fs/promises";
|
|
@@ -35,6 +36,7 @@ var MIGRATIONS = [
|
|
|
35
36
|
title TEXT NOT NULL,
|
|
36
37
|
status TEXT NOT NULL,
|
|
37
38
|
hermes_session_id TEXT NOT NULL,
|
|
39
|
+
workspace_id TEXT,
|
|
38
40
|
profile TEXT,
|
|
39
41
|
model TEXT,
|
|
40
42
|
provider TEXT,
|
|
@@ -58,6 +60,8 @@ var MIGRATIONS = [
|
|
|
58
60
|
ON conversation_stats(model);
|
|
59
61
|
CREATE INDEX IF NOT EXISTS idx_conversation_stats_profile
|
|
60
62
|
ON conversation_stats(profile);
|
|
63
|
+
CREATE INDEX IF NOT EXISTS idx_conversation_stats_workspace_status_updated
|
|
64
|
+
ON conversation_stats(workspace_id, status, updated_at);
|
|
61
65
|
`
|
|
62
66
|
},
|
|
63
67
|
{
|
|
@@ -234,6 +238,7 @@ async function listConversationStatsPage(paths, input) {
|
|
|
234
238
|
try {
|
|
235
239
|
const conditions = ["status = ?"];
|
|
236
240
|
const params = [status];
|
|
241
|
+
appendWorkspaceFilter(conditions, params, input.workspace);
|
|
237
242
|
if (input.cursor) {
|
|
238
243
|
conditions.push(`(
|
|
239
244
|
updated_at < ?
|
|
@@ -276,7 +281,8 @@ async function searchConversationStatsPage(paths, input) {
|
|
|
276
281
|
return listConversationStatsPage(paths, {
|
|
277
282
|
limit,
|
|
278
283
|
cursor: input.cursor,
|
|
279
|
-
status
|
|
284
|
+
status,
|
|
285
|
+
workspace: input.workspace
|
|
280
286
|
});
|
|
281
287
|
}
|
|
282
288
|
const db = openDatabase(paths);
|
|
@@ -286,6 +292,7 @@ async function searchConversationStatsPage(paths, input) {
|
|
|
286
292
|
status,
|
|
287
293
|
`%${escapeSqlLike(query.toLowerCase())}%`
|
|
288
294
|
];
|
|
295
|
+
appendWorkspaceFilter(conditions, params, input.workspace);
|
|
289
296
|
if (input.cursor) {
|
|
290
297
|
conditions.push(`(
|
|
291
298
|
updated_at < ?
|
|
@@ -818,7 +825,12 @@ function ensureProfileIdentitySchema(db) {
|
|
|
818
825
|
"ALTER TABLE conversation_stats ADD COLUMN profile_name_snapshot TEXT;"
|
|
819
826
|
);
|
|
820
827
|
}
|
|
828
|
+
if (!conversationColumns.has("workspace_id")) {
|
|
829
|
+
db.exec("ALTER TABLE conversation_stats ADD COLUMN workspace_id TEXT;");
|
|
830
|
+
}
|
|
821
831
|
db.exec(`
|
|
832
|
+
CREATE INDEX IF NOT EXISTS idx_conversation_stats_workspace_status_updated
|
|
833
|
+
ON conversation_stats(workspace_id, status, updated_at);
|
|
822
834
|
CREATE INDEX IF NOT EXISTS idx_conversation_stats_profile_uid
|
|
823
835
|
ON conversation_stats(profile_uid);
|
|
824
836
|
CREATE INDEX IF NOT EXISTS idx_conversation_stats_profile_name_snapshot
|
|
@@ -835,6 +847,7 @@ function conversationStatsUpsertSql() {
|
|
|
835
847
|
title,
|
|
836
848
|
status,
|
|
837
849
|
hermes_session_id,
|
|
850
|
+
workspace_id,
|
|
838
851
|
profile_uid,
|
|
839
852
|
profile_name_snapshot,
|
|
840
853
|
profile,
|
|
@@ -850,12 +863,13 @@ function conversationStatsUpsertSql() {
|
|
|
850
863
|
updated_at,
|
|
851
864
|
deleted_at,
|
|
852
865
|
stats_updated_at
|
|
853
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
866
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
854
867
|
ON CONFLICT(conversation_id) DO UPDATE SET
|
|
855
868
|
kind = excluded.kind,
|
|
856
869
|
title = excluded.title,
|
|
857
870
|
status = excluded.status,
|
|
858
871
|
hermes_session_id = excluded.hermes_session_id,
|
|
872
|
+
workspace_id = excluded.workspace_id,
|
|
859
873
|
profile_uid = excluded.profile_uid,
|
|
860
874
|
profile_name_snapshot = excluded.profile_name_snapshot,
|
|
861
875
|
profile = excluded.profile,
|
|
@@ -880,6 +894,7 @@ function conversationStatsParams(record) {
|
|
|
880
894
|
record.title,
|
|
881
895
|
record.status,
|
|
882
896
|
record.hermesSessionId,
|
|
897
|
+
record.workspaceId ?? null,
|
|
883
898
|
record.profileUid ?? null,
|
|
884
899
|
record.profileNameSnapshot ?? record.profile ?? null,
|
|
885
900
|
record.profile ?? record.profileNameSnapshot ?? null,
|
|
@@ -897,6 +912,17 @@ function conversationStatsParams(record) {
|
|
|
897
912
|
record.statsUpdatedAt
|
|
898
913
|
];
|
|
899
914
|
}
|
|
915
|
+
function appendWorkspaceFilter(conditions, params, filter) {
|
|
916
|
+
if (!filter || filter.kind === "all") {
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
if (filter.kind === "default") {
|
|
920
|
+
conditions.push("(workspace_id IS NULL OR workspace_id = '')");
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
conditions.push("workspace_id = ?");
|
|
924
|
+
params.push(filter.workspaceId);
|
|
925
|
+
}
|
|
900
926
|
function runUsageFactUpsertSql() {
|
|
901
927
|
return `
|
|
902
928
|
INSERT INTO run_usage_facts (
|
|
@@ -1642,6 +1668,11 @@ var messages = {
|
|
|
1642
1668
|
"error.relayChallengeInvalid": "Relay did not return a valid install challenge.",
|
|
1643
1669
|
"error.relayLinkInvalid": "Relay did not return a valid link_id.",
|
|
1644
1670
|
"error.relayEmpty": "Relay returned an empty response.",
|
|
1671
|
+
"error.workspaceNameConflict": "A workspace with this name already exists.",
|
|
1672
|
+
"error.workspaceNameRequired": "Workspace name is required.",
|
|
1673
|
+
"error.workspaceNameTooLong": "Workspace name is too long.",
|
|
1674
|
+
"error.workspaceNotFound": "Workspace was not found.",
|
|
1675
|
+
"error.workspaceIdInvalid": "Workspace id is invalid.",
|
|
1645
1676
|
"error.serverHttp": "HermesPilot Server request failed with HTTP {status}.",
|
|
1646
1677
|
"error.pairingServerUnreachable": "Could not reach HermesPilot Server while creating the pairing session. Check whether {url} is reachable, then try again. If you use a proxy network, add hermes-server.clawpilot.me and hermes-relay.clawpilot.me to the proxy exclusion list, or temporarily turn off VPN/proxy and retry.",
|
|
1647
1678
|
"error.pairingRelayUnreachable": "Could not reach Hermes Relay while creating the pairing session. Check whether {url} is reachable, then try again. If you use a proxy network, add hermes-server.clawpilot.me and hermes-relay.clawpilot.me to the proxy exclusion list, or temporarily turn off VPN/proxy and retry.",
|
|
@@ -1883,6 +1914,11 @@ var messages = {
|
|
|
1883
1914
|
"error.relayChallengeInvalid": "Relay \u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684\u5B89\u88C5\u6311\u6218\u3002",
|
|
1884
1915
|
"error.relayLinkInvalid": "Relay \u6CA1\u6709\u8FD4\u56DE\u6709\u6548\u7684 link_id\u3002",
|
|
1885
1916
|
"error.relayEmpty": "Relay \u8FD4\u56DE\u4E86\u7A7A\u54CD\u5E94\u3002",
|
|
1917
|
+
"error.workspaceNameConflict": "\u5DF2\u5B58\u5728\u540C\u540D\u5DE5\u4F5C\u533A\u3002",
|
|
1918
|
+
"error.workspaceNameRequired": "\u8BF7\u8F93\u5165\u5DE5\u4F5C\u533A\u540D\u79F0\u3002",
|
|
1919
|
+
"error.workspaceNameTooLong": "\u5DE5\u4F5C\u533A\u540D\u79F0\u592A\u957F\u3002",
|
|
1920
|
+
"error.workspaceNotFound": "\u5DE5\u4F5C\u533A\u4E0D\u5B58\u5728\u3002",
|
|
1921
|
+
"error.workspaceIdInvalid": "\u5DE5\u4F5C\u533A\u6807\u8BC6\u65E0\u6548\u3002",
|
|
1886
1922
|
"error.serverHttp": "HermesPilot Server \u8BF7\u6C42\u5931\u8D25\uFF0CHTTP \u72B6\u6001\u7801\uFF1A{status}\u3002",
|
|
1887
1923
|
"error.pairingServerUnreachable": "\u521B\u5EFA\u914D\u5BF9\u4F1A\u8BDD\u65F6\u65E0\u6CD5\u8FDE\u63A5 HermesPilot Server\u3002\u8BF7\u5148\u786E\u8BA4 {url} \u53EF\u4EE5\u8BBF\u95EE\uFF0C\u7136\u540E\u91CD\u8BD5\u3002\u91CD\u70B9\u63D0\u9192\uFF1A\u5982\u679C\u4F60\u4F7F\u7528\u4E86\u4EE3\u7406\u7F51\u7EDC\uFF0C\u53EF\u4EE5\u628A hermes-server.clawpilot.me \u548C hermes-relay.clawpilot.me \u52A0\u5165\u4EE3\u7406\u6392\u9664\u540D\u5355\uFF0C\u6216\u4E34\u65F6\u5173\u95ED VPN/\u4EE3\u7406\u540E\u518D\u8BD5\u3002",
|
|
1888
1924
|
"error.pairingRelayUnreachable": "\u521B\u5EFA\u914D\u5BF9\u4F1A\u8BDD\u65F6\u65E0\u6CD5\u8FDE\u63A5 Hermes Relay\u3002\u8BF7\u5148\u786E\u8BA4 {url} \u53EF\u4EE5\u8BBF\u95EE\uFF0C\u7136\u540E\u91CD\u8BD5\u3002\u91CD\u70B9\u63D0\u9192\uFF1A\u5982\u679C\u4F60\u4F7F\u7528\u4E86\u4EE3\u7406\u7F51\u7EDC\uFF0C\u53EF\u4EE5\u628A hermes-server.clawpilot.me \u548C hermes-relay.clawpilot.me \u52A0\u5165\u4EE3\u7406\u6392\u9664\u540D\u5355\uFF0C\u6216\u4E34\u65F6\u5173\u95ED VPN/\u4EE3\u7406\u540E\u518D\u8BD5\u3002",
|
|
@@ -1944,6 +1980,21 @@ function translateKnownError(message, language) {
|
|
|
1944
1980
|
if (message === "Relay returned an empty response") {
|
|
1945
1981
|
return translate(language, "error.relayEmpty");
|
|
1946
1982
|
}
|
|
1983
|
+
if (message === "workspace name already exists") {
|
|
1984
|
+
return translate(language, "error.workspaceNameConflict");
|
|
1985
|
+
}
|
|
1986
|
+
if (message === "workspace name is required") {
|
|
1987
|
+
return translate(language, "error.workspaceNameRequired");
|
|
1988
|
+
}
|
|
1989
|
+
if (message === "workspace name is too long") {
|
|
1990
|
+
return translate(language, "error.workspaceNameTooLong");
|
|
1991
|
+
}
|
|
1992
|
+
if (message === "workspace was not found") {
|
|
1993
|
+
return translate(language, "error.workspaceNotFound");
|
|
1994
|
+
}
|
|
1995
|
+
if (message === "workspace_id is invalid") {
|
|
1996
|
+
return translate(language, "error.workspaceIdInvalid");
|
|
1997
|
+
}
|
|
1947
1998
|
const portInUse = /^listen EADDRINUSE: address already in use .*:(?<port>\d+)$/u.exec(message);
|
|
1948
1999
|
if (portInUse?.groups?.port) {
|
|
1949
2000
|
return translate(language, "error.portInUse", { port: portInUse.groups.port });
|
|
@@ -6496,7 +6547,7 @@ async function listCronOutputFiles(profileName, jobId) {
|
|
|
6496
6547
|
orderTimeMs: fileStat.mtimeMs
|
|
6497
6548
|
});
|
|
6498
6549
|
}
|
|
6499
|
-
return files.sort((left, right) => left.orderTimeMs - right.orderTimeMs).map(({ path:
|
|
6550
|
+
return files.sort((left, right) => left.orderTimeMs - right.orderTimeMs).map(({ path: path34, mtime }) => ({ path: path34, mtime }));
|
|
6500
6551
|
}
|
|
6501
6552
|
function readCronOutputTimestamp(fileName) {
|
|
6502
6553
|
const match = fileName.match(
|
|
@@ -6585,7 +6636,7 @@ function isConversationMissingError(error) {
|
|
|
6585
6636
|
}
|
|
6586
6637
|
|
|
6587
6638
|
// src/constants.ts
|
|
6588
|
-
var LINK_VERSION = "0.7.7
|
|
6639
|
+
var LINK_VERSION = "0.7.7";
|
|
6589
6640
|
var LINK_COMMAND = "hermeslink";
|
|
6590
6641
|
var LINK_DEFAULT_PORT = 52379;
|
|
6591
6642
|
var LINK_RUNTIME_DIR_NAME = ".hermeslink";
|
|
@@ -6605,6 +6656,7 @@ function resolveRuntimePaths(homeDir = resolveRuntimeHome()) {
|
|
|
6605
6656
|
credentialsFile: path5.join(homeDir, "credentials.json"),
|
|
6606
6657
|
databaseFile: path5.join(homeDir, "link.db"),
|
|
6607
6658
|
conversationsDir: path5.join(homeDir, "conversations"),
|
|
6659
|
+
workspacesFile: path5.join(homeDir, "workspaces.json"),
|
|
6608
6660
|
blobsDir: path5.join(homeDir, "blobs"),
|
|
6609
6661
|
indexesDir: path5.join(homeDir, "indexes"),
|
|
6610
6662
|
logsDir: path5.join(homeDir, "logs"),
|
|
@@ -7266,7 +7318,14 @@ async function deleteHermesSession(sessionId, profileName = "default") {
|
|
|
7266
7318
|
status: readSessionDeleteStatus(output.stdout, output.stderr)
|
|
7267
7319
|
};
|
|
7268
7320
|
} catch (error) {
|
|
7269
|
-
|
|
7321
|
+
const output = readExecErrorOutput(error);
|
|
7322
|
+
if (isProfileNotFoundOutput(output)) {
|
|
7323
|
+
return {
|
|
7324
|
+
session_id: normalizedSessionId,
|
|
7325
|
+
status: "profile_not_found"
|
|
7326
|
+
};
|
|
7327
|
+
}
|
|
7328
|
+
if (isSessionNotFoundOutput(output)) {
|
|
7270
7329
|
return {
|
|
7271
7330
|
session_id: normalizedSessionId,
|
|
7272
7331
|
status: "not_found"
|
|
@@ -7328,6 +7387,9 @@ ${stderr.toString()}`;
|
|
|
7328
7387
|
if (isSessionNotFoundOutput(output)) {
|
|
7329
7388
|
return "not_found";
|
|
7330
7389
|
}
|
|
7390
|
+
if (isProfileNotFoundOutput(output)) {
|
|
7391
|
+
return "profile_not_found";
|
|
7392
|
+
}
|
|
7331
7393
|
if (/deleted session\b/i.test(output)) {
|
|
7332
7394
|
return "deleted";
|
|
7333
7395
|
}
|
|
@@ -7336,6 +7398,9 @@ ${stderr.toString()}`;
|
|
|
7336
7398
|
function isSessionNotFoundOutput(output) {
|
|
7337
7399
|
return /\bsession\b[\s\S]*\bnot found\b/i.test(output);
|
|
7338
7400
|
}
|
|
7401
|
+
function isProfileNotFoundOutput(output) {
|
|
7402
|
+
return /\bprofile\b[\s\S]*\bdoes not exist\b/i.test(output);
|
|
7403
|
+
}
|
|
7339
7404
|
function readExecErrorOutput(error) {
|
|
7340
7405
|
if (typeof error !== "object" || error === null) {
|
|
7341
7406
|
return "";
|
|
@@ -8641,6 +8706,61 @@ async function respondTuiGatewayApproval(input) {
|
|
|
8641
8706
|
);
|
|
8642
8707
|
return { resolved: readNumber2(result, "resolved") ?? 0 };
|
|
8643
8708
|
}
|
|
8709
|
+
async function setTuiGatewaySessionArchived(input) {
|
|
8710
|
+
const sessionId = input.sessionId.trim();
|
|
8711
|
+
if (!sessionId) {
|
|
8712
|
+
throw new LinkHttpError(
|
|
8713
|
+
400,
|
|
8714
|
+
"hermes_session_id_required",
|
|
8715
|
+
"Hermes session id is required"
|
|
8716
|
+
);
|
|
8717
|
+
}
|
|
8718
|
+
if (sessionId.startsWith("hp_")) {
|
|
8719
|
+
return;
|
|
8720
|
+
}
|
|
8721
|
+
const profileName = normalizeProfileName2(input.profileName);
|
|
8722
|
+
const backend = await ensureTuiGatewayBackend({
|
|
8723
|
+
profileName,
|
|
8724
|
+
paths: input.paths,
|
|
8725
|
+
logger: input.logger
|
|
8726
|
+
});
|
|
8727
|
+
const response = await fetch(
|
|
8728
|
+
`${backend.baseUrl}/api/sessions/${encodeURIComponent(sessionId)}`,
|
|
8729
|
+
{
|
|
8730
|
+
method: "PATCH",
|
|
8731
|
+
headers: {
|
|
8732
|
+
"content-type": "application/json",
|
|
8733
|
+
"X-Hermes-Session-Token": backend.token
|
|
8734
|
+
},
|
|
8735
|
+
body: JSON.stringify({ archived: input.archived })
|
|
8736
|
+
}
|
|
8737
|
+
).catch((error) => {
|
|
8738
|
+
throw new LinkHttpError(
|
|
8739
|
+
502,
|
|
8740
|
+
"hermes_session_archive_failed",
|
|
8741
|
+
error instanceof Error ? error.message : "Hermes session archive request failed"
|
|
8742
|
+
);
|
|
8743
|
+
});
|
|
8744
|
+
touchBackend(profileName);
|
|
8745
|
+
if (response.ok) {
|
|
8746
|
+
return;
|
|
8747
|
+
}
|
|
8748
|
+
const detail = await response.text().catch(() => "");
|
|
8749
|
+
if (response.status === 404) {
|
|
8750
|
+
void input.logger?.debug("hermes_session_archive_missing", {
|
|
8751
|
+
profile: profileName,
|
|
8752
|
+
session_id: sessionId,
|
|
8753
|
+
archived: input.archived,
|
|
8754
|
+
detail
|
|
8755
|
+
});
|
|
8756
|
+
return;
|
|
8757
|
+
}
|
|
8758
|
+
throw new LinkHttpError(
|
|
8759
|
+
502,
|
|
8760
|
+
"hermes_session_archive_failed",
|
|
8761
|
+
`Hermes session archive request failed with HTTP ${response.status}${detail ? `: ${detail}` : ""}`
|
|
8762
|
+
);
|
|
8763
|
+
}
|
|
8644
8764
|
async function readTuiGatewayStatus(input = {}) {
|
|
8645
8765
|
const profile = normalizeProfileName2(input.profileName);
|
|
8646
8766
|
const client = clients.get(profile);
|
|
@@ -9850,6 +9970,7 @@ function toStatsIndexRecord(manifest, stats = manifest.stats ?? buildConversatio
|
|
|
9850
9970
|
title: manifest.title,
|
|
9851
9971
|
status: manifest.status,
|
|
9852
9972
|
hermesSessionId: manifest.hermes_session_id,
|
|
9973
|
+
workspaceId: manifest.workspace_id ?? null,
|
|
9853
9974
|
profileUid: stats.profile_uid ?? null,
|
|
9854
9975
|
profileNameSnapshot: stats.profile_name_snapshot ?? stats.profile ?? null,
|
|
9855
9976
|
profile: stats.profile ?? stats.profile_name_snapshot ?? null,
|
|
@@ -9999,8 +10120,204 @@ function firstRecord(...values) {
|
|
|
9999
10120
|
return {};
|
|
10000
10121
|
}
|
|
10001
10122
|
|
|
10002
|
-
// src/conversations/
|
|
10123
|
+
// src/conversations/workspaces.ts
|
|
10003
10124
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
10125
|
+
var MAX_WORKSPACE_NAME_LENGTH = 40;
|
|
10126
|
+
var DEFAULT_WORKSPACE_ICON = "grid";
|
|
10127
|
+
var WORKSPACE_ICON_KEYS = /* @__PURE__ */ new Set([
|
|
10128
|
+
"grid",
|
|
10129
|
+
"folder",
|
|
10130
|
+
"briefcase",
|
|
10131
|
+
"code",
|
|
10132
|
+
"cube",
|
|
10133
|
+
"rocket",
|
|
10134
|
+
"albums",
|
|
10135
|
+
"bookmark",
|
|
10136
|
+
"heart",
|
|
10137
|
+
"library",
|
|
10138
|
+
"planet",
|
|
10139
|
+
"school"
|
|
10140
|
+
]);
|
|
10141
|
+
async function listWorkspaces(paths) {
|
|
10142
|
+
const store = await readWorkspaceStore(paths);
|
|
10143
|
+
return sortWorkspaces(store.workspaces);
|
|
10144
|
+
}
|
|
10145
|
+
async function createWorkspace(paths, input) {
|
|
10146
|
+
const name = normalizeWorkspaceName(input.name);
|
|
10147
|
+
const icon = normalizeWorkspaceIcon(input.icon);
|
|
10148
|
+
let created;
|
|
10149
|
+
await updateWorkspaceStore(paths, (store) => {
|
|
10150
|
+
assertUniqueWorkspaceName(store.workspaces, name);
|
|
10151
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10152
|
+
created = {
|
|
10153
|
+
id: `ws_${randomUUID4().replaceAll("-", "")}`,
|
|
10154
|
+
name,
|
|
10155
|
+
icon,
|
|
10156
|
+
created_at: now,
|
|
10157
|
+
updated_at: now
|
|
10158
|
+
};
|
|
10159
|
+
return {
|
|
10160
|
+
...store,
|
|
10161
|
+
workspaces: [...store.workspaces, created]
|
|
10162
|
+
};
|
|
10163
|
+
});
|
|
10164
|
+
return created;
|
|
10165
|
+
}
|
|
10166
|
+
async function renameWorkspace(paths, workspaceId, input) {
|
|
10167
|
+
const id = normalizeWorkspaceId(workspaceId);
|
|
10168
|
+
const name = normalizeWorkspaceName(input.name);
|
|
10169
|
+
const icon = normalizeWorkspaceIcon(input.icon);
|
|
10170
|
+
let renamed;
|
|
10171
|
+
await updateWorkspaceStore(paths, (store) => {
|
|
10172
|
+
assertUniqueWorkspaceName(store.workspaces, name, id);
|
|
10173
|
+
const index = store.workspaces.findIndex((workspace) => workspace.id === id);
|
|
10174
|
+
if (index < 0) {
|
|
10175
|
+
throw workspaceNotFound();
|
|
10176
|
+
}
|
|
10177
|
+
const next = [...store.workspaces];
|
|
10178
|
+
renamed = {
|
|
10179
|
+
...next[index],
|
|
10180
|
+
name,
|
|
10181
|
+
icon,
|
|
10182
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
10183
|
+
};
|
|
10184
|
+
next[index] = renamed;
|
|
10185
|
+
return { ...store, workspaces: next };
|
|
10186
|
+
});
|
|
10187
|
+
return renamed;
|
|
10188
|
+
}
|
|
10189
|
+
async function deleteWorkspace(paths, workspaceId) {
|
|
10190
|
+
const id = normalizeWorkspaceId(workspaceId);
|
|
10191
|
+
let deleted;
|
|
10192
|
+
await updateWorkspaceStore(paths, (store) => {
|
|
10193
|
+
const next = store.workspaces.filter((workspace) => {
|
|
10194
|
+
if (workspace.id === id) {
|
|
10195
|
+
deleted = workspace;
|
|
10196
|
+
return false;
|
|
10197
|
+
}
|
|
10198
|
+
return true;
|
|
10199
|
+
});
|
|
10200
|
+
if (!deleted) {
|
|
10201
|
+
throw workspaceNotFound();
|
|
10202
|
+
}
|
|
10203
|
+
return { ...store, workspaces: next };
|
|
10204
|
+
});
|
|
10205
|
+
return deleted;
|
|
10206
|
+
}
|
|
10207
|
+
async function assertWorkspaceExists(paths, workspaceId) {
|
|
10208
|
+
const id = normalizeOptionalWorkspaceId(workspaceId);
|
|
10209
|
+
if (!id) {
|
|
10210
|
+
return void 0;
|
|
10211
|
+
}
|
|
10212
|
+
const store = await readWorkspaceStore(paths);
|
|
10213
|
+
if (!store.workspaces.some((workspace) => workspace.id === id)) {
|
|
10214
|
+
throw workspaceNotFound();
|
|
10215
|
+
}
|
|
10216
|
+
return id;
|
|
10217
|
+
}
|
|
10218
|
+
function normalizeOptionalWorkspaceId(value) {
|
|
10219
|
+
if (value == null) {
|
|
10220
|
+
return void 0;
|
|
10221
|
+
}
|
|
10222
|
+
const normalized = value.trim();
|
|
10223
|
+
if (!normalized || normalized === "default") {
|
|
10224
|
+
return void 0;
|
|
10225
|
+
}
|
|
10226
|
+
return normalizeWorkspaceId(normalized);
|
|
10227
|
+
}
|
|
10228
|
+
function normalizeWorkspaceId(value) {
|
|
10229
|
+
const normalized = value.trim();
|
|
10230
|
+
if (!/^ws_[a-zA-Z0-9]+$/u.test(normalized)) {
|
|
10231
|
+
throw new LinkHttpError(400, "workspace_id_invalid", "workspace_id is invalid");
|
|
10232
|
+
}
|
|
10233
|
+
return normalized;
|
|
10234
|
+
}
|
|
10235
|
+
function normalizeWorkspaceName(value) {
|
|
10236
|
+
const normalized = value.replace(/\s+/gu, " ").trim();
|
|
10237
|
+
if (!normalized) {
|
|
10238
|
+
throw new LinkHttpError(
|
|
10239
|
+
400,
|
|
10240
|
+
"workspace_name_required",
|
|
10241
|
+
"workspace name is required"
|
|
10242
|
+
);
|
|
10243
|
+
}
|
|
10244
|
+
if (Array.from(normalized).length > MAX_WORKSPACE_NAME_LENGTH) {
|
|
10245
|
+
throw new LinkHttpError(
|
|
10246
|
+
400,
|
|
10247
|
+
"workspace_name_too_long",
|
|
10248
|
+
"workspace name is too long"
|
|
10249
|
+
);
|
|
10250
|
+
}
|
|
10251
|
+
return normalized;
|
|
10252
|
+
}
|
|
10253
|
+
function normalizeWorkspaceIcon(value) {
|
|
10254
|
+
const normalized = value?.trim() ?? "";
|
|
10255
|
+
if (!normalized) {
|
|
10256
|
+
return DEFAULT_WORKSPACE_ICON;
|
|
10257
|
+
}
|
|
10258
|
+
return WORKSPACE_ICON_KEYS.has(normalized) ? normalized : DEFAULT_WORKSPACE_ICON;
|
|
10259
|
+
}
|
|
10260
|
+
function assertUniqueWorkspaceName(workspaces, name, exceptWorkspaceId) {
|
|
10261
|
+
const normalized = name.toLocaleLowerCase();
|
|
10262
|
+
if (workspaces.some(
|
|
10263
|
+
(workspace) => workspace.id !== exceptWorkspaceId && workspace.name.toLocaleLowerCase() === normalized
|
|
10264
|
+
)) {
|
|
10265
|
+
throw new LinkHttpError(
|
|
10266
|
+
409,
|
|
10267
|
+
"workspace_name_conflict",
|
|
10268
|
+
"workspace name already exists"
|
|
10269
|
+
);
|
|
10270
|
+
}
|
|
10271
|
+
}
|
|
10272
|
+
async function readWorkspaceStore(paths) {
|
|
10273
|
+
return normalizeWorkspaceStore(await readJsonFile(paths.workspacesFile));
|
|
10274
|
+
}
|
|
10275
|
+
async function updateWorkspaceStore(paths, update) {
|
|
10276
|
+
return updateJsonFile(
|
|
10277
|
+
paths.workspacesFile,
|
|
10278
|
+
(current) => update(normalizeWorkspaceStore(current)),
|
|
10279
|
+
384
|
|
10280
|
+
);
|
|
10281
|
+
}
|
|
10282
|
+
function normalizeWorkspaceStore(store) {
|
|
10283
|
+
if (!store || !Array.isArray(store.workspaces)) {
|
|
10284
|
+
return { schema_version: 1, workspaces: [] };
|
|
10285
|
+
}
|
|
10286
|
+
return {
|
|
10287
|
+
schema_version: 1,
|
|
10288
|
+
workspaces: store.workspaces.map(normalizeWorkspaceRecord).filter(
|
|
10289
|
+
(workspace) => Boolean(workspace)
|
|
10290
|
+
)
|
|
10291
|
+
};
|
|
10292
|
+
}
|
|
10293
|
+
function normalizeWorkspaceRecord(value) {
|
|
10294
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
10295
|
+
return null;
|
|
10296
|
+
}
|
|
10297
|
+
const record = value;
|
|
10298
|
+
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
10299
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
10300
|
+
const icon = normalizeWorkspaceIcon(
|
|
10301
|
+
typeof record.icon === "string" ? record.icon : void 0
|
|
10302
|
+
);
|
|
10303
|
+
const createdAt = typeof record.created_at === "string" ? record.created_at.trim() : "";
|
|
10304
|
+
const updatedAt = typeof record.updated_at === "string" ? record.updated_at.trim() : "";
|
|
10305
|
+
if (!id || !name || !createdAt || !updatedAt) {
|
|
10306
|
+
return null;
|
|
10307
|
+
}
|
|
10308
|
+
return { id, name, icon, created_at: createdAt, updated_at: updatedAt };
|
|
10309
|
+
}
|
|
10310
|
+
function sortWorkspaces(workspaces) {
|
|
10311
|
+
return [...workspaces].sort(
|
|
10312
|
+
(left, right) => left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id)
|
|
10313
|
+
);
|
|
10314
|
+
}
|
|
10315
|
+
function workspaceNotFound() {
|
|
10316
|
+
return new LinkHttpError(404, "workspace_not_found", "workspace was not found");
|
|
10317
|
+
}
|
|
10318
|
+
|
|
10319
|
+
// src/conversations/blob-store.ts
|
|
10320
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
10004
10321
|
import { mkdir as mkdir6, readFile as readFile7, readdir as readdir4, rm as rm3, stat as stat6, writeFile } from "fs/promises";
|
|
10005
10322
|
import path10 from "path";
|
|
10006
10323
|
|
|
@@ -10453,7 +10770,7 @@ async function writeConversationBlob(paths, conversationId, input, options) {
|
|
|
10453
10770
|
if (input.bytes.byteLength > options.maxBytes) {
|
|
10454
10771
|
throw new LinkHttpError(413, "blob_too_large", "Blob is too large");
|
|
10455
10772
|
}
|
|
10456
|
-
const id = `blob_${
|
|
10773
|
+
const id = `blob_${randomUUID5().replaceAll("-", "")}`;
|
|
10457
10774
|
const filePath = blobPath(paths, id);
|
|
10458
10775
|
await mkdir6(path10.dirname(filePath), { recursive: true, mode: 448 });
|
|
10459
10776
|
await writeFile(filePath, input.bytes, { mode: 384 });
|
|
@@ -10592,13 +10909,6 @@ async function listConversationBlobIds(paths, conversationId) {
|
|
|
10592
10909
|
}
|
|
10593
10910
|
return blobIds;
|
|
10594
10911
|
}
|
|
10595
|
-
async function removeConversationFiles(paths, conversationId) {
|
|
10596
|
-
assertValidConversationId(conversationId);
|
|
10597
|
-
await rm3(path10.join(paths.conversationsDir, conversationId), {
|
|
10598
|
-
recursive: true,
|
|
10599
|
-
force: true
|
|
10600
|
-
});
|
|
10601
|
-
}
|
|
10602
10912
|
function conversationAttachmentsDir(paths, conversationId) {
|
|
10603
10913
|
assertValidConversationId(conversationId);
|
|
10604
10914
|
return path10.join(paths.conversationsDir, conversationId, "attachments");
|
|
@@ -10828,6 +11138,7 @@ function toSummary(manifest, snapshot, profile) {
|
|
|
10828
11138
|
id: manifest.id,
|
|
10829
11139
|
title: manifest.title,
|
|
10830
11140
|
title_source: manifest.title_source,
|
|
11141
|
+
workspace_id: manifest.workspace_id ?? null,
|
|
10831
11142
|
created_at: manifest.created_at,
|
|
10832
11143
|
updated_at: manifest.updated_at,
|
|
10833
11144
|
last_event_seq: manifest.last_event_seq,
|
|
@@ -10878,7 +11189,7 @@ function isRealtimeRunStatus(status) {
|
|
|
10878
11189
|
}
|
|
10879
11190
|
|
|
10880
11191
|
// src/conversations/slash-commands.ts
|
|
10881
|
-
import { randomUUID as
|
|
11192
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
10882
11193
|
var MODEL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/@+-]{0,127}$/u;
|
|
10883
11194
|
function isValidModelId(value) {
|
|
10884
11195
|
return MODEL_ID_PATTERN.test(value);
|
|
@@ -10975,7 +11286,7 @@ function parseSlashCommandInput(content, language = "zh-CN") {
|
|
|
10975
11286
|
}
|
|
10976
11287
|
function createSlashCommandUserMessage(input) {
|
|
10977
11288
|
return {
|
|
10978
|
-
id: `msg_${
|
|
11289
|
+
id: `msg_${randomUUID6().replaceAll("-", "")}`,
|
|
10979
11290
|
schema_version: 1,
|
|
10980
11291
|
conversation_id: input.conversationId,
|
|
10981
11292
|
role: "user",
|
|
@@ -11025,7 +11336,7 @@ function slashHelpMessage(language = "zh-CN") {
|
|
|
11025
11336
|
].join("\n");
|
|
11026
11337
|
}
|
|
11027
11338
|
function freshHermesSessionId(conversationId) {
|
|
11028
|
-
return `hp_${conversationId}_${
|
|
11339
|
+
return `hp_${conversationId}_${randomUUID6().replaceAll("-", "").slice(0, 12)}`;
|
|
11029
11340
|
}
|
|
11030
11341
|
function nextVerboseMode(current) {
|
|
11031
11342
|
const modes = [
|
|
@@ -11663,7 +11974,7 @@ function safePathSegment(value, fallback) {
|
|
|
11663
11974
|
}
|
|
11664
11975
|
|
|
11665
11976
|
// src/conversations/conversation-archive-plans.ts
|
|
11666
|
-
import { randomUUID as
|
|
11977
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
11667
11978
|
import { mkdir as mkdir8 } from "fs/promises";
|
|
11668
11979
|
import path12 from "path";
|
|
11669
11980
|
var PLAN_ID_PATTERN = /^archive_[a-f0-9]{32}$/u;
|
|
@@ -11675,7 +11986,7 @@ var ConversationArchivePlanStore = class {
|
|
|
11675
11986
|
async create(conversationIds) {
|
|
11676
11987
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11677
11988
|
const plan = {
|
|
11678
|
-
id: `archive_${
|
|
11989
|
+
id: `archive_${randomUUID7().replaceAll("-", "")}`,
|
|
11679
11990
|
status: "prepared",
|
|
11680
11991
|
created_at: now,
|
|
11681
11992
|
updated_at: now,
|
|
@@ -11727,7 +12038,7 @@ function normalizePlanId(planId) {
|
|
|
11727
12038
|
}
|
|
11728
12039
|
|
|
11729
12040
|
// src/conversations/conversation-clear-plans.ts
|
|
11730
|
-
import { randomUUID as
|
|
12041
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
11731
12042
|
import { mkdir as mkdir9 } from "fs/promises";
|
|
11732
12043
|
import path13 from "path";
|
|
11733
12044
|
var PLAN_ID_PATTERN2 = /^clear_[a-f0-9]{32}$/u;
|
|
@@ -11739,7 +12050,7 @@ var ConversationClearPlanStore = class {
|
|
|
11739
12050
|
async create(conversationIds, targetStatus = "active") {
|
|
11740
12051
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11741
12052
|
const plan = {
|
|
11742
|
-
id: `clear_${
|
|
12053
|
+
id: `clear_${randomUUID8().replaceAll("-", "")}`,
|
|
11743
12054
|
status: "prepared",
|
|
11744
12055
|
target_status: targetStatus,
|
|
11745
12056
|
created_at: now,
|
|
@@ -11940,6 +12251,11 @@ function collectHermesSessionDeleteTargets(manifest, snapshot) {
|
|
|
11940
12251
|
}
|
|
11941
12252
|
return targets;
|
|
11942
12253
|
}
|
|
12254
|
+
function collectHermesSessionMutationTargets(manifest, snapshot) {
|
|
12255
|
+
return collectHermesSessionDeleteTargets(manifest, snapshot).filter(
|
|
12256
|
+
(target) => !target.sessionId.startsWith("hp_")
|
|
12257
|
+
);
|
|
12258
|
+
}
|
|
11943
12259
|
function arraysEqual(left, right) {
|
|
11944
12260
|
if (left.length !== right.length) {
|
|
11945
12261
|
return false;
|
|
@@ -12389,6 +12705,7 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
12389
12705
|
}
|
|
12390
12706
|
const archivedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12391
12707
|
const snapshot = await this.deps.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
12708
|
+
await this.setHermesSessionsArchived(manifest, snapshot, true);
|
|
12392
12709
|
await this.deps.store.writeManifest({
|
|
12393
12710
|
...manifest,
|
|
12394
12711
|
status: "archived",
|
|
@@ -12429,6 +12746,7 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
12429
12746
|
}
|
|
12430
12747
|
const unarchivedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12431
12748
|
const snapshot = await this.deps.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
12749
|
+
await this.setHermesSessionsArchived(manifest, snapshot, false);
|
|
12432
12750
|
const next = {
|
|
12433
12751
|
...manifest,
|
|
12434
12752
|
status: "active",
|
|
@@ -12532,6 +12850,18 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
12532
12850
|
}
|
|
12533
12851
|
return results;
|
|
12534
12852
|
}
|
|
12853
|
+
async setHermesSessionsArchived(manifest, snapshot, archived) {
|
|
12854
|
+
const targets = collectHermesSessionMutationTargets(manifest, snapshot);
|
|
12855
|
+
for (const target of targets) {
|
|
12856
|
+
await setTuiGatewaySessionArchived({
|
|
12857
|
+
paths: this.deps.paths,
|
|
12858
|
+
logger: this.deps.logger,
|
|
12859
|
+
profileName: target.profileName,
|
|
12860
|
+
sessionId: target.sessionId,
|
|
12861
|
+
archived
|
|
12862
|
+
});
|
|
12863
|
+
}
|
|
12864
|
+
}
|
|
12535
12865
|
async pruneConversationBlobReferences(conversationId, blobIds) {
|
|
12536
12866
|
for (const blobId of blobIds) {
|
|
12537
12867
|
try {
|
|
@@ -13056,7 +13386,7 @@ function stripCompressionTitleSuffix(value) {
|
|
|
13056
13386
|
}
|
|
13057
13387
|
|
|
13058
13388
|
// src/conversations/conversation-turns.ts
|
|
13059
|
-
import { randomUUID as
|
|
13389
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
13060
13390
|
var MESSAGE_ORDER_STEP_MS = 10;
|
|
13061
13391
|
var ASSISTANT_ORDER_OFFSET_MS = 1;
|
|
13062
13392
|
function createAgentTurnDraft(input) {
|
|
@@ -13305,10 +13635,10 @@ function createAssistantMessage(input) {
|
|
|
13305
13635
|
};
|
|
13306
13636
|
}
|
|
13307
13637
|
function createMessageId() {
|
|
13308
|
-
return `msg_${
|
|
13638
|
+
return `msg_${randomUUID9().replaceAll("-", "")}`;
|
|
13309
13639
|
}
|
|
13310
13640
|
function createRunId() {
|
|
13311
|
-
return `run_${
|
|
13641
|
+
return `run_${randomUUID9().replaceAll("-", "")}`;
|
|
13312
13642
|
}
|
|
13313
13643
|
function hasActiveOrQueuedRuns(snapshot) {
|
|
13314
13644
|
return snapshot.runs.some(
|
|
@@ -14600,11 +14930,17 @@ var ConversationQueryCoordinator = class {
|
|
|
14600
14930
|
status,
|
|
14601
14931
|
limit,
|
|
14602
14932
|
cursor,
|
|
14603
|
-
fallback: () => this.listConversationPageFromStore({
|
|
14933
|
+
fallback: () => this.listConversationPageFromStore({
|
|
14934
|
+
status,
|
|
14935
|
+
limit,
|
|
14936
|
+
cursor,
|
|
14937
|
+
workspace: options.workspace
|
|
14938
|
+
}),
|
|
14604
14939
|
listPage: (pageCursor) => listConversationStatsPage(this.deps.paths, {
|
|
14605
14940
|
status,
|
|
14606
14941
|
limit,
|
|
14607
|
-
cursor: pageCursor
|
|
14942
|
+
cursor: pageCursor,
|
|
14943
|
+
workspace: options.workspace
|
|
14608
14944
|
})
|
|
14609
14945
|
});
|
|
14610
14946
|
}
|
|
@@ -14693,11 +15029,11 @@ var ConversationQueryCoordinator = class {
|
|
|
14693
15029
|
}
|
|
14694
15030
|
};
|
|
14695
15031
|
}
|
|
14696
|
-
async listConversationsFromStore(status = "active") {
|
|
15032
|
+
async listConversationsFromStore(status = "active", workspace) {
|
|
14697
15033
|
const summaries = [];
|
|
14698
15034
|
for (const conversationId of await this.deps.store.listConversationIds()) {
|
|
14699
15035
|
const manifest = await this.deps.store.readManifest(conversationId).catch(() => null);
|
|
14700
|
-
if (!manifest || manifest.status !== status) {
|
|
15036
|
+
if (!manifest || manifest.status !== status || !matchesWorkspaceFilter(manifest, workspace)) {
|
|
14701
15037
|
continue;
|
|
14702
15038
|
}
|
|
14703
15039
|
const snapshot = await this.deps.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
@@ -14709,7 +15045,10 @@ var ConversationQueryCoordinator = class {
|
|
|
14709
15045
|
);
|
|
14710
15046
|
}
|
|
14711
15047
|
async listConversationPageFromStore(input) {
|
|
14712
|
-
const all = await this.listConversationsFromStore(
|
|
15048
|
+
const all = await this.listConversationsFromStore(
|
|
15049
|
+
input.status,
|
|
15050
|
+
input.workspace
|
|
15051
|
+
);
|
|
14713
15052
|
const startIndex = input.cursor ? all.findIndex(
|
|
14714
15053
|
(summary) => isAfterConversationListCursor(summary, input.cursor)
|
|
14715
15054
|
) : 0;
|
|
@@ -14914,6 +15253,16 @@ function readNonEmptyString(value) {
|
|
|
14914
15253
|
function normalizeConversationSearchQuery(value) {
|
|
14915
15254
|
return typeof value === "string" ? value.trim() : "";
|
|
14916
15255
|
}
|
|
15256
|
+
function matchesWorkspaceFilter(manifest, filter) {
|
|
15257
|
+
if (!filter || filter.kind === "all") {
|
|
15258
|
+
return true;
|
|
15259
|
+
}
|
|
15260
|
+
const workspaceId = manifest.workspace_id?.trim() ?? "";
|
|
15261
|
+
if (filter.kind === "default") {
|
|
15262
|
+
return workspaceId === "";
|
|
15263
|
+
}
|
|
15264
|
+
return workspaceId === filter.workspaceId;
|
|
15265
|
+
}
|
|
14917
15266
|
function isAfterConversationListCursor(summary, cursor) {
|
|
14918
15267
|
return summary.updated_at < cursor.updatedAt || summary.updated_at === cursor.updatedAt && summary.id < cursor.conversationId;
|
|
14919
15268
|
}
|
|
@@ -15106,7 +15455,7 @@ function isNodeError9(error, code) {
|
|
|
15106
15455
|
}
|
|
15107
15456
|
|
|
15108
15457
|
// src/conversations/hermes-session-sync.ts
|
|
15109
|
-
import { randomUUID as
|
|
15458
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
15110
15459
|
import { readdir as readdir7, readFile as readFile10, stat as stat10 } from "fs/promises";
|
|
15111
15460
|
import path17 from "path";
|
|
15112
15461
|
|
|
@@ -17068,7 +17417,7 @@ function toLinkMessage(input) {
|
|
|
17068
17417
|
const sessionId = readString10(input.message, "session_id") ?? input.sessionId;
|
|
17069
17418
|
const createdAt = isoFromHermesTime(input.message.timestamp) ?? new Date(Date.now() + input.index).toISOString();
|
|
17070
17419
|
return {
|
|
17071
|
-
id: `msg_${
|
|
17420
|
+
id: `msg_${randomUUID10().replaceAll("-", "")}`,
|
|
17072
17421
|
schema_version: 1,
|
|
17073
17422
|
conversation_id: input.conversationId,
|
|
17074
17423
|
role,
|
|
@@ -17251,7 +17600,7 @@ async function isFile(filePath) {
|
|
|
17251
17600
|
});
|
|
17252
17601
|
}
|
|
17253
17602
|
function createConversationId() {
|
|
17254
|
-
return `conv_${
|
|
17603
|
+
return `conv_${randomUUID10().replaceAll("-", "")}`;
|
|
17255
17604
|
}
|
|
17256
17605
|
function isoFromHermesTime(value) {
|
|
17257
17606
|
const numeric = readNumber3(value);
|
|
@@ -17942,10 +18291,10 @@ function parseHermesApiCapabilities(payload) {
|
|
|
17942
18291
|
sessionKeyHeader: readString12(features, "session_key_header")
|
|
17943
18292
|
};
|
|
17944
18293
|
}
|
|
17945
|
-
async function callHermesApi(
|
|
18294
|
+
async function callHermesApi(path34, init, options) {
|
|
17946
18295
|
const method = init.method ?? "GET";
|
|
17947
18296
|
const startedAt = Date.now();
|
|
17948
|
-
void options.logger?.debug("hermes_api_request_started", { method, path:
|
|
18297
|
+
void options.logger?.debug("hermes_api_request_started", { method, path: path34 });
|
|
17949
18298
|
const availability = await ensureHermesApiServerAvailable({
|
|
17950
18299
|
fetchImpl: options.fetchImpl,
|
|
17951
18300
|
logger: options.logger,
|
|
@@ -17954,7 +18303,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
17954
18303
|
});
|
|
17955
18304
|
let config = availability.configResult.apiServer;
|
|
17956
18305
|
const fetcher = options.fetchImpl ?? fetch;
|
|
17957
|
-
const request = () => fetchHermesApi(fetcher, config,
|
|
18306
|
+
const request = () => fetchHermesApi(fetcher, config, path34, init, options);
|
|
17958
18307
|
let response;
|
|
17959
18308
|
try {
|
|
17960
18309
|
response = await request();
|
|
@@ -17962,7 +18311,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
17962
18311
|
logHermesApiError(
|
|
17963
18312
|
options.logger,
|
|
17964
18313
|
method,
|
|
17965
|
-
|
|
18314
|
+
path34,
|
|
17966
18315
|
options.profileName,
|
|
17967
18316
|
startedAt,
|
|
17968
18317
|
error
|
|
@@ -17973,7 +18322,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
17973
18322
|
logHermesApiResponse(
|
|
17974
18323
|
options.logger,
|
|
17975
18324
|
method,
|
|
17976
|
-
|
|
18325
|
+
path34,
|
|
17977
18326
|
options.profileName,
|
|
17978
18327
|
startedAt,
|
|
17979
18328
|
response
|
|
@@ -17982,7 +18331,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
17982
18331
|
}
|
|
17983
18332
|
void options.logger?.warn("hermes_api_request_retrying_after_401", {
|
|
17984
18333
|
method,
|
|
17985
|
-
path:
|
|
18334
|
+
path: path34,
|
|
17986
18335
|
profile: options.profileName ?? "default",
|
|
17987
18336
|
port: config.port ?? null,
|
|
17988
18337
|
duration_ms: Date.now() - startedAt
|
|
@@ -18001,7 +18350,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
18001
18350
|
logHermesApiError(
|
|
18002
18351
|
options.logger,
|
|
18003
18352
|
method,
|
|
18004
|
-
|
|
18353
|
+
path34,
|
|
18005
18354
|
options.profileName,
|
|
18006
18355
|
startedAt,
|
|
18007
18356
|
error
|
|
@@ -18011,7 +18360,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
18011
18360
|
logHermesApiResponse(
|
|
18012
18361
|
options.logger,
|
|
18013
18362
|
method,
|
|
18014
|
-
|
|
18363
|
+
path34,
|
|
18015
18364
|
options.profileName,
|
|
18016
18365
|
startedAt,
|
|
18017
18366
|
response
|
|
@@ -18021,7 +18370,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
18021
18370
|
}
|
|
18022
18371
|
void options.logger?.warn("hermes_api_request_repairing_after_401", {
|
|
18023
18372
|
method,
|
|
18024
|
-
path:
|
|
18373
|
+
path: path34,
|
|
18025
18374
|
profile: options.profileName ?? "default",
|
|
18026
18375
|
port: config.port ?? null,
|
|
18027
18376
|
duration_ms: Date.now() - startedAt
|
|
@@ -18042,7 +18391,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
18042
18391
|
logHermesApiError(
|
|
18043
18392
|
options.logger,
|
|
18044
18393
|
method,
|
|
18045
|
-
|
|
18394
|
+
path34,
|
|
18046
18395
|
options.profileName,
|
|
18047
18396
|
startedAt,
|
|
18048
18397
|
error
|
|
@@ -18052,21 +18401,21 @@ async function callHermesApi(path33, init, options) {
|
|
|
18052
18401
|
logHermesApiResponse(
|
|
18053
18402
|
options.logger,
|
|
18054
18403
|
method,
|
|
18055
|
-
|
|
18404
|
+
path34,
|
|
18056
18405
|
options.profileName,
|
|
18057
18406
|
startedAt,
|
|
18058
18407
|
response
|
|
18059
18408
|
);
|
|
18060
18409
|
return response;
|
|
18061
18410
|
}
|
|
18062
|
-
async function fetchHermesApi(fetcher, config,
|
|
18411
|
+
async function fetchHermesApi(fetcher, config, path34, init, options) {
|
|
18063
18412
|
const headers = new Headers(init.headers);
|
|
18064
18413
|
headers.set("accept", headers.get("accept") ?? "application/json");
|
|
18065
18414
|
if (config.key) {
|
|
18066
18415
|
headers.set("x-api-key", config.key);
|
|
18067
18416
|
headers.set("authorization", `Bearer ${config.key}`);
|
|
18068
18417
|
}
|
|
18069
|
-
return await fetcher(`http://127.0.0.1:${config.port}${
|
|
18418
|
+
return await fetcher(`http://127.0.0.1:${config.port}${path34}`, {
|
|
18070
18419
|
...init,
|
|
18071
18420
|
headers
|
|
18072
18421
|
}).catch((error) => {
|
|
@@ -18075,10 +18424,10 @@ async function fetchHermesApi(fetcher, config, path33, init, options) {
|
|
|
18075
18424
|
}
|
|
18076
18425
|
void options.logger?.warn("hermes_api_server_connect_failed", {
|
|
18077
18426
|
method: String(init.method ?? "GET").toUpperCase(),
|
|
18078
|
-
path:
|
|
18427
|
+
path: path34,
|
|
18079
18428
|
profile: options.profileName ?? "default",
|
|
18080
18429
|
port: config.port ?? null,
|
|
18081
|
-
url: `http://127.0.0.1:${config.port}${
|
|
18430
|
+
url: `http://127.0.0.1:${config.port}${path34}`,
|
|
18082
18431
|
error: error instanceof Error ? error.message : String(error)
|
|
18083
18432
|
});
|
|
18084
18433
|
throw new LinkHttpError(
|
|
@@ -18088,10 +18437,10 @@ async function fetchHermesApi(fetcher, config, path33, init, options) {
|
|
|
18088
18437
|
);
|
|
18089
18438
|
});
|
|
18090
18439
|
}
|
|
18091
|
-
function logHermesApiResponse(logger, method,
|
|
18440
|
+
function logHermesApiResponse(logger, method, path34, profileName, startedAt, response) {
|
|
18092
18441
|
const fields = {
|
|
18093
18442
|
method,
|
|
18094
|
-
path:
|
|
18443
|
+
path: path34,
|
|
18095
18444
|
profile: profileName ?? "default",
|
|
18096
18445
|
status: response.status,
|
|
18097
18446
|
duration_ms: Date.now() - startedAt
|
|
@@ -18112,10 +18461,10 @@ async function logHermesApiFailureResponse(logger, fields, response) {
|
|
|
18112
18461
|
...upstreamError ? { upstream_error: upstreamError } : {}
|
|
18113
18462
|
});
|
|
18114
18463
|
}
|
|
18115
|
-
function logHermesApiError(logger, method,
|
|
18464
|
+
function logHermesApiError(logger, method, path34, profileName, startedAt, error) {
|
|
18116
18465
|
void logger?.warn("hermes_api_request_failed", {
|
|
18117
18466
|
method,
|
|
18118
|
-
path:
|
|
18467
|
+
path: path34,
|
|
18119
18468
|
profile: profileName ?? "default",
|
|
18120
18469
|
duration_ms: Date.now() - startedAt,
|
|
18121
18470
|
...error instanceof LinkHttpError ? { status: error.status, code: error.code } : {},
|
|
@@ -23989,6 +24338,7 @@ function isNodeError17(error, code) {
|
|
|
23989
24338
|
|
|
23990
24339
|
// src/conversations/conversation-service.ts
|
|
23991
24340
|
var ALL_CONVERSATION_EVENTS = "conversation:*";
|
|
24341
|
+
var HERMES_ARCHIVE_STATE_SYNC_ID = "hermes-agent-archive-state-v1";
|
|
23992
24342
|
function isConversationNotificationEvent(event) {
|
|
23993
24343
|
const type = event.type.toLowerCase();
|
|
23994
24344
|
return type === "conversation.created" || type === "conversation.updated" || isAlwaysPublishedNotificationEvent(type) || type === "message.created" || type === "message.completed" || type === "message.failed" || type === "run.started" || type === "run.queued" || 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");
|
|
@@ -24115,6 +24465,7 @@ var ConversationService = class {
|
|
|
24115
24465
|
return manifest;
|
|
24116
24466
|
}
|
|
24117
24467
|
const snapshot = await this.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
24468
|
+
await this.setHermesSessionsArchived(manifest, snapshot, false);
|
|
24118
24469
|
const unarchivedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
24119
24470
|
const next = {
|
|
24120
24471
|
...manifest,
|
|
@@ -24183,12 +24534,16 @@ var ConversationService = class {
|
|
|
24183
24534
|
async createConversation(input = {}) {
|
|
24184
24535
|
await this.store.ensureConversationsDir();
|
|
24185
24536
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
24186
|
-
const id = `conv_${
|
|
24537
|
+
const id = `conv_${randomUUID11().replaceAll("-", "")}`;
|
|
24187
24538
|
const title = input.title?.trim() || DEFAULT_CONVERSATION_TITLE;
|
|
24188
24539
|
const profile = await resolveConversationProfileTarget(
|
|
24189
24540
|
this.paths,
|
|
24190
24541
|
input.profileName
|
|
24191
24542
|
);
|
|
24543
|
+
const workspaceId = await assertWorkspaceExists(
|
|
24544
|
+
this.paths,
|
|
24545
|
+
input.workspaceId
|
|
24546
|
+
);
|
|
24192
24547
|
const manifest = {
|
|
24193
24548
|
id,
|
|
24194
24549
|
schema_version: 1,
|
|
@@ -24204,6 +24559,7 @@ var ConversationService = class {
|
|
|
24204
24559
|
profile: profile.profileName,
|
|
24205
24560
|
owner_account_id: input.accountId,
|
|
24206
24561
|
owner_app_instance_id: input.appInstanceId,
|
|
24562
|
+
workspace_id: workspaceId ?? null,
|
|
24207
24563
|
created_at: now,
|
|
24208
24564
|
updated_at: now,
|
|
24209
24565
|
last_event_seq: 0
|
|
@@ -24214,6 +24570,7 @@ var ConversationService = class {
|
|
|
24214
24570
|
payload: {
|
|
24215
24571
|
title,
|
|
24216
24572
|
title_source: manifest.title_source,
|
|
24573
|
+
workspace_id: workspaceId ?? null,
|
|
24217
24574
|
profile: {
|
|
24218
24575
|
uid: profile.profileUid,
|
|
24219
24576
|
name: profile.profileName,
|
|
@@ -24368,7 +24725,7 @@ var ConversationService = class {
|
|
|
24368
24725
|
updated_at: now
|
|
24369
24726
|
} : manifest;
|
|
24370
24727
|
const message = {
|
|
24371
|
-
id: `msg_${
|
|
24728
|
+
id: `msg_${randomUUID11().replaceAll("-", "")}`,
|
|
24372
24729
|
schema_version: 1,
|
|
24373
24730
|
conversation_id: manifest.id,
|
|
24374
24731
|
role: "assistant",
|
|
@@ -24559,6 +24916,7 @@ var ConversationService = class {
|
|
|
24559
24916
|
if (result.imported_count > 0 || result.reprojected_count > 0) {
|
|
24560
24917
|
await this.rebuildStatisticsIndex();
|
|
24561
24918
|
}
|
|
24919
|
+
await this.syncHermesArchiveStatesBestEffort();
|
|
24562
24920
|
return result;
|
|
24563
24921
|
})();
|
|
24564
24922
|
this.hermesSessionSyncPromise = task;
|
|
@@ -25126,6 +25484,136 @@ var ConversationService = class {
|
|
|
25126
25484
|
async unarchiveConversation(conversationId) {
|
|
25127
25485
|
return this.maintenance.unarchiveConversation(conversationId);
|
|
25128
25486
|
}
|
|
25487
|
+
listWorkspaces() {
|
|
25488
|
+
return listWorkspaces(this.paths);
|
|
25489
|
+
}
|
|
25490
|
+
createWorkspace(input) {
|
|
25491
|
+
return createWorkspace(this.paths, input);
|
|
25492
|
+
}
|
|
25493
|
+
renameWorkspace(workspaceId, input) {
|
|
25494
|
+
return renameWorkspace(this.paths, workspaceId, input);
|
|
25495
|
+
}
|
|
25496
|
+
async deleteWorkspace(workspaceId) {
|
|
25497
|
+
const workspace = await deleteWorkspace(this.paths, workspaceId);
|
|
25498
|
+
const movedConversationCount = await this.clearWorkspaceFromConversations(workspace.id);
|
|
25499
|
+
return { workspace, movedConversationCount };
|
|
25500
|
+
}
|
|
25501
|
+
async setConversationWorkspace(conversationId, workspaceId) {
|
|
25502
|
+
const normalizedWorkspaceId = await assertWorkspaceExists(
|
|
25503
|
+
this.paths,
|
|
25504
|
+
workspaceId
|
|
25505
|
+
);
|
|
25506
|
+
return this.withConversationLock(conversationId, async () => {
|
|
25507
|
+
const manifest = await this.store.readRunnableManifest(conversationId);
|
|
25508
|
+
const currentWorkspaceId = manifest.workspace_id ?? null;
|
|
25509
|
+
const nextWorkspaceId = normalizedWorkspaceId ?? null;
|
|
25510
|
+
const snapshot = await this.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
25511
|
+
if (currentWorkspaceId !== nextWorkspaceId) {
|
|
25512
|
+
const nextManifest = {
|
|
25513
|
+
...manifest,
|
|
25514
|
+
workspace_id: nextWorkspaceId
|
|
25515
|
+
};
|
|
25516
|
+
await this.store.writeManifest(nextManifest);
|
|
25517
|
+
await this.persistConversationStatsWithoutManifestRewrite(
|
|
25518
|
+
nextManifest,
|
|
25519
|
+
snapshot
|
|
25520
|
+
);
|
|
25521
|
+
const conversation = await this.queries.summarizeConversation(
|
|
25522
|
+
nextManifest,
|
|
25523
|
+
snapshot
|
|
25524
|
+
);
|
|
25525
|
+
return {
|
|
25526
|
+
conversation_id: conversationId,
|
|
25527
|
+
workspace_id: nextWorkspaceId,
|
|
25528
|
+
conversation
|
|
25529
|
+
};
|
|
25530
|
+
}
|
|
25531
|
+
return {
|
|
25532
|
+
conversation_id: conversationId,
|
|
25533
|
+
workspace_id: currentWorkspaceId,
|
|
25534
|
+
conversation: await this.queries.summarizeConversation(
|
|
25535
|
+
manifest,
|
|
25536
|
+
snapshot
|
|
25537
|
+
)
|
|
25538
|
+
};
|
|
25539
|
+
});
|
|
25540
|
+
}
|
|
25541
|
+
async clearWorkspaceFromConversations(workspaceId) {
|
|
25542
|
+
const normalizedWorkspaceId = normalizeOptionalWorkspaceId(workspaceId);
|
|
25543
|
+
if (!normalizedWorkspaceId) {
|
|
25544
|
+
return 0;
|
|
25545
|
+
}
|
|
25546
|
+
let movedConversationCount = 0;
|
|
25547
|
+
for (const conversationId of await this.store.listConversationIds()) {
|
|
25548
|
+
await this.withConversationLock(conversationId, async () => {
|
|
25549
|
+
const manifest = await this.store.readManifest(conversationId).catch(() => null);
|
|
25550
|
+
if (!manifest || manifest.workspace_id !== normalizedWorkspaceId) {
|
|
25551
|
+
return;
|
|
25552
|
+
}
|
|
25553
|
+
const nextManifest = {
|
|
25554
|
+
...manifest,
|
|
25555
|
+
workspace_id: null
|
|
25556
|
+
};
|
|
25557
|
+
await this.store.writeManifest(nextManifest);
|
|
25558
|
+
const snapshot = await this.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
25559
|
+
await this.persistConversationStatsWithoutManifestRewrite(
|
|
25560
|
+
nextManifest,
|
|
25561
|
+
snapshot
|
|
25562
|
+
);
|
|
25563
|
+
movedConversationCount += 1;
|
|
25564
|
+
});
|
|
25565
|
+
}
|
|
25566
|
+
return movedConversationCount;
|
|
25567
|
+
}
|
|
25568
|
+
async syncHermesArchiveStatesBestEffort() {
|
|
25569
|
+
const markerPath = this.hermesArchiveStateSyncMarkerPath();
|
|
25570
|
+
const marker = await readJsonFile(
|
|
25571
|
+
markerPath
|
|
25572
|
+
).catch(() => null);
|
|
25573
|
+
if (marker?.id === HERMES_ARCHIVE_STATE_SYNC_ID) {
|
|
25574
|
+
return;
|
|
25575
|
+
}
|
|
25576
|
+
let failedCount = 0;
|
|
25577
|
+
for (const conversationId of await this.store.listConversationIds()) {
|
|
25578
|
+
const manifest = await this.store.readManifest(conversationId).catch(() => null);
|
|
25579
|
+
if (!manifest || manifest.status !== "archived") {
|
|
25580
|
+
continue;
|
|
25581
|
+
}
|
|
25582
|
+
const snapshot = await this.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
25583
|
+
await this.setHermesSessionsArchived(manifest, snapshot, true).catch(
|
|
25584
|
+
(error) => {
|
|
25585
|
+
failedCount += 1;
|
|
25586
|
+
void this.logger.debug("hermes_archive_state_sync_failed", {
|
|
25587
|
+
conversation_id: conversationId,
|
|
25588
|
+
archived: true,
|
|
25589
|
+
error: error instanceof Error ? error.message : String(error)
|
|
25590
|
+
});
|
|
25591
|
+
}
|
|
25592
|
+
);
|
|
25593
|
+
}
|
|
25594
|
+
if (failedCount > 0) {
|
|
25595
|
+
return;
|
|
25596
|
+
}
|
|
25597
|
+
await writeJsonFile(markerPath, {
|
|
25598
|
+
id: HERMES_ARCHIVE_STATE_SYNC_ID,
|
|
25599
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
25600
|
+
});
|
|
25601
|
+
}
|
|
25602
|
+
async setHermesSessionsArchived(manifest, snapshot, archived) {
|
|
25603
|
+
const targets = collectHermesSessionMutationTargets(manifest, snapshot);
|
|
25604
|
+
for (const target of targets) {
|
|
25605
|
+
await setTuiGatewaySessionArchived({
|
|
25606
|
+
paths: this.paths,
|
|
25607
|
+
logger: this.logger,
|
|
25608
|
+
profileName: target.profileName,
|
|
25609
|
+
sessionId: target.sessionId,
|
|
25610
|
+
archived
|
|
25611
|
+
});
|
|
25612
|
+
}
|
|
25613
|
+
}
|
|
25614
|
+
hermesArchiveStateSyncMarkerPath() {
|
|
25615
|
+
return path24.join(this.paths.indexesDir, "hermes-archive-state-sync.json");
|
|
25616
|
+
}
|
|
25129
25617
|
prepareClearAllConversationPlan(targetStatus) {
|
|
25130
25618
|
return this.maintenance.prepareClearAllConversationPlan(targetStatus);
|
|
25131
25619
|
}
|
|
@@ -25162,45 +25650,15 @@ var ConversationService = class {
|
|
|
25162
25650
|
if (!manifest || !conversationMatchesProfile(manifest, profileName, profileUid)) {
|
|
25163
25651
|
continue;
|
|
25164
25652
|
}
|
|
25165
|
-
|
|
25166
|
-
conversationId
|
|
25167
|
-
async () => {
|
|
25168
|
-
const lockedManifest = await this.store.readManifest(conversationId).catch(() => null);
|
|
25169
|
-
if (!lockedManifest || !conversationMatchesProfile(lockedManifest, profileName, profileUid)) {
|
|
25170
|
-
return false;
|
|
25171
|
-
}
|
|
25172
|
-
this.abortActiveRunsForConversation(conversationId);
|
|
25173
|
-
const snapshot = await this.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
25174
|
-
const blobIds = /* @__PURE__ */ new Set([
|
|
25175
|
-
...collectBlobIds(snapshot),
|
|
25176
|
-
...await listConversationBlobIds(this.paths, conversationId).catch(
|
|
25177
|
-
() => []
|
|
25178
|
-
)
|
|
25179
|
-
]);
|
|
25180
|
-
for (const blobId of blobIds) {
|
|
25181
|
-
await pruneConversationBlobReference(
|
|
25182
|
-
this.paths,
|
|
25183
|
-
conversationId,
|
|
25184
|
-
blobId
|
|
25185
|
-
).catch((error) => {
|
|
25186
|
-
void this.logger.warn("profile_delete_blob_gc_failed", {
|
|
25187
|
-
profile: profileName,
|
|
25188
|
-
conversation_id: conversationId,
|
|
25189
|
-
blob_id: blobId,
|
|
25190
|
-
error: error instanceof Error ? error.message : String(error)
|
|
25191
|
-
});
|
|
25192
|
-
});
|
|
25193
|
-
}
|
|
25194
|
-
await removeConversationFiles(this.paths, conversationId);
|
|
25195
|
-
await removeConversationDeliveryStaging(
|
|
25196
|
-
this.paths,
|
|
25197
|
-
conversationId
|
|
25198
|
-
).catch(() => void 0);
|
|
25199
|
-
return true;
|
|
25200
|
-
}
|
|
25201
|
-
);
|
|
25202
|
-
if (deleted) {
|
|
25653
|
+
try {
|
|
25654
|
+
await this.deleteConversation(conversationId);
|
|
25203
25655
|
deletedConversationIds.push(conversationId);
|
|
25656
|
+
} catch (error) {
|
|
25657
|
+
void this.logger.warn("profile_delete_conversation_cleanup_failed", {
|
|
25658
|
+
profile: profileName,
|
|
25659
|
+
conversation_id: conversationId,
|
|
25660
|
+
error: error instanceof Error ? error.message : String(error)
|
|
25661
|
+
});
|
|
25204
25662
|
}
|
|
25205
25663
|
}
|
|
25206
25664
|
await deleteConversationStatsForProfile(this.paths, {
|
|
@@ -25231,6 +25689,13 @@ var ConversationService = class {
|
|
|
25231
25689
|
statsOverride
|
|
25232
25690
|
);
|
|
25233
25691
|
}
|
|
25692
|
+
async persistConversationStatsWithoutManifestRewrite(manifest, snapshot) {
|
|
25693
|
+
const stats = manifest.stats ?? buildConversationStats(manifest, snapshot);
|
|
25694
|
+
await upsertConversationStats(
|
|
25695
|
+
this.paths,
|
|
25696
|
+
toStatsIndexRecord({ ...manifest, stats }, stats)
|
|
25697
|
+
);
|
|
25698
|
+
}
|
|
25234
25699
|
async appendEvent(conversationId, input) {
|
|
25235
25700
|
const event = await this.store.appendEvent(conversationId, input);
|
|
25236
25701
|
this.emitter.emit(this.liveEventName(conversationId), event);
|
|
@@ -25345,7 +25810,7 @@ function approvalRestartText(language, zh, en) {
|
|
|
25345
25810
|
}
|
|
25346
25811
|
|
|
25347
25812
|
// src/security/devices.ts
|
|
25348
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
25813
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12, timingSafeEqual, createHash as createHash7 } from "crypto";
|
|
25349
25814
|
var ACCESS_TOKEN_TTL_MS = 15 * 60 * 1e3;
|
|
25350
25815
|
var REFRESH_TOKEN_TTL_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
25351
25816
|
var DEVICE_SEEN_WRITE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
@@ -25363,7 +25828,7 @@ async function createDeviceSession(input, paths = resolveRuntimePaths()) {
|
|
|
25363
25828
|
}
|
|
25364
25829
|
}
|
|
25365
25830
|
const device = {
|
|
25366
|
-
id: `dev_${
|
|
25831
|
+
id: `dev_${randomUUID12().replaceAll("-", "")}`,
|
|
25367
25832
|
label: normalizeDeviceLabel(input.label),
|
|
25368
25833
|
platform: normalizeDevicePlatform(input.platform),
|
|
25369
25834
|
model: normalizeDeviceModel(input.model),
|
|
@@ -26240,7 +26705,8 @@ function registerConversationRoutes(router, options) {
|
|
|
26240
26705
|
ctx.set("cache-control", "no-store");
|
|
26241
26706
|
const result = await conversations.listConversationPage({
|
|
26242
26707
|
limit: readLimit(ctx.query.limit),
|
|
26243
|
-
cursor
|
|
26708
|
+
cursor,
|
|
26709
|
+
workspace: readConversationWorkspaceFilter(ctx.query)
|
|
26244
26710
|
});
|
|
26245
26711
|
const localized = localizeConversationListPage(result, language);
|
|
26246
26712
|
ctx.body = {
|
|
@@ -26309,7 +26775,8 @@ function registerConversationRoutes(router, options) {
|
|
|
26309
26775
|
title: readString19(body, "title") ?? void 0,
|
|
26310
26776
|
profileName: readOptionalProfileName(body),
|
|
26311
26777
|
accountId: auth.accountId,
|
|
26312
|
-
appInstanceId: auth.appInstanceId
|
|
26778
|
+
appInstanceId: auth.appInstanceId,
|
|
26779
|
+
workspaceId: readConversationWorkspaceId(body)
|
|
26313
26780
|
}),
|
|
26314
26781
|
language
|
|
26315
26782
|
)
|
|
@@ -26691,6 +27158,21 @@ function registerConversationRoutes(router, options) {
|
|
|
26691
27158
|
};
|
|
26692
27159
|
}
|
|
26693
27160
|
);
|
|
27161
|
+
router.patch("/api/v1/conversations/:conversationId/workspace", async (ctx) => {
|
|
27162
|
+
await authenticateRequest(ctx, paths);
|
|
27163
|
+
const language = readPreferredLanguage(ctx);
|
|
27164
|
+
const body = await readJsonBody(ctx.req);
|
|
27165
|
+
ctx.body = {
|
|
27166
|
+
ok: true,
|
|
27167
|
+
...localizeConversationResult(
|
|
27168
|
+
await conversations.setConversationWorkspace(
|
|
27169
|
+
ctx.params.conversationId,
|
|
27170
|
+
readConversationWorkspaceId(body)
|
|
27171
|
+
),
|
|
27172
|
+
language
|
|
27173
|
+
)
|
|
27174
|
+
};
|
|
27175
|
+
});
|
|
26694
27176
|
router.delete("/api/v1/conversations/:conversationId", async (ctx) => {
|
|
26695
27177
|
const auth = await authenticateRequest(ctx, paths);
|
|
26696
27178
|
const result = await conversations.deleteConversation(
|
|
@@ -26784,6 +27266,25 @@ function readConversationListForce(query) {
|
|
|
26784
27266
|
const raw = Array.isArray(query.force) ? query.force[0] : query.force;
|
|
26785
27267
|
return readBoolean3(raw) === true;
|
|
26786
27268
|
}
|
|
27269
|
+
function readConversationWorkspaceFilter(query) {
|
|
27270
|
+
const workspace = readQueryString(query.workspace) ?? readQueryString(query.workspace_id);
|
|
27271
|
+
if (!workspace || workspace === "all") {
|
|
27272
|
+
return { kind: "all" };
|
|
27273
|
+
}
|
|
27274
|
+
if (workspace === "default") {
|
|
27275
|
+
return { kind: "default" };
|
|
27276
|
+
}
|
|
27277
|
+
return { kind: "workspace", workspaceId: workspace };
|
|
27278
|
+
}
|
|
27279
|
+
function readConversationWorkspaceId(body) {
|
|
27280
|
+
if (Object.prototype.hasOwnProperty.call(body, "workspace_id") || Object.prototype.hasOwnProperty.call(body, "workspaceId")) {
|
|
27281
|
+
return readString19(body, "workspace_id") ?? readString19(body, "workspaceId");
|
|
27282
|
+
}
|
|
27283
|
+
if (Object.prototype.hasOwnProperty.call(body, "workspace")) {
|
|
27284
|
+
return readString19(body, "workspace");
|
|
27285
|
+
}
|
|
27286
|
+
return void 0;
|
|
27287
|
+
}
|
|
26787
27288
|
function localizeConversationListPage(page, language) {
|
|
26788
27289
|
return {
|
|
26789
27290
|
...page,
|
|
@@ -26997,11 +27498,11 @@ function isSseRequestContext(ctx) {
|
|
|
26997
27498
|
}
|
|
26998
27499
|
return isSseRequestPath(ctx.path) || isActiveSseSocket(ctx.req.socket);
|
|
26999
27500
|
}
|
|
27000
|
-
function isSseRequestPath(
|
|
27001
|
-
if (!
|
|
27501
|
+
function isSseRequestPath(path34) {
|
|
27502
|
+
if (!path34) {
|
|
27002
27503
|
return false;
|
|
27003
27504
|
}
|
|
27004
|
-
return
|
|
27505
|
+
return path34 === "/api/v1/conversations/events" || path34 === "/api/v1/profile-creation/events" || path34 === "/api/v1/hermes/update/events" || path34 === "/api/v1/link/update/events" || /^\/api\/v1\/conversations\/[^/]+\/events$/u.test(path34) || /^\/api\/v1\/runs\/[^/]+\/events$/u.test(path34);
|
|
27005
27506
|
}
|
|
27006
27507
|
function isExpectedClientDisconnectError2(error, options = {}) {
|
|
27007
27508
|
if (!(error instanceof Error)) {
|
|
@@ -27833,7 +28334,7 @@ function errorMessage3(error) {
|
|
|
27833
28334
|
|
|
27834
28335
|
// src/hermes/profile-identity.ts
|
|
27835
28336
|
import { readFile as readFile16, stat as stat17 } from "fs/promises";
|
|
27836
|
-
import
|
|
28337
|
+
import path25 from "path";
|
|
27837
28338
|
var MAX_SOUL_MD_LENGTH = 2e4;
|
|
27838
28339
|
async function readHermesProfileIdentity(profileName, paths) {
|
|
27839
28340
|
await assertProfileExists3(profileName, paths);
|
|
@@ -27890,7 +28391,7 @@ async function assertProfileExists3(profileName, paths) {
|
|
|
27890
28391
|
}
|
|
27891
28392
|
}
|
|
27892
28393
|
function resolveSoulPath(profileName) {
|
|
27893
|
-
return
|
|
28394
|
+
return path25.join(resolveHermesProfileDir(profileName), "SOUL.md");
|
|
27894
28395
|
}
|
|
27895
28396
|
function isNodeError18(error, code) {
|
|
27896
28397
|
return error instanceof Error && "code" in error && error.code === code;
|
|
@@ -27906,13 +28407,13 @@ import {
|
|
|
27906
28407
|
rm as rm7,
|
|
27907
28408
|
stat as stat19
|
|
27908
28409
|
} from "fs/promises";
|
|
27909
|
-
import
|
|
28410
|
+
import path27 from "path";
|
|
27910
28411
|
import YAML5 from "yaml";
|
|
27911
28412
|
|
|
27912
28413
|
// src/hermes/link-skill.ts
|
|
27913
28414
|
import { readFile as readFile17, stat as stat18 } from "fs/promises";
|
|
27914
28415
|
import os5 from "os";
|
|
27915
|
-
import
|
|
28416
|
+
import path26 from "path";
|
|
27916
28417
|
import YAML4 from "yaml";
|
|
27917
28418
|
var HERMES_LINK_SKILL_ROOT_DIR = "hermes-skills";
|
|
27918
28419
|
var HERMES_LINK_SKILL_DIR = "hermes-link";
|
|
@@ -27997,7 +28498,7 @@ Do not modify Hermes profiles, delete user data, edit config files, or kill proc
|
|
|
27997
28498
|
async function ensureHermesLinkSkillInstalledForProfiles(options = {}) {
|
|
27998
28499
|
const paths = options.paths ?? resolveRuntimePaths();
|
|
27999
28500
|
const externalDir = resolveHermesLinkSkillExternalDir(paths);
|
|
28000
|
-
const skillPath =
|
|
28501
|
+
const skillPath = path26.join(
|
|
28001
28502
|
externalDir,
|
|
28002
28503
|
HERMES_LINK_SKILL_DIR,
|
|
28003
28504
|
HERMES_LINK_SKILL_FILE
|
|
@@ -28079,7 +28580,7 @@ function withDefaultProfilePlaceholder2(profiles) {
|
|
|
28079
28580
|
];
|
|
28080
28581
|
}
|
|
28081
28582
|
function resolveHermesLinkSkillExternalDir(paths = resolveRuntimePaths()) {
|
|
28082
|
-
return
|
|
28583
|
+
return path26.join(paths.homeDir, HERMES_LINK_SKILL_ROOT_DIR);
|
|
28083
28584
|
}
|
|
28084
28585
|
async function writeHermesLinkSkill(skillPath) {
|
|
28085
28586
|
const existing = await readFile17(skillPath, "utf8").catch((error) => {
|
|
@@ -28174,11 +28675,11 @@ function appendExternalDir(current, externalDir, hermesHome) {
|
|
|
28174
28675
|
const seen = new Set(
|
|
28175
28676
|
entries.map((entry) => resolveExternalDirEntry(entry, hermesHome))
|
|
28176
28677
|
);
|
|
28177
|
-
const normalizedExternalDir =
|
|
28678
|
+
const normalizedExternalDir = path26.resolve(externalDir);
|
|
28178
28679
|
return seen.has(normalizedExternalDir) ? entries : [...entries, normalizedExternalDir];
|
|
28179
28680
|
}
|
|
28180
28681
|
function externalDirsInclude(current, externalDir, hermesHome) {
|
|
28181
|
-
const normalizedExternalDir =
|
|
28682
|
+
const normalizedExternalDir = path26.resolve(externalDir);
|
|
28182
28683
|
return readExternalDirEntries(current).some(
|
|
28183
28684
|
(entry) => resolveExternalDirEntry(entry, hermesHome) === normalizedExternalDir
|
|
28184
28685
|
);
|
|
@@ -28189,14 +28690,14 @@ function readExternalDirEntries(value) {
|
|
|
28189
28690
|
}
|
|
28190
28691
|
function resolveExternalDirEntry(entry, hermesHome) {
|
|
28191
28692
|
const expanded = expandHome(expandEnvVars(entry));
|
|
28192
|
-
return
|
|
28693
|
+
return path26.resolve(path26.isAbsolute(expanded) ? expanded : path26.join(hermesHome, expanded));
|
|
28193
28694
|
}
|
|
28194
28695
|
function expandHome(value) {
|
|
28195
28696
|
if (value === "~") {
|
|
28196
28697
|
return os5.homedir();
|
|
28197
28698
|
}
|
|
28198
|
-
if (value.startsWith(`~${
|
|
28199
|
-
return
|
|
28699
|
+
if (value.startsWith(`~${path26.sep}`) || value.startsWith("~/")) {
|
|
28700
|
+
return path26.join(os5.homedir(), value.slice(2));
|
|
28200
28701
|
}
|
|
28201
28702
|
return value;
|
|
28202
28703
|
}
|
|
@@ -28748,7 +29249,7 @@ function collectEnvKeys(value, keys = /* @__PURE__ */ new Set()) {
|
|
|
28748
29249
|
return keys;
|
|
28749
29250
|
}
|
|
28750
29251
|
async function writeEnvValues(profileName, values) {
|
|
28751
|
-
const envPath =
|
|
29252
|
+
const envPath = path27.join(resolveHermesProfileDir(profileName), ".env");
|
|
28752
29253
|
const existingRaw = await readFile18(envPath, "utf8").catch((error) => {
|
|
28753
29254
|
if (isNodeError20(error, "ENOENT")) {
|
|
28754
29255
|
return "";
|
|
@@ -28785,8 +29286,8 @@ async function writeEnvValues(profileName, values) {
|
|
|
28785
29286
|
await atomicWriteFilePreservingMetadata(envPath, nextRaw);
|
|
28786
29287
|
}
|
|
28787
29288
|
async function copySkills(sourceProfile, targetProfile) {
|
|
28788
|
-
const sourceSkills =
|
|
28789
|
-
const targetSkills =
|
|
29289
|
+
const sourceSkills = path27.join(resolveHermesProfileDir(sourceProfile), "skills");
|
|
29290
|
+
const targetSkills = path27.join(resolveHermesProfileDir(targetProfile), "skills");
|
|
28790
29291
|
if (!await pathExists2(sourceSkills)) {
|
|
28791
29292
|
return;
|
|
28792
29293
|
}
|
|
@@ -28881,10 +29382,10 @@ async function readProfileCreationLogLines(paths) {
|
|
|
28881
29382
|
);
|
|
28882
29383
|
}
|
|
28883
29384
|
function profileCreationStatePath(paths) {
|
|
28884
|
-
return
|
|
29385
|
+
return path27.join(paths.runDir, "profile-create-state.json");
|
|
28885
29386
|
}
|
|
28886
29387
|
function profileCreationLogPath(paths) {
|
|
28887
|
-
return
|
|
29388
|
+
return path27.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
|
|
28888
29389
|
}
|
|
28889
29390
|
async function clearProfileCreationLogFiles(paths) {
|
|
28890
29391
|
const primary = profileCreationLogPath(paths);
|
|
@@ -29173,7 +29674,7 @@ import {
|
|
|
29173
29674
|
readFile as readFile19,
|
|
29174
29675
|
stat as stat20
|
|
29175
29676
|
} from "fs/promises";
|
|
29176
|
-
import
|
|
29677
|
+
import path28 from "path";
|
|
29177
29678
|
import YAML6 from "yaml";
|
|
29178
29679
|
var ENTRY_DELIMITER = "\n\xA7\n";
|
|
29179
29680
|
var DEFAULT_MEMORY_LIMIT = 2200;
|
|
@@ -29504,7 +30005,7 @@ async function saveProviderSettings(profileName, provider, patch) {
|
|
|
29504
30005
|
});
|
|
29505
30006
|
await patchJsonProviderConfig(
|
|
29506
30007
|
profileName,
|
|
29507
|
-
|
|
30008
|
+
path28.join("hindsight", "config.json"),
|
|
29508
30009
|
{
|
|
29509
30010
|
mode: patch.mode,
|
|
29510
30011
|
api_url: patch.apiUrl,
|
|
@@ -29707,7 +30208,7 @@ async function patchHermesMemoryLimits(profileName, patch) {
|
|
|
29707
30208
|
await atomicWriteFilePreservingMetadata(configPath, document.toString());
|
|
29708
30209
|
}
|
|
29709
30210
|
function resolveMemoryDir(profileName) {
|
|
29710
|
-
return
|
|
30211
|
+
return path28.join(resolveHermesProfileDir(profileName), "memories");
|
|
29711
30212
|
}
|
|
29712
30213
|
async function readMemoryStore(profileName, target, limits) {
|
|
29713
30214
|
const filePath = memoryFilePath(profileName, target);
|
|
@@ -29768,7 +30269,7 @@ async function writeMemoryEntries(profileName, target, entries) {
|
|
|
29768
30269
|
);
|
|
29769
30270
|
}
|
|
29770
30271
|
function memoryFilePath(profileName, target) {
|
|
29771
|
-
return
|
|
30272
|
+
return path28.join(
|
|
29772
30273
|
resolveMemoryDir(profileName),
|
|
29773
30274
|
target === "user" ? "USER.md" : "MEMORY.md"
|
|
29774
30275
|
);
|
|
@@ -29828,7 +30329,7 @@ async function readCustomProviderSetupSummary(profileName) {
|
|
|
29828
30329
|
configurable: true,
|
|
29829
30330
|
configured: true,
|
|
29830
30331
|
configurationIssue: null,
|
|
29831
|
-
providerConfigPath:
|
|
30332
|
+
providerConfigPath: path28.join(
|
|
29832
30333
|
resolveHermesProfileDir(profileName),
|
|
29833
30334
|
"<provider>.json"
|
|
29834
30335
|
),
|
|
@@ -30222,7 +30723,7 @@ async function readProviderSettings(profileName, provider) {
|
|
|
30222
30723
|
stringSetting(
|
|
30223
30724
|
"dbPath",
|
|
30224
30725
|
"SQLite \u6570\u636E\u5E93\u8DEF\u5F84",
|
|
30225
|
-
config.db_path ??
|
|
30726
|
+
config.db_path ?? path28.join(resolveHermesProfileDir(profileName), "memory_store.db")
|
|
30226
30727
|
),
|
|
30227
30728
|
booleanSetting("autoExtract", "\u4F1A\u8BDD\u7ED3\u675F\u81EA\u52A8\u62BD\u53D6", config.auto_extract ?? false),
|
|
30228
30729
|
numberSetting("defaultTrust", "\u9ED8\u8BA4\u4FE1\u4EFB\u5206", config.default_trust ?? 0.5),
|
|
@@ -30258,7 +30759,7 @@ async function readProviderSettings(profileName, provider) {
|
|
|
30258
30759
|
stringSetting(
|
|
30259
30760
|
"workingDirectory",
|
|
30260
30761
|
"\u5DE5\u4F5C\u76EE\u5F55",
|
|
30261
|
-
|
|
30762
|
+
path28.join(resolveHermesProfileDir(profileName), "byterover"),
|
|
30262
30763
|
false
|
|
30263
30764
|
)
|
|
30264
30765
|
];
|
|
@@ -30267,16 +30768,16 @@ async function readProviderSettings(profileName, provider) {
|
|
|
30267
30768
|
}
|
|
30268
30769
|
function memoryProviderConfigPath(profileName, provider) {
|
|
30269
30770
|
if (provider === "honcho") {
|
|
30270
|
-
return
|
|
30771
|
+
return path28.join(resolveHermesProfileDir(profileName), "honcho.json");
|
|
30271
30772
|
}
|
|
30272
30773
|
if (provider === "mem0") {
|
|
30273
|
-
return
|
|
30774
|
+
return path28.join(resolveHermesProfileDir(profileName), "mem0.json");
|
|
30274
30775
|
}
|
|
30275
30776
|
if (provider === "supermemory") {
|
|
30276
|
-
return
|
|
30777
|
+
return path28.join(resolveHermesProfileDir(profileName), "supermemory.json");
|
|
30277
30778
|
}
|
|
30278
30779
|
if (provider === "hindsight") {
|
|
30279
|
-
return
|
|
30780
|
+
return path28.join(
|
|
30280
30781
|
resolveHermesProfileDir(profileName),
|
|
30281
30782
|
"hindsight",
|
|
30282
30783
|
"config.json"
|
|
@@ -30285,13 +30786,13 @@ function memoryProviderConfigPath(profileName, provider) {
|
|
|
30285
30786
|
return null;
|
|
30286
30787
|
}
|
|
30287
30788
|
function customProviderConfigPath(profileName, provider) {
|
|
30288
|
-
return
|
|
30789
|
+
return path28.join(
|
|
30289
30790
|
resolveHermesProfileDir(profileName),
|
|
30290
30791
|
`${normalizeCustomProviderId(provider)}.json`
|
|
30291
30792
|
);
|
|
30292
30793
|
}
|
|
30293
30794
|
function customProviderRegistryPath(profileName) {
|
|
30294
|
-
return
|
|
30795
|
+
return path28.join(
|
|
30295
30796
|
resolveHermesProfileDir(profileName),
|
|
30296
30797
|
CUSTOM_PROVIDER_REGISTRY_FILE
|
|
30297
30798
|
);
|
|
@@ -30343,7 +30844,7 @@ async function saveCustomProviderRegistryEntry(profileName, provider) {
|
|
|
30343
30844
|
);
|
|
30344
30845
|
}
|
|
30345
30846
|
async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
30346
|
-
const pluginsDir =
|
|
30847
|
+
const pluginsDir = path28.join(resolveHermesProfileDir(profileName), "plugins");
|
|
30347
30848
|
const entries = await readdir10(pluginsDir, { withFileTypes: true }).catch(
|
|
30348
30849
|
(error) => {
|
|
30349
30850
|
if (isNodeError21(error, "ENOENT")) {
|
|
@@ -30363,7 +30864,7 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
|
30363
30864
|
} catch {
|
|
30364
30865
|
continue;
|
|
30365
30866
|
}
|
|
30366
|
-
const providerDir =
|
|
30867
|
+
const providerDir = path28.join(pluginsDir, entry.name);
|
|
30367
30868
|
if (!await isMemoryProviderPluginDir(providerDir)) {
|
|
30368
30869
|
continue;
|
|
30369
30870
|
}
|
|
@@ -30377,7 +30878,7 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
|
30377
30878
|
return descriptors;
|
|
30378
30879
|
}
|
|
30379
30880
|
async function isUserMemoryProviderInstalled(profileName, provider) {
|
|
30380
|
-
const providerDir =
|
|
30881
|
+
const providerDir = path28.join(
|
|
30381
30882
|
resolveHermesProfileDir(profileName),
|
|
30382
30883
|
"plugins",
|
|
30383
30884
|
normalizeCustomProviderId(provider)
|
|
@@ -30385,7 +30886,7 @@ async function isUserMemoryProviderInstalled(profileName, provider) {
|
|
|
30385
30886
|
return isMemoryProviderPluginDir(providerDir);
|
|
30386
30887
|
}
|
|
30387
30888
|
async function isMemoryProviderPluginDir(providerDir) {
|
|
30388
|
-
const source = await readFile19(
|
|
30889
|
+
const source = await readFile19(path28.join(providerDir, "__init__.py"), "utf8").catch(
|
|
30389
30890
|
(error) => {
|
|
30390
30891
|
if (isNodeError21(error, "ENOENT")) {
|
|
30391
30892
|
return "";
|
|
@@ -30397,7 +30898,7 @@ async function isMemoryProviderPluginDir(providerDir) {
|
|
|
30397
30898
|
return sample.includes("register_memory_provider") || sample.includes("MemoryProvider");
|
|
30398
30899
|
}
|
|
30399
30900
|
async function readPluginMetadata(providerDir) {
|
|
30400
|
-
const raw = await readFile19(
|
|
30901
|
+
const raw = await readFile19(path28.join(providerDir, "plugin.yaml"), "utf8").catch(
|
|
30401
30902
|
(error) => {
|
|
30402
30903
|
if (isNodeError21(error, "ENOENT")) {
|
|
30403
30904
|
return "";
|
|
@@ -30409,10 +30910,10 @@ async function readPluginMetadata(providerDir) {
|
|
|
30409
30910
|
}
|
|
30410
30911
|
async function resolveByteRoverCli() {
|
|
30411
30912
|
const candidates = [
|
|
30412
|
-
...(process.env.PATH ?? "").split(
|
|
30413
|
-
|
|
30913
|
+
...(process.env.PATH ?? "").split(path28.delimiter).filter(Boolean).map((dir) => path28.join(dir, "brv")),
|
|
30914
|
+
path28.join(process.env.HOME ?? "", ".brv-cli", "bin", "brv"),
|
|
30414
30915
|
"/usr/local/bin/brv",
|
|
30415
|
-
|
|
30916
|
+
path28.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
|
|
30416
30917
|
].filter(Boolean);
|
|
30417
30918
|
for (const candidate of candidates) {
|
|
30418
30919
|
const found = await access3(candidate).then(() => true).catch(() => false);
|
|
@@ -30473,7 +30974,7 @@ async function patchHermesMemoryEnv(profileName, patch) {
|
|
|
30473
30974
|
if (entries.length === 0) {
|
|
30474
30975
|
return;
|
|
30475
30976
|
}
|
|
30476
|
-
const envPath =
|
|
30977
|
+
const envPath = path28.join(resolveHermesProfileDir(profileName), ".env");
|
|
30477
30978
|
const existingRaw = await readFile19(envPath, "utf8").catch((error) => {
|
|
30478
30979
|
if (isNodeError21(error, "ENOENT")) {
|
|
30479
30980
|
return "";
|
|
@@ -30644,7 +31145,7 @@ async function readActiveMemoryProvider(profileName) {
|
|
|
30644
31145
|
return provider;
|
|
30645
31146
|
}
|
|
30646
31147
|
async function patchJsonProviderConfig(profileName, relativePath, patch) {
|
|
30647
|
-
const configPath =
|
|
31148
|
+
const configPath = path28.join(
|
|
30648
31149
|
resolveHermesProfileDir(profileName),
|
|
30649
31150
|
relativePath
|
|
30650
31151
|
);
|
|
@@ -30673,7 +31174,7 @@ async function readJsonObject(filePath) {
|
|
|
30673
31174
|
} catch {
|
|
30674
31175
|
throw new HermesMemoryError(
|
|
30675
31176
|
"memory_provider_config_invalid",
|
|
30676
|
-
`${
|
|
31177
|
+
`${path28.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
|
|
30677
31178
|
);
|
|
30678
31179
|
}
|
|
30679
31180
|
}
|
|
@@ -31297,7 +31798,7 @@ function toMemoryHttpError(error) {
|
|
|
31297
31798
|
|
|
31298
31799
|
// src/hermes/skills.ts
|
|
31299
31800
|
import { readFile as readFile20, readdir as readdir11 } from "fs/promises";
|
|
31300
|
-
import
|
|
31801
|
+
import path29 from "path";
|
|
31301
31802
|
import YAML7 from "yaml";
|
|
31302
31803
|
var HermesSkillNotFoundError = class extends Error {
|
|
31303
31804
|
constructor(skillName) {
|
|
@@ -31311,7 +31812,7 @@ var EXCLUDED_SKILL_DIRS = /* @__PURE__ */ new Set([".git", ".github", ".hub"]);
|
|
|
31311
31812
|
async function listHermesProfileSkills(profileName, paths = resolveRuntimePaths()) {
|
|
31312
31813
|
const profile = await readExistingProfile(profileName, paths);
|
|
31313
31814
|
const profileDir = resolveHermesProfileDir(profile.name);
|
|
31314
|
-
const skillsRoot =
|
|
31815
|
+
const skillsRoot = path29.join(profileDir, "skills");
|
|
31315
31816
|
const [skillFiles, disabled, provenance] = await Promise.all([
|
|
31316
31817
|
findSkillFiles(skillsRoot),
|
|
31317
31818
|
readDisabledSkillNames(resolveHermesConfigPath(profile.name)),
|
|
@@ -31402,7 +31903,7 @@ async function collectSkillFiles(directory, results) {
|
|
|
31402
31903
|
if (EXCLUDED_SKILL_DIRS.has(entry.name)) {
|
|
31403
31904
|
continue;
|
|
31404
31905
|
}
|
|
31405
|
-
const entryPath =
|
|
31906
|
+
const entryPath = path29.join(directory, entry.name);
|
|
31406
31907
|
if (entry.isDirectory()) {
|
|
31407
31908
|
await collectSkillFiles(entryPath, results);
|
|
31408
31909
|
continue;
|
|
@@ -31424,10 +31925,10 @@ async function readSkillMetadata(input) {
|
|
|
31424
31925
|
if (raw === null) {
|
|
31425
31926
|
return null;
|
|
31426
31927
|
}
|
|
31427
|
-
const skillDir =
|
|
31928
|
+
const skillDir = path29.dirname(input.skillFile);
|
|
31428
31929
|
const { frontmatter, body } = parseSkillDocument(raw.slice(0, 4e3));
|
|
31429
31930
|
const name = normalizeSkillName(
|
|
31430
|
-
readString21(frontmatter.name) ??
|
|
31931
|
+
readString21(frontmatter.name) ?? path29.basename(skillDir)
|
|
31431
31932
|
);
|
|
31432
31933
|
if (!name) {
|
|
31433
31934
|
return null;
|
|
@@ -31446,7 +31947,7 @@ async function readSkillMetadata(input) {
|
|
|
31446
31947
|
enabled: !input.disabled.has(name),
|
|
31447
31948
|
source: provenance.source,
|
|
31448
31949
|
trust: provenance.trust,
|
|
31449
|
-
relativePath:
|
|
31950
|
+
relativePath: path29.relative(input.skillsRoot, skillDir)
|
|
31450
31951
|
};
|
|
31451
31952
|
}
|
|
31452
31953
|
function parseSkillDocument(raw) {
|
|
@@ -31467,8 +31968,8 @@ function parseSkillDocument(raw) {
|
|
|
31467
31968
|
}
|
|
31468
31969
|
}
|
|
31469
31970
|
function categoryFromPath(skillsRoot, skillFile) {
|
|
31470
|
-
const relative =
|
|
31471
|
-
const parts = relative.split(
|
|
31971
|
+
const relative = path29.relative(skillsRoot, skillFile);
|
|
31972
|
+
const parts = relative.split(path29.sep).filter(Boolean);
|
|
31472
31973
|
return parts.length >= 3 ? parts[0] : null;
|
|
31473
31974
|
}
|
|
31474
31975
|
function firstBodyDescription(body) {
|
|
@@ -31515,7 +32016,7 @@ async function readSkillProvenance(root) {
|
|
|
31515
32016
|
return provenance;
|
|
31516
32017
|
}
|
|
31517
32018
|
async function readBundledSkillNames(root) {
|
|
31518
|
-
const raw = await readFile20(
|
|
32019
|
+
const raw = await readFile20(path29.join(root, ".bundled_manifest"), "utf8").catch(
|
|
31519
32020
|
(error) => {
|
|
31520
32021
|
if (isNodeError22(error, "ENOENT")) {
|
|
31521
32022
|
return "";
|
|
@@ -31538,7 +32039,7 @@ async function readBundledSkillNames(root) {
|
|
|
31538
32039
|
return names;
|
|
31539
32040
|
}
|
|
31540
32041
|
async function readHubInstalledSkills(root) {
|
|
31541
|
-
const raw = await readFile20(
|
|
32042
|
+
const raw = await readFile20(path29.join(root, ".hub", "lock.json"), "utf8").catch(
|
|
31542
32043
|
(error) => {
|
|
31543
32044
|
if (isNodeError22(error, "ENOENT")) {
|
|
31544
32045
|
return "";
|
|
@@ -32201,7 +32702,7 @@ function readModelList(payload) {
|
|
|
32201
32702
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
32202
32703
|
import { spawn as spawn4 } from "child_process";
|
|
32203
32704
|
import { mkdir as mkdir13, readFile as readFile21, rm as rm8 } from "fs/promises";
|
|
32204
|
-
import
|
|
32705
|
+
import path30 from "path";
|
|
32205
32706
|
var SERVER_HERMES_RELEASES_LATEST_PATH = "/api/v1/hermes-agent/releases/latest";
|
|
32206
32707
|
var RELEASE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
32207
32708
|
var RELEASE_FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -32479,13 +32980,13 @@ async function readUpdateLogLines(paths) {
|
|
|
32479
32980
|
);
|
|
32480
32981
|
}
|
|
32481
32982
|
function releaseCachePath(paths) {
|
|
32482
|
-
return
|
|
32983
|
+
return path30.join(paths.indexesDir, "hermes-release-check.json");
|
|
32483
32984
|
}
|
|
32484
32985
|
function updateStatePath(paths) {
|
|
32485
|
-
return
|
|
32986
|
+
return path30.join(paths.runDir, "hermes-update-state.json");
|
|
32486
32987
|
}
|
|
32487
32988
|
function updateLogPath(paths) {
|
|
32488
|
-
return
|
|
32989
|
+
return path30.join(paths.logsDir, UPDATE_LOG_FILE);
|
|
32489
32990
|
}
|
|
32490
32991
|
async function clearUpdateLogFiles(paths) {
|
|
32491
32992
|
const primary = updateLogPath(paths);
|
|
@@ -32586,12 +33087,12 @@ function readString22(payload, key) {
|
|
|
32586
33087
|
import { spawn as spawn6 } from "child_process";
|
|
32587
33088
|
import { EventEmitter as EventEmitter4 } from "events";
|
|
32588
33089
|
import { mkdir as mkdir16, readFile as readFile23, rm as rm11 } from "fs/promises";
|
|
32589
|
-
import
|
|
33090
|
+
import path32 from "path";
|
|
32590
33091
|
|
|
32591
33092
|
// src/daemon/process.ts
|
|
32592
33093
|
import { spawn as spawn5 } from "child_process";
|
|
32593
33094
|
import { mkdir as mkdir15, readFile as readFile22, rm as rm10, writeFile as writeFile4 } from "fs/promises";
|
|
32594
|
-
import
|
|
33095
|
+
import path31 from "path";
|
|
32595
33096
|
|
|
32596
33097
|
// src/daemon/service.ts
|
|
32597
33098
|
import { createServer } from "http";
|
|
@@ -34538,7 +35039,7 @@ async function runDaemonSupervisor(paths = resolveRuntimePaths()) {
|
|
|
34538
35039
|
await mkdir15(paths.logsDir, { recursive: true, mode: 448 });
|
|
34539
35040
|
const log = createRotatingTextLogWriter({
|
|
34540
35041
|
paths,
|
|
34541
|
-
fileName:
|
|
35042
|
+
fileName: path31.basename(daemonLogFile(paths))
|
|
34542
35043
|
});
|
|
34543
35044
|
const scriptPath = currentCliScriptPath();
|
|
34544
35045
|
const write = (chunk) => {
|
|
@@ -34836,7 +35337,7 @@ function terminateChild(child, previousForceKillTimer) {
|
|
|
34836
35337
|
}
|
|
34837
35338
|
}
|
|
34838
35339
|
function supervisorStopIntentPath(paths) {
|
|
34839
|
-
return
|
|
35340
|
+
return path31.join(paths.runDir, "supervisor-stop-intent.json");
|
|
34840
35341
|
}
|
|
34841
35342
|
async function writeSupervisorStopIntent(paths, pid) {
|
|
34842
35343
|
await mkdir15(paths.runDir, { recursive: true, mode: 448 });
|
|
@@ -35399,7 +35900,7 @@ async function buildOfficialInstallCommand(options, targetVersion) {
|
|
|
35399
35900
|
};
|
|
35400
35901
|
}
|
|
35401
35902
|
function buildUnixInstallCommand(installerUrl) {
|
|
35402
|
-
const nodeBinDir =
|
|
35903
|
+
const nodeBinDir = path32.dirname(process.execPath);
|
|
35403
35904
|
const fetchScript = [
|
|
35404
35905
|
quoteShellToken(process.execPath),
|
|
35405
35906
|
"--input-type=module",
|
|
@@ -35669,10 +36170,10 @@ async function readUpdateLogLines2(paths) {
|
|
|
35669
36170
|
);
|
|
35670
36171
|
}
|
|
35671
36172
|
function updateStatePath2(paths) {
|
|
35672
|
-
return
|
|
36173
|
+
return path32.join(paths.runDir, "link-update-state.json");
|
|
35673
36174
|
}
|
|
35674
36175
|
function updateLogPath2(paths) {
|
|
35675
|
-
return
|
|
36176
|
+
return path32.join(paths.logsDir, UPDATE_LOG_FILE2);
|
|
35676
36177
|
}
|
|
35677
36178
|
async function clearUpdateLogFiles2(paths) {
|
|
35678
36179
|
const primary = updateLogPath2(paths);
|
|
@@ -35756,7 +36257,7 @@ function readString23(payload, key) {
|
|
|
35756
36257
|
}
|
|
35757
36258
|
|
|
35758
36259
|
// src/pairing/pairing.ts
|
|
35759
|
-
import
|
|
36260
|
+
import path33 from "path";
|
|
35760
36261
|
import { rm as rm12 } from "fs/promises";
|
|
35761
36262
|
|
|
35762
36263
|
// src/relay/bootstrap.ts
|
|
@@ -36096,10 +36597,10 @@ async function loadRequiredIdentity2(paths) {
|
|
|
36096
36597
|
}
|
|
36097
36598
|
return identity;
|
|
36098
36599
|
}
|
|
36099
|
-
async function postServerJson(serverBaseUrl,
|
|
36600
|
+
async function postServerJson(serverBaseUrl, path34, body, options) {
|
|
36100
36601
|
let response;
|
|
36101
36602
|
try {
|
|
36102
|
-
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${
|
|
36603
|
+
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path34}`, {
|
|
36103
36604
|
method: "POST",
|
|
36104
36605
|
headers: {
|
|
36105
36606
|
accept: "application/json",
|
|
@@ -36147,10 +36648,10 @@ function pairingErrorSnapshot(stage, error) {
|
|
|
36147
36648
|
occurred_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
36148
36649
|
};
|
|
36149
36650
|
}
|
|
36150
|
-
async function patchServerJson(serverBaseUrl,
|
|
36651
|
+
async function patchServerJson(serverBaseUrl, path34, token, body, options) {
|
|
36151
36652
|
let response;
|
|
36152
36653
|
try {
|
|
36153
|
-
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${
|
|
36654
|
+
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path34}`, {
|
|
36154
36655
|
method: "PATCH",
|
|
36155
36656
|
headers: {
|
|
36156
36657
|
accept: "application/json",
|
|
@@ -36198,10 +36699,10 @@ function createPairingNetworkError(input) {
|
|
|
36198
36699
|
);
|
|
36199
36700
|
}
|
|
36200
36701
|
function pairingClaimPath(sessionId, paths) {
|
|
36201
|
-
return
|
|
36702
|
+
return path33.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
|
|
36202
36703
|
}
|
|
36203
36704
|
function pairingSessionPath(sessionId, paths) {
|
|
36204
|
-
return
|
|
36705
|
+
return path33.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
|
|
36205
36706
|
}
|
|
36206
36707
|
function qrPreferredUrls(routes) {
|
|
36207
36708
|
return routes.preferredUrls.filter((url) => !url.includes("/api/v1/relay/links/")).slice(0, 1);
|
|
@@ -36272,6 +36773,7 @@ function registerSystemRoutes(router, options) {
|
|
|
36272
36773
|
conversation_queue_limit: MAX_CONVERSATION_QUEUED_RUNS,
|
|
36273
36774
|
responses_interrupted_previous_response: true,
|
|
36274
36775
|
conversation_rename: true,
|
|
36776
|
+
conversation_workspaces: true,
|
|
36275
36777
|
blobs: true,
|
|
36276
36778
|
devices: true,
|
|
36277
36779
|
device_delete: true,
|
|
@@ -36758,6 +37260,51 @@ function readDeviceModelHeader(ctx) {
|
|
|
36758
37260
|
return value ? value.slice(0, 128) : null;
|
|
36759
37261
|
}
|
|
36760
37262
|
|
|
37263
|
+
// src/http/routes/workspaces.ts
|
|
37264
|
+
function registerWorkspaceRoutes(router, options) {
|
|
37265
|
+
const { paths, conversations } = options;
|
|
37266
|
+
router.get("/api/v1/workspaces", async (ctx) => {
|
|
37267
|
+
await authenticateRequest(ctx, paths);
|
|
37268
|
+
ctx.set("cache-control", "no-store");
|
|
37269
|
+
ctx.body = {
|
|
37270
|
+
ok: true,
|
|
37271
|
+
workspaces: await conversations.listWorkspaces()
|
|
37272
|
+
};
|
|
37273
|
+
});
|
|
37274
|
+
router.post("/api/v1/workspaces", async (ctx) => {
|
|
37275
|
+
await authenticateRequest(ctx, paths);
|
|
37276
|
+
const body = await readJsonBody(ctx.req);
|
|
37277
|
+
ctx.status = 201;
|
|
37278
|
+
ctx.body = {
|
|
37279
|
+
ok: true,
|
|
37280
|
+
workspace: await conversations.createWorkspace({
|
|
37281
|
+
name: readString19(body, "name") ?? "",
|
|
37282
|
+
icon: readString19(body, "icon") ?? void 0
|
|
37283
|
+
})
|
|
37284
|
+
};
|
|
37285
|
+
});
|
|
37286
|
+
router.patch("/api/v1/workspaces/:workspaceId", async (ctx) => {
|
|
37287
|
+
await authenticateRequest(ctx, paths);
|
|
37288
|
+
const body = await readJsonBody(ctx.req);
|
|
37289
|
+
ctx.body = {
|
|
37290
|
+
ok: true,
|
|
37291
|
+
workspace: await conversations.renameWorkspace(ctx.params.workspaceId, {
|
|
37292
|
+
name: readString19(body, "name") ?? "",
|
|
37293
|
+
icon: readString19(body, "icon") ?? void 0
|
|
37294
|
+
})
|
|
37295
|
+
};
|
|
37296
|
+
});
|
|
37297
|
+
router.delete("/api/v1/workspaces/:workspaceId", async (ctx) => {
|
|
37298
|
+
await authenticateRequest(ctx, paths);
|
|
37299
|
+
const result = await conversations.deleteWorkspace(ctx.params.workspaceId);
|
|
37300
|
+
ctx.body = {
|
|
37301
|
+
ok: true,
|
|
37302
|
+
workspace: result.workspace,
|
|
37303
|
+
moved_conversation_count: result.movedConversationCount
|
|
37304
|
+
};
|
|
37305
|
+
});
|
|
37306
|
+
}
|
|
37307
|
+
|
|
36761
37308
|
// src/http/routes/hermes-updates.ts
|
|
36762
37309
|
function registerHermesUpdateRoutes(router, options) {
|
|
36763
37310
|
const { paths, logger } = options;
|
|
@@ -37447,6 +37994,7 @@ async function createApp(options = {}) {
|
|
|
37447
37994
|
conversations,
|
|
37448
37995
|
syncCronDeliveries
|
|
37449
37996
|
});
|
|
37997
|
+
registerWorkspaceRoutes(router, { paths, conversations });
|
|
37450
37998
|
registerConversationRoutes(router, { paths, logger, conversations });
|
|
37451
37999
|
registerRunRoutes(router, { paths, logger, conversations });
|
|
37452
38000
|
registerProfileRoutes(router, { paths, logger, conversations });
|