mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
chore(lint): enable object and reassignment rules
This commit is contained in:
@@ -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 () => {
|
||||
|
||||
+1
-2
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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 } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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" });
|
||||
|
||||
|
||||
@@ -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)}`);
|
||||
};
|
||||
|
||||
|
||||
@@ -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?.();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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: [],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 & {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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(() => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user