diff --git a/src/main/database/documents.ts b/src/main/database/documents.ts index abaa1cbb..fe9907db 100644 --- a/src/main/database/documents.ts +++ b/src/main/database/documents.ts @@ -353,11 +353,17 @@ export function buildDocumentMethods( */ resolveWorkProductTarget(workProductId: string, companyId: string): { kind: "url" | "path"; value: string } | null { const row = getDb().prepare( - "SELECT provider, external_id, url, task_id, created_by_run_id, last_touched_by_run_id, workspace_id FROM work_products WHERE id = ? AND company_id = ?", + "SELECT provider, external_id, url, status, task_id, created_by_run_id, last_touched_by_run_id, workspace_id FROM work_products WHERE id = ? AND company_id = ?", ).get(workProductId, companyId) as - | { provider: string; external_id: string | null; url: string | null; task_id: string | null; created_by_run_id: string | null; last_touched_by_run_id: string | null; workspace_id: string | null } + | { provider: string; external_id: string | null; url: string | null; status: string; task_id: string | null; created_by_run_id: string | null; last_touched_by_run_id: string | null; workspace_id: string | null } | undefined; if (!row) return null; + // Archived rows are retirement tombstones — the file no longer + // exists (or was explicitly retired) and the row is kept for + // audit only. Refuse to resolve a target so `openWorkProduct` + // can fail cleanly instead of pointing the shell at a path that + // leads nowhere. + if (row.status === "archived") return null; if (row.url && row.url.length > 0) { return { kind: "url", value: row.url }; @@ -378,6 +384,10 @@ export function buildDocumentMethods( return { kind: "path", value: absolute }; }, + // Task-scoped visible listing. Archived rows stay in the table as + // retirement tombstones; the task API and the thread view only + // surface currently-active deliverables so a deleted file stops + // appearing in the task's artifact strip. listWorkProductsForTask(taskId: string, companyId: string) { return readAll<{ id: string; company_id: string; project_id: string | null; task_id: string; @@ -388,7 +398,7 @@ export function buildDocumentMethods( created_by_run_id: string | null; last_touched_by_run_id: string | null; created_at: string; updated_at: string; }>( - "SELECT * FROM work_products WHERE task_id = ? AND company_id = ? ORDER BY is_primary DESC, updated_at DESC", + "SELECT * FROM work_products WHERE task_id = ? AND company_id = ? AND status <> 'archived' ORDER BY is_primary DESC, updated_at DESC", taskId, companyId, ).map((r) => ({ id: r.id, companyId: r.company_id, projectId: r.project_id, taskId: r.task_id, @@ -405,10 +415,10 @@ export function buildDocumentMethods( /** * Company-scoped work-product listing, newest first. Used by the * renderer snapshot so the DocumentCenter can surface filesystem - * artifacts (type = "file") the agents produce via bash/write/edit - * — these are the deliverables operators expect to see alongside - * prose documents, so the page stops looking empty when every - * artifact of the night lives in `work_products`. + * artifacts (type = "file") the agents produce via bash/write/edit. + * Archived rows are excluded so retired files stop showing in the + * operator UI; callers that need the full audit trail go through + * `listArchivedWorkProductsForCompany`. */ listWorkProductsForCompany(companyId: string, limit = 500) { return readAll<{ @@ -420,7 +430,7 @@ export function buildDocumentMethods( created_by_run_id: string | null; last_touched_by_run_id: string | null; created_at: string; updated_at: string; }>( - "SELECT * FROM work_products WHERE company_id = ? ORDER BY updated_at DESC LIMIT ?", + "SELECT * FROM work_products WHERE company_id = ? AND status <> 'archived' ORDER BY updated_at DESC LIMIT ?", companyId, limit, ).map((r) => ({ id: r.id, companyId: r.company_id, projectId: r.project_id, taskId: r.task_id, diff --git a/src/main/database/snapshot.ts b/src/main/database/snapshot.ts index 6c3d14c4..e088dbb7 100644 --- a/src/main/database/snapshot.ts +++ b/src/main/database/snapshot.ts @@ -129,8 +129,12 @@ export function buildSnapshotMethods( goalId: r.goal_id, parentDocId: r.parent_doc_id, version: r.version, status: r.status, tagsJson: r.tags_json, latestRevisionId: r.latest_revision_id ?? null, createdAt: r.created_at, updatedAt: r.updated_at, })), + // Archived rows stay in the table as tombstones for audit but + // never surface as deliverables — the DocumentCenter and every + // downstream consumer read this snapshot, so the filter is + // pinned here as the canonical visibility gate. workProducts: readAll( - `select id, company_id, project_id, task_id, workspace_id, type, provider, external_id, title, url, status, review_state, is_primary, summary, metadata_json, created_by_run_id, last_touched_by_run_id, created_at, updated_at from work_products${companyWhere} order by updated_at desc limit 500`, + `select id, company_id, project_id, task_id, workspace_id, type, provider, external_id, title, url, status, review_state, is_primary, summary, metadata_json, created_by_run_id, last_touched_by_run_id, created_at, updated_at from work_products${companyWhere}${companyWhere ? " and" : " where"} status <> 'archived' order by updated_at desc limit 500`, ...companyArgs, ).map((r) => ({ id: r.id, companyId: r.company_id, projectId: r.project_id, taskId: r.task_id,