fix: resolve lint errors (unused vars, imports, formatting)

- Prefix unused test variables with underscore
- Remove unused piSpec import and idleMs class member
- Fix import ordering and code formatting
main
Eng. Juan Combetto 2025-12-05 00:36:41 +09:00 committed by Peter Steinberger
parent 518af0ef24
commit 4a35bcec21
9 changed files with 144 additions and 126 deletions

View File

@ -16,8 +16,8 @@ import {
formatToolAggregate, formatToolAggregate,
shortenMeta, shortenMeta,
shortenPath, shortenPath,
TOOL_RESULT_FLUSH_COUNT,
TOOL_RESULT_DEBOUNCE_MS, TOOL_RESULT_DEBOUNCE_MS,
TOOL_RESULT_FLUSH_COUNT,
} from "./tool-meta.js"; } from "./tool-meta.js";
import type { ReplyPayload } from "./types.js"; import type { ReplyPayload } from "./types.js";
@ -345,7 +345,11 @@ export async function runCommandReply(
// Tau (pi agent) needs --continue to reload prior messages when resuming. // Tau (pi agent) needs --continue to reload prior messages when resuming.
// Without it, pi starts from a blank state even though we pass the session file path. // Without it, pi starts from a blank state even though we pass the session file path.
if (agentKind === "pi" && !isNewSession && !sessionArgList.includes("--continue")) { if (
agentKind === "pi" &&
!isNewSession &&
!sessionArgList.includes("--continue")
) {
sessionArgList.push("--continue"); sessionArgList.push("--continue");
} }
@ -433,10 +437,7 @@ export async function runCommandReply(
} }
}; };
let lastStreamedAssistant: string | undefined; let lastStreamedAssistant: string | undefined;
const streamAssistant = (msg?: { const streamAssistant = (msg?: { role?: string; content?: unknown[] }) => {
role?: string;
content?: unknown[];
}) => {
if (!onPartialReply || msg?.role !== "assistant") return; if (!onPartialReply || msg?.role !== "assistant") return;
const textBlocks = Array.isArray(msg.content) const textBlocks = Array.isArray(msg.content)
? (msg.content as Array<{ type?: string; text?: string }>) ? (msg.content as Array<{ type?: string; text?: string }>)
@ -478,68 +479,37 @@ export async function runCommandReply(
cwd: reply.cwd, cwd: reply.cwd,
prompt: body, prompt: body,
timeoutMs, timeoutMs,
onEvent: onEvent: onPartialReply
onPartialReply ? (line: string) => {
? (line: string) => { try {
try { const ev = JSON.parse(line) as {
const ev = JSON.parse(line) as { type?: string;
type?: string; message?: {
message?: { role?: string;
role?: string; content?: unknown[];
content?: unknown[]; details?: Record<string, unknown>;
details?: Record<string, unknown>; arguments?: Record<string, unknown>;
arguments?: Record<string, unknown>;
toolCallId?: string;
tool_call_id?: string;
toolName?: string;
name?: string;
};
toolCallId?: string; toolCallId?: string;
tool_call_id?: string;
toolName?: string; toolName?: string;
args?: Record<string, unknown>; name?: string;
}; };
// Capture metadata as soon as the tool starts (from args). toolCallId?: string;
if (ev.type === "tool_execution_start") { toolName?: string;
const toolName = ev.toolName; args?: Record<string, unknown>;
const meta = inferToolMeta({ };
toolName, // Capture metadata as soon as the tool starts (from args).
name: ev.toolName, if (ev.type === "tool_execution_start") {
arguments: ev.args, const toolName = ev.toolName;
}); const meta = inferToolMeta({
if (ev.toolCallId) { toolName,
toolMetaById.set(ev.toolCallId, meta); name: ev.toolName,
} arguments: ev.args,
if (meta) { });
if (pendingToolName && toolName && toolName !== pendingToolName) { if (ev.toolCallId) {
flushPendingTool(); toolMetaById.set(ev.toolCallId, meta);
}
if (!pendingToolName) pendingToolName = toolName;
pendingMetas.push(meta);
if (
TOOL_RESULT_FLUSH_COUNT > 0 &&
pendingMetas.length >= TOOL_RESULT_FLUSH_COUNT
) {
flushPendingTool();
} else {
if (pendingTimer) clearTimeout(pendingTimer);
pendingTimer = setTimeout(
flushPendingTool,
TOOL_RESULT_DEBOUNCE_MS,
);
}
}
} }
if ( if (meta) {
(ev.type === "message" || ev.type === "message_end") &&
ev.message?.role === "tool_result" &&
Array.isArray(ev.message.content)
) {
const toolName = inferToolName(ev.message);
const toolCallId =
ev.message.toolCallId ?? ev.message.tool_call_id;
const meta =
inferToolMeta(ev.message) ??
(toolCallId ? toolMetaById.get(toolCallId) : undefined);
if ( if (
pendingToolName && pendingToolName &&
toolName && toolName &&
@ -548,32 +518,66 @@ export async function runCommandReply(
flushPendingTool(); flushPendingTool();
} }
if (!pendingToolName) pendingToolName = toolName; if (!pendingToolName) pendingToolName = toolName;
if (meta) pendingMetas.push(meta); pendingMetas.push(meta);
if ( if (
TOOL_RESULT_FLUSH_COUNT > 0 && TOOL_RESULT_FLUSH_COUNT > 0 &&
pendingMetas.length >= TOOL_RESULT_FLUSH_COUNT pendingMetas.length >= TOOL_RESULT_FLUSH_COUNT
) { ) {
flushPendingTool(); flushPendingTool();
return; } else {
if (pendingTimer) clearTimeout(pendingTimer);
pendingTimer = setTimeout(
flushPendingTool,
TOOL_RESULT_DEBOUNCE_MS,
);
} }
if (pendingTimer) clearTimeout(pendingTimer);
pendingTimer = setTimeout(
flushPendingTool,
TOOL_RESULT_DEBOUNCE_MS,
);
} }
if (
ev.type === "message_end" ||
ev.type === "message_update" ||
ev.type === "message"
) {
streamAssistant(ev.message);
}
} catch {
// ignore malformed lines
} }
if (
(ev.type === "message" || ev.type === "message_end") &&
ev.message?.role === "tool_result" &&
Array.isArray(ev.message.content)
) {
const toolName = inferToolName(ev.message);
const toolCallId =
ev.message.toolCallId ?? ev.message.tool_call_id;
const meta =
inferToolMeta(ev.message) ??
(toolCallId ? toolMetaById.get(toolCallId) : undefined);
if (
pendingToolName &&
toolName &&
toolName !== pendingToolName
) {
flushPendingTool();
}
if (!pendingToolName) pendingToolName = toolName;
if (meta) pendingMetas.push(meta);
if (
TOOL_RESULT_FLUSH_COUNT > 0 &&
pendingMetas.length >= TOOL_RESULT_FLUSH_COUNT
) {
flushPendingTool();
return;
}
if (pendingTimer) clearTimeout(pendingTimer);
pendingTimer = setTimeout(
flushPendingTool,
TOOL_RESULT_DEBOUNCE_MS,
);
}
if (
ev.type === "message_end" ||
ev.type === "message_update" ||
ev.type === "message"
) {
streamAssistant(ev.message);
}
} catch {
// ignore malformed lines
} }
: undefined, }
: undefined,
}); });
flushPendingTool(); flushPendingTool();
return rpcResult; return rpcResult;
@ -610,10 +614,10 @@ export async function runCommandReply(
type ReplyItem = { text: string; media?: string[] }; type ReplyItem = { text: string; media?: string[] };
const replyItems: ReplyItem[] = []; const replyItems: ReplyItem[] = [];
const includeToolResultsInline = const includeToolResultsInline =
verboseLevel === "on" && !onPartialReply && parsedToolResults.length > 0; verboseLevel === "on" && !onPartialReply && parsedToolResults.length > 0;
if (includeToolResultsInline) { if (includeToolResultsInline) {
const aggregated = parsedToolResults.reduce< const aggregated = parsedToolResults.reduce<
{ toolName?: string; metas: string[]; previews: string[] }[] { toolName?: string; metas: string[]; previews: string[] }[]
>((acc, tr) => { >((acc, tr) => {
@ -647,7 +651,8 @@ export async function runCommandReply(
const formatPreview = (texts: string[]) => { const formatPreview = (texts: string[]) => {
const joined = texts.join(" ").trim(); const joined = texts.join(" ").trim();
if (!joined) return ""; if (!joined) return "";
const clipped = joined.length > 120 ? `${joined.slice(0, 117)}` : joined; const clipped =
joined.length > 120 ? `${joined.slice(0, 117)}` : joined;
return ` — “${clipped}`; return ` — “${clipped}`;
}; };
@ -662,7 +667,7 @@ export async function runCommandReply(
media: mediaFound?.length ? mediaFound : undefined, media: mediaFound?.length ? mediaFound : undefined,
}); });
} }
} }
for (const t of parsedTexts) { for (const t of parsedTexts) {
const { text: cleanedText, mediaUrls: mediaFound } = const { text: cleanedText, mediaUrls: mediaFound } =

View File

@ -11,12 +11,12 @@ import {
saveSessionStore, saveSessionStore,
} from "../config/sessions.js"; } from "../config/sessions.js";
import { info, isVerbose, logVerbose } from "../globals.js"; import { info, isVerbose, logVerbose } from "../globals.js";
import { triggerWarelayRestart } from "../infra/restart.js";
import { ensureMediaHosted } from "../media/host.js"; import { ensureMediaHosted } from "../media/host.js";
import { runCommandWithTimeout } from "../process/exec.js"; import { runCommandWithTimeout } from "../process/exec.js";
import { defaultRuntime, type RuntimeEnv } from "../runtime.js"; import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
import type { TwilioRequester } from "../twilio/types.js"; import type { TwilioRequester } from "../twilio/types.js";
import { sendTypingIndicator } from "../twilio/typing.js"; import { sendTypingIndicator } from "../twilio/typing.js";
import { triggerWarelayRestart } from "../infra/restart.js";
import { chunkText } from "./chunk.js"; import { chunkText } from "./chunk.js";
import { runCommandReply } from "./command-reply.js"; import { runCommandReply } from "./command-reply.js";
import { import {
@ -358,7 +358,13 @@ export async function getReplyFromConfig(
await saveSessionStore(storePath, sessionStore); await saveSessionStore(storePath, sessionStore);
} }
// If verbose directive is also present, persist it too. // If verbose directive is also present, persist it too.
if (hasVerboseDirective && inlineVerbose && sessionEntry && sessionStore && sessionKey) { if (
hasVerboseDirective &&
inlineVerbose &&
sessionEntry &&
sessionStore &&
sessionKey
) {
if (inlineVerbose === "off") { if (inlineVerbose === "off") {
delete sessionEntry.verboseLevel; delete sessionEntry.verboseLevel;
} else { } else {
@ -431,9 +437,7 @@ export async function getReplyFromConfig(
const to = (ctx.To ?? "").replace(/^whatsapp:/, ""); const to = (ctx.To ?? "").replace(/^whatsapp:/, "");
const isSamePhone = from && to && from === to; const isSamePhone = from && to && from === to;
const abortKey = sessionKey ?? (from || undefined) ?? (to || undefined); const abortKey = sessionKey ?? (from || undefined) ?? (to || undefined);
const rawBodyNormalized = ( const rawBodyNormalized = (sessionCtx.BodyStripped ?? sessionCtx.Body ?? "")
sessionCtx.BodyStripped ?? sessionCtx.Body ?? ""
)
.trim() .trim()
.toLowerCase(); .toLowerCase();

View File

@ -18,8 +18,8 @@ describe("sessions", () => {
}); });
it("keeps group chats distinct", () => { it("keeps group chats distinct", () => {
expect( expect(deriveSessionKey("per-sender", { From: "12345-678@g.us" })).toBe(
deriveSessionKey("per-sender", { From: "12345-678@g.us" }), "group:12345-678@g.us",
).toBe("group:12345-678@g.us"); );
}); });
}); });

View File

@ -678,7 +678,7 @@ describe("config and templating", () => {
}, },
}; };
const ack = await index.getReplyFromConfig( const _ack = await index.getReplyFromConfig(
{ Body: "/v:on", From: "+1", To: "+2" }, { Body: "/v:on", From: "+1", To: "+2" },
undefined, undefined,
cfg, cfg,
@ -979,7 +979,7 @@ describe("config and templating", () => {
const batchBody = const batchBody =
"[Current message - respond to this]\nPeter: @Clawd UK /thinking medium /v on"; "[Current message - respond to this]\nPeter: @Clawd UK /thinking medium /v on";
const ack = await index.getReplyFromConfig( const _ack = await index.getReplyFromConfig(
{ {
Body: batchBody, Body: batchBody,
From: "group:456@g.us", From: "group:456@g.us",
@ -1000,7 +1000,7 @@ describe("config and templating", () => {
const persisted = JSON.parse( const persisted = JSON.parse(
await fs.promises.readFile(storePath, "utf-8"), await fs.promises.readFile(storePath, "utf-8"),
) as Record<string, { thinkingLevel?: string; verboseLevel?: string }>; ) as Record<string, { thinkingLevel?: string; verboseLevel?: string }>;
const entry = Object.values(persisted)[0] as { const _entry = Object.values(persisted)[0] as {
thinkingLevel?: string; thinkingLevel?: string;
verboseLevel?: string; verboseLevel?: string;
}; };

View File

@ -7,7 +7,6 @@ import {
autoReplyIfConfigured, autoReplyIfConfigured,
getReplyFromConfig, getReplyFromConfig,
} from "./auto-reply/reply.js"; } from "./auto-reply/reply.js";
import { enableConsoleCapture } from "./logging.js";
import { applyTemplate } from "./auto-reply/templating.js"; import { applyTemplate } from "./auto-reply/templating.js";
import { createDefaultDeps, monitorTwilio } from "./cli/deps.js"; import { createDefaultDeps, monitorTwilio } from "./cli/deps.js";
import { promptYesNo } from "./cli/prompt.js"; import { promptYesNo } from "./cli/prompt.js";
@ -33,6 +32,7 @@ import {
ensureTailscaledInstalled, ensureTailscaledInstalled,
getTailnetHostname, getTailnetHostname,
} from "./infra/tailscale.js"; } from "./infra/tailscale.js";
import { enableConsoleCapture } from "./logging.js";
import { runCommandWithTimeout, runExec } from "./process/exec.js"; import { runCommandWithTimeout, runExec } from "./process/exec.js";
import { monitorWebProvider } from "./provider-web.js"; import { monitorWebProvider } from "./provider-web.js";
import { createClient } from "./twilio/client.js"; import { createClient } from "./twilio/client.js";

View File

@ -4,7 +4,8 @@ const DEFAULT_LAUNCHD_LABEL = "com.steipete.warelay";
export function triggerWarelayRestart(): void { export function triggerWarelayRestart(): void {
const label = process.env.WARELAY_LAUNCHD_LABEL || DEFAULT_LAUNCHD_LABEL; const label = process.env.WARELAY_LAUNCHD_LABEL || DEFAULT_LAUNCHD_LABEL;
const uid = typeof process.getuid === "function" ? process.getuid() : undefined; const uid =
typeof process.getuid === "function" ? process.getuid() : undefined;
const target = uid !== undefined ? `gui/${uid}/${label}` : label; const target = uid !== undefined ? `gui/${uid}/${label}` : label;
const child = spawn("launchctl", ["kickstart", "-k", target], { const child = spawn("launchctl", ["kickstart", "-k", target], {
detached: true, detached: true,

View File

@ -1,8 +1,6 @@
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process"; import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
import readline from "node:readline"; import readline from "node:readline";
import { piSpec } from "../agents/pi.js";
type TauRpcOptions = { type TauRpcOptions = {
argv: string[]; argv: string[];
cwd?: string; cwd?: string;
@ -24,7 +22,6 @@ class TauRpcClient {
private stderr = ""; private stderr = "";
private buffer: string[] = []; private buffer: string[] = [];
private idleTimer: NodeJS.Timeout | null = null; private idleTimer: NodeJS.Timeout | null = null;
private readonly idleMs = 120;
private pending: private pending:
| { | {
resolve: (r: TauRpcResult) => void; resolve: (r: TauRpcResult) => void;
@ -58,7 +55,12 @@ class TauRpcClient {
const out = this.buffer.join("\n"); const out = this.buffer.join("\n");
clearTimeout(pending.timer); clearTimeout(pending.timer);
// Treat process exit as completion with whatever output we captured. // Treat process exit as completion with whatever output we captured.
pending.resolve({ stdout: out, stderr: this.stderr, code: code ?? 0, signal }); pending.resolve({
stdout: out,
stderr: this.stderr,
code: code ?? 0,
signal,
});
} }
this.dispose(); this.dispose();
}); });

View File

@ -84,13 +84,15 @@ function buildMentionConfig(cfg: ReturnType<typeof loadConfig>): MentionConfig {
const gc = cfg.inbound?.groupChat; const gc = cfg.inbound?.groupChat;
const requireMention = gc?.requireMention !== false; // default true const requireMention = gc?.requireMention !== false; // default true
const mentionRegexes = const mentionRegexes =
gc?.mentionPatterns?.map((p) => { gc?.mentionPatterns
try { ?.map((p) => {
return new RegExp(p, "i"); try {
} catch { return new RegExp(p, "i");
return null; } catch {
} return null;
}).filter((r): r is RegExp => Boolean(r)) ?? []; }
})
.filter((r): r is RegExp => Boolean(r)) ?? [];
return { requireMention, mentionRegexes }; return { requireMention, mentionRegexes };
} }
@ -728,7 +730,7 @@ export async function monitorWebProvider(
const senderLabel = const senderLabel =
latest.senderName && latest.senderE164 latest.senderName && latest.senderE164
? `${latest.senderName} (${latest.senderE164})` ? `${latest.senderName} (${latest.senderE164})`
: latest.senderName ?? latest.senderE164 ?? "Unknown"; : (latest.senderName ?? latest.senderE164 ?? "Unknown");
combinedBody = `${combinedBody}\\n[from: ${senderLabel}]`; combinedBody = `${combinedBody}\\n[from: ${senderLabel}]`;
// Clear stored history after using it // Clear stored history after using it
groupHistories.set(conversationId, []); groupHistories.set(conversationId, []);
@ -834,7 +836,7 @@ export async function monitorWebProvider(
const fromDisplay = const fromDisplay =
latest.chatType === "group" latest.chatType === "group"
? conversationId ? conversationId
: latest.from ?? "unknown"; : (latest.from ?? "unknown");
if (isVerbose()) { if (isVerbose()) {
console.log( console.log(
success( success(
@ -850,24 +852,26 @@ export async function monitorWebProvider(
} }
} catch (err) { } catch (err) {
console.error( console.error(
danger(`Failed sending web auto-reply to ${latest.from ?? conversationId}: ${String(err)}`), danger(
`Failed sending web auto-reply to ${latest.from ?? conversationId}: ${String(err)}`,
),
); );
} }
} }
}; };
const enqueueBatch = async (msg: WebInboundMsg) => { const enqueueBatch = async (msg: WebInboundMsg) => {
const key = msg.conversationId ?? msg.from; const key = msg.conversationId ?? msg.from;
const bucket = pendingBatches.get(key) ?? { messages: [] }; const bucket = pendingBatches.get(key) ?? { messages: [] };
bucket.messages.push(msg); bucket.messages.push(msg);
pendingBatches.set(key, bucket); pendingBatches.set(key, bucket);
if (getQueueSize() === 0) { if (getQueueSize() === 0) {
await processBatch(key); await processBatch(key);
} else { } else {
bucket.timer = bucket.timer =
bucket.timer ?? setTimeout(() => void processBatch(key), 150); bucket.timer ?? setTimeout(() => void processBatch(key), 150);
} }
}; };
const listener = await (listenerFactory ?? monitorWebInbox)({ const listener = await (listenerFactory ?? monitorWebInbox)({
verbose, verbose,

View File

@ -339,7 +339,9 @@ export async function monitorWebInbox(options: {
} as const; } as const;
} }
function unwrapMessage(message: proto.IMessage | undefined): proto.IMessage | undefined { function unwrapMessage(
message: proto.IMessage | undefined,
): proto.IMessage | undefined {
if (!message) return undefined; if (!message) return undefined;
if (message.ephemeralMessage?.message) { if (message.ephemeralMessage?.message) {
return unwrapMessage(message.ephemeralMessage.message as proto.IMessage); return unwrapMessage(message.ephemeralMessage.message as proto.IMessage);