feat: sweep My Tasks (Recently Assigned) for already-done tasks

This commit is contained in:
Azreen Jamal
2026-03-06 16:51:01 +08:00
parent 9c3e1e3a24
commit 91b8ceeac9
3 changed files with 69 additions and 0 deletions
+17
View File
@@ -110,6 +110,23 @@ export class AsanaClient {
});
}
// Get the authenticated user's GID
async getMe(): Promise<{ gid: string; name: string }> {
return this.request("/users/me?opt_fields=name");
}
// Get user task list for a workspace
async getUserTaskList(userGid: string, workspaceGid: string): Promise<{ gid: string; name: string }> {
return this.request(`/users/${userGid}/user_task_list?workspace=${workspaceGid}&opt_fields=name`);
}
// Get tasks from user task list (My Tasks / Recently Assigned)
async getUserTasks(userTaskListGid: string): Promise<AsanaTask[]> {
return this.request(
`/user_task_lists/${userTaskListGid}/tasks?opt_fields=name,notes,completed,assignee.name,tags.name,custom_fields,due_on,created_at,modified_at,memberships.project.name,memberships.section.name`
);
}
// Helper: Find section by name
async findSection(projectGid: string, sectionName: string): Promise<AsanaSection | null> {
const sections = await this.getSections(projectGid);
+51
View File
@@ -19,6 +19,8 @@ export class TaskLoop {
private lock: FileLock;
private running = false;
private timer: ReturnType<typeof setInterval> | null = null;
private userTaskListGid: string | null = null;
private workspaceGid: string = "";
constructor(config: Config) {
this.config = config;
@@ -75,6 +77,20 @@ export class TaskLoop {
this.config.asanaProjectGid = projects[0].gid;
logger.info("task-loop", `Auto-selected project: ${projects[0].name} (${projects[0].gid})`);
}
this.workspaceGid = ws.gid;
}
// Cache user task list for "My Tasks" / "Recently Assigned" sweep
try {
const me = await this.asana.getMe();
if (this.workspaceGid) {
const utl = await this.asana.getUserTaskList(me.gid, this.workspaceGid);
this.userTaskListGid = utl.gid;
logger.info("task-loop", `Cached user task list: ${utl.name} (${utl.gid})`);
}
} catch (error: any) {
logger.warn("task-loop", `Could not get user task list: ${error.message}`);
}
} catch (error: any) {
logger.error("task-loop", "Failed to discover Asana project", { error: error.message });
@@ -303,6 +319,41 @@ EVIDENCE: one line why`;
logger.debug("task-loop", `Sweep: "${task.name}" not yet done`, { evidence: check.evidence });
}
}
// Also sweep user's "My Tasks" (Recently Assigned)
if (this.userTaskListGid) {
try {
const myTasks = await this.asana.getUserTasks(this.userTaskListGid);
const incompleteMyTasks = myTasks.filter((t) => !t.completed);
if (incompleteMyTasks.length > 0) {
logger.info("task-loop", `Sweeping ${incompleteMyTasks.length} tasks from My Tasks (Recently Assigned)`);
for (const task of incompleteMyTasks) {
// Skip if already checked in project sweep
if (seen.has(task.gid)) continue;
const check = await this.verifyTaskAlreadyDone(task);
if (check.done) {
logger.info("task-loop", `My Tasks sweep: "${task.name}" is already done`, { evidence: check.evidence });
await this.asana.addComment(
task.gid,
`✅ Pi Worker sweep: This task is **already completed**.\n\nEvidence:\n${check.evidence}`
);
await this.asana.updateTask(task.gid, { completed: true });
logger.info("task-loop", `My Tasks sweep: Marked done: ${task.name}`);
} else {
logger.debug("task-loop", `My Tasks sweep: "${task.name}" not yet done`);
}
}
}
} catch (error: any) {
logger.warn("task-loop", `My Tasks sweep error: ${error.message}`);
}
}
} catch (error: any) {
logger.error("task-loop", `Sweep error: ${error.message}`);
}
+1
View File
@@ -0,0 +1 @@
❌ Poll error: Network request for 'getUpdates' failed!