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,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
|