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,723 @@
1
+ import { randomUUID } from "crypto";
2
+ import { readFileSync, statSync } from "fs";
3
+ import { mkdir, writeFile } from "fs/promises";
4
+ import { join } from "path";
5
+ import { spawn } from "child_process";
6
+ import { buildMobileAssistantDeltaPayload, } from "../../core/relay/mobile-chat-run-bridge.js";
7
+ import { buildMessagePartDeltaEvent, buildToolInvocationUpdatedEvent, } from "../../core/relay/timeline-event-builder.js";
8
+ import { forgetHermesSession, getMappedHermesSessionId, rememberHermesSession, } from "../hermes-session-store.js";
9
+ import { extractDeliverablePaths } from "./hermes-runtime-artifacts.js";
10
+ import { CHAT_TIMEOUT_MS, CLAWCONNECT_MOBILE_BRIDGE_HINT, HERMES_AGENT_LOG_FILE, HERMES_INBOX_DIR, SUBPROCESS_ENV, resolveHermesBin, isHermesCommandDeniedTimeoutLine, isHermesMissingSessionError, runHermes, runHermesPython, sanitizeHermesChatOutput, stripAnsi, stripHermesSessionResumeNotices, } from "./hermes-runtime-process.js";
11
+ import { collectHermesUsageSnapshot, listHermesSessions, readHermesStatusSnapshot } from "./hermes-runtime-usage.js";
12
+ import { compactStringArray, sanitizeFileName } from "./hermes-runtime-values.js";
13
+ const HERMES_ASSISTANT_DELTA_FLUSH_MS = 120;
14
+ const HERMES_ASSISTANT_DELTA_MAX_BYTES = 4096;
15
+ const HERMES_COMMAND_DENIED_TIMEOUT_MESSAGE = "Timeout – denying command";
16
+ const HERMES_SLASH_COMMAND_SCRIPT = String.raw `
17
+ import contextlib
18
+ import io
19
+ import json
20
+ import os
21
+ import sys
22
+
23
+ from rich.console import Console
24
+
25
+ import cli as cli_mod
26
+ from cli import HermesCLI
27
+
28
+ command = os.environ.get("CLAWCONNECT_HERMES_SLASH_COMMAND", "").strip()
29
+ resume = os.environ.get("CLAWCONNECT_HERMES_SLASH_RESUME", "").strip() or None
30
+ if command and not command.startswith("/"):
31
+ command = "/" + command
32
+
33
+ with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
34
+ cli = HermesCLI(model=None, compact=True, resume=resume, verbose=False)
35
+
36
+ buf = io.StringIO()
37
+ cli.console = Console(file=buf, force_terminal=True, width=120)
38
+
39
+ def approve_once(prompt=""):
40
+ if prompt:
41
+ print(prompt, end="")
42
+ return "1"
43
+
44
+ def approve_once_modal(*args, **kwargs):
45
+ return "once"
46
+
47
+ try:
48
+ cli._prompt_text_input = approve_once
49
+ cli._prompt_text_input_modal = approve_once_modal
50
+ except Exception:
51
+ pass
52
+
53
+ old_cprint = getattr(cli_mod, "_cprint", None)
54
+ if old_cprint is not None:
55
+ cli_mod._cprint = lambda text: print(text)
56
+
57
+ try:
58
+ with contextlib.redirect_stdout(buf), contextlib.redirect_stderr(buf):
59
+ keep_going = cli.process_command(command)
60
+ pending_inputs = []
61
+ pending_queue = getattr(cli, "_pending_input", None)
62
+ if pending_queue is not None:
63
+ while True:
64
+ try:
65
+ pending_inputs.append(pending_queue.get_nowait())
66
+ except Exception:
67
+ break
68
+ payload = {
69
+ "ok": True,
70
+ "output": buf.getvalue().rstrip(),
71
+ "sessionId": getattr(cli, "session_id", None),
72
+ "keepGoing": bool(keep_going),
73
+ "pendingInputs": pending_inputs,
74
+ }
75
+ except Exception as exc:
76
+ payload = {
77
+ "ok": False,
78
+ "output": buf.getvalue().rstrip(),
79
+ "error": str(exc),
80
+ "sessionId": getattr(cli, "session_id", None),
81
+ }
82
+ finally:
83
+ if old_cprint is not None:
84
+ cli_mod._cprint = old_cprint
85
+
86
+ sys.stdout.write(json.dumps(payload, ensure_ascii=False))
87
+ `;
88
+ export async function runHermesChat(params, context = {}) {
89
+ const record = params && typeof params === "object" && !Array.isArray(params)
90
+ ? params
91
+ : {};
92
+ const rawMessage = typeof record.message === "string" ? record.message : "";
93
+ const sessionKey = typeof record.sessionKey === "string" && record.sessionKey.trim().length > 0
94
+ ? record.sessionKey.trim()
95
+ : "main";
96
+ if (isHermesSlashCommandMessage(rawMessage)) {
97
+ return await runHermesSlashCommand({ message: rawMessage, sessionKey, hermesSessionId: record.hermesSessionId });
98
+ }
99
+ const message = await prepareHermesMessage(rawMessage, record.attachments, sessionKey);
100
+ if (!message.trim()) {
101
+ throw new Error("message_required");
102
+ }
103
+ const explicitResume = typeof record.hermesSessionId === "string" && record.hermesSessionId.trim().length > 0
104
+ ? record.hermesSessionId.trim()
105
+ : undefined;
106
+ const mappedResume = explicitResume ? undefined : await getMappedHermesSessionId(sessionKey);
107
+ let resume = explicitResume ?? mappedResume;
108
+ const beforeSessions = await listHermesSessions();
109
+ let rawOutput;
110
+ try {
111
+ rawOutput = await runHermesChatOnce({ message, sessionKey, resume, context });
112
+ }
113
+ catch (error) {
114
+ if (!mappedResume || !isHermesMissingSessionError(error)) {
115
+ throw error;
116
+ }
117
+ await forgetHermesSession(sessionKey, mappedResume);
118
+ resume = undefined;
119
+ rawOutput = await runHermesChatOnce({ message, sessionKey, context });
120
+ }
121
+ const output = sanitizeHermesChatOutput(rawOutput).trim();
122
+ const sessions = await listHermesSessions();
123
+ const mappedSession = selectHermesSessionForCompletedChat(sessions, {
124
+ beforeSessions,
125
+ resume,
126
+ userMessage: rawMessage,
127
+ });
128
+ if (mappedSession) {
129
+ await rememberHermesSession(sessionKey, mappedSession);
130
+ }
131
+ const usage = mappedSession?.hermesSessionId
132
+ ? await collectHermesUsageSnapshot(mappedSession.hermesSessionId)
133
+ : readHermesStatusSnapshot();
134
+ return {
135
+ output,
136
+ sessionKey,
137
+ artifactPaths: extractDeliverablePaths(output, { userMessage: rawMessage }),
138
+ usage,
139
+ };
140
+ }
141
+ async function runHermesChatOnce(params) {
142
+ const args = ["chat", "--query", params.message, "--quiet", "--source", "pocketclaw", "--yolo"];
143
+ if (params.resume) {
144
+ args.push("--resume", params.resume);
145
+ }
146
+ return params.context.publishEvent
147
+ ? await runHermesChatStreaming(args, params.sessionKey, params.context)
148
+ : runHermes(args, CHAT_TIMEOUT_MS);
149
+ }
150
+ export function isHermesSlashCommandMessage(message) {
151
+ return /^\/[A-Za-z0-9][\w-]*(?:\s|$)/.test(message.trim());
152
+ }
153
+ export function selectHermesSessionForCompletedChat(sessions, options = {}) {
154
+ const resume = options.resume?.trim();
155
+ if (resume) {
156
+ return sessions.find((session) => session.hermesSessionId === resume);
157
+ }
158
+ const beforeIds = new Set((options.beforeSessions ?? []).map((session) => session.hermesSessionId));
159
+ const newSessions = sessions.filter((session) => !beforeIds.has(session.hermesSessionId));
160
+ if (newSessions.length === 0) {
161
+ return sessions[0];
162
+ }
163
+ const normalizedUserMessage = normalizeSessionSelectionText(options.userMessage);
164
+ if (normalizedUserMessage) {
165
+ const matched = newSessions.find((session) => {
166
+ const haystack = normalizeSessionSelectionText([
167
+ session.displayName,
168
+ session.derivedTitle,
169
+ session.label,
170
+ ].filter(Boolean).join(" "));
171
+ return haystack.length > 0
172
+ && (haystack.includes(normalizedUserMessage) || normalizedUserMessage.includes(haystack));
173
+ });
174
+ if (matched) {
175
+ return matched;
176
+ }
177
+ }
178
+ return newSessions[0];
179
+ }
180
+ function normalizeSessionSelectionText(value) {
181
+ return value
182
+ ?.replace(/\[Hermes runtime context][\s\S]*$/i, "")
183
+ .replace(/\s+/g, " ")
184
+ .trim()
185
+ .slice(0, 80)
186
+ .toLowerCase() ?? "";
187
+ }
188
+ export async function runHermesSlashCommand(params) {
189
+ const command = params.message.trim();
190
+ const resume = typeof params.hermesSessionId === "string" && params.hermesSessionId.trim().length > 0
191
+ ? params.hermesSessionId.trim()
192
+ : await getMappedHermesSessionId(params.sessionKey);
193
+ const raw = runHermesPython(HERMES_SLASH_COMMAND_SCRIPT, {
194
+ CLAWCONNECT_HERMES_SLASH_COMMAND: command,
195
+ CLAWCONNECT_HERMES_SLASH_RESUME: resume ?? "",
196
+ });
197
+ const payload = parseHermesSlashCommandPayload(raw);
198
+ const output = sanitizeHermesChatOutput(payload.output ?? "").trim();
199
+ if (!payload.ok) {
200
+ const message = [output, payload.error].filter(Boolean).join("\n").trim() || "hermes_slash_command_failed";
201
+ throw new Error(message);
202
+ }
203
+ const sessionId = typeof payload.sessionId === "string" && payload.sessionId.trim().length > 0
204
+ ? payload.sessionId.trim()
205
+ : undefined;
206
+ if (sessionId) {
207
+ await rememberHermesSession(params.sessionKey, {
208
+ sessionKey: params.sessionKey,
209
+ hermesSessionId: sessionId,
210
+ displayName: sessionId,
211
+ label: command,
212
+ lastActivityAt: new Date().toISOString(),
213
+ kind: "hermes",
214
+ });
215
+ }
216
+ if (/^\/(?:new|reset)\b/i.test(command) && resume && sessionId && sessionId !== resume) {
217
+ await forgetHermesSession(resume, resume);
218
+ }
219
+ if (payload.pendingInputs && payload.pendingInputs.length > 0) {
220
+ const queuedMessage = payload.pendingInputs.join("\n\n").trim();
221
+ if (queuedMessage) {
222
+ const queued = await runHermesChat({
223
+ message: queuedMessage,
224
+ sessionKey: params.sessionKey,
225
+ ...(sessionId ? { hermesSessionId: sessionId } : {}),
226
+ });
227
+ return {
228
+ ...queued,
229
+ output: [output, queued.output].filter((part) => part && part !== "(no output)").join("\n\n") || queued.output,
230
+ };
231
+ }
232
+ }
233
+ const usage = await collectHermesUsageSnapshot(sessionId);
234
+ return {
235
+ output: output || "(no output)",
236
+ sessionKey: params.sessionKey,
237
+ artifactPaths: [],
238
+ usage,
239
+ };
240
+ }
241
+ function parseHermesSlashCommandPayload(raw) {
242
+ const parsed = JSON.parse(raw);
243
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
244
+ throw new Error("invalid_hermes_slash_command_payload");
245
+ }
246
+ const record = parsed;
247
+ return {
248
+ ok: record.ok === true,
249
+ output: typeof record.output === "string" ? record.output : undefined,
250
+ error: typeof record.error === "string" ? record.error : undefined,
251
+ sessionId: typeof record.sessionId === "string" ? record.sessionId : undefined,
252
+ pendingInputs: Array.isArray(record.pendingInputs)
253
+ ? record.pendingInputs.filter((value) => typeof value === "string" && value.trim().length > 0)
254
+ : undefined,
255
+ };
256
+ }
257
+ async function runHermesChatStreaming(args, sessionKey, context) {
258
+ const child = spawn(resolveHermesBin(), args, {
259
+ env: SUBPROCESS_ENV,
260
+ stdio: ["ignore", "pipe", "pipe"],
261
+ windowsHide: true,
262
+ });
263
+ const runId = context.requestId ?? `hermes-${Date.now()}`;
264
+ let output = "";
265
+ let stderr = "";
266
+ let seq = 0;
267
+ let stdoutLineBuffer = "";
268
+ let pendingAssistantDelta = "";
269
+ let assistantDeltaFlushTimer;
270
+ let inSecurityReview = false;
271
+ let commandDeniedTimeout = false;
272
+ let commandDeniedTimeoutKillTimer;
273
+ const toolCallIdsByName = new Map();
274
+ let toolCallCounter = 0;
275
+ const requestCommandDeniedTimeoutFailure = () => {
276
+ if (commandDeniedTimeout) {
277
+ return;
278
+ }
279
+ commandDeniedTimeout = true;
280
+ child.kill("SIGTERM");
281
+ commandDeniedTimeoutKillTimer = setTimeout(() => child.kill("SIGKILL"), 1000);
282
+ commandDeniedTimeoutKillTimer.unref?.();
283
+ };
284
+ const publishToolLogEvent = (event) => {
285
+ let toolCallId = toolCallIdsByName.get(event.toolName);
286
+ if (!toolCallId) {
287
+ toolCallCounter += 1;
288
+ toolCallId = `${runId}:hermes-tool-${toolCallCounter}`;
289
+ toolCallIdsByName.set(event.toolName, toolCallId);
290
+ }
291
+ if (event.phase === "completed" || event.phase === "failed") {
292
+ toolCallIdsByName.delete(event.toolName);
293
+ }
294
+ context.publishEvent?.({
295
+ type: "event",
296
+ event: "chat",
297
+ payload: {
298
+ runId,
299
+ sessionKey,
300
+ stream: "tool",
301
+ state: event.phase,
302
+ phase: event.phase,
303
+ role: "tool",
304
+ seq: seq += 1,
305
+ ts: Date.now(),
306
+ data: {
307
+ phase: event.phase,
308
+ tool_call_id: toolCallId,
309
+ tool_name: event.toolName,
310
+ text: event.text,
311
+ is_error: event.isError === true,
312
+ },
313
+ timelineEvents: [
314
+ buildToolInvocationUpdatedEvent({
315
+ gatewayId: context.gatewayId ?? "clawconnect",
316
+ sessionKey,
317
+ turnId: runId,
318
+ runId,
319
+ toolInvocationId: toolCallId,
320
+ toolState: hermesToolState(event),
321
+ seq: seq,
322
+ turnSeq: seq,
323
+ content: [{
324
+ type: event.phase === "completed" || event.phase === "failed" ? "tool_result" : "tool_call",
325
+ toolName: event.toolName,
326
+ text: event.text,
327
+ isError: event.isError === true,
328
+ }],
329
+ }),
330
+ ],
331
+ },
332
+ });
333
+ };
334
+ const toolLogWatcher = createHermesToolLogWatcher(publishToolLogEvent);
335
+ const filterChatLine = (line) => {
336
+ const clean = stripAnsi(line).trim();
337
+ if (isHermesCommandDeniedTimeoutLine(clean)) {
338
+ requestCommandDeniedTimeoutFailure();
339
+ return null;
340
+ }
341
+ if (/DANGEROUS COMMAND:\s*Security scan/i.test(clean)) {
342
+ inSecurityReview = true;
343
+ return null;
344
+ }
345
+ if (inSecurityReview) {
346
+ if (/Choice\s*\[[^\]]+\]:/i.test(clean) || /(?:^|\s)[✕x]\s*Denied\b/i.test(clean) || /\bDenied\b/i.test(clean)) {
347
+ inSecurityReview = false;
348
+ }
349
+ return null;
350
+ }
351
+ return line;
352
+ };
353
+ const clearAssistantDeltaFlushTimer = () => {
354
+ if (!assistantDeltaFlushTimer) {
355
+ return;
356
+ }
357
+ clearTimeout(assistantDeltaFlushTimer);
358
+ assistantDeltaFlushTimer = undefined;
359
+ };
360
+ const flushAssistantDelta = () => {
361
+ clearAssistantDeltaFlushTimer();
362
+ if (!pendingAssistantDelta) {
363
+ return;
364
+ }
365
+ const delta = pendingAssistantDelta;
366
+ pendingAssistantDelta = "";
367
+ const timestampMs = Date.now();
368
+ try {
369
+ context.publishEvent?.({
370
+ type: "event",
371
+ event: "chat",
372
+ payload: buildHermesAssistantDeltaPayload({
373
+ runId,
374
+ sessionKey,
375
+ seq: seq += 1,
376
+ timestampMs,
377
+ delta,
378
+ }),
379
+ });
380
+ }
381
+ catch (error) {
382
+ context.publishEvent?.({
383
+ type: "event",
384
+ event: "maintenance_log",
385
+ payload: {
386
+ gatewayId: context.gatewayId,
387
+ requestId: context.requestId,
388
+ runId,
389
+ stream: "stderr",
390
+ seq: seq += 1,
391
+ ts: Date.now(),
392
+ text: `Hermes stream publish failed: ${error instanceof Error ? error.message : String(error)}`,
393
+ },
394
+ });
395
+ }
396
+ };
397
+ const publishAssistantDelta = (text) => {
398
+ output += text;
399
+ pendingAssistantDelta += text;
400
+ if (Buffer.byteLength(pendingAssistantDelta, "utf8") >= HERMES_ASSISTANT_DELTA_MAX_BYTES) {
401
+ flushAssistantDelta();
402
+ return;
403
+ }
404
+ if (!assistantDeltaFlushTimer) {
405
+ assistantDeltaFlushTimer = setTimeout(flushAssistantDelta, HERMES_ASSISTANT_DELTA_FLUSH_MS);
406
+ assistantDeltaFlushTimer.unref?.();
407
+ }
408
+ };
409
+ const publishText = (text) => {
410
+ stdoutLineBuffer += text;
411
+ const lines = stdoutLineBuffer.split(/\r?\n/);
412
+ stdoutLineBuffer = lines.pop() ?? "";
413
+ const clean = stripHermesSessionResumeNotices(lines
414
+ .map(filterChatLine)
415
+ .filter((line) => line !== null)
416
+ .join("\n"));
417
+ if (!clean.trim()) {
418
+ return;
419
+ }
420
+ const chunk = `${clean}\n`;
421
+ publishAssistantDelta(chunk);
422
+ };
423
+ const flushStdoutLineBuffer = () => {
424
+ if (!stdoutLineBuffer) {
425
+ return;
426
+ }
427
+ const clean = stripHermesSessionResumeNotices(filterChatLine(stdoutLineBuffer) ?? "");
428
+ stdoutLineBuffer = "";
429
+ if (!clean.trim()) {
430
+ return;
431
+ }
432
+ publishAssistantDelta(clean);
433
+ };
434
+ const publishStderr = (text) => {
435
+ const clean = stripAnsi(text).trimEnd();
436
+ if (!clean) {
437
+ return;
438
+ }
439
+ stderr += `${clean}\n`;
440
+ context.publishEvent?.({
441
+ type: "event",
442
+ event: "maintenance_log",
443
+ payload: {
444
+ gatewayId: context.gatewayId,
445
+ requestId: context.requestId,
446
+ runId,
447
+ stream: "stderr",
448
+ seq: seq += 1,
449
+ ts: Date.now(),
450
+ text: clean,
451
+ },
452
+ });
453
+ };
454
+ const publishTypingMarker = () => {
455
+ // The local mobile client already owns the pending placeholder. Do not
456
+ // forward protocol-only typing markers as empty assistant chat events.
457
+ };
458
+ child.stdout?.on("data", (chunk) => publishText(chunk.toString()));
459
+ child.stderr?.on("data", (chunk) => publishStderr(chunk.toString()));
460
+ toolLogWatcher.start();
461
+ publishTypingMarker();
462
+ return await new Promise((resolveOutput, rejectOutput) => {
463
+ const abortSignal = context.abortSignal;
464
+ let abortRequested = abortSignal?.aborted === true;
465
+ const typingTimer = setInterval(publishTypingMarker, 5000);
466
+ typingTimer.unref?.();
467
+ const timeout = setTimeout(() => {
468
+ clearInterval(typingTimer);
469
+ clearAssistantDeltaFlushTimer();
470
+ toolLogWatcher.stop();
471
+ child.kill("SIGTERM");
472
+ rejectOutput(new Error("hermes_chat_timeout"));
473
+ }, CHAT_TIMEOUT_MS);
474
+ timeout.unref?.();
475
+ const abortChat = () => {
476
+ abortRequested = true;
477
+ stdoutLineBuffer = "";
478
+ pendingAssistantDelta = "";
479
+ clearAssistantDeltaFlushTimer();
480
+ child.kill("SIGTERM");
481
+ };
482
+ const cleanup = () => {
483
+ abortSignal?.removeEventListener("abort", abortChat);
484
+ };
485
+ if (abortRequested) {
486
+ abortChat();
487
+ }
488
+ else {
489
+ abortSignal?.addEventListener("abort", abortChat, { once: true });
490
+ }
491
+ child.once("error", (error) => {
492
+ clearInterval(typingTimer);
493
+ clearTimeout(timeout);
494
+ if (commandDeniedTimeoutKillTimer) {
495
+ clearTimeout(commandDeniedTimeoutKillTimer);
496
+ commandDeniedTimeoutKillTimer = undefined;
497
+ }
498
+ clearAssistantDeltaFlushTimer();
499
+ toolLogWatcher.stop();
500
+ cleanup();
501
+ rejectOutput(error);
502
+ });
503
+ child.once("close", (code, signal) => {
504
+ void (async () => {
505
+ clearInterval(typingTimer);
506
+ clearTimeout(timeout);
507
+ if (commandDeniedTimeoutKillTimer) {
508
+ clearTimeout(commandDeniedTimeoutKillTimer);
509
+ commandDeniedTimeoutKillTimer = undefined;
510
+ }
511
+ toolLogWatcher.stop();
512
+ cleanup();
513
+ if (abortRequested) {
514
+ rejectOutput(new Error("hermes_chat_aborted"));
515
+ return;
516
+ }
517
+ flushStdoutLineBuffer();
518
+ flushAssistantDelta();
519
+ if (commandDeniedTimeout) {
520
+ rejectOutput(new Error(HERMES_COMMAND_DENIED_TIMEOUT_MESSAGE));
521
+ return;
522
+ }
523
+ if (code && code !== 0) {
524
+ const reason = stderr.trim() || output.trim() || `hermes chat exited with code ${code}`;
525
+ rejectOutput(new Error(signal ? `${reason} (${signal})` : reason));
526
+ return;
527
+ }
528
+ resolveOutput(output);
529
+ })().catch(rejectOutput);
530
+ });
531
+ });
532
+ }
533
+ export function buildHermesAssistantDeltaPayload(params) {
534
+ const payload = buildMobileAssistantDeltaPayload({
535
+ run: { runId: params.runId, sessionKey: params.sessionKey },
536
+ seq: params.seq,
537
+ timestampMs: params.timestampMs,
538
+ delta: params.delta,
539
+ });
540
+ return {
541
+ ...payload,
542
+ timelineEvents: [
543
+ buildMessagePartDeltaEvent({
544
+ gatewayId: "clawconnect",
545
+ sessionKey: params.sessionKey,
546
+ turnId: params.runId,
547
+ runId: params.runId,
548
+ role: "assistant",
549
+ seq: params.seq,
550
+ turnSeq: params.seq,
551
+ now: () => new Date(params.timestampMs),
552
+ content: [{ type: "text", text: params.delta }],
553
+ }),
554
+ ],
555
+ };
556
+ }
557
+ function hermesToolState(event) {
558
+ if (event.phase === "completed") {
559
+ return "success";
560
+ }
561
+ if (event.phase === "failed") {
562
+ return "failed";
563
+ }
564
+ return "streaming_output";
565
+ }
566
+ function createHermesToolLogWatcher(onEvent) {
567
+ let offset = 0;
568
+ let timer;
569
+ try {
570
+ offset = statSync(HERMES_AGENT_LOG_FILE).size;
571
+ }
572
+ catch {
573
+ offset = 0;
574
+ }
575
+ const poll = () => {
576
+ let content = "";
577
+ try {
578
+ const bytes = readFileSync(HERMES_AGENT_LOG_FILE);
579
+ if (bytes.length < offset) {
580
+ offset = 0;
581
+ }
582
+ if (bytes.length === offset) {
583
+ return;
584
+ }
585
+ content = bytes.subarray(offset).toString("utf8");
586
+ offset = bytes.length;
587
+ }
588
+ catch {
589
+ return;
590
+ }
591
+ for (const line of content.split(/\r?\n/)) {
592
+ const event = parseHermesToolLogLine(line);
593
+ if (event) {
594
+ onEvent(event);
595
+ }
596
+ }
597
+ };
598
+ return {
599
+ start: () => {
600
+ if (timer) {
601
+ return;
602
+ }
603
+ timer = setInterval(poll, 250);
604
+ timer.unref?.();
605
+ },
606
+ stop: () => {
607
+ poll();
608
+ if (timer) {
609
+ clearInterval(timer);
610
+ timer = undefined;
611
+ }
612
+ },
613
+ };
614
+ }
615
+ export function parseHermesToolLogLine(line) {
616
+ const clean = stripAnsi(line).trim();
617
+ if (!clean) {
618
+ return null;
619
+ }
620
+ const executor = clean.match(/\bagent\.tool_executor:\s*tool\s+([A-Za-z0-9_.-]+)\s+(.+)$/i);
621
+ if (executor) {
622
+ const toolName = normalizeHermesToolName(executor[1] ?? "tool");
623
+ const detail = (executor[2] ?? "").trim();
624
+ if (/\b(?:running|started|executing|start)\b/i.test(detail)) {
625
+ return {
626
+ toolName,
627
+ phase: "streaming",
628
+ text: `${toolName} ${detail}`.trim(),
629
+ isError: false,
630
+ };
631
+ }
632
+ const failed = /\b(?:failed|error|errored|denied|aborted)\b/i.test(detail);
633
+ return {
634
+ toolName,
635
+ phase: failed ? "failed" : "completed",
636
+ text: `${toolName} ${detail}`.trim(),
637
+ isError: failed,
638
+ };
639
+ }
640
+ const toolLogger = clean.match(/\btools\.([A-Za-z0-9_.-]+):\s*(.+)$/i);
641
+ if (!toolLogger) {
642
+ return null;
643
+ }
644
+ const loggerName = toolLogger[1] ?? "tool";
645
+ if (!/(?:_tool|_tools)$/i.test(loggerName)) {
646
+ return null;
647
+ }
648
+ let toolName = normalizeHermesToolName(loggerName);
649
+ let detail = (toolLogger[2] ?? "").trim();
650
+ if (/\b(?:Shutting down \d+ remaining sandbox(?:\(es\))?|Manually cleaned up environment|Cleaned \d+ environments?)\b/i.test(detail)) {
651
+ return null;
652
+ }
653
+ const nestedTool = detail.match(/^([A-Za-z0-9_.-]+):\s*(.+)$/);
654
+ if (nestedTool) {
655
+ toolName = normalizeHermesToolName(nestedTool[1] ?? toolName);
656
+ detail = (nestedTool[2] ?? "").trim();
657
+ }
658
+ if (!detail) {
659
+ return null;
660
+ }
661
+ return {
662
+ toolName,
663
+ phase: "streaming",
664
+ text: `${toolName}: ${detail}`,
665
+ };
666
+ }
667
+ function normalizeHermesToolName(rawName) {
668
+ const normalized = rawName
669
+ .trim()
670
+ .replace(/[^A-Za-z0-9_.-]+/g, "_")
671
+ .replace(/_tools$/i, "")
672
+ .replace(/_tool$/i, "");
673
+ return normalized || "tool";
674
+ }
675
+ async function prepareHermesMessage(message, attachments, sessionKey) {
676
+ const refs = [];
677
+ if (Array.isArray(attachments)) {
678
+ const safeSession = sessionKey.replace(/[^\w.-]/g, "_") || "main";
679
+ for (const attachment of attachments) {
680
+ if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) {
681
+ continue;
682
+ }
683
+ const record = attachment;
684
+ if (typeof record.content !== "string" || record.content.length === 0) {
685
+ continue;
686
+ }
687
+ const fileName = sanitizeFileName(typeof record.fileName === "string" ? record.fileName
688
+ : typeof record.name === "string" ? record.name
689
+ : `attachment-${randomUUID()}`);
690
+ const dir = join(HERMES_INBOX_DIR, safeSession, randomUUID());
691
+ await mkdir(dir, { recursive: true });
692
+ const filePath = join(dir, fileName);
693
+ await writeFile(filePath, Buffer.from(record.content, "base64"));
694
+ const mimeType = typeof record.mimeType === "string" ? record.mimeType : "application/octet-stream";
695
+ refs.push(`[file attached: ${filePath} (${mimeType})]`);
696
+ }
697
+ }
698
+ const sections = [message.trim()];
699
+ if (refs.length > 0) {
700
+ sections.push(refs.join("\n"));
701
+ }
702
+ const runtimeHint = buildHermesRuntimeContextHint(readHermesStatusSnapshot());
703
+ if (runtimeHint) {
704
+ sections.push(runtimeHint);
705
+ }
706
+ sections.push(CLAWCONNECT_MOBILE_BRIDGE_HINT);
707
+ return sections.filter(Boolean).join("\n\n").trim();
708
+ }
709
+ export function buildHermesRuntimeContextHint(snapshot) {
710
+ const details = compactStringArray([
711
+ snapshot.currentModel ? `model=${snapshot.currentModel}` : undefined,
712
+ snapshot.provider ? `provider=${snapshot.provider}` : undefined,
713
+ ]);
714
+ if (details.length === 0) {
715
+ return undefined;
716
+ }
717
+ return [
718
+ "[Hermes runtime context]",
719
+ `Current runtime: ${details.join(", ")}.`,
720
+ "If the user asks which model or provider is currently being used, answer from this runtime context.",
721
+ ].join("\n");
722
+ }
723
+ //# sourceMappingURL=hermes-runtime-chat.js.map