hovclaw 0.1.4 → 0.1.5
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/dist/gateway/ui/index.html +1 -1
- package/dist/hovclaw.js +80 -17
- package/dist/index.js +160 -20
- package/dist/{src-GZDRRc5A.js → src-GoIM4Tv1.js} +81 -4
- package/package.json +1 -1
package/dist/hovclaw.js
CHANGED
|
@@ -181,7 +181,7 @@ const DEFAULT_FILE_CONFIG = {
|
|
|
181
181
|
gateway: {
|
|
182
182
|
enabled: true,
|
|
183
183
|
host: "127.0.0.1",
|
|
184
|
-
port:
|
|
184
|
+
port: 18889,
|
|
185
185
|
mode: "local",
|
|
186
186
|
tickIntervalMs: 3e4,
|
|
187
187
|
webUi: {
|
|
@@ -1696,8 +1696,13 @@ async function writeFileIfMissing(filePath, content) {
|
|
|
1696
1696
|
throw error;
|
|
1697
1697
|
}
|
|
1698
1698
|
}
|
|
1699
|
+
function isMeaningfulWorkspaceEntry(entry) {
|
|
1700
|
+
if (IGNORED_WORKSPACE_ENTRIES.has(entry)) return false;
|
|
1701
|
+
if (entry.startsWith(".")) return false;
|
|
1702
|
+
return true;
|
|
1703
|
+
}
|
|
1699
1704
|
async function listMeaningfulWorkspaceEntries(workspaceDir) {
|
|
1700
|
-
return (await fs$1.readdir(workspaceDir)).filter((entry) =>
|
|
1705
|
+
return (await fs$1.readdir(workspaceDir)).filter((entry) => isMeaningfulWorkspaceEntry(entry));
|
|
1701
1706
|
}
|
|
1702
1707
|
function resolveAgentWorkspaceDir(config, agentId) {
|
|
1703
1708
|
return (config.agents.list.find((entry) => entry.id === agentId)?.workspace)?.trim() || config.agents.defaults.workspace;
|
|
@@ -1769,6 +1774,7 @@ function buildDefaultSystemPrompt(workspaceDir) {
|
|
|
1769
1774
|
"",
|
|
1770
1775
|
"## Messaging",
|
|
1771
1776
|
"Respond with clear, direct language tailored to the user's request.",
|
|
1777
|
+
"Default to plain text and avoid markdown formatting unless the user asks for markdown or structured output.",
|
|
1772
1778
|
"If you cannot complete a request, explain the blocker and the minimum next step."
|
|
1773
1779
|
].join("\n");
|
|
1774
1780
|
}
|
|
@@ -2257,7 +2263,8 @@ var PiAgentManager = class {
|
|
|
2257
2263
|
model: model.resolvedRef,
|
|
2258
2264
|
modelResolutionReason: selectedModel.reason,
|
|
2259
2265
|
source: input.meta.source,
|
|
2260
|
-
channel: input.meta.channel
|
|
2266
|
+
channel: input.meta.channel,
|
|
2267
|
+
...input.meta.sender ? { sender: input.meta.sender } : {}
|
|
2261
2268
|
}
|
|
2262
2269
|
});
|
|
2263
2270
|
const exec = async () => {
|
|
@@ -2499,6 +2506,57 @@ function isTelegramParseModeError(error) {
|
|
|
2499
2506
|
const message = error.message.toLowerCase();
|
|
2500
2507
|
return message.includes("can't parse entities") || message.includes("parse entities") || message.includes("parse mode");
|
|
2501
2508
|
}
|
|
2509
|
+
function escapeTelegramHtml(text) {
|
|
2510
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2511
|
+
}
|
|
2512
|
+
function escapeTelegramHtmlAttr(text) {
|
|
2513
|
+
return escapeTelegramHtml(text).replace(/"/g, """);
|
|
2514
|
+
}
|
|
2515
|
+
function stripMarkdownToPlainText(input) {
|
|
2516
|
+
let value = input.replace(/\r\n/g, "\n");
|
|
2517
|
+
value = value.replace(/^#{1,6}\s+/gm, "");
|
|
2518
|
+
value = value.replace(/^>\s?/gm, "");
|
|
2519
|
+
value = value.replace(/```(?:[a-zA-Z0-9_-]+)?\n?([\s\S]*?)```/g, (_match, code) => code.replace(/\n$/, ""));
|
|
2520
|
+
value = value.replace(/`([^`\n]+)`/g, "$1");
|
|
2521
|
+
value = value.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, "$1");
|
|
2522
|
+
value = value.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
2523
|
+
value = value.replace(/~~([^~]+)~~/g, "$1");
|
|
2524
|
+
value = value.replace(/\|\|([^|]+)\|\|/g, "$1");
|
|
2525
|
+
value = value.replace(/\*([^*\n]+)\*/g, "$1");
|
|
2526
|
+
value = value.replace(/_([^_\n]+)_/g, "$1");
|
|
2527
|
+
return value;
|
|
2528
|
+
}
|
|
2529
|
+
function renderMarkdownToTelegramHtml(input) {
|
|
2530
|
+
const placeholders = [];
|
|
2531
|
+
const toPlaceholder = (html) => {
|
|
2532
|
+
return `\u0000TG_PLACEHOLDER_${placeholders.push(html) - 1}\u0000`;
|
|
2533
|
+
};
|
|
2534
|
+
let value = input.replace(/\r\n/g, "\n");
|
|
2535
|
+
value = value.replace(/```(?:[a-zA-Z0-9_-]+)?\n?([\s\S]*?)```/g, (_match, code) => toPlaceholder(`<pre><code>${escapeTelegramHtml(code.replace(/\n$/, ""))}</code></pre>`));
|
|
2536
|
+
value = value.replace(/`([^`\n]+)`/g, (_match, code) => toPlaceholder(`<code>${escapeTelegramHtml(code)}</code>`));
|
|
2537
|
+
value = escapeTelegramHtml(value);
|
|
2538
|
+
value = value.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, (_match, text, href) => `<a href="${escapeTelegramHtmlAttr(href)}">${text}</a>`);
|
|
2539
|
+
value = value.replace(/\*\*([^*]+)\*\*/g, "<b>$1</b>");
|
|
2540
|
+
value = value.replace(/~~([^~]+)~~/g, "<s>$1</s>");
|
|
2541
|
+
value = value.replace(/\|\|([^|]+)\|\|/g, "<tg-spoiler>$1</tg-spoiler>");
|
|
2542
|
+
value = value.replace(/\*([^*\n]+)\*/g, "<i>$1</i>");
|
|
2543
|
+
value = value.replace(/_([^_\n]+)_/g, "<i>$1</i>");
|
|
2544
|
+
return value.replace(/\u0000TG_PLACEHOLDER_(\d+)\u0000/g, (_match, rawIndex) => {
|
|
2545
|
+
return placeholders[Number.parseInt(rawIndex, 10)] ?? "";
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
function prepareTelegramTextCandidates(text, textMode) {
|
|
2549
|
+
const plain = stripMarkdownToPlainText(text);
|
|
2550
|
+
const fallbackPlain = plain.trim().length > 0 ? plain : text;
|
|
2551
|
+
if (textMode === "plain") return {
|
|
2552
|
+
html: escapeTelegramHtml(fallbackPlain),
|
|
2553
|
+
plain: fallbackPlain
|
|
2554
|
+
};
|
|
2555
|
+
return {
|
|
2556
|
+
html: renderMarkdownToTelegramHtml(text),
|
|
2557
|
+
plain: fallbackPlain
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2502
2560
|
function mediaMethod(media) {
|
|
2503
2561
|
if (media.kind === "image") return {
|
|
2504
2562
|
method: "sendPhoto",
|
|
@@ -2928,25 +2986,24 @@ var TelegramChannel = class {
|
|
|
2928
2986
|
async sendRichMessage(target, text, options) {
|
|
2929
2987
|
const parsedTarget = parseTargetChatId(target.chatId);
|
|
2930
2988
|
for (const chunk of chunkText(text, TELEGRAM_CHUNK_LIMIT)) {
|
|
2989
|
+
const rendered = prepareTelegramTextCandidates(chunk, this.textMode);
|
|
2931
2990
|
const payload = {
|
|
2932
2991
|
chat_id: parsedTarget.chatId,
|
|
2933
|
-
text:
|
|
2992
|
+
text: rendered.html,
|
|
2934
2993
|
message_thread_id: parsedTarget.messageThreadId,
|
|
2935
2994
|
disable_web_page_preview: true,
|
|
2936
|
-
reply_markup: options?.inlineKeyboard ? { inline_keyboard: options.inlineKeyboard } : void 0
|
|
2995
|
+
reply_markup: options?.inlineKeyboard ? { inline_keyboard: options.inlineKeyboard } : void 0,
|
|
2996
|
+
parse_mode: "HTML"
|
|
2937
2997
|
};
|
|
2938
|
-
if (this.textMode !== "markdown") {
|
|
2939
|
-
await this.callApi("sendMessage", payload);
|
|
2940
|
-
continue;
|
|
2941
|
-
}
|
|
2942
2998
|
try {
|
|
2999
|
+
await this.callApi("sendMessage", payload);
|
|
3000
|
+
} catch (error) {
|
|
3001
|
+
if (!isTelegramParseModeError(error)) throw error;
|
|
2943
3002
|
await this.callApi("sendMessage", {
|
|
2944
3003
|
...payload,
|
|
2945
|
-
|
|
3004
|
+
text: rendered.plain,
|
|
3005
|
+
parse_mode: void 0
|
|
2946
3006
|
});
|
|
2947
|
-
} catch (error) {
|
|
2948
|
-
if (!isTelegramParseModeError(error)) throw error;
|
|
2949
|
-
await this.callApi("sendMessage", payload);
|
|
2950
3007
|
}
|
|
2951
3008
|
}
|
|
2952
3009
|
}
|
|
@@ -2981,18 +3038,24 @@ var TelegramChannel = class {
|
|
|
2981
3038
|
caption: media.caption?.trim() || void 0,
|
|
2982
3039
|
message_thread_id: parsedTarget.messageThreadId
|
|
2983
3040
|
};
|
|
2984
|
-
if (
|
|
3041
|
+
if (!payload.caption) {
|
|
2985
3042
|
await this.callApi(method, payload);
|
|
2986
3043
|
return;
|
|
2987
3044
|
}
|
|
3045
|
+
const renderedCaption = prepareTelegramTextCandidates(payload.caption, this.textMode);
|
|
2988
3046
|
try {
|
|
2989
3047
|
await this.callApi(method, {
|
|
2990
3048
|
...payload,
|
|
2991
|
-
|
|
3049
|
+
caption: renderedCaption.html,
|
|
3050
|
+
parse_mode: "HTML"
|
|
2992
3051
|
});
|
|
2993
3052
|
} catch (error) {
|
|
2994
3053
|
if (!isTelegramParseModeError(error)) throw error;
|
|
2995
|
-
await this.callApi(method,
|
|
3054
|
+
await this.callApi(method, {
|
|
3055
|
+
...payload,
|
|
3056
|
+
caption: renderedCaption.plain,
|
|
3057
|
+
parse_mode: void 0
|
|
3058
|
+
});
|
|
2996
3059
|
}
|
|
2997
3060
|
}
|
|
2998
3061
|
async setReaction(target, reaction) {
|
|
@@ -6233,7 +6296,7 @@ function registerGatewayCommands(program, deps = defaultGatewayCommandDeps) {
|
|
|
6233
6296
|
gateway.command("run").description("Run HOVClaw daemon with gateway in foreground").action(async () => {
|
|
6234
6297
|
if (!config.gateway.enabled) throw new Error("Gateway is disabled in config. Enable gateway.enabled first.");
|
|
6235
6298
|
process.stdout.write(`Starting HOVClaw gateway on ${config.gateway.host}:${config.gateway.port}...\n`);
|
|
6236
|
-
await import("./src-
|
|
6299
|
+
await import("./src-GoIM4Tv1.js");
|
|
6237
6300
|
});
|
|
6238
6301
|
gateway.command("open-ui").description("Open the minimal gateway web UI in your default browser").option("--no-open", "Print URL but do not open browser").option("--json", "Print JSON output").action(async (options) => {
|
|
6239
6302
|
const url = resolveGatewayWebUiUrl();
|
package/dist/index.js
CHANGED
|
@@ -162,7 +162,7 @@ const DEFAULT_FILE_CONFIG = {
|
|
|
162
162
|
gateway: {
|
|
163
163
|
enabled: true,
|
|
164
164
|
host: "127.0.0.1",
|
|
165
|
-
port:
|
|
165
|
+
port: 18889,
|
|
166
166
|
mode: "local",
|
|
167
167
|
tickIntervalMs: 3e4,
|
|
168
168
|
webUi: {
|
|
@@ -1480,8 +1480,13 @@ async function writeFileIfMissing(filePath, content) {
|
|
|
1480
1480
|
throw error;
|
|
1481
1481
|
}
|
|
1482
1482
|
}
|
|
1483
|
+
function isMeaningfulWorkspaceEntry(entry) {
|
|
1484
|
+
if (IGNORED_WORKSPACE_ENTRIES.has(entry)) return false;
|
|
1485
|
+
if (entry.startsWith(".")) return false;
|
|
1486
|
+
return true;
|
|
1487
|
+
}
|
|
1483
1488
|
async function listMeaningfulWorkspaceEntries(workspaceDir) {
|
|
1484
|
-
return (await fs$1.readdir(workspaceDir)).filter((entry) =>
|
|
1489
|
+
return (await fs$1.readdir(workspaceDir)).filter((entry) => isMeaningfulWorkspaceEntry(entry));
|
|
1485
1490
|
}
|
|
1486
1491
|
function resolveAgentWorkspaceDir(config, agentId) {
|
|
1487
1492
|
return (config.agents.list.find((entry) => entry.id === agentId)?.workspace)?.trim() || config.agents.defaults.workspace;
|
|
@@ -1553,6 +1558,7 @@ function buildDefaultSystemPrompt(workspaceDir) {
|
|
|
1553
1558
|
"",
|
|
1554
1559
|
"## Messaging",
|
|
1555
1560
|
"Respond with clear, direct language tailored to the user's request.",
|
|
1561
|
+
"Default to plain text and avoid markdown formatting unless the user asks for markdown or structured output.",
|
|
1556
1562
|
"If you cannot complete a request, explain the blocker and the minimum next step."
|
|
1557
1563
|
].join("\n");
|
|
1558
1564
|
}
|
|
@@ -2041,7 +2047,8 @@ var PiAgentManager = class {
|
|
|
2041
2047
|
model: model.resolvedRef,
|
|
2042
2048
|
modelResolutionReason: selectedModel.reason,
|
|
2043
2049
|
source: input.meta.source,
|
|
2044
|
-
channel: input.meta.channel
|
|
2050
|
+
channel: input.meta.channel,
|
|
2051
|
+
...input.meta.sender ? { sender: input.meta.sender } : {}
|
|
2045
2052
|
}
|
|
2046
2053
|
});
|
|
2047
2054
|
const exec = async () => {
|
|
@@ -2330,6 +2337,57 @@ function isTelegramParseModeError(error) {
|
|
|
2330
2337
|
const message = error.message.toLowerCase();
|
|
2331
2338
|
return message.includes("can't parse entities") || message.includes("parse entities") || message.includes("parse mode");
|
|
2332
2339
|
}
|
|
2340
|
+
function escapeTelegramHtml(text) {
|
|
2341
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2342
|
+
}
|
|
2343
|
+
function escapeTelegramHtmlAttr(text) {
|
|
2344
|
+
return escapeTelegramHtml(text).replace(/"/g, """);
|
|
2345
|
+
}
|
|
2346
|
+
function stripMarkdownToPlainText(input) {
|
|
2347
|
+
let value = input.replace(/\r\n/g, "\n");
|
|
2348
|
+
value = value.replace(/^#{1,6}\s+/gm, "");
|
|
2349
|
+
value = value.replace(/^>\s?/gm, "");
|
|
2350
|
+
value = value.replace(/```(?:[a-zA-Z0-9_-]+)?\n?([\s\S]*?)```/g, (_match, code) => code.replace(/\n$/, ""));
|
|
2351
|
+
value = value.replace(/`([^`\n]+)`/g, "$1");
|
|
2352
|
+
value = value.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, "$1");
|
|
2353
|
+
value = value.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
2354
|
+
value = value.replace(/~~([^~]+)~~/g, "$1");
|
|
2355
|
+
value = value.replace(/\|\|([^|]+)\|\|/g, "$1");
|
|
2356
|
+
value = value.replace(/\*([^*\n]+)\*/g, "$1");
|
|
2357
|
+
value = value.replace(/_([^_\n]+)_/g, "$1");
|
|
2358
|
+
return value;
|
|
2359
|
+
}
|
|
2360
|
+
function renderMarkdownToTelegramHtml(input) {
|
|
2361
|
+
const placeholders = [];
|
|
2362
|
+
const toPlaceholder = (html) => {
|
|
2363
|
+
return `\u0000TG_PLACEHOLDER_${placeholders.push(html) - 1}\u0000`;
|
|
2364
|
+
};
|
|
2365
|
+
let value = input.replace(/\r\n/g, "\n");
|
|
2366
|
+
value = value.replace(/```(?:[a-zA-Z0-9_-]+)?\n?([\s\S]*?)```/g, (_match, code) => toPlaceholder(`<pre><code>${escapeTelegramHtml(code.replace(/\n$/, ""))}</code></pre>`));
|
|
2367
|
+
value = value.replace(/`([^`\n]+)`/g, (_match, code) => toPlaceholder(`<code>${escapeTelegramHtml(code)}</code>`));
|
|
2368
|
+
value = escapeTelegramHtml(value);
|
|
2369
|
+
value = value.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, (_match, text, href) => `<a href="${escapeTelegramHtmlAttr(href)}">${text}</a>`);
|
|
2370
|
+
value = value.replace(/\*\*([^*]+)\*\*/g, "<b>$1</b>");
|
|
2371
|
+
value = value.replace(/~~([^~]+)~~/g, "<s>$1</s>");
|
|
2372
|
+
value = value.replace(/\|\|([^|]+)\|\|/g, "<tg-spoiler>$1</tg-spoiler>");
|
|
2373
|
+
value = value.replace(/\*([^*\n]+)\*/g, "<i>$1</i>");
|
|
2374
|
+
value = value.replace(/_([^_\n]+)_/g, "<i>$1</i>");
|
|
2375
|
+
return value.replace(/\u0000TG_PLACEHOLDER_(\d+)\u0000/g, (_match, rawIndex) => {
|
|
2376
|
+
return placeholders[Number.parseInt(rawIndex, 10)] ?? "";
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
function prepareTelegramTextCandidates(text, textMode) {
|
|
2380
|
+
const plain = stripMarkdownToPlainText(text);
|
|
2381
|
+
const fallbackPlain = plain.trim().length > 0 ? plain : text;
|
|
2382
|
+
if (textMode === "plain") return {
|
|
2383
|
+
html: escapeTelegramHtml(fallbackPlain),
|
|
2384
|
+
plain: fallbackPlain
|
|
2385
|
+
};
|
|
2386
|
+
return {
|
|
2387
|
+
html: renderMarkdownToTelegramHtml(text),
|
|
2388
|
+
plain: fallbackPlain
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2333
2391
|
function mediaMethod(media) {
|
|
2334
2392
|
if (media.kind === "image") return {
|
|
2335
2393
|
method: "sendPhoto",
|
|
@@ -2759,25 +2817,24 @@ var TelegramChannel = class {
|
|
|
2759
2817
|
async sendRichMessage(target, text, options) {
|
|
2760
2818
|
const parsedTarget = parseTargetChatId(target.chatId);
|
|
2761
2819
|
for (const chunk of chunkText(text, TELEGRAM_CHUNK_LIMIT)) {
|
|
2820
|
+
const rendered = prepareTelegramTextCandidates(chunk, this.textMode);
|
|
2762
2821
|
const payload = {
|
|
2763
2822
|
chat_id: parsedTarget.chatId,
|
|
2764
|
-
text:
|
|
2823
|
+
text: rendered.html,
|
|
2765
2824
|
message_thread_id: parsedTarget.messageThreadId,
|
|
2766
2825
|
disable_web_page_preview: true,
|
|
2767
|
-
reply_markup: options?.inlineKeyboard ? { inline_keyboard: options.inlineKeyboard } : void 0
|
|
2826
|
+
reply_markup: options?.inlineKeyboard ? { inline_keyboard: options.inlineKeyboard } : void 0,
|
|
2827
|
+
parse_mode: "HTML"
|
|
2768
2828
|
};
|
|
2769
|
-
if (this.textMode !== "markdown") {
|
|
2770
|
-
await this.callApi("sendMessage", payload);
|
|
2771
|
-
continue;
|
|
2772
|
-
}
|
|
2773
2829
|
try {
|
|
2830
|
+
await this.callApi("sendMessage", payload);
|
|
2831
|
+
} catch (error) {
|
|
2832
|
+
if (!isTelegramParseModeError(error)) throw error;
|
|
2774
2833
|
await this.callApi("sendMessage", {
|
|
2775
2834
|
...payload,
|
|
2776
|
-
|
|
2835
|
+
text: rendered.plain,
|
|
2836
|
+
parse_mode: void 0
|
|
2777
2837
|
});
|
|
2778
|
-
} catch (error) {
|
|
2779
|
-
if (!isTelegramParseModeError(error)) throw error;
|
|
2780
|
-
await this.callApi("sendMessage", payload);
|
|
2781
2838
|
}
|
|
2782
2839
|
}
|
|
2783
2840
|
}
|
|
@@ -2812,18 +2869,24 @@ var TelegramChannel = class {
|
|
|
2812
2869
|
caption: media.caption?.trim() || void 0,
|
|
2813
2870
|
message_thread_id: parsedTarget.messageThreadId
|
|
2814
2871
|
};
|
|
2815
|
-
if (
|
|
2872
|
+
if (!payload.caption) {
|
|
2816
2873
|
await this.callApi(method, payload);
|
|
2817
2874
|
return;
|
|
2818
2875
|
}
|
|
2876
|
+
const renderedCaption = prepareTelegramTextCandidates(payload.caption, this.textMode);
|
|
2819
2877
|
try {
|
|
2820
2878
|
await this.callApi(method, {
|
|
2821
2879
|
...payload,
|
|
2822
|
-
|
|
2880
|
+
caption: renderedCaption.html,
|
|
2881
|
+
parse_mode: "HTML"
|
|
2823
2882
|
});
|
|
2824
2883
|
} catch (error) {
|
|
2825
2884
|
if (!isTelegramParseModeError(error)) throw error;
|
|
2826
|
-
await this.callApi(method,
|
|
2885
|
+
await this.callApi(method, {
|
|
2886
|
+
...payload,
|
|
2887
|
+
caption: renderedCaption.plain,
|
|
2888
|
+
parse_mode: void 0
|
|
2889
|
+
});
|
|
2827
2890
|
}
|
|
2828
2891
|
}
|
|
2829
2892
|
async setReaction(target, reaction) {
|
|
@@ -5578,6 +5641,39 @@ const channelsLogoutMethod = async (params, context) => {
|
|
|
5578
5641
|
return { ok: true };
|
|
5579
5642
|
};
|
|
5580
5643
|
|
|
5644
|
+
//#endregion
|
|
5645
|
+
//#region src/inbound-context.ts
|
|
5646
|
+
const SENDER_DISPLAY_NAME_MAX_CHARS = 80;
|
|
5647
|
+
const SENDER_TOKEN_MAX_CHARS = 80;
|
|
5648
|
+
const SENDER_CONTEXT_PREFIX = "[Sender Context]";
|
|
5649
|
+
function normalizeInlineToken(value, maxChars, fallback) {
|
|
5650
|
+
const resolved = (value ?? "").replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim() || fallback;
|
|
5651
|
+
if (resolved.length <= maxChars) return resolved;
|
|
5652
|
+
return `${resolved.slice(0, maxChars - 3)}...`;
|
|
5653
|
+
}
|
|
5654
|
+
function sanitizeSenderDisplayName(displayName, fallbackUserId) {
|
|
5655
|
+
return normalizeInlineToken(displayName, SENDER_DISPLAY_NAME_MAX_CHARS, fallbackUserId);
|
|
5656
|
+
}
|
|
5657
|
+
function buildSenderContextEnvelope(sender) {
|
|
5658
|
+
const userId = normalizeInlineToken(sender.userId, SENDER_TOKEN_MAX_CHARS, "unknown");
|
|
5659
|
+
const displayName = sanitizeSenderDisplayName(sender.displayName, userId);
|
|
5660
|
+
const accountId = sender.accountId ? normalizeInlineToken(sender.accountId, SENDER_TOKEN_MAX_CHARS, "") : "";
|
|
5661
|
+
return [
|
|
5662
|
+
SENDER_CONTEXT_PREFIX,
|
|
5663
|
+
`displayName=${displayName}`,
|
|
5664
|
+
`userId=${userId}`,
|
|
5665
|
+
`channel=${sender.channel}`,
|
|
5666
|
+
...accountId ? [`accountId=${accountId}`] : [],
|
|
5667
|
+
...sender.peerKind ? [`peerKind=${sender.peerKind}`] : [],
|
|
5668
|
+
""
|
|
5669
|
+
].join("\n");
|
|
5670
|
+
}
|
|
5671
|
+
function prependSenderContext(prompt, sender) {
|
|
5672
|
+
if (!sender) return prompt;
|
|
5673
|
+
if (prompt.startsWith(`${SENDER_CONTEXT_PREFIX}\n`)) return prompt;
|
|
5674
|
+
return `${buildSenderContextEnvelope(sender)}${prompt}`;
|
|
5675
|
+
}
|
|
5676
|
+
|
|
5581
5677
|
//#endregion
|
|
5582
5678
|
//#region src/gateway/methods/chat.ts
|
|
5583
5679
|
const chatHistoryParamsSchema = z.object({ sessionKey: z.string().min(1) });
|
|
@@ -5596,15 +5692,23 @@ const chatHistoryMethod = async (params, context) => {
|
|
|
5596
5692
|
};
|
|
5597
5693
|
const chatSendMethod = async (params, context, connId) => {
|
|
5598
5694
|
const parsed = chatSendParamsSchema.parse(params);
|
|
5695
|
+
const client = context.getConnectionClient?.(connId) ?? null;
|
|
5696
|
+
const sender = client ? {
|
|
5697
|
+
displayName: client.displayName ?? client.id,
|
|
5698
|
+
userId: client.id,
|
|
5699
|
+
channel: "cli"
|
|
5700
|
+
} : void 0;
|
|
5701
|
+
const prompt = prependSenderContext(parsed.prompt, sender);
|
|
5599
5702
|
let finalText = null;
|
|
5600
5703
|
let finalError = null;
|
|
5601
5704
|
for await (const event of context.agentManager.run({
|
|
5602
5705
|
sessionKey: parsed.sessionKey,
|
|
5603
|
-
prompt
|
|
5706
|
+
prompt,
|
|
5604
5707
|
modelOverride: parsed.model,
|
|
5605
5708
|
meta: {
|
|
5606
5709
|
source: "interactive",
|
|
5607
|
-
channel: "cli"
|
|
5710
|
+
channel: "cli",
|
|
5711
|
+
...sender ? { sender } : {}
|
|
5608
5712
|
}
|
|
5609
5713
|
})) {
|
|
5610
5714
|
const maybeText = extractAssistantText(event);
|
|
@@ -6271,6 +6375,24 @@ var HovClawGatewayServer = class {
|
|
|
6271
6375
|
this.httpServer = createServer((req, res) => {
|
|
6272
6376
|
this.handleHttpRequest(req, res);
|
|
6273
6377
|
});
|
|
6378
|
+
this.httpServer.on("error", (error) => {
|
|
6379
|
+
logger.error({
|
|
6380
|
+
error,
|
|
6381
|
+
host: this.runtimeConfig.gateway.host,
|
|
6382
|
+
port: this.runtimeConfig.gateway.port
|
|
6383
|
+
}, "Gateway server error; continuing without gateway listener");
|
|
6384
|
+
if (this.tickTimer) {
|
|
6385
|
+
clearInterval(this.tickTimer);
|
|
6386
|
+
this.tickTimer = null;
|
|
6387
|
+
}
|
|
6388
|
+
for (const state of this.connections.values()) state.socket.close(1011, "gateway-error");
|
|
6389
|
+
this.connections.clear();
|
|
6390
|
+
if (this.wss) {
|
|
6391
|
+
this.wss.close();
|
|
6392
|
+
this.wss = null;
|
|
6393
|
+
}
|
|
6394
|
+
this.httpServer = null;
|
|
6395
|
+
});
|
|
6274
6396
|
this.httpServer.on("upgrade", (request, socket, head) => {
|
|
6275
6397
|
if (!this.wss) {
|
|
6276
6398
|
socket.destroy();
|
|
@@ -6433,6 +6555,10 @@ var HovClawGatewayServer = class {
|
|
|
6433
6555
|
return;
|
|
6434
6556
|
}
|
|
6435
6557
|
state.connected = true;
|
|
6558
|
+
state.client = {
|
|
6559
|
+
id: connectParams.client.id,
|
|
6560
|
+
...connectParams.client.displayName ? { displayName: connectParams.client.displayName } : {}
|
|
6561
|
+
};
|
|
6436
6562
|
sendResponse(state.socket, request.id, true, {
|
|
6437
6563
|
type: "hello-ok",
|
|
6438
6564
|
protocol: PROTOCOL_VERSION,
|
|
@@ -6512,6 +6638,11 @@ var HovClawGatewayServer = class {
|
|
|
6512
6638
|
readFileConfig: this.readFileConfig,
|
|
6513
6639
|
writeFileConfig: this.writeFileConfig,
|
|
6514
6640
|
getChannelStatus: this.getChannelStatus,
|
|
6641
|
+
getConnectionClient: (connId) => {
|
|
6642
|
+
const connection = this.connections.get(connId);
|
|
6643
|
+
if (!connection?.client) return null;
|
|
6644
|
+
return connection.client;
|
|
6645
|
+
},
|
|
6515
6646
|
emitEvent: (event, payload, connId) => {
|
|
6516
6647
|
this.emitEvent(event, payload, connId);
|
|
6517
6648
|
}
|
|
@@ -8107,16 +8238,25 @@ async function main() {
|
|
|
8107
8238
|
}
|
|
8108
8239
|
}
|
|
8109
8240
|
normalizedPrompt = applyThinkingLevel(normalizedPrompt, thinkingLevel, thinkingLevelForced);
|
|
8241
|
+
const sender = {
|
|
8242
|
+
displayName: msg.displayName,
|
|
8243
|
+
userId: msg.userId,
|
|
8244
|
+
channel: msg.channel,
|
|
8245
|
+
...msg.accountId ? { accountId: msg.accountId } : {},
|
|
8246
|
+
...msg.peer?.kind ? { peerKind: msg.peer.kind } : {}
|
|
8247
|
+
};
|
|
8248
|
+
const promptWithSender = prependSenderContext(normalizedPrompt, sender);
|
|
8110
8249
|
try {
|
|
8111
8250
|
await channel.setTyping?.(target, true);
|
|
8112
8251
|
let finalText = null;
|
|
8113
8252
|
let finalError = null;
|
|
8114
8253
|
for await (const event of agentManager.run({
|
|
8115
8254
|
sessionKey,
|
|
8116
|
-
prompt:
|
|
8255
|
+
prompt: promptWithSender,
|
|
8117
8256
|
meta: {
|
|
8118
8257
|
source: "interactive",
|
|
8119
|
-
channel: msg.channel
|
|
8258
|
+
channel: msg.channel,
|
|
8259
|
+
sender
|
|
8120
8260
|
}
|
|
8121
8261
|
})) {
|
|
8122
8262
|
const maybe = extractAssistantText(event);
|
|
@@ -1553,6 +1553,39 @@ const channelsLogoutMethod = async (params, context) => {
|
|
|
1553
1553
|
return { ok: true };
|
|
1554
1554
|
};
|
|
1555
1555
|
|
|
1556
|
+
//#endregion
|
|
1557
|
+
//#region src/inbound-context.ts
|
|
1558
|
+
const SENDER_DISPLAY_NAME_MAX_CHARS = 80;
|
|
1559
|
+
const SENDER_TOKEN_MAX_CHARS = 80;
|
|
1560
|
+
const SENDER_CONTEXT_PREFIX = "[Sender Context]";
|
|
1561
|
+
function normalizeInlineToken(value, maxChars, fallback) {
|
|
1562
|
+
const resolved = (value ?? "").replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim() || fallback;
|
|
1563
|
+
if (resolved.length <= maxChars) return resolved;
|
|
1564
|
+
return `${resolved.slice(0, maxChars - 3)}...`;
|
|
1565
|
+
}
|
|
1566
|
+
function sanitizeSenderDisplayName(displayName, fallbackUserId) {
|
|
1567
|
+
return normalizeInlineToken(displayName, SENDER_DISPLAY_NAME_MAX_CHARS, fallbackUserId);
|
|
1568
|
+
}
|
|
1569
|
+
function buildSenderContextEnvelope(sender) {
|
|
1570
|
+
const userId = normalizeInlineToken(sender.userId, SENDER_TOKEN_MAX_CHARS, "unknown");
|
|
1571
|
+
const displayName = sanitizeSenderDisplayName(sender.displayName, userId);
|
|
1572
|
+
const accountId = sender.accountId ? normalizeInlineToken(sender.accountId, SENDER_TOKEN_MAX_CHARS, "") : "";
|
|
1573
|
+
return [
|
|
1574
|
+
SENDER_CONTEXT_PREFIX,
|
|
1575
|
+
`displayName=${displayName}`,
|
|
1576
|
+
`userId=${userId}`,
|
|
1577
|
+
`channel=${sender.channel}`,
|
|
1578
|
+
...accountId ? [`accountId=${accountId}`] : [],
|
|
1579
|
+
...sender.peerKind ? [`peerKind=${sender.peerKind}`] : [],
|
|
1580
|
+
""
|
|
1581
|
+
].join("\n");
|
|
1582
|
+
}
|
|
1583
|
+
function prependSenderContext(prompt, sender) {
|
|
1584
|
+
if (!sender) return prompt;
|
|
1585
|
+
if (prompt.startsWith(`${SENDER_CONTEXT_PREFIX}\n`)) return prompt;
|
|
1586
|
+
return `${buildSenderContextEnvelope(sender)}${prompt}`;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1556
1589
|
//#endregion
|
|
1557
1590
|
//#region src/gateway/methods/chat.ts
|
|
1558
1591
|
const chatHistoryParamsSchema = z.object({ sessionKey: z.string().min(1) });
|
|
@@ -1571,15 +1604,23 @@ const chatHistoryMethod = async (params, context) => {
|
|
|
1571
1604
|
};
|
|
1572
1605
|
const chatSendMethod = async (params, context, connId) => {
|
|
1573
1606
|
const parsed = chatSendParamsSchema.parse(params);
|
|
1607
|
+
const client = context.getConnectionClient?.(connId) ?? null;
|
|
1608
|
+
const sender = client ? {
|
|
1609
|
+
displayName: client.displayName ?? client.id,
|
|
1610
|
+
userId: client.id,
|
|
1611
|
+
channel: "cli"
|
|
1612
|
+
} : void 0;
|
|
1613
|
+
const prompt = prependSenderContext(parsed.prompt, sender);
|
|
1574
1614
|
let finalText = null;
|
|
1575
1615
|
let finalError = null;
|
|
1576
1616
|
for await (const event of context.agentManager.run({
|
|
1577
1617
|
sessionKey: parsed.sessionKey,
|
|
1578
|
-
prompt
|
|
1618
|
+
prompt,
|
|
1579
1619
|
modelOverride: parsed.model,
|
|
1580
1620
|
meta: {
|
|
1581
1621
|
source: "interactive",
|
|
1582
|
-
channel: "cli"
|
|
1622
|
+
channel: "cli",
|
|
1623
|
+
...sender ? { sender } : {}
|
|
1583
1624
|
}
|
|
1584
1625
|
})) {
|
|
1585
1626
|
const maybeText = extractAssistantText(event);
|
|
@@ -2164,6 +2205,24 @@ var HovClawGatewayServer = class {
|
|
|
2164
2205
|
this.httpServer = createServer((req, res) => {
|
|
2165
2206
|
this.handleHttpRequest(req, res);
|
|
2166
2207
|
});
|
|
2208
|
+
this.httpServer.on("error", (error) => {
|
|
2209
|
+
logger.error({
|
|
2210
|
+
error,
|
|
2211
|
+
host: this.runtimeConfig.gateway.host,
|
|
2212
|
+
port: this.runtimeConfig.gateway.port
|
|
2213
|
+
}, "Gateway server error; continuing without gateway listener");
|
|
2214
|
+
if (this.tickTimer) {
|
|
2215
|
+
clearInterval(this.tickTimer);
|
|
2216
|
+
this.tickTimer = null;
|
|
2217
|
+
}
|
|
2218
|
+
for (const state of this.connections.values()) state.socket.close(1011, "gateway-error");
|
|
2219
|
+
this.connections.clear();
|
|
2220
|
+
if (this.wss) {
|
|
2221
|
+
this.wss.close();
|
|
2222
|
+
this.wss = null;
|
|
2223
|
+
}
|
|
2224
|
+
this.httpServer = null;
|
|
2225
|
+
});
|
|
2167
2226
|
this.httpServer.on("upgrade", (request, socket, head) => {
|
|
2168
2227
|
if (!this.wss) {
|
|
2169
2228
|
socket.destroy();
|
|
@@ -2326,6 +2385,10 @@ var HovClawGatewayServer = class {
|
|
|
2326
2385
|
return;
|
|
2327
2386
|
}
|
|
2328
2387
|
state.connected = true;
|
|
2388
|
+
state.client = {
|
|
2389
|
+
id: connectParams.client.id,
|
|
2390
|
+
...connectParams.client.displayName ? { displayName: connectParams.client.displayName } : {}
|
|
2391
|
+
};
|
|
2329
2392
|
sendResponse(state.socket, request.id, true, {
|
|
2330
2393
|
type: "hello-ok",
|
|
2331
2394
|
protocol: PROTOCOL_VERSION,
|
|
@@ -2405,6 +2468,11 @@ var HovClawGatewayServer = class {
|
|
|
2405
2468
|
readFileConfig: this.readFileConfig,
|
|
2406
2469
|
writeFileConfig: this.writeFileConfig,
|
|
2407
2470
|
getChannelStatus: this.getChannelStatus,
|
|
2471
|
+
getConnectionClient: (connId) => {
|
|
2472
|
+
const connection = this.connections.get(connId);
|
|
2473
|
+
if (!connection?.client) return null;
|
|
2474
|
+
return connection.client;
|
|
2475
|
+
},
|
|
2408
2476
|
emitEvent: (event, payload, connId) => {
|
|
2409
2477
|
this.emitEvent(event, payload, connId);
|
|
2410
2478
|
}
|
|
@@ -2968,16 +3036,25 @@ async function main() {
|
|
|
2968
3036
|
}
|
|
2969
3037
|
}
|
|
2970
3038
|
normalizedPrompt = applyThinkingLevel(normalizedPrompt, thinkingLevel, thinkingLevelForced);
|
|
3039
|
+
const sender = {
|
|
3040
|
+
displayName: msg.displayName,
|
|
3041
|
+
userId: msg.userId,
|
|
3042
|
+
channel: msg.channel,
|
|
3043
|
+
...msg.accountId ? { accountId: msg.accountId } : {},
|
|
3044
|
+
...msg.peer?.kind ? { peerKind: msg.peer.kind } : {}
|
|
3045
|
+
};
|
|
3046
|
+
const promptWithSender = prependSenderContext(normalizedPrompt, sender);
|
|
2971
3047
|
try {
|
|
2972
3048
|
await channel.setTyping?.(target, true);
|
|
2973
3049
|
let finalText = null;
|
|
2974
3050
|
let finalError = null;
|
|
2975
3051
|
for await (const event of agentManager.run({
|
|
2976
3052
|
sessionKey,
|
|
2977
|
-
prompt:
|
|
3053
|
+
prompt: promptWithSender,
|
|
2978
3054
|
meta: {
|
|
2979
3055
|
source: "interactive",
|
|
2980
|
-
channel: msg.channel
|
|
3056
|
+
channel: msg.channel,
|
|
3057
|
+
sender
|
|
2981
3058
|
}
|
|
2982
3059
|
})) {
|
|
2983
3060
|
const maybe = extractAssistantText(event);
|