@nextclaw/openclaw-compat 0.3.27 → 0.3.29
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/index.d.ts +70 -0
- package/dist/index.js +588 -6
- package/package.json +15 -15
package/dist/index.d.ts
CHANGED
|
@@ -329,6 +329,15 @@ type PluginRuntime = {
|
|
|
329
329
|
loadConfig: () => Record<string, unknown>;
|
|
330
330
|
writeConfigFile: (next: Record<string, unknown>) => Promise<void>;
|
|
331
331
|
};
|
|
332
|
+
logging: {
|
|
333
|
+
shouldLogVerbose: () => boolean;
|
|
334
|
+
};
|
|
335
|
+
media: {
|
|
336
|
+
detectMime: (params: {
|
|
337
|
+
buffer: Buffer;
|
|
338
|
+
}) => Promise<string | undefined>;
|
|
339
|
+
loadWebMedia: (url: string, options?: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
340
|
+
};
|
|
332
341
|
tools: {
|
|
333
342
|
createMemorySearchTool: (params: {
|
|
334
343
|
config?: Config;
|
|
@@ -340,9 +349,70 @@ type PluginRuntime = {
|
|
|
340
349
|
}) => OpenClawPluginTool | null;
|
|
341
350
|
};
|
|
342
351
|
channel: {
|
|
352
|
+
media: {
|
|
353
|
+
fetchRemoteMedia: (params: {
|
|
354
|
+
url: string;
|
|
355
|
+
maxBytes?: number;
|
|
356
|
+
}) => Promise<Record<string, unknown>>;
|
|
357
|
+
saveMediaBuffer: (buffer: Buffer, contentType?: string, direction?: string, maxBytes?: number, fileName?: string) => Promise<{
|
|
358
|
+
path: string;
|
|
359
|
+
contentType?: string;
|
|
360
|
+
}>;
|
|
361
|
+
};
|
|
362
|
+
text: {
|
|
363
|
+
chunkMarkdownText: (text: string, limit: number) => string[];
|
|
364
|
+
resolveMarkdownTableMode: (params: Record<string, unknown>) => unknown;
|
|
365
|
+
convertMarkdownTables: (text: string, mode?: unknown) => string;
|
|
366
|
+
resolveTextChunkLimit: (cfg: Config, channel: string, accountId?: string, options?: Record<string, unknown>) => number;
|
|
367
|
+
resolveChunkMode: (cfg: Config, channel: string, accountId?: string) => string;
|
|
368
|
+
chunkTextWithMode: (text: string, limit: number, mode?: unknown) => string[];
|
|
369
|
+
hasControlCommand: (text: string, cfg?: Config) => boolean;
|
|
370
|
+
};
|
|
343
371
|
reply: {
|
|
372
|
+
resolveEnvelopeFormatOptions: (cfg: Config) => Record<string, unknown>;
|
|
373
|
+
formatAgentEnvelope: (params: Record<string, unknown>) => string;
|
|
374
|
+
finalizeInboundContext: (params: Record<string, unknown>) => Record<string, unknown>;
|
|
375
|
+
withReplyDispatcher: (params: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
376
|
+
dispatchReplyFromConfig: (params: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
377
|
+
createReplyDispatcherWithTyping: (params: Record<string, unknown>) => {
|
|
378
|
+
dispatcher: {
|
|
379
|
+
sendToolResult: (payload: Record<string, unknown>) => boolean;
|
|
380
|
+
sendBlockReply: (payload: Record<string, unknown>) => boolean;
|
|
381
|
+
sendFinalReply: (payload: Record<string, unknown>) => boolean;
|
|
382
|
+
waitForIdle: () => Promise<void>;
|
|
383
|
+
getQueuedCounts: () => Record<string, number>;
|
|
384
|
+
markComplete: () => void;
|
|
385
|
+
};
|
|
386
|
+
replyOptions: Record<string, unknown>;
|
|
387
|
+
markDispatchIdle: () => void;
|
|
388
|
+
};
|
|
389
|
+
resolveHumanDelayConfig: (cfg: Config, agentId: string) => unknown;
|
|
344
390
|
dispatchReplyWithBufferedBlockDispatcher: (params: PluginReplyDispatchParams) => Promise<void>;
|
|
345
391
|
};
|
|
392
|
+
routing: {
|
|
393
|
+
resolveAgentRoute: (params: Record<string, unknown>) => Record<string, unknown>;
|
|
394
|
+
};
|
|
395
|
+
pairing: {
|
|
396
|
+
readAllowFromStore: (params: {
|
|
397
|
+
channel: string;
|
|
398
|
+
accountId?: string;
|
|
399
|
+
}) => Promise<string[]>;
|
|
400
|
+
upsertPairingRequest: (params: Record<string, unknown>) => Promise<{
|
|
401
|
+
code: string;
|
|
402
|
+
created: boolean;
|
|
403
|
+
}>;
|
|
404
|
+
};
|
|
405
|
+
commands: {
|
|
406
|
+
shouldComputeCommandAuthorized: (text: string, cfg?: Config) => boolean;
|
|
407
|
+
resolveCommandAuthorizedFromAuthorizers: (params: Record<string, unknown>) => boolean;
|
|
408
|
+
};
|
|
409
|
+
debounce: {
|
|
410
|
+
resolveInboundDebounceMs: (params: Record<string, unknown>) => number;
|
|
411
|
+
createInboundDebouncer: <T>(params: Record<string, unknown>) => {
|
|
412
|
+
enqueue: (item: T) => Promise<void>;
|
|
413
|
+
flushKey: (key: string) => Promise<void>;
|
|
414
|
+
};
|
|
415
|
+
};
|
|
346
416
|
};
|
|
347
417
|
};
|
|
348
418
|
type OpenClawPluginApi = {
|
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ function buildOauthProviderAuthResult(params) {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
async function sleep(ms) {
|
|
34
|
-
await new Promise((
|
|
34
|
+
await new Promise((resolve2) => setTimeout(resolve2, Math.max(0, ms)));
|
|
35
35
|
}
|
|
36
36
|
function normalizePluginHttpPath(rawPath) {
|
|
37
37
|
const trimmed = rawPath.trim();
|
|
@@ -883,7 +883,7 @@ async function ensureOpenClawExtensions(manifest) {
|
|
|
883
883
|
return list;
|
|
884
884
|
}
|
|
885
885
|
async function runCommand(command, args, cwd) {
|
|
886
|
-
return new Promise((
|
|
886
|
+
return new Promise((resolve2) => {
|
|
887
887
|
const child = spawn(command, args, {
|
|
888
888
|
cwd,
|
|
889
889
|
env: {
|
|
@@ -902,10 +902,10 @@ async function runCommand(command, args, cwd) {
|
|
|
902
902
|
stderr += String(chunk);
|
|
903
903
|
});
|
|
904
904
|
child.on("close", (code) => {
|
|
905
|
-
|
|
905
|
+
resolve2({ code: code ?? 1, stdout, stderr });
|
|
906
906
|
});
|
|
907
907
|
child.on("error", (error) => {
|
|
908
|
-
|
|
908
|
+
resolve2({ code: 1, stdout, stderr: `${stderr}
|
|
909
909
|
${String(error)}` });
|
|
910
910
|
});
|
|
911
911
|
});
|
|
@@ -1767,8 +1767,520 @@ function registerPluginNcpAgentRuntime(params) {
|
|
|
1767
1767
|
}
|
|
1768
1768
|
|
|
1769
1769
|
// src/plugins/runtime.ts
|
|
1770
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1770
1771
|
import { getPackageVersion } from "@nextclaw/core";
|
|
1771
1772
|
import { MemoryGetTool, MemorySearchTool } from "@nextclaw/core";
|
|
1773
|
+
|
|
1774
|
+
// src/plugins/runtime-shared.ts
|
|
1775
|
+
function asRecord(value) {
|
|
1776
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1777
|
+
}
|
|
1778
|
+
function asString(value) {
|
|
1779
|
+
if (typeof value !== "string") {
|
|
1780
|
+
return void 0;
|
|
1781
|
+
}
|
|
1782
|
+
const trimmed = value.trim();
|
|
1783
|
+
return trimmed || void 0;
|
|
1784
|
+
}
|
|
1785
|
+
function asNumber(value) {
|
|
1786
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1787
|
+
return void 0;
|
|
1788
|
+
}
|
|
1789
|
+
return Math.trunc(value);
|
|
1790
|
+
}
|
|
1791
|
+
function readStringArray(value) {
|
|
1792
|
+
if (!Array.isArray(value)) {
|
|
1793
|
+
return [];
|
|
1794
|
+
}
|
|
1795
|
+
return value.map((entry) => asString(entry)).filter((entry) => Boolean(entry));
|
|
1796
|
+
}
|
|
1797
|
+
function resolveChannelConfig(cfg, channel) {
|
|
1798
|
+
const channels = asRecord(asRecord(cfg).channels);
|
|
1799
|
+
return asRecord(channels[channel]);
|
|
1800
|
+
}
|
|
1801
|
+
function resolveDefaultAgentId(cfg) {
|
|
1802
|
+
const agents = asRecord(cfg).agents;
|
|
1803
|
+
const list = Array.isArray(asRecord(agents).list) ? asRecord(agents).list : [];
|
|
1804
|
+
for (const entry of list) {
|
|
1805
|
+
const agent = asRecord(entry);
|
|
1806
|
+
if (agent.default === true) {
|
|
1807
|
+
return asString(agent.id) ?? "main";
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
for (const entry of list) {
|
|
1811
|
+
const agentId = asString(asRecord(entry).id);
|
|
1812
|
+
if (agentId) {
|
|
1813
|
+
return agentId;
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
return "main";
|
|
1817
|
+
}
|
|
1818
|
+
function resolveAccountId(value) {
|
|
1819
|
+
return asString(value) ?? "default";
|
|
1820
|
+
}
|
|
1821
|
+
function resolvePeer(params) {
|
|
1822
|
+
const peer = asRecord(params.peer);
|
|
1823
|
+
return {
|
|
1824
|
+
kind: asString(peer.kind) ?? "direct",
|
|
1825
|
+
id: asString(peer.id) ?? "default"
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
function resolveSessionKey(params) {
|
|
1829
|
+
return [
|
|
1830
|
+
"agent",
|
|
1831
|
+
params.agentId,
|
|
1832
|
+
params.channel || "unknown",
|
|
1833
|
+
params.accountId || "default",
|
|
1834
|
+
params.peerKind || "direct",
|
|
1835
|
+
params.peerId || "default"
|
|
1836
|
+
].join(":");
|
|
1837
|
+
}
|
|
1838
|
+
function splitTextIntoChunks(text, limit) {
|
|
1839
|
+
const maxLength = Number.isFinite(limit) && limit > 0 ? Math.trunc(limit) : 4e3;
|
|
1840
|
+
const normalized = text ?? "";
|
|
1841
|
+
if (normalized.length <= maxLength) {
|
|
1842
|
+
return [normalized];
|
|
1843
|
+
}
|
|
1844
|
+
const chunks = [];
|
|
1845
|
+
let remaining = normalized;
|
|
1846
|
+
while (remaining.length > maxLength) {
|
|
1847
|
+
let splitAt = remaining.lastIndexOf("\n", maxLength);
|
|
1848
|
+
if (splitAt <= 0 || splitAt < Math.floor(maxLength * 0.5)) {
|
|
1849
|
+
splitAt = maxLength;
|
|
1850
|
+
}
|
|
1851
|
+
chunks.push(remaining.slice(0, splitAt));
|
|
1852
|
+
remaining = remaining.slice(splitAt).replace(/^\n+/, "");
|
|
1853
|
+
}
|
|
1854
|
+
if (remaining.length > 0) {
|
|
1855
|
+
chunks.push(remaining);
|
|
1856
|
+
}
|
|
1857
|
+
return chunks;
|
|
1858
|
+
}
|
|
1859
|
+
function resolveTextChunkLimit(cfg, channel, _accountId, options) {
|
|
1860
|
+
const channelConfig = resolveChannelConfig(cfg, channel);
|
|
1861
|
+
const explicit = asNumber(channelConfig.textChunkLimit);
|
|
1862
|
+
if (explicit !== void 0 && explicit > 0) {
|
|
1863
|
+
return explicit;
|
|
1864
|
+
}
|
|
1865
|
+
const fallback = asNumber(options?.fallbackLimit);
|
|
1866
|
+
return fallback && fallback > 0 ? fallback : 4e3;
|
|
1867
|
+
}
|
|
1868
|
+
function hasControlCommand(text) {
|
|
1869
|
+
const body = text?.trim() ?? "";
|
|
1870
|
+
if (!body) {
|
|
1871
|
+
return false;
|
|
1872
|
+
}
|
|
1873
|
+
if (/^(?:\/|!)[a-z0-9_-]+(?:\s|$)/i.test(body)) {
|
|
1874
|
+
return true;
|
|
1875
|
+
}
|
|
1876
|
+
return /(?:^|\s)(?:\/|!)[a-z0-9_-]+(?:\s|$)/i.test(body);
|
|
1877
|
+
}
|
|
1878
|
+
function resolveCommandAuthorizedFromAuthorizers(params) {
|
|
1879
|
+
const authorizers = Array.isArray(params.authorizers) ? params.authorizers : [];
|
|
1880
|
+
const configured = authorizers.filter((entry) => asRecord(entry).configured === true);
|
|
1881
|
+
if (configured.length === 0) {
|
|
1882
|
+
return true;
|
|
1883
|
+
}
|
|
1884
|
+
return configured.some((entry) => asRecord(entry).allowed === true);
|
|
1885
|
+
}
|
|
1886
|
+
function resolveAgentRoute(params) {
|
|
1887
|
+
const cfg = params.cfg;
|
|
1888
|
+
const channel = asString(params.channel) ?? "unknown";
|
|
1889
|
+
const accountId = resolveAccountId(params.accountId);
|
|
1890
|
+
const peer = resolvePeer(params);
|
|
1891
|
+
const agentId = resolveDefaultAgentId(cfg);
|
|
1892
|
+
const sessionKey = resolveSessionKey({
|
|
1893
|
+
channel,
|
|
1894
|
+
accountId,
|
|
1895
|
+
agentId,
|
|
1896
|
+
peerKind: peer.kind,
|
|
1897
|
+
peerId: peer.id
|
|
1898
|
+
});
|
|
1899
|
+
return {
|
|
1900
|
+
agentId,
|
|
1901
|
+
accountId,
|
|
1902
|
+
channel,
|
|
1903
|
+
sessionKey,
|
|
1904
|
+
mainSessionKey: sessionKey,
|
|
1905
|
+
lastRoutePolicy: "main",
|
|
1906
|
+
matchedBy: "default"
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// src/plugins/runtime-debounce.ts
|
|
1911
|
+
function resolveInboundDebounceMs(params) {
|
|
1912
|
+
const cfg = asRecord(params.cfg);
|
|
1913
|
+
const inbound = asRecord(asRecord(cfg.messages).inbound);
|
|
1914
|
+
const override = asNumber(params.overrideMs);
|
|
1915
|
+
const byChannel = asRecord(inbound.byChannel);
|
|
1916
|
+
const channelOverride = asNumber(byChannel[asString(params.channel) ?? ""]);
|
|
1917
|
+
const base = asNumber(inbound.debounceMs);
|
|
1918
|
+
return Math.max(0, override ?? channelOverride ?? base ?? 0);
|
|
1919
|
+
}
|
|
1920
|
+
function createInboundDebouncer(params) {
|
|
1921
|
+
const buffers = /* @__PURE__ */ new Map();
|
|
1922
|
+
const defaultDebounceMs = Math.max(0, Math.trunc(params.debounceMs ?? 0));
|
|
1923
|
+
const flushBuffer = async (key, buffer) => {
|
|
1924
|
+
buffers.delete(key);
|
|
1925
|
+
if (buffer.timeout) {
|
|
1926
|
+
clearTimeout(buffer.timeout);
|
|
1927
|
+
buffer.timeout = null;
|
|
1928
|
+
}
|
|
1929
|
+
if (buffer.items.length === 0 || !params.onFlush) {
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
try {
|
|
1933
|
+
await params.onFlush(buffer.items);
|
|
1934
|
+
} catch (error) {
|
|
1935
|
+
params.onError?.(error, buffer.items);
|
|
1936
|
+
}
|
|
1937
|
+
};
|
|
1938
|
+
const flushKey = async (key) => {
|
|
1939
|
+
const buffer = buffers.get(key);
|
|
1940
|
+
if (!buffer) {
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
await flushBuffer(key, buffer);
|
|
1944
|
+
};
|
|
1945
|
+
const scheduleFlush = (key, buffer) => {
|
|
1946
|
+
if (buffer.timeout) {
|
|
1947
|
+
clearTimeout(buffer.timeout);
|
|
1948
|
+
}
|
|
1949
|
+
buffer.timeout = setTimeout(() => {
|
|
1950
|
+
void flushBuffer(key, buffer);
|
|
1951
|
+
}, buffer.debounceMs);
|
|
1952
|
+
buffer.timeout.unref?.();
|
|
1953
|
+
};
|
|
1954
|
+
const enqueue = async (item) => {
|
|
1955
|
+
const key = params.buildKey?.(item);
|
|
1956
|
+
const resolvedDebounceMs = params.resolveDebounceMs?.(item);
|
|
1957
|
+
const debounceMs = typeof resolvedDebounceMs === "number" && Number.isFinite(resolvedDebounceMs) ? Math.max(0, Math.trunc(resolvedDebounceMs)) : defaultDebounceMs;
|
|
1958
|
+
const canDebounce = debounceMs > 0 && (params.shouldDebounce?.(item) ?? true);
|
|
1959
|
+
if (!key || !canDebounce) {
|
|
1960
|
+
if (key && buffers.has(key)) {
|
|
1961
|
+
await flushKey(key);
|
|
1962
|
+
}
|
|
1963
|
+
if (!params.onFlush) {
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
try {
|
|
1967
|
+
await params.onFlush([item]);
|
|
1968
|
+
} catch (error) {
|
|
1969
|
+
params.onError?.(error, [item]);
|
|
1970
|
+
}
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
const existing = buffers.get(key);
|
|
1974
|
+
if (existing) {
|
|
1975
|
+
existing.items.push(item);
|
|
1976
|
+
existing.debounceMs = debounceMs;
|
|
1977
|
+
scheduleFlush(key, existing);
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
const buffer = {
|
|
1981
|
+
items: [item],
|
|
1982
|
+
timeout: null,
|
|
1983
|
+
debounceMs
|
|
1984
|
+
};
|
|
1985
|
+
buffers.set(key, buffer);
|
|
1986
|
+
scheduleFlush(key, buffer);
|
|
1987
|
+
};
|
|
1988
|
+
return { enqueue, flushKey };
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
// src/plugins/runtime-reply.ts
|
|
1992
|
+
function normalizeTextField(value) {
|
|
1993
|
+
if (typeof value !== "string") {
|
|
1994
|
+
return void 0;
|
|
1995
|
+
}
|
|
1996
|
+
return value.replace(/\r\n/g, "\n").trim() || void 0;
|
|
1997
|
+
}
|
|
1998
|
+
function countMediaEntries(ctx) {
|
|
1999
|
+
const paths = Array.isArray(ctx.MediaPaths) ? ctx.MediaPaths.length : 0;
|
|
2000
|
+
const urls = Array.isArray(ctx.MediaUrls) ? ctx.MediaUrls.length : 0;
|
|
2001
|
+
const single = ctx.MediaPath || ctx.MediaUrl ? 1 : 0;
|
|
2002
|
+
return Math.max(paths, urls, single);
|
|
2003
|
+
}
|
|
2004
|
+
function resolveEnvelopeFormatOptions(_cfg) {
|
|
2005
|
+
return {
|
|
2006
|
+
includeTimestamp: true,
|
|
2007
|
+
includeElapsed: true
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
function formatAgentEnvelope(params) {
|
|
2011
|
+
const channel = typeof params.channel === "string" && params.channel.trim() ? params.channel.trim() : "Channel";
|
|
2012
|
+
const from = typeof params.from === "string" && params.from.trim() ? params.from.trim() : void 0;
|
|
2013
|
+
const body = typeof params.body === "string" ? params.body : "";
|
|
2014
|
+
const timestamp = params.timestamp instanceof Date ? params.timestamp : void 0;
|
|
2015
|
+
const parts = [channel];
|
|
2016
|
+
if (from) {
|
|
2017
|
+
parts.push(from);
|
|
2018
|
+
}
|
|
2019
|
+
if (timestamp) {
|
|
2020
|
+
parts.push(timestamp.toISOString());
|
|
2021
|
+
}
|
|
2022
|
+
return `[${parts.join(" ")}] ${body}`.trim();
|
|
2023
|
+
}
|
|
2024
|
+
function finalizeInboundContext(params) {
|
|
2025
|
+
const ctx = { ...params };
|
|
2026
|
+
const body = normalizeTextField(ctx.Body) ?? "";
|
|
2027
|
+
const rawBody = normalizeTextField(ctx.RawBody);
|
|
2028
|
+
const commandBody = normalizeTextField(ctx.CommandBody);
|
|
2029
|
+
ctx.Body = body;
|
|
2030
|
+
ctx.RawBody = rawBody;
|
|
2031
|
+
ctx.CommandBody = commandBody;
|
|
2032
|
+
ctx.BodyForAgent = normalizeTextField(ctx.BodyForAgent) ?? commandBody ?? rawBody ?? body;
|
|
2033
|
+
ctx.BodyForCommands = normalizeTextField(ctx.BodyForCommands) ?? commandBody ?? rawBody ?? body;
|
|
2034
|
+
ctx.CommandAuthorized = ctx.CommandAuthorized === true;
|
|
2035
|
+
const mediaCount = countMediaEntries(ctx);
|
|
2036
|
+
if (mediaCount > 0) {
|
|
2037
|
+
const mediaTypes = readStringArray(ctx.MediaTypes);
|
|
2038
|
+
const fallbackType = typeof ctx.MediaType === "string" && ctx.MediaType.trim() ? ctx.MediaType.trim() : "application/octet-stream";
|
|
2039
|
+
const padded = mediaTypes.slice();
|
|
2040
|
+
while (padded.length < mediaCount) {
|
|
2041
|
+
padded.push(fallbackType);
|
|
2042
|
+
}
|
|
2043
|
+
ctx.MediaTypes = padded;
|
|
2044
|
+
ctx.MediaType = padded[0] ?? fallbackType;
|
|
2045
|
+
}
|
|
2046
|
+
return ctx;
|
|
2047
|
+
}
|
|
2048
|
+
function createReplyDispatcher(options) {
|
|
2049
|
+
let chain = Promise.resolve();
|
|
2050
|
+
let pending = 1;
|
|
2051
|
+
let completeCalled = false;
|
|
2052
|
+
const counts = {
|
|
2053
|
+
tool: 0,
|
|
2054
|
+
block: 0,
|
|
2055
|
+
final: 0
|
|
2056
|
+
};
|
|
2057
|
+
const enqueue = (kind, payload) => {
|
|
2058
|
+
counts[kind] += 1;
|
|
2059
|
+
pending += 1;
|
|
2060
|
+
chain = chain.then(async () => {
|
|
2061
|
+
await options.deliver(payload, { kind });
|
|
2062
|
+
}).catch((error) => {
|
|
2063
|
+
options.onError?.(error, { kind });
|
|
2064
|
+
}).finally(() => {
|
|
2065
|
+
pending -= 1;
|
|
2066
|
+
if (pending === 1 && completeCalled) {
|
|
2067
|
+
pending -= 1;
|
|
2068
|
+
}
|
|
2069
|
+
if (pending === 0) {
|
|
2070
|
+
options.onIdle?.();
|
|
2071
|
+
}
|
|
2072
|
+
});
|
|
2073
|
+
return true;
|
|
2074
|
+
};
|
|
2075
|
+
const markComplete = () => {
|
|
2076
|
+
if (completeCalled) {
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
completeCalled = true;
|
|
2080
|
+
void Promise.resolve().then(() => {
|
|
2081
|
+
if (pending === 1) {
|
|
2082
|
+
pending -= 1;
|
|
2083
|
+
if (pending === 0) {
|
|
2084
|
+
options.onIdle?.();
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
});
|
|
2088
|
+
};
|
|
2089
|
+
return {
|
|
2090
|
+
sendToolResult: (payload) => enqueue("tool", payload),
|
|
2091
|
+
sendBlockReply: (payload) => enqueue("block", payload),
|
|
2092
|
+
sendFinalReply: (payload) => enqueue("final", payload),
|
|
2093
|
+
waitForIdle: () => chain,
|
|
2094
|
+
getQueuedCounts: () => ({ ...counts }),
|
|
2095
|
+
markComplete
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
function createReplyDispatcherWithTyping(options) {
|
|
2099
|
+
const dispatcher = createReplyDispatcher({
|
|
2100
|
+
deliver: options.deliver,
|
|
2101
|
+
onError: options.onError,
|
|
2102
|
+
onIdle: options.onIdle
|
|
2103
|
+
});
|
|
2104
|
+
return {
|
|
2105
|
+
dispatcher,
|
|
2106
|
+
replyOptions: {
|
|
2107
|
+
onReplyStart: options.onReplyStart
|
|
2108
|
+
},
|
|
2109
|
+
markDispatchIdle: () => {
|
|
2110
|
+
options.onIdle?.();
|
|
2111
|
+
}
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
2114
|
+
async function withReplyDispatcher(params) {
|
|
2115
|
+
try {
|
|
2116
|
+
return await params.run();
|
|
2117
|
+
} finally {
|
|
2118
|
+
params.dispatcher.markComplete();
|
|
2119
|
+
try {
|
|
2120
|
+
await params.dispatcher.waitForIdle();
|
|
2121
|
+
} finally {
|
|
2122
|
+
await params.onSettled?.();
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
async function dispatchReplyFromConfig(params) {
|
|
2127
|
+
const finalized = finalizeInboundContext(params.ctx);
|
|
2128
|
+
let queuedFinal = false;
|
|
2129
|
+
await params.bridgeDispatch({
|
|
2130
|
+
ctx: finalized,
|
|
2131
|
+
cfg: params.cfg,
|
|
2132
|
+
dispatcherOptions: {
|
|
2133
|
+
deliver: async (payload, info) => {
|
|
2134
|
+
if (info.kind === "tool") {
|
|
2135
|
+
params.dispatcher.sendToolResult(payload);
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
if (info.kind === "block") {
|
|
2139
|
+
params.dispatcher.sendBlockReply(payload);
|
|
2140
|
+
return;
|
|
2141
|
+
}
|
|
2142
|
+
queuedFinal = params.dispatcher.sendFinalReply(payload);
|
|
2143
|
+
},
|
|
2144
|
+
onError: () => void 0,
|
|
2145
|
+
...params.replyOptions ?? {}
|
|
2146
|
+
}
|
|
2147
|
+
});
|
|
2148
|
+
return {
|
|
2149
|
+
queuedFinal,
|
|
2150
|
+
counts: params.dispatcher.getQueuedCounts()
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
function resolveHumanDelayConfig(_cfg, _agentId) {
|
|
2154
|
+
return void 0;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
// src/plugins/runtime-media.ts
|
|
2158
|
+
import { existsSync } from "fs";
|
|
2159
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2160
|
+
import { randomUUID } from "crypto";
|
|
2161
|
+
import { tmpdir } from "os";
|
|
2162
|
+
import { basename, extname, isAbsolute, resolve } from "path";
|
|
2163
|
+
function detectMimeFromBuffer(buffer) {
|
|
2164
|
+
if (buffer.length >= 8 && buffer.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
|
|
2165
|
+
return "image/png";
|
|
2166
|
+
}
|
|
2167
|
+
if (buffer.length >= 3 && buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) {
|
|
2168
|
+
return "image/jpeg";
|
|
2169
|
+
}
|
|
2170
|
+
if (buffer.length >= 6 && buffer.subarray(0, 6).toString("ascii") === "GIF87a") {
|
|
2171
|
+
return "image/gif";
|
|
2172
|
+
}
|
|
2173
|
+
if (buffer.length >= 6 && buffer.subarray(0, 6).toString("ascii") === "GIF89a") {
|
|
2174
|
+
return "image/gif";
|
|
2175
|
+
}
|
|
2176
|
+
if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") {
|
|
2177
|
+
return "image/webp";
|
|
2178
|
+
}
|
|
2179
|
+
if (buffer.length >= 4 && buffer.subarray(0, 4).toString("ascii") === "%PDF") {
|
|
2180
|
+
return "application/pdf";
|
|
2181
|
+
}
|
|
2182
|
+
if (buffer.length >= 12 && buffer.subarray(4, 8).toString("ascii") === "ftyp") {
|
|
2183
|
+
return "video/mp4";
|
|
2184
|
+
}
|
|
2185
|
+
return void 0;
|
|
2186
|
+
}
|
|
2187
|
+
function mimeToExtension(contentType, fileName) {
|
|
2188
|
+
const byName = extname(fileName ?? "").trim();
|
|
2189
|
+
if (byName) {
|
|
2190
|
+
return byName;
|
|
2191
|
+
}
|
|
2192
|
+
switch (contentType) {
|
|
2193
|
+
case "image/png":
|
|
2194
|
+
return ".png";
|
|
2195
|
+
case "image/jpeg":
|
|
2196
|
+
return ".jpg";
|
|
2197
|
+
case "image/gif":
|
|
2198
|
+
return ".gif";
|
|
2199
|
+
case "image/webp":
|
|
2200
|
+
return ".webp";
|
|
2201
|
+
case "application/pdf":
|
|
2202
|
+
return ".pdf";
|
|
2203
|
+
case "video/mp4":
|
|
2204
|
+
return ".mp4";
|
|
2205
|
+
default:
|
|
2206
|
+
return ".bin";
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
async function saveMediaBuffer(buffer, contentType, direction, maxBytes, fileName) {
|
|
2210
|
+
if (typeof maxBytes === "number" && Number.isFinite(maxBytes) && buffer.length > maxBytes) {
|
|
2211
|
+
throw new Error(`media exceeds maxBytes (${buffer.length} > ${maxBytes})`);
|
|
2212
|
+
}
|
|
2213
|
+
const detectedContentType = contentType ?? detectMimeFromBuffer(buffer) ?? "application/octet-stream";
|
|
2214
|
+
const targetDir = resolve(tmpdir(), "nextclaw-media", direction ?? "shared");
|
|
2215
|
+
await mkdir(targetDir, { recursive: true });
|
|
2216
|
+
const targetPath = resolve(
|
|
2217
|
+
targetDir,
|
|
2218
|
+
`${randomUUID()}${mimeToExtension(detectedContentType, fileName)}`
|
|
2219
|
+
);
|
|
2220
|
+
await writeFile(targetPath, buffer);
|
|
2221
|
+
return {
|
|
2222
|
+
path: targetPath,
|
|
2223
|
+
contentType: detectedContentType
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
async function fetchRemoteMedia(params) {
|
|
2227
|
+
const response = await fetch(params.url);
|
|
2228
|
+
if (!response.ok) {
|
|
2229
|
+
throw new Error(`failed to fetch media: ${response.status} ${response.statusText}`);
|
|
2230
|
+
}
|
|
2231
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
2232
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
2233
|
+
if (typeof params.maxBytes === "number" && Number.isFinite(params.maxBytes) && buffer.length > params.maxBytes) {
|
|
2234
|
+
throw new Error(`media exceeds maxBytes (${buffer.length} > ${params.maxBytes})`);
|
|
2235
|
+
}
|
|
2236
|
+
return {
|
|
2237
|
+
buffer,
|
|
2238
|
+
contentType: asString(response.headers.get("content-type")) ?? detectMimeFromBuffer(buffer),
|
|
2239
|
+
fileName: basename(new URL(params.url).pathname) || "file"
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
function isRemoteUrl(value) {
|
|
2243
|
+
return /^https?:\/\//i.test(value);
|
|
2244
|
+
}
|
|
2245
|
+
function assertAllowedLocalPath(filePath, localRoots) {
|
|
2246
|
+
if (localRoots.length === 0) {
|
|
2247
|
+
return;
|
|
2248
|
+
}
|
|
2249
|
+
const resolvedPath = resolve(filePath);
|
|
2250
|
+
const allowed = localRoots.some((root) => {
|
|
2251
|
+
const resolvedRoot = resolve(root);
|
|
2252
|
+
return resolvedPath === resolvedRoot || resolvedPath.startsWith(`${resolvedRoot}/`);
|
|
2253
|
+
});
|
|
2254
|
+
if (!allowed) {
|
|
2255
|
+
throw new Error(`local media path is outside allowed roots: ${resolvedPath}`);
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
async function loadWebMedia(url, options) {
|
|
2259
|
+
if (isRemoteUrl(url)) {
|
|
2260
|
+
return fetchRemoteMedia({
|
|
2261
|
+
url,
|
|
2262
|
+
maxBytes: asNumber(options?.maxBytes)
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
const localRoots = readStringArray(options?.localRoots);
|
|
2266
|
+
const candidate = isAbsolute(url) ? url : resolve(url);
|
|
2267
|
+
if (!existsSync(candidate)) {
|
|
2268
|
+
throw new Error(`local media file not found: ${candidate}`);
|
|
2269
|
+
}
|
|
2270
|
+
assertAllowedLocalPath(candidate, localRoots);
|
|
2271
|
+
const buffer = await readFile(candidate);
|
|
2272
|
+
const maxBytes = asNumber(options?.maxBytes);
|
|
2273
|
+
if (typeof maxBytes === "number" && buffer.length > maxBytes) {
|
|
2274
|
+
throw new Error(`media exceeds maxBytes (${buffer.length} > ${maxBytes})`);
|
|
2275
|
+
}
|
|
2276
|
+
return {
|
|
2277
|
+
buffer,
|
|
2278
|
+
fileName: basename(candidate),
|
|
2279
|
+
contentType: detectMimeFromBuffer(buffer)
|
|
2280
|
+
};
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
// src/plugins/runtime.ts
|
|
1772
2284
|
var bridge = {};
|
|
1773
2285
|
function setPluginRuntimeBridge(next) {
|
|
1774
2286
|
bridge = next ?? {};
|
|
@@ -1806,13 +2318,83 @@ function createPluginRuntime(params) {
|
|
|
1806
2318
|
loadConfig: () => loadConfigWithFallback(params.config),
|
|
1807
2319
|
writeConfigFile: async (next) => writeConfigWithFallback(next)
|
|
1808
2320
|
},
|
|
2321
|
+
logging: {
|
|
2322
|
+
shouldLogVerbose: () => false
|
|
2323
|
+
},
|
|
2324
|
+
media: {
|
|
2325
|
+
detectMime: async ({ buffer }) => detectMimeFromBuffer(buffer),
|
|
2326
|
+
loadWebMedia: async (url, options) => loadWebMedia(url, options)
|
|
2327
|
+
},
|
|
1809
2328
|
tools: {
|
|
1810
2329
|
createMemorySearchTool: () => toPluginTool(new MemorySearchTool(params.workspace)),
|
|
1811
2330
|
createMemoryGetTool: () => toPluginTool(new MemoryGetTool(params.workspace))
|
|
1812
2331
|
},
|
|
1813
2332
|
channel: {
|
|
2333
|
+
media: {
|
|
2334
|
+
fetchRemoteMedia: async (input) => fetchRemoteMedia({
|
|
2335
|
+
url: asString(input.url) ?? "",
|
|
2336
|
+
maxBytes: asNumber(input.maxBytes)
|
|
2337
|
+
}),
|
|
2338
|
+
saveMediaBuffer: async (buffer, contentType, direction, maxBytes, fileName) => saveMediaBuffer(buffer, contentType, direction, maxBytes, fileName)
|
|
2339
|
+
},
|
|
2340
|
+
text: {
|
|
2341
|
+
chunkMarkdownText: (text, limit) => splitTextIntoChunks(text, limit),
|
|
2342
|
+
resolveMarkdownTableMode: () => "preserve",
|
|
2343
|
+
convertMarkdownTables: (text) => text,
|
|
2344
|
+
resolveTextChunkLimit,
|
|
2345
|
+
resolveChunkMode: () => "length",
|
|
2346
|
+
chunkTextWithMode: (text, limit) => splitTextIntoChunks(text, limit),
|
|
2347
|
+
hasControlCommand: (text) => hasControlCommand(text)
|
|
2348
|
+
},
|
|
1814
2349
|
reply: {
|
|
2350
|
+
resolveEnvelopeFormatOptions,
|
|
2351
|
+
formatAgentEnvelope,
|
|
2352
|
+
finalizeInboundContext,
|
|
2353
|
+
withReplyDispatcher: async (input) => withReplyDispatcher({
|
|
2354
|
+
dispatcher: input.dispatcher,
|
|
2355
|
+
run: input.run,
|
|
2356
|
+
onSettled: typeof input.onSettled === "function" ? input.onSettled : void 0
|
|
2357
|
+
}),
|
|
2358
|
+
dispatchReplyFromConfig: async (input) => dispatchReplyFromConfig({
|
|
2359
|
+
ctx: asRecord(input.ctx),
|
|
2360
|
+
cfg: input.cfg,
|
|
2361
|
+
dispatcher: input.dispatcher,
|
|
2362
|
+
bridgeDispatch: dispatchReplyWithFallback,
|
|
2363
|
+
replyOptions: asRecord(input.replyOptions)
|
|
2364
|
+
}),
|
|
2365
|
+
createReplyDispatcherWithTyping: (input) => createReplyDispatcherWithTyping({
|
|
2366
|
+
deliver: input.deliver,
|
|
2367
|
+
onError: typeof input.onError === "function" ? input.onError : void 0,
|
|
2368
|
+
onIdle: typeof input.onIdle === "function" ? input.onIdle : void 0,
|
|
2369
|
+
onReplyStart: typeof input.onReplyStart === "function" ? input.onReplyStart : void 0
|
|
2370
|
+
}),
|
|
2371
|
+
resolveHumanDelayConfig,
|
|
1815
2372
|
dispatchReplyWithBufferedBlockDispatcher: async (dispatchParams) => dispatchReplyWithFallback(dispatchParams)
|
|
2373
|
+
},
|
|
2374
|
+
routing: {
|
|
2375
|
+
resolveAgentRoute
|
|
2376
|
+
},
|
|
2377
|
+
pairing: {
|
|
2378
|
+
readAllowFromStore: async () => [],
|
|
2379
|
+
upsertPairingRequest: async () => ({
|
|
2380
|
+
code: randomUUID2().replace(/-/g, "").slice(0, 8).toUpperCase(),
|
|
2381
|
+
created: true
|
|
2382
|
+
})
|
|
2383
|
+
},
|
|
2384
|
+
commands: {
|
|
2385
|
+
shouldComputeCommandAuthorized: (text) => hasControlCommand(text),
|
|
2386
|
+
resolveCommandAuthorizedFromAuthorizers
|
|
2387
|
+
},
|
|
2388
|
+
debounce: {
|
|
2389
|
+
resolveInboundDebounceMs,
|
|
2390
|
+
createInboundDebouncer: (input) => createInboundDebouncer({
|
|
2391
|
+
debounceMs: asNumber(input.debounceMs),
|
|
2392
|
+
buildKey: typeof input.buildKey === "function" ? input.buildKey : void 0,
|
|
2393
|
+
shouldDebounce: typeof input.shouldDebounce === "function" ? input.shouldDebounce : void 0,
|
|
2394
|
+
resolveDebounceMs: typeof input.resolveDebounceMs === "function" ? input.resolveDebounceMs : void 0,
|
|
2395
|
+
onFlush: typeof input.onFlush === "function" ? input.onFlush : void 0,
|
|
2396
|
+
onError: typeof input.onError === "function" ? input.onError : void 0
|
|
2397
|
+
})
|
|
1816
2398
|
}
|
|
1817
2399
|
}
|
|
1818
2400
|
};
|
|
@@ -2686,7 +3268,7 @@ function buildPluginStatusReport(params) {
|
|
|
2686
3268
|
|
|
2687
3269
|
// src/plugins/uninstall.ts
|
|
2688
3270
|
import fs8 from "fs/promises";
|
|
2689
|
-
import { existsSync, statSync } from "fs";
|
|
3271
|
+
import { existsSync as existsSync2, statSync } from "fs";
|
|
2690
3272
|
import path9 from "path";
|
|
2691
3273
|
import { getWorkspacePathFromConfig as getWorkspacePathFromConfig3 } from "@nextclaw/core";
|
|
2692
3274
|
function isLinkedPathInstall(record) {
|
|
@@ -2827,7 +3409,7 @@ function matchesPluginLoadPath(rawPath, pluginId) {
|
|
|
2827
3409
|
return false;
|
|
2828
3410
|
}
|
|
2829
3411
|
const resolvedPath = path9.resolve(normalizedPath);
|
|
2830
|
-
if (!
|
|
3412
|
+
if (!existsSync2(resolvedPath)) {
|
|
2831
3413
|
return false;
|
|
2832
3414
|
}
|
|
2833
3415
|
const candidateRoot = (() => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/openclaw-compat",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.29",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "OpenClaw plugin compatibility layer for NextClaw.",
|
|
6
6
|
"type": "module",
|
|
@@ -19,21 +19,21 @@
|
|
|
19
19
|
"jiti": "^1.21.7",
|
|
20
20
|
"jszip": "^3.10.1",
|
|
21
21
|
"tar": "^7.4.3",
|
|
22
|
-
"@nextclaw/channel-plugin-
|
|
23
|
-
"@nextclaw/channel-plugin-
|
|
24
|
-
"@nextclaw/channel-plugin-
|
|
25
|
-
"@nextclaw/channel-plugin-
|
|
26
|
-
"@nextclaw/channel-plugin-
|
|
27
|
-
"@nextclaw/channel-plugin-
|
|
28
|
-
"@nextclaw/channel-plugin-
|
|
29
|
-
"@nextclaw/channel-plugin-
|
|
30
|
-
"@nextclaw/channel-plugin-weixin": "0.1.9",
|
|
31
|
-
"@nextclaw/core": "0.11.1",
|
|
32
|
-
"@nextclaw/channel-plugin-wecom": "0.2.15",
|
|
33
|
-
"@nextclaw/channel-runtime": "0.4.1",
|
|
34
|
-
"@nextclaw/ncp": "0.3.2",
|
|
22
|
+
"@nextclaw/channel-plugin-email": "0.2.16",
|
|
23
|
+
"@nextclaw/channel-plugin-dingtalk": "0.2.16",
|
|
24
|
+
"@nextclaw/channel-plugin-slack": "0.2.16",
|
|
25
|
+
"@nextclaw/channel-plugin-mochat": "0.2.16",
|
|
26
|
+
"@nextclaw/channel-plugin-feishu": "0.2.19",
|
|
27
|
+
"@nextclaw/channel-plugin-discord": "0.2.16",
|
|
28
|
+
"@nextclaw/channel-plugin-qq": "0.2.16",
|
|
29
|
+
"@nextclaw/channel-plugin-wecom": "0.2.16",
|
|
35
30
|
"@nextclaw/ncp-toolkit": "0.4.2",
|
|
36
|
-
"@nextclaw/channel-
|
|
31
|
+
"@nextclaw/channel-runtime": "0.4.2",
|
|
32
|
+
"@nextclaw/ncp": "0.3.2",
|
|
33
|
+
"@nextclaw/channel-plugin-telegram": "0.2.16",
|
|
34
|
+
"@nextclaw/channel-plugin-weixin": "0.1.9",
|
|
35
|
+
"@nextclaw/channel-plugin-whatsapp": "0.2.16",
|
|
36
|
+
"@nextclaw/core": "0.11.1"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^20.17.6",
|