feat(documents): surface filesystem work-products in Document Center

The Document Center only read the `documents` table, which is prose
content POSTed via /api/documents. Everything the agents wrote via
`bash` / `write` / `edit` — the bulk of their output on a real
project — landed in `work_products` and never appeared on the page.
Operators saw "0 items" after a full night's run.

Lift work_products into the same UI:

  - `listWorkProductsForCompany` returns a newest-first slice with a
    500-row cap so the snapshot stays bounded.
  - `ProfileSnapshot.workProducts` flows through `useCompanyData` as
    `companyWorkProducts`, filtered to the current company.
  - `DocumentCenter` renders two labelled sections in order — prose
    documents (editable) on top, files & deliverables below. Each
    work-product row opens the underlying URL or filesystem path
    when clickable, so the agent's bash-generated markdown, PRs,
    and artifacts are one click away.

`WorkProductRecord` lifts into `@shared/types/records-extended` so
the renderer, snapshot, and database shells all reference the same
shape. The store-test fixture learns about the new field.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-19 13:24:50 -04:00
1 parent 734a2b04e0
commit 22ba7a9004
15 files changed
+890 -158

No files matched your search

+28
View File
@@ -299,6 +299,34 @@ 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`.
*/
listWorkProductsForCompany(companyId: string, limit = 500) {
return readAll<{
id: string; company_id: string; project_id: string | null; task_id: string | null;
type: string; provider: string; external_id: string | null; title: string;
url: string | null; status: string; review_state: string; is_primary: number;
summary: string | null; metadata_json: string | null; created_by_run_id: string | null;
created_at: string; updated_at: string;
}>(
"SELECT * FROM work_products WHERE company_id = ? 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,
type: r.type, provider: r.provider, externalId: r.external_id, title: r.title,
url: r.url, status: r.status, reviewState: r.review_state,
isPrimary: Boolean(r.is_primary), summary: r.summary,
metadataJson: r.metadata_json, createdByRunId: r.created_by_run_id,
createdAt: r.created_at, updatedAt: r.updated_at,
}));
},
deleteWorkProduct(id: string, companyId: string): number {
const result = getDb()
.prepare("DELETE FROM work_products WHERE id = ? AND company_id = ?")
+11
View File
@@ -129,6 +129,17 @@ 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,
})),
workProducts: readAll<SqliteRow>(
`select id, company_id, project_id, task_id, type, provider, external_id, title, url, status, review_state, is_primary, summary, metadata_json, created_by_run_id, created_at, updated_at from work_products${companyWhere} 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,
type: r.type, provider: r.provider, externalId: r.external_id, title: r.title,
url: r.url, status: r.status, reviewState: r.review_state,
isPrimary: Boolean(r.is_primary), summary: r.summary,
metadataJson: r.metadata_json, createdByRunId: r.created_by_run_id,
createdAt: r.created_at, updatedAt: r.updated_at,
})),
knowledgeBase: readAll<SqliteRow>(
`select id, company_id, category, topic, content, author_agent_id, importance, tags_json, referenced_entity_type, referenced_entity_id, created_at, updated_at from knowledge_base${companyWhere} order by updated_at desc`,
...companyArgs,