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.
Files changed (216) hide show
  1. package/README.md +142 -34
  2. package/README.zh-CN.md +152 -44
  3. package/dist/commands/install.js +10 -4
  4. package/dist/commands/install.js.map +1 -1
  5. package/dist/commands/pair.d.ts +6 -0
  6. package/dist/commands/pair.js +78 -10
  7. package/dist/commands/pair.js.map +1 -1
  8. package/dist/commands/run.js +14 -17
  9. package/dist/commands/run.js.map +1 -1
  10. package/dist/commands/send-file.d.ts +3 -24
  11. package/dist/commands/send-file.js +22 -449
  12. package/dist/commands/send-file.js.map +1 -1
  13. package/dist/commands/status.d.ts +13 -1
  14. package/dist/commands/status.js +58 -15
  15. package/dist/commands/status.js.map +1 -1
  16. package/dist/config/config.d.ts +6 -11
  17. package/dist/config/config.js +19 -48
  18. package/dist/config/config.js.map +1 -1
  19. package/dist/config/env.d.ts +1 -1
  20. package/dist/config/env.js +4 -5
  21. package/dist/config/env.js.map +1 -1
  22. package/dist/config/profile.d.ts +11 -0
  23. package/dist/config/profile.js +80 -0
  24. package/dist/config/profile.js.map +1 -0
  25. package/dist/core/command-types.d.ts +18 -0
  26. package/dist/core/command-types.js +2 -0
  27. package/dist/core/command-types.js.map +1 -0
  28. package/dist/core/relay/attachment-staging.js.map +1 -0
  29. package/dist/{relay → core/relay}/chat-payload.js +17 -3
  30. package/dist/core/relay/chat-payload.js.map +1 -0
  31. package/dist/{commands/send-file-utils.js → core/relay/file-upload-utils.js} +1 -1
  32. package/dist/core/relay/file-upload-utils.js.map +1 -0
  33. package/dist/core/relay/file-upload.d.ts +40 -0
  34. package/dist/core/relay/file-upload.js +401 -0
  35. package/dist/core/relay/file-upload.js.map +1 -0
  36. package/dist/core/relay/mobile-chat-run-bridge.d.ts +201 -0
  37. package/dist/core/relay/mobile-chat-run-bridge.js +192 -0
  38. package/dist/core/relay/mobile-chat-run-bridge.js.map +1 -0
  39. package/dist/core/relay/office-payload.js.map +1 -0
  40. package/dist/core/relay/reconnect.js.map +1 -0
  41. package/dist/core/relay/relay-server-connection.d.ts +6 -0
  42. package/dist/core/relay/relay-server-connection.js +32 -0
  43. package/dist/core/relay/relay-server-connection.js.map +1 -0
  44. package/dist/core/relay/slash-command-types.d.ts +7 -0
  45. package/dist/core/relay/slash-command-types.js +2 -0
  46. package/dist/core/relay/slash-command-types.js.map +1 -0
  47. package/dist/core/relay/timeline-event-builder.d.ts +86 -0
  48. package/dist/core/relay/timeline-event-builder.js +199 -0
  49. package/dist/core/relay/timeline-event-builder.js.map +1 -0
  50. package/dist/core/relay/timeline-event-log.d.ts +81 -0
  51. package/dist/core/relay/timeline-event-log.js +254 -0
  52. package/dist/core/relay/timeline-event-log.js.map +1 -0
  53. package/dist/core/relay/voice-input.d.ts +11 -0
  54. package/dist/core/relay/voice-input.js +120 -0
  55. package/dist/core/relay/voice-input.js.map +1 -0
  56. package/dist/gateway-profiles.d.ts +8 -0
  57. package/dist/gateway-profiles.js +24 -0
  58. package/dist/gateway-profiles.js.map +1 -0
  59. package/dist/hermes/hermes-relay-manager.d.ts +59 -0
  60. package/dist/hermes/hermes-relay-manager.js +754 -0
  61. package/dist/hermes/hermes-relay-manager.js.map +1 -0
  62. package/dist/hermes/hermes-runtime.d.ts +9 -0
  63. package/dist/hermes/hermes-runtime.js +9 -0
  64. package/dist/hermes/hermes-runtime.js.map +1 -0
  65. package/dist/hermes/hermes-session-store.d.ts +15 -0
  66. package/dist/hermes/hermes-session-store.js +174 -0
  67. package/dist/hermes/hermes-session-store.js.map +1 -0
  68. package/dist/hermes/hermes-voice-input.d.ts +13 -0
  69. package/dist/hermes/hermes-voice-input.js +27 -0
  70. package/dist/hermes/hermes-voice-input.js.map +1 -0
  71. package/dist/hermes/runtime/hermes-runtime-artifacts.d.ts +3 -0
  72. package/dist/hermes/runtime/hermes-runtime-artifacts.js +65 -0
  73. package/dist/hermes/runtime/hermes-runtime-artifacts.js.map +1 -0
  74. package/dist/hermes/runtime/hermes-runtime-backups.d.ts +5 -0
  75. package/dist/hermes/runtime/hermes-runtime-backups.js +128 -0
  76. package/dist/hermes/runtime/hermes-runtime-backups.js.map +1 -0
  77. package/dist/hermes/runtime/hermes-runtime-chat.d.ts +41 -0
  78. package/dist/hermes/runtime/hermes-runtime-chat.js +723 -0
  79. package/dist/hermes/runtime/hermes-runtime-chat.js.map +1 -0
  80. package/dist/hermes/runtime/hermes-runtime-command-router.d.ts +2 -0
  81. package/dist/hermes/runtime/hermes-runtime-command-router.js +96 -0
  82. package/dist/hermes/runtime/hermes-runtime-command-router.js.map +1 -0
  83. package/dist/hermes/runtime/hermes-runtime-command-utils.d.ts +3 -0
  84. package/dist/hermes/runtime/hermes-runtime-command-utils.js +19 -0
  85. package/dist/hermes/runtime/hermes-runtime-command-utils.js.map +1 -0
  86. package/dist/hermes/runtime/hermes-runtime-cron.d.ts +11 -0
  87. package/dist/hermes/runtime/hermes-runtime-cron.js +301 -0
  88. package/dist/hermes/runtime/hermes-runtime-cron.js.map +1 -0
  89. package/dist/hermes/runtime/hermes-runtime-history.d.ts +2 -0
  90. package/dist/hermes/runtime/hermes-runtime-history.js +249 -0
  91. package/dist/hermes/runtime/hermes-runtime-history.js.map +1 -0
  92. package/dist/hermes/runtime/hermes-runtime-lifecycle.d.ts +9 -0
  93. package/dist/hermes/runtime/hermes-runtime-lifecycle.js +111 -0
  94. package/dist/hermes/runtime/hermes-runtime-lifecycle.js.map +1 -0
  95. package/dist/hermes/runtime/hermes-runtime-models.d.ts +29 -0
  96. package/dist/hermes/runtime/hermes-runtime-models.js +125 -0
  97. package/dist/hermes/runtime/hermes-runtime-models.js.map +1 -0
  98. package/dist/hermes/runtime/hermes-runtime-process.d.ts +24 -0
  99. package/dist/hermes/runtime/hermes-runtime-process.js +147 -0
  100. package/dist/hermes/runtime/hermes-runtime-process.js.map +1 -0
  101. package/dist/hermes/runtime/hermes-runtime-sessions.d.ts +5 -0
  102. package/dist/hermes/runtime/hermes-runtime-sessions.js +88 -0
  103. package/dist/hermes/runtime/hermes-runtime-sessions.js.map +1 -0
  104. package/dist/hermes/runtime/hermes-runtime-skills.d.ts +13 -0
  105. package/dist/hermes/runtime/hermes-runtime-skills.js +322 -0
  106. package/dist/hermes/runtime/hermes-runtime-skills.js.map +1 -0
  107. package/dist/hermes/runtime/hermes-runtime-types.d.ts +19 -0
  108. package/dist/hermes/runtime/hermes-runtime-types.js +2 -0
  109. package/dist/hermes/runtime/hermes-runtime-types.js.map +1 -0
  110. package/dist/hermes/runtime/hermes-runtime-usage.d.ts +22 -0
  111. package/dist/hermes/runtime/hermes-runtime-usage.js +211 -0
  112. package/dist/hermes/runtime/hermes-runtime-usage.js.map +1 -0
  113. package/dist/hermes/runtime/hermes-runtime-values.d.ts +14 -0
  114. package/dist/hermes/runtime/hermes-runtime-values.js +105 -0
  115. package/dist/hermes/runtime/hermes-runtime-values.js.map +1 -0
  116. package/dist/i18n/index.js +4 -0
  117. package/dist/i18n/index.js.map +1 -1
  118. package/dist/index.js +115 -9
  119. package/dist/index.js.map +1 -1
  120. package/dist/openclaw/backups/backup-manager.js.map +1 -0
  121. package/dist/{commands → openclaw/config}/provider-config.js +1 -1
  122. package/dist/openclaw/config/provider-config.js.map +1 -0
  123. package/dist/openclaw/config/provider-registry.js.map +1 -0
  124. package/dist/{relay → openclaw}/gateway-client.js +4 -3
  125. package/dist/openclaw/gateway-client.js.map +1 -0
  126. package/dist/{commands → openclaw/handlers}/local-handlers.d.ts +1 -1
  127. package/dist/{commands → openclaw/handlers}/local-handlers.js +2 -30
  128. package/dist/openclaw/handlers/local-handlers.js.map +1 -0
  129. package/dist/{commands → openclaw/handlers}/provider-handlers.d.ts +1 -1
  130. package/dist/{commands → openclaw/handlers}/provider-handlers.js +3 -3
  131. package/dist/openclaw/handlers/provider-handlers.js.map +1 -0
  132. package/dist/openclaw/relay/chat-history.d.ts +51 -0
  133. package/dist/openclaw/relay/chat-history.js +347 -0
  134. package/dist/openclaw/relay/chat-history.js.map +1 -0
  135. package/dist/{relay → openclaw/relay}/chat-send-attachments.js +12 -1
  136. package/dist/openclaw/relay/chat-send-attachments.js.map +1 -0
  137. package/dist/openclaw/relay/openclaw-voice-input.d.ts +13 -0
  138. package/dist/openclaw/relay/openclaw-voice-input.js +18 -0
  139. package/dist/openclaw/relay/openclaw-voice-input.js.map +1 -0
  140. package/dist/openclaw/relay/outgoing-media-relay.d.ts +11 -0
  141. package/dist/openclaw/relay/outgoing-media-relay.js +120 -0
  142. package/dist/openclaw/relay/outgoing-media-relay.js.map +1 -0
  143. package/dist/{relay → openclaw/relay}/session-context.d.ts +6 -0
  144. package/dist/{relay → openclaw/relay}/session-context.js +53 -3
  145. package/dist/openclaw/relay/session-context.js.map +1 -0
  146. package/dist/openclaw/relay/slash-command-catalog.d.ts +2 -0
  147. package/dist/{relay → openclaw/relay}/slash-command-catalog.generated.d.ts +0 -5
  148. package/dist/{relay → openclaw/relay}/slash-command-catalog.generated.js +0 -6
  149. package/dist/openclaw/relay/slash-command-catalog.generated.js.map +1 -0
  150. package/dist/openclaw/relay/slash-command-catalog.js.map +1 -0
  151. package/dist/openclaw/relay/transcript-dedupe.d.ts +21 -0
  152. package/dist/openclaw/relay/transcript-dedupe.js +211 -0
  153. package/dist/openclaw/relay/transcript-dedupe.js.map +1 -0
  154. package/dist/{relay → openclaw}/relay-manager.d.ts +1 -4
  155. package/dist/openclaw/relay-manager.js +787 -0
  156. package/dist/openclaw/relay-manager.js.map +1 -0
  157. package/dist/{commands → openclaw/runtime}/local-runtime.d.ts +1 -17
  158. package/dist/openclaw/runtime/local-runtime.js.map +1 -0
  159. package/dist/openclaw/session-store.d.ts +1 -0
  160. package/dist/openclaw/session-store.js +70 -0
  161. package/dist/openclaw/session-store.js.map +1 -0
  162. package/dist/platform/service-manager-common.d.ts +5 -2
  163. package/dist/platform/service-manager-common.js +18 -7
  164. package/dist/platform/service-manager-common.js.map +1 -1
  165. package/dist/platform/service-manager.d.ts +16 -5
  166. package/dist/platform/service-manager.js +85 -50
  167. package/dist/platform/service-manager.js.map +1 -1
  168. package/dist/runtime-adapters.d.ts +19 -0
  169. package/dist/runtime-adapters.js +39 -0
  170. package/dist/runtime-adapters.js.map +1 -0
  171. package/package.json +3 -3
  172. package/dist/commands/backup-manager.js.map +0 -1
  173. package/dist/commands/local-handlers.js.map +0 -1
  174. package/dist/commands/local-runtime.js.map +0 -1
  175. package/dist/commands/provider-config.js.map +0 -1
  176. package/dist/commands/provider-handlers.js.map +0 -1
  177. package/dist/commands/provider-registry.js.map +0 -1
  178. package/dist/commands/send-file-utils.js.map +0 -1
  179. package/dist/commands/voice-reply.d.ts +0 -39
  180. package/dist/commands/voice-reply.js +0 -329
  181. package/dist/commands/voice-reply.js.map +0 -1
  182. package/dist/relay/attachment-staging.js.map +0 -1
  183. package/dist/relay/chat-history.d.ts +0 -29
  184. package/dist/relay/chat-history.js +0 -69
  185. package/dist/relay/chat-history.js.map +0 -1
  186. package/dist/relay/chat-payload.js.map +0 -1
  187. package/dist/relay/chat-send-attachments.js.map +0 -1
  188. package/dist/relay/gateway-client.js.map +0 -1
  189. package/dist/relay/office-payload.js.map +0 -1
  190. package/dist/relay/reconnect.js.map +0 -1
  191. package/dist/relay/relay-manager.js +0 -644
  192. package/dist/relay/relay-manager.js.map +0 -1
  193. package/dist/relay/session-context.js.map +0 -1
  194. package/dist/relay/slash-command-catalog.d.ts +0 -8
  195. package/dist/relay/slash-command-catalog.generated.js.map +0 -1
  196. package/dist/relay/slash-command-catalog.js.map +0 -1
  197. package/dist/relay/voice-reply-preference.d.ts +0 -25
  198. package/dist/relay/voice-reply-preference.js +0 -103
  199. package/dist/relay/voice-reply-preference.js.map +0 -1
  200. /package/dist/{relay → core/relay}/attachment-staging.d.ts +0 -0
  201. /package/dist/{relay → core/relay}/attachment-staging.js +0 -0
  202. /package/dist/{relay → core/relay}/chat-payload.d.ts +0 -0
  203. /package/dist/{commands/send-file-utils.d.ts → core/relay/file-upload-utils.d.ts} +0 -0
  204. /package/dist/{relay → core/relay}/office-payload.d.ts +0 -0
  205. /package/dist/{relay → core/relay}/office-payload.js +0 -0
  206. /package/dist/{relay → core/relay}/reconnect.d.ts +0 -0
  207. /package/dist/{relay → core/relay}/reconnect.js +0 -0
  208. /package/dist/{commands → openclaw/backups}/backup-manager.d.ts +0 -0
  209. /package/dist/{commands → openclaw/backups}/backup-manager.js +0 -0
  210. /package/dist/{commands → openclaw/config}/provider-config.d.ts +0 -0
  211. /package/dist/{commands → openclaw/config}/provider-registry.d.ts +0 -0
  212. /package/dist/{commands → openclaw/config}/provider-registry.js +0 -0
  213. /package/dist/{relay → openclaw}/gateway-client.d.ts +0 -0
  214. /package/dist/{relay → openclaw/relay}/chat-send-attachments.d.ts +0 -0
  215. /package/dist/{relay → openclaw/relay}/slash-command-catalog.js +0 -0
  216. /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