diff --git a/pi-worker/src/asana/client.ts b/pi-worker/src/asana/client.ts index 15179dc..ee8e1a3 100644 --- a/pi-worker/src/asana/client.ts +++ b/pi-worker/src/asana/client.ts @@ -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 { + 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 { const sections = await this.getSections(projectGid); diff --git a/pi-worker/src/scheduler/task-loop.ts b/pi-worker/src/scheduler/task-loop.ts index 8f36bfa..32a8f35 100644 --- a/pi-worker/src/scheduler/task-loop.ts +++ b/pi-worker/src/scheduler/task-loop.ts @@ -19,6 +19,8 @@ export class TaskLoop { private lock: FileLock; private running = false; private timer: ReturnType | 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}`); } diff --git a/telegram-bot/bot.err b/telegram-bot/bot.err index e69de29..187fb0e 100644 --- a/telegram-bot/bot.err +++ b/telegram-bot/bot.err @@ -0,0 +1 @@ +❌ Poll error: Network request for 'getUpdates' failed!