worker: track work as natural task steps

This commit is contained in:
2026-08-14 14:23:58 +09:00
parent 93eea24420
commit ecbd003579
8 changed files with 171 additions and 124 deletions
@@ -53,7 +53,7 @@ Deno.test("TaskCreate and TaskUpdate mirror the active TUI task store", () => {
assert(counts.inprogress === 1, "one task should be in progress");
});
Deno.test("completed and deleted tasks remain in the TUI-style store", () => {
Deno.test("completed tasks remain visible until explicitly deleted", () => {
let state = emptyConsoleTaskState();
for (const subject of ["complete", "delete"]) {
state = applyTaskToolCall(
@@ -72,14 +72,11 @@ Deno.test("completed and deleted tasks remain in the TUI-style store", () => {
"TaskUpdate",
JSON.stringify({ taskid: 2, status: "deleted" }),
);
assert(
state.tasks.length === 2,
"the full TaskStore should retain inactive tasks",
);
assert(state.tasks.length === 1, "only deleted tasks should be forgotten");
const counts = taskCounts(state.tasks);
assert(counts.completed === 1, "completed tasks should remain counted");
assert(counts.deleted === 1, "deleted tasks should remain counted");
assert(counts.active === 0, "inactive tasks should not be shown as active");
assert(counts.deleted === 0, "deleted tasks should be forgotten");
assert(counts.active === 0, "completed tasks should not be shown as active");
});
Deno.test("session TaskStore snapshot replaces stale state and advances ids", () => {
@@ -102,13 +99,13 @@ TaskStore: 1 active task(s) (pending: 0, inprogress: 1)
\`\`\`
`;
state = applyTaskSnapshotText(state, snapshot);
assert(state.tasks.length === 2, "snapshot should restore retained tasks");
assert(state.tasks[0].taskid === 4, "the completed task should be restored");
assert(state.tasks[1].taskid === 7, "the active task should be restored");
assert(
state.tasks.length === 2,
"snapshot should restore the full TaskStore",
state.nextTaskId === 8,
"next id should follow the highest retained task id",
);
assert(state.tasks[0].taskid === 4, "completed tasks should remain restored");
assert(state.tasks[1].taskid === 7, "active tasks should remain restored");
assert(state.nextTaskId === 8, "next id should follow the highest task id");
state = applyTaskToolCall(
state,
"TaskCreate",
@@ -62,6 +62,13 @@ export function applyTaskToolCall(
const current = state.tasks[index];
const status = update.status ?? current.status;
if (status === "deleted") {
return {
tasks: state.tasks.filter((_, taskIndex) => taskIndex !== index),
nextTaskId: state.nextTaskId,
};
}
const tasks = [...state.tasks];
tasks[index] = {
taskid: current.taskid,
@@ -78,9 +85,10 @@ export function applyTaskSnapshotText(
): ConsoleTaskState {
const tasks = parseTaskSnapshotText(text);
if (!tasks) return state;
const retained = tasks.filter((task) => task.status !== "deleted");
return {
tasks,
nextTaskId: Math.max(1, ...tasks.map((task) => task.taskid + 1)),
tasks: retained,
nextTaskId: Math.max(1, ...retained.map((task) => task.taskid + 1)),
};
}
@@ -105,7 +113,9 @@ export function parseTaskSnapshotText(text: string): ConsoleTask[] | null {
for (const candidate of value.tasks) {
const task = taskEntry(candidate);
if (!task) return null;
tasks.push(task);
if (task.status !== "deleted") {
tasks.push(task);
}
}
return tasks;
}