clawconnect-agent 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -34
- package/README.zh-CN.md +152 -44
- package/dist/commands/install.js +10 -4
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/pair.d.ts +6 -0
- package/dist/commands/pair.js +78 -10
- package/dist/commands/pair.js.map +1 -1
- package/dist/commands/run.js +14 -17
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/send-file.d.ts +3 -24
- package/dist/commands/send-file.js +22 -449
- package/dist/commands/send-file.js.map +1 -1
- package/dist/commands/status.d.ts +13 -1
- package/dist/commands/status.js +58 -15
- package/dist/commands/status.js.map +1 -1
- package/dist/config/config.d.ts +6 -11
- package/dist/config/config.js +19 -48
- package/dist/config/config.js.map +1 -1
- package/dist/config/env.d.ts +1 -1
- package/dist/config/env.js +4 -5
- package/dist/config/env.js.map +1 -1
- package/dist/config/profile.d.ts +11 -0
- package/dist/config/profile.js +80 -0
- package/dist/config/profile.js.map +1 -0
- package/dist/core/command-types.d.ts +18 -0
- package/dist/core/command-types.js +2 -0
- package/dist/core/command-types.js.map +1 -0
- package/dist/core/relay/attachment-staging.js.map +1 -0
- package/dist/{relay → core/relay}/chat-payload.js +17 -3
- package/dist/core/relay/chat-payload.js.map +1 -0
- package/dist/{commands/send-file-utils.js → core/relay/file-upload-utils.js} +1 -1
- package/dist/core/relay/file-upload-utils.js.map +1 -0
- package/dist/core/relay/file-upload.d.ts +40 -0
- package/dist/core/relay/file-upload.js +401 -0
- package/dist/core/relay/file-upload.js.map +1 -0
- package/dist/core/relay/mobile-chat-run-bridge.d.ts +201 -0
- package/dist/core/relay/mobile-chat-run-bridge.js +192 -0
- package/dist/core/relay/mobile-chat-run-bridge.js.map +1 -0
- package/dist/core/relay/office-payload.js.map +1 -0
- package/dist/core/relay/reconnect.js.map +1 -0
- package/dist/core/relay/relay-server-connection.d.ts +6 -0
- package/dist/core/relay/relay-server-connection.js +32 -0
- package/dist/core/relay/relay-server-connection.js.map +1 -0
- package/dist/core/relay/slash-command-types.d.ts +7 -0
- package/dist/core/relay/slash-command-types.js +2 -0
- package/dist/core/relay/slash-command-types.js.map +1 -0
- package/dist/core/relay/timeline-event-builder.d.ts +86 -0
- package/dist/core/relay/timeline-event-builder.js +199 -0
- package/dist/core/relay/timeline-event-builder.js.map +1 -0
- package/dist/core/relay/timeline-event-log.d.ts +81 -0
- package/dist/core/relay/timeline-event-log.js +254 -0
- package/dist/core/relay/timeline-event-log.js.map +1 -0
- package/dist/core/relay/voice-input.d.ts +11 -0
- package/dist/core/relay/voice-input.js +120 -0
- package/dist/core/relay/voice-input.js.map +1 -0
- package/dist/gateway-profiles.d.ts +8 -0
- package/dist/gateway-profiles.js +24 -0
- package/dist/gateway-profiles.js.map +1 -0
- package/dist/hermes/hermes-relay-manager.d.ts +59 -0
- package/dist/hermes/hermes-relay-manager.js +754 -0
- package/dist/hermes/hermes-relay-manager.js.map +1 -0
- package/dist/hermes/hermes-runtime.d.ts +9 -0
- package/dist/hermes/hermes-runtime.js +9 -0
- package/dist/hermes/hermes-runtime.js.map +1 -0
- package/dist/hermes/hermes-session-store.d.ts +15 -0
- package/dist/hermes/hermes-session-store.js +174 -0
- package/dist/hermes/hermes-session-store.js.map +1 -0
- package/dist/hermes/hermes-voice-input.d.ts +13 -0
- package/dist/hermes/hermes-voice-input.js +27 -0
- package/dist/hermes/hermes-voice-input.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-artifacts.d.ts +3 -0
- package/dist/hermes/runtime/hermes-runtime-artifacts.js +65 -0
- package/dist/hermes/runtime/hermes-runtime-artifacts.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-backups.d.ts +5 -0
- package/dist/hermes/runtime/hermes-runtime-backups.js +128 -0
- package/dist/hermes/runtime/hermes-runtime-backups.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-chat.d.ts +41 -0
- package/dist/hermes/runtime/hermes-runtime-chat.js +723 -0
- package/dist/hermes/runtime/hermes-runtime-chat.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-command-router.d.ts +2 -0
- package/dist/hermes/runtime/hermes-runtime-command-router.js +96 -0
- package/dist/hermes/runtime/hermes-runtime-command-router.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-command-utils.d.ts +3 -0
- package/dist/hermes/runtime/hermes-runtime-command-utils.js +19 -0
- package/dist/hermes/runtime/hermes-runtime-command-utils.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-cron.d.ts +11 -0
- package/dist/hermes/runtime/hermes-runtime-cron.js +301 -0
- package/dist/hermes/runtime/hermes-runtime-cron.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-history.d.ts +2 -0
- package/dist/hermes/runtime/hermes-runtime-history.js +249 -0
- package/dist/hermes/runtime/hermes-runtime-history.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-lifecycle.d.ts +9 -0
- package/dist/hermes/runtime/hermes-runtime-lifecycle.js +111 -0
- package/dist/hermes/runtime/hermes-runtime-lifecycle.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-models.d.ts +29 -0
- package/dist/hermes/runtime/hermes-runtime-models.js +125 -0
- package/dist/hermes/runtime/hermes-runtime-models.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-process.d.ts +24 -0
- package/dist/hermes/runtime/hermes-runtime-process.js +147 -0
- package/dist/hermes/runtime/hermes-runtime-process.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-sessions.d.ts +5 -0
- package/dist/hermes/runtime/hermes-runtime-sessions.js +88 -0
- package/dist/hermes/runtime/hermes-runtime-sessions.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-skills.d.ts +13 -0
- package/dist/hermes/runtime/hermes-runtime-skills.js +322 -0
- package/dist/hermes/runtime/hermes-runtime-skills.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-types.d.ts +19 -0
- package/dist/hermes/runtime/hermes-runtime-types.js +2 -0
- package/dist/hermes/runtime/hermes-runtime-types.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-usage.d.ts +22 -0
- package/dist/hermes/runtime/hermes-runtime-usage.js +211 -0
- package/dist/hermes/runtime/hermes-runtime-usage.js.map +1 -0
- package/dist/hermes/runtime/hermes-runtime-values.d.ts +14 -0
- package/dist/hermes/runtime/hermes-runtime-values.js +105 -0
- package/dist/hermes/runtime/hermes-runtime-values.js.map +1 -0
- package/dist/i18n/index.js +4 -0
- package/dist/i18n/index.js.map +1 -1
- package/dist/index.js +115 -9
- package/dist/index.js.map +1 -1
- package/dist/openclaw/backups/backup-manager.js.map +1 -0
- package/dist/{commands → openclaw/config}/provider-config.js +1 -1
- package/dist/openclaw/config/provider-config.js.map +1 -0
- package/dist/openclaw/config/provider-registry.js.map +1 -0
- package/dist/{relay → openclaw}/gateway-client.js +4 -3
- package/dist/openclaw/gateway-client.js.map +1 -0
- package/dist/{commands → openclaw/handlers}/local-handlers.d.ts +1 -1
- package/dist/{commands → openclaw/handlers}/local-handlers.js +2 -30
- package/dist/openclaw/handlers/local-handlers.js.map +1 -0
- package/dist/{commands → openclaw/handlers}/provider-handlers.d.ts +1 -1
- package/dist/{commands → openclaw/handlers}/provider-handlers.js +3 -3
- package/dist/openclaw/handlers/provider-handlers.js.map +1 -0
- package/dist/openclaw/relay/chat-history.d.ts +51 -0
- package/dist/openclaw/relay/chat-history.js +347 -0
- package/dist/openclaw/relay/chat-history.js.map +1 -0
- package/dist/{relay → openclaw/relay}/chat-send-attachments.js +12 -1
- package/dist/openclaw/relay/chat-send-attachments.js.map +1 -0
- package/dist/openclaw/relay/openclaw-voice-input.d.ts +13 -0
- package/dist/openclaw/relay/openclaw-voice-input.js +18 -0
- package/dist/openclaw/relay/openclaw-voice-input.js.map +1 -0
- package/dist/openclaw/relay/outgoing-media-relay.d.ts +11 -0
- package/dist/openclaw/relay/outgoing-media-relay.js +120 -0
- package/dist/openclaw/relay/outgoing-media-relay.js.map +1 -0
- package/dist/{relay → openclaw/relay}/session-context.d.ts +6 -0
- package/dist/{relay → openclaw/relay}/session-context.js +53 -3
- package/dist/openclaw/relay/session-context.js.map +1 -0
- package/dist/openclaw/relay/slash-command-catalog.d.ts +2 -0
- package/dist/{relay → openclaw/relay}/slash-command-catalog.generated.d.ts +0 -5
- package/dist/{relay → openclaw/relay}/slash-command-catalog.generated.js +0 -6
- package/dist/openclaw/relay/slash-command-catalog.generated.js.map +1 -0
- package/dist/openclaw/relay/slash-command-catalog.js.map +1 -0
- package/dist/openclaw/relay/transcript-dedupe.d.ts +21 -0
- package/dist/openclaw/relay/transcript-dedupe.js +211 -0
- package/dist/openclaw/relay/transcript-dedupe.js.map +1 -0
- package/dist/{relay → openclaw}/relay-manager.d.ts +1 -4
- package/dist/openclaw/relay-manager.js +787 -0
- package/dist/openclaw/relay-manager.js.map +1 -0
- package/dist/{commands → openclaw/runtime}/local-runtime.d.ts +1 -17
- package/dist/openclaw/runtime/local-runtime.js.map +1 -0
- package/dist/openclaw/session-store.d.ts +1 -0
- package/dist/openclaw/session-store.js +70 -0
- package/dist/openclaw/session-store.js.map +1 -0
- package/dist/platform/service-manager-common.d.ts +5 -2
- package/dist/platform/service-manager-common.js +18 -7
- package/dist/platform/service-manager-common.js.map +1 -1
- package/dist/platform/service-manager.d.ts +16 -5
- package/dist/platform/service-manager.js +85 -50
- package/dist/platform/service-manager.js.map +1 -1
- package/dist/runtime-adapters.d.ts +19 -0
- package/dist/runtime-adapters.js +39 -0
- package/dist/runtime-adapters.js.map +1 -0
- package/package.json +3 -3
- package/dist/commands/backup-manager.js.map +0 -1
- package/dist/commands/local-handlers.js.map +0 -1
- package/dist/commands/local-runtime.js.map +0 -1
- package/dist/commands/provider-config.js.map +0 -1
- package/dist/commands/provider-handlers.js.map +0 -1
- package/dist/commands/provider-registry.js.map +0 -1
- package/dist/commands/send-file-utils.js.map +0 -1
- package/dist/commands/voice-reply.d.ts +0 -39
- package/dist/commands/voice-reply.js +0 -329
- package/dist/commands/voice-reply.js.map +0 -1
- package/dist/relay/attachment-staging.js.map +0 -1
- package/dist/relay/chat-history.d.ts +0 -29
- package/dist/relay/chat-history.js +0 -69
- package/dist/relay/chat-history.js.map +0 -1
- package/dist/relay/chat-payload.js.map +0 -1
- package/dist/relay/chat-send-attachments.js.map +0 -1
- package/dist/relay/gateway-client.js.map +0 -1
- package/dist/relay/office-payload.js.map +0 -1
- package/dist/relay/reconnect.js.map +0 -1
- package/dist/relay/relay-manager.js +0 -644
- package/dist/relay/relay-manager.js.map +0 -1
- package/dist/relay/session-context.js.map +0 -1
- package/dist/relay/slash-command-catalog.d.ts +0 -8
- package/dist/relay/slash-command-catalog.generated.js.map +0 -1
- package/dist/relay/slash-command-catalog.js.map +0 -1
- package/dist/relay/voice-reply-preference.d.ts +0 -25
- package/dist/relay/voice-reply-preference.js +0 -103
- package/dist/relay/voice-reply-preference.js.map +0 -1
- /package/dist/{relay → core/relay}/attachment-staging.d.ts +0 -0
- /package/dist/{relay → core/relay}/attachment-staging.js +0 -0
- /package/dist/{relay → core/relay}/chat-payload.d.ts +0 -0
- /package/dist/{commands/send-file-utils.d.ts → core/relay/file-upload-utils.d.ts} +0 -0
- /package/dist/{relay → core/relay}/office-payload.d.ts +0 -0
- /package/dist/{relay → core/relay}/office-payload.js +0 -0
- /package/dist/{relay → core/relay}/reconnect.d.ts +0 -0
- /package/dist/{relay → core/relay}/reconnect.js +0 -0
- /package/dist/{commands → openclaw/backups}/backup-manager.d.ts +0 -0
- /package/dist/{commands → openclaw/backups}/backup-manager.js +0 -0
- /package/dist/{commands → openclaw/config}/provider-config.d.ts +0 -0
- /package/dist/{commands → openclaw/config}/provider-registry.d.ts +0 -0
- /package/dist/{commands → openclaw/config}/provider-registry.js +0 -0
- /package/dist/{relay → openclaw}/gateway-client.d.ts +0 -0
- /package/dist/{relay → openclaw/relay}/chat-send-attachments.d.ts +0 -0
- /package/dist/{relay → openclaw/relay}/slash-command-catalog.js +0 -0
- /package/dist/{commands → openclaw/runtime}/local-runtime.js +0 -0
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
import { statSync } from "fs";
|
|
2
|
+
import { WebSocket } from "ws";
|
|
3
|
+
import { uploadFileToRelay } from "../core/relay/file-upload.js";
|
|
4
|
+
import { bindRelayAbortSignal, buildRelayUrl, parseRelayFrame, sendRelayJson, shouldRetryRelayClose, } from "../core/relay/relay-server-connection.js";
|
|
5
|
+
import { buildOfficeEventPayload } from "../core/relay/office-payload.js";
|
|
6
|
+
import { buildMobileAssistantAbortedPayload, buildMobileAssistantErrorPayload, buildMobileAssistantFinalPayload, resolveMobileChatRun, } from "../core/relay/mobile-chat-run-bridge.js";
|
|
7
|
+
import { voiceInputSetupMessage } from "../core/relay/voice-input.js";
|
|
8
|
+
import { buildAttachmentStateChangedEvent, deriveAttachmentId, derivePartId, } from "../core/relay/timeline-event-builder.js";
|
|
9
|
+
import { gatewayCapabilitiesForType } from "../gateway-profiles.js";
|
|
10
|
+
import { runHermesPython } from "./runtime/hermes-runtime-process.js";
|
|
11
|
+
import { prepareHermesVoiceInputCommand, resolveHermesVoiceInputSessionKey } from "./hermes-voice-input.js";
|
|
12
|
+
import { collectHermesUsageSnapshot, handleHermesCommand, readHermesStatusSnapshot, runHermesChat, } from "./hermes-runtime.js";
|
|
13
|
+
;
|
|
14
|
+
const HERMES_SLASH_COMMAND_CATALOG_SCRIPT = String.raw `
|
|
15
|
+
import json
|
|
16
|
+
|
|
17
|
+
items = []
|
|
18
|
+
|
|
19
|
+
def add(command, title=None, detail=None):
|
|
20
|
+
if not command:
|
|
21
|
+
return
|
|
22
|
+
command = str(command).strip()
|
|
23
|
+
if not command:
|
|
24
|
+
return
|
|
25
|
+
if not command.startswith("/"):
|
|
26
|
+
command = "/" + command
|
|
27
|
+
title = str(title or command.lstrip("/") or command).strip()
|
|
28
|
+
detail = str(detail or title or command).strip()
|
|
29
|
+
items.append({"command": command, "title": title, "detail": detail})
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
from hermes_cli.commands import COMMANDS
|
|
33
|
+
for command, detail in COMMANDS.items():
|
|
34
|
+
add(command, command.lstrip("/"), detail)
|
|
35
|
+
except Exception:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
from agent.skill_commands import get_skill_commands
|
|
40
|
+
for command, info in get_skill_commands().items():
|
|
41
|
+
if not isinstance(info, dict):
|
|
42
|
+
info = {}
|
|
43
|
+
add(command, info.get("name") or str(command).lstrip("/"), info.get("description") or "Skill command")
|
|
44
|
+
except Exception:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
from hermes_cli.plugins import get_plugin_commands
|
|
49
|
+
for command, info in get_plugin_commands().items():
|
|
50
|
+
if not isinstance(info, dict):
|
|
51
|
+
info = {}
|
|
52
|
+
add(command, command, info.get("description") or "Plugin command")
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
print(json.dumps(items, ensure_ascii=False))
|
|
57
|
+
`;
|
|
58
|
+
const DEFAULT_HERMES_SLASH_COMMAND_SEARCH_LIMIT = 16;
|
|
59
|
+
const MAX_HERMES_SLASH_COMMAND_SEARCH_LIMIT = 50;
|
|
60
|
+
const MAX_HERMES_SLASH_COMMAND_SEARCH_OFFSET = 10_000;
|
|
61
|
+
let hermesSlashCommandCatalogCache;
|
|
62
|
+
export function collectHermesSlashCommandCatalog(runPython = runHermesPython) {
|
|
63
|
+
try {
|
|
64
|
+
return parseHermesSlashCommandCatalog(runPython(HERMES_SLASH_COMMAND_CATALOG_SCRIPT));
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
68
|
+
console.warn(`[hermes-relay] failed to load Hermes slash command catalog: ${message}`);
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function searchHermesSlashCommandCatalog(opts = {}) {
|
|
73
|
+
const query = normalizeHermesSlashCommandQuery(opts.query);
|
|
74
|
+
const limit = normalizeHermesSlashCommandSearchLimit(opts.limit);
|
|
75
|
+
const offset = normalizeHermesSlashCommandSearchOffset(opts.offset);
|
|
76
|
+
const catalog = [...(opts.collect ?? getCachedHermesSlashCommandCatalog)()];
|
|
77
|
+
const matches = catalog
|
|
78
|
+
.map((command, index) => {
|
|
79
|
+
const rank = hermesSlashCommandMatchRank(command, query);
|
|
80
|
+
return rank === undefined ? undefined : { command, index, rank };
|
|
81
|
+
})
|
|
82
|
+
.filter((entry) => entry !== undefined)
|
|
83
|
+
.sort((left, right) => {
|
|
84
|
+
if (left.rank !== right.rank)
|
|
85
|
+
return left.rank - right.rank;
|
|
86
|
+
if (left.index !== right.index)
|
|
87
|
+
return left.index - right.index;
|
|
88
|
+
return left.command.title.localeCompare(right.command.title);
|
|
89
|
+
});
|
|
90
|
+
const items = matches.slice(offset, offset + limit).map((entry) => entry.command);
|
|
91
|
+
const nextOffset = offset + items.length;
|
|
92
|
+
const hasMore = nextOffset < matches.length;
|
|
93
|
+
return {
|
|
94
|
+
items,
|
|
95
|
+
hasMore,
|
|
96
|
+
...(hasMore ? { nextOffset } : {}),
|
|
97
|
+
total: matches.length,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function buildHermesRelayHelloMessage(opts) {
|
|
101
|
+
return {
|
|
102
|
+
type: "hello",
|
|
103
|
+
platform: opts.platform,
|
|
104
|
+
agentVersion: opts.agentVersion,
|
|
105
|
+
capabilities: opts.capabilities,
|
|
106
|
+
...(opts.slashCommands && opts.slashCommands.length > 0 ? { slashCommands: opts.slashCommands } : {}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function parseHermesSlashCommandCatalog(rawOutput) {
|
|
110
|
+
const rawValue = JSON.parse(rawOutput);
|
|
111
|
+
if (!Array.isArray(rawValue)) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
const commands = [];
|
|
115
|
+
const seen = new Set();
|
|
116
|
+
for (const entry of rawValue) {
|
|
117
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const record = entry;
|
|
121
|
+
const command = normalizeHermesSlashCommandText(record.command ?? record.name ?? record.value ?? record.text);
|
|
122
|
+
if (!command) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const key = command.toLowerCase();
|
|
126
|
+
if (seen.has(key)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
seen.add(key);
|
|
130
|
+
const title = readHermesSlashCommandLabel(record.title ?? record.label ?? record.name) ?? command.replace(/^\//, "");
|
|
131
|
+
const detail = readHermesSlashCommandLabel(record.detail ?? record.description ?? record.summary) ?? title;
|
|
132
|
+
commands.push({
|
|
133
|
+
source: "Hermes",
|
|
134
|
+
command,
|
|
135
|
+
title,
|
|
136
|
+
detail,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return commands;
|
|
140
|
+
}
|
|
141
|
+
function normalizeHermesSlashCommandText(value) {
|
|
142
|
+
if (typeof value !== "string") {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
const trimmed = value.trim().replace(/\s+/g, " ");
|
|
146
|
+
if (!trimmed) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
150
|
+
}
|
|
151
|
+
function readHermesSlashCommandLabel(value) {
|
|
152
|
+
if (typeof value !== "string") {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
const trimmed = value.trim().replace(/\s+/g, " ");
|
|
156
|
+
return trimmed || undefined;
|
|
157
|
+
}
|
|
158
|
+
function getCachedHermesSlashCommandCatalog() {
|
|
159
|
+
if (!hermesSlashCommandCatalogCache) {
|
|
160
|
+
hermesSlashCommandCatalogCache = collectHermesSlashCommandCatalog();
|
|
161
|
+
}
|
|
162
|
+
return hermesSlashCommandCatalogCache;
|
|
163
|
+
}
|
|
164
|
+
function normalizeHermesSlashCommandQuery(value) {
|
|
165
|
+
if (typeof value !== "string") {
|
|
166
|
+
return "/";
|
|
167
|
+
}
|
|
168
|
+
const trimmed = value.trim().replace(/\s+/g, " ").toLowerCase();
|
|
169
|
+
if (!trimmed) {
|
|
170
|
+
return "/";
|
|
171
|
+
}
|
|
172
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
173
|
+
}
|
|
174
|
+
function normalizeHermesSlashCommandSearchLimit(value) {
|
|
175
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
176
|
+
return Math.max(1, Math.min(MAX_HERMES_SLASH_COMMAND_SEARCH_LIMIT, Math.trunc(value)));
|
|
177
|
+
}
|
|
178
|
+
if (typeof value === "string" && value.trim()) {
|
|
179
|
+
const parsed = Number(value);
|
|
180
|
+
if (Number.isFinite(parsed)) {
|
|
181
|
+
return Math.max(1, Math.min(MAX_HERMES_SLASH_COMMAND_SEARCH_LIMIT, Math.trunc(parsed)));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return DEFAULT_HERMES_SLASH_COMMAND_SEARCH_LIMIT;
|
|
185
|
+
}
|
|
186
|
+
function normalizeHermesSlashCommandSearchOffset(value) {
|
|
187
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
188
|
+
return Math.max(0, Math.min(MAX_HERMES_SLASH_COMMAND_SEARCH_OFFSET, Math.trunc(value)));
|
|
189
|
+
}
|
|
190
|
+
if (typeof value === "string" && value.trim()) {
|
|
191
|
+
const parsed = Number(value);
|
|
192
|
+
if (Number.isFinite(parsed)) {
|
|
193
|
+
return Math.max(0, Math.min(MAX_HERMES_SLASH_COMMAND_SEARCH_OFFSET, Math.trunc(parsed)));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
function hermesSlashCommandMatchRank(command, query) {
|
|
199
|
+
const normalizedCommand = command.command.trim().replace(/\s+/g, " ").toLowerCase();
|
|
200
|
+
if (!normalizedCommand) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
if (query === "/")
|
|
204
|
+
return 0;
|
|
205
|
+
if (normalizedCommand === query) {
|
|
206
|
+
return 0;
|
|
207
|
+
}
|
|
208
|
+
if (normalizedCommand.startsWith(query)) {
|
|
209
|
+
return 1;
|
|
210
|
+
}
|
|
211
|
+
if (query.startsWith(normalizedCommand)) {
|
|
212
|
+
return 2;
|
|
213
|
+
}
|
|
214
|
+
const compactCommand = compactSlashSearchText(normalizedCommand);
|
|
215
|
+
const compactQuery = compactSlashSearchText(query);
|
|
216
|
+
if (!compactQuery)
|
|
217
|
+
return 0;
|
|
218
|
+
if (compactCommand.includes(compactQuery)) {
|
|
219
|
+
return 3;
|
|
220
|
+
}
|
|
221
|
+
if (isSubsequence(compactQuery, compactCommand)) {
|
|
222
|
+
return 4;
|
|
223
|
+
}
|
|
224
|
+
const searchableText = [command.title, command.detail]
|
|
225
|
+
.map((value) => compactSlashSearchText(value))
|
|
226
|
+
.filter(Boolean)
|
|
227
|
+
.join(" ");
|
|
228
|
+
if (searchableText.includes(compactQuery)) {
|
|
229
|
+
return 5;
|
|
230
|
+
}
|
|
231
|
+
if (isSubsequence(compactQuery, searchableText)) {
|
|
232
|
+
return 6;
|
|
233
|
+
}
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
function compactSlashSearchText(value) {
|
|
237
|
+
return value
|
|
238
|
+
.trim()
|
|
239
|
+
.replace(/^\//, "")
|
|
240
|
+
.replace(/\s+/g, "")
|
|
241
|
+
.toLowerCase();
|
|
242
|
+
}
|
|
243
|
+
function isSubsequence(needle, haystack) {
|
|
244
|
+
if (!needle)
|
|
245
|
+
return true;
|
|
246
|
+
let needleIndex = 0;
|
|
247
|
+
for (const char of haystack) {
|
|
248
|
+
if (char === needle[needleIndex]) {
|
|
249
|
+
needleIndex += 1;
|
|
250
|
+
if (needleIndex === needle.length) {
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
function readHermesSlashCommandSearchParams(params) {
|
|
258
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
259
|
+
return {};
|
|
260
|
+
}
|
|
261
|
+
const record = params;
|
|
262
|
+
return {
|
|
263
|
+
query: record.query,
|
|
264
|
+
limit: record.limit,
|
|
265
|
+
offset: record.offset,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function readStringParam(params, key) {
|
|
269
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
const value = params[key];
|
|
273
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
274
|
+
}
|
|
275
|
+
export function rememberActiveHermesChatRun(activeChatRuns, run, params, requestId, controller) {
|
|
276
|
+
const entry = { controller, run };
|
|
277
|
+
for (const key of [run.runId, requestId, readStringParam(params, "idempotencyKey")]) {
|
|
278
|
+
if (key) {
|
|
279
|
+
activeChatRuns.set(key, entry);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function forgetActiveHermesChatRun(activeChatRuns, run) {
|
|
284
|
+
for (const [key, entry] of activeChatRuns) {
|
|
285
|
+
if (entry.run.runId === run.runId && entry.run.sessionKey === run.sessionKey) {
|
|
286
|
+
activeChatRuns.delete(key);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
export function resolveHermesAbortRun(params, activeChatRuns) {
|
|
291
|
+
const runId = readStringParam(params, "runId")
|
|
292
|
+
?? readStringParam(params, "run_id")
|
|
293
|
+
?? readStringParam(params, "idempotencyKey");
|
|
294
|
+
if (runId) {
|
|
295
|
+
return activeChatRuns.get(runId);
|
|
296
|
+
}
|
|
297
|
+
const sessionKey = readStringParam(params, "sessionKey") ?? readStringParam(params, "session_key");
|
|
298
|
+
if (sessionKey) {
|
|
299
|
+
return [...activeChatRuns.values()].find((entry) => entry.run.sessionKey === sessionKey);
|
|
300
|
+
}
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
export function resolveHermesChatPreferredRunId(params, voiceInputRun) {
|
|
304
|
+
return voiceInputRun?.runId
|
|
305
|
+
?? readStringParam(params, "runId")
|
|
306
|
+
?? readStringParam(params, "run_id")
|
|
307
|
+
?? readStringParam(params, "idempotencyKey");
|
|
308
|
+
}
|
|
309
|
+
export async function runHermesRelayManager(opts) {
|
|
310
|
+
const wsUrl = buildRelayUrl(opts.relayServerUrl, opts.gatewayId, opts.relaySecret);
|
|
311
|
+
const recentMobileFiles = new Map();
|
|
312
|
+
const sentArtifacts = new Map();
|
|
313
|
+
const activeChatRuns = new Map();
|
|
314
|
+
return new Promise((resolve) => {
|
|
315
|
+
let relayWs;
|
|
316
|
+
try {
|
|
317
|
+
relayWs = new WebSocket(wsUrl);
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
console.error("Failed to create Hermes relay WebSocket:", error);
|
|
321
|
+
resolve(true);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
bindRelayAbortSignal(relayWs, opts.signal);
|
|
325
|
+
const send = (message) => {
|
|
326
|
+
sendRelayJson(relayWs, message);
|
|
327
|
+
};
|
|
328
|
+
relayWs.on("open", () => {
|
|
329
|
+
console.log(`Connected to relay server (hermes gatewayId=${opts.gatewayId})`);
|
|
330
|
+
opts.onConnected?.();
|
|
331
|
+
const statusSnapshot = readHermesStatusSnapshot();
|
|
332
|
+
send(buildHermesRelayHelloMessage({
|
|
333
|
+
platform: `${process.platform} (Hermes)`,
|
|
334
|
+
agentVersion: "hermes",
|
|
335
|
+
capabilities: opts.capabilities ?? [...gatewayCapabilitiesForType("hermes"), "models"],
|
|
336
|
+
}));
|
|
337
|
+
send({ type: "gateway_connected" });
|
|
338
|
+
send({
|
|
339
|
+
type: "event",
|
|
340
|
+
event: "office",
|
|
341
|
+
payload: {
|
|
342
|
+
currentModel: statusSnapshot.currentModel,
|
|
343
|
+
provider: statusSnapshot.provider,
|
|
344
|
+
office: {
|
|
345
|
+
kind: "idle",
|
|
346
|
+
title: "Hermes Agent",
|
|
347
|
+
detail: opts.displayName ?? "Hermes gateway connected",
|
|
348
|
+
phase: "connected",
|
|
349
|
+
updatedAt: new Date().toISOString(),
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
void publishHermesUsageSnapshot(send);
|
|
354
|
+
});
|
|
355
|
+
relayWs.on("message", async (raw) => {
|
|
356
|
+
let requestId;
|
|
357
|
+
let methodForLog = "";
|
|
358
|
+
let acknowledgedChatRun;
|
|
359
|
+
let voiceInputRun;
|
|
360
|
+
try {
|
|
361
|
+
const msg = parseRelayFrame(raw);
|
|
362
|
+
if (!msg) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (msg.type === "heartbeat") {
|
|
366
|
+
send({ type: "heartbeat" });
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (msg.type === "hello") {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (msg.type === "event") {
|
|
373
|
+
if (msg.event === "file") {
|
|
374
|
+
await rememberMobileFileEvent(msg.payload, recentMobileFiles, opts);
|
|
375
|
+
}
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (msg.type !== "cmd" || !msg.method) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
requestId = msg.id;
|
|
382
|
+
methodForLog = msg.method;
|
|
383
|
+
if (msg.method === "chat.abort") {
|
|
384
|
+
const abortRun = resolveHermesAbortRun(msg.params, activeChatRuns);
|
|
385
|
+
abortRun?.controller.abort();
|
|
386
|
+
if (requestId) {
|
|
387
|
+
send({ type: "res", id: requestId, ok: true, payload: abortRun?.run });
|
|
388
|
+
}
|
|
389
|
+
if (abortRun) {
|
|
390
|
+
send({
|
|
391
|
+
type: "event",
|
|
392
|
+
event: "chat",
|
|
393
|
+
payload: {
|
|
394
|
+
runId: abortRun.run.runId,
|
|
395
|
+
sessionKey: abortRun.run.sessionKey,
|
|
396
|
+
state: "aborted",
|
|
397
|
+
role: "assistant",
|
|
398
|
+
message: {
|
|
399
|
+
role: "assistant",
|
|
400
|
+
content: [{ type: "text", text: "" }],
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (msg.method === "slash_commands.search") {
|
|
408
|
+
if (requestId) {
|
|
409
|
+
send({
|
|
410
|
+
type: "res",
|
|
411
|
+
id: requestId,
|
|
412
|
+
ok: true,
|
|
413
|
+
payload: searchHermesSlashCommandCatalog(readHermesSlashCommandSearchParams(msg.params)),
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const localResult = await handleHermesCommand(msg.method, msg.params, {
|
|
419
|
+
requestId,
|
|
420
|
+
gatewayId: opts.gatewayId,
|
|
421
|
+
publishEvent: (event) => send(event),
|
|
422
|
+
});
|
|
423
|
+
if (localResult !== null) {
|
|
424
|
+
if (requestId) {
|
|
425
|
+
send(localResult.ok
|
|
426
|
+
? { type: "res", id: requestId, ok: true, payload: localResult.payload }
|
|
427
|
+
: { type: "res", id: requestId, ok: false, error: { message: localResult.error } });
|
|
428
|
+
}
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (msg.method === "chat.voice.send" || msg.method === "hermes.voice.send") {
|
|
432
|
+
const voiceInput = await prepareHermesVoiceInputCommand(msg.params, { requestId });
|
|
433
|
+
voiceInputRun = voiceInput.run;
|
|
434
|
+
msg.params = voiceInput.params;
|
|
435
|
+
msg.method = voiceInput.method;
|
|
436
|
+
}
|
|
437
|
+
if (msg.method !== "chat.send" && msg.method !== "agent" && msg.method !== "hermes.chat.send") {
|
|
438
|
+
throw new Error(`Unsupported Hermes command: ${msg.method}`);
|
|
439
|
+
}
|
|
440
|
+
const requestedSessionKey = resolveHermesRelaySessionKey(msg.params);
|
|
441
|
+
const run = resolveMobileChatRun({
|
|
442
|
+
preferredRunId: resolveHermesChatPreferredRunId(msg.params, voiceInputRun),
|
|
443
|
+
requestId,
|
|
444
|
+
sessionKey: requestedSessionKey,
|
|
445
|
+
fallbackPrefix: "hermes",
|
|
446
|
+
});
|
|
447
|
+
const runId = run.runId;
|
|
448
|
+
const sessionKey = run.sessionKey;
|
|
449
|
+
const abortController = new AbortController();
|
|
450
|
+
rememberActiveHermesChatRun(activeChatRuns, run, msg.params, requestId, abortController);
|
|
451
|
+
if (requestId) {
|
|
452
|
+
acknowledgedChatRun = { runId, sessionKey };
|
|
453
|
+
send({ type: "res", id: requestId, ok: true, payload: acknowledgedChatRun });
|
|
454
|
+
}
|
|
455
|
+
const paramsWithFiles = await attachRecentMobileFiles(msg.params, recentMobileFiles, opts);
|
|
456
|
+
const chat = await runHermesChat(paramsWithFiles, {
|
|
457
|
+
requestId: runId,
|
|
458
|
+
gatewayId: opts.gatewayId,
|
|
459
|
+
abortSignal: abortController.signal,
|
|
460
|
+
publishEvent: (event) => {
|
|
461
|
+
send(event);
|
|
462
|
+
publishHermesOfficeSnapshot(send, event.event, event.payload);
|
|
463
|
+
},
|
|
464
|
+
});
|
|
465
|
+
forgetActiveHermesChatRun(activeChatRuns, run);
|
|
466
|
+
const finalChatPayload = buildMobileAssistantFinalPayload({
|
|
467
|
+
run: { runId, sessionKey: chat.sessionKey },
|
|
468
|
+
text: chat.output,
|
|
469
|
+
currentModel: chat.usage?.currentModel,
|
|
470
|
+
provider: chat.usage?.provider,
|
|
471
|
+
contextUsage: chat.usage?.contextUsage,
|
|
472
|
+
contextLimit: chat.usage?.contextLimit,
|
|
473
|
+
includeTimelineEvents: true,
|
|
474
|
+
});
|
|
475
|
+
send({
|
|
476
|
+
type: "event",
|
|
477
|
+
event: "chat",
|
|
478
|
+
payload: finalChatPayload,
|
|
479
|
+
});
|
|
480
|
+
publishHermesOfficeSnapshot(send, "chat", finalChatPayload);
|
|
481
|
+
for (const artifactPath of chat.artifactPaths) {
|
|
482
|
+
const artifactKey = artifactDeliveryKey(chat.sessionKey, artifactPath);
|
|
483
|
+
pruneSentArtifacts(sentArtifacts);
|
|
484
|
+
if (artifactKey && sentArtifacts.has(artifactKey)) {
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
const upload = await uploadFileToRelay(buildHermesArtifactUploadRequest({
|
|
488
|
+
artifactPath,
|
|
489
|
+
relayServerUrl: opts.relayServerUrl,
|
|
490
|
+
relaySecret: opts.relaySecret,
|
|
491
|
+
gatewayId: opts.gatewayId,
|
|
492
|
+
sessionKey: chat.sessionKey,
|
|
493
|
+
runId,
|
|
494
|
+
}));
|
|
495
|
+
const attachmentId = deriveAttachmentId({
|
|
496
|
+
sessionKey: chat.sessionKey,
|
|
497
|
+
name: upload.fileName,
|
|
498
|
+
mimeType: upload.mimeType,
|
|
499
|
+
size: upload.sizeBytes,
|
|
500
|
+
contentHash: upload.sha256,
|
|
501
|
+
});
|
|
502
|
+
send({
|
|
503
|
+
type: "event",
|
|
504
|
+
event: "chat",
|
|
505
|
+
payload: {
|
|
506
|
+
runId,
|
|
507
|
+
sessionKey: chat.sessionKey,
|
|
508
|
+
state: "attachment",
|
|
509
|
+
role: "assistant",
|
|
510
|
+
timelineEvents: [
|
|
511
|
+
buildAttachmentStateChangedEvent({
|
|
512
|
+
gatewayId: opts.gatewayId,
|
|
513
|
+
sessionKey: chat.sessionKey,
|
|
514
|
+
turnId: runId,
|
|
515
|
+
runId,
|
|
516
|
+
messageId: `assistant-${runId}`,
|
|
517
|
+
partId: derivePartId({
|
|
518
|
+
type: upload.mimeType.startsWith("image/")
|
|
519
|
+
? "image"
|
|
520
|
+
: upload.mimeType.startsWith("audio/")
|
|
521
|
+
? "voice"
|
|
522
|
+
: "file",
|
|
523
|
+
index: 0,
|
|
524
|
+
}),
|
|
525
|
+
attachment: {
|
|
526
|
+
attachmentId,
|
|
527
|
+
state: "available",
|
|
528
|
+
fileId: upload.fileId,
|
|
529
|
+
name: upload.fileName,
|
|
530
|
+
mimeType: upload.mimeType,
|
|
531
|
+
sizeBytes: upload.sizeBytes,
|
|
532
|
+
url: upload.downloadUrl,
|
|
533
|
+
expiresAt: upload.expiresAt,
|
|
534
|
+
sha256: upload.sha256,
|
|
535
|
+
},
|
|
536
|
+
}),
|
|
537
|
+
],
|
|
538
|
+
},
|
|
539
|
+
});
|
|
540
|
+
if (artifactKey) {
|
|
541
|
+
sentArtifacts.set(artifactKey, Date.now());
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (requestId && !acknowledgedChatRun) {
|
|
545
|
+
send({ type: "res", id: requestId, ok: true, payload: { runId, sessionKey: chat.sessionKey } });
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
550
|
+
if (message === "hermes_chat_aborted") {
|
|
551
|
+
if (acknowledgedChatRun) {
|
|
552
|
+
const abortedPayload = buildMobileAssistantAbortedPayload({
|
|
553
|
+
run: acknowledgedChatRun,
|
|
554
|
+
includeTimelineEvents: true,
|
|
555
|
+
});
|
|
556
|
+
send({
|
|
557
|
+
type: "event",
|
|
558
|
+
event: "chat",
|
|
559
|
+
payload: abortedPayload,
|
|
560
|
+
});
|
|
561
|
+
publishHermesOfficeSnapshot(send, "chat", abortedPayload);
|
|
562
|
+
forgetActiveHermesChatRun(activeChatRuns, acknowledgedChatRun);
|
|
563
|
+
}
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (acknowledgedChatRun) {
|
|
567
|
+
forgetActiveHermesChatRun(activeChatRuns, acknowledgedChatRun);
|
|
568
|
+
}
|
|
569
|
+
console.error(`[hermes-relay] cmd failed method=${methodForLog || "(unknown)"} id=${requestId ?? "(no-id)"}: ${message}`);
|
|
570
|
+
const setupMessage = voiceInputSetupMessage(error);
|
|
571
|
+
const chatRun = setupMessage ? (acknowledgedChatRun ?? voiceInputRun) : acknowledgedChatRun;
|
|
572
|
+
if (chatRun) {
|
|
573
|
+
const errorPayload = buildMobileAssistantErrorPayload({
|
|
574
|
+
run: chatRun,
|
|
575
|
+
errorMessage: setupMessage ?? message,
|
|
576
|
+
includeTimelineEvents: true,
|
|
577
|
+
});
|
|
578
|
+
send({
|
|
579
|
+
type: "event",
|
|
580
|
+
event: "chat",
|
|
581
|
+
payload: errorPayload,
|
|
582
|
+
});
|
|
583
|
+
publishHermesOfficeSnapshot(send, "chat", errorPayload);
|
|
584
|
+
if (setupMessage && requestId && !acknowledgedChatRun) {
|
|
585
|
+
send({ type: "res", id: requestId, ok: true, payload: chatRun });
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
else if (requestId) {
|
|
589
|
+
send({ type: "res", id: requestId, ok: false, error: { message } });
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
relayWs.on("close", (code, reason) => {
|
|
594
|
+
console.log(`Hermes relay connection closed: ${code} ${reason.toString()}`);
|
|
595
|
+
opts.onDisconnected?.();
|
|
596
|
+
activeChatRuns.forEach((entry) => entry.controller.abort());
|
|
597
|
+
activeChatRuns.clear();
|
|
598
|
+
resolve(shouldRetryRelayClose(code, opts.signal));
|
|
599
|
+
});
|
|
600
|
+
relayWs.on("error", (error) => {
|
|
601
|
+
console.error("Hermes relay WebSocket error:", error.message);
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
export function buildHermesArtifactUploadRequest(params) {
|
|
606
|
+
return {
|
|
607
|
+
relayServerUrl: params.relayServerUrl,
|
|
608
|
+
relaySecret: params.relaySecret,
|
|
609
|
+
gatewayId: params.gatewayId,
|
|
610
|
+
sessionKey: params.sessionKey,
|
|
611
|
+
filePath: params.artifactPath,
|
|
612
|
+
sourceRunId: params.runId,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
export function shouldPublishHermesOfficeSnapshot(eventName, payload) {
|
|
616
|
+
if (eventName !== "chat" && eventName !== "agent" && eventName !== "context_usage") {
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
const state = typeof payload.state === "string"
|
|
623
|
+
? payload.state.trim().toLowerCase()
|
|
624
|
+
: "";
|
|
625
|
+
return state !== "delta";
|
|
626
|
+
}
|
|
627
|
+
function publishHermesOfficeSnapshot(send, eventName, payload) {
|
|
628
|
+
if (!shouldPublishHermesOfficeSnapshot(eventName, payload)) {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
if (!eventName) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
const officePayload = buildOfficeEventPayload(eventName, payload, () => new Date().toISOString());
|
|
635
|
+
if (!officePayload) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
send({ type: "event", event: "office", payload: officePayload });
|
|
639
|
+
}
|
|
640
|
+
function artifactDeliveryKey(sessionKey, filePath) {
|
|
641
|
+
try {
|
|
642
|
+
const stat = statSync(filePath);
|
|
643
|
+
return [
|
|
644
|
+
sessionKey,
|
|
645
|
+
filePath,
|
|
646
|
+
stat.size,
|
|
647
|
+
Math.floor(stat.mtimeMs),
|
|
648
|
+
].join("\0");
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
return undefined;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function pruneSentArtifacts(sentArtifacts) {
|
|
655
|
+
const cutoff = Date.now() - 30 * 60_000;
|
|
656
|
+
for (const [key, timestamp] of sentArtifacts) {
|
|
657
|
+
if (timestamp < cutoff) {
|
|
658
|
+
sentArtifacts.delete(key);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
async function publishHermesUsageSnapshot(send) {
|
|
663
|
+
try {
|
|
664
|
+
const snapshot = await collectHermesUsageSnapshot();
|
|
665
|
+
if (!snapshot.currentModel && snapshot.contextUsage === undefined && snapshot.contextLimit === undefined) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
send({
|
|
669
|
+
type: "event",
|
|
670
|
+
event: "context_usage",
|
|
671
|
+
payload: {
|
|
672
|
+
currentModel: snapshot.currentModel,
|
|
673
|
+
provider: snapshot.provider,
|
|
674
|
+
contextUsage: snapshot.contextUsage,
|
|
675
|
+
contextLimit: snapshot.contextLimit,
|
|
676
|
+
hermesSessionId: snapshot.hermesSessionId,
|
|
677
|
+
updatedAt: new Date().toISOString(),
|
|
678
|
+
},
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
683
|
+
console.warn(`[hermes-relay] usage snapshot failed: ${message}`);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
async function rememberMobileFileEvent(payload, recentMobileFiles, opts) {
|
|
687
|
+
const block = extractFileBlock(payload);
|
|
688
|
+
if (!block || block.origin !== "mobile") {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
const sessionKey = typeof block.sessionKey === "string" && block.sessionKey.trim() ? block.sessionKey.trim() : "main";
|
|
692
|
+
const downloadUrl = typeof block.downloadUrl === "string" ? block.downloadUrl : "";
|
|
693
|
+
const fileName = typeof block.fileName === "string" ? block.fileName : typeof block.name === "string" ? block.name : "attachment";
|
|
694
|
+
if (!downloadUrl) {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
const content = await downloadFileAsBase64(downloadUrl, opts);
|
|
698
|
+
const attachments = recentMobileFiles.get(sessionKey) ?? [];
|
|
699
|
+
attachments.push({
|
|
700
|
+
fileName,
|
|
701
|
+
mimeType: typeof block.mimeType === "string" ? block.mimeType : "application/octet-stream",
|
|
702
|
+
content,
|
|
703
|
+
});
|
|
704
|
+
recentMobileFiles.set(sessionKey, attachments.slice(-12));
|
|
705
|
+
}
|
|
706
|
+
async function attachRecentMobileFiles(params, recentMobileFiles, opts) {
|
|
707
|
+
const record = params && typeof params === "object" && !Array.isArray(params)
|
|
708
|
+
? { ...params }
|
|
709
|
+
: {};
|
|
710
|
+
const sessionKey = typeof record.sessionKey === "string" && record.sessionKey.trim() ? record.sessionKey.trim() : "main";
|
|
711
|
+
const pending = recentMobileFiles.get(sessionKey) ?? [];
|
|
712
|
+
if (pending.length === 0) {
|
|
713
|
+
return params;
|
|
714
|
+
}
|
|
715
|
+
const existing = Array.isArray(record.attachments) ? record.attachments : [];
|
|
716
|
+
recentMobileFiles.delete(sessionKey);
|
|
717
|
+
if (existing.length > 0) {
|
|
718
|
+
return record;
|
|
719
|
+
}
|
|
720
|
+
return {
|
|
721
|
+
...record,
|
|
722
|
+
attachments: pending,
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
function resolveHermesRelaySessionKey(params) {
|
|
726
|
+
return resolveHermesVoiceInputSessionKey(params);
|
|
727
|
+
}
|
|
728
|
+
function extractFileBlock(payload) {
|
|
729
|
+
const record = payload && typeof payload === "object" && !Array.isArray(payload)
|
|
730
|
+
? payload
|
|
731
|
+
: undefined;
|
|
732
|
+
const content = record?.message && typeof record.message === "object" && !Array.isArray(record.message)
|
|
733
|
+
? record.message.content
|
|
734
|
+
: undefined;
|
|
735
|
+
const blocks = Array.isArray(content) ? content : [];
|
|
736
|
+
return blocks.find((block) => {
|
|
737
|
+
if (!block || typeof block !== "object" || Array.isArray(block)) {
|
|
738
|
+
return false;
|
|
739
|
+
}
|
|
740
|
+
const type = block.type;
|
|
741
|
+
return type === "file" || type === "voice";
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
async function downloadFileAsBase64(downloadUrl, opts) {
|
|
745
|
+
const url = new URL(downloadUrl, opts.relayServerUrl);
|
|
746
|
+
url.searchParams.set("secret", opts.relaySecret);
|
|
747
|
+
const response = await fetch(url);
|
|
748
|
+
if (!response.ok) {
|
|
749
|
+
throw new Error(`file_download_failed:${response.status}`);
|
|
750
|
+
}
|
|
751
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
752
|
+
return buffer.toString("base64");
|
|
753
|
+
}
|
|
754
|
+
//# sourceMappingURL=hermes-relay-manager.js.map
|