(home: string = KIMI_CODE_HOME)
| 17 | const MAX_OUTPUT_LIMIT = 4 * 1024 * 1024; |
| 18 | |
| 19 | export function tasksRoute(home: string = KIMI_CODE_HOME): Hono { |
| 20 | const r = new Hono(); |
| 21 | |
| 22 | // List background tasks (process / agent / question) for a session. Tasks are |
| 23 | // persisted under each spawning agent's homedir (`<homedir>/tasks`), NOT the |
| 24 | // session root, so aggregate across every agent in the session. |
| 25 | r.get('/:id/tasks', async (c) => { |
| 26 | const id = c.req.param('id'); |
| 27 | const detail = await readSessionDetail(home, id); |
| 28 | if (!detail) { |
| 29 | return c.json({ error: 'session not found', code: 'NOT_FOUND' }, 404); |
| 30 | } |
| 31 | const entries: BackgroundTaskEntry[] = []; |
| 32 | for (const agent of detail.agents) { |
| 33 | const tasks = await listBackgroundTasks(agent.homedir); |
| 34 | for (const task of tasks) { |
| 35 | const outputSizeBytes = await taskOutputSizeBytes(agent.homedir, task.taskId); |
| 36 | entries.push({ task, agentId: agent.agentId, outputSizeBytes, outputExists: outputSizeBytes > 0 }); |
| 37 | } |
| 38 | } |
| 39 | // Newest first across all agents. |
| 40 | entries.sort((a, b) => (b.task.startedAt ?? 0) - (a.task.startedAt ?? 0)); |
| 41 | return c.json({ sessionId: id, tasks: entries }); |
| 42 | }); |
| 43 | |
| 44 | // Read a byte-window of a single task's output.log. The task may belong to |
| 45 | // any agent, so locate the agent whose tasks/ directory holds it. |
| 46 | r.get('/:id/tasks/:taskId/output', async (c) => { |
| 47 | const id = c.req.param('id'); |
| 48 | const taskId = c.req.param('taskId'); |
| 49 | if (!isSafeTaskId(taskId)) { |
| 50 | return c.json({ error: 'invalid task id', code: 'BAD_REQUEST' }, 400); |
| 51 | } |
| 52 | const offset = parseNonNegativeInt(c.req.query('offset'), 0); |
| 53 | const limit = Math.min( |
| 54 | parseNonNegativeInt(c.req.query('limit'), DEFAULT_OUTPUT_LIMIT), |
| 55 | MAX_OUTPUT_LIMIT, |
| 56 | ); |
| 57 | const detail = await readSessionDetail(home, id); |
| 58 | if (!detail) { |
| 59 | return c.json({ error: 'session not found', code: 'NOT_FOUND' }, 404); |
| 60 | } |
| 61 | // Prefer the agent whose log actually has bytes; otherwise any agent's dir |
| 62 | // yields the same empty window. An explicit ?agent= short-circuits the scan. |
| 63 | const hinted = c.req.query('agent'); |
| 64 | let dir = detail.agents.find((a) => a.agentId === hinted)?.homedir ?? detail.agents[0]?.homedir ?? detail.sessionDir; |
| 65 | for (const agent of detail.agents) { |
| 66 | if ((await taskOutputSizeBytes(agent.homedir, taskId)) > 0) { |
| 67 | dir = agent.homedir; |
| 68 | break; |
| 69 | } |
| 70 | } |
| 71 | const window = await readTaskOutput(dir, taskId, offset, limit); |
| 72 | return c.json({ |
| 73 | sessionId: id, |
| 74 | taskId, |
| 75 | offset: window.offset, |
| 76 | nextOffset: window.nextOffset, |
no test coverage detected