chore(lint): parallelize more safe await-in-loop in server

This commit is contained in:
Mohamed Boudra
2026-04-24 01:36:39 +07:00
parent fe3a9bd833
commit 94b6604d7a
6 changed files with 225 additions and 231 deletions

View File

@@ -545,20 +545,23 @@ export class AgentManager {
return client.listPersistedAgents({ limit: options.limit });
}
const descriptors: PersistedAgentDescriptor[] = [];
for (const [provider, client] of this.clients.entries()) {
if (!client.listPersistedAgents) {
continue;
}
try {
const entries = await client.listPersistedAgents({
limit: options?.limit,
});
descriptors.push(...entries);
} catch (error) {
this.logger.warn({ err: error, provider }, "Failed to list persisted agents for provider");
}
}
const providerEntries = Array.from(this.clients.entries()).filter(
([, client]) => !!client.listPersistedAgents,
);
const descriptorLists = await Promise.all(
providerEntries.map(async ([provider, client]) => {
try {
return await client.listPersistedAgents!({ limit: options?.limit });
} catch (error) {
this.logger.warn(
{ err: error, provider },
"Failed to list persisted agents for provider",
);
return [];
}
}),
);
const descriptors: PersistedAgentDescriptor[] = descriptorLists.flat();
const limit = options?.limit ?? 20;
return descriptors

View File

@@ -274,45 +274,42 @@ export class AgentStorage {
throw error;
}
for (const entry of entries) {
if (entry.isFile() && entry.name.endsWith(".json")) {
const rootPath = path.join(this.baseDir, entry.name);
const rootRecord = await this.readRecordFile(rootPath);
if (!rootRecord) {
continue;
}
records.push(rootRecord);
this.cache.set(rootRecord.id, rootRecord);
this.pathById.set(rootRecord.id, rootPath);
this.addIndexedPath(rootRecord.id, rootPath);
continue;
}
const rootRecordPaths = entries
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
.map((entry) => path.join(this.baseDir, entry.name));
if (!entry.isDirectory()) {
continue;
}
const projectDir = path.join(this.baseDir, entry.name);
let files: Array<import("node:fs").Dirent> = [];
try {
files = await fs.readdir(projectDir, { withFileTypes: true });
} catch {
continue;
}
const projectDirs = entries
.filter((entry) => entry.isDirectory())
.map((entry) => path.join(this.baseDir, entry.name));
for (const file of files) {
if (!file.isFile() || !file.name.endsWith(".json")) {
continue;
const projectFileLists = await Promise.all(
projectDirs.map(async (projectDir) => {
try {
const files = await fs.readdir(projectDir, { withFileTypes: true });
return files
.filter((file) => file.isFile() && file.name.endsWith(".json"))
.map((file) => path.join(projectDir, file.name));
} catch {
return [];
}
const filePath = path.join(projectDir, file.name);
}),
);
const allFilePaths = [...rootRecordPaths, ...projectFileLists.flat()];
const loaded = await Promise.all(
allFilePaths.map(async (filePath) => {
const record = await this.readRecordFile(filePath);
if (!record) {
continue;
}
records.push(record);
this.cache.set(record.id, record);
this.pathById.set(record.id, filePath);
this.addIndexedPath(record.id, filePath);
}
return record ? { record, filePath } : null;
}),
);
for (const item of loaded) {
if (!item) continue;
const { record, filePath } = item;
records.push(record);
this.cache.set(record.id, record);
this.pathById.set(record.id, filePath);
this.addIndexedPath(record.id, filePath);
}
return records;

View File

@@ -4285,37 +4285,35 @@ async function collectRecentClaudeSessions(
} catch {
return [];
}
const candidates: ClaudeSessionCandidate[] = [];
for (const dirName of projectDirs) {
const projectPath = path.join(root, dirName);
let stats: fs.Stats;
try {
stats = await fsPromises.stat(projectPath);
} catch {
continue;
}
if (!stats.isDirectory()) {
continue;
}
let files: string[];
try {
files = await fsPromises.readdir(projectPath);
} catch {
continue;
}
for (const file of files) {
if (!file.endsWith(".jsonl")) {
continue;
const projectFileLists = await Promise.all(
projectDirs.map(async (dirName) => {
const projectPath = path.join(root, dirName);
try {
const stats = await fsPromises.stat(projectPath);
if (!stats.isDirectory()) return { projectPath, files: [] as string[] };
const files = await fsPromises.readdir(projectPath);
return { projectPath, files };
} catch {
return { projectPath, files: [] as string[] };
}
const fullPath = path.join(projectPath, file);
}),
);
const fileEntries = projectFileLists.flatMap(({ projectPath, files }) =>
files.filter((f) => f.endsWith(".jsonl")).map((f) => path.join(projectPath, f)),
);
const statResults = await Promise.all(
fileEntries.map(async (fullPath) => {
try {
const fileStats = await fsPromises.stat(fullPath);
candidates.push({ path: fullPath, mtime: fileStats.mtime });
return { path: fullPath, mtime: fileStats.mtime };
} catch {
// ignore stat errors for individual files
return null;
}
}
}
}),
);
const candidates: ClaudeSessionCandidate[] = statResults.filter(
(entry): entry is ClaudeSessionCandidate => entry !== null,
);
return candidates.sort((a, b) => b.mtime.getTime() - a.mtime.getTime()).slice(0, limit);
}

View File

@@ -372,35 +372,33 @@ async function listCodexCustomPrompts(): Promise<AgentSlashCommand[]> {
return [];
}
const commands: AgentSlashCommand[] = [];
for (const entry of entries) {
if (!entry.isFile()) {
continue;
}
if (!entry.name.endsWith(".md")) {
continue;
}
const name = entry.name.slice(0, -".md".length);
if (!name) {
continue;
}
const fullPath = path.join(promptsDir, entry.name);
let content: string;
try {
content = await fs.readFile(fullPath, "utf8");
} catch {
continue;
}
const parsed = parseFrontMatter(content);
const description = parsed.frontMatter["description"] ?? "Custom prompt";
const argumentHint =
parsed.frontMatter["argument-hint"] ?? parsed.frontMatter["argument_hint"] ?? "";
commands.push({
name: `prompts:${name}`,
description,
argumentHint,
});
}
const mdEntries = entries.filter(
(entry) => entry.isFile() && entry.name.endsWith(".md") && entry.name.slice(0, -".md".length),
);
const parsedCommands = await Promise.all(
mdEntries.map(async (entry): Promise<AgentSlashCommand | null> => {
const name = entry.name.slice(0, -".md".length);
const fullPath = path.join(promptsDir, entry.name);
let content: string;
try {
content = await fs.readFile(fullPath, "utf8");
} catch {
return null;
}
const parsed = parseFrontMatter(content);
const description = parsed.frontMatter["description"] ?? "Custom prompt";
const argumentHint =
parsed.frontMatter["argument-hint"] ?? parsed.frontMatter["argument_hint"] ?? "";
return {
name: `prompts:${name}`,
description,
argumentHint,
};
}),
);
const commands: AgentSlashCommand[] = parsedCommands.filter(
(cmd): cmd is AgentSlashCommand => cmd !== null,
);
return commands.sort((a, b) => a.name.localeCompare(b.name));
}
@@ -431,18 +429,20 @@ async function listCodexSkills(
continue;
}
for (const entry of entries) {
if (!entry.isDirectory() && !entry.isSymbolicLink()) {
continue;
}
const skillDir = path.join(dir, entry.name);
const skillPath = path.join(skillDir, "SKILL.md");
let content: string;
try {
content = await fs.readFile(skillPath, "utf8");
} catch {
continue;
}
const dirEntries = entries.filter((entry) => entry.isDirectory() || entry.isSymbolicLink());
const skillContents = await Promise.all(
dirEntries.map(async (entry) => {
const skillDir = path.join(dir, entry.name);
const skillPath = path.join(skillDir, "SKILL.md");
try {
return await fs.readFile(skillPath, "utf8");
} catch {
return null;
}
}),
);
for (const content of skillContents) {
if (!content) continue;
const { frontMatter } = parseFrontMatter(content);
const name = frontMatter["name"];
const description = frontMatter["description"];
@@ -2396,42 +2396,40 @@ export async function codexAppServerTurnInputFromPrompt(
}
const blocks = prompt as Array<unknown>;
const output: unknown[] = [];
for (const block of blocks) {
if (!block || typeof block !== "object") {
output.push(block);
continue;
}
const record = block as { type?: unknown; mimeType?: unknown; data?: unknown };
if (
record.type === "image" &&
typeof record.mimeType === "string" &&
typeof record.data === "string"
) {
try {
const filePath = await writeImageAttachment(record.mimeType, record.data);
output.push({ type: "localImage", path: filePath });
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.warn({ message }, "Failed to write Codex image attachment");
output.push({
type: "text",
text: `User attached image (failed to write temp file): ${message}`,
});
const output = await Promise.all(
blocks.map(async (block) => {
if (!block || typeof block !== "object") {
return block;
}
continue;
}
if (record.type === "github_pr" || record.type === "github_issue") {
output.push({
type: "text",
text: renderPromptAttachmentAsText(
record as Extract<AgentPromptContentBlock, { type: "github_pr" | "github_issue" }>,
),
});
continue;
}
output.push(block);
}
const record = block as { type?: unknown; mimeType?: unknown; data?: unknown };
if (
record.type === "image" &&
typeof record.mimeType === "string" &&
typeof record.data === "string"
) {
try {
const filePath = await writeImageAttachment(record.mimeType, record.data);
return { type: "localImage", path: filePath };
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.warn({ message }, "Failed to write Codex image attachment");
return {
type: "text",
text: `User attached image (failed to write temp file): ${message}`,
};
}
}
if (record.type === "github_pr" || record.type === "github_issue") {
return {
type: "text",
text: renderPromptAttachmentAsText(
record as Extract<AgentPromptContentBlock, { type: "github_pr" | "github_issue" }>,
),
};
}
return block;
}),
);
return output;
}
@@ -4243,56 +4241,54 @@ export class CodexAppServerAgentClient implements AgentClient {
data?: Array<any>;
};
const threads = Array.isArray(response?.data) ? response.data : [];
const descriptors: PersistedAgentDescriptor[] = [];
for (const thread of threads.slice(0, limit)) {
const threadId = thread.id;
const cwd = thread.cwd ?? process.cwd();
const title = thread.preview ?? null;
let timeline: AgentTimelineItem[] = [];
try {
const rolloutTimeline = await loadCodexPersistedTimeline(
threadId,
undefined,
this.logger,
);
const read = (await client.request("thread/read", {
threadId,
includeTurns: true,
})) as { thread?: { turns?: Array<{ items?: any[] }> } };
const turns = read.thread?.turns ?? [];
const itemsFromThreadRead: AgentTimelineItem[] = [];
for (const turn of turns) {
for (const item of turn.items ?? []) {
const timelineItem = threadItemToTimeline(item, { cwd });
if (timelineItem) itemsFromThreadRead.push(timelineItem);
const descriptors: PersistedAgentDescriptor[] = await Promise.all(
threads.slice(0, limit).map(async (thread) => {
const threadId = thread.id;
const cwd = thread.cwd ?? process.cwd();
const title = thread.preview ?? null;
let timeline: AgentTimelineItem[] = [];
try {
const [rolloutTimeline, read] = await Promise.all([
loadCodexPersistedTimeline(threadId, undefined, this.logger),
client.request("thread/read", {
threadId,
includeTurns: true,
}) as Promise<{ thread?: { turns?: Array<{ items?: any[] }> } }>,
]);
const turns = read.thread?.turns ?? [];
const itemsFromThreadRead: AgentTimelineItem[] = [];
for (const turn of turns) {
for (const item of turn.items ?? []) {
const timelineItem = threadItemToTimeline(item, { cwd });
if (timelineItem) itemsFromThreadRead.push(timelineItem);
}
}
timeline = rolloutTimeline.length > 0 ? rolloutTimeline : itemsFromThreadRead;
} catch {
timeline = [];
}
timeline = rolloutTimeline.length > 0 ? rolloutTimeline : itemsFromThreadRead;
} catch {
timeline = [];
}
descriptors.push({
provider: CODEX_PROVIDER,
sessionId: threadId,
cwd,
title,
lastActivityAt: new Date((thread.updatedAt ?? thread.createdAt ?? 0) * 1000),
persistence: {
return {
provider: CODEX_PROVIDER,
sessionId: threadId,
nativeHandle: threadId,
metadata: {
cwd,
title,
lastActivityAt: new Date((thread.updatedAt ?? thread.createdAt ?? 0) * 1000),
persistence: {
provider: CODEX_PROVIDER,
cwd,
title,
threadId,
sessionId: threadId,
nativeHandle: threadId,
metadata: {
provider: CODEX_PROVIDER,
cwd,
title,
threadId,
},
},
},
timeline,
});
}
timeline,
};
}),
);
return descriptors;
} finally {

View File

@@ -905,9 +905,7 @@ export class OpenCodeServerManager {
...(this.currentServer ? [this.currentServer] : []),
...Array.from(this.retiredServers),
];
for (const server of servers) {
await this.killServer(server);
}
await Promise.all(servers.map((server) => this.killServer(server)));
this.currentServer = null;
this.retiredServers.clear();
}

View File

@@ -244,43 +244,45 @@ export class ScheduleService {
private async recoverInterruptedRuns(): Promise<void> {
const schedules = await this.store.list();
const now = this.now();
for (const schedule of schedules) {
let updated = { ...schedule };
let dirty = false;
await Promise.all(
schedules.map(async (schedule) => {
let updated = { ...schedule };
let dirty = false;
// Mark any in-flight runs as failed
const runningIndex = updated.runs.findIndex((run) => run.status === "running");
if (runningIndex !== -1) {
const runs = [...updated.runs];
runs[runningIndex] = {
...runs[runningIndex],
status: "failed",
endedAt: now.toISOString(),
error: "Daemon restarted before the scheduled run completed",
};
updated = { ...updated, runs };
dirty = true;
}
// Advance stale nextRunAt for active schedules
if (
updated.status === "active" &&
updated.nextRunAt &&
new Date(updated.nextRunAt).getTime() <= now.getTime()
) {
let nextRunAt = computeNextRunAt(updated.cadence, new Date(updated.nextRunAt));
while (nextRunAt.getTime() <= now.getTime()) {
nextRunAt = computeNextRunAt(updated.cadence, nextRunAt);
// Mark any in-flight runs as failed
const runningIndex = updated.runs.findIndex((run) => run.status === "running");
if (runningIndex !== -1) {
const runs = [...updated.runs];
runs[runningIndex] = {
...runs[runningIndex],
status: "failed",
endedAt: now.toISOString(),
error: "Daemon restarted before the scheduled run completed",
};
updated = { ...updated, runs };
dirty = true;
}
updated = { ...updated, nextRunAt: nextRunAt.toISOString() };
dirty = true;
}
if (dirty) {
updated = { ...updated, updatedAt: now.toISOString() };
await this.store.put(updated);
}
}
// Advance stale nextRunAt for active schedules
if (
updated.status === "active" &&
updated.nextRunAt &&
new Date(updated.nextRunAt).getTime() <= now.getTime()
) {
let nextRunAt = computeNextRunAt(updated.cadence, new Date(updated.nextRunAt));
while (nextRunAt.getTime() <= now.getTime()) {
nextRunAt = computeNextRunAt(updated.cadence, nextRunAt);
}
updated = { ...updated, nextRunAt: nextRunAt.toISOString() };
dirty = true;
}
if (dirty) {
updated = { ...updated, updatedAt: now.toISOString() };
await this.store.put(updated);
}
}),
);
}
private async runSchedule(schedule: StoredSchedule, now: Date): Promise<void> {