@hermespilot/link 0.7.7-beta.0 → 0.7.8-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -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.
|
|
6639
|
+
var LINK_VERSION = "0.7.8-beta.0";
|
|
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 "";
|
|
@@ -8571,6 +8636,7 @@ import { setTimeout as delay3 } from "timers/promises";
|
|
|
8571
8636
|
import WebSocket from "ws";
|
|
8572
8637
|
var CONNECT_TIMEOUT_MS = 15e3;
|
|
8573
8638
|
var REQUEST_TIMEOUT_MS = 12e4;
|
|
8639
|
+
var USAGE_REQUEST_TIMEOUT_MS = 1e4;
|
|
8574
8640
|
var PROMPT_SUBMIT_TIMEOUT_MS = 0;
|
|
8575
8641
|
var READY_TIMEOUT_MS = 1e4;
|
|
8576
8642
|
var BACKEND_START_TIMEOUT_MS = 45e3;
|
|
@@ -8598,12 +8664,23 @@ async function streamTuiGatewayRun(input) {
|
|
|
8598
8664
|
profileName
|
|
8599
8665
|
});
|
|
8600
8666
|
await client.waitForSessionDrain(started.runtimeSessionId, input.signal);
|
|
8667
|
+
const usageBaseline = await client.readSessionUsage(started.runtimeSessionId).catch((error) => {
|
|
8668
|
+
void input.logger?.debug("tui_gateway_usage_baseline_read_failed", {
|
|
8669
|
+
profile: profileName,
|
|
8670
|
+
session_id: started.runtimeSessionId,
|
|
8671
|
+
error: error instanceof Error ? error.message : String(error)
|
|
8672
|
+
});
|
|
8673
|
+
return started.resumed ? null : {};
|
|
8674
|
+
});
|
|
8601
8675
|
const events = client.submitPrompt({
|
|
8602
8676
|
sessionId: started.runtimeSessionId,
|
|
8603
8677
|
text: buildTuiGatewayPromptText(input),
|
|
8604
8678
|
signal: input.signal
|
|
8605
8679
|
});
|
|
8606
|
-
return {
|
|
8680
|
+
return {
|
|
8681
|
+
started,
|
|
8682
|
+
events: normalizeTuiGatewayRunUsageDeltas(events, usageBaseline)
|
|
8683
|
+
};
|
|
8607
8684
|
}
|
|
8608
8685
|
function buildTuiGatewayPromptText(input) {
|
|
8609
8686
|
return input.input;
|
|
@@ -8641,6 +8718,61 @@ async function respondTuiGatewayApproval(input) {
|
|
|
8641
8718
|
);
|
|
8642
8719
|
return { resolved: readNumber2(result, "resolved") ?? 0 };
|
|
8643
8720
|
}
|
|
8721
|
+
async function setTuiGatewaySessionArchived(input) {
|
|
8722
|
+
const sessionId = input.sessionId.trim();
|
|
8723
|
+
if (!sessionId) {
|
|
8724
|
+
throw new LinkHttpError(
|
|
8725
|
+
400,
|
|
8726
|
+
"hermes_session_id_required",
|
|
8727
|
+
"Hermes session id is required"
|
|
8728
|
+
);
|
|
8729
|
+
}
|
|
8730
|
+
if (sessionId.startsWith("hp_")) {
|
|
8731
|
+
return;
|
|
8732
|
+
}
|
|
8733
|
+
const profileName = normalizeProfileName2(input.profileName);
|
|
8734
|
+
const backend = await ensureTuiGatewayBackend({
|
|
8735
|
+
profileName,
|
|
8736
|
+
paths: input.paths,
|
|
8737
|
+
logger: input.logger
|
|
8738
|
+
});
|
|
8739
|
+
const response = await fetch(
|
|
8740
|
+
`${backend.baseUrl}/api/sessions/${encodeURIComponent(sessionId)}`,
|
|
8741
|
+
{
|
|
8742
|
+
method: "PATCH",
|
|
8743
|
+
headers: {
|
|
8744
|
+
"content-type": "application/json",
|
|
8745
|
+
"X-Hermes-Session-Token": backend.token
|
|
8746
|
+
},
|
|
8747
|
+
body: JSON.stringify({ archived: input.archived })
|
|
8748
|
+
}
|
|
8749
|
+
).catch((error) => {
|
|
8750
|
+
throw new LinkHttpError(
|
|
8751
|
+
502,
|
|
8752
|
+
"hermes_session_archive_failed",
|
|
8753
|
+
error instanceof Error ? error.message : "Hermes session archive request failed"
|
|
8754
|
+
);
|
|
8755
|
+
});
|
|
8756
|
+
touchBackend(profileName);
|
|
8757
|
+
if (response.ok) {
|
|
8758
|
+
return;
|
|
8759
|
+
}
|
|
8760
|
+
const detail = await response.text().catch(() => "");
|
|
8761
|
+
if (response.status === 404) {
|
|
8762
|
+
void input.logger?.debug("hermes_session_archive_missing", {
|
|
8763
|
+
profile: profileName,
|
|
8764
|
+
session_id: sessionId,
|
|
8765
|
+
archived: input.archived,
|
|
8766
|
+
detail
|
|
8767
|
+
});
|
|
8768
|
+
return;
|
|
8769
|
+
}
|
|
8770
|
+
throw new LinkHttpError(
|
|
8771
|
+
502,
|
|
8772
|
+
"hermes_session_archive_failed",
|
|
8773
|
+
`Hermes session archive request failed with HTTP ${response.status}${detail ? `: ${detail}` : ""}`
|
|
8774
|
+
);
|
|
8775
|
+
}
|
|
8644
8776
|
async function readTuiGatewayStatus(input = {}) {
|
|
8645
8777
|
const profile = normalizeProfileName2(input.profileName);
|
|
8646
8778
|
const client = clients.get(profile);
|
|
@@ -9060,6 +9192,13 @@ var TuiGatewayClient = class {
|
|
|
9060
9192
|
queue.closed.finally(cleanup).catch(() => void 0);
|
|
9061
9193
|
return queue;
|
|
9062
9194
|
}
|
|
9195
|
+
async readSessionUsage(sessionId) {
|
|
9196
|
+
return await this.request(
|
|
9197
|
+
"session.usage",
|
|
9198
|
+
{ session_id: sessionId },
|
|
9199
|
+
USAGE_REQUEST_TIMEOUT_MS
|
|
9200
|
+
);
|
|
9201
|
+
}
|
|
9063
9202
|
async interruptSession(sessionId) {
|
|
9064
9203
|
const queue = new GatewayEventQueue(sessionId);
|
|
9065
9204
|
this.eventQueues.add(queue);
|
|
@@ -9357,6 +9496,94 @@ var GatewayEventQueue = class {
|
|
|
9357
9496
|
});
|
|
9358
9497
|
}
|
|
9359
9498
|
};
|
|
9499
|
+
async function* normalizeTuiGatewayRunUsageDeltas(events, baseline) {
|
|
9500
|
+
for await (const event of events) {
|
|
9501
|
+
yield normalizeTuiGatewayRunUsageDelta(event, baseline);
|
|
9502
|
+
}
|
|
9503
|
+
}
|
|
9504
|
+
function normalizeTuiGatewayRunUsageDelta(event, baseline) {
|
|
9505
|
+
if (baseline === null || event.payloadType !== "run.completed" && event.payloadType !== "run.failed" && event.payloadType !== "run.cancelled") {
|
|
9506
|
+
return event;
|
|
9507
|
+
}
|
|
9508
|
+
const usage = normalizeTuiGatewayUsageDelta(event.payload.usage, baseline);
|
|
9509
|
+
if (!usage) {
|
|
9510
|
+
return event;
|
|
9511
|
+
}
|
|
9512
|
+
return {
|
|
9513
|
+
...event,
|
|
9514
|
+
payload: {
|
|
9515
|
+
...event.payload,
|
|
9516
|
+
usage
|
|
9517
|
+
}
|
|
9518
|
+
};
|
|
9519
|
+
}
|
|
9520
|
+
function normalizeTuiGatewayUsageDelta(usageValue, baseline) {
|
|
9521
|
+
const usage = toRecord3(usageValue);
|
|
9522
|
+
if (Object.keys(usage).length === 0) {
|
|
9523
|
+
return null;
|
|
9524
|
+
}
|
|
9525
|
+
const inputTokens = usageDelta(usage, baseline, [
|
|
9526
|
+
"input",
|
|
9527
|
+
"input_tokens",
|
|
9528
|
+
"prompt",
|
|
9529
|
+
"prompt_tokens"
|
|
9530
|
+
]);
|
|
9531
|
+
const outputTokens = usageDelta(usage, baseline, [
|
|
9532
|
+
"output",
|
|
9533
|
+
"output_tokens",
|
|
9534
|
+
"completion",
|
|
9535
|
+
"completion_tokens"
|
|
9536
|
+
]);
|
|
9537
|
+
const totalTokens = totalUsageDelta(
|
|
9538
|
+
usage,
|
|
9539
|
+
baseline,
|
|
9540
|
+
inputTokens,
|
|
9541
|
+
outputTokens
|
|
9542
|
+
);
|
|
9543
|
+
const contextTokens = readNumber2(usage, "context_used") ?? readNumber2(usage, "context_tokens") ?? readNumber2(usage, "current_context_tokens") ?? readNumber2(usage, "last_prompt_tokens");
|
|
9544
|
+
const contextWindow = readNumber2(usage, "context_max") ?? readNumber2(usage, "context_window") ?? readNumber2(usage, "context_length");
|
|
9545
|
+
const usagePercent = readNumber2(usage, "context_percent") ?? readNumber2(usage, "usage_percent");
|
|
9546
|
+
if (inputTokens === void 0 && outputTokens === void 0 && totalTokens === void 0 && contextTokens === void 0) {
|
|
9547
|
+
return null;
|
|
9548
|
+
}
|
|
9549
|
+
return {
|
|
9550
|
+
...usage,
|
|
9551
|
+
...inputTokens !== void 0 ? { input_tokens: inputTokens } : {},
|
|
9552
|
+
...outputTokens !== void 0 ? { output_tokens: outputTokens } : {},
|
|
9553
|
+
...totalTokens !== void 0 ? { total_tokens: totalTokens } : {},
|
|
9554
|
+
...contextTokens !== void 0 ? { context_tokens: contextTokens } : {},
|
|
9555
|
+
...contextWindow !== void 0 ? { context_window: contextWindow } : {},
|
|
9556
|
+
...usagePercent !== void 0 ? { usage_percent: usagePercent } : {}
|
|
9557
|
+
};
|
|
9558
|
+
}
|
|
9559
|
+
function usageDelta(current, baseline, keys) {
|
|
9560
|
+
const currentValue = readFirstNumber(current, keys);
|
|
9561
|
+
if (currentValue === void 0) {
|
|
9562
|
+
return void 0;
|
|
9563
|
+
}
|
|
9564
|
+
const baselineValue = readFirstNumber(baseline, keys) ?? 0;
|
|
9565
|
+
return Math.max(0, currentValue - baselineValue);
|
|
9566
|
+
}
|
|
9567
|
+
function totalUsageDelta(current, baseline, inputTokens, outputTokens) {
|
|
9568
|
+
const currentTotal = readFirstNumber(current, ["total", "total_tokens"]);
|
|
9569
|
+
const baselineTotal = readFirstNumber(baseline, ["total", "total_tokens"]);
|
|
9570
|
+
if (currentTotal !== void 0 && (baselineTotal !== void 0 || Object.keys(baseline).length === 0)) {
|
|
9571
|
+
return Math.max(0, currentTotal - (baselineTotal ?? 0));
|
|
9572
|
+
}
|
|
9573
|
+
if (inputTokens !== void 0 || outputTokens !== void 0) {
|
|
9574
|
+
return (inputTokens ?? 0) + (outputTokens ?? 0);
|
|
9575
|
+
}
|
|
9576
|
+
return void 0;
|
|
9577
|
+
}
|
|
9578
|
+
function readFirstNumber(payload, keys) {
|
|
9579
|
+
for (const key of keys) {
|
|
9580
|
+
const value = readNumber2(payload, key);
|
|
9581
|
+
if (value !== void 0) {
|
|
9582
|
+
return value;
|
|
9583
|
+
}
|
|
9584
|
+
}
|
|
9585
|
+
return void 0;
|
|
9586
|
+
}
|
|
9360
9587
|
function normalizeGatewayEvent(event) {
|
|
9361
9588
|
const payload = toRecord3(event.payload);
|
|
9362
9589
|
const base = {
|
|
@@ -9642,7 +9869,14 @@ function readString5(payload, key) {
|
|
|
9642
9869
|
}
|
|
9643
9870
|
function readNumber2(payload, key) {
|
|
9644
9871
|
const value = payload[key];
|
|
9645
|
-
|
|
9872
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
9873
|
+
return Math.max(0, Math.floor(value));
|
|
9874
|
+
}
|
|
9875
|
+
if (typeof value === "string" && value.trim()) {
|
|
9876
|
+
const parsed = Number.parseInt(value.replaceAll(",", ""), 10);
|
|
9877
|
+
return Number.isFinite(parsed) ? Math.max(0, parsed) : void 0;
|
|
9878
|
+
}
|
|
9879
|
+
return void 0;
|
|
9646
9880
|
}
|
|
9647
9881
|
function readText(payload, key) {
|
|
9648
9882
|
const value = payload[key];
|
|
@@ -9850,6 +10084,7 @@ function toStatsIndexRecord(manifest, stats = manifest.stats ?? buildConversatio
|
|
|
9850
10084
|
title: manifest.title,
|
|
9851
10085
|
status: manifest.status,
|
|
9852
10086
|
hermesSessionId: manifest.hermes_session_id,
|
|
10087
|
+
workspaceId: manifest.workspace_id ?? null,
|
|
9853
10088
|
profileUid: stats.profile_uid ?? null,
|
|
9854
10089
|
profileNameSnapshot: stats.profile_name_snapshot ?? stats.profile ?? null,
|
|
9855
10090
|
profile: stats.profile ?? stats.profile_name_snapshot ?? null,
|
|
@@ -9875,6 +10110,25 @@ function latestUsageRun(snapshot) {
|
|
|
9875
10110
|
}
|
|
9876
10111
|
return void 0;
|
|
9877
10112
|
}
|
|
10113
|
+
function latestContextUsageRun(snapshot) {
|
|
10114
|
+
let latestEstimatedRun;
|
|
10115
|
+
let latestUsageRun2;
|
|
10116
|
+
for (const run of [...snapshot.runs].reverse()) {
|
|
10117
|
+
if (!isAgentRun(run) || !run.usage) {
|
|
10118
|
+
continue;
|
|
10119
|
+
}
|
|
10120
|
+
latestUsageRun2 ??= run;
|
|
10121
|
+
if (run.usage.context_tokens === void 0) {
|
|
10122
|
+
continue;
|
|
10123
|
+
}
|
|
10124
|
+
if (run.usage.context_source === "estimated") {
|
|
10125
|
+
latestEstimatedRun ??= run;
|
|
10126
|
+
continue;
|
|
10127
|
+
}
|
|
10128
|
+
return run;
|
|
10129
|
+
}
|
|
10130
|
+
return latestEstimatedRun ?? latestUsageRun2;
|
|
10131
|
+
}
|
|
9878
10132
|
function latestRuntimeRun(snapshot) {
|
|
9879
10133
|
for (const run of [...snapshot.runs].reverse()) {
|
|
9880
10134
|
if (isAgentRun(run) && (run.model || run.profile_uid || run.profile_name_snapshot || run.profile || run.usage)) {
|
|
@@ -9999,8 +10253,204 @@ function firstRecord(...values) {
|
|
|
9999
10253
|
return {};
|
|
10000
10254
|
}
|
|
10001
10255
|
|
|
10002
|
-
// src/conversations/
|
|
10256
|
+
// src/conversations/workspaces.ts
|
|
10003
10257
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
10258
|
+
var MAX_WORKSPACE_NAME_LENGTH = 40;
|
|
10259
|
+
var DEFAULT_WORKSPACE_ICON = "grid";
|
|
10260
|
+
var WORKSPACE_ICON_KEYS = /* @__PURE__ */ new Set([
|
|
10261
|
+
"grid",
|
|
10262
|
+
"folder",
|
|
10263
|
+
"briefcase",
|
|
10264
|
+
"code",
|
|
10265
|
+
"cube",
|
|
10266
|
+
"rocket",
|
|
10267
|
+
"albums",
|
|
10268
|
+
"bookmark",
|
|
10269
|
+
"heart",
|
|
10270
|
+
"library",
|
|
10271
|
+
"planet",
|
|
10272
|
+
"school"
|
|
10273
|
+
]);
|
|
10274
|
+
async function listWorkspaces(paths) {
|
|
10275
|
+
const store = await readWorkspaceStore(paths);
|
|
10276
|
+
return sortWorkspaces(store.workspaces);
|
|
10277
|
+
}
|
|
10278
|
+
async function createWorkspace(paths, input) {
|
|
10279
|
+
const name = normalizeWorkspaceName(input.name);
|
|
10280
|
+
const icon = normalizeWorkspaceIcon(input.icon);
|
|
10281
|
+
let created;
|
|
10282
|
+
await updateWorkspaceStore(paths, (store) => {
|
|
10283
|
+
assertUniqueWorkspaceName(store.workspaces, name);
|
|
10284
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10285
|
+
created = {
|
|
10286
|
+
id: `ws_${randomUUID4().replaceAll("-", "")}`,
|
|
10287
|
+
name,
|
|
10288
|
+
icon,
|
|
10289
|
+
created_at: now,
|
|
10290
|
+
updated_at: now
|
|
10291
|
+
};
|
|
10292
|
+
return {
|
|
10293
|
+
...store,
|
|
10294
|
+
workspaces: [...store.workspaces, created]
|
|
10295
|
+
};
|
|
10296
|
+
});
|
|
10297
|
+
return created;
|
|
10298
|
+
}
|
|
10299
|
+
async function renameWorkspace(paths, workspaceId, input) {
|
|
10300
|
+
const id = normalizeWorkspaceId(workspaceId);
|
|
10301
|
+
const name = normalizeWorkspaceName(input.name);
|
|
10302
|
+
const icon = normalizeWorkspaceIcon(input.icon);
|
|
10303
|
+
let renamed;
|
|
10304
|
+
await updateWorkspaceStore(paths, (store) => {
|
|
10305
|
+
assertUniqueWorkspaceName(store.workspaces, name, id);
|
|
10306
|
+
const index = store.workspaces.findIndex((workspace) => workspace.id === id);
|
|
10307
|
+
if (index < 0) {
|
|
10308
|
+
throw workspaceNotFound();
|
|
10309
|
+
}
|
|
10310
|
+
const next = [...store.workspaces];
|
|
10311
|
+
renamed = {
|
|
10312
|
+
...next[index],
|
|
10313
|
+
name,
|
|
10314
|
+
icon,
|
|
10315
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
10316
|
+
};
|
|
10317
|
+
next[index] = renamed;
|
|
10318
|
+
return { ...store, workspaces: next };
|
|
10319
|
+
});
|
|
10320
|
+
return renamed;
|
|
10321
|
+
}
|
|
10322
|
+
async function deleteWorkspace(paths, workspaceId) {
|
|
10323
|
+
const id = normalizeWorkspaceId(workspaceId);
|
|
10324
|
+
let deleted;
|
|
10325
|
+
await updateWorkspaceStore(paths, (store) => {
|
|
10326
|
+
const next = store.workspaces.filter((workspace) => {
|
|
10327
|
+
if (workspace.id === id) {
|
|
10328
|
+
deleted = workspace;
|
|
10329
|
+
return false;
|
|
10330
|
+
}
|
|
10331
|
+
return true;
|
|
10332
|
+
});
|
|
10333
|
+
if (!deleted) {
|
|
10334
|
+
throw workspaceNotFound();
|
|
10335
|
+
}
|
|
10336
|
+
return { ...store, workspaces: next };
|
|
10337
|
+
});
|
|
10338
|
+
return deleted;
|
|
10339
|
+
}
|
|
10340
|
+
async function assertWorkspaceExists(paths, workspaceId) {
|
|
10341
|
+
const id = normalizeOptionalWorkspaceId(workspaceId);
|
|
10342
|
+
if (!id) {
|
|
10343
|
+
return void 0;
|
|
10344
|
+
}
|
|
10345
|
+
const store = await readWorkspaceStore(paths);
|
|
10346
|
+
if (!store.workspaces.some((workspace) => workspace.id === id)) {
|
|
10347
|
+
throw workspaceNotFound();
|
|
10348
|
+
}
|
|
10349
|
+
return id;
|
|
10350
|
+
}
|
|
10351
|
+
function normalizeOptionalWorkspaceId(value) {
|
|
10352
|
+
if (value == null) {
|
|
10353
|
+
return void 0;
|
|
10354
|
+
}
|
|
10355
|
+
const normalized = value.trim();
|
|
10356
|
+
if (!normalized || normalized === "default") {
|
|
10357
|
+
return void 0;
|
|
10358
|
+
}
|
|
10359
|
+
return normalizeWorkspaceId(normalized);
|
|
10360
|
+
}
|
|
10361
|
+
function normalizeWorkspaceId(value) {
|
|
10362
|
+
const normalized = value.trim();
|
|
10363
|
+
if (!/^ws_[a-zA-Z0-9]+$/u.test(normalized)) {
|
|
10364
|
+
throw new LinkHttpError(400, "workspace_id_invalid", "workspace_id is invalid");
|
|
10365
|
+
}
|
|
10366
|
+
return normalized;
|
|
10367
|
+
}
|
|
10368
|
+
function normalizeWorkspaceName(value) {
|
|
10369
|
+
const normalized = value.replace(/\s+/gu, " ").trim();
|
|
10370
|
+
if (!normalized) {
|
|
10371
|
+
throw new LinkHttpError(
|
|
10372
|
+
400,
|
|
10373
|
+
"workspace_name_required",
|
|
10374
|
+
"workspace name is required"
|
|
10375
|
+
);
|
|
10376
|
+
}
|
|
10377
|
+
if (Array.from(normalized).length > MAX_WORKSPACE_NAME_LENGTH) {
|
|
10378
|
+
throw new LinkHttpError(
|
|
10379
|
+
400,
|
|
10380
|
+
"workspace_name_too_long",
|
|
10381
|
+
"workspace name is too long"
|
|
10382
|
+
);
|
|
10383
|
+
}
|
|
10384
|
+
return normalized;
|
|
10385
|
+
}
|
|
10386
|
+
function normalizeWorkspaceIcon(value) {
|
|
10387
|
+
const normalized = value?.trim() ?? "";
|
|
10388
|
+
if (!normalized) {
|
|
10389
|
+
return DEFAULT_WORKSPACE_ICON;
|
|
10390
|
+
}
|
|
10391
|
+
return WORKSPACE_ICON_KEYS.has(normalized) ? normalized : DEFAULT_WORKSPACE_ICON;
|
|
10392
|
+
}
|
|
10393
|
+
function assertUniqueWorkspaceName(workspaces, name, exceptWorkspaceId) {
|
|
10394
|
+
const normalized = name.toLocaleLowerCase();
|
|
10395
|
+
if (workspaces.some(
|
|
10396
|
+
(workspace) => workspace.id !== exceptWorkspaceId && workspace.name.toLocaleLowerCase() === normalized
|
|
10397
|
+
)) {
|
|
10398
|
+
throw new LinkHttpError(
|
|
10399
|
+
409,
|
|
10400
|
+
"workspace_name_conflict",
|
|
10401
|
+
"workspace name already exists"
|
|
10402
|
+
);
|
|
10403
|
+
}
|
|
10404
|
+
}
|
|
10405
|
+
async function readWorkspaceStore(paths) {
|
|
10406
|
+
return normalizeWorkspaceStore(await readJsonFile(paths.workspacesFile));
|
|
10407
|
+
}
|
|
10408
|
+
async function updateWorkspaceStore(paths, update) {
|
|
10409
|
+
return updateJsonFile(
|
|
10410
|
+
paths.workspacesFile,
|
|
10411
|
+
(current) => update(normalizeWorkspaceStore(current)),
|
|
10412
|
+
384
|
|
10413
|
+
);
|
|
10414
|
+
}
|
|
10415
|
+
function normalizeWorkspaceStore(store) {
|
|
10416
|
+
if (!store || !Array.isArray(store.workspaces)) {
|
|
10417
|
+
return { schema_version: 1, workspaces: [] };
|
|
10418
|
+
}
|
|
10419
|
+
return {
|
|
10420
|
+
schema_version: 1,
|
|
10421
|
+
workspaces: store.workspaces.map(normalizeWorkspaceRecord).filter(
|
|
10422
|
+
(workspace) => Boolean(workspace)
|
|
10423
|
+
)
|
|
10424
|
+
};
|
|
10425
|
+
}
|
|
10426
|
+
function normalizeWorkspaceRecord(value) {
|
|
10427
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
10428
|
+
return null;
|
|
10429
|
+
}
|
|
10430
|
+
const record = value;
|
|
10431
|
+
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
10432
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
10433
|
+
const icon = normalizeWorkspaceIcon(
|
|
10434
|
+
typeof record.icon === "string" ? record.icon : void 0
|
|
10435
|
+
);
|
|
10436
|
+
const createdAt = typeof record.created_at === "string" ? record.created_at.trim() : "";
|
|
10437
|
+
const updatedAt = typeof record.updated_at === "string" ? record.updated_at.trim() : "";
|
|
10438
|
+
if (!id || !name || !createdAt || !updatedAt) {
|
|
10439
|
+
return null;
|
|
10440
|
+
}
|
|
10441
|
+
return { id, name, icon, created_at: createdAt, updated_at: updatedAt };
|
|
10442
|
+
}
|
|
10443
|
+
function sortWorkspaces(workspaces) {
|
|
10444
|
+
return [...workspaces].sort(
|
|
10445
|
+
(left, right) => left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id)
|
|
10446
|
+
);
|
|
10447
|
+
}
|
|
10448
|
+
function workspaceNotFound() {
|
|
10449
|
+
return new LinkHttpError(404, "workspace_not_found", "workspace was not found");
|
|
10450
|
+
}
|
|
10451
|
+
|
|
10452
|
+
// src/conversations/blob-store.ts
|
|
10453
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
10004
10454
|
import { mkdir as mkdir6, readFile as readFile7, readdir as readdir4, rm as rm3, stat as stat6, writeFile } from "fs/promises";
|
|
10005
10455
|
import path10 from "path";
|
|
10006
10456
|
|
|
@@ -10453,7 +10903,7 @@ async function writeConversationBlob(paths, conversationId, input, options) {
|
|
|
10453
10903
|
if (input.bytes.byteLength > options.maxBytes) {
|
|
10454
10904
|
throw new LinkHttpError(413, "blob_too_large", "Blob is too large");
|
|
10455
10905
|
}
|
|
10456
|
-
const id = `blob_${
|
|
10906
|
+
const id = `blob_${randomUUID5().replaceAll("-", "")}`;
|
|
10457
10907
|
const filePath = blobPath(paths, id);
|
|
10458
10908
|
await mkdir6(path10.dirname(filePath), { recursive: true, mode: 448 });
|
|
10459
10909
|
await writeFile(filePath, input.bytes, { mode: 384 });
|
|
@@ -10592,13 +11042,6 @@ async function listConversationBlobIds(paths, conversationId) {
|
|
|
10592
11042
|
}
|
|
10593
11043
|
return blobIds;
|
|
10594
11044
|
}
|
|
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
11045
|
function conversationAttachmentsDir(paths, conversationId) {
|
|
10603
11046
|
assertValidConversationId(conversationId);
|
|
10604
11047
|
return path10.join(paths.conversationsDir, conversationId, "attachments");
|
|
@@ -10681,7 +11124,7 @@ async function readConversationProfileSummary(paths, manifest) {
|
|
|
10681
11124
|
}
|
|
10682
11125
|
async function buildConversationRuntimeMetadata(paths, manifest, snapshot) {
|
|
10683
11126
|
const current = await readCurrentConversationRuntime(paths, manifest);
|
|
10684
|
-
const usageRun =
|
|
11127
|
+
const usageRun = latestContextUsageRun(snapshot);
|
|
10685
11128
|
const profileUid = current.profileUid;
|
|
10686
11129
|
const profileName = current.profileName;
|
|
10687
11130
|
const profilePresentation = {
|
|
@@ -10828,6 +11271,7 @@ function toSummary(manifest, snapshot, profile) {
|
|
|
10828
11271
|
id: manifest.id,
|
|
10829
11272
|
title: manifest.title,
|
|
10830
11273
|
title_source: manifest.title_source,
|
|
11274
|
+
workspace_id: manifest.workspace_id ?? null,
|
|
10831
11275
|
created_at: manifest.created_at,
|
|
10832
11276
|
updated_at: manifest.updated_at,
|
|
10833
11277
|
last_event_seq: manifest.last_event_seq,
|
|
@@ -10878,7 +11322,7 @@ function isRealtimeRunStatus(status) {
|
|
|
10878
11322
|
}
|
|
10879
11323
|
|
|
10880
11324
|
// src/conversations/slash-commands.ts
|
|
10881
|
-
import { randomUUID as
|
|
11325
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
10882
11326
|
var MODEL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/@+-]{0,127}$/u;
|
|
10883
11327
|
function isValidModelId(value) {
|
|
10884
11328
|
return MODEL_ID_PATTERN.test(value);
|
|
@@ -10975,7 +11419,7 @@ function parseSlashCommandInput(content, language = "zh-CN") {
|
|
|
10975
11419
|
}
|
|
10976
11420
|
function createSlashCommandUserMessage(input) {
|
|
10977
11421
|
return {
|
|
10978
|
-
id: `msg_${
|
|
11422
|
+
id: `msg_${randomUUID6().replaceAll("-", "")}`,
|
|
10979
11423
|
schema_version: 1,
|
|
10980
11424
|
conversation_id: input.conversationId,
|
|
10981
11425
|
role: "user",
|
|
@@ -11025,7 +11469,7 @@ function slashHelpMessage(language = "zh-CN") {
|
|
|
11025
11469
|
].join("\n");
|
|
11026
11470
|
}
|
|
11027
11471
|
function freshHermesSessionId(conversationId) {
|
|
11028
|
-
return `hp_${conversationId}_${
|
|
11472
|
+
return `hp_${conversationId}_${randomUUID6().replaceAll("-", "").slice(0, 12)}`;
|
|
11029
11473
|
}
|
|
11030
11474
|
function nextVerboseMode(current) {
|
|
11031
11475
|
const modes = [
|
|
@@ -11663,7 +12107,7 @@ function safePathSegment(value, fallback) {
|
|
|
11663
12107
|
}
|
|
11664
12108
|
|
|
11665
12109
|
// src/conversations/conversation-archive-plans.ts
|
|
11666
|
-
import { randomUUID as
|
|
12110
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
11667
12111
|
import { mkdir as mkdir8 } from "fs/promises";
|
|
11668
12112
|
import path12 from "path";
|
|
11669
12113
|
var PLAN_ID_PATTERN = /^archive_[a-f0-9]{32}$/u;
|
|
@@ -11675,7 +12119,7 @@ var ConversationArchivePlanStore = class {
|
|
|
11675
12119
|
async create(conversationIds) {
|
|
11676
12120
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11677
12121
|
const plan = {
|
|
11678
|
-
id: `archive_${
|
|
12122
|
+
id: `archive_${randomUUID7().replaceAll("-", "")}`,
|
|
11679
12123
|
status: "prepared",
|
|
11680
12124
|
created_at: now,
|
|
11681
12125
|
updated_at: now,
|
|
@@ -11727,7 +12171,7 @@ function normalizePlanId(planId) {
|
|
|
11727
12171
|
}
|
|
11728
12172
|
|
|
11729
12173
|
// src/conversations/conversation-clear-plans.ts
|
|
11730
|
-
import { randomUUID as
|
|
12174
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
11731
12175
|
import { mkdir as mkdir9 } from "fs/promises";
|
|
11732
12176
|
import path13 from "path";
|
|
11733
12177
|
var PLAN_ID_PATTERN2 = /^clear_[a-f0-9]{32}$/u;
|
|
@@ -11739,7 +12183,7 @@ var ConversationClearPlanStore = class {
|
|
|
11739
12183
|
async create(conversationIds, targetStatus = "active") {
|
|
11740
12184
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11741
12185
|
const plan = {
|
|
11742
|
-
id: `clear_${
|
|
12186
|
+
id: `clear_${randomUUID8().replaceAll("-", "")}`,
|
|
11743
12187
|
status: "prepared",
|
|
11744
12188
|
target_status: targetStatus,
|
|
11745
12189
|
created_at: now,
|
|
@@ -11940,6 +12384,11 @@ function collectHermesSessionDeleteTargets(manifest, snapshot) {
|
|
|
11940
12384
|
}
|
|
11941
12385
|
return targets;
|
|
11942
12386
|
}
|
|
12387
|
+
function collectHermesSessionMutationTargets(manifest, snapshot) {
|
|
12388
|
+
return collectHermesSessionDeleteTargets(manifest, snapshot).filter(
|
|
12389
|
+
(target) => !target.sessionId.startsWith("hp_")
|
|
12390
|
+
);
|
|
12391
|
+
}
|
|
11943
12392
|
function arraysEqual(left, right) {
|
|
11944
12393
|
if (left.length !== right.length) {
|
|
11945
12394
|
return false;
|
|
@@ -12389,6 +12838,7 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
12389
12838
|
}
|
|
12390
12839
|
const archivedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12391
12840
|
const snapshot = await this.deps.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
12841
|
+
await this.setHermesSessionsArchived(manifest, snapshot, true);
|
|
12392
12842
|
await this.deps.store.writeManifest({
|
|
12393
12843
|
...manifest,
|
|
12394
12844
|
status: "archived",
|
|
@@ -12429,6 +12879,7 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
12429
12879
|
}
|
|
12430
12880
|
const unarchivedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12431
12881
|
const snapshot = await this.deps.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
12882
|
+
await this.setHermesSessionsArchived(manifest, snapshot, false);
|
|
12432
12883
|
const next = {
|
|
12433
12884
|
...manifest,
|
|
12434
12885
|
status: "active",
|
|
@@ -12532,6 +12983,18 @@ var ConversationMaintenanceCoordinator = class {
|
|
|
12532
12983
|
}
|
|
12533
12984
|
return results;
|
|
12534
12985
|
}
|
|
12986
|
+
async setHermesSessionsArchived(manifest, snapshot, archived) {
|
|
12987
|
+
const targets = collectHermesSessionMutationTargets(manifest, snapshot);
|
|
12988
|
+
for (const target of targets) {
|
|
12989
|
+
await setTuiGatewaySessionArchived({
|
|
12990
|
+
paths: this.deps.paths,
|
|
12991
|
+
logger: this.deps.logger,
|
|
12992
|
+
profileName: target.profileName,
|
|
12993
|
+
sessionId: target.sessionId,
|
|
12994
|
+
archived
|
|
12995
|
+
});
|
|
12996
|
+
}
|
|
12997
|
+
}
|
|
12535
12998
|
async pruneConversationBlobReferences(conversationId, blobIds) {
|
|
12536
12999
|
for (const blobId of blobIds) {
|
|
12537
13000
|
try {
|
|
@@ -13056,7 +13519,7 @@ function stripCompressionTitleSuffix(value) {
|
|
|
13056
13519
|
}
|
|
13057
13520
|
|
|
13058
13521
|
// src/conversations/conversation-turns.ts
|
|
13059
|
-
import { randomUUID as
|
|
13522
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
13060
13523
|
var MESSAGE_ORDER_STEP_MS = 10;
|
|
13061
13524
|
var ASSISTANT_ORDER_OFFSET_MS = 1;
|
|
13062
13525
|
function createAgentTurnDraft(input) {
|
|
@@ -13305,10 +13768,10 @@ function createAssistantMessage(input) {
|
|
|
13305
13768
|
};
|
|
13306
13769
|
}
|
|
13307
13770
|
function createMessageId() {
|
|
13308
|
-
return `msg_${
|
|
13771
|
+
return `msg_${randomUUID9().replaceAll("-", "")}`;
|
|
13309
13772
|
}
|
|
13310
13773
|
function createRunId() {
|
|
13311
|
-
return `run_${
|
|
13774
|
+
return `run_${randomUUID9().replaceAll("-", "")}`;
|
|
13312
13775
|
}
|
|
13313
13776
|
function hasActiveOrQueuedRuns(snapshot) {
|
|
13314
13777
|
return snapshot.runs.some(
|
|
@@ -14600,11 +15063,17 @@ var ConversationQueryCoordinator = class {
|
|
|
14600
15063
|
status,
|
|
14601
15064
|
limit,
|
|
14602
15065
|
cursor,
|
|
14603
|
-
fallback: () => this.listConversationPageFromStore({
|
|
15066
|
+
fallback: () => this.listConversationPageFromStore({
|
|
15067
|
+
status,
|
|
15068
|
+
limit,
|
|
15069
|
+
cursor,
|
|
15070
|
+
workspace: options.workspace
|
|
15071
|
+
}),
|
|
14604
15072
|
listPage: (pageCursor) => listConversationStatsPage(this.deps.paths, {
|
|
14605
15073
|
status,
|
|
14606
15074
|
limit,
|
|
14607
|
-
cursor: pageCursor
|
|
15075
|
+
cursor: pageCursor,
|
|
15076
|
+
workspace: options.workspace
|
|
14608
15077
|
})
|
|
14609
15078
|
});
|
|
14610
15079
|
}
|
|
@@ -14693,11 +15162,11 @@ var ConversationQueryCoordinator = class {
|
|
|
14693
15162
|
}
|
|
14694
15163
|
};
|
|
14695
15164
|
}
|
|
14696
|
-
async listConversationsFromStore(status = "active") {
|
|
15165
|
+
async listConversationsFromStore(status = "active", workspace) {
|
|
14697
15166
|
const summaries = [];
|
|
14698
15167
|
for (const conversationId of await this.deps.store.listConversationIds()) {
|
|
14699
15168
|
const manifest = await this.deps.store.readManifest(conversationId).catch(() => null);
|
|
14700
|
-
if (!manifest || manifest.status !== status) {
|
|
15169
|
+
if (!manifest || manifest.status !== status || !matchesWorkspaceFilter(manifest, workspace)) {
|
|
14701
15170
|
continue;
|
|
14702
15171
|
}
|
|
14703
15172
|
const snapshot = await this.deps.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
@@ -14709,7 +15178,10 @@ var ConversationQueryCoordinator = class {
|
|
|
14709
15178
|
);
|
|
14710
15179
|
}
|
|
14711
15180
|
async listConversationPageFromStore(input) {
|
|
14712
|
-
const all = await this.listConversationsFromStore(
|
|
15181
|
+
const all = await this.listConversationsFromStore(
|
|
15182
|
+
input.status,
|
|
15183
|
+
input.workspace
|
|
15184
|
+
);
|
|
14713
15185
|
const startIndex = input.cursor ? all.findIndex(
|
|
14714
15186
|
(summary) => isAfterConversationListCursor(summary, input.cursor)
|
|
14715
15187
|
) : 0;
|
|
@@ -14914,6 +15386,16 @@ function readNonEmptyString(value) {
|
|
|
14914
15386
|
function normalizeConversationSearchQuery(value) {
|
|
14915
15387
|
return typeof value === "string" ? value.trim() : "";
|
|
14916
15388
|
}
|
|
15389
|
+
function matchesWorkspaceFilter(manifest, filter) {
|
|
15390
|
+
if (!filter || filter.kind === "all") {
|
|
15391
|
+
return true;
|
|
15392
|
+
}
|
|
15393
|
+
const workspaceId = manifest.workspace_id?.trim() ?? "";
|
|
15394
|
+
if (filter.kind === "default") {
|
|
15395
|
+
return workspaceId === "";
|
|
15396
|
+
}
|
|
15397
|
+
return workspaceId === filter.workspaceId;
|
|
15398
|
+
}
|
|
14917
15399
|
function isAfterConversationListCursor(summary, cursor) {
|
|
14918
15400
|
return summary.updated_at < cursor.updatedAt || summary.updated_at === cursor.updatedAt && summary.id < cursor.conversationId;
|
|
14919
15401
|
}
|
|
@@ -15106,7 +15588,7 @@ function isNodeError9(error, code) {
|
|
|
15106
15588
|
}
|
|
15107
15589
|
|
|
15108
15590
|
// src/conversations/hermes-session-sync.ts
|
|
15109
|
-
import { randomUUID as
|
|
15591
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
15110
15592
|
import { readdir as readdir7, readFile as readFile10, stat as stat10 } from "fs/promises";
|
|
15111
15593
|
import path17 from "path";
|
|
15112
15594
|
|
|
@@ -17068,7 +17550,7 @@ function toLinkMessage(input) {
|
|
|
17068
17550
|
const sessionId = readString10(input.message, "session_id") ?? input.sessionId;
|
|
17069
17551
|
const createdAt = isoFromHermesTime(input.message.timestamp) ?? new Date(Date.now() + input.index).toISOString();
|
|
17070
17552
|
return {
|
|
17071
|
-
id: `msg_${
|
|
17553
|
+
id: `msg_${randomUUID10().replaceAll("-", "")}`,
|
|
17072
17554
|
schema_version: 1,
|
|
17073
17555
|
conversation_id: input.conversationId,
|
|
17074
17556
|
role,
|
|
@@ -17251,7 +17733,7 @@ async function isFile(filePath) {
|
|
|
17251
17733
|
});
|
|
17252
17734
|
}
|
|
17253
17735
|
function createConversationId() {
|
|
17254
|
-
return `conv_${
|
|
17736
|
+
return `conv_${randomUUID10().replaceAll("-", "")}`;
|
|
17255
17737
|
}
|
|
17256
17738
|
function isoFromHermesTime(value) {
|
|
17257
17739
|
const numeric = readNumber3(value);
|
|
@@ -17942,10 +18424,10 @@ function parseHermesApiCapabilities(payload) {
|
|
|
17942
18424
|
sessionKeyHeader: readString12(features, "session_key_header")
|
|
17943
18425
|
};
|
|
17944
18426
|
}
|
|
17945
|
-
async function callHermesApi(
|
|
18427
|
+
async function callHermesApi(path34, init, options) {
|
|
17946
18428
|
const method = init.method ?? "GET";
|
|
17947
18429
|
const startedAt = Date.now();
|
|
17948
|
-
void options.logger?.debug("hermes_api_request_started", { method, path:
|
|
18430
|
+
void options.logger?.debug("hermes_api_request_started", { method, path: path34 });
|
|
17949
18431
|
const availability = await ensureHermesApiServerAvailable({
|
|
17950
18432
|
fetchImpl: options.fetchImpl,
|
|
17951
18433
|
logger: options.logger,
|
|
@@ -17954,7 +18436,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
17954
18436
|
});
|
|
17955
18437
|
let config = availability.configResult.apiServer;
|
|
17956
18438
|
const fetcher = options.fetchImpl ?? fetch;
|
|
17957
|
-
const request = () => fetchHermesApi(fetcher, config,
|
|
18439
|
+
const request = () => fetchHermesApi(fetcher, config, path34, init, options);
|
|
17958
18440
|
let response;
|
|
17959
18441
|
try {
|
|
17960
18442
|
response = await request();
|
|
@@ -17962,7 +18444,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
17962
18444
|
logHermesApiError(
|
|
17963
18445
|
options.logger,
|
|
17964
18446
|
method,
|
|
17965
|
-
|
|
18447
|
+
path34,
|
|
17966
18448
|
options.profileName,
|
|
17967
18449
|
startedAt,
|
|
17968
18450
|
error
|
|
@@ -17973,7 +18455,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
17973
18455
|
logHermesApiResponse(
|
|
17974
18456
|
options.logger,
|
|
17975
18457
|
method,
|
|
17976
|
-
|
|
18458
|
+
path34,
|
|
17977
18459
|
options.profileName,
|
|
17978
18460
|
startedAt,
|
|
17979
18461
|
response
|
|
@@ -17982,7 +18464,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
17982
18464
|
}
|
|
17983
18465
|
void options.logger?.warn("hermes_api_request_retrying_after_401", {
|
|
17984
18466
|
method,
|
|
17985
|
-
path:
|
|
18467
|
+
path: path34,
|
|
17986
18468
|
profile: options.profileName ?? "default",
|
|
17987
18469
|
port: config.port ?? null,
|
|
17988
18470
|
duration_ms: Date.now() - startedAt
|
|
@@ -18001,7 +18483,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
18001
18483
|
logHermesApiError(
|
|
18002
18484
|
options.logger,
|
|
18003
18485
|
method,
|
|
18004
|
-
|
|
18486
|
+
path34,
|
|
18005
18487
|
options.profileName,
|
|
18006
18488
|
startedAt,
|
|
18007
18489
|
error
|
|
@@ -18011,7 +18493,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
18011
18493
|
logHermesApiResponse(
|
|
18012
18494
|
options.logger,
|
|
18013
18495
|
method,
|
|
18014
|
-
|
|
18496
|
+
path34,
|
|
18015
18497
|
options.profileName,
|
|
18016
18498
|
startedAt,
|
|
18017
18499
|
response
|
|
@@ -18021,7 +18503,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
18021
18503
|
}
|
|
18022
18504
|
void options.logger?.warn("hermes_api_request_repairing_after_401", {
|
|
18023
18505
|
method,
|
|
18024
|
-
path:
|
|
18506
|
+
path: path34,
|
|
18025
18507
|
profile: options.profileName ?? "default",
|
|
18026
18508
|
port: config.port ?? null,
|
|
18027
18509
|
duration_ms: Date.now() - startedAt
|
|
@@ -18042,7 +18524,7 @@ async function callHermesApi(path33, init, options) {
|
|
|
18042
18524
|
logHermesApiError(
|
|
18043
18525
|
options.logger,
|
|
18044
18526
|
method,
|
|
18045
|
-
|
|
18527
|
+
path34,
|
|
18046
18528
|
options.profileName,
|
|
18047
18529
|
startedAt,
|
|
18048
18530
|
error
|
|
@@ -18052,21 +18534,21 @@ async function callHermesApi(path33, init, options) {
|
|
|
18052
18534
|
logHermesApiResponse(
|
|
18053
18535
|
options.logger,
|
|
18054
18536
|
method,
|
|
18055
|
-
|
|
18537
|
+
path34,
|
|
18056
18538
|
options.profileName,
|
|
18057
18539
|
startedAt,
|
|
18058
18540
|
response
|
|
18059
18541
|
);
|
|
18060
18542
|
return response;
|
|
18061
18543
|
}
|
|
18062
|
-
async function fetchHermesApi(fetcher, config,
|
|
18544
|
+
async function fetchHermesApi(fetcher, config, path34, init, options) {
|
|
18063
18545
|
const headers = new Headers(init.headers);
|
|
18064
18546
|
headers.set("accept", headers.get("accept") ?? "application/json");
|
|
18065
18547
|
if (config.key) {
|
|
18066
18548
|
headers.set("x-api-key", config.key);
|
|
18067
18549
|
headers.set("authorization", `Bearer ${config.key}`);
|
|
18068
18550
|
}
|
|
18069
|
-
return await fetcher(`http://127.0.0.1:${config.port}${
|
|
18551
|
+
return await fetcher(`http://127.0.0.1:${config.port}${path34}`, {
|
|
18070
18552
|
...init,
|
|
18071
18553
|
headers
|
|
18072
18554
|
}).catch((error) => {
|
|
@@ -18075,10 +18557,10 @@ async function fetchHermesApi(fetcher, config, path33, init, options) {
|
|
|
18075
18557
|
}
|
|
18076
18558
|
void options.logger?.warn("hermes_api_server_connect_failed", {
|
|
18077
18559
|
method: String(init.method ?? "GET").toUpperCase(),
|
|
18078
|
-
path:
|
|
18560
|
+
path: path34,
|
|
18079
18561
|
profile: options.profileName ?? "default",
|
|
18080
18562
|
port: config.port ?? null,
|
|
18081
|
-
url: `http://127.0.0.1:${config.port}${
|
|
18563
|
+
url: `http://127.0.0.1:${config.port}${path34}`,
|
|
18082
18564
|
error: error instanceof Error ? error.message : String(error)
|
|
18083
18565
|
});
|
|
18084
18566
|
throw new LinkHttpError(
|
|
@@ -18088,10 +18570,10 @@ async function fetchHermesApi(fetcher, config, path33, init, options) {
|
|
|
18088
18570
|
);
|
|
18089
18571
|
});
|
|
18090
18572
|
}
|
|
18091
|
-
function logHermesApiResponse(logger, method,
|
|
18573
|
+
function logHermesApiResponse(logger, method, path34, profileName, startedAt, response) {
|
|
18092
18574
|
const fields = {
|
|
18093
18575
|
method,
|
|
18094
|
-
path:
|
|
18576
|
+
path: path34,
|
|
18095
18577
|
profile: profileName ?? "default",
|
|
18096
18578
|
status: response.status,
|
|
18097
18579
|
duration_ms: Date.now() - startedAt
|
|
@@ -18112,10 +18594,10 @@ async function logHermesApiFailureResponse(logger, fields, response) {
|
|
|
18112
18594
|
...upstreamError ? { upstream_error: upstreamError } : {}
|
|
18113
18595
|
});
|
|
18114
18596
|
}
|
|
18115
|
-
function logHermesApiError(logger, method,
|
|
18597
|
+
function logHermesApiError(logger, method, path34, profileName, startedAt, error) {
|
|
18116
18598
|
void logger?.warn("hermes_api_request_failed", {
|
|
18117
18599
|
method,
|
|
18118
|
-
path:
|
|
18600
|
+
path: path34,
|
|
18119
18601
|
profile: profileName ?? "default",
|
|
18120
18602
|
duration_ms: Date.now() - startedAt,
|
|
18121
18603
|
...error instanceof LinkHttpError ? { status: error.status, code: error.code } : {},
|
|
@@ -23140,7 +23622,7 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
|
|
|
23140
23622
|
payload: { message: assistant }
|
|
23141
23623
|
});
|
|
23142
23624
|
}
|
|
23143
|
-
const contextUsage =
|
|
23625
|
+
const contextUsage = contextUsagePayloadForSnapshot(snapshot);
|
|
23144
23626
|
await this.deps.appendEvent(conversationId, {
|
|
23145
23627
|
type: "run.completed",
|
|
23146
23628
|
message_id: assistant?.id,
|
|
@@ -23208,7 +23690,7 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
|
|
|
23208
23690
|
assistant?.id,
|
|
23209
23691
|
expiredApprovals
|
|
23210
23692
|
);
|
|
23211
|
-
const contextUsage =
|
|
23693
|
+
const contextUsage = contextUsagePayloadForSnapshot(snapshot);
|
|
23212
23694
|
await this.deps.appendEvent(conversationId, {
|
|
23213
23695
|
type: "run.failed",
|
|
23214
23696
|
message_id: assistant?.id,
|
|
@@ -23411,11 +23893,16 @@ ${details.join("\n")}` : localizedEmptyHermesResponseMessage(language);
|
|
|
23411
23893
|
payload: { message: assistant, cancelled: true }
|
|
23412
23894
|
});
|
|
23413
23895
|
}
|
|
23896
|
+
const contextUsage = contextUsagePayloadForSnapshot(snapshot);
|
|
23414
23897
|
const event = await this.deps.appendEvent(conversationId, {
|
|
23415
23898
|
type: "run.cancelled",
|
|
23416
23899
|
message_id: assistant?.id,
|
|
23417
23900
|
run_id: runId,
|
|
23418
|
-
payload: {
|
|
23901
|
+
payload: {
|
|
23902
|
+
run,
|
|
23903
|
+
reason: options.reason,
|
|
23904
|
+
...contextUsage ? { context: contextUsage } : {}
|
|
23905
|
+
}
|
|
23419
23906
|
});
|
|
23420
23907
|
await this.deps.persistConversationStats(conversationId, snapshot);
|
|
23421
23908
|
void this.deps.logger.info("conversation_run_cancelled", {
|
|
@@ -23777,6 +24264,10 @@ function appendAgentEventBlock2(message, event, updatedAt) {
|
|
|
23777
24264
|
}
|
|
23778
24265
|
message.blocks = blocks;
|
|
23779
24266
|
}
|
|
24267
|
+
function contextUsagePayloadForSnapshot(snapshot) {
|
|
24268
|
+
const run = latestContextUsageRun(snapshot);
|
|
24269
|
+
return run ? contextUsagePayload(run) : null;
|
|
24270
|
+
}
|
|
23780
24271
|
function contextUsagePayload(run) {
|
|
23781
24272
|
const usage = run.usage;
|
|
23782
24273
|
const runtimeContext = resolveRuntimeContextUsage({
|
|
@@ -23989,6 +24480,7 @@ function isNodeError17(error, code) {
|
|
|
23989
24480
|
|
|
23990
24481
|
// src/conversations/conversation-service.ts
|
|
23991
24482
|
var ALL_CONVERSATION_EVENTS = "conversation:*";
|
|
24483
|
+
var HERMES_ARCHIVE_STATE_SYNC_ID = "hermes-agent-archive-state-v1";
|
|
23992
24484
|
function isConversationNotificationEvent(event) {
|
|
23993
24485
|
const type = event.type.toLowerCase();
|
|
23994
24486
|
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 +24607,7 @@ var ConversationService = class {
|
|
|
24115
24607
|
return manifest;
|
|
24116
24608
|
}
|
|
24117
24609
|
const snapshot = await this.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
24610
|
+
await this.setHermesSessionsArchived(manifest, snapshot, false);
|
|
24118
24611
|
const unarchivedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
24119
24612
|
const next = {
|
|
24120
24613
|
...manifest,
|
|
@@ -24183,12 +24676,16 @@ var ConversationService = class {
|
|
|
24183
24676
|
async createConversation(input = {}) {
|
|
24184
24677
|
await this.store.ensureConversationsDir();
|
|
24185
24678
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
24186
|
-
const id = `conv_${
|
|
24679
|
+
const id = `conv_${randomUUID11().replaceAll("-", "")}`;
|
|
24187
24680
|
const title = input.title?.trim() || DEFAULT_CONVERSATION_TITLE;
|
|
24188
24681
|
const profile = await resolveConversationProfileTarget(
|
|
24189
24682
|
this.paths,
|
|
24190
24683
|
input.profileName
|
|
24191
24684
|
);
|
|
24685
|
+
const workspaceId = await assertWorkspaceExists(
|
|
24686
|
+
this.paths,
|
|
24687
|
+
input.workspaceId
|
|
24688
|
+
);
|
|
24192
24689
|
const manifest = {
|
|
24193
24690
|
id,
|
|
24194
24691
|
schema_version: 1,
|
|
@@ -24204,6 +24701,7 @@ var ConversationService = class {
|
|
|
24204
24701
|
profile: profile.profileName,
|
|
24205
24702
|
owner_account_id: input.accountId,
|
|
24206
24703
|
owner_app_instance_id: input.appInstanceId,
|
|
24704
|
+
workspace_id: workspaceId ?? null,
|
|
24207
24705
|
created_at: now,
|
|
24208
24706
|
updated_at: now,
|
|
24209
24707
|
last_event_seq: 0
|
|
@@ -24214,6 +24712,7 @@ var ConversationService = class {
|
|
|
24214
24712
|
payload: {
|
|
24215
24713
|
title,
|
|
24216
24714
|
title_source: manifest.title_source,
|
|
24715
|
+
workspace_id: workspaceId ?? null,
|
|
24217
24716
|
profile: {
|
|
24218
24717
|
uid: profile.profileUid,
|
|
24219
24718
|
name: profile.profileName,
|
|
@@ -24368,7 +24867,7 @@ var ConversationService = class {
|
|
|
24368
24867
|
updated_at: now
|
|
24369
24868
|
} : manifest;
|
|
24370
24869
|
const message = {
|
|
24371
|
-
id: `msg_${
|
|
24870
|
+
id: `msg_${randomUUID11().replaceAll("-", "")}`,
|
|
24372
24871
|
schema_version: 1,
|
|
24373
24872
|
conversation_id: manifest.id,
|
|
24374
24873
|
role: "assistant",
|
|
@@ -24559,6 +25058,7 @@ var ConversationService = class {
|
|
|
24559
25058
|
if (result.imported_count > 0 || result.reprojected_count > 0) {
|
|
24560
25059
|
await this.rebuildStatisticsIndex();
|
|
24561
25060
|
}
|
|
25061
|
+
await this.syncHermesArchiveStatesBestEffort();
|
|
24562
25062
|
return result;
|
|
24563
25063
|
})();
|
|
24564
25064
|
this.hermesSessionSyncPromise = task;
|
|
@@ -25126,6 +25626,136 @@ var ConversationService = class {
|
|
|
25126
25626
|
async unarchiveConversation(conversationId) {
|
|
25127
25627
|
return this.maintenance.unarchiveConversation(conversationId);
|
|
25128
25628
|
}
|
|
25629
|
+
listWorkspaces() {
|
|
25630
|
+
return listWorkspaces(this.paths);
|
|
25631
|
+
}
|
|
25632
|
+
createWorkspace(input) {
|
|
25633
|
+
return createWorkspace(this.paths, input);
|
|
25634
|
+
}
|
|
25635
|
+
renameWorkspace(workspaceId, input) {
|
|
25636
|
+
return renameWorkspace(this.paths, workspaceId, input);
|
|
25637
|
+
}
|
|
25638
|
+
async deleteWorkspace(workspaceId) {
|
|
25639
|
+
const workspace = await deleteWorkspace(this.paths, workspaceId);
|
|
25640
|
+
const movedConversationCount = await this.clearWorkspaceFromConversations(workspace.id);
|
|
25641
|
+
return { workspace, movedConversationCount };
|
|
25642
|
+
}
|
|
25643
|
+
async setConversationWorkspace(conversationId, workspaceId) {
|
|
25644
|
+
const normalizedWorkspaceId = await assertWorkspaceExists(
|
|
25645
|
+
this.paths,
|
|
25646
|
+
workspaceId
|
|
25647
|
+
);
|
|
25648
|
+
return this.withConversationLock(conversationId, async () => {
|
|
25649
|
+
const manifest = await this.store.readRunnableManifest(conversationId);
|
|
25650
|
+
const currentWorkspaceId = manifest.workspace_id ?? null;
|
|
25651
|
+
const nextWorkspaceId = normalizedWorkspaceId ?? null;
|
|
25652
|
+
const snapshot = await this.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
25653
|
+
if (currentWorkspaceId !== nextWorkspaceId) {
|
|
25654
|
+
const nextManifest = {
|
|
25655
|
+
...manifest,
|
|
25656
|
+
workspace_id: nextWorkspaceId
|
|
25657
|
+
};
|
|
25658
|
+
await this.store.writeManifest(nextManifest);
|
|
25659
|
+
await this.persistConversationStatsWithoutManifestRewrite(
|
|
25660
|
+
nextManifest,
|
|
25661
|
+
snapshot
|
|
25662
|
+
);
|
|
25663
|
+
const conversation = await this.queries.summarizeConversation(
|
|
25664
|
+
nextManifest,
|
|
25665
|
+
snapshot
|
|
25666
|
+
);
|
|
25667
|
+
return {
|
|
25668
|
+
conversation_id: conversationId,
|
|
25669
|
+
workspace_id: nextWorkspaceId,
|
|
25670
|
+
conversation
|
|
25671
|
+
};
|
|
25672
|
+
}
|
|
25673
|
+
return {
|
|
25674
|
+
conversation_id: conversationId,
|
|
25675
|
+
workspace_id: currentWorkspaceId,
|
|
25676
|
+
conversation: await this.queries.summarizeConversation(
|
|
25677
|
+
manifest,
|
|
25678
|
+
snapshot
|
|
25679
|
+
)
|
|
25680
|
+
};
|
|
25681
|
+
});
|
|
25682
|
+
}
|
|
25683
|
+
async clearWorkspaceFromConversations(workspaceId) {
|
|
25684
|
+
const normalizedWorkspaceId = normalizeOptionalWorkspaceId(workspaceId);
|
|
25685
|
+
if (!normalizedWorkspaceId) {
|
|
25686
|
+
return 0;
|
|
25687
|
+
}
|
|
25688
|
+
let movedConversationCount = 0;
|
|
25689
|
+
for (const conversationId of await this.store.listConversationIds()) {
|
|
25690
|
+
await this.withConversationLock(conversationId, async () => {
|
|
25691
|
+
const manifest = await this.store.readManifest(conversationId).catch(() => null);
|
|
25692
|
+
if (!manifest || manifest.workspace_id !== normalizedWorkspaceId) {
|
|
25693
|
+
return;
|
|
25694
|
+
}
|
|
25695
|
+
const nextManifest = {
|
|
25696
|
+
...manifest,
|
|
25697
|
+
workspace_id: null
|
|
25698
|
+
};
|
|
25699
|
+
await this.store.writeManifest(nextManifest);
|
|
25700
|
+
const snapshot = await this.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
25701
|
+
await this.persistConversationStatsWithoutManifestRewrite(
|
|
25702
|
+
nextManifest,
|
|
25703
|
+
snapshot
|
|
25704
|
+
);
|
|
25705
|
+
movedConversationCount += 1;
|
|
25706
|
+
});
|
|
25707
|
+
}
|
|
25708
|
+
return movedConversationCount;
|
|
25709
|
+
}
|
|
25710
|
+
async syncHermesArchiveStatesBestEffort() {
|
|
25711
|
+
const markerPath = this.hermesArchiveStateSyncMarkerPath();
|
|
25712
|
+
const marker = await readJsonFile(
|
|
25713
|
+
markerPath
|
|
25714
|
+
).catch(() => null);
|
|
25715
|
+
if (marker?.id === HERMES_ARCHIVE_STATE_SYNC_ID) {
|
|
25716
|
+
return;
|
|
25717
|
+
}
|
|
25718
|
+
let failedCount = 0;
|
|
25719
|
+
for (const conversationId of await this.store.listConversationIds()) {
|
|
25720
|
+
const manifest = await this.store.readManifest(conversationId).catch(() => null);
|
|
25721
|
+
if (!manifest || manifest.status !== "archived") {
|
|
25722
|
+
continue;
|
|
25723
|
+
}
|
|
25724
|
+
const snapshot = await this.store.readSnapshot(conversationId).catch(() => emptySnapshot2());
|
|
25725
|
+
await this.setHermesSessionsArchived(manifest, snapshot, true).catch(
|
|
25726
|
+
(error) => {
|
|
25727
|
+
failedCount += 1;
|
|
25728
|
+
void this.logger.debug("hermes_archive_state_sync_failed", {
|
|
25729
|
+
conversation_id: conversationId,
|
|
25730
|
+
archived: true,
|
|
25731
|
+
error: error instanceof Error ? error.message : String(error)
|
|
25732
|
+
});
|
|
25733
|
+
}
|
|
25734
|
+
);
|
|
25735
|
+
}
|
|
25736
|
+
if (failedCount > 0) {
|
|
25737
|
+
return;
|
|
25738
|
+
}
|
|
25739
|
+
await writeJsonFile(markerPath, {
|
|
25740
|
+
id: HERMES_ARCHIVE_STATE_SYNC_ID,
|
|
25741
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
25742
|
+
});
|
|
25743
|
+
}
|
|
25744
|
+
async setHermesSessionsArchived(manifest, snapshot, archived) {
|
|
25745
|
+
const targets = collectHermesSessionMutationTargets(manifest, snapshot);
|
|
25746
|
+
for (const target of targets) {
|
|
25747
|
+
await setTuiGatewaySessionArchived({
|
|
25748
|
+
paths: this.paths,
|
|
25749
|
+
logger: this.logger,
|
|
25750
|
+
profileName: target.profileName,
|
|
25751
|
+
sessionId: target.sessionId,
|
|
25752
|
+
archived
|
|
25753
|
+
});
|
|
25754
|
+
}
|
|
25755
|
+
}
|
|
25756
|
+
hermesArchiveStateSyncMarkerPath() {
|
|
25757
|
+
return path24.join(this.paths.indexesDir, "hermes-archive-state-sync.json");
|
|
25758
|
+
}
|
|
25129
25759
|
prepareClearAllConversationPlan(targetStatus) {
|
|
25130
25760
|
return this.maintenance.prepareClearAllConversationPlan(targetStatus);
|
|
25131
25761
|
}
|
|
@@ -25162,45 +25792,15 @@ var ConversationService = class {
|
|
|
25162
25792
|
if (!manifest || !conversationMatchesProfile(manifest, profileName, profileUid)) {
|
|
25163
25793
|
continue;
|
|
25164
25794
|
}
|
|
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) {
|
|
25795
|
+
try {
|
|
25796
|
+
await this.deleteConversation(conversationId);
|
|
25203
25797
|
deletedConversationIds.push(conversationId);
|
|
25798
|
+
} catch (error) {
|
|
25799
|
+
void this.logger.warn("profile_delete_conversation_cleanup_failed", {
|
|
25800
|
+
profile: profileName,
|
|
25801
|
+
conversation_id: conversationId,
|
|
25802
|
+
error: error instanceof Error ? error.message : String(error)
|
|
25803
|
+
});
|
|
25204
25804
|
}
|
|
25205
25805
|
}
|
|
25206
25806
|
await deleteConversationStatsForProfile(this.paths, {
|
|
@@ -25231,6 +25831,13 @@ var ConversationService = class {
|
|
|
25231
25831
|
statsOverride
|
|
25232
25832
|
);
|
|
25233
25833
|
}
|
|
25834
|
+
async persistConversationStatsWithoutManifestRewrite(manifest, snapshot) {
|
|
25835
|
+
const stats = manifest.stats ?? buildConversationStats(manifest, snapshot);
|
|
25836
|
+
await upsertConversationStats(
|
|
25837
|
+
this.paths,
|
|
25838
|
+
toStatsIndexRecord({ ...manifest, stats }, stats)
|
|
25839
|
+
);
|
|
25840
|
+
}
|
|
25234
25841
|
async appendEvent(conversationId, input) {
|
|
25235
25842
|
const event = await this.store.appendEvent(conversationId, input);
|
|
25236
25843
|
this.emitter.emit(this.liveEventName(conversationId), event);
|
|
@@ -25345,7 +25952,7 @@ function approvalRestartText(language, zh, en) {
|
|
|
25345
25952
|
}
|
|
25346
25953
|
|
|
25347
25954
|
// src/security/devices.ts
|
|
25348
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
25955
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID12, timingSafeEqual, createHash as createHash7 } from "crypto";
|
|
25349
25956
|
var ACCESS_TOKEN_TTL_MS = 15 * 60 * 1e3;
|
|
25350
25957
|
var REFRESH_TOKEN_TTL_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
25351
25958
|
var DEVICE_SEEN_WRITE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
@@ -25363,7 +25970,7 @@ async function createDeviceSession(input, paths = resolveRuntimePaths()) {
|
|
|
25363
25970
|
}
|
|
25364
25971
|
}
|
|
25365
25972
|
const device = {
|
|
25366
|
-
id: `dev_${
|
|
25973
|
+
id: `dev_${randomUUID12().replaceAll("-", "")}`,
|
|
25367
25974
|
label: normalizeDeviceLabel(input.label),
|
|
25368
25975
|
platform: normalizeDevicePlatform(input.platform),
|
|
25369
25976
|
model: normalizeDeviceModel(input.model),
|
|
@@ -26240,7 +26847,8 @@ function registerConversationRoutes(router, options) {
|
|
|
26240
26847
|
ctx.set("cache-control", "no-store");
|
|
26241
26848
|
const result = await conversations.listConversationPage({
|
|
26242
26849
|
limit: readLimit(ctx.query.limit),
|
|
26243
|
-
cursor
|
|
26850
|
+
cursor,
|
|
26851
|
+
workspace: readConversationWorkspaceFilter(ctx.query)
|
|
26244
26852
|
});
|
|
26245
26853
|
const localized = localizeConversationListPage(result, language);
|
|
26246
26854
|
ctx.body = {
|
|
@@ -26309,7 +26917,8 @@ function registerConversationRoutes(router, options) {
|
|
|
26309
26917
|
title: readString19(body, "title") ?? void 0,
|
|
26310
26918
|
profileName: readOptionalProfileName(body),
|
|
26311
26919
|
accountId: auth.accountId,
|
|
26312
|
-
appInstanceId: auth.appInstanceId
|
|
26920
|
+
appInstanceId: auth.appInstanceId,
|
|
26921
|
+
workspaceId: readConversationWorkspaceId(body)
|
|
26313
26922
|
}),
|
|
26314
26923
|
language
|
|
26315
26924
|
)
|
|
@@ -26691,6 +27300,21 @@ function registerConversationRoutes(router, options) {
|
|
|
26691
27300
|
};
|
|
26692
27301
|
}
|
|
26693
27302
|
);
|
|
27303
|
+
router.patch("/api/v1/conversations/:conversationId/workspace", async (ctx) => {
|
|
27304
|
+
await authenticateRequest(ctx, paths);
|
|
27305
|
+
const language = readPreferredLanguage(ctx);
|
|
27306
|
+
const body = await readJsonBody(ctx.req);
|
|
27307
|
+
ctx.body = {
|
|
27308
|
+
ok: true,
|
|
27309
|
+
...localizeConversationResult(
|
|
27310
|
+
await conversations.setConversationWorkspace(
|
|
27311
|
+
ctx.params.conversationId,
|
|
27312
|
+
readConversationWorkspaceId(body)
|
|
27313
|
+
),
|
|
27314
|
+
language
|
|
27315
|
+
)
|
|
27316
|
+
};
|
|
27317
|
+
});
|
|
26694
27318
|
router.delete("/api/v1/conversations/:conversationId", async (ctx) => {
|
|
26695
27319
|
const auth = await authenticateRequest(ctx, paths);
|
|
26696
27320
|
const result = await conversations.deleteConversation(
|
|
@@ -26784,6 +27408,25 @@ function readConversationListForce(query) {
|
|
|
26784
27408
|
const raw = Array.isArray(query.force) ? query.force[0] : query.force;
|
|
26785
27409
|
return readBoolean3(raw) === true;
|
|
26786
27410
|
}
|
|
27411
|
+
function readConversationWorkspaceFilter(query) {
|
|
27412
|
+
const workspace = readQueryString(query.workspace) ?? readQueryString(query.workspace_id);
|
|
27413
|
+
if (!workspace || workspace === "all") {
|
|
27414
|
+
return { kind: "all" };
|
|
27415
|
+
}
|
|
27416
|
+
if (workspace === "default") {
|
|
27417
|
+
return { kind: "default" };
|
|
27418
|
+
}
|
|
27419
|
+
return { kind: "workspace", workspaceId: workspace };
|
|
27420
|
+
}
|
|
27421
|
+
function readConversationWorkspaceId(body) {
|
|
27422
|
+
if (Object.prototype.hasOwnProperty.call(body, "workspace_id") || Object.prototype.hasOwnProperty.call(body, "workspaceId")) {
|
|
27423
|
+
return readString19(body, "workspace_id") ?? readString19(body, "workspaceId");
|
|
27424
|
+
}
|
|
27425
|
+
if (Object.prototype.hasOwnProperty.call(body, "workspace")) {
|
|
27426
|
+
return readString19(body, "workspace");
|
|
27427
|
+
}
|
|
27428
|
+
return void 0;
|
|
27429
|
+
}
|
|
26787
27430
|
function localizeConversationListPage(page, language) {
|
|
26788
27431
|
return {
|
|
26789
27432
|
...page,
|
|
@@ -26997,11 +27640,11 @@ function isSseRequestContext(ctx) {
|
|
|
26997
27640
|
}
|
|
26998
27641
|
return isSseRequestPath(ctx.path) || isActiveSseSocket(ctx.req.socket);
|
|
26999
27642
|
}
|
|
27000
|
-
function isSseRequestPath(
|
|
27001
|
-
if (!
|
|
27643
|
+
function isSseRequestPath(path34) {
|
|
27644
|
+
if (!path34) {
|
|
27002
27645
|
return false;
|
|
27003
27646
|
}
|
|
27004
|
-
return
|
|
27647
|
+
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
27648
|
}
|
|
27006
27649
|
function isExpectedClientDisconnectError2(error, options = {}) {
|
|
27007
27650
|
if (!(error instanceof Error)) {
|
|
@@ -27833,7 +28476,7 @@ function errorMessage3(error) {
|
|
|
27833
28476
|
|
|
27834
28477
|
// src/hermes/profile-identity.ts
|
|
27835
28478
|
import { readFile as readFile16, stat as stat17 } from "fs/promises";
|
|
27836
|
-
import
|
|
28479
|
+
import path25 from "path";
|
|
27837
28480
|
var MAX_SOUL_MD_LENGTH = 2e4;
|
|
27838
28481
|
async function readHermesProfileIdentity(profileName, paths) {
|
|
27839
28482
|
await assertProfileExists3(profileName, paths);
|
|
@@ -27890,7 +28533,7 @@ async function assertProfileExists3(profileName, paths) {
|
|
|
27890
28533
|
}
|
|
27891
28534
|
}
|
|
27892
28535
|
function resolveSoulPath(profileName) {
|
|
27893
|
-
return
|
|
28536
|
+
return path25.join(resolveHermesProfileDir(profileName), "SOUL.md");
|
|
27894
28537
|
}
|
|
27895
28538
|
function isNodeError18(error, code) {
|
|
27896
28539
|
return error instanceof Error && "code" in error && error.code === code;
|
|
@@ -27906,13 +28549,13 @@ import {
|
|
|
27906
28549
|
rm as rm7,
|
|
27907
28550
|
stat as stat19
|
|
27908
28551
|
} from "fs/promises";
|
|
27909
|
-
import
|
|
28552
|
+
import path27 from "path";
|
|
27910
28553
|
import YAML5 from "yaml";
|
|
27911
28554
|
|
|
27912
28555
|
// src/hermes/link-skill.ts
|
|
27913
28556
|
import { readFile as readFile17, stat as stat18 } from "fs/promises";
|
|
27914
28557
|
import os5 from "os";
|
|
27915
|
-
import
|
|
28558
|
+
import path26 from "path";
|
|
27916
28559
|
import YAML4 from "yaml";
|
|
27917
28560
|
var HERMES_LINK_SKILL_ROOT_DIR = "hermes-skills";
|
|
27918
28561
|
var HERMES_LINK_SKILL_DIR = "hermes-link";
|
|
@@ -27997,7 +28640,7 @@ Do not modify Hermes profiles, delete user data, edit config files, or kill proc
|
|
|
27997
28640
|
async function ensureHermesLinkSkillInstalledForProfiles(options = {}) {
|
|
27998
28641
|
const paths = options.paths ?? resolveRuntimePaths();
|
|
27999
28642
|
const externalDir = resolveHermesLinkSkillExternalDir(paths);
|
|
28000
|
-
const skillPath =
|
|
28643
|
+
const skillPath = path26.join(
|
|
28001
28644
|
externalDir,
|
|
28002
28645
|
HERMES_LINK_SKILL_DIR,
|
|
28003
28646
|
HERMES_LINK_SKILL_FILE
|
|
@@ -28079,7 +28722,7 @@ function withDefaultProfilePlaceholder2(profiles) {
|
|
|
28079
28722
|
];
|
|
28080
28723
|
}
|
|
28081
28724
|
function resolveHermesLinkSkillExternalDir(paths = resolveRuntimePaths()) {
|
|
28082
|
-
return
|
|
28725
|
+
return path26.join(paths.homeDir, HERMES_LINK_SKILL_ROOT_DIR);
|
|
28083
28726
|
}
|
|
28084
28727
|
async function writeHermesLinkSkill(skillPath) {
|
|
28085
28728
|
const existing = await readFile17(skillPath, "utf8").catch((error) => {
|
|
@@ -28174,11 +28817,11 @@ function appendExternalDir(current, externalDir, hermesHome) {
|
|
|
28174
28817
|
const seen = new Set(
|
|
28175
28818
|
entries.map((entry) => resolveExternalDirEntry(entry, hermesHome))
|
|
28176
28819
|
);
|
|
28177
|
-
const normalizedExternalDir =
|
|
28820
|
+
const normalizedExternalDir = path26.resolve(externalDir);
|
|
28178
28821
|
return seen.has(normalizedExternalDir) ? entries : [...entries, normalizedExternalDir];
|
|
28179
28822
|
}
|
|
28180
28823
|
function externalDirsInclude(current, externalDir, hermesHome) {
|
|
28181
|
-
const normalizedExternalDir =
|
|
28824
|
+
const normalizedExternalDir = path26.resolve(externalDir);
|
|
28182
28825
|
return readExternalDirEntries(current).some(
|
|
28183
28826
|
(entry) => resolveExternalDirEntry(entry, hermesHome) === normalizedExternalDir
|
|
28184
28827
|
);
|
|
@@ -28189,14 +28832,14 @@ function readExternalDirEntries(value) {
|
|
|
28189
28832
|
}
|
|
28190
28833
|
function resolveExternalDirEntry(entry, hermesHome) {
|
|
28191
28834
|
const expanded = expandHome(expandEnvVars(entry));
|
|
28192
|
-
return
|
|
28835
|
+
return path26.resolve(path26.isAbsolute(expanded) ? expanded : path26.join(hermesHome, expanded));
|
|
28193
28836
|
}
|
|
28194
28837
|
function expandHome(value) {
|
|
28195
28838
|
if (value === "~") {
|
|
28196
28839
|
return os5.homedir();
|
|
28197
28840
|
}
|
|
28198
|
-
if (value.startsWith(`~${
|
|
28199
|
-
return
|
|
28841
|
+
if (value.startsWith(`~${path26.sep}`) || value.startsWith("~/")) {
|
|
28842
|
+
return path26.join(os5.homedir(), value.slice(2));
|
|
28200
28843
|
}
|
|
28201
28844
|
return value;
|
|
28202
28845
|
}
|
|
@@ -28748,7 +29391,7 @@ function collectEnvKeys(value, keys = /* @__PURE__ */ new Set()) {
|
|
|
28748
29391
|
return keys;
|
|
28749
29392
|
}
|
|
28750
29393
|
async function writeEnvValues(profileName, values) {
|
|
28751
|
-
const envPath =
|
|
29394
|
+
const envPath = path27.join(resolveHermesProfileDir(profileName), ".env");
|
|
28752
29395
|
const existingRaw = await readFile18(envPath, "utf8").catch((error) => {
|
|
28753
29396
|
if (isNodeError20(error, "ENOENT")) {
|
|
28754
29397
|
return "";
|
|
@@ -28785,8 +29428,8 @@ async function writeEnvValues(profileName, values) {
|
|
|
28785
29428
|
await atomicWriteFilePreservingMetadata(envPath, nextRaw);
|
|
28786
29429
|
}
|
|
28787
29430
|
async function copySkills(sourceProfile, targetProfile) {
|
|
28788
|
-
const sourceSkills =
|
|
28789
|
-
const targetSkills =
|
|
29431
|
+
const sourceSkills = path27.join(resolveHermesProfileDir(sourceProfile), "skills");
|
|
29432
|
+
const targetSkills = path27.join(resolveHermesProfileDir(targetProfile), "skills");
|
|
28790
29433
|
if (!await pathExists2(sourceSkills)) {
|
|
28791
29434
|
return;
|
|
28792
29435
|
}
|
|
@@ -28881,10 +29524,10 @@ async function readProfileCreationLogLines(paths) {
|
|
|
28881
29524
|
);
|
|
28882
29525
|
}
|
|
28883
29526
|
function profileCreationStatePath(paths) {
|
|
28884
|
-
return
|
|
29527
|
+
return path27.join(paths.runDir, "profile-create-state.json");
|
|
28885
29528
|
}
|
|
28886
29529
|
function profileCreationLogPath(paths) {
|
|
28887
|
-
return
|
|
29530
|
+
return path27.join(paths.logsDir, PROFILE_CREATE_LOG_FILE);
|
|
28888
29531
|
}
|
|
28889
29532
|
async function clearProfileCreationLogFiles(paths) {
|
|
28890
29533
|
const primary = profileCreationLogPath(paths);
|
|
@@ -29173,7 +29816,7 @@ import {
|
|
|
29173
29816
|
readFile as readFile19,
|
|
29174
29817
|
stat as stat20
|
|
29175
29818
|
} from "fs/promises";
|
|
29176
|
-
import
|
|
29819
|
+
import path28 from "path";
|
|
29177
29820
|
import YAML6 from "yaml";
|
|
29178
29821
|
var ENTRY_DELIMITER = "\n\xA7\n";
|
|
29179
29822
|
var DEFAULT_MEMORY_LIMIT = 2200;
|
|
@@ -29504,7 +30147,7 @@ async function saveProviderSettings(profileName, provider, patch) {
|
|
|
29504
30147
|
});
|
|
29505
30148
|
await patchJsonProviderConfig(
|
|
29506
30149
|
profileName,
|
|
29507
|
-
|
|
30150
|
+
path28.join("hindsight", "config.json"),
|
|
29508
30151
|
{
|
|
29509
30152
|
mode: patch.mode,
|
|
29510
30153
|
api_url: patch.apiUrl,
|
|
@@ -29707,7 +30350,7 @@ async function patchHermesMemoryLimits(profileName, patch) {
|
|
|
29707
30350
|
await atomicWriteFilePreservingMetadata(configPath, document.toString());
|
|
29708
30351
|
}
|
|
29709
30352
|
function resolveMemoryDir(profileName) {
|
|
29710
|
-
return
|
|
30353
|
+
return path28.join(resolveHermesProfileDir(profileName), "memories");
|
|
29711
30354
|
}
|
|
29712
30355
|
async function readMemoryStore(profileName, target, limits) {
|
|
29713
30356
|
const filePath = memoryFilePath(profileName, target);
|
|
@@ -29768,7 +30411,7 @@ async function writeMemoryEntries(profileName, target, entries) {
|
|
|
29768
30411
|
);
|
|
29769
30412
|
}
|
|
29770
30413
|
function memoryFilePath(profileName, target) {
|
|
29771
|
-
return
|
|
30414
|
+
return path28.join(
|
|
29772
30415
|
resolveMemoryDir(profileName),
|
|
29773
30416
|
target === "user" ? "USER.md" : "MEMORY.md"
|
|
29774
30417
|
);
|
|
@@ -29828,7 +30471,7 @@ async function readCustomProviderSetupSummary(profileName) {
|
|
|
29828
30471
|
configurable: true,
|
|
29829
30472
|
configured: true,
|
|
29830
30473
|
configurationIssue: null,
|
|
29831
|
-
providerConfigPath:
|
|
30474
|
+
providerConfigPath: path28.join(
|
|
29832
30475
|
resolveHermesProfileDir(profileName),
|
|
29833
30476
|
"<provider>.json"
|
|
29834
30477
|
),
|
|
@@ -30222,7 +30865,7 @@ async function readProviderSettings(profileName, provider) {
|
|
|
30222
30865
|
stringSetting(
|
|
30223
30866
|
"dbPath",
|
|
30224
30867
|
"SQLite \u6570\u636E\u5E93\u8DEF\u5F84",
|
|
30225
|
-
config.db_path ??
|
|
30868
|
+
config.db_path ?? path28.join(resolveHermesProfileDir(profileName), "memory_store.db")
|
|
30226
30869
|
),
|
|
30227
30870
|
booleanSetting("autoExtract", "\u4F1A\u8BDD\u7ED3\u675F\u81EA\u52A8\u62BD\u53D6", config.auto_extract ?? false),
|
|
30228
30871
|
numberSetting("defaultTrust", "\u9ED8\u8BA4\u4FE1\u4EFB\u5206", config.default_trust ?? 0.5),
|
|
@@ -30258,7 +30901,7 @@ async function readProviderSettings(profileName, provider) {
|
|
|
30258
30901
|
stringSetting(
|
|
30259
30902
|
"workingDirectory",
|
|
30260
30903
|
"\u5DE5\u4F5C\u76EE\u5F55",
|
|
30261
|
-
|
|
30904
|
+
path28.join(resolveHermesProfileDir(profileName), "byterover"),
|
|
30262
30905
|
false
|
|
30263
30906
|
)
|
|
30264
30907
|
];
|
|
@@ -30267,16 +30910,16 @@ async function readProviderSettings(profileName, provider) {
|
|
|
30267
30910
|
}
|
|
30268
30911
|
function memoryProviderConfigPath(profileName, provider) {
|
|
30269
30912
|
if (provider === "honcho") {
|
|
30270
|
-
return
|
|
30913
|
+
return path28.join(resolveHermesProfileDir(profileName), "honcho.json");
|
|
30271
30914
|
}
|
|
30272
30915
|
if (provider === "mem0") {
|
|
30273
|
-
return
|
|
30916
|
+
return path28.join(resolveHermesProfileDir(profileName), "mem0.json");
|
|
30274
30917
|
}
|
|
30275
30918
|
if (provider === "supermemory") {
|
|
30276
|
-
return
|
|
30919
|
+
return path28.join(resolveHermesProfileDir(profileName), "supermemory.json");
|
|
30277
30920
|
}
|
|
30278
30921
|
if (provider === "hindsight") {
|
|
30279
|
-
return
|
|
30922
|
+
return path28.join(
|
|
30280
30923
|
resolveHermesProfileDir(profileName),
|
|
30281
30924
|
"hindsight",
|
|
30282
30925
|
"config.json"
|
|
@@ -30285,13 +30928,13 @@ function memoryProviderConfigPath(profileName, provider) {
|
|
|
30285
30928
|
return null;
|
|
30286
30929
|
}
|
|
30287
30930
|
function customProviderConfigPath(profileName, provider) {
|
|
30288
|
-
return
|
|
30931
|
+
return path28.join(
|
|
30289
30932
|
resolveHermesProfileDir(profileName),
|
|
30290
30933
|
`${normalizeCustomProviderId(provider)}.json`
|
|
30291
30934
|
);
|
|
30292
30935
|
}
|
|
30293
30936
|
function customProviderRegistryPath(profileName) {
|
|
30294
|
-
return
|
|
30937
|
+
return path28.join(
|
|
30295
30938
|
resolveHermesProfileDir(profileName),
|
|
30296
30939
|
CUSTOM_PROVIDER_REGISTRY_FILE
|
|
30297
30940
|
);
|
|
@@ -30343,7 +30986,7 @@ async function saveCustomProviderRegistryEntry(profileName, provider) {
|
|
|
30343
30986
|
);
|
|
30344
30987
|
}
|
|
30345
30988
|
async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
30346
|
-
const pluginsDir =
|
|
30989
|
+
const pluginsDir = path28.join(resolveHermesProfileDir(profileName), "plugins");
|
|
30347
30990
|
const entries = await readdir10(pluginsDir, { withFileTypes: true }).catch(
|
|
30348
30991
|
(error) => {
|
|
30349
30992
|
if (isNodeError21(error, "ENOENT")) {
|
|
@@ -30363,7 +31006,7 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
|
30363
31006
|
} catch {
|
|
30364
31007
|
continue;
|
|
30365
31008
|
}
|
|
30366
|
-
const providerDir =
|
|
31009
|
+
const providerDir = path28.join(pluginsDir, entry.name);
|
|
30367
31010
|
if (!await isMemoryProviderPluginDir(providerDir)) {
|
|
30368
31011
|
continue;
|
|
30369
31012
|
}
|
|
@@ -30377,7 +31020,7 @@ async function discoverUserMemoryProviderDescriptors(profileName) {
|
|
|
30377
31020
|
return descriptors;
|
|
30378
31021
|
}
|
|
30379
31022
|
async function isUserMemoryProviderInstalled(profileName, provider) {
|
|
30380
|
-
const providerDir =
|
|
31023
|
+
const providerDir = path28.join(
|
|
30381
31024
|
resolveHermesProfileDir(profileName),
|
|
30382
31025
|
"plugins",
|
|
30383
31026
|
normalizeCustomProviderId(provider)
|
|
@@ -30385,7 +31028,7 @@ async function isUserMemoryProviderInstalled(profileName, provider) {
|
|
|
30385
31028
|
return isMemoryProviderPluginDir(providerDir);
|
|
30386
31029
|
}
|
|
30387
31030
|
async function isMemoryProviderPluginDir(providerDir) {
|
|
30388
|
-
const source = await readFile19(
|
|
31031
|
+
const source = await readFile19(path28.join(providerDir, "__init__.py"), "utf8").catch(
|
|
30389
31032
|
(error) => {
|
|
30390
31033
|
if (isNodeError21(error, "ENOENT")) {
|
|
30391
31034
|
return "";
|
|
@@ -30397,7 +31040,7 @@ async function isMemoryProviderPluginDir(providerDir) {
|
|
|
30397
31040
|
return sample.includes("register_memory_provider") || sample.includes("MemoryProvider");
|
|
30398
31041
|
}
|
|
30399
31042
|
async function readPluginMetadata(providerDir) {
|
|
30400
|
-
const raw = await readFile19(
|
|
31043
|
+
const raw = await readFile19(path28.join(providerDir, "plugin.yaml"), "utf8").catch(
|
|
30401
31044
|
(error) => {
|
|
30402
31045
|
if (isNodeError21(error, "ENOENT")) {
|
|
30403
31046
|
return "";
|
|
@@ -30409,10 +31052,10 @@ async function readPluginMetadata(providerDir) {
|
|
|
30409
31052
|
}
|
|
30410
31053
|
async function resolveByteRoverCli() {
|
|
30411
31054
|
const candidates = [
|
|
30412
|
-
...(process.env.PATH ?? "").split(
|
|
30413
|
-
|
|
31055
|
+
...(process.env.PATH ?? "").split(path28.delimiter).filter(Boolean).map((dir) => path28.join(dir, "brv")),
|
|
31056
|
+
path28.join(process.env.HOME ?? "", ".brv-cli", "bin", "brv"),
|
|
30414
31057
|
"/usr/local/bin/brv",
|
|
30415
|
-
|
|
31058
|
+
path28.join(process.env.HOME ?? "", ".npm-global", "bin", "brv")
|
|
30416
31059
|
].filter(Boolean);
|
|
30417
31060
|
for (const candidate of candidates) {
|
|
30418
31061
|
const found = await access3(candidate).then(() => true).catch(() => false);
|
|
@@ -30473,7 +31116,7 @@ async function patchHermesMemoryEnv(profileName, patch) {
|
|
|
30473
31116
|
if (entries.length === 0) {
|
|
30474
31117
|
return;
|
|
30475
31118
|
}
|
|
30476
|
-
const envPath =
|
|
31119
|
+
const envPath = path28.join(resolveHermesProfileDir(profileName), ".env");
|
|
30477
31120
|
const existingRaw = await readFile19(envPath, "utf8").catch((error) => {
|
|
30478
31121
|
if (isNodeError21(error, "ENOENT")) {
|
|
30479
31122
|
return "";
|
|
@@ -30644,7 +31287,7 @@ async function readActiveMemoryProvider(profileName) {
|
|
|
30644
31287
|
return provider;
|
|
30645
31288
|
}
|
|
30646
31289
|
async function patchJsonProviderConfig(profileName, relativePath, patch) {
|
|
30647
|
-
const configPath =
|
|
31290
|
+
const configPath = path28.join(
|
|
30648
31291
|
resolveHermesProfileDir(profileName),
|
|
30649
31292
|
relativePath
|
|
30650
31293
|
);
|
|
@@ -30673,7 +31316,7 @@ async function readJsonObject(filePath) {
|
|
|
30673
31316
|
} catch {
|
|
30674
31317
|
throw new HermesMemoryError(
|
|
30675
31318
|
"memory_provider_config_invalid",
|
|
30676
|
-
`${
|
|
31319
|
+
`${path28.basename(filePath)} \u4E0D\u662F\u6709\u6548\u7684 JSON \u914D\u7F6E\u6587\u4EF6\u3002`
|
|
30677
31320
|
);
|
|
30678
31321
|
}
|
|
30679
31322
|
}
|
|
@@ -31297,7 +31940,7 @@ function toMemoryHttpError(error) {
|
|
|
31297
31940
|
|
|
31298
31941
|
// src/hermes/skills.ts
|
|
31299
31942
|
import { readFile as readFile20, readdir as readdir11 } from "fs/promises";
|
|
31300
|
-
import
|
|
31943
|
+
import path29 from "path";
|
|
31301
31944
|
import YAML7 from "yaml";
|
|
31302
31945
|
var HermesSkillNotFoundError = class extends Error {
|
|
31303
31946
|
constructor(skillName) {
|
|
@@ -31311,7 +31954,7 @@ var EXCLUDED_SKILL_DIRS = /* @__PURE__ */ new Set([".git", ".github", ".hub"]);
|
|
|
31311
31954
|
async function listHermesProfileSkills(profileName, paths = resolveRuntimePaths()) {
|
|
31312
31955
|
const profile = await readExistingProfile(profileName, paths);
|
|
31313
31956
|
const profileDir = resolveHermesProfileDir(profile.name);
|
|
31314
|
-
const skillsRoot =
|
|
31957
|
+
const skillsRoot = path29.join(profileDir, "skills");
|
|
31315
31958
|
const [skillFiles, disabled, provenance] = await Promise.all([
|
|
31316
31959
|
findSkillFiles(skillsRoot),
|
|
31317
31960
|
readDisabledSkillNames(resolveHermesConfigPath(profile.name)),
|
|
@@ -31402,7 +32045,7 @@ async function collectSkillFiles(directory, results) {
|
|
|
31402
32045
|
if (EXCLUDED_SKILL_DIRS.has(entry.name)) {
|
|
31403
32046
|
continue;
|
|
31404
32047
|
}
|
|
31405
|
-
const entryPath =
|
|
32048
|
+
const entryPath = path29.join(directory, entry.name);
|
|
31406
32049
|
if (entry.isDirectory()) {
|
|
31407
32050
|
await collectSkillFiles(entryPath, results);
|
|
31408
32051
|
continue;
|
|
@@ -31424,10 +32067,10 @@ async function readSkillMetadata(input) {
|
|
|
31424
32067
|
if (raw === null) {
|
|
31425
32068
|
return null;
|
|
31426
32069
|
}
|
|
31427
|
-
const skillDir =
|
|
32070
|
+
const skillDir = path29.dirname(input.skillFile);
|
|
31428
32071
|
const { frontmatter, body } = parseSkillDocument(raw.slice(0, 4e3));
|
|
31429
32072
|
const name = normalizeSkillName(
|
|
31430
|
-
readString21(frontmatter.name) ??
|
|
32073
|
+
readString21(frontmatter.name) ?? path29.basename(skillDir)
|
|
31431
32074
|
);
|
|
31432
32075
|
if (!name) {
|
|
31433
32076
|
return null;
|
|
@@ -31446,7 +32089,7 @@ async function readSkillMetadata(input) {
|
|
|
31446
32089
|
enabled: !input.disabled.has(name),
|
|
31447
32090
|
source: provenance.source,
|
|
31448
32091
|
trust: provenance.trust,
|
|
31449
|
-
relativePath:
|
|
32092
|
+
relativePath: path29.relative(input.skillsRoot, skillDir)
|
|
31450
32093
|
};
|
|
31451
32094
|
}
|
|
31452
32095
|
function parseSkillDocument(raw) {
|
|
@@ -31467,8 +32110,8 @@ function parseSkillDocument(raw) {
|
|
|
31467
32110
|
}
|
|
31468
32111
|
}
|
|
31469
32112
|
function categoryFromPath(skillsRoot, skillFile) {
|
|
31470
|
-
const relative =
|
|
31471
|
-
const parts = relative.split(
|
|
32113
|
+
const relative = path29.relative(skillsRoot, skillFile);
|
|
32114
|
+
const parts = relative.split(path29.sep).filter(Boolean);
|
|
31472
32115
|
return parts.length >= 3 ? parts[0] : null;
|
|
31473
32116
|
}
|
|
31474
32117
|
function firstBodyDescription(body) {
|
|
@@ -31515,7 +32158,7 @@ async function readSkillProvenance(root) {
|
|
|
31515
32158
|
return provenance;
|
|
31516
32159
|
}
|
|
31517
32160
|
async function readBundledSkillNames(root) {
|
|
31518
|
-
const raw = await readFile20(
|
|
32161
|
+
const raw = await readFile20(path29.join(root, ".bundled_manifest"), "utf8").catch(
|
|
31519
32162
|
(error) => {
|
|
31520
32163
|
if (isNodeError22(error, "ENOENT")) {
|
|
31521
32164
|
return "";
|
|
@@ -31538,7 +32181,7 @@ async function readBundledSkillNames(root) {
|
|
|
31538
32181
|
return names;
|
|
31539
32182
|
}
|
|
31540
32183
|
async function readHubInstalledSkills(root) {
|
|
31541
|
-
const raw = await readFile20(
|
|
32184
|
+
const raw = await readFile20(path29.join(root, ".hub", "lock.json"), "utf8").catch(
|
|
31542
32185
|
(error) => {
|
|
31543
32186
|
if (isNodeError22(error, "ENOENT")) {
|
|
31544
32187
|
return "";
|
|
@@ -32201,7 +32844,7 @@ function readModelList(payload) {
|
|
|
32201
32844
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
32202
32845
|
import { spawn as spawn4 } from "child_process";
|
|
32203
32846
|
import { mkdir as mkdir13, readFile as readFile21, rm as rm8 } from "fs/promises";
|
|
32204
|
-
import
|
|
32847
|
+
import path30 from "path";
|
|
32205
32848
|
var SERVER_HERMES_RELEASES_LATEST_PATH = "/api/v1/hermes-agent/releases/latest";
|
|
32206
32849
|
var RELEASE_CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
32207
32850
|
var RELEASE_FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -32479,13 +33122,13 @@ async function readUpdateLogLines(paths) {
|
|
|
32479
33122
|
);
|
|
32480
33123
|
}
|
|
32481
33124
|
function releaseCachePath(paths) {
|
|
32482
|
-
return
|
|
33125
|
+
return path30.join(paths.indexesDir, "hermes-release-check.json");
|
|
32483
33126
|
}
|
|
32484
33127
|
function updateStatePath(paths) {
|
|
32485
|
-
return
|
|
33128
|
+
return path30.join(paths.runDir, "hermes-update-state.json");
|
|
32486
33129
|
}
|
|
32487
33130
|
function updateLogPath(paths) {
|
|
32488
|
-
return
|
|
33131
|
+
return path30.join(paths.logsDir, UPDATE_LOG_FILE);
|
|
32489
33132
|
}
|
|
32490
33133
|
async function clearUpdateLogFiles(paths) {
|
|
32491
33134
|
const primary = updateLogPath(paths);
|
|
@@ -32586,12 +33229,12 @@ function readString22(payload, key) {
|
|
|
32586
33229
|
import { spawn as spawn6 } from "child_process";
|
|
32587
33230
|
import { EventEmitter as EventEmitter4 } from "events";
|
|
32588
33231
|
import { mkdir as mkdir16, readFile as readFile23, rm as rm11 } from "fs/promises";
|
|
32589
|
-
import
|
|
33232
|
+
import path32 from "path";
|
|
32590
33233
|
|
|
32591
33234
|
// src/daemon/process.ts
|
|
32592
33235
|
import { spawn as spawn5 } from "child_process";
|
|
32593
33236
|
import { mkdir as mkdir15, readFile as readFile22, rm as rm10, writeFile as writeFile4 } from "fs/promises";
|
|
32594
|
-
import
|
|
33237
|
+
import path31 from "path";
|
|
32595
33238
|
|
|
32596
33239
|
// src/daemon/service.ts
|
|
32597
33240
|
import { createServer } from "http";
|
|
@@ -34538,7 +35181,7 @@ async function runDaemonSupervisor(paths = resolveRuntimePaths()) {
|
|
|
34538
35181
|
await mkdir15(paths.logsDir, { recursive: true, mode: 448 });
|
|
34539
35182
|
const log = createRotatingTextLogWriter({
|
|
34540
35183
|
paths,
|
|
34541
|
-
fileName:
|
|
35184
|
+
fileName: path31.basename(daemonLogFile(paths))
|
|
34542
35185
|
});
|
|
34543
35186
|
const scriptPath = currentCliScriptPath();
|
|
34544
35187
|
const write = (chunk) => {
|
|
@@ -34836,7 +35479,7 @@ function terminateChild(child, previousForceKillTimer) {
|
|
|
34836
35479
|
}
|
|
34837
35480
|
}
|
|
34838
35481
|
function supervisorStopIntentPath(paths) {
|
|
34839
|
-
return
|
|
35482
|
+
return path31.join(paths.runDir, "supervisor-stop-intent.json");
|
|
34840
35483
|
}
|
|
34841
35484
|
async function writeSupervisorStopIntent(paths, pid) {
|
|
34842
35485
|
await mkdir15(paths.runDir, { recursive: true, mode: 448 });
|
|
@@ -35399,7 +36042,7 @@ async function buildOfficialInstallCommand(options, targetVersion) {
|
|
|
35399
36042
|
};
|
|
35400
36043
|
}
|
|
35401
36044
|
function buildUnixInstallCommand(installerUrl) {
|
|
35402
|
-
const nodeBinDir =
|
|
36045
|
+
const nodeBinDir = path32.dirname(process.execPath);
|
|
35403
36046
|
const fetchScript = [
|
|
35404
36047
|
quoteShellToken(process.execPath),
|
|
35405
36048
|
"--input-type=module",
|
|
@@ -35669,10 +36312,10 @@ async function readUpdateLogLines2(paths) {
|
|
|
35669
36312
|
);
|
|
35670
36313
|
}
|
|
35671
36314
|
function updateStatePath2(paths) {
|
|
35672
|
-
return
|
|
36315
|
+
return path32.join(paths.runDir, "link-update-state.json");
|
|
35673
36316
|
}
|
|
35674
36317
|
function updateLogPath2(paths) {
|
|
35675
|
-
return
|
|
36318
|
+
return path32.join(paths.logsDir, UPDATE_LOG_FILE2);
|
|
35676
36319
|
}
|
|
35677
36320
|
async function clearUpdateLogFiles2(paths) {
|
|
35678
36321
|
const primary = updateLogPath2(paths);
|
|
@@ -35756,7 +36399,7 @@ function readString23(payload, key) {
|
|
|
35756
36399
|
}
|
|
35757
36400
|
|
|
35758
36401
|
// src/pairing/pairing.ts
|
|
35759
|
-
import
|
|
36402
|
+
import path33 from "path";
|
|
35760
36403
|
import { rm as rm12 } from "fs/promises";
|
|
35761
36404
|
|
|
35762
36405
|
// src/relay/bootstrap.ts
|
|
@@ -36096,10 +36739,10 @@ async function loadRequiredIdentity2(paths) {
|
|
|
36096
36739
|
}
|
|
36097
36740
|
return identity;
|
|
36098
36741
|
}
|
|
36099
|
-
async function postServerJson(serverBaseUrl,
|
|
36742
|
+
async function postServerJson(serverBaseUrl, path34, body, options) {
|
|
36100
36743
|
let response;
|
|
36101
36744
|
try {
|
|
36102
|
-
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${
|
|
36745
|
+
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path34}`, {
|
|
36103
36746
|
method: "POST",
|
|
36104
36747
|
headers: {
|
|
36105
36748
|
accept: "application/json",
|
|
@@ -36147,10 +36790,10 @@ function pairingErrorSnapshot(stage, error) {
|
|
|
36147
36790
|
occurred_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
36148
36791
|
};
|
|
36149
36792
|
}
|
|
36150
|
-
async function patchServerJson(serverBaseUrl,
|
|
36793
|
+
async function patchServerJson(serverBaseUrl, path34, token, body, options) {
|
|
36151
36794
|
let response;
|
|
36152
36795
|
try {
|
|
36153
|
-
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${
|
|
36796
|
+
response = await fetch(`${serverBaseUrl.replace(/\/+$/u, "")}${path34}`, {
|
|
36154
36797
|
method: "PATCH",
|
|
36155
36798
|
headers: {
|
|
36156
36799
|
accept: "application/json",
|
|
@@ -36198,10 +36841,10 @@ function createPairingNetworkError(input) {
|
|
|
36198
36841
|
);
|
|
36199
36842
|
}
|
|
36200
36843
|
function pairingClaimPath(sessionId, paths) {
|
|
36201
|
-
return
|
|
36844
|
+
return path33.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.claimed.json`);
|
|
36202
36845
|
}
|
|
36203
36846
|
function pairingSessionPath(sessionId, paths) {
|
|
36204
|
-
return
|
|
36847
|
+
return path33.join(paths.pairingDir, `${Buffer.from(sessionId).toString("base64url")}.json`);
|
|
36205
36848
|
}
|
|
36206
36849
|
function qrPreferredUrls(routes) {
|
|
36207
36850
|
return routes.preferredUrls.filter((url) => !url.includes("/api/v1/relay/links/")).slice(0, 1);
|
|
@@ -36272,6 +36915,7 @@ function registerSystemRoutes(router, options) {
|
|
|
36272
36915
|
conversation_queue_limit: MAX_CONVERSATION_QUEUED_RUNS,
|
|
36273
36916
|
responses_interrupted_previous_response: true,
|
|
36274
36917
|
conversation_rename: true,
|
|
36918
|
+
conversation_workspaces: true,
|
|
36275
36919
|
blobs: true,
|
|
36276
36920
|
devices: true,
|
|
36277
36921
|
device_delete: true,
|
|
@@ -36758,6 +37402,51 @@ function readDeviceModelHeader(ctx) {
|
|
|
36758
37402
|
return value ? value.slice(0, 128) : null;
|
|
36759
37403
|
}
|
|
36760
37404
|
|
|
37405
|
+
// src/http/routes/workspaces.ts
|
|
37406
|
+
function registerWorkspaceRoutes(router, options) {
|
|
37407
|
+
const { paths, conversations } = options;
|
|
37408
|
+
router.get("/api/v1/workspaces", async (ctx) => {
|
|
37409
|
+
await authenticateRequest(ctx, paths);
|
|
37410
|
+
ctx.set("cache-control", "no-store");
|
|
37411
|
+
ctx.body = {
|
|
37412
|
+
ok: true,
|
|
37413
|
+
workspaces: await conversations.listWorkspaces()
|
|
37414
|
+
};
|
|
37415
|
+
});
|
|
37416
|
+
router.post("/api/v1/workspaces", async (ctx) => {
|
|
37417
|
+
await authenticateRequest(ctx, paths);
|
|
37418
|
+
const body = await readJsonBody(ctx.req);
|
|
37419
|
+
ctx.status = 201;
|
|
37420
|
+
ctx.body = {
|
|
37421
|
+
ok: true,
|
|
37422
|
+
workspace: await conversations.createWorkspace({
|
|
37423
|
+
name: readString19(body, "name") ?? "",
|
|
37424
|
+
icon: readString19(body, "icon") ?? void 0
|
|
37425
|
+
})
|
|
37426
|
+
};
|
|
37427
|
+
});
|
|
37428
|
+
router.patch("/api/v1/workspaces/:workspaceId", async (ctx) => {
|
|
37429
|
+
await authenticateRequest(ctx, paths);
|
|
37430
|
+
const body = await readJsonBody(ctx.req);
|
|
37431
|
+
ctx.body = {
|
|
37432
|
+
ok: true,
|
|
37433
|
+
workspace: await conversations.renameWorkspace(ctx.params.workspaceId, {
|
|
37434
|
+
name: readString19(body, "name") ?? "",
|
|
37435
|
+
icon: readString19(body, "icon") ?? void 0
|
|
37436
|
+
})
|
|
37437
|
+
};
|
|
37438
|
+
});
|
|
37439
|
+
router.delete("/api/v1/workspaces/:workspaceId", async (ctx) => {
|
|
37440
|
+
await authenticateRequest(ctx, paths);
|
|
37441
|
+
const result = await conversations.deleteWorkspace(ctx.params.workspaceId);
|
|
37442
|
+
ctx.body = {
|
|
37443
|
+
ok: true,
|
|
37444
|
+
workspace: result.workspace,
|
|
37445
|
+
moved_conversation_count: result.movedConversationCount
|
|
37446
|
+
};
|
|
37447
|
+
});
|
|
37448
|
+
}
|
|
37449
|
+
|
|
36761
37450
|
// src/http/routes/hermes-updates.ts
|
|
36762
37451
|
function registerHermesUpdateRoutes(router, options) {
|
|
36763
37452
|
const { paths, logger } = options;
|
|
@@ -37447,6 +38136,7 @@ async function createApp(options = {}) {
|
|
|
37447
38136
|
conversations,
|
|
37448
38137
|
syncCronDeliveries
|
|
37449
38138
|
});
|
|
38139
|
+
registerWorkspaceRoutes(router, { paths, conversations });
|
|
37450
38140
|
registerConversationRoutes(router, { paths, logger, conversations });
|
|
37451
38141
|
registerRunRoutes(router, { paths, logger, conversations });
|
|
37452
38142
|
registerProfileRoutes(router, { paths, logger, conversations });
|