From d51682b06dec3e7ba9ada155368021d4b0e6ccf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sun, 19 Apr 2026 18:38:19 -0400 Subject: [PATCH] Surface the agent's own outputs in standup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The standup briefing knew about tasks, approvals, comments, and report statuses but had no view of what the agent itself recently produced. Without that view the model could narrate "I wrote the PRD" in its assistant text without ever calling POST /documents, and we'd have no automated way to catch the drift — the user just saw an empty document page and a confused agent. Standup now returns: - `recentDocuments` — documents the agent authored (capped at 20), with id / type / title / status / version / updatedAt; - `recentWorkProducts` — filesystem artifacts produced on tasks the agent owns (capped at 20), with title / externalId / url / status / task title / updatedAt. The CEO operating prompt was already updated in the previous commit to reference these fields and re-do a missing POST when a task claims completion but the persistence is absent. The trust- but-verify loop is now closed: the agent can compare its own narration against its own actual outputs every wake. --- src/main/api-server/routes-agents-standup.ts | 37 ++++++++++++++++++++ src/main/database.ts | 1 + 2 files changed, 38 insertions(+) diff --git a/src/main/api-server/routes-agents-standup.ts b/src/main/api-server/routes-agents-standup.ts index 35731ac3..cf0c8526 100644 --- a/src/main/api-server/routes-agents-standup.ts +++ b/src/main/api-server/routes-agents-standup.ts @@ -49,6 +49,23 @@ export function registerAgentStandupRoute(server: AgentApiServer): void { ? new Map(db.listAgentNamesByIds(token.companyId, Array.from(senderIds)).map((row) => [row.id, row.name])) : new Map(); + // Recent outputs the AGENT itself produced — closes the + // trust-but-verify loop. Without this view the model could + // narrate "I wrote the spec doc" in its assistant text without + // ever calling the persistence API, and we'd never catch it. + // Filter scope: documents the agent authored, work_products on + // any task the agent has worked on (assigned tasks). Taskless + // run outputs are intentionally out of scope here — the smaller + // surface keeps the briefing readable. + const RECENT_OUTPUT_LIMIT = 20; + const myTaskIds = new Set(myTasks.map((t) => t.id)); + const recentDocuments = db.listDocuments(token.companyId) + .filter((doc) => doc.authorAgentId === token.agentId) + .slice(0, RECENT_OUTPUT_LIMIT); + const recentWorkProducts = db.listWorkProductsForCompany(token.companyId) + .filter((wp) => wp.taskId !== null && myTaskIds.has(wp.taskId)) + .slice(0, RECENT_OUTPUT_LIMIT); + const reportNameMap = new Map(directReports.map((a) => [a.id, a.name])); const taskTitleMap = new Map([ ...myTasks.map<[string, string]>((t) => [t.id, t.title]), @@ -80,6 +97,26 @@ export function registerAgentStandupRoute(server: AgentApiServer): void { parentMessageId: m.parentMessageId, createdAt: m.createdAt, })), + recentDocuments: recentDocuments.map((d) => ({ + id: d.id, + type: d.type, + title: d.title, + status: d.status, + version: d.version, + updatedAt: d.updatedAt, + })), + recentWorkProducts: recentWorkProducts.map((wp) => ({ + id: wp.id, + type: wp.type, + provider: wp.provider, + title: wp.title, + externalId: wp.externalId, + url: wp.url, + status: wp.status, + taskId: wp.taskId, + taskTitle: wp.taskId ? (taskTitleMap.get(wp.taskId) ?? "") : "", + updatedAt: wp.updatedAt, + })), tasksAwaitingReview: tasksAwaitingMyReview.map((t) => ({ id: t.id, title: t.title, diff --git a/src/main/database.ts b/src/main/database.ts index bd1a2835..aa0c41a3 100644 --- a/src/main/database.ts +++ b/src/main/database.ts @@ -96,6 +96,7 @@ export class AppDatabase extends AppDatabaseBase { createWorkProduct(input: { companyId: string; taskId: string | null; projectId?: string | null; workspaceId?: string | null; type: string; provider: string; externalId?: string | null; title: string; url?: string | null; status?: string; reviewState?: string; isPrimary?: boolean; summary?: string | null; metadataJson?: string | null; createdByRunId?: string | null }) { return this.documentsMod.createWorkProduct(input); } updateWorkProduct(id: string, companyId: string, patch: Parameters["updateWorkProduct"]>[2]) { return this.documentsMod.updateWorkProduct(id, companyId, patch); } listWorkProductsForTask(taskId: string, companyId: string) { return this.documentsMod.listWorkProductsForTask(taskId, companyId); } + listWorkProductsForCompany(companyId: string, limit?: number) { return this.documentsMod.listWorkProductsForCompany(companyId, limit); } findWorkProductByExternalId(companyId: string, provider: string, externalId: string, workspaceId: string | null) { return this.documentsMod.findWorkProductByExternalId(companyId, provider, externalId, workspaceId); } resolveWorkProductTarget(workProductId: string, companyId: string) { return this.documentsMod.resolveWorkProductTarget(workProductId, companyId); } deleteWorkProduct(id: string, companyId: string) { return this.documentsMod.deleteWorkProduct(id, companyId); }