( agentDir: string, )
| 49 | * task shape — matching agent-core's tolerant `listTasks`. |
| 50 | */ |
| 51 | export async function listBackgroundTasks( |
| 52 | agentDir: string, |
| 53 | ): Promise<BackgroundTaskInfo[]> { |
| 54 | const dir = tasksDirOf(agentDir); |
| 55 | let entries: import('node:fs').Dirent[]; |
| 56 | try { |
| 57 | entries = await readdir(dir, { withFileTypes: true }); |
| 58 | } catch { |
| 59 | return []; |
| 60 | } |
| 61 | const out: BackgroundTaskInfo[] = []; |
| 62 | for (const entry of entries) { |
| 63 | if (!entry.isFile() || !entry.name.endsWith('.json')) continue; |
| 64 | const id = entry.name.slice(0, -'.json'.length); |
| 65 | if (!VALID_TASK_ID.test(id)) continue; |
| 66 | let parsed: unknown; |
| 67 | try { |
| 68 | parsed = JSON.parse(await readFile(join(dir, entry.name), 'utf8')); |
| 69 | } catch { |
| 70 | continue; |
| 71 | } |
| 72 | if (!isReadablePersistedTask(parsed)) continue; |
| 73 | try { |
| 74 | out.push(normalizePersistedTask(parsed)); |
| 75 | } catch { |
| 76 | // A record can pass the shape guard but still hold type-corrupt fields |
| 77 | // (e.g. a legacy `stop_reason` that is a number). Honour the |
| 78 | // silently-skips contract instead of failing the whole listing. |
| 79 | continue; |
| 80 | } |
| 81 | } |
| 82 | // Newest first; tasks with no start time sort last. |
| 83 | out.sort((a, b) => (b.startedAt ?? 0) - (a.startedAt ?? 0)); |
| 84 | return out; |
| 85 | } |
| 86 | |
| 87 | /** Byte size of a task's `output.log` (0 when absent or unreadable). */ |
| 88 | export async function taskOutputSizeBytes( |
no test coverage detected