mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
chore(lint): parallelize safe await-in-loop in server
This commit is contained in:
@@ -163,16 +163,21 @@ export class AgentStorage {
|
||||
this.beginDelete(agentId);
|
||||
await (this.pendingWrites.get(agentId) ?? Promise.resolve());
|
||||
const paths = Array.from(this.pathsById.get(agentId) ?? []);
|
||||
for (const filePath of paths) {
|
||||
try {
|
||||
await fs.unlink(filePath);
|
||||
} catch (error) {
|
||||
const code = (error as NodeJS.ErrnoException).code;
|
||||
if (code && code !== "ENOENT") {
|
||||
this.logger.warn({ err: error, agentId, filePath }, "Failed to remove agent record file");
|
||||
await Promise.all(
|
||||
paths.map(async (filePath) => {
|
||||
try {
|
||||
await fs.unlink(filePath);
|
||||
} catch (error) {
|
||||
const code = (error as NodeJS.ErrnoException).code;
|
||||
if (code && code !== "ENOENT") {
|
||||
this.logger.warn(
|
||||
{ err: error, agentId, filePath },
|
||||
"Failed to remove agent record file",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
this.cache.delete(agentId);
|
||||
this.pathById.delete(agentId);
|
||||
|
||||
@@ -858,11 +858,13 @@ export async function createPaseoDaemon(
|
||||
|
||||
async function closeAllAgents(logger: Logger, agentManager: AgentManager): Promise<void> {
|
||||
const agents = agentManager.listAgents();
|
||||
for (const agent of agents) {
|
||||
try {
|
||||
await agentManager.closeAgent(agent.id);
|
||||
} catch (err) {
|
||||
logger.error({ err, agentId: agent.id }, "Failed to close agent");
|
||||
}
|
||||
}
|
||||
await Promise.all(
|
||||
agents.map(async (agent) => {
|
||||
try {
|
||||
await agentManager.closeAgent(agent.id);
|
||||
} catch (err) {
|
||||
logger.error({ err, agentId: agent.id }, "Failed to close agent");
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,15 +72,16 @@ export async function listAvailableEditorTargets(
|
||||
const platform = dependencies.platform ?? process.platform;
|
||||
const findExecutableFn = dependencies.findExecutable ?? findExecutable;
|
||||
|
||||
const supportedTargets = EDITOR_TARGETS.filter((target) =>
|
||||
isTargetSupportedOnPlatform(target, platform),
|
||||
);
|
||||
const executables = await Promise.all(
|
||||
supportedTargets.map((target) => findExecutableFn(target.command)),
|
||||
);
|
||||
const results: EditorTargetDescriptorPayload[] = [];
|
||||
for (const target of EDITOR_TARGETS) {
|
||||
if (!isTargetSupportedOnPlatform(target, platform)) {
|
||||
continue;
|
||||
}
|
||||
const executable = await findExecutableFn(target.command);
|
||||
if (!executable) {
|
||||
continue;
|
||||
}
|
||||
for (let i = 0; i < supportedTargets.length; i += 1) {
|
||||
if (!executables[i]) continue;
|
||||
const target = supportedTargets[i]!;
|
||||
results.push({
|
||||
id: target.id,
|
||||
label: target.label,
|
||||
|
||||
@@ -143,16 +143,18 @@ export async function archivePaseoWorktree(
|
||||
dependencies.github.invalidate({ cwd });
|
||||
}
|
||||
|
||||
for (const workspaceId of affectedWorkspaceIds) {
|
||||
try {
|
||||
await dependencies.archiveWorkspaceRecord(workspaceId);
|
||||
} catch (error) {
|
||||
dependencies.sessionLogger?.warn(
|
||||
{ err: error, workspaceId },
|
||||
"Failed to archive workspace record; worktree FS already removed",
|
||||
);
|
||||
}
|
||||
}
|
||||
await Promise.all(
|
||||
Array.from(affectedWorkspaceIds).map(async (workspaceId) => {
|
||||
try {
|
||||
await dependencies.archiveWorkspaceRecord(workspaceId);
|
||||
} catch (error) {
|
||||
dependencies.sessionLogger?.warn(
|
||||
{ err: error, workspaceId },
|
||||
"Failed to archive workspace record; worktree FS already removed",
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
for (const agentId of removedAgents) {
|
||||
dependencies.emit({
|
||||
@@ -179,21 +181,25 @@ export async function killTerminalsUnderPath(
|
||||
}
|
||||
|
||||
const terminalIds: string[] = [];
|
||||
const terminalDirectories = [...terminalManager.listDirectories()];
|
||||
for (const terminalCwd of terminalDirectories) {
|
||||
if (!dependencies.isPathWithinRoot(rootPath, terminalCwd)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const terminals = await terminalManager.getTerminals(terminalCwd);
|
||||
for (const terminal of terminals) {
|
||||
terminalIds.push(terminal.id);
|
||||
const relevantCwds = [...terminalManager.listDirectories()].filter((terminalCwd) =>
|
||||
dependencies.isPathWithinRoot(rootPath, terminalCwd),
|
||||
);
|
||||
const terminalLists = await Promise.all(
|
||||
relevantCwds.map(async (terminalCwd) => {
|
||||
try {
|
||||
return await terminalManager.getTerminals(terminalCwd);
|
||||
} catch (error) {
|
||||
dependencies.sessionLogger.warn(
|
||||
{ err: error, cwd: terminalCwd },
|
||||
"Failed to enumerate worktree terminals during archive",
|
||||
);
|
||||
return [];
|
||||
}
|
||||
} catch (error) {
|
||||
dependencies.sessionLogger.warn(
|
||||
{ err: error, cwd: terminalCwd },
|
||||
"Failed to enumerate worktree terminals during archive",
|
||||
);
|
||||
}),
|
||||
);
|
||||
for (const terminals of terminalLists) {
|
||||
for (const terminal of terminals) {
|
||||
terminalIds.push(terminal.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,9 +57,7 @@ export class PushService {
|
||||
batches.push(messages.slice(i, i + MAX_BATCH_SIZE));
|
||||
}
|
||||
|
||||
for (const batch of batches) {
|
||||
await this.sendBatch(batch);
|
||||
}
|
||||
await Promise.all(batches.map((batch) => this.sendBatch(batch)));
|
||||
}
|
||||
|
||||
private async sendBatch(messages: ExpoPushMessage[]): Promise<void> {
|
||||
|
||||
@@ -98,13 +98,15 @@ export class ScriptHealthMonitor {
|
||||
const changedWorkspaceIds = new Set<string>();
|
||||
const now = Date.now();
|
||||
|
||||
for (const route of routes) {
|
||||
const state = this.getOrCreateState(route, now);
|
||||
if (now - state.registeredAt < this.graceMs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isHealthy = await this.probeRoute(route.port);
|
||||
const probeTargets = routes
|
||||
.map((route) => ({ route, state: this.getOrCreateState(route, now) }))
|
||||
.filter(({ state }) => now - state.registeredAt >= this.graceMs);
|
||||
const healthResults = await Promise.all(
|
||||
probeTargets.map(({ route }) => this.probeRoute(route.port)),
|
||||
);
|
||||
for (let i = 0; i < probeTargets.length; i += 1) {
|
||||
const { route, state } = probeTargets[i]!;
|
||||
const isHealthy = healthResults[i]!;
|
||||
const previousHealth = state.health;
|
||||
|
||||
if (isHealthy) {
|
||||
|
||||
@@ -150,13 +150,18 @@ export class WorkspaceReconciliationService {
|
||||
}
|
||||
|
||||
// 3. Reconcile git metadata for active projects whose directories still exist
|
||||
for (const project of activeProjects) {
|
||||
if (project.archivedAt) continue;
|
||||
const projectsToReconcile = activeProjects.filter((project) => {
|
||||
if (project.archivedAt) return false;
|
||||
const siblings = workspacesByProject.get(project.projectId) ?? [];
|
||||
if (siblings.length === 0) continue;
|
||||
if (!existsSync(project.rootPath)) continue;
|
||||
await this.reconcileProject(project, siblings, changes);
|
||||
}
|
||||
if (siblings.length === 0) return false;
|
||||
if (!existsSync(project.rootPath)) return false;
|
||||
return true;
|
||||
});
|
||||
await Promise.all(
|
||||
projectsToReconcile.map((project) =>
|
||||
this.reconcileProject(project, workspacesByProject.get(project.projectId) ?? [], changes),
|
||||
),
|
||||
);
|
||||
|
||||
if (changes.length > 0 && this.onChanges) {
|
||||
this.onChanges(changes);
|
||||
@@ -208,27 +213,28 @@ export class WorkspaceReconciliationService {
|
||||
});
|
||||
}
|
||||
|
||||
for (const workspace of siblings) {
|
||||
if (!existsSync(workspace.cwd)) continue;
|
||||
const existingSiblings = siblings.filter((workspace) => existsSync(workspace.cwd));
|
||||
await Promise.all(
|
||||
existingSiblings.map(async (workspace) => {
|
||||
const wsDirName = workspace.cwd.split(/[\\/]/).filter(Boolean).at(-1) ?? workspace.cwd;
|
||||
const wsGit = await this.readWorkspaceGitMetadata(workspace.cwd, wsDirName);
|
||||
|
||||
const wsDirName = workspace.cwd.split(/[\\/]/).filter(Boolean).at(-1) ?? workspace.cwd;
|
||||
const wsGit = await this.readWorkspaceGitMetadata(workspace.cwd, wsDirName);
|
||||
|
||||
if (wsGit.projectKind === "git" && workspace.displayName !== wsGit.workspaceDisplayName) {
|
||||
const timestamp = new Date().toISOString();
|
||||
await this.workspaceRegistry.upsert({
|
||||
...workspace,
|
||||
displayName: wsGit.workspaceDisplayName,
|
||||
updatedAt: timestamp,
|
||||
});
|
||||
changes.push({
|
||||
kind: "workspace_updated",
|
||||
workspaceId: workspace.workspaceId,
|
||||
directory: workspace.cwd,
|
||||
fields: { displayName: wsGit.workspaceDisplayName },
|
||||
});
|
||||
}
|
||||
}
|
||||
if (wsGit.projectKind === "git" && workspace.displayName !== wsGit.workspaceDisplayName) {
|
||||
const timestamp = new Date().toISOString();
|
||||
await this.workspaceRegistry.upsert({
|
||||
...workspace,
|
||||
displayName: wsGit.workspaceDisplayName,
|
||||
updatedAt: timestamp,
|
||||
});
|
||||
changes.push({
|
||||
kind: "workspace_updated",
|
||||
workspaceId: workspace.workspaceId,
|
||||
directory: workspace.cwd,
|
||||
fields: { displayName: wsGit.workspaceDisplayName },
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async readWorkspaceGitMetadata(cwd: string, directoryName: string) {
|
||||
|
||||
@@ -77,13 +77,18 @@ export async function bootstrapWorkspaceRegistries(options: {
|
||||
records: StoredAgentRecord[];
|
||||
}
|
||||
>();
|
||||
for (const record of activeRecords) {
|
||||
const normalizedCwd = normalizeWorkspaceId(record.cwd);
|
||||
const placement = await buildProjectPlacementForCwd({
|
||||
cwd: normalizedCwd,
|
||||
workspaceGitService: options.workspaceGitService,
|
||||
});
|
||||
const workspaceId = deriveWorkspaceId(normalizedCwd, placement.checkout);
|
||||
const placements = await Promise.all(
|
||||
activeRecords.map(async (record) => {
|
||||
const normalizedCwd = normalizeWorkspaceId(record.cwd);
|
||||
const placement = await buildProjectPlacementForCwd({
|
||||
cwd: normalizedCwd,
|
||||
workspaceGitService: options.workspaceGitService,
|
||||
});
|
||||
const workspaceId = deriveWorkspaceId(normalizedCwd, placement.checkout);
|
||||
return { record, placement, workspaceId };
|
||||
}),
|
||||
);
|
||||
for (const { record, placement, workspaceId } of placements) {
|
||||
const existing = recordsByWorkspaceId.get(workspaceId) ?? { placement, records: [] };
|
||||
existing.records.push(record);
|
||||
recordsByWorkspaceId.set(workspaceId, existing);
|
||||
|
||||
@@ -188,9 +188,14 @@ export async function detectStaleWorkspaces(
|
||||
): Promise<Set<string>> {
|
||||
const staleWorkspaceIds = new Set<string>();
|
||||
|
||||
for (const workspace of input.activeWorkspaces) {
|
||||
const dirExists = await input.checkDirectoryExists(workspace.cwd);
|
||||
if (!dirExists) {
|
||||
const existenceChecks = await Promise.all(
|
||||
input.activeWorkspaces.map(async (workspace) => ({
|
||||
workspace,
|
||||
exists: await input.checkDirectoryExists(workspace.cwd),
|
||||
})),
|
||||
);
|
||||
for (const { workspace, exists } of existenceChecks) {
|
||||
if (!exists) {
|
||||
staleWorkspaceIds.add(workspace.workspaceId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,16 +181,9 @@ export class FileTaskStore implements TaskStore {
|
||||
await this.ensureDir();
|
||||
try {
|
||||
const files = await readdir(this.dir);
|
||||
const tasks: Task[] = [];
|
||||
for (const file of files) {
|
||||
if (file.endsWith(".md")) {
|
||||
const id = file.slice(0, -3);
|
||||
const task = await this.readTask(id);
|
||||
if (task) {
|
||||
tasks.push(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
const ids = files.filter((file) => file.endsWith(".md")).map((file) => file.slice(0, -3));
|
||||
const loaded = await Promise.all(ids.map((id) => this.readTask(id)));
|
||||
const tasks: Task[] = loaded.filter((task): task is Task => task !== null);
|
||||
// Sort by created date (oldest first) for consistent ordering
|
||||
return tasks.sort((a, b) => a.created.localeCompare(b.created));
|
||||
} catch (error) {
|
||||
|
||||
@@ -764,30 +764,26 @@ async function listWorkspaceChildEntries(input: {
|
||||
const dirents = await readdir(input.directory, { withFileTypes: true }).catch(
|
||||
() => [] as Dirent[],
|
||||
);
|
||||
const entries: ChildWorkspaceEntry[] = [];
|
||||
for (const dirent of dirents) {
|
||||
if (isHiddenDirectoryName(dirent.name)) {
|
||||
continue;
|
||||
}
|
||||
if (isIgnoredWorkspaceDirectoryName(dirent.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const candidatePath = path.join(input.directory, dirent.name);
|
||||
const entry = await resolveWorkspaceCandidate({
|
||||
candidatePath,
|
||||
dirent,
|
||||
workspaceRoot: input.workspaceRoot,
|
||||
});
|
||||
if (!entry) {
|
||||
continue;
|
||||
}
|
||||
entries.push({
|
||||
name: dirent.name,
|
||||
absolutePath: entry.absolutePath,
|
||||
kind: entry.kind,
|
||||
});
|
||||
}
|
||||
const candidates = dirents.filter(
|
||||
(dirent) =>
|
||||
!isHiddenDirectoryName(dirent.name) && !isIgnoredWorkspaceDirectoryName(dirent.name),
|
||||
);
|
||||
const resolved = await Promise.all(
|
||||
candidates.map(async (dirent) => {
|
||||
const candidatePath = path.join(input.directory, dirent.name);
|
||||
const entry = await resolveWorkspaceCandidate({
|
||||
candidatePath,
|
||||
dirent,
|
||||
workspaceRoot: input.workspaceRoot,
|
||||
});
|
||||
return entry
|
||||
? { name: dirent.name, absolutePath: entry.absolutePath, kind: entry.kind }
|
||||
: null;
|
||||
}),
|
||||
);
|
||||
const entries: ChildWorkspaceEntry[] = resolved.filter(
|
||||
(entry): entry is ChildWorkspaceEntry => entry !== null,
|
||||
);
|
||||
|
||||
setWorkspaceEntryListCache(input.directory, {
|
||||
expiresAt: now + DIRECTORY_LIST_CACHE_TTL_MS,
|
||||
|
||||
Reference in New Issue
Block a user