diff --git a/apps/server/src/db.ts b/apps/server/src/db.ts index 290753221..f2f03b08d 100644 --- a/apps/server/src/db.ts +++ b/apps/server/src/db.ts @@ -30,6 +30,17 @@ export class Store { ); return result.rows.map((row) => row.data as T); } + async listByGoalId>( + owner: string, + kind: string, + goalId: string, + ): Promise { + const result = await this.db.query( + "SELECT data FROM records WHERE owner=$1 AND kind=$2 AND data->>'goalId'=$3 ORDER BY updated_at DESC,id", + [owner, kind, goalId], + ); + return result.rows.map((row) => row.data as T); + } async put(owner: string, kind: string, value: T): Promise { await this.db.query( "INSERT INTO records(owner,kind,id,data) VALUES($1,$2,$3,$4::jsonb) ON CONFLICT(owner,kind,id) DO UPDATE SET data=excluded.data,updated_at=now()", diff --git a/apps/server/src/engine/service.ts b/apps/server/src/engine/service.ts index c7b94c985..a0a36d021 100644 --- a/apps/server/src/engine/service.ts +++ b/apps/server/src/engine/service.ts @@ -356,8 +356,8 @@ export class AgentService { if (!goal) throw new AppError("Goal not found", 404); const saved = await this.db.put(owner, "goals", { ...goal, ...patch }); if (patch.status === "paused") - for (const task of await this.db.list(owner, "tasks")) - if (task.goalId === id && !terminal.has(task.status) && task.status !== "paused") + for (const task of await this.db.listByGoalId(owner, "tasks", id)) + if (!terminal.has(task.status) && task.status !== "paused") await this.control(owner, task.id, "pause"); return saved; } diff --git a/tests/persistence.test.ts b/tests/persistence.test.ts index 88f10d5b2..d23e9af3c 100644 --- a/tests/persistence.test.ts +++ b/tests/persistence.test.ts @@ -82,3 +82,30 @@ test("workspace section reads match the selected snapshot sections", async () => await store.close(); } }); + +test("listByGoalId matches list plus goalId filtering", async () => { + const store = await createStore(); + try { + await store.put("owner", "tasks", { id: "t0", goalId: "g1" }); + await store.put("owner", "tasks", { id: "t1", goalId: "g2" }); + await store.put("owner", "tasks", { id: "t2", goalId: "g1" }); + await store.put("owner", "tasks", { id: "t-none" }); + + const scoped = await store.listByGoalId<{ id: string; goalId?: string }>( + "owner", + "tasks", + "g1", + ); + const expected = (await store.list<{ id: string; goalId?: string }>("owner", "tasks")).filter( + (item) => item.goalId === "g1", + ); + assert.deepEqual(scoped, expected); + + await store.put("other", "tasks", { id: "x", goalId: "g1" }); + await store.put("owner", "goals", { id: "g", goalId: "g1" }); + assert.equal((await store.listByGoalId("owner", "tasks", "g1")).length, 2); + assert.equal((await store.listByGoalId("nobody", "tasks", "g1")).length, 0); + } finally { + await store.close(); + } +});