mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
fix(ci): restore main lint/typecheck after direct merges
This commit is contained in:
@@ -3,23 +3,25 @@
|
||||
在你的项目中导入:
|
||||
|
||||
```javascript
|
||||
const translations = require('./translations/zh-CN.json');
|
||||
console.log(translations['Save']); // 输出:保存
|
||||
const translations = require("./translations/zh-CN.json");
|
||||
console.log(translations["Save"]); // 输出:保存
|
||||
```
|
||||
|
||||
## 继续翻译工作
|
||||
|
||||
1. **提取 OpenClaw 界面字符串**
|
||||
1. **提取 OpenClaw 界面字符串**
|
||||
|
||||
```bash
|
||||
node scripts/extract-strings.js
|
||||
```
|
||||
|
||||
2. **过滤真正的界面文本**
|
||||
2. **过滤真正的界面文本**
|
||||
|
||||
```bash
|
||||
node scripts/filter-real-ui.js
|
||||
```
|
||||
|
||||
3. **翻译剩余的字符串**
|
||||
3. **翻译剩余的字符串**
|
||||
- 编辑 `translations/ui-only.json`
|
||||
|
||||
## 🛠️ 工具说明
|
||||
@@ -64,10 +66,12 @@ extensions/openclaw-zh-cn-ui/
|
||||
## 📈 路线图
|
||||
|
||||
### 短期目标
|
||||
|
||||
- 完成剩余翻译
|
||||
- 提交 Pull Request
|
||||
|
||||
### 长期目标
|
||||
|
||||
- 支持更多语言
|
||||
- 创建翻译平台
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { whatsappPlugin } from "./channel.js";
|
||||
|
||||
// Mock runtime
|
||||
const mockSendMessageWhatsApp = vi.fn().mockResolvedValue({ messageId: "123", toJid: "123@s.whatsapp.net" });
|
||||
const mockSendMessageWhatsApp = vi
|
||||
.fn()
|
||||
.mockResolvedValue({ messageId: "123", toJid: "123@s.whatsapp.net" });
|
||||
|
||||
vi.mock("./runtime.js", () => ({
|
||||
getWhatsAppRuntime: () => ({
|
||||
@@ -35,7 +37,7 @@ describe("whatsappPlugin.outbound.sendText", () => {
|
||||
"http://example.com",
|
||||
expect.objectContaining({
|
||||
linkPreview: false,
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -50,7 +52,7 @@ describe("whatsappPlugin.outbound.sendText", () => {
|
||||
"hello",
|
||||
expect.objectContaining({
|
||||
linkPreview: undefined,
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,10 +73,18 @@ async function listJsonlFiles(dir: string): Promise<string[]> {
|
||||
function safeParseLine(line: string): CronRunLogEntry | null {
|
||||
try {
|
||||
const obj = JSON.parse(line) as Partial<CronRunLogEntry> | null;
|
||||
if (!obj || typeof obj !== "object") return null;
|
||||
if (obj.action !== "finished") return null;
|
||||
if (typeof obj.ts !== "number" || !Number.isFinite(obj.ts)) return null;
|
||||
if (typeof obj.jobId !== "string" || !obj.jobId.trim()) return null;
|
||||
if (!obj || typeof obj !== "object") {
|
||||
return null;
|
||||
}
|
||||
if (obj.action !== "finished") {
|
||||
return null;
|
||||
}
|
||||
if (typeof obj.ts !== "number" || !Number.isFinite(obj.ts)) {
|
||||
return null;
|
||||
}
|
||||
if (typeof obj.jobId !== "string" || !obj.jobId.trim()) {
|
||||
return null;
|
||||
}
|
||||
return obj as CronRunLogEntry;
|
||||
} catch {
|
||||
return null;
|
||||
@@ -91,7 +99,8 @@ export async function main() {
|
||||
const args = parseArgs(process.argv);
|
||||
const store = typeof args.store === "string" ? args.store : undefined;
|
||||
const runsDirArg = typeof args.runsDir === "string" ? args.runsDir : undefined;
|
||||
const runsDir = runsDirArg ?? (store ? path.join(path.dirname(path.resolve(store)), "runs") : null);
|
||||
const runsDir =
|
||||
runsDirArg ?? (store ? path.join(path.dirname(path.resolve(store)), "runs") : null);
|
||||
if (!runsDir) {
|
||||
usageAndExit(2);
|
||||
}
|
||||
@@ -138,19 +147,31 @@ export async function main() {
|
||||
|
||||
for (const file of files) {
|
||||
const raw = await fs.readFile(file, "utf-8").catch(() => "");
|
||||
if (!raw.trim()) continue;
|
||||
if (!raw.trim()) {
|
||||
continue;
|
||||
}
|
||||
const lines = raw.split("\n");
|
||||
for (const line of lines) {
|
||||
const entry = safeParseLine(line.trim());
|
||||
if (!entry) continue;
|
||||
if (entry.ts < fromMs || entry.ts > toMs) continue;
|
||||
if (filterJobId && entry.jobId !== filterJobId) continue;
|
||||
if (!entry) {
|
||||
continue;
|
||||
}
|
||||
if (entry.ts < fromMs || entry.ts > toMs) {
|
||||
continue;
|
||||
}
|
||||
if (filterJobId && entry.jobId !== filterJobId) {
|
||||
continue;
|
||||
}
|
||||
const model = (entry.model ?? "<unknown>").trim() || "<unknown>";
|
||||
if (filterModel && model !== filterModel) continue;
|
||||
if (filterModel && model !== filterModel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const jobId = entry.jobId;
|
||||
const usage = entry.usage;
|
||||
const hasUsage = Boolean(usage && (usage.total_tokens ?? usage.input_tokens ?? usage.output_tokens) !== undefined);
|
||||
const hasUsage = Boolean(
|
||||
usage && (usage.total_tokens ?? usage.input_tokens ?? usage.output_tokens) !== undefined,
|
||||
);
|
||||
|
||||
const jobAgg = (totalsByJob[jobId] ??= {
|
||||
jobId,
|
||||
@@ -219,8 +240,12 @@ export async function main() {
|
||||
console.log(`Cron usage report`);
|
||||
console.log(` runsDir: ${runsDir}`);
|
||||
console.log(` window: ${new Date(fromMs).toISOString()} → ${new Date(toMs).toISOString()}`);
|
||||
if (filterJobId) console.log(` filter jobId: ${filterJobId}`);
|
||||
if (filterModel) console.log(` filter model: ${filterModel}`);
|
||||
if (filterJobId) {
|
||||
console.log(` filter jobId: ${filterJobId}`);
|
||||
}
|
||||
if (filterModel) {
|
||||
console.log(` filter model: ${filterModel}`);
|
||||
}
|
||||
console.log("");
|
||||
|
||||
if (rows.length === 0) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { StreamFn } from "@mariozechner/pi-agent-core";
|
||||
import type { Context, Model } from "@mariozechner/pi-ai";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { applyExtraParamsToAgent } from "./extra-params.js";
|
||||
|
||||
// Mock streamSimple for testing
|
||||
@@ -13,7 +12,6 @@ vi.mock("@mariozechner/pi-ai", () => ({
|
||||
|
||||
describe("extra-params: Z.AI tool_stream support", () => {
|
||||
it("should inject tool_stream=true for zai provider by default", () => {
|
||||
const capturedPayloads: unknown[] = [];
|
||||
const mockStreamFn: StreamFn = vi.fn((model, context, options) => {
|
||||
// Capture the payload that would be sent
|
||||
options?.onPayload?.({ model: model.id, messages: [] });
|
||||
@@ -24,7 +22,7 @@ describe("extra-params: Z.AI tool_stream support", () => {
|
||||
content: [{ type: "text", text: "ok" }],
|
||||
stopReason: "stop",
|
||||
}),
|
||||
} as any;
|
||||
} as unknown as ReturnType<StreamFn>;
|
||||
});
|
||||
|
||||
const agent = { streamFn: mockStreamFn };
|
||||
@@ -34,7 +32,12 @@ describe("extra-params: Z.AI tool_stream support", () => {
|
||||
},
|
||||
};
|
||||
|
||||
applyExtraParamsToAgent(agent, cfg as any, "zai", "glm-5");
|
||||
applyExtraParamsToAgent(
|
||||
agent,
|
||||
cfg as unknown as Parameters<typeof applyExtraParamsToAgent>[1],
|
||||
"zai",
|
||||
"glm-5",
|
||||
);
|
||||
|
||||
// The streamFn should be wrapped
|
||||
expect(agent.streamFn).toBeDefined();
|
||||
@@ -42,33 +45,44 @@ describe("extra-params: Z.AI tool_stream support", () => {
|
||||
});
|
||||
|
||||
it("should not inject tool_stream for non-zai providers", () => {
|
||||
const mockStreamFn: StreamFn = vi.fn(() => ({
|
||||
push: vi.fn(),
|
||||
result: vi.fn().mockResolvedValue({
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "ok" }],
|
||||
stopReason: "stop",
|
||||
}),
|
||||
} as any));
|
||||
const mockStreamFn: StreamFn = vi.fn(
|
||||
() =>
|
||||
({
|
||||
push: vi.fn(),
|
||||
result: vi.fn().mockResolvedValue({
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "ok" }],
|
||||
stopReason: "stop",
|
||||
}),
|
||||
}) as unknown as ReturnType<StreamFn>,
|
||||
);
|
||||
|
||||
const agent = { streamFn: mockStreamFn };
|
||||
const cfg = {};
|
||||
|
||||
applyExtraParamsToAgent(agent, cfg as any, "anthropic", "claude-opus-4-6");
|
||||
applyExtraParamsToAgent(
|
||||
agent,
|
||||
cfg as unknown as Parameters<typeof applyExtraParamsToAgent>[1],
|
||||
"anthropic",
|
||||
"claude-opus-4-6",
|
||||
);
|
||||
|
||||
// Should remain unchanged (except for OpenAI wrapper)
|
||||
expect(agent.streamFn).toBeDefined();
|
||||
});
|
||||
|
||||
it("should allow disabling tool_stream via params", () => {
|
||||
const mockStreamFn: StreamFn = vi.fn(() => ({
|
||||
push: vi.fn(),
|
||||
result: vi.fn().mockResolvedValue({
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "ok" }],
|
||||
stopReason: "stop",
|
||||
}),
|
||||
} as any));
|
||||
const mockStreamFn: StreamFn = vi.fn(
|
||||
() =>
|
||||
({
|
||||
push: vi.fn(),
|
||||
result: vi.fn().mockResolvedValue({
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "ok" }],
|
||||
stopReason: "stop",
|
||||
}),
|
||||
}) as unknown as ReturnType<StreamFn>,
|
||||
);
|
||||
|
||||
const agent = { streamFn: mockStreamFn };
|
||||
const cfg = {
|
||||
@@ -85,7 +99,12 @@ describe("extra-params: Z.AI tool_stream support", () => {
|
||||
},
|
||||
};
|
||||
|
||||
applyExtraParamsToAgent(agent, cfg as any, "zai", "glm-5");
|
||||
applyExtraParamsToAgent(
|
||||
agent,
|
||||
cfg as unknown as Parameters<typeof applyExtraParamsToAgent>[1],
|
||||
"zai",
|
||||
"glm-5",
|
||||
);
|
||||
|
||||
// The tool_stream wrapper should be applied but with enabled=false
|
||||
// In this case, it should just return the underlying streamFn
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import type { TextContent } from "@mariozechner/pi-ai";
|
||||
import type { SessionManager } from "@mariozechner/pi-coding-agent";
|
||||
import type {
|
||||
PluginHookBeforeMessageWriteEvent,
|
||||
PluginHookBeforeMessageWriteResult,
|
||||
} from "../plugins/types.js";
|
||||
import type { TextContent } from "@mariozechner/pi-ai";
|
||||
import type { SessionManager } from "@mariozechner/pi-coding-agent";
|
||||
import { emitSessionTranscriptUpdate } from "../sessions/transcript-events.js";
|
||||
import { HARD_MAX_TOOL_RESULT_CHARS } from "./pi-embedded-runner/tool-result-truncation.js";
|
||||
import { makeMissingToolResult, sanitizeToolCallInputs } from "./session-transcript-repair.js";
|
||||
@@ -132,10 +132,16 @@ export function installSessionToolResultGuard(
|
||||
* or null if the message should be blocked.
|
||||
*/
|
||||
const applyBeforeWriteHook = (msg: AgentMessage): AgentMessage | null => {
|
||||
if (!beforeWrite) return msg;
|
||||
if (!beforeWrite) {
|
||||
return msg;
|
||||
}
|
||||
const result = beforeWrite({ message: msg });
|
||||
if (result?.block) return null;
|
||||
if (result?.message) return result.message;
|
||||
if (result?.block) {
|
||||
return null;
|
||||
}
|
||||
if (result?.message) {
|
||||
return result.message;
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
|
||||
@@ -192,7 +198,9 @@ export function installSessionToolResultGuard(
|
||||
isSynthetic: false,
|
||||
}),
|
||||
);
|
||||
if (!persisted) return undefined;
|
||||
if (!persisted) {
|
||||
return undefined;
|
||||
}
|
||||
return originalAppend(persisted as never);
|
||||
}
|
||||
|
||||
@@ -213,7 +221,9 @@ export function installSessionToolResultGuard(
|
||||
}
|
||||
|
||||
const finalMessage = applyBeforeWriteHook(persistMessage(nextMessage));
|
||||
if (!finalMessage) return undefined;
|
||||
if (!finalMessage) {
|
||||
return undefined;
|
||||
}
|
||||
const result = originalAppend(finalMessage as never);
|
||||
|
||||
const sessionFile = (
|
||||
|
||||
@@ -97,10 +97,10 @@ function generateHtml(sessionData: SessionData): string {
|
||||
|
||||
// Build CSS with theme variables
|
||||
const css = templateCss
|
||||
.replace("{{THEME_VARS}}", themeVars)
|
||||
.replace("{{BODY_BG}}", bodyBg)
|
||||
.replace("{{CONTAINER_BG}}", containerBg)
|
||||
.replace("{{INFO_BG}}", infoBg);
|
||||
.replace("/* {{THEME_VARS}} */", themeVars.trim())
|
||||
.replace("/* {{BODY_BG_DECL}} */", `--body-bg: ${bodyBg};`)
|
||||
.replace("/* {{CONTAINER_BG_DECL}} */", `--container-bg: ${containerBg};`)
|
||||
.replace("/* {{INFO_BG_DECL}} */", `--info-bg: ${infoBg};`);
|
||||
|
||||
return template
|
||||
.replace("{{CSS}}", css)
|
||||
@@ -234,7 +234,7 @@ export async function buildExportSessionReply(params: HandleCommandsParams): Pro
|
||||
const args = parseExportArgs(params.command.commandBodyNormalized);
|
||||
|
||||
// 1. Resolve session file
|
||||
const sessionEntry = params.sessionEntry as SessionEntry | undefined;
|
||||
const sessionEntry = params.sessionEntry;
|
||||
if (!sessionEntry?.sessionId) {
|
||||
return { text: "❌ No active session found." };
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ function trimMeshPlanCache() {
|
||||
return;
|
||||
}
|
||||
const oldest = [...meshPlanCache.entries()]
|
||||
.sort((a, b) => a[1].createdAt - b[1].createdAt)
|
||||
.toSorted((a, b) => a[1].createdAt - b[1].createdAt)
|
||||
.slice(0, meshPlanCache.size - MAX_CACHED_MESH_PLANS);
|
||||
for (const [key] of oldest) {
|
||||
meshPlanCache.delete(key);
|
||||
@@ -110,7 +110,10 @@ function putCachedPlan(params: Parameters<CommandHandler>[0], plan: MeshPlanShap
|
||||
trimMeshPlanCache();
|
||||
}
|
||||
|
||||
function getCachedPlan(params: Parameters<CommandHandler>[0], planId: string): MeshPlanShape | null {
|
||||
function getCachedPlan(
|
||||
params: Parameters<CommandHandler>[0],
|
||||
planId: string,
|
||||
): MeshPlanShape | null {
|
||||
return meshPlanCache.get(cacheKeyForPlan(params, planId))?.plan ?? null;
|
||||
}
|
||||
|
||||
@@ -190,7 +193,9 @@ export const handleMeshCommand: CommandHandler = async (params, allowTextCommand
|
||||
return null;
|
||||
}
|
||||
if (!params.command.isAuthorizedSender) {
|
||||
logVerbose(`Ignoring /mesh from unauthorized sender: ${params.command.senderId || "<unknown>"}`);
|
||||
logVerbose(
|
||||
`Ignoring /mesh from unauthorized sender: ${params.command.senderId || "<unknown>"}`,
|
||||
);
|
||||
return { shouldContinue: false };
|
||||
}
|
||||
if (!parsed.ok) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,54 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Session Export</title>
|
||||
<style>
|
||||
{{CSS}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<button id="hamburger" title="Open sidebar"><svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="none"><circle cx="6" cy="6" r="2.5"/><circle cx="6" cy="18" r="2.5"/><circle cx="18" cy="12" r="2.5"/><rect x="5" y="6" width="2" height="12"/><path d="M6 12h10c1 0 2 0 2-2V8"/></svg></button>
|
||||
<div id="sidebar-overlay"></div>
|
||||
<div id="app">
|
||||
<aside id="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<div class="sidebar-controls">
|
||||
<input type="text" class="sidebar-search" id="tree-search" placeholder="Search...">
|
||||
</div>
|
||||
<div class="sidebar-filters">
|
||||
<button class="filter-btn active" data-filter="default" title="Hide settings entries">Default</button>
|
||||
<button class="filter-btn" data-filter="no-tools" title="Default minus tool results">No-tools</button>
|
||||
<button class="filter-btn" data-filter="user-only" title="Only user messages">User</button>
|
||||
<button class="filter-btn" data-filter="labeled-only" title="Only labeled entries">Labeled</button>
|
||||
<button class="filter-btn" data-filter="all" title="Show everything">All</button>
|
||||
<button class="sidebar-close" id="sidebar-close" title="Close">✕</button>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Session Export</title>
|
||||
<style>
|
||||
{{CSS}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<button id="hamburger" title="Open sidebar">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||
<circle cx="6" cy="6" r="2.5" />
|
||||
<circle cx="6" cy="18" r="2.5" />
|
||||
<circle cx="18" cy="12" r="2.5" />
|
||||
<rect x="5" y="6" width="2" height="12" />
|
||||
<path d="M6 12h10c1 0 2 0 2-2V8" />
|
||||
</svg>
|
||||
</button>
|
||||
<div id="sidebar-overlay"></div>
|
||||
<div id="app">
|
||||
<aside id="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<div class="sidebar-controls">
|
||||
<input type="text" class="sidebar-search" id="tree-search" placeholder="Search..." />
|
||||
</div>
|
||||
<div class="sidebar-filters">
|
||||
<button class="filter-btn active" data-filter="default" title="Hide settings entries">
|
||||
Default
|
||||
</button>
|
||||
<button class="filter-btn" data-filter="no-tools" title="Default minus tool results">
|
||||
No-tools
|
||||
</button>
|
||||
<button class="filter-btn" data-filter="user-only" title="Only user messages">
|
||||
User
|
||||
</button>
|
||||
<button class="filter-btn" data-filter="labeled-only" title="Only labeled entries">
|
||||
Labeled
|
||||
</button>
|
||||
<button class="filter-btn" data-filter="all" title="Show everything">All</button>
|
||||
<button class="sidebar-close" id="sidebar-close" title="Close">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-container" id="tree-container"></div>
|
||||
<div class="tree-status" id="tree-status"></div>
|
||||
</aside>
|
||||
<main id="content">
|
||||
<div id="header-container"></div>
|
||||
<div id="messages"></div>
|
||||
</main>
|
||||
<div id="image-modal" class="image-modal">
|
||||
<img id="modal-image" src="" alt="" />
|
||||
</div>
|
||||
<div class="tree-container" id="tree-container"></div>
|
||||
<div class="tree-status" id="tree-status"></div>
|
||||
</aside>
|
||||
<main id="content">
|
||||
<div id="header-container"></div>
|
||||
<div id="messages"></div>
|
||||
</main>
|
||||
<div id="image-modal" class="image-modal">
|
||||
<img id="modal-image" src="" alt="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script id="session-data" type="application/json">{{SESSION_DATA}}</script>
|
||||
<script id="session-data" type="application/json">
|
||||
{{SESSION_DATA}}
|
||||
</script>
|
||||
|
||||
<!-- Vendored libraries -->
|
||||
<script>{{MARKED_JS}}</script>
|
||||
<!-- Vendored libraries -->
|
||||
<script>
|
||||
{
|
||||
{
|
||||
MARKED_JS;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- highlight.js -->
|
||||
<script>{{HIGHLIGHT_JS}}</script>
|
||||
<!-- highlight.js -->
|
||||
<script>
|
||||
{
|
||||
{
|
||||
HIGHLIGHT_JS;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Main application code -->
|
||||
<script>
|
||||
{{JS}}
|
||||
</script>
|
||||
</body>
|
||||
<!-- Main application code -->
|
||||
<script>
|
||||
{
|
||||
{
|
||||
JS;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -130,7 +130,9 @@ export async function initSessionState(params: {
|
||||
// Stale cache (especially with multiple gateway processes or on Windows where
|
||||
// mtime granularity may miss rapid writes) can cause incorrect sessionId
|
||||
// generation, leading to orphaned transcript files. See #17971.
|
||||
const sessionStore: Record<string, SessionEntry> = loadSessionStore(storePath, { skipCache: true });
|
||||
const sessionStore: Record<string, SessionEntry> = loadSessionStore(storePath, {
|
||||
skipCache: true,
|
||||
});
|
||||
let sessionKey: string | undefined;
|
||||
let sessionEntry: SessionEntry;
|
||||
|
||||
|
||||
@@ -235,31 +235,6 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "poll") {
|
||||
const to = readStringParam(params, "to", { required: true });
|
||||
const question =
|
||||
readStringParam(params, "pollQuestion") ??
|
||||
readStringParam(params, "question", { required: true });
|
||||
const options =
|
||||
readStringArrayParam(params, "pollOption") ?? readStringArrayParam(params, "options");
|
||||
const threadId = readStringParam(params, "threadId");
|
||||
const replyTo = readStringParam(params, "replyTo");
|
||||
const silent = typeof params.silent === "boolean" ? params.silent : undefined;
|
||||
return await handleTelegramAction(
|
||||
{
|
||||
action: "sendPoll",
|
||||
to,
|
||||
question,
|
||||
options,
|
||||
replyTo: replyTo != null ? Number(replyTo) : undefined,
|
||||
threadId: threadId != null ? Number(threadId) : undefined,
|
||||
silent,
|
||||
accountId: accountId ?? undefined,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(`Action ${action} is not supported for provider ${providerId}.`);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -36,6 +36,7 @@ function createActionIO(params: { action: DaemonAction; json: boolean }) {
|
||||
message?: string;
|
||||
error?: string;
|
||||
hints?: string[];
|
||||
warnings?: string[];
|
||||
service?: {
|
||||
label: string;
|
||||
loaded: boolean;
|
||||
|
||||
@@ -171,10 +171,7 @@ export function loadSessionStore(
|
||||
let store: Record<string, SessionEntry> = {};
|
||||
let mtimeMs = getFileMtimeMs(storePath);
|
||||
const maxReadAttempts = process.platform === "win32" ? 3 : 1;
|
||||
const retryBuf =
|
||||
maxReadAttempts > 1
|
||||
? new Int32Array(new SharedArrayBuffer(4))
|
||||
: undefined;
|
||||
const retryBuf = maxReadAttempts > 1 ? new Int32Array(new SharedArrayBuffer(4)) : undefined;
|
||||
for (let attempt = 0; attempt < maxReadAttempts; attempt++) {
|
||||
try {
|
||||
const raw = fs.readFileSync(storePath, "utf-8");
|
||||
@@ -587,9 +584,7 @@ async function saveSessionStoreUnlocked(
|
||||
// Final attempt failed — skip this save. The write lock ensures
|
||||
// the next save will retry with fresh data. Log for diagnostics.
|
||||
if (i === 4) {
|
||||
console.warn(
|
||||
`[session-store] rename failed after 5 attempts: ${storePath}`,
|
||||
);
|
||||
console.warn(`[session-store] rename failed after 5 attempts: ${storePath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -641,7 +641,13 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
}
|
||||
} catch (err) {
|
||||
if (!deliveryBestEffort) {
|
||||
return withRunSession({ status: "error", summary, outputText, error: String(err), ...telemetry });
|
||||
return withRunSession({
|
||||
status: "error",
|
||||
summary,
|
||||
outputText,
|
||||
error: String(err),
|
||||
...telemetry,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (synthesizedText) {
|
||||
@@ -739,7 +745,13 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
}
|
||||
} catch (err) {
|
||||
if (!deliveryBestEffort) {
|
||||
return withRunSession({ status: "error", summary, outputText, error: String(err), ...telemetry });
|
||||
return withRunSession({
|
||||
status: "error",
|
||||
summary,
|
||||
outputText,
|
||||
error: String(err),
|
||||
...telemetry,
|
||||
});
|
||||
}
|
||||
logWarn(`[cron:${params.job.id}] ${String(err)}`);
|
||||
}
|
||||
|
||||
@@ -8,10 +8,7 @@ vi.mock("../../config/sessions.js", () => ({
|
||||
resolveSessionResetPolicy: vi.fn().mockReturnValue({ mode: "idle", idleMinutes: 60 }),
|
||||
}));
|
||||
|
||||
import {
|
||||
loadSessionStore,
|
||||
evaluateSessionFreshness,
|
||||
} from "../../config/sessions.js";
|
||||
import { loadSessionStore, evaluateSessionFreshness } from "../../config/sessions.js";
|
||||
import { resolveCronSession } from "./session.js";
|
||||
|
||||
describe("resolveCronSession", () => {
|
||||
@@ -153,7 +150,11 @@ describe("resolveCronSession", () => {
|
||||
"webhook:stable-key": {
|
||||
updatedAt: Date.now() - 1000,
|
||||
modelOverride: "some-model",
|
||||
} as any,
|
||||
} as unknown as {
|
||||
sessionId: string;
|
||||
updatedAt: number;
|
||||
modelOverride?: string;
|
||||
},
|
||||
});
|
||||
vi.mocked(evaluateSessionFreshness).mockReturnValue({ fresh: true });
|
||||
|
||||
|
||||
@@ -106,6 +106,10 @@ export async function readCronRunLogEntries(
|
||||
if (jobId && obj.jobId !== jobId) {
|
||||
continue;
|
||||
}
|
||||
const usage =
|
||||
obj.usage && typeof obj.usage === "object"
|
||||
? (obj.usage as Record<string, unknown>)
|
||||
: undefined;
|
||||
const entry: CronRunLogEntry = {
|
||||
ts: obj.ts,
|
||||
jobId: obj.jobId,
|
||||
@@ -117,26 +121,20 @@ export async function readCronRunLogEntries(
|
||||
durationMs: obj.durationMs,
|
||||
nextRunAtMs: obj.nextRunAtMs,
|
||||
model: typeof obj.model === "string" && obj.model.trim() ? obj.model : undefined,
|
||||
provider: typeof obj.provider === "string" && obj.provider.trim() ? obj.provider : undefined,
|
||||
usage:
|
||||
obj.usage && typeof obj.usage === "object"
|
||||
? {
|
||||
input_tokens:
|
||||
typeof (obj.usage as any).input_tokens === "number" ? (obj.usage as any).input_tokens : undefined,
|
||||
output_tokens:
|
||||
typeof (obj.usage as any).output_tokens === "number" ? (obj.usage as any).output_tokens : undefined,
|
||||
total_tokens:
|
||||
typeof (obj.usage as any).total_tokens === "number" ? (obj.usage as any).total_tokens : undefined,
|
||||
cache_read_tokens:
|
||||
typeof (obj.usage as any).cache_read_tokens === "number"
|
||||
? (obj.usage as any).cache_read_tokens
|
||||
: undefined,
|
||||
cache_write_tokens:
|
||||
typeof (obj.usage as any).cache_write_tokens === "number"
|
||||
? (obj.usage as any).cache_write_tokens
|
||||
: undefined,
|
||||
}
|
||||
: undefined,
|
||||
provider:
|
||||
typeof obj.provider === "string" && obj.provider.trim() ? obj.provider : undefined,
|
||||
usage: usage
|
||||
? {
|
||||
input_tokens: typeof usage.input_tokens === "number" ? usage.input_tokens : undefined,
|
||||
output_tokens:
|
||||
typeof usage.output_tokens === "number" ? usage.output_tokens : undefined,
|
||||
total_tokens: typeof usage.total_tokens === "number" ? usage.total_tokens : undefined,
|
||||
cache_read_tokens:
|
||||
typeof usage.cache_read_tokens === "number" ? usage.cache_read_tokens : undefined,
|
||||
cache_write_tokens:
|
||||
typeof usage.cache_write_tokens === "number" ? usage.cache_write_tokens : undefined,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
if (typeof obj.sessionId === "string" && obj.sessionId.trim().length > 0) {
|
||||
entry.sessionId = obj.sessionId;
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { auditGatewayServiceConfig, checkTokenDrift, SERVICE_AUDIT_CODES } from "./service-audit.js";
|
||||
import {
|
||||
auditGatewayServiceConfig,
|
||||
checkTokenDrift,
|
||||
SERVICE_AUDIT_CODES,
|
||||
} from "./service-audit.js";
|
||||
import { buildMinimalServicePath } from "./service-env.js";
|
||||
|
||||
describe("auditGatewayServiceConfig", () => {
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { ChannelType } from "@buape/carbon";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { maybeCreateDiscordAutoThread } from "./threading.js";
|
||||
|
||||
describe("maybeCreateDiscordAutoThread", () => {
|
||||
const mockClient = { rest: { post: vi.fn(), get: vi.fn() } } as any;
|
||||
const mockMessage = { id: "msg1", timestamp: "123" } as any;
|
||||
const postMock = vi.fn();
|
||||
const getMock = vi.fn();
|
||||
const mockClient = {
|
||||
rest: { post: postMock, get: getMock },
|
||||
} as unknown as Parameters<typeof maybeCreateDiscordAutoThread>[0]["client"];
|
||||
const mockMessage = {
|
||||
id: "msg1",
|
||||
timestamp: "123",
|
||||
} as unknown as Parameters<typeof maybeCreateDiscordAutoThread>[0]["message"];
|
||||
|
||||
it("skips auto-thread if channelType is GuildForum", async () => {
|
||||
const result = await maybeCreateDiscordAutoThread({
|
||||
@@ -18,7 +25,7 @@ describe("maybeCreateDiscordAutoThread", () => {
|
||||
combinedBody: "test",
|
||||
});
|
||||
expect(result).toBeUndefined();
|
||||
expect(mockClient.rest.post).not.toHaveBeenCalled();
|
||||
expect(postMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips auto-thread if channelType is GuildMedia", async () => {
|
||||
@@ -33,11 +40,11 @@ describe("maybeCreateDiscordAutoThread", () => {
|
||||
combinedBody: "test",
|
||||
});
|
||||
expect(result).toBeUndefined();
|
||||
expect(mockClient.rest.post).not.toHaveBeenCalled();
|
||||
expect(postMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("creates auto-thread if channelType is GuildText", async () => {
|
||||
mockClient.rest.post.mockResolvedValueOnce({ id: "thread1" });
|
||||
postMock.mockResolvedValueOnce({ id: "thread1" });
|
||||
const result = await maybeCreateDiscordAutoThread({
|
||||
client: mockClient,
|
||||
message: mockMessage,
|
||||
@@ -49,6 +56,6 @@ describe("maybeCreateDiscordAutoThread", () => {
|
||||
combinedBody: "test",
|
||||
});
|
||||
expect(result).toBe("thread1");
|
||||
expect(mockClient.rest.post).toHaveBeenCalled();
|
||||
expect(postMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,7 +73,10 @@ describe("mesh handlers", () => {
|
||||
it("runs steps in DAG order and supports retrying failed steps", async () => {
|
||||
const runState = new Map<string, "ok" | "error">();
|
||||
mocks.agent.mockImplementation(
|
||||
(opts: { params: { idempotencyKey: string }; respond: (ok: boolean, payload?: unknown) => void }) => {
|
||||
(opts: {
|
||||
params: { idempotencyKey: string };
|
||||
respond: (ok: boolean, payload?: unknown) => void;
|
||||
}) => {
|
||||
const agentRunId = `agent-${opts.params.idempotencyKey}`;
|
||||
runState.set(agentRunId, "ok");
|
||||
if (opts.params.idempotencyKey.includes(":review:1")) {
|
||||
@@ -120,7 +123,10 @@ describe("mesh handlers", () => {
|
||||
|
||||
// Make subsequent retries succeed
|
||||
mocks.agent.mockImplementation(
|
||||
(opts: { params: { idempotencyKey: string }; respond: (ok: boolean, payload?: unknown) => void }) => {
|
||||
(opts: {
|
||||
params: { idempotencyKey: string };
|
||||
respond: (ok: boolean, payload?: unknown) => void;
|
||||
}) => {
|
||||
const agentRunId = `agent-${opts.params.idempotencyKey}`;
|
||||
runState.set(agentRunId, "ok");
|
||||
opts.respond(true, { runId: agentRunId, status: "accepted" });
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { GatewayRequestHandlerOptions, GatewayRequestHandlers, RespondFn } from "./types.js";
|
||||
import { agentCommand } from "../../commands/agent.js";
|
||||
import { normalizeAgentId } from "../../routing/session-key.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import type { GatewayRequestHandlerOptions, GatewayRequestHandlers, RespondFn } from "./types.js";
|
||||
import {
|
||||
ErrorCodes,
|
||||
errorShape,
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
validateMeshRetryParams,
|
||||
validateMeshRunParams,
|
||||
validateMeshStatusParams,
|
||||
type MeshPlanAutoParams,
|
||||
type MeshRunParams,
|
||||
type MeshWorkflowPlan,
|
||||
} from "../protocol/index.js";
|
||||
import { agentHandlers } from "./agent.js";
|
||||
@@ -77,13 +75,27 @@ function trimMap() {
|
||||
if (meshRuns.size <= MAX_KEEP_RUNS) {
|
||||
return;
|
||||
}
|
||||
const sorted = [...meshRuns.values()].sort((a, b) => a.startedAt - b.startedAt);
|
||||
const sorted = [...meshRuns.values()].toSorted((a, b) => a.startedAt - b.startedAt);
|
||||
const overflow = meshRuns.size - MAX_KEEP_RUNS;
|
||||
for (const stale of sorted.slice(0, overflow)) {
|
||||
meshRuns.delete(stale.runId);
|
||||
}
|
||||
}
|
||||
|
||||
function stringifyUnknown(value: unknown): string {
|
||||
if (typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
if (value instanceof Error) {
|
||||
return value.message;
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(value);
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDependsOn(dependsOn: string[] | undefined): string[] {
|
||||
if (!Array.isArray(dependsOn)) {
|
||||
return [];
|
||||
@@ -123,10 +135,7 @@ function normalizePlan(plan: MeshWorkflowPlan): MeshWorkflowPlan {
|
||||
};
|
||||
}
|
||||
|
||||
function createPlanFromParams(params: {
|
||||
goal: string;
|
||||
steps?: MeshAutoStep[];
|
||||
}): MeshWorkflowPlan {
|
||||
function createPlanFromParams(params: { goal: string; steps?: MeshAutoStep[] }): MeshWorkflowPlan {
|
||||
const now = Date.now();
|
||||
const goal = params.goal.trim();
|
||||
const sourceSteps = params.steps?.length
|
||||
@@ -164,7 +173,9 @@ function createPlanFromParams(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function validatePlanGraph(plan: MeshWorkflowPlan): { ok: true; order: string[] } | { ok: false; error: string } {
|
||||
function validatePlanGraph(
|
||||
plan: MeshWorkflowPlan,
|
||||
): { ok: true; order: string[] } | { ok: false; error: string } {
|
||||
const ids = new Set<string>();
|
||||
for (const step of plan.steps) {
|
||||
if (ids.has(step.id)) {
|
||||
@@ -231,7 +242,12 @@ async function callGatewayHandler(
|
||||
): Promise<{ ok: boolean; payload?: unknown; error?: unknown; meta?: Record<string, unknown> }> {
|
||||
return await new Promise((resolve) => {
|
||||
let settled = false;
|
||||
const settle = (result: { ok: boolean; payload?: unknown; error?: unknown; meta?: Record<string, unknown> }) => {
|
||||
const settle = (result: {
|
||||
ok: boolean;
|
||||
payload?: unknown;
|
||||
error?: unknown;
|
||||
meta?: Record<string, unknown>;
|
||||
}) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
@@ -312,7 +328,7 @@ async function executeStep(params: {
|
||||
if (!accepted.ok) {
|
||||
step.status = "failed";
|
||||
step.endedAt = Date.now();
|
||||
step.error = String(accepted.error ?? "agent request failed");
|
||||
step.error = stringifyUnknown(accepted.error ?? "agent request failed");
|
||||
run.history.push({
|
||||
ts: Date.now(),
|
||||
type: "step.error",
|
||||
@@ -369,7 +385,7 @@ async function executeStep(params: {
|
||||
step.error =
|
||||
typeof waitPayload?.error === "string"
|
||||
? waitPayload.error
|
||||
: String(waited.error ?? `agent.wait returned status ${waitStatus}`);
|
||||
: stringifyUnknown(waited.error ?? `agent.wait returned status ${waitStatus}`);
|
||||
run.history.push({
|
||||
ts: Date.now(),
|
||||
type: "step.error",
|
||||
@@ -647,7 +663,8 @@ async function generateAutoPlan(params: {
|
||||
const prompt = buildAutoPlannerPrompt({ goal: params.goal, maxSteps: params.maxSteps });
|
||||
const timeoutSeconds = Math.ceil((params.timeoutMs ?? AUTO_PLAN_TIMEOUT_MS) / 1000);
|
||||
const resolvedAgentId = normalizeAgentId(params.agentId ?? "main");
|
||||
const plannerSessionKey = params.sessionKey?.trim() || `agent:${resolvedAgentId}:${PLANNER_MAIN_KEY}`;
|
||||
const plannerSessionKey =
|
||||
params.sessionKey?.trim() || `agent:${resolvedAgentId}:${PLANNER_MAIN_KEY}`;
|
||||
|
||||
try {
|
||||
const runResult = await agentCommand(
|
||||
@@ -732,7 +749,7 @@ export const meshHandlers: GatewayRequestHandlers = {
|
||||
return;
|
||||
}
|
||||
|
||||
const p = params as MeshPlanAutoParams;
|
||||
const p = params;
|
||||
const maxSteps =
|
||||
typeof p.maxSteps === "number" && Number.isFinite(p.maxSteps)
|
||||
? Math.max(1, Math.min(16, Math.floor(p.maxSteps)))
|
||||
@@ -782,7 +799,7 @@ export const meshHandlers: GatewayRequestHandlers = {
|
||||
);
|
||||
return;
|
||||
}
|
||||
const p = params as MeshRunParams;
|
||||
const p = params;
|
||||
const plan = normalizePlan(p.plan);
|
||||
const graph = validatePlanGraph(plan);
|
||||
if (!graph.ok) {
|
||||
@@ -853,7 +870,11 @@ export const meshHandlers: GatewayRequestHandlers = {
|
||||
return;
|
||||
}
|
||||
if (run.status === "running") {
|
||||
respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, "mesh run is currently running"));
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(ErrorCodes.UNAVAILABLE, "mesh run is currently running"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const stepIds = resolveStepIdsForRetry(run, params.stepIds);
|
||||
|
||||
@@ -526,8 +526,9 @@ describe("gateway mesh.plan.auto scope handling", () => {
|
||||
it("allows operator.write clients for mesh.plan.auto", async () => {
|
||||
const { handleGatewayRequest } = await import("../server-methods.js");
|
||||
const respond = vi.fn();
|
||||
const handler = vi.fn(({ respond: send }: { respond: (ok: boolean, payload?: unknown) => void }) =>
|
||||
send(true, { ok: true }),
|
||||
const handler = vi.fn(
|
||||
({ respond: send }: { respond: (ok: boolean, payload?: unknown) => void }) =>
|
||||
send(true, { ok: true }),
|
||||
);
|
||||
|
||||
await handleGatewayRequest({
|
||||
|
||||
@@ -19,9 +19,7 @@ beforeEach(() => {
|
||||
const runtime = createPluginRuntime();
|
||||
setTelegramRuntime(runtime);
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{ pluginId: "telegram", plugin: telegramPlugin, source: "test" },
|
||||
]),
|
||||
createTestRegistry([{ pluginId: "telegram", plugin: telegramPlugin, source: "test" }]),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -99,7 +97,7 @@ describe("heartbeat transcript pruning", () => {
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
|
||||
// Create a transcript with some existing content
|
||||
const originalContent = await createTranscriptWithContent(transcriptPath, sessionId);
|
||||
await createTranscriptWithContent(transcriptPath, sessionId);
|
||||
const originalSize = (await fs.stat(transcriptPath)).size;
|
||||
|
||||
// Seed session store
|
||||
@@ -147,7 +145,7 @@ describe("heartbeat transcript pruning", () => {
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
|
||||
// Create a transcript with some existing content
|
||||
const originalContent = await createTranscriptWithContent(transcriptPath, sessionId);
|
||||
await createTranscriptWithContent(transcriptPath, sessionId);
|
||||
const originalSize = (await fs.stat(transcriptPath)).size;
|
||||
|
||||
// Seed session store
|
||||
|
||||
@@ -50,7 +50,11 @@ function resolvePrimaryIPv4(): string | undefined {
|
||||
function initSelfPresence() {
|
||||
const host = os.hostname();
|
||||
const ip = resolvePrimaryIPv4() ?? undefined;
|
||||
const version = process.env.OPENCLAW_VERSION ?? process.env.OPENCLAW_SERVICE_VERSION ?? process.env.npm_package_version ?? "unknown";
|
||||
const version =
|
||||
process.env.OPENCLAW_VERSION ??
|
||||
process.env.OPENCLAW_SERVICE_VERSION ??
|
||||
process.env.npm_package_version ??
|
||||
"unknown";
|
||||
const modelIdentifier = (() => {
|
||||
const p = os.platform();
|
||||
if (p === "darwin") {
|
||||
|
||||
@@ -414,7 +414,6 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
|
||||
return { message: current };
|
||||
}
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// Message Write Hooks
|
||||
// =========================================================================
|
||||
|
||||
@@ -502,7 +502,7 @@ export type PluginHookBeforeMessageWriteEvent = {
|
||||
};
|
||||
|
||||
export type PluginHookBeforeMessageWriteResult = {
|
||||
block?: boolean; // If true, message is NOT written to JSONL
|
||||
block?: boolean; // If true, message is NOT written to JSONL
|
||||
message?: AgentMessage; // Optional: modified message to write instead
|
||||
};
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ describe("Discord Session Key Continuity", () => {
|
||||
});
|
||||
|
||||
expect(missingIdKey).toContain("unknown");
|
||||
|
||||
|
||||
// Should still be distinct from main
|
||||
expect(missingIdKey).not.toBe("agent:main:main");
|
||||
});
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { AnyMessageContent, MiscMessageGenerationOptions, WAPresence } from "@whiskeysockets/baileys";
|
||||
import type {
|
||||
AnyMessageContent,
|
||||
MiscMessageGenerationOptions,
|
||||
WAPresence,
|
||||
} from "@whiskeysockets/baileys";
|
||||
import type { ActiveWebSendOptions } from "../active-listener.js";
|
||||
import { recordChannelActivity } from "../../infra/channel-activity.js";
|
||||
import { toWhatsappJid } from "../../utils.js";
|
||||
@@ -67,9 +71,11 @@ export function createWebSendApi(params: {
|
||||
} else {
|
||||
payload = { text };
|
||||
}
|
||||
const miscOptions: MiscMessageGenerationOptions = {
|
||||
linkPreview: sendOptions?.linkPreview === false ? null : undefined,
|
||||
};
|
||||
const miscOptions: MiscMessageGenerationOptions | undefined =
|
||||
sendOptions?.linkPreview === false
|
||||
? // Baileys typing removed linkPreview from public options, but runtime still accepts it.
|
||||
({ linkPreview: null } as unknown as MiscMessageGenerationOptions)
|
||||
: undefined;
|
||||
const result = await params.sock.sendMessage(jid, payload, miscOptions);
|
||||
const accountId = sendOptions?.accountId ?? params.defaultAccountId;
|
||||
recordWhatsAppOutbound(accountId);
|
||||
|
||||
@@ -83,9 +83,7 @@ export async function sendMessageWhatsApp(
|
||||
? {
|
||||
...(options.gifPlayback ? { gifPlayback: true } : {}),
|
||||
...(documentFileName ? { fileName: documentFileName } : {}),
|
||||
...(options.linkPreview !== undefined
|
||||
? { linkPreview: options.linkPreview }
|
||||
: {}),
|
||||
...(options.linkPreview !== undefined ? { linkPreview: options.linkPreview } : {}),
|
||||
accountId,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
Reference in New Issue
Block a user