chore(lint): enable object and reassignment rules

This commit is contained in:
Peter Steinberger
2026-05-31 09:32:33 +01:00
parent ea11b8ad3d
commit f5eca3f84c
197 changed files with 619 additions and 638 deletions
+3
View File
@@ -20,6 +20,7 @@
"eslint/no-multi-str": "error",
"eslint/no-new": "error",
"eslint/no-object-constructor": "error",
"eslint/no-param-reassign": "error",
"eslint/no-proto": "error",
"eslint/no-regex-spaces": "error",
"eslint/no-return-assign": "error",
@@ -44,8 +45,10 @@
"eslint/default-case-last": "error",
"eslint/default-param-last": "error",
"eslint/prefer-exponentiation-operator": "error",
"eslint/prefer-const": "error",
"eslint/prefer-numeric-literals": "error",
"eslint/prefer-object-has-own": "error",
"eslint/object-shorthand": "error",
"eslint/prefer-rest-params": "error",
"eslint/prefer-spread": "error",
"eslint/radix": "error",
@@ -17,7 +17,6 @@ type BrowserMockBundle = {
};
function makeBrowser(targetId: string, url: string): BrowserMockBundle {
let context: import("playwright-core").BrowserContext;
const browserClose = vi.fn(async () => {});
const page = {
on: vi.fn(),
@@ -26,7 +25,7 @@ function makeBrowser(targetId: string, url: string): BrowserMockBundle {
url: vi.fn(() => url),
} as unknown as import("playwright-core").Page;
context = {
const context: import("playwright-core").BrowserContext = {
pages: () => [page],
on: vi.fn(),
newCDPSession: vi.fn(async () => ({
@@ -66,7 +65,6 @@ function makeEmptyBrowser(): BrowserMockBundle {
}
function makeDisconnectedReadBrowser(): BrowserMockBundle {
let context: import("playwright-core").BrowserContext;
const browserClose = vi.fn(async () => {});
const page = {
on: vi.fn(),
@@ -79,7 +77,7 @@ function makeDisconnectedReadBrowser(): BrowserMockBundle {
}),
} as unknown as import("playwright-core").Page;
context = {
const context: import("playwright-core").BrowserContext = {
pages: () => [page],
on: vi.fn(),
newCDPSession: vi.fn(async () => {
@@ -43,7 +43,6 @@ function requireFetchInit(init: Parameters<typeof fetch>[1]): FetchInitWithDispa
}
function makeBrowser(pages: MockPageSpec[]): BrowserMockBundle {
let context: import("playwright-core").BrowserContext;
const browserClose = vi.fn(async () => {});
const targetIdByPage = new Map<import("playwright-core").Page, string | undefined>();
@@ -58,7 +57,7 @@ function makeBrowser(pages: MockPageSpec[]): BrowserMockBundle {
return page;
});
context = {
const context: import("playwright-core").BrowserContext = {
pages: () => pageObjects,
on: vi.fn(),
newCDPSession: vi.fn(async (page: import("playwright-core").Page) => ({
@@ -32,7 +32,6 @@ export async function responseBodyViaPlaywright(opts: {
const promise = new Promise<unknown>((resolve, reject) => {
let done = false;
let timer: NodeJS.Timeout | undefined;
let handler: ((resp: unknown) => void) | undefined;
const cleanup = () => {
if (timer) {
@@ -44,7 +43,7 @@ export async function responseBodyViaPlaywright(opts: {
}
};
handler = (resp: unknown) => {
const handler: ((resp: unknown) => void) | undefined = (resp: unknown) => {
if (done) {
return;
}
+2 -1
View File
@@ -202,8 +202,9 @@ export class CodexAppServerClient {
request<T = JsonValue | undefined>(
method: string,
params?: unknown,
options?: { timeoutMs?: number; signal?: AbortSignal },
optionsInput?: { timeoutMs?: number; signal?: AbortSignal },
): Promise<T> {
let options = optionsInput;
options ??= {};
if (this.closed) {
return Promise.reject(this.closeError ?? new Error("codex app-server client is closed"));
@@ -487,13 +487,12 @@ async function delay(ms: number, signal?: AbortSignal): Promise<void> {
throw abortError(signal);
}
await new Promise<void>((resolve, reject) => {
let timer: ReturnType<typeof setTimeout>;
const onAbort = () => {
clearTimeout(timer);
signal?.removeEventListener("abort", onAbort);
reject(abortError(signal));
};
timer = setTimeout(() => {
const timer: ReturnType<typeof setTimeout> = setTimeout(() => {
signal?.removeEventListener("abort", onAbort);
resolve();
}, ms);
@@ -2735,17 +2735,18 @@ describe("runCodexAppServerAttempt", () => {
});
it("does not drop turn completion notifications emitted while turn/start is in flight", async () => {
let harness: ReturnType<typeof createAppServerHarness>;
harness = createAppServerHarness(async (method) => {
if (method === "thread/start") {
return threadStartResult();
}
if (method === "turn/start") {
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
return turnStartResult("turn-1", "completed");
}
return {};
});
const harness: ReturnType<typeof createAppServerHarness> = createAppServerHarness(
async (method) => {
if (method === "thread/start") {
return threadStartResult();
}
if (method === "turn/start") {
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
return turnStartResult("turn-1", "completed");
}
return {};
},
);
const result = await runCodexAppServerAttempt(
createParams(path.join(tempDir, "session.jsonl"), path.join(tempDir, "workspace")),
@@ -2755,30 +2756,31 @@ describe("runCodexAppServerAttempt", () => {
});
it("does not fail when a buffered terminal notification is followed by client close", async () => {
let harness: ReturnType<typeof createAppServerHarness>;
let resolveBufferedTerminal!: () => void;
const bufferedTerminal = new Promise<void>((resolve) => {
resolveBufferedTerminal = resolve;
});
harness = createAppServerHarness(async (method) => {
if (method === "thread/start") {
return threadStartResult();
}
if (method === "turn/start") {
await harness.notify({
method: "item/started",
params: {
threadId: "thread-1",
turnId: "turn-1",
item: { id: "tool-1", type: "commandExecution" },
},
});
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
resolveBufferedTerminal();
return turnStartResult("turn-1", "inProgress");
}
return {};
});
const harness: ReturnType<typeof createAppServerHarness> = createAppServerHarness(
async (method) => {
if (method === "thread/start") {
return threadStartResult();
}
if (method === "turn/start") {
await harness.notify({
method: "item/started",
params: {
threadId: "thread-1",
turnId: "turn-1",
item: { id: "tool-1", type: "commandExecution" },
},
});
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
resolveBufferedTerminal();
return turnStartResult("turn-1", "inProgress");
}
return {};
},
);
const run = runCodexAppServerAttempt(
createParams(path.join(tempDir, "session.jsonl"), path.join(tempDir, "workspace")),
@@ -2795,24 +2797,25 @@ describe("runCodexAppServerAttempt", () => {
});
it("does not time out when turn progress arrives before turn/start returns", async () => {
let harness: ReturnType<typeof createAppServerHarness>;
harness = createAppServerHarness(async (method) => {
if (method === "thread/start") {
return threadStartResult();
}
if (method === "turn/start") {
await harness.notify({
method: "turn/started",
params: {
threadId: "thread-1",
turnId: "turn-1",
turn: { id: "turn-1", status: "inProgress" },
},
});
return turnStartResult("turn-1", "inProgress");
}
return {};
});
const harness: ReturnType<typeof createAppServerHarness> = createAppServerHarness(
async (method) => {
if (method === "thread/start") {
return threadStartResult();
}
if (method === "turn/start") {
await harness.notify({
method: "turn/started",
params: {
threadId: "thread-1",
turnId: "turn-1",
turn: { id: "turn-1", status: "inProgress" },
},
});
return turnStartResult("turn-1", "inProgress");
}
return {};
},
);
const params = createParams(
path.join(tempDir, "session.jsonl"),
path.join(tempDir, "workspace"),
+37 -21
View File
@@ -475,8 +475,8 @@ export async function runCodexAppServerAttempt(
sessionKey: contextSessionKey,
...(startupAuthProfileId ? { authProfileId: startupAuthProfileId } : {}),
};
let activeSessionId = params.sessionId;
let activeSessionFile = params.sessionFile;
const activeSessionId = params.sessionId;
const activeSessionFile = params.sessionFile;
const buildActiveRunAttemptParams = (): EmbeddedRunAttemptParams => ({
...runtimeParams,
sessionId: activeSessionId,
@@ -982,12 +982,7 @@ export async function runCodexAppServerAttempt(
prompt: codexTurnPromptText,
tools: toolBridge.availableSpecs,
});
let projector: CodexAppServerEventProjector | undefined;
let turnId: string | undefined;
const pendingNotifications: CodexServerNotification[] = [];
let userInputBridge: ReturnType<typeof createCodexUserInputBridge> | undefined;
let steeringQueue: ReturnType<typeof createCodexSteeringQueue> | undefined;
let completed = false;
let terminalTurnNotificationQueued = false;
let timedOut = false;
@@ -1034,6 +1029,14 @@ export async function runCodexAppServerAttempt(
| undefined;
let terminalDynamicToolReleaseCheckScheduled = false;
let currentTurnHadNonTerminalDynamicToolResult = false;
const turnIdRef: { current?: string } = {};
const projectorRef: { current?: CodexAppServerEventProjector } = {};
const userInputBridgeRef: {
current?: ReturnType<typeof createCodexUserInputBridge>;
} = {};
const steeringQueueRef: {
current?: ReturnType<typeof createCodexSteeringQueue>;
} = {};
const renewNativeHookRelayForTurnProgress = () => {
if (!nativeHookRelay || options.nativeHookRelay?.ttlMs !== undefined) {
@@ -1060,7 +1063,7 @@ export async function runCodexAppServerAttempt(
const turnWatches = createCodexAttemptTurnWatchController({
threadId: thread.threadId,
signal: runAbortController.signal,
getTurnId: () => turnId,
getTurnId: () => turnIdRef.current,
isCompleted: () => completed,
isTerminalTurnNotificationQueued: () => terminalTurnNotificationQueued,
getActiveAppServerTurnRequests: () => activeAppServerTurnRequests,
@@ -1078,7 +1081,7 @@ export async function runCodexAppServerAttempt(
turnCompletionIdleTimeoutMessage =
"codex app-server turn idle timed out waiting for turn/completed";
},
onMarkTimedOut: () => projector?.markTimedOut(),
onMarkTimedOut: () => projectorRef.current?.markTimedOut(),
onAbort: (reason) => runAbortController.abort(reason),
onCompleted: () => {
completed = true;
@@ -1245,6 +1248,10 @@ export async function runCodexAppServerAttempt(
});
const handleNotification = async (notification: CodexServerNotification) => {
const projector = projectorRef.current;
const turnId = turnIdRef.current;
const userInputBridge = userInputBridgeRef.current;
const steeringQueue = steeringQueueRef.current;
userInputBridge?.handleNotification(notification);
if (!projector || !turnId) {
pendingNotifications.push(notification);
@@ -1296,6 +1303,9 @@ export async function runCodexAppServerAttempt(
}
};
const enqueueNotification = (notification: CodexServerNotification): Promise<void> => {
const projector = projectorRef.current;
const turnId = turnIdRef.current;
const userInputBridge = userInputBridgeRef.current;
const correlation = describeCodexNotificationCorrelation(notification, {
threadId: thread.threadId,
...(turnId ? { turnId } : {}),
@@ -1371,6 +1381,9 @@ export async function runCodexAppServerAttempt(
});
const notificationCleanup = client.addNotificationHandler(enqueueNotification);
const requestCleanup = client.addRequestHandler(async (request) => {
const turnId = turnIdRef.current;
const userInputBridge = userInputBridgeRef.current;
const projector = projectorRef.current;
let armCompletionWatchOnResponse = false;
let requestCountsAsTurnActivity = false;
const markCurrentTurnRequestProgress = () => {
@@ -1632,7 +1645,6 @@ export async function runCodexAppServerAttempt(
}
}
});
let closeCleanup: (() => void) | undefined;
const buildLlmInputEvent = () => ({
runId: params.runId,
@@ -1880,10 +1892,10 @@ export async function runCodexAppServerAttempt(
releaseSharedClientLease = undefined;
throw new Error("codex app-server turn/start failed without an error");
}
turnId = turn.turn.id;
turnIdRef.current = turn.turn.id;
const activeTurnId = turn.turn.id;
emitExecutionPhaseOnce("turn_accepted", { phase: "turn_accepted" });
userInputBridge = createCodexUserInputBridge({
userInputBridgeRef.current = createCodexUserInputBridge({
paramsForRun: params,
threadId: thread.threadId,
turnId: activeTurnId,
@@ -1895,7 +1907,7 @@ export async function runCodexAppServerAttempt(
prompt: codexTurnPromptText,
imagesCount: params.images?.length ?? 0,
});
projector = new CodexAppServerEventProjector(params, thread.threadId, activeTurnId, {
projectorRef.current = new CodexAppServerEventProjector(params, thread.threadId, activeTurnId, {
nativePostToolUseRelayEnabled:
nativeHookRelay?.allowedEvents.includes("post_tool_use") === true &&
nativeHookRelay.shouldRelayEvent("post_tool_use"),
@@ -1909,7 +1921,7 @@ export async function runCodexAppServerAttempt(
) {
terminalTurnNotificationQueued = true;
}
closeCleanup = (
const closeCleanup: (() => void) | undefined = (
client as {
addCloseHandler?: (handler: (client: CodexAppServerClient) => void) => () => void;
}
@@ -1933,7 +1945,10 @@ export async function runCodexAppServerAttempt(
resolveCompletion?.();
});
emitLifecycleStart();
const activeProjector = projector;
const activeProjector = projectorRef.current;
if (!activeProjector) {
throw new Error("codex app-server projector was not initialized");
}
turnWatches.armTerminalIdleWatch();
turnWatches.touchActivity("turn:start", { arm: true });
turnWatches.armAttemptIdleWatch();
@@ -1956,16 +1971,17 @@ export async function runCodexAppServerAttempt(
client,
threadId: thread.threadId,
turnId: activeTurnId,
answerPendingUserInput: (text) => userInputBridge?.handleQueuedMessage(text) ?? false,
answerPendingUserInput: (text) =>
userInputBridgeRef.current?.handleQueuedMessage(text) ?? false,
signal: runAbortController.signal,
});
steeringQueue = activeSteeringQueue;
steeringQueueRef.current = activeSteeringQueue;
const handle = {
kind: "embedded" as const,
queueMessage: async (text: string, options?: CodexSteeringQueueOptions) =>
activeSteeringQueue.queue(text, options),
isStreaming: () => !completed,
isCompacting: () => projector?.isCompacting() ?? false,
isCompacting: () => projectorRef.current?.isCompacting() ?? false,
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
cancel: () => runAbortController.abort("cancelled"),
abort: () => runAbortController.abort("aborted"),
@@ -2276,7 +2292,7 @@ export async function runCodexAppServerAttempt(
},
});
if (!timedOut && !runAbortController.signal.aborted) {
await steeringQueue?.flushPending();
await steeringQueueRef.current?.flushPending();
}
if (!timedOut) {
await unsubscribeCodexThreadBestEffort(client, {
@@ -2284,7 +2300,7 @@ export async function runCodexAppServerAttempt(
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS,
});
}
userInputBridge?.cancelPending();
userInputBridgeRef.current?.cancelPending();
turnWatches.clearAllTimers();
notificationCleanup();
requestCleanup();
@@ -2306,7 +2322,7 @@ export async function runCodexAppServerAttempt(
await releaseSandboxExecEnvironment();
runAbortController.signal.removeEventListener("abort", abortListener);
params.abortSignal?.removeEventListener("abort", abortFromUpstream);
steeringQueue?.cancel();
steeringQueueRef.current?.cancel();
clearActiveEmbeddedRun(params.sessionId, handle, params.sessionKey);
}
}
@@ -452,13 +452,13 @@ export const discordApprovalNativeRuntime = createChannelApprovalNativeRuntimeAd
const container =
view.approvalKind === "plugin"
? createPluginApprovalRequestContainer({
view: view,
view,
cfg,
accountId: resolved.accountId,
actionRow,
})
: createExecApprovalRequestContainer({
view: view,
view,
cfg,
accountId: resolved.accountId,
actionRow,
@@ -475,12 +475,12 @@ export const discordApprovalNativeRuntime = createChannelApprovalNativeRuntimeAd
const container =
view.approvalKind === "plugin"
? createPluginResolvedContainer({
view: view,
view,
cfg,
accountId: resolvedContext.accountId,
})
: createExecResolvedContainer({
view: view,
view,
cfg,
accountId: resolvedContext.accountId,
});
@@ -494,12 +494,12 @@ export const discordApprovalNativeRuntime = createChannelApprovalNativeRuntimeAd
const container =
view.approvalKind === "plugin"
? createPluginExpiredContainer({
view: view,
view,
cfg,
accountId: resolvedContext.accountId,
})
: createExecExpiredContainer({
view: view,
view,
cfg,
accountId: resolvedContext.accountId,
});
@@ -143,14 +143,13 @@ describe("waitForDiscordGatewayStop", () => {
it("keeps the lifecycle handler active until disconnect returns on abort", async () => {
const onGatewayEvent = vi.fn(() => "stop" as const);
const fatalEvent = createGatewayEvent("fatal", "disconnect emitted error");
let emitFromDisconnect: ((event: DiscordGatewayEvent) => void) | undefined;
const { abort, detachLifecycle, disconnect, emitGatewayEvent, promise } = startGatewayWait({
onGatewayEvent,
disconnect: () => {
emitFromDisconnect?.(fatalEvent);
},
});
emitFromDisconnect = emitGatewayEvent;
const emitFromDisconnect: ((event: DiscordGatewayEvent) => void) | undefined = emitGatewayEvent;
abort.abort();
@@ -164,7 +163,6 @@ describe("waitForDiscordGatewayStop", () => {
const firstEvent = createGatewayEvent("fatal", "first failure");
const secondEvent = createGatewayEvent("fatal", "second failure");
const seenEvents: DiscordGatewayEvent[] = [];
let emitFromDisconnect: ((event: DiscordGatewayEvent) => void) | undefined;
const { emitGatewayEvent, promise } = startGatewayWait({
onGatewayEvent: (event) => {
seenEvents.push(event);
@@ -174,7 +172,7 @@ describe("waitForDiscordGatewayStop", () => {
emitFromDisconnect?.(secondEvent);
},
});
emitFromDisconnect = emitGatewayEvent;
const emitFromDisconnect: ((event: DiscordGatewayEvent) => void) | undefined = emitGatewayEvent;
emitGatewayEvent(firstEvent);
@@ -94,7 +94,7 @@ describe("createDiscordGatewayPlugin", () => {
error: vi.fn(),
exit: vi.fn(),
},
...(testing ? { testing: testing } : {}),
...(testing ? { testing } : {}),
});
}
+1 -1
View File
@@ -480,7 +480,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
string,
import("openclaw/plugin-sdk/reply-history").HistoryEntry[]
>();
let { botUserId, botUserName } = await fetchDiscordBotIdentity({
const { botUserId, botUserName } = await fetchDiscordBotIdentity({
client,
token,
runtime,
+2 -2
View File
@@ -326,7 +326,7 @@ export async function resolveDiscordChannelAllowlist(params: {
results.push({
input,
resolved: false,
channelName: channelName,
channelName,
});
continue;
}
@@ -361,7 +361,7 @@ export async function resolveDiscordChannelAllowlist(params: {
results.push({
input,
resolved: false,
channelName: channelName,
channelName,
});
}
@@ -76,7 +76,7 @@ const {
on: vi.fn(),
off: vi.fn(),
destroy: vi.fn(),
[Symbol.asyncIterator]: async function* () {},
async *[Symbol.asyncIterator]() {},
})),
},
state: {
@@ -2088,12 +2088,11 @@ describe("DiscordVoiceManager", () => {
const firstConnection = createConnectionMock();
const secondConnection = createConnectionMock();
joinVoiceChannelMock.mockReturnValueOnce(firstConnection).mockReturnValueOnce(secondConnection);
let manager!: InstanceType<typeof managerModule.DiscordVoiceManager>;
entersStateMock.mockImplementationOnce(async () => {
await manager.destroy();
throw new Error("The operation was aborted");
});
manager = createManager();
const manager: InstanceType<typeof managerModule.DiscordVoiceManager> = createManager();
const result = await manager.join({ guildId: "g1", channelId: "1001" });
+5 -11
View File
@@ -674,12 +674,6 @@ export class DiscordVoiceManager {
const player = voiceSdk.createAudioPlayer();
connection.subscribe(player);
let speakingHandler: ((userId: string) => void) | undefined;
let speakingEndHandler: ((userId: string) => void) | undefined;
let disconnectedHandler: (() => Promise<void>) | undefined;
let destroyedHandler: (() => void) | undefined;
let playerErrorHandler: ((err: Error) => void) | undefined;
let stopped = false;
const clearSessionIfCurrent = () => {
const active = this.sessions.get(guildId);
@@ -787,16 +781,16 @@ export class DiscordVoiceManager {
};
}
speakingHandler = (userId: string) => {
const speakingHandler: ((userId: string) => void) | undefined = (userId: string) => {
void this.handleSpeakingStart(entry, userId).catch((err) => {
logger.warn(`discord voice: capture failed: ${formatErrorMessage(err)}`);
});
};
speakingEndHandler = (userId: string) => {
const speakingEndHandler: ((userId: string) => void) | undefined = (userId: string) => {
this.scheduleCaptureFinalize(entry, userId, "speaker end");
};
disconnectedHandler = async () => {
const disconnectedHandler: (() => Promise<void>) | undefined = async () => {
try {
logVoiceVerbose(
`disconnected: attempting recovery guild ${guildId} channel ${channelId} grace=${reconnectGraceMs}ms`,
@@ -825,14 +819,14 @@ export class DiscordVoiceManager {
});
}
};
destroyedHandler = () => {
const destroyedHandler: (() => void) | undefined = () => {
clearSessionIfCurrent();
stopEntry(entry, {
destroyConnection: false,
reason: `destroyed guild ${guildId} channel ${channelId}`,
});
};
playerErrorHandler = (err: Error) => {
const playerErrorHandler: ((err: Error) => void) | undefined = (err: Error) => {
logger.warn(`discord voice: playback error: ${formatErrorMessage(err)}`);
};
+5 -4
View File
@@ -74,8 +74,6 @@ export function waitForAbortableDelay(
return new Promise((resolve) => {
let settled = false;
let timer: ReturnType<typeof setTimeout> | undefined;
let handleAbort: (() => void) | undefined;
const finish = (value: boolean) => {
if (settled) {
@@ -91,7 +89,7 @@ export function waitForAbortableDelay(
resolve(value);
};
handleAbort = () => {
const handleAbort: (() => void) | undefined = () => {
finish(false);
};
@@ -101,7 +99,10 @@ export function waitForAbortableDelay(
return;
}
timer = setTimeout(() => finish(true), resolveTimerTimeoutMs(delayMs, 1));
const timer: ReturnType<typeof setTimeout> | undefined = setTimeout(
() => finish(true),
resolveTimerTimeoutMs(delayMs, 1),
);
timer.unref?.();
});
}
+1 -1
View File
@@ -1203,7 +1203,7 @@ export async function handleFeishuMessage(params: {
}
const rootMsg = await getRootMessageInfo();
let feishuThreadId = ctx.threadId ?? rootMessageThreadId ?? rootMsg?.threadId;
const feishuThreadId = ctx.threadId ?? rootMessageThreadId ?? rootMsg?.threadId;
if (feishuThreadId) {
log(`feishu[${account.accountId}]: resolved thread ID: ${feishuThreadId}`);
}
@@ -25,7 +25,7 @@ function createCountingIterable<T>(values: T[]) {
let iterations = 0;
return {
values: {
[Symbol.iterator]: function* () {
*[Symbol.iterator]() {
iterations += 1;
yield* values;
},
+1 -2
View File
@@ -187,7 +187,6 @@ function waitForFeishuWsCycleEnd(params: {
return new Promise((resolve) => {
let settled = false;
let handleAbort: (() => void) | undefined;
const finish = (result: "abort" | Error) => {
if (settled) {
@@ -200,7 +199,7 @@ function waitForFeishuWsCycleEnd(params: {
resolve(result);
};
handleAbort = () => finish("abort");
const handleAbort: (() => void) | undefined = () => finish("abort");
params.abortSignal?.addEventListener("abort", handleAbort, { once: true });
if (params.abortSignal?.aborted) {
finish("abort");
@@ -58,7 +58,6 @@ async function listTarOutputLines<T>(input: {
let outputChars = 0;
let stderr = "";
let settled = false;
let watchdog: ReturnType<typeof setTimeout>;
const finish = (result: { ok: true; values: T[] } | { ok: false; reason: string }): void => {
if (settled) {
@@ -110,7 +109,7 @@ async function listTarOutputLines<T>(input: {
}
};
watchdog = setTimeout(() => {
const watchdog: ReturnType<typeof setTimeout> = setTimeout(() => {
stopChild();
finish({ ok: false, reason: `${input.label} timed out` });
}, 30_000);
@@ -274,7 +273,6 @@ export async function validateTarUncompressedBudget(
let totalBytes = 0;
let stderr = "";
let settled = false;
let watchdog: ReturnType<typeof setTimeout>;
const finish = (result: { ok: true } | { ok: false; reason: string }): void => {
if (settled) {
return;
@@ -283,7 +281,7 @@ export async function validateTarUncompressedBudget(
clearTimeout(watchdog);
resolve(result);
};
watchdog = setTimeout(() => {
const watchdog: ReturnType<typeof setTimeout> = setTimeout(() => {
try {
child.kill("SIGKILL");
} catch {
@@ -388,7 +386,6 @@ async function unpackTar(tarBuffer: Buffer, destDir: string): Promise<void> {
);
let stderrOut = "";
let settled = false;
let watchdog: ReturnType<typeof setTimeout>;
const fail = (error: Error): void => {
if (settled) {
return;
@@ -405,7 +402,7 @@ async function unpackTar(tarBuffer: Buffer, destDir: string): Promise<void> {
clearTimeout(watchdog);
resolve();
};
watchdog = setTimeout(() => {
const watchdog: ReturnType<typeof setTimeout> = setTimeout(() => {
try {
child.kill("SIGKILL");
} catch {
+44 -44
View File
@@ -191,7 +191,6 @@ export async function startNodeAgentAudioBridge(params: {
}),
);
const transcript: GoogleMeetRealtimeTranscriptEntry[] = [];
let agentTalkback: RealtimeVoiceAgentTalkbackQueue | undefined;
let ttsQueue = Promise.resolve();
const stop = async () => {
@@ -281,26 +280,27 @@ export async function startNodeAgentAudioBridge(params: {
});
};
agentTalkback = createRealtimeVoiceAgentTalkbackQueue({
debounceMs: GOOGLE_MEET_AGENT_TRANSCRIPT_DEBOUNCE_MS,
isStopped: () => stopped,
logger: params.logger,
logPrefix: "[google-meet] node agent",
responseStyle: "Brief, natural spoken answer for a live meeting.",
fallbackText: "I hit an error while checking that. Please try again.",
consult: ({ question, responseStyle }) =>
consultOpenClawAgentForGoogleMeet({
config: params.config,
fullConfig: params.fullConfig,
runtime: params.runtime,
logger: params.logger,
meetingSessionId: params.meetingSessionId,
requesterSessionKey: params.requesterSessionKey,
args: { question, responseStyle },
transcript,
}),
deliver: enqueueSpeakText,
});
const agentTalkback: RealtimeVoiceAgentTalkbackQueue | undefined =
createRealtimeVoiceAgentTalkbackQueue({
debounceMs: GOOGLE_MEET_AGENT_TRANSCRIPT_DEBOUNCE_MS,
isStopped: () => stopped,
logger: params.logger,
logPrefix: "[google-meet] node agent",
responseStyle: "Brief, natural spoken answer for a live meeting.",
fallbackText: "I hit an error while checking that. Please try again.",
consult: ({ question, responseStyle }) =>
consultOpenClawAgentForGoogleMeet({
config: params.config,
fullConfig: params.fullConfig,
runtime: params.runtime,
logger: params.logger,
meetingSessionId: params.meetingSessionId,
requesterSessionKey: params.requesterSessionKey,
args: { question, responseStyle },
transcript,
}),
deliver: enqueueSpeakText,
});
sttSession = resolved.provider.createSession({
cfg: params.fullConfig,
@@ -455,29 +455,29 @@ export async function startNodeRealtimeAudioBridge(params: {
audioFormat: params.config.chrome.audioFormat,
}),
);
let agentTalkback: RealtimeVoiceAgentTalkbackQueue | undefined;
agentTalkback = createRealtimeVoiceAgentTalkbackQueue({
debounceMs: GOOGLE_MEET_AGENT_TRANSCRIPT_DEBOUNCE_MS,
isStopped: () => stopped,
logger: params.logger,
logPrefix: "[google-meet] node realtime agent",
responseStyle: "Brief, natural spoken answer for a live meeting.",
fallbackText: "I hit an error while checking that. Please try again.",
consult: ({ question, responseStyle }) =>
consultOpenClawAgentForGoogleMeet({
config: params.config,
fullConfig: params.fullConfig,
runtime: params.runtime,
logger: params.logger,
meetingSessionId: params.meetingSessionId,
requesterSessionKey: params.requesterSessionKey,
args: { question, responseStyle },
transcript,
}),
deliver: (text) => {
bridge?.sendUserMessage(buildGoogleMeetSpeakExactUserMessage(text));
},
});
const agentTalkback: RealtimeVoiceAgentTalkbackQueue | undefined =
createRealtimeVoiceAgentTalkbackQueue({
debounceMs: GOOGLE_MEET_AGENT_TRANSCRIPT_DEBOUNCE_MS,
isStopped: () => stopped,
logger: params.logger,
logPrefix: "[google-meet] node realtime agent",
responseStyle: "Brief, natural spoken answer for a live meeting.",
fallbackText: "I hit an error while checking that. Please try again.",
consult: ({ question, responseStyle }) =>
consultOpenClawAgentForGoogleMeet({
config: params.config,
fullConfig: params.fullConfig,
runtime: params.runtime,
logger: params.logger,
meetingSessionId: params.meetingSessionId,
requesterSessionKey: params.requesterSessionKey,
args: { question, responseStyle },
transcript,
}),
deliver: (text) => {
bridge?.sendUserMessage(buildGoogleMeetSpeakExactUserMessage(text));
},
});
const stop = async () => {
if (stopped) {
+45 -45
View File
@@ -279,7 +279,7 @@ function alawByteToLinear(value: number): number {
const sign = aLaw & 0x80;
const exponent = (aLaw & 0x70) >> 4;
const mantissa = aLaw & 0x0f;
let sample = exponent === 0 ? (mantissa << 4) + 8 : ((mantissa << 4) + 0x108) << (exponent - 1);
const sample = exponent === 0 ? (mantissa << 4) + 8 : ((mantissa << 4) + 0x108) << (exponent - 1);
return sign ? sample : -sample;
}
@@ -502,7 +502,6 @@ export async function startCommandAgentAudioBridge(params: {
let lastSuppressedInputAt: string | undefined;
let suppressInputUntil = 0;
let lastOutputPlayableUntilMs = 0;
let agentTalkback: RealtimeVoiceAgentTalkbackQueue | undefined;
let ttsQueue = Promise.resolve();
const transcript: GoogleMeetRealtimeTranscriptEntry[] = [];
const resolved = resolveGoogleMeetRealtimeTranscriptionProvider({
@@ -702,26 +701,27 @@ export async function startCommandAgentAudioBridge(params: {
});
};
agentTalkback = createRealtimeVoiceAgentTalkbackQueue({
debounceMs: GOOGLE_MEET_AGENT_TRANSCRIPT_DEBOUNCE_MS,
isStopped: () => stopped,
logger: params.logger,
logPrefix: "[google-meet] agent",
responseStyle: "Brief, natural spoken answer for a live meeting.",
fallbackText: "I hit an error while checking that. Please try again.",
consult: ({ question, responseStyle }) =>
consultOpenClawAgentForGoogleMeet({
config: params.config,
fullConfig: params.fullConfig,
runtime: params.runtime,
logger: params.logger,
meetingSessionId: params.meetingSessionId,
requesterSessionKey: params.requesterSessionKey,
args: { question, responseStyle },
transcript,
}),
deliver: enqueueSpeakText,
});
const agentTalkback: RealtimeVoiceAgentTalkbackQueue | undefined =
createRealtimeVoiceAgentTalkbackQueue({
debounceMs: GOOGLE_MEET_AGENT_TRANSCRIPT_DEBOUNCE_MS,
isStopped: () => stopped,
logger: params.logger,
logPrefix: "[google-meet] agent",
responseStyle: "Brief, natural spoken answer for a live meeting.",
fallbackText: "I hit an error while checking that. Please try again.",
consult: ({ question, responseStyle }) =>
consultOpenClawAgentForGoogleMeet({
config: params.config,
fullConfig: params.fullConfig,
runtime: params.runtime,
logger: params.logger,
meetingSessionId: params.meetingSessionId,
requesterSessionKey: params.requesterSessionKey,
args: { question, responseStyle },
transcript,
}),
deliver: enqueueSpeakText,
});
sttSession = resolved.provider.createSession({
cfg: params.fullConfig,
@@ -861,7 +861,6 @@ export async function startCommandRealtimeAudioBridge(params: {
let suppressInputUntil = 0;
let lastOutputPlayableUntilMs = 0;
let bargeInInputProcess: BridgeProcess | undefined;
let agentTalkback: RealtimeVoiceAgentTalkbackQueue | undefined;
const suppressInputForOutput = (audio: Buffer) => {
const suppression = recordGoogleMeetOutputActivity({
@@ -1113,28 +1112,29 @@ export async function startCommandRealtimeAudioBridge(params: {
type: "session.started",
payload: { meetingSessionId: params.meetingSessionId },
});
agentTalkback = createRealtimeVoiceAgentTalkbackQueue({
debounceMs: GOOGLE_MEET_AGENT_TRANSCRIPT_DEBOUNCE_MS,
isStopped: () => stopped,
logger: params.logger,
logPrefix: "[google-meet] realtime agent",
responseStyle: "Brief, natural spoken answer for a live meeting.",
fallbackText: "I hit an error while checking that. Please try again.",
consult: ({ question, responseStyle }) =>
consultOpenClawAgentForGoogleMeet({
config: params.config,
fullConfig: params.fullConfig,
runtime: params.runtime,
logger: params.logger,
meetingSessionId: params.meetingSessionId,
requesterSessionKey: params.requesterSessionKey,
args: { question, responseStyle },
transcript,
}),
deliver: (text) => {
bridge?.sendUserMessage(buildGoogleMeetSpeakExactUserMessage(text));
},
});
const agentTalkback: RealtimeVoiceAgentTalkbackQueue | undefined =
createRealtimeVoiceAgentTalkbackQueue({
debounceMs: GOOGLE_MEET_AGENT_TRANSCRIPT_DEBOUNCE_MS,
isStopped: () => stopped,
logger: params.logger,
logPrefix: "[google-meet] realtime agent",
responseStyle: "Brief, natural spoken answer for a live meeting.",
fallbackText: "I hit an error while checking that. Please try again.",
consult: ({ question, responseStyle }) =>
consultOpenClawAgentForGoogleMeet({
config: params.config,
fullConfig: params.fullConfig,
runtime: params.runtime,
logger: params.logger,
meetingSessionId: params.meetingSessionId,
requesterSessionKey: params.requesterSessionKey,
args: { question, responseStyle },
transcript,
}),
deliver: (text) => {
bridge?.sendUserMessage(buildGoogleMeetSpeakExactUserMessage(text));
},
});
bridge = createRealtimeVoiceBridgeSession({
provider: resolved.provider,
cfg: params.fullConfig,
+1 -1
View File
@@ -101,7 +101,7 @@ export const googlechatMessageActions: ChannelMessageActionAdapter = {
mediaReadFile,
}) => {
const account = resolveGoogleChatAccount({
cfg: cfg,
cfg,
accountId,
});
if (account.credentialSource === "none") {
@@ -144,7 +144,7 @@ export const googlechatPairingTextAdapter = {
message: string;
accountId?: string | null;
}) => {
const account = resolveGoogleChatAccount({ cfg: cfg, accountId });
const account = resolveGoogleChatAccount({ cfg, accountId });
if (account.credentialSource === "none") {
return;
}
@@ -205,7 +205,7 @@ export const googlechatOutboundAdapter = {
threadId?: string | number | null;
}) => {
const account = resolveGoogleChatAccount({
cfg: cfg,
cfg,
accountId,
});
const space = await resolveGoogleChatOutboundSpace({ account, target: to });
@@ -252,14 +252,14 @@ export const googlechatOutboundAdapter = {
throw new Error("Google Chat mediaUrl is required.");
}
const account = resolveGoogleChatAccount({
cfg: cfg,
cfg,
accountId,
});
const space = await resolveGoogleChatOutboundSpace({ account, target: to });
const thread =
typeof threadId === "number" ? String(threadId) : (threadId ?? replyToId ?? undefined);
const maxBytes = resolveChannelMediaMaxBytes({
cfg: cfg,
cfg,
resolveChannelLimitMb: ({ cfg, accountId }) =>
(
cfg.channels?.googlechat as
@@ -4,7 +4,7 @@ import { attachIMessageMonitorAbortHandler } from "./monitor/abort-handler.js";
describe("monitorIMessageProvider", () => {
it("does not trigger unhandledRejection when aborting during shutdown", async () => {
const abortController = new AbortController();
let subscriptionId: number | null = 1;
const subscriptionId: number | null = 1;
const requestMock = vi.fn((method: string, _params?: Record<string, unknown>) => {
if (method === "watch.unsubscribe") {
return Promise.reject(new Error("imsg rpc closed"));
+2 -2
View File
@@ -909,9 +909,9 @@ export async function sendMessageIMessage(
(opts.createClient
? await opts.createClient({ cliPath, dbPath })
: await createIMessageRpcClient({ cliPath, dbPath }));
let shouldClose = !opts.client;
const shouldClose = !opts.client;
let result: Record<string, unknown>;
let sendStartedAtMs = Date.now();
const sendStartedAtMs = Date.now();
try {
try {
result = await client.request<Record<string, unknown>>("send", params, {
+2 -1
View File
@@ -123,11 +123,12 @@ function parseReceiptItems(itemsStr: string): Array<{ name: string; value: strin
* Parse quoted arguments from command string
* Supports: /card type "arg1" "arg2" "arg3" --flag value
*/
function parseCardArgs(argsStr: string): {
function parseCardArgs(argsStrInput: string): {
type: string;
args: string[];
flags: Record<string, string>;
} {
let argsStr = argsStrInput;
const result: { type: string; args: string[]; flags: Record<string, string> } = {
type: "",
args: [],
+6 -1
View File
@@ -128,7 +128,12 @@ function isMentionStartBoundary(charBefore: string | undefined): boolean {
return !charBefore || !/[A-Za-z0-9_]/.test(charBefore);
}
function trimMentionSuffix(raw: string, end: number): { raw: string; end: number } | null {
function trimMentionSuffix(
rawInput: string,
endInput: number,
): { raw: string; end: number } | null {
let raw = rawInput;
let end = endInput;
while (raw.length > 1 && TRIMMABLE_MENTION_SUFFIX.test(raw.at(-1) ?? "")) {
if (raw.at(-1) === "]" && /\[[0-9A-Fa-f:.]+\](?::\d+)?$/i.test(raw)) {
break;
@@ -1623,10 +1623,10 @@ describe("registerMatrixMonitorEvents verification routing", () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-04-10T16:21:00.000Z"));
try {
let healthySyncSinceMs: number | undefined;
const healthySync = { sinceMs: undefined as number | undefined };
const { logger, failedDecryptListener } = createHarness({
accountId: "ops",
getHealthySyncSinceMs: () => healthySyncSinceMs,
getHealthySyncSinceMs: () => healthySync.sinceMs,
});
if (!failedDecryptListener) {
throw new Error("room.failed_decryption listener was not registered");
@@ -1650,7 +1650,7 @@ describe("registerMatrixMonitorEvents verification routing", () => {
freshAfterHealthySync: false,
});
healthySyncSinceMs = Date.now();
healthySync.sinceMs = Date.now();
await failedDecryptListener(
"!room:example.org",
@@ -637,7 +637,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
}
}
let content = event.content as RoomMessageEventContent;
const content = event.content as RoomMessageEventContent;
if (
eventType === EventType.RoomMessage &&
@@ -7,8 +7,7 @@ export function createMatrixMonitorTaskRunner(params: {
const inFlight = new Set<Promise<void>>();
const runDetachedTask = (label: string, task: () => Promise<void>): Promise<void> => {
let trackedTask!: Promise<void>;
trackedTask = Promise.resolve()
const trackedTask: Promise<void> = Promise.resolve()
.then(task)
.catch((error) => {
const message = String(error);
@@ -909,7 +909,7 @@ async function collectSessionIngestionBatches(params: {
const sessionScope = buildSessionScopeKey(file.agentId, file.absolutePath);
const previousSeen = nextSeenMessages[sessionScope] ?? [];
let seenSet = new Set(previousSeen);
const seenSet = new Set(previousSeen);
const newSeenHashes: string[] = [];
const lines = entry.content.length > 0 ? entry.content.split("\n") : [];
@@ -224,7 +224,6 @@ export async function getMemorySearchManager(params: {
if (!primary) {
return { entry: null, failureReason };
}
let cacheEntry!: CachedQmdManagerEntry;
const wrapper = new FallbackMemoryManager(
{
primary,
@@ -240,7 +239,7 @@ export async function getMemorySearchManager(params: {
}
},
);
cacheEntry = {
const cacheEntry: CachedQmdManagerEntry = {
identityKey: expectedIdentityKey,
manager: wrapper,
};
@@ -77,11 +77,9 @@ function createActivityHandler() {
await handler(context, async () => {});
}
});
let handler: MSTeamsActivityHandler & {
const handler: MSTeamsActivityHandler & {
run: NonNullable<MSTeamsActivityHandler["run"]>;
};
handler = {
} = {
onMessage: (nextHandler) => {
messageHandlers.push(nextHandler);
return handler;
@@ -137,10 +137,9 @@ export function createActivityHandler(
): MSTeamsActivityHandler & {
run: NonNullable<MSTeamsActivityHandler["run"]>;
} {
let handler: MSTeamsActivityHandler & {
const handler: MSTeamsActivityHandler & {
run: NonNullable<MSTeamsActivityHandler["run"]>;
};
handler = {
} = {
onMessage: () => handler,
onMembersAdded: () => handler,
onReactionsAdded: () => handler,
+1 -1
View File
@@ -7,7 +7,7 @@ import {
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
export function resolveMSTeamsOutboundSessionRoute(params: ChannelOutboundSessionRouteParams) {
let trimmed = stripChannelTargetPrefix(params.target, "msteams", "teams");
const trimmed = stripChannelTargetPrefix(params.target, "msteams", "teams");
if (!trimmed) {
return null;
}
@@ -1231,8 +1231,7 @@ describe("buildOpenAIRealtimeVoiceProvider", () => {
const provider = buildOpenAIRealtimeVoiceProvider();
const onAudio = vi.fn();
const onClearAudio = vi.fn();
let bridge: ReturnType<typeof provider.createBridge>;
bridge = provider.createBridge({
const bridge: ReturnType<typeof provider.createBridge> = provider.createBridge({
providerConfig: { apiKey: "sk-test" }, // pragma: allowlist secret
onAudio,
onClearAudio,
+1 -2
View File
@@ -538,7 +538,6 @@ class OpenAIRealtimeVoiceBridge implements RealtimeVoiceBridge {
private async doConnect(): Promise<void> {
await new Promise<void>((resolve, reject) => {
let connectTimeout: ReturnType<typeof setTimeout>;
let settled = false;
const settleResolve = () => {
if (settled) {
@@ -556,7 +555,7 @@ class OpenAIRealtimeVoiceBridge implements RealtimeVoiceBridge {
clearTimeout(connectTimeout);
reject(error);
};
connectTimeout = setTimeout(() => {
const connectTimeout: ReturnType<typeof setTimeout> = setTimeout(() => {
if (!this.sessionConfigured && !this.intentionallyClosed) {
this.ws?.terminate();
settleReject(new Error("OpenAI realtime connection timeout"));
@@ -249,7 +249,7 @@ type VoiceResult =
async function processVoiceAttachment(
localPath: string,
audioPath: string | null,
audioPathInput: string | null,
att: RawAttachment,
asrReferText: string,
cfg: unknown,
@@ -257,6 +257,7 @@ async function processVoiceAttachment(
audioConvert: AudioConvertPort,
log: ProcessContext["log"],
): Promise<VoiceResult> {
let audioPath = audioPathInput;
const wavUrl = att.voice_wav_url
? att.voice_wav_url.startsWith("//")
? `https:${att.voice_wav_url}`
@@ -90,7 +90,7 @@ function fixPathEncoding(
log?.debug?.(`Decoding path with mixed encoding: ${result}`);
// Step 1: 将八进制转义转换为字节
let decoded = result.replace(/\\([0-7]{1,3})/g, (_: string, octal: string) =>
const decoded = result.replace(/\\([0-7]{1,3})/g, (_: string, octal: string) =>
String.fromCharCode(Number.parseInt(octal, 8)),
);
@@ -32,7 +32,7 @@ interface ParsedTarget {
* @throws {Error} When the target format is invalid.
*/
export function parseTarget(to: string): ParsedTarget {
let id = to.replace(/^qqbot:/i, "");
const id = to.replace(/^qqbot:/i, "");
if (id.startsWith("c2c:")) {
const userId = id.slice(4);
+2 -4
View File
@@ -125,7 +125,6 @@ function requestSignalHttpText(
const client = url.protocol === "https:" ? https : http;
return new Promise((resolve, reject) => {
let settled = false;
let request: ClientRequest | undefined;
const deadline = setTimeout(() => {
request?.destroy(new Error(`Signal HTTP exceeded deadline after ${timeoutMs}ms`));
}, timeoutMs);
@@ -151,7 +150,7 @@ function requestSignalHttpText(
resolve(response);
};
const maxResponseBytes = normalizeSignalHttpResponseMaxBytes(options.maxResponseBytes);
request = client.request(
const request: ClientRequest | undefined = client.request(
url,
{
method: options.method,
@@ -267,7 +266,6 @@ function openSignalEventStream(
let settled = false;
let response: IncomingMessage | undefined;
let onAbort: () => void = () => {};
let request: ClientRequest;
const effectiveTimeoutMs = normalizeSignalSseTimeoutMs(timeoutMs);
const headerDeadline =
effectiveTimeoutMs === null
@@ -295,7 +293,7 @@ function openSignalEventStream(
cleanup();
reject(error);
};
request = client.request(
const request: ClientRequest = client.request(
url,
{
method: "GET",
@@ -122,9 +122,15 @@ export async function resolveSlackThreadContextData(params: {
let threadStarterBody: string | undefined;
let threadHistoryBody: string | undefined;
let threadSessionPreviousTimestamp: number | undefined;
let threadLabel: string | undefined;
let threadStarterMedia: SlackMediaResult[] | null = null;
const threadSessionPreviousTimestamp =
params.isThreadReply && params.threadTs
? readSessionUpdatedAt({
storePath: params.storePath,
sessionKey: params.sessionKey,
})
: undefined;
if (!params.isThreadReply || !params.threadTs) {
return {
@@ -188,10 +194,6 @@ export async function resolveSlackThreadContextData(params: {
threadLabel = `Slack thread ${params.roomLabel}`;
}
threadSessionPreviousTimestamp = readSessionUpdatedAt({
storePath: params.storePath,
sessionKey: params.sessionKey,
});
const isNewThreadSession = !threadSessionPreviousTimestamp;
const includeBotStarterAsRootContext = shouldIncludeBotThreadStarterContext({
starterIsCurrentBot,
+1 -1
View File
@@ -153,7 +153,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
const cfg = opts.config ?? getRuntimeConfig();
const runtime: RuntimeEnv = opts.runtime ?? createNonExitingRuntime();
let account = resolveSlackAccount({
const account = resolveSlackAccount({
cfg,
accountId: opts.accountId,
});
+1 -1
View File
@@ -312,7 +312,7 @@ function createArgMenusHarness() {
action: (id: string | RegExp, handler: (args: unknown) => Promise<void>) => {
actions.set(id, handler);
},
options: function (this: unknown, id: string, handler: (args: unknown) => Promise<void>) {
options(this: unknown, id: string, handler: (args: unknown) => Promise<void>) {
optionsReceiverContexts.push(this);
options.set(id, handler);
},
@@ -6,8 +6,7 @@ vi.mock("./send.js", () => ({
sendMessageSlack: (...args: unknown[]) => sendMessageSlackMock(...args),
}));
let slackOutbound: typeof import("./outbound-adapter.js").slackOutbound;
({ slackOutbound } = await import("./outbound-adapter.js"));
const { slackOutbound } = await import("./outbound-adapter.js");
describe("slackOutbound", () => {
const cfg = {
+2 -5
View File
@@ -54,11 +54,8 @@ vi.mock("./runtime-api.js", async () => {
};
});
let sendMessageSlack: typeof import("./send.js").sendMessageSlack;
let clearSlackDmChannelCache: typeof import("./send.js").clearSlackDmChannelCache;
let clearSlackSendQueuesForTest: typeof import("./send.js").clearSlackSendQueuesForTest;
({ sendMessageSlack, clearSlackDmChannelCache, clearSlackSendQueuesForTest } =
await import("./send.js"));
const { sendMessageSlack, clearSlackDmChannelCache, clearSlackSendQueuesForTest } =
await import("./send.js");
const SLACK_TEST_CFG = { channels: { slack: { botToken: "xoxb-test" } } };
type UploadTestClient = WebClient & {
+2 -1
View File
@@ -3,7 +3,8 @@ import { describe, expect, it, vi } from "vitest";
import type { PluginRuntime } from "./api.js";
import register from "./index.js";
function createHarness(config: Record<string, unknown>) {
function createHarness(initialConfig: Record<string, unknown>) {
let config = initialConfig;
let command: OpenClawPluginCommandDefinition | undefined;
const runtime = {
config: {
@@ -242,7 +242,7 @@ export const buildTelegramMessageContext = async ({
const freshCfg =
loadFreshConfig?.() ??
(runtime?.getRuntimeConfig ?? (await loadTelegramMessageContextRuntime()).getRuntimeConfig)();
let { route, bindingMode } = resolveTelegramConversationRoute({
const conversationRoute = resolveTelegramConversationRoute({
cfg: freshCfg,
accountId: account.accountId,
chatId,
@@ -252,6 +252,8 @@ export const buildTelegramMessageContext = async ({
senderId,
topicAgentId: topicConfig?.agentId,
});
const { bindingMode } = conversationRoute;
let { route } = conversationRoute;
const requiresExplicitAccountBinding = (
candidate: ReturnType<typeof resolveTelegramConversationRoute>["route"],
): boolean =>
@@ -426,7 +428,7 @@ export const buildTelegramMessageContext = async ({
const activationOverride = resolveGroupActivation({
chatId,
messageThreadId: resolvedThreadId,
sessionKey: sessionKey,
sessionKey,
agentId: route.agentId,
});
const baseRequireMention = resolveGroupRequireMention(chatId);
@@ -1546,7 +1546,7 @@ export const dispatchTelegramMessage = async ({
draftMaxChars,
applyTextToPayload,
applyTextToFollowUpPayload,
splitFinalTextForStream: splitFinalTextForStream,
splitFinalTextForStream,
sendPayload,
flushDraftLane,
stopDraftLane: async (lane) => {
@@ -741,7 +741,7 @@ export const registerTelegramNativeCommands = ({
"nativeSkillsEnabled is true but no agent route is bound for this Telegram account; skill commands will not appear in the native menu.",
);
}
let skillCommands =
const skillCommands =
nativeEnabled && nativeSkillsEnabled && boundRoute
? telegramDeps.listSkillCommandsForAgents({
cfg,
@@ -920,7 +920,7 @@ export const registerTelegramNativeCommands = ({
isForum,
messageThreadId: resolvedThreadId ?? messageThreadId,
});
let { route, bindingMode } = resolveTelegramConversationRoute({
const { route, bindingMode } = resolveTelegramConversationRoute({
cfg: runtimeCfg,
accountId,
chatId,
+5 -6
View File
@@ -117,11 +117,6 @@ export function createTelegramDraftStream(params: {
let previewRevision = 0;
let generation = 0;
let deliveredTextOffset = 0;
let resetStreamToNewMessage: (options?: {
keepFinal?: boolean;
keepPending?: boolean;
resetOffset?: boolean;
}) => void;
type PreviewSendParams = {
renderedText: string;
renderedParseMode: "HTML" | undefined;
@@ -316,7 +311,11 @@ export function createTelegramDraftStream(params: {
streamState.final = true;
};
resetStreamToNewMessage = (options) => {
const resetStreamToNewMessage: (options?: {
keepFinal?: boolean;
keepPending?: boolean;
resetOffset?: boolean;
}) => void = (options) => {
streamState.stopped = false;
streamState.final = options?.keepFinal === true;
generation += 1;
@@ -235,9 +235,8 @@ describe("createLaneTextDeliverer", () => {
"Ja. Hier nochmal sauber Schritt fuer Schritt. Einen API Key kopiert man aus der Google Cloud Console. Danach pruefst du die Projekt- und API-Einstellungen.";
const truncatedFinal =
"Ja. Hier nochmal sauber Schritt fuer Schritt. Einen API Key kopiert man...";
let answer: ReturnType<typeof createTestDraftStream>;
let deliveredText = "";
answer = createTestDraftStream({
const answer: ReturnType<typeof createTestDraftStream> = createTestDraftStream({
onStop: () => {
answer.setMessageId(999);
deliveredText = fullAnswer;
+1 -1
View File
@@ -729,7 +729,7 @@ export function createTelegramThreadBindingManager(params: {
if (placement === "child") {
const rawConversationId = input.conversation.conversationId?.trim() ?? "";
const rawParent = input.conversation.parentConversationId?.trim() ?? "";
let chatId = rawParent || rawConversationId;
const chatId = rawParent || rawConversationId;
if (!chatId) {
logVerbose(
`telegram: child bind failed: could not resolve group chat ID from conversationId=${rawConversationId}`,
+6 -6
View File
@@ -371,7 +371,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
const parsed = parseChannelNest(groupChannel);
if (parsed) {
await sendGroupMessage({
api: api,
api,
fromShip: botShipName,
hostShip: parsed.hostShip,
channelName: parsed.channelName,
@@ -380,7 +380,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
}
} else {
await sendDm({
api: api,
api,
fromShip: botShipName,
toShip: senderShip,
text: noHistoryMsg,
@@ -408,7 +408,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
const parsed = parseChannelNest(groupChannel);
if (parsed) {
await sendGroupMessage({
api: api,
api,
fromShip: botShipName,
hostShip: parsed.hostShip,
channelName: parsed.channelName,
@@ -416,7 +416,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
});
}
} else {
await sendDm({ api: api, fromShip: botShipName, toShip: senderShip, text: errorMsg });
await sendDm({ api, fromShip: botShipName, toShip: senderShip, text: errorMsg });
}
return;
}
@@ -627,7 +627,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
return { visibleReplySent: false };
}
await sendGroupMessage({
api: api,
api,
fromShip: botShipName,
hostShip: parsed.hostShip,
channelName: parsed.channelName,
@@ -638,7 +638,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
}
await sendDm({
api: api,
api,
fromShip: botShipName,
toShip: senderShip,
text: replyText,
+1 -1
View File
@@ -299,7 +299,7 @@ type SettingsLogger = {
* settings.subscribe((newSettings) => { ... });
*/
export function createSettingsManager(api: UrbitSSEClient, logger?: SettingsLogger) {
let state: TlonSettingsState = {
const state: TlonSettingsState = {
current: {},
loaded: false,
};
+16 -15
View File
@@ -50,9 +50,6 @@ export async function probeTwitch(
// Create a promise that resolves when connected
const connectionPromise = new Promise<void>((resolve, reject) => {
let settled = false;
let connectListener: ReturnType<ChatClient["onConnect"]> | undefined;
let disconnectListener: ReturnType<ChatClient["onDisconnect"]> | undefined;
let authFailListener: ReturnType<ChatClient["onAuthenticationFailure"]> | undefined;
const cleanup = () => {
if (settled) {
@@ -65,22 +62,26 @@ export async function probeTwitch(
};
// Success: connection established
connectListener = client?.onConnect(() => {
cleanup();
resolve();
});
const connectListener: ReturnType<ChatClient["onConnect"]> | undefined = client?.onConnect(
() => {
cleanup();
resolve();
},
);
// Failure: disconnected (e.g., auth failed)
disconnectListener = client?.onDisconnect((_manually, reason) => {
cleanup();
reject(reason || new Error("Disconnected"));
});
const disconnectListener: ReturnType<ChatClient["onDisconnect"]> | undefined =
client?.onDisconnect((_manually, reason) => {
cleanup();
reject(reason || new Error("Disconnected"));
});
// Failure: authentication failed
authFailListener = client?.onAuthenticationFailure(() => {
cleanup();
reject(new Error("Authentication failed"));
});
const authFailListener: ReturnType<ChatClient["onAuthenticationFailure"]> | undefined =
client?.onAuthenticationFailure(() => {
cleanup();
reject(new Error("Authentication failed"));
});
});
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
+1 -2
View File
@@ -200,7 +200,6 @@ export class TwitchClientManager {
let settled = false;
let authRetryPending = false;
const listeners: Array<{ unbind: () => void }> = [];
let timeout: NodeJS.Timeout | undefined;
const finish = (error?: Error) => {
if (settled) {
return;
@@ -248,7 +247,7 @@ export class TwitchClientManager {
);
}),
);
timeout = setTimeout(
const timeout: NodeJS.Timeout | undefined = setTimeout(
() => finish(new Error(`Timed out connecting to Twitch as ${account.username}`)),
connectTimeoutMs,
);
@@ -41,7 +41,6 @@ function runTailscaleCommand(
let stdout: TailscaleCommandStdout = { bytes: 0, exceeded: false, text: "" };
let settled = false;
let timer: ReturnType<typeof setTimeout>;
const finish = (result: { code: number; stdout: string }) => {
if (settled) {
return;
@@ -59,7 +58,7 @@ function runTailscaleCommand(
}
});
timer = setTimeout(() => {
const timer: ReturnType<typeof setTimeout> = setTimeout(() => {
proc.kill("SIGKILL");
finish({ code: -1, stdout: "" });
}, timeoutMs);
+2 -1
View File
@@ -70,9 +70,10 @@ export function createWebSendApi(params: {
to: string,
text: string,
mediaBuffer?: Buffer,
mediaType?: string,
mediaTypeInput?: string,
sendOptions?: ActiveWebSendOptions,
): Promise<WhatsAppSendResult> => {
let mediaType = mediaTypeInput;
const jid = resolveOutboundJid(to);
let payload: AnyMessageContent;
if (mediaBuffer) {
+1 -2
View File
@@ -1633,7 +1633,6 @@ describe("WorkboardStore", () => {
});
it("does not drop concurrent updates while refreshing diagnostics", async () => {
let store!: WorkboardStore;
let proofPromise: Promise<unknown> | undefined;
let triggered = false;
const keyed = createMemoryStore({
@@ -1646,7 +1645,7 @@ describe("WorkboardStore", () => {
await new Promise((resolve) => setTimeout(resolve, 0));
},
});
store = new WorkboardStore(keyed);
const store: WorkboardStore = new WorkboardStore(keyed);
const card = await store.create({ title: "Ready too long", agentId: "main" });
await store.refreshDiagnostics(Date.now() + 2 * 24 * 60 * 60 * 1000);
+1 -1
View File
@@ -44,7 +44,7 @@ export const zaloMessageActions: ChannelMessageActionAdapter = {
const result = await sendMessageZalo(to ?? "", content ?? "", {
accountId: accountId ?? undefined,
mediaUrl: mediaUrl ?? undefined,
cfg: cfg,
cfg,
});
if (!result.ok) {
+2 -2
View File
@@ -60,7 +60,7 @@ export const zaloDmPolicy: ChannelSetupDmPolicy = {
},
getCurrent: (cfg, accountId) =>
resolveZaloAccount({
cfg: cfg,
cfg,
accountId: accountId ?? resolveDefaultZaloAccountId(cfg),
}).config.dmPolicy ?? "pairing",
setPolicy: (cfg, policy, accountId) => {
@@ -69,7 +69,7 @@ export const zaloDmPolicy: ChannelSetupDmPolicy = {
? (normalizeAccountId(accountId) ?? DEFAULT_ACCOUNT_ID)
: resolveDefaultZaloAccountId(cfg);
const resolved = resolveZaloAccount({
cfg: cfg,
cfg,
accountId: resolvedAccountId,
});
if (resolvedAccountId === DEFAULT_ACCOUNT_ID) {
+5 -5
View File
@@ -108,7 +108,7 @@ function resolveZalouserRequireMention(params: ChannelGroupContext): boolean {
async function sendZalouserTextFromContext({ to, text, accountId, cfg }: ZalouserSendTextContext) {
const { sendMessageZalouser } = await loadZalouserChannelRuntime();
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
const account = resolveZalouserAccountSync({ cfg, accountId });
const target = parseZalouserOutboundTarget(to);
return await sendMessageZalouser(target.threadId, text, {
profile: account.profile,
@@ -129,7 +129,7 @@ async function sendZalouserMediaFromContext({
mediaReadFile,
}: ZalouserSendMediaContext) {
const { sendMessageZalouser } = await loadZalouserChannelRuntime();
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
const account = resolveZalouserAccountSync({ cfg, accountId });
const target = parseZalouserOutboundTarget(to);
return await sendMessageZalouser(target.threadId, text, {
profile: account.profile,
@@ -279,7 +279,7 @@ export const zalouserResolverAdapter = {
try {
const runtimeModule = await loadZalouserChannelRuntime();
const account = resolveZalouserAccountSync({
cfg: cfg,
cfg,
accountId: accountId ?? resolveDefaultZalouserAccountId(cfg),
});
if (kind === "user") {
@@ -329,7 +329,7 @@ export const zalouserAuthAdapter = {
}) => {
const { startZaloQrLogin, waitForZaloQrLogin } = await loadZalouserChannelRuntime();
const account = resolveZalouserAccountSync({
cfg: cfg,
cfg,
accountId: accountId ?? resolveDefaultZalouserAccountId(cfg),
});
@@ -381,7 +381,7 @@ export const zalouserPairingTextAdapter = {
normalizeAllowEntry: createPairingPrefixStripper(/^(zalouser|zlu):/i),
notify: async ({ cfg, id, message }: { cfg: OpenClawConfig; id: string; message: string }) => {
const { sendMessageZalouser } = await loadZalouserChannelRuntime();
const account = resolveZalouserAccountSync({ cfg: cfg });
const account = resolveZalouserAccountSync({ cfg });
const authenticated = await checkZcaAuthenticated(account.profile);
if (!authenticated) {
throw new Error("Zalouser not authenticated");
+3 -3
View File
@@ -78,7 +78,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount, ZalouserProb
directory: {
self: async ({ cfg, accountId }) => {
const { getZaloUserInfo } = await loadZalouserChannelRuntime();
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
const account = resolveZalouserAccountSync({ cfg, accountId });
const parsed = await getZaloUserInfo(account.profile);
if (!parsed?.userId) {
return null;
@@ -92,7 +92,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount, ZalouserProb
},
listPeers: async ({ cfg, accountId, query, limit }) => {
const { listZaloFriendsMatching } = await loadZalouserChannelRuntime();
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
const account = resolveZalouserAccountSync({ cfg, accountId });
const friends = await listZaloFriendsMatching(account.profile, query);
const rows = friends.map((friend) =>
mapUser({
@@ -106,7 +106,7 @@ export const zalouserPlugin: ChannelPlugin<ResolvedZalouserAccount, ZalouserProb
},
listGroups: async ({ cfg, accountId, query, limit }) => {
const { listZaloGroupsMatching } = await loadZalouserChannelRuntime();
const account = resolveZalouserAccountSync({ cfg: cfg, accountId });
const account = resolveZalouserAccountSync({ cfg, accountId });
const groups = await listZaloGroupsMatching(account.profile, query);
const rows = groups.map((group) =>
mapGroup({
+2 -1
View File
@@ -816,7 +816,8 @@ async function deliverZalouserReply(params: {
export async function monitorZalouserProvider(
options: ZalouserMonitorOptions,
): Promise<ZalouserMonitorResult> {
let { account, config } = options;
const { config } = options;
let { account } = options;
const { abortSignal, statusSink, runtime } = options;
const core = getZalouserRuntime();
+2 -2
View File
@@ -236,7 +236,7 @@ const zalouserDmPolicy: ChannelSetupDmPolicy = {
? (normalizeAccountId(accountId) ?? DEFAULT_ACCOUNT_ID)
: resolveDefaultZalouserAccountId(cfg);
return await promptZalouserAllowFrom({
cfg: cfg,
cfg,
prompter,
accountId: id,
});
@@ -438,7 +438,7 @@ export const zalouserSetupWizard: ChannelSetupWizard = {
);
return [];
}
const updatedAccount = resolveZalouserAccountSync({ cfg: cfg, accountId });
const updatedAccount = resolveZalouserAccountSync({ cfg, accountId });
try {
const resolved = await resolveZaloGroupsByEntries({
profile: updatedAccount.profile,
+1 -1
View File
@@ -133,7 +133,7 @@ export function parseZalouserTextStyles(input: string): { text: string; styles:
continue;
}
let line = unquotedLine;
const line = unquotedLine;
const openingFence = resolveOpeningFence(rawLine);
if (openingFence) {
const fenceLine = openingFence.quoteIndent > 0 ? unquotedLine : rawLine;
+4 -4
View File
@@ -288,7 +288,7 @@ export class NodeExecutionEnv implements ExecutionEnv {
let timedOut = false;
let callbackError: ExecutionError | undefined;
let child: ReturnType<typeof spawn> | undefined;
let timeoutId: ReturnType<typeof setTimeout> | undefined;
const timeoutRef: { current?: ReturnType<typeof setTimeout> } = {};
const onAbort = () => {
if (child?.pid) {
@@ -299,8 +299,8 @@ export class NodeExecutionEnv implements ExecutionEnv {
const settle = (
result: Result<{ stdout: string; stderr: string; exitCode: number }, ExecutionError>,
) => {
if (timeoutId) {
clearTimeout(timeoutId);
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
if (options?.abortSignal) {
options.abortSignal.removeEventListener("abort", onAbort);
@@ -327,7 +327,7 @@ export class NodeExecutionEnv implements ExecutionEnv {
}
const timeoutMs = resolveExecTimeoutMs(options?.timeout);
timeoutId =
timeoutRef.current =
timeoutMs === undefined
? undefined
: setTimeout(() => {
+1 -2
View File
@@ -1470,7 +1470,6 @@ export class GatewayClient {
: this.requestTimeoutMs;
const signal = opts?.signal;
const p = new Promise<T>((resolve, reject) => {
let abortHandler: (() => void) | undefined;
const timeout =
timeoutMs === null
? null
@@ -1488,7 +1487,7 @@ export class GatewayClient {
signal.removeEventListener("abort", abortHandler);
}
};
abortHandler = () => {
const abortHandler: (() => void) | undefined = () => {
const pending = this.pending.get(id);
this.pending.delete(id);
pending?.cleanup?.();
@@ -159,7 +159,7 @@ function isPathInsideRoot(candidatePath: string, rootPath: string): boolean {
}
function ensureUniqueName(base: string, existing: Set<string>): string {
let name = sanitizeName(base);
const name = sanitizeName(base);
if (!existing.has(name)) {
existing.add(name);
return name;
+4 -2
View File
@@ -598,9 +598,10 @@ function getResolvedSpeechProviderConfigForVoiceModel(params: {
}
export function resolveTtsConfig(
cfg: OpenClawConfig,
cfgInput: OpenClawConfig,
contextOrAgentId?: string | TtsConfigResolutionContext,
): ResolvedTtsConfig {
let cfg = cfgInput;
cfg = resolveTtsRuntimeConfig(cfg);
const raw: TtsConfig = resolveEffectiveTtsConfig(cfg, contextOrAgentId);
const providerSource = raw.provider ? "config" : "default";
@@ -691,9 +692,10 @@ function resolveEffectiveTtsAutoState(params: {
}
export function buildTtsSystemPromptHint(
cfg: OpenClawConfig,
cfgInput: OpenClawConfig,
agentId?: string,
): string | undefined {
let cfg = cfgInput;
cfg = resolveTtsRuntimeConfig(cfg);
const { autoMode, prefsPath } = resolveEffectiveTtsAutoState({ cfg, agentId });
if (autoMode === "off") {
+16 -8
View File
@@ -428,7 +428,8 @@ function crabboxOptionArgs(commandArgs) {
return delimiter >= 0 ? commandArgs.slice(0, delimiter) : commandArgs;
}
function commandProvider(commandArgs) {
function commandProvider(commandArgsInput) {
let commandArgs = commandArgsInput;
commandArgs = crabboxOptionArgs(commandArgs);
for (let index = 0; index < commandArgs.length; index += 1) {
const arg = commandArgs[index];
@@ -495,7 +496,8 @@ function enforceBrokeredAws(commandArgs, providerName) {
process.exit(2);
}
function optionValue(commandArgs, name) {
function optionValue(commandArgsInput, name) {
let commandArgs = commandArgsInput;
commandArgs = crabboxOptionArgs(commandArgs);
for (let index = 0; index < commandArgs.length; index += 1) {
const arg = commandArgs[index];
@@ -509,7 +511,8 @@ function optionValue(commandArgs, name) {
return "";
}
function hasOption(commandArgs, name) {
function hasOption(commandArgsInput, name) {
let commandArgs = commandArgsInput;
commandArgs = crabboxOptionArgs(commandArgs);
const shortName = name.replace(/^--/u, "-");
for (const arg of commandArgs) {
@@ -716,7 +719,8 @@ function commandRuntimeEntrypoint(commandArgs) {
return "";
}
function commandWordsRuntimeEntrypoint(words) {
function commandWordsRuntimeEntrypoint(wordsInput) {
let words = wordsInput;
words = normalizeExecutableWords(words);
const first = (words[0] ?? "").split("/").pop();
if (jsRuntimeEntrypoints.has(first)) {
@@ -746,7 +750,8 @@ function commandNeedsAwsMacosPackageManager(commandArgs) {
return commandWordsNeedAwsMacosPackageManager(normalizedCommandWords(commandArgs));
}
function commandWordsNeedAwsMacosPackageManager(words) {
function commandWordsNeedAwsMacosPackageManager(wordsInput) {
let words = wordsInput;
words = normalizeExecutableWords(words);
const first = (words[0] ?? "").split("/").pop();
if (awsMacosCorepackEntrypoints.has(first)) {
@@ -768,7 +773,8 @@ function isChangedGateCommand(commandArgs) {
return isChangedGateCommandWords(words);
}
function isChangedGateCommandWords(words) {
function isChangedGateCommandWords(wordsInput) {
let words = wordsInput;
words = normalizeExecutableWords(words);
if (isChangedGateWords(words)) {
return true;
@@ -780,7 +786,8 @@ function isChangedGateCommandWords(words) {
: false;
}
function isChangedGateWords(words) {
function isChangedGateWords(wordsInput) {
let words = wordsInput;
words = normalizeExecutableWords(words);
if (words[0] === "corepack") {
words.shift();
@@ -848,7 +855,8 @@ function normalizeExecutableWords(words) {
return normalizedCommandWords(stripShellExecutionPrefixes(words));
}
function stripShellExecutionPrefixes(words) {
function stripShellExecutionPrefixes(wordsInput) {
let words = wordsInput;
words = [...words];
for (;;) {
const first = shellWordBasename(words[0]);
+2 -2
View File
@@ -55,12 +55,12 @@ const result = await executePluginCommand({
config: cfg,
from: `telegram:${chatId}`,
to: `telegram:${chatId}`,
accountId: accountId,
accountId,
});
if (result.text) {
await sendMessageTelegram(chatId, result.text, {
accountId: accountId,
accountId,
});
}
+4 -4
View File
@@ -121,8 +121,8 @@ function writePluginWithCliRegistryDependency([
writePluginManifest(path.join(dir, "openclaw.plugin.json"), id);
}
function writeClaudeBundle([root]) {
root = requireArg(root, "root");
function writeClaudeBundle(args) {
const root = requireArg(args[0], "root");
writeJson(path.join(root, ".claude-plugin", "plugin.json"), { name: "claude-bundle-e2e" });
write(
path.join(root, "commands", "office-hours.md"),
@@ -130,8 +130,8 @@ function writeClaudeBundle([root]) {
);
}
function writePluginMarketplace([root]) {
root = requireArg(root, "root");
function writePluginMarketplace(args) {
const root = requireArg(args[0], "root");
writeJson(path.join(root, ".claude-plugin", "marketplace.json"), {
name: "Fixture Marketplace",
version: "1.0.0",
+1 -2
View File
@@ -29,7 +29,6 @@ export function waitForWebSocketOpen(
): Promise<void> {
return new Promise((resolve, reject) => {
let settled = false;
let timer: ReturnType<typeof setTimeout>;
const cleanup = () => {
clearTimeout(timer);
@@ -62,7 +61,7 @@ export function waitForWebSocketOpen(
const suffix = closeDetails ? `: ${closeDetails}` : "";
rejectOpen(new Error(`closed before open${suffix}`));
};
timer = setTimeout(() => {
const timer: ReturnType<typeof setTimeout> = setTimeout(() => {
const consumeAbortError = () => {};
const removeAbortErrorConsumer = () => {
ws.off?.("error", consumeAbortError);
+2 -1
View File
@@ -231,7 +231,8 @@ function parsePositiveInteger(value: string, label: string) {
return parsed;
}
function parseArgs(argv: string[]): Options {
function parseArgs(argvInput: string[]): Options {
let argv = argvInput;
argv = argv[0] === "--" ? argv.slice(1) : argv;
const commands = new Set([
"finish",
+1 -2
View File
@@ -78,7 +78,6 @@ export function runCommand(
let stdout = "";
let stderr = "";
let settled = false;
let timeout: NodeJS.Timeout;
let killTimer: NodeJS.Timeout | undefined;
let timedOutError: Error | undefined;
const timeoutMs = Math.max(1, options.timeoutMs);
@@ -97,7 +96,7 @@ export function runCommand(
clearTimers();
reject(error);
};
timeout = setTimeout(() => {
const timeout: NodeJS.Timeout = setTimeout(() => {
if (settled) {
return;
}
+1 -2
View File
@@ -104,7 +104,6 @@ function run(command, args, cwd, options = {}) {
let stdout = "";
let stdoutBytes = 0;
let settled = false;
let timeout;
let forceKillTimeout;
const maxCapturedStdoutBytes = Math.max(
1,
@@ -152,7 +151,7 @@ function run(command, args, cwd, options = {}) {
forceKillTimeout.unref?.();
};
ACTIVE_CHILD_KILLERS.add(killChild);
timeout =
const timeout =
options.timeoutMs === undefined
? undefined
: setTimeout(() => {
+2 -4
View File
@@ -150,7 +150,7 @@ export function createBoundedOutputBuffer(maxBytes = DEFAULT_OUTPUT_MAX_BYTES) {
const append = (value) => {
const text = String(value);
let textBytes = Buffer.byteLength(text);
const textBytes = Buffer.byteLength(text);
if (textBytes >= limit) {
const buffer = Buffer.from(text);
const tail = buffer.subarray(buffer.length - limit).toString("utf8");
@@ -185,9 +185,7 @@ export function createBoundedOutputBuffer(maxBytes = DEFAULT_OUTPUT_MAX_BYTES) {
append,
read() {
const output = chunks.join("");
return truncated
? `[output truncated to last ${limit} bytes]\n${output}`
: output;
return truncated ? `[output truncated to last ${limit} bytes]\n${output}` : output;
},
};
}
+2 -4
View File
@@ -910,8 +910,6 @@ const resolveRunNodeDiagnosticArgs = (deps) => {
const waitForSpawnedProcess = async (childProcess, deps) => {
let forwardedSignal = null;
let onSigInt;
let onSigTerm;
const cleanupSignals = () => {
if (onSigInt) {
@@ -934,10 +932,10 @@ const waitForSpawnedProcess = async (childProcess, deps) => {
}
};
onSigInt = () => {
const onSigInt = () => {
forwardSignal("SIGINT");
};
onSigTerm = () => {
const onSigTerm = () => {
forwardSignal("SIGTERM");
};
+1 -2
View File
@@ -1013,8 +1013,7 @@ async function runLanePool(poolLanes, baseEnv, logDir, parallelism, options) {
await waitForLaneStartSlot();
reserve(poolLane);
activeLanes.set(poolLane.name, { name: poolLane.name, startedAt: Date.now() });
let promise;
promise = runLane(poolLane, baseEnv, logDir, options.timeoutMs)
const promise = runLane(poolLane, baseEnv, logDir, options.timeoutMs)
.then((result) => ({ lane: poolLane, promise, result }))
.finally(() => {
activeLanes.delete(poolLane.name);
+2 -1
View File
@@ -2303,7 +2303,8 @@ function hasConservativeVitestWorkerBudget(env) {
return workerBudget !== null && workerBudget <= 1;
}
export function resolveParallelFullSuiteConcurrency(specCount, env, hostInfo) {
export function resolveParallelFullSuiteConcurrency(specCount, envInput, hostInfo) {
let env = envInput;
env ??= process.env;
const override = parsePositiveInt(env.OPENCLAW_TEST_PROJECTS_PARALLEL);
if (override !== null) {
+1 -1
View File
@@ -109,7 +109,7 @@ for (const line of log.split("\n")) {
continue;
}
let login = resolveLogin(currentName, currentEmail, apiByLogin, nameToLogin, emailToLogin);
const login = resolveLogin(currentName, currentEmail, apiByLogin, nameToLogin, emailToLogin);
if (!login) {
continue;
}
+2 -4
View File
@@ -289,8 +289,6 @@ export async function runWatchMain(params = {}) {
let watcher = null;
let lockHandle = null;
let autoDoctorAttempted = false;
let onSigInt;
let onSigTerm;
const settle = (code) => {
if (settled) {
@@ -448,14 +446,14 @@ export async function runWatchMain(params = {}) {
void resolveCreateWatcher().then(attachWatcher).catch(rejectWatcherStartupError);
};
onSigInt = () => {
const onSigInt = () => {
shuttingDown = true;
if (watchProcess && typeof watchProcess.kill === "function") {
watchProcess.kill(WATCH_RESTART_SIGNAL);
}
settle(130);
};
onSigTerm = () => {
const onSigTerm = () => {
shuttingDown = true;
if (watchProcess && typeof watchProcess.kill === "function") {
watchProcess.kill(WATCH_RESTART_SIGNAL);
+1 -2
View File
@@ -473,7 +473,6 @@ describe("acp translator stop reason mapping", () => {
it("finishes terminal prompts while rejecting stale pre-ack prompts", async () => {
vi.useFakeTimers();
try {
let acceptedRunId: string | undefined;
let acceptedWaitCount = 0;
const requestMock = vi.fn(async (method: string, params?: Record<string, unknown>) => {
if (method === "chat.send") {
@@ -520,7 +519,7 @@ describe("acp translator stop reason mapping", () => {
void preAckPrompt.catch(() => {});
await Promise.resolve();
acceptedRunId = requestMock.mock.calls.find((call) => {
const acceptedRunId: string | undefined = requestMock.mock.calls.find((call) => {
const [method, requestParams] = call;
return method === "chat.send" && requestParams?.sessionKey === "agent:main:first";
})?.[1]?.idempotencyKey as string | undefined;
+1 -1
View File
@@ -1211,7 +1211,7 @@ export async function spawnAcpDirect(
});
}
let requestThreadBinding = params.thread === true;
const requestThreadBinding = params.thread === true;
const runtimePolicyError = resolveAcpSpawnRuntimePolicyError({
cfg,
requesterSessionKey: ctx.agentSessionKey,
+2 -1
View File
@@ -234,7 +234,7 @@ export function resolveProcessToolScopeKey(params: {
}
function applyModelProviderToolPolicy(
tools: AnyAgentTool[],
toolsInput: AnyAgentTool[],
params?: {
config?: OpenClawConfig;
modelProvider?: string;
@@ -247,6 +247,7 @@ function applyModelProviderToolPolicy(
suppressManagedWebSearch?: boolean;
},
): AnyAgentTool[] {
let tools = toolsInput;
tools = filterLocalModelLeanTools({
tools,
config: params?.config,
+2 -1
View File
@@ -255,7 +255,8 @@ function sumPendingChars(buffer: string[]) {
return total;
}
function capPendingBuffer(buffer: string[], pendingChars: number, cap: number) {
function capPendingBuffer(buffer: string[], pendingCharsInput: number, cap: number) {
let pendingChars = pendingCharsInput;
if (pendingChars <= cap) {
return pendingChars;
}
+2 -4
View File
@@ -149,8 +149,6 @@ async function sleepPollInterval(ms: number, signal?: AbortSignal): Promise<void
throw createAbortError(signal.reason);
}
await new Promise<void>((resolve, reject) => {
let timer: ReturnType<typeof setTimeout> | undefined;
let onAbort: (() => void) | undefined;
const cleanup = () => {
if (timer) {
clearTimeout(timer);
@@ -163,11 +161,11 @@ async function sleepPollInterval(ms: number, signal?: AbortSignal): Promise<void
cleanup();
resolve();
};
onAbort = () => {
const onAbort: (() => void) | undefined = () => {
cleanup();
reject(createAbortError(signal?.reason));
};
timer = setTimeout(onResolve, ms);
const timer: ReturnType<typeof setTimeout> | undefined = setTimeout(onResolve, ms);
timer.unref?.();
signal?.addEventListener("abort", onAbort, { once: true });
});
@@ -642,7 +642,6 @@ function scheduleDeferredTurnMaintenance(
});
return undefined;
}
let state!: DeferredTurnMaintenanceRunState;
const trackedPromise = runPromise
.catch((err) => {
params.onScheduleFailure?.(err);
@@ -670,7 +669,7 @@ function scheduleDeferredTurnMaintenance(
await disposeDeferredMaintenanceContextEngine(discardedRerunParams.contextEngine);
}
});
state = {
const state: DeferredTurnMaintenanceRunState = {
promise: trackedPromise,
rerunRequested: false,
latestParams: { ...params, sessionKey },
+2 -1
View File
@@ -456,8 +456,9 @@ function buildHandledReplyPayloads(reply?: ReplyPayload) {
}
export async function runEmbeddedAgent(
params: RunEmbeddedAgentParams,
paramsInput: RunEmbeddedAgentParams,
): Promise<EmbeddedAgentRunResult> {
let params = paramsInput;
// Resolve sessionKey early so all downstream consumers (hooks, LCM, compaction)
// receive a non-null key even when callers omit it. See #60552.
const effectiveSessionKey = backfillSessionKey({
@@ -553,7 +553,7 @@ describe("wrapStreamFnWithDiagnosticModelCallEvents", () => {
const stream = {};
Object.defineProperty(stream, Symbol.asyncIterator, {
configurable: false,
value: async function* () {
async *value() {
yield { type: "text", text: "ok" };
},
});
@@ -110,8 +110,6 @@ export async function steerAndWaitForTranscriptCommit(
): Promise<void> {
await new Promise<void>((resolve, reject) => {
let settled = false;
let unsubscribe: (() => void) | undefined;
let timer: ReturnType<typeof setTimeout> | undefined;
let terminalTimer: ReturnType<typeof setTimeout> | undefined;
const finish = (err?: unknown) => {
if (settled) {
@@ -157,7 +155,7 @@ export async function steerAndWaitForTranscriptCommit(
}, 0);
terminalTimer.unref?.();
};
timer = setTimeout(
const timer: ReturnType<typeof setTimeout> | undefined = setTimeout(
() => {
rejectAfterCancellation(
"queued steering message was not committed to the transcript before timeout",
@@ -166,7 +164,7 @@ export async function steerAndWaitForTranscriptCommit(
Math.max(1, timeoutMs),
);
timer.unref?.();
unsubscribe = activeSession.subscribe((event) => {
const unsubscribe: (() => void) | undefined = activeSession.subscribe((event) => {
if (isAutoRetryStartEvent(event) || isCompactionStartEvent(event)) {
if (terminalTimer) {
clearTimeout(terminalTimer);
@@ -852,7 +852,7 @@ export async function runEmbeddedAttempt(
let bundleLspRuntime: Awaited<ReturnType<typeof createBundleLspToolRuntime>> | undefined;
let toolSearchCatalogRef: ToolSearchCatalogRef | undefined;
let toolSearchCatalogApplied = false;
let sessionCleanupOwnsEmbeddedResources = false;
const sessionCleanupOwnsEmbeddedResources = false;
let abortActiveSessionForExternalSignal: (() => Promise<void>) | undefined;
let abortRunForExternalSignal: ((isTimeout?: boolean, reason?: unknown) => void) | undefined;
let isCompactionPendingForExternalSignal: (() => boolean) | undefined;
@@ -2659,8 +2659,6 @@ export async function runEmbeddedAttempt(
activeSession.agent.streamFn,
);
let idleTimeoutTrigger: ((error: Error) => void) | undefined;
// Wrap stream with idle timeout detection.
//
// Prefer the caller's explicit `runTimeoutOverrideMs` when provided —
@@ -2922,7 +2920,6 @@ export async function runEmbeddedAttempt(
}
}
};
let queueHandleForAbandonment: EmbeddedAgentQueueHandle | undefined;
const abortRun = (isTimeout = false, reason?: unknown) => {
aborted = true;
if (isTimeout) {
@@ -2954,7 +2951,7 @@ export async function runEmbeddedAttempt(
}
};
abortRunForExternalSignal = abortRun;
idleTimeoutTrigger = (error) => {
const idleTimeoutTrigger: ((error: Error) => void) | undefined = (error) => {
idleTimedOut = true;
abortRun(true, error);
};
@@ -3131,7 +3128,7 @@ export async function runEmbeddedAttempt(
if (params.replyOperation) {
params.replyOperation.attachBackend(queueHandle);
}
queueHandleForAbandonment = queueHandle;
const queueHandleForAbandonment: EmbeddedAgentQueueHandle | undefined = queueHandle;
setActiveEmbeddedRun(params.sessionId, queueHandle, params.sessionKey, params.sessionFile);
let abortWarnTimer: NodeJS.Timeout | undefined;
@@ -3844,7 +3841,7 @@ export async function runEmbeddedAttempt(
prompt: promptForModel,
historyMessages: cloneHookMessages(hookMessagesForCurrentPrompt),
imagesCount: imageResult.images.length,
tools: tools,
tools,
},
{
runId: params.runId,
@@ -172,8 +172,7 @@ export function createEmbeddedRunAuthController(params: {
}
const refreshGeneration = runtimeAuthState.generation;
const refreshProfileId = runtimeAuthState.profileId;
let refreshPromise: Promise<void>;
refreshPromise = (async () => {
const refreshPromise: Promise<void> = (async () => {
const currentRuntimeAuthState = params.getRuntimeAuthState();
const sourceApiKey = currentRuntimeAuthState?.sourceApiKey.trim() ?? "";
if (!sourceApiKey) {
@@ -628,7 +628,7 @@ describe("installContextEngineLoopHook", () => {
const engine = makeMockEngine();
installHook(agent, engine, 1, () => ({
provider: "anthropic",
modelId: modelId,
modelId,
promptCache: {
retention: "short",
lastCacheTouchAt: 123,
@@ -813,8 +813,8 @@ export function handleMessageEnd(
const parsedText = trimmedText
? parseReplyDirectives(splitTrailingDirective(trimmedText, { final: true }).text)
: null;
let cleanedText = parsedText?.text ?? "";
let { mediaUrls, hasMedia } = resolveSendableOutboundReplyParts(parsedText ?? {});
const cleanedText = parsedText?.text ?? "";
const { mediaUrls, hasMedia } = resolveSendableOutboundReplyParts(parsedText ?? {});
const finalizeMessageEnd = () => {
ctx.state.deltaBuffer = "";
+6 -6
View File
@@ -1521,12 +1521,12 @@ async function startNativeHookRelayPermissionApprovalWithBudget(params: {
);
return "defer";
}
let approval!: Promise<NativeHookRelayPermissionApprovalResult>;
approval = nativeHookRelayPermissionApprovalRequester(params.request).finally(() => {
if (pendingPermissionApprovals.get(params.approvalKey) === approval) {
pendingPermissionApprovals.delete(params.approvalKey);
}
});
const approval: Promise<NativeHookRelayPermissionApprovalResult> =
nativeHookRelayPermissionApprovalRequester(params.request).finally(() => {
if (pendingPermissionApprovals.get(params.approvalKey) === approval) {
pendingPermissionApprovals.delete(params.approvalKey);
}
});
pendingPermissionApprovals.set(params.approvalKey, approval);
return approval;
}
+3 -1
View File
@@ -42,7 +42,9 @@ describeLive("minimax live", () => {
contextWindow: 200000,
maxTokens: 8192,
};
let { res, text } = await runMinimaxTextProbe(model, 128);
const probeResult = await runMinimaxTextProbe(model, 128);
const { res } = probeResult;
let { text } = probeResult;
// MiniMax can spend a small token budget in hidden thinking before it emits
// the visible answer. Give this smoke probe one larger retry.
if (text.length === 0 && res.stopReason === "length") {

Some files were not shown because too many files have changed in this diff Show More