Skip to content

Commit 58689b6

Browse files
committed
perf: reduce task index rebuilds
Change-Id: If1559cc6776d7d5668d11f23ca2acb93e984ffe1 Signed-off-by: Thomas Kosiewski <tk@coder.com>
1 parent 5f40aa3 commit 58689b6

File tree

2 files changed

+52
-15
lines changed

2 files changed

+52
-15
lines changed

src/node/services/taskService.ts

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ type AgentTaskWorkspaceEntry = WorkspaceConfigEntry & { projectPath: string };
7373
const COMPLETED_REPORT_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
7474
const COMPLETED_REPORT_CACHE_MAX_ENTRIES = 128;
7575

76+
interface AgentTaskIndex {
77+
byId: Map<string, AgentTaskWorkspaceEntry>;
78+
childrenByParent: Map<string, string[]>;
79+
parentById: Map<string, string>;
80+
}
81+
7682
interface PendingTaskWaiter {
7783
createdAt: number;
7884
resolve: (report: { reportMarkdown: string; title?: string }) => void;
@@ -582,16 +588,19 @@ export class TaskService {
582588
return Err("Task not found");
583589
}
584590

585-
if (!this.isDescendantAgentTask(ancestorWorkspaceId, taskId)) {
591+
const index = this.buildAgentTaskIndex(cfg);
592+
if (
593+
!this.isDescendantAgentTaskUsingParentById(index.parentById, ancestorWorkspaceId, taskId)
594+
) {
586595
return Err("Task is not a descendant of this workspace");
587596
}
588597

589598
// Terminate the entire subtree to avoid orphaned descendant tasks.
590-
const descendants = this.listDescendantAgentTaskIds(cfg, taskId);
599+
const descendants = this.listDescendantAgentTaskIdsFromIndex(index, taskId);
591600
const toTerminate = Array.from(new Set([taskId, ...descendants]));
592601

593602
// Delete leaves first to avoid leaving children with missing parents.
594-
const parentById = this.buildAgentTaskIndex(cfg).parentById;
603+
const parentById = index.parentById;
595604
const depthById = new Map<string, number>();
596605
for (const id of toTerminate) {
597606
depthById.set(id, this.getTaskDepthFromParentById(parentById, id));
@@ -918,13 +927,35 @@ export class TaskService {
918927
return result;
919928
}
920929

921-
private listDescendantAgentTaskIds(
922-
config: ReturnType<Config["loadConfigOrDefault"]>,
930+
filterDescendantAgentTaskIds(ancestorWorkspaceId: string, taskIds: string[]): string[] {
931+
assert(
932+
ancestorWorkspaceId.length > 0,
933+
"filterDescendantAgentTaskIds: ancestorWorkspaceId required"
934+
);
935+
assert(Array.isArray(taskIds), "filterDescendantAgentTaskIds: taskIds must be an array");
936+
937+
const cfg = this.config.loadConfigOrDefault();
938+
const parentById = this.buildAgentTaskIndex(cfg).parentById;
939+
940+
const result: string[] = [];
941+
for (const taskId of taskIds) {
942+
if (typeof taskId !== "string" || taskId.length === 0) continue;
943+
if (this.isDescendantAgentTaskUsingParentById(parentById, ancestorWorkspaceId, taskId)) {
944+
result.push(taskId);
945+
}
946+
}
947+
948+
return result;
949+
}
950+
951+
private listDescendantAgentTaskIdsFromIndex(
952+
index: AgentTaskIndex,
923953
workspaceId: string
924954
): string[] {
925-
assert(workspaceId.length > 0, "listDescendantAgentTaskIds: workspaceId must be non-empty");
926-
927-
const index = this.buildAgentTaskIndex(config);
955+
assert(
956+
workspaceId.length > 0,
957+
"listDescendantAgentTaskIdsFromIndex: workspaceId must be non-empty"
958+
);
928959

929960
const result: string[] = [];
930961
const stack: string[] = [...(index.childrenByParent.get(workspaceId) ?? [])];
@@ -947,7 +978,14 @@ export class TaskService {
947978

948979
const cfg = this.config.loadConfigOrDefault();
949980
const parentById = this.buildAgentTaskIndex(cfg).parentById;
981+
return this.isDescendantAgentTaskUsingParentById(parentById, ancestorWorkspaceId, taskId);
982+
}
950983

984+
private isDescendantAgentTaskUsingParentById(
985+
parentById: Map<string, string>,
986+
ancestorWorkspaceId: string,
987+
taskId: string
988+
): boolean {
951989
let current = taskId;
952990
for (let i = 0; i < 32; i++) {
953991
const parent = parentById.get(current);
@@ -957,7 +995,7 @@ export class TaskService {
957995
}
958996

959997
throw new Error(
960-
`isDescendantAgentTask: possible parentWorkspaceId cycle starting at ${taskId}`
998+
`isDescendantAgentTaskUsingParentById: possible parentWorkspaceId cycle starting at ${taskId}`
961999
);
9621000
}
9631001

@@ -977,11 +1015,7 @@ export class TaskService {
9771015
return tasks;
9781016
}
9791017

980-
private buildAgentTaskIndex(config: ReturnType<Config["loadConfigOrDefault"]>): {
981-
byId: Map<string, AgentTaskWorkspaceEntry>;
982-
childrenByParent: Map<string, string[]>;
983-
parentById: Map<string, string>;
984-
} {
1018+
private buildAgentTaskIndex(config: ReturnType<Config["loadConfigOrDefault"]>): AgentTaskIndex {
9851019
const byId = new Map<string, AgentTaskWorkspaceEntry>();
9861020
const childrenByParent = new Map<string, string[]>();
9871021
const parentById = new Map<string, string>();

src/node/services/tools/task_await.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,13 @@ export const createTaskAwaitTool: ToolFactory = (config: ToolConfiguration) => {
3434
requestedIds ?? taskService.listActiveDescendantAgentTaskIds(workspaceId);
3535

3636
const uniqueTaskIds = dedupeStrings(candidateTaskIds);
37+
const descendantTaskIdSet = new Set(
38+
taskService.filterDescendantAgentTaskIds(workspaceId, uniqueTaskIds)
39+
);
3740

3841
const results = await Promise.all(
3942
uniqueTaskIds.map(async (taskId) => {
40-
if (!taskService.isDescendantAgentTask(workspaceId, taskId)) {
43+
if (!descendantTaskIdSet.has(taskId)) {
4144
return { status: "invalid_scope" as const, taskId };
4245
}
4346

0 commit comments

Comments
 (0)