From d9b35d73abd337e34f217b6286fb2b6bffc7a372 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 15:43:48 -0400 Subject: [PATCH] Pin work_products identity to (company, workspace, path) The natural key used to be (companyId, provider, externalId), so two workspaces with the same workspace-relative path collided onto one row. Provenance was also frozen at the first write: `created_by_run_id` never refreshed, so a run that edited a pre-existing file never qualified as a deliverable and the DocumentCenter "open" link could still jump into the first workspace after a repo move. Migration v45 adds `workspace_id` + `last_touched_by_run_id`, backfills `workspace_id` via the task/run join, seeds `last_touched_by_run_id = created_by_run_id`, and installs a partial unique index on `(company_id, workspace_id, provider, external_id)` scoped to rows that have a workspace. The artifact projector now records `workspace_id` on insert and refreshes `last_touched_by_run_id` on every upsert. The deliverable signal query reads either provenance column so a run that only edited a pre-existing file still counts. `resolveWorkProductTarget` prefers the stored `workspace_id` and falls back to the task/run join for legacy NULL rows. --- src/main/agent-runtime/artifact-projector.ts | 15 +++- src/main/agent-runtime/hook-handlers.ts | 2 + src/main/agent-runtime/runtime-bridge.ts | 3 + src/main/api-server/routes-files.ts | 1 + src/main/database.ts | 4 +- src/main/database/documents.ts | 74 ++++++++++++---- src/main/database/knowledge.ts | 8 +- src/main/database/migrations/v45-v45.ts | 88 ++++++++++++++++++++ src/main/database/schema.ts | 2 + src/main/database/snapshot.ts | 4 +- src/main/worker/worker-runner.ts | 1 + src/shared/types/records-extended.ts | 2 + 12 files changed, 179 insertions(+), 25 deletions(-) create mode 100644 src/main/database/migrations/v45-v45.ts diff --git a/src/main/agent-runtime/artifact-projector.ts b/src/main/agent-runtime/artifact-projector.ts index de578a07..c210146e 100644 --- a/src/main/agent-runtime/artifact-projector.ts +++ b/src/main/agent-runtime/artifact-projector.ts @@ -69,6 +69,7 @@ export type ArtifactProjectorRunContext = { agentId: string; companyId: string; taskId: string | null; + workspaceId: string | null; workspacePath: string | null; }; @@ -284,14 +285,24 @@ function upsertFilesystemArtifact( const title = lastSlash >= 0 ? relativePath.slice(lastSlash + 1) : relativePath; const provider = "filesystem"; - const existing = db.findWorkProductByExternalId(ctx.companyId, provider, relativePath); + // Identity is scoped to (company, workspace, provider, externalId) so two + // workspaces with the same relative path do not collapse onto one row. + // Every touch refreshes `last_touched_by_run_id` so the deliverable + // signal, the DocumentCenter preview, and the run-provenance readers + // all see the latest run that wrote the file. + const existing = db.findWorkProductByExternalId(ctx.companyId, provider, relativePath, ctx.workspaceId); if (existing) { - db.updateWorkProduct(existing.id, ctx.companyId, { title }); + db.updateWorkProduct(existing.id, ctx.companyId, { + title, + lastTouchedByRunId: ctx.runId, + workspaceId: ctx.workspaceId, + }); return { kind: "updated", workProductId: existing.id, relativePath }; } const workProductId = db.createWorkProduct({ companyId: ctx.companyId, taskId: ctx.taskId, + workspaceId: ctx.workspaceId, type: "file", provider, externalId: relativePath, diff --git a/src/main/agent-runtime/hook-handlers.ts b/src/main/agent-runtime/hook-handlers.ts index 6ee10da3..ac29a5d7 100644 --- a/src/main/agent-runtime/hook-handlers.ts +++ b/src/main/agent-runtime/hook-handlers.ts @@ -56,6 +56,7 @@ export interface ActiveRunContext { connectorId: string; modelId: string; taskSessionId: string; + workspaceId: string | null; workspacePath: string | null; state: ActiveRunLifecycleState; } @@ -176,6 +177,7 @@ export function handleAfterToolCall( agentId: ctx.agentId, companyId: ctx.companyId, taskId: ctx.taskId, + workspaceId: ctx.workspaceId, workspacePath: ctx.workspacePath, }; try { diff --git a/src/main/agent-runtime/runtime-bridge.ts b/src/main/agent-runtime/runtime-bridge.ts index f88fb18c..456df068 100644 --- a/src/main/agent-runtime/runtime-bridge.ts +++ b/src/main/agent-runtime/runtime-bridge.ts @@ -114,6 +114,7 @@ export class RuntimeBridge { agentId: ctx.agentId, companyId: ctx.companyId, taskId: ctx.taskId, + workspaceId: ctx.workspaceId, workspacePath: ctx.workspacePath, }); const inserted = outcomes.filter((o) => o.kind === "inserted").length; @@ -460,6 +461,7 @@ export class RuntimeBridge { companyId: string; taskId: string | null; modelId: string; + workspaceId: string | null; workspacePath: string | null; }; this.registerActiveRun({ @@ -470,6 +472,7 @@ export class RuntimeBridge { connectorId: payload.connectorId, modelId: payload.modelId, taskSessionId: payload.taskSessionId, + workspaceId: payload.workspaceId, workspacePath: payload.workspacePath, state: "preparing", }); diff --git a/src/main/api-server/routes-files.ts b/src/main/api-server/routes-files.ts index c797fb14..5421eb70 100644 --- a/src/main/api-server/routes-files.ts +++ b/src/main/api-server/routes-files.ts @@ -231,6 +231,7 @@ export function registerFileRoutes(server: AgentApiServer) { companyId: task.companyId, taskId: task.id, projectId: data.projectId ?? task.projectId, + workspaceId: task.workspaceId ?? null, type: data.type, provider: data.provider, externalId: data.externalId ?? null, diff --git a/src/main/database.ts b/src/main/database.ts index 12f1400b..1011b3cc 100644 --- a/src/main/database.ts +++ b/src/main/database.ts @@ -94,10 +94,10 @@ export class AppDatabase extends AppDatabaseBase { createDocumentRevision(input: { documentId: string; companyId: string; body: string; changeSummary?: string | null; createdByAgentId?: string | null; createdByUserId?: string | null }) { return this.documentsMod.createDocumentRevision(input); } getDocumentLatestRevisionId(documentId: string) { return this.documentsMod.getDocumentLatestRevisionId(documentId); } listDocumentRevisions(documentId: string, companyId: string) { return this.documentsMod.listDocumentRevisions(documentId, companyId); } - createWorkProduct(input: { companyId: string; taskId: string | null; projectId?: 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); } + 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); } - findWorkProductByExternalId(companyId: string, provider: string, externalId: string) { return this.documentsMod.findWorkProductByExternalId(companyId, provider, externalId); } + 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); } linkDocumentToTask(taskId: string, documentId: string, companyId: string, key: string) { return this.documentsMod.linkDocumentToTask(taskId, documentId, companyId, key); } diff --git a/src/main/database/documents.ts b/src/main/database/documents.ts index 87560e79..abaa1cbb 100644 --- a/src/main/database/documents.ts +++ b/src/main/database/documents.ts @@ -8,19 +8,26 @@ import { buildConfigRevisionMethods } from "./config-revisions"; function resolveWorkspaceRoot( getDb: () => DatabaseSync, companyId: string, + workspaceId: string | null, taskId: string | null, - createdByRunId: string | null, + runId: string | null, ): string | null { + if (workspaceId) { + const row = getDb().prepare( + "SELECT local_path AS localPath FROM workspaces WHERE id = ? AND company_id = ?", + ).get(workspaceId, companyId) as { localPath: string } | undefined; + if (row?.localPath) return row.localPath; + } if (taskId) { const row = getDb().prepare( "SELECT w.local_path AS localPath FROM tasks t JOIN workspaces w ON w.id = t.workspace_id WHERE t.id = ? AND t.company_id = ?", ).get(taskId, companyId) as { localPath: string } | undefined; if (row?.localPath) return row.localPath; } - if (createdByRunId) { + if (runId) { const row = getDb().prepare( "SELECT w.local_path AS localPath FROM runs r JOIN workspaces w ON w.id = r.workspace_id WHERE r.id = ? AND r.company_id = ?", - ).get(createdByRunId, companyId) as { localPath: string } | undefined; + ).get(runId, companyId) as { localPath: string } | undefined; if (row?.localPath) return row.localPath; } return null; @@ -222,6 +229,7 @@ export function buildDocumentMethods( 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; @@ -235,35 +243,47 @@ export function buildDocumentMethods( ).run(ts, input.companyId, input.taskId, input.type); } getDb().prepare( - `INSERT INTO work_products (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) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + `INSERT INTO work_products (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) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ).run( id, input.companyId, input.projectId ?? null, input.taskId ?? null, + input.workspaceId ?? null, input.type, input.provider, input.externalId ?? null, input.title, input.url ?? null, input.status ?? "active", input.reviewState ?? "none", input.isPrimary ? 1 : 0, input.summary ?? null, input.metadataJson ?? null, - input.createdByRunId ?? null, ts, ts, + input.createdByRunId ?? null, input.createdByRunId ?? null, ts, ts, ); return id; }, /** - * Look up a work product by its natural identity — the tuple - * (companyId, provider, externalId) uniquely names a registered - * artifact. Returns null when not found. Used by the artifact - * projector to make tool-output registration idempotent: the first - * write inserts, subsequent writes (same path, same provider) go - * through `updateWorkProduct` instead of spamming duplicates. + * Look up a work product by its natural identity. The v45 schema + * pinned identity to `(companyId, workspaceId, provider, externalId)` + * so two workspaces with the same relative path no longer collide. + * When `workspaceId` is null we fall back to the legacy + * `(companyId, provider, externalId)` scope so historical rows that + * pre-date the workspace backfill can still be found and updated. */ findWorkProductByExternalId( companyId: string, provider: string, externalId: string, + workspaceId: string | null, ): { id: string; taskId: string | null } | null { + if (workspaceId) { + const row = getDb() + .prepare( + "SELECT id, task_id as taskId FROM work_products WHERE company_id = ? AND workspace_id = ? AND provider = ? AND external_id = ? LIMIT 1", + ) + .get(companyId, workspaceId, provider, externalId) as + | { id: string; taskId: string | null } + | undefined; + return row ?? null; + } const row = getDb() .prepare( - "SELECT id, task_id as taskId FROM work_products WHERE company_id = ? AND provider = ? AND external_id = ? LIMIT 1", + "SELECT id, task_id as taskId FROM work_products WHERE company_id = ? AND workspace_id IS NULL AND provider = ? AND external_id = ? LIMIT 1", ) .get(companyId, provider, externalId) as | { id: string; taskId: string | null } @@ -278,6 +298,7 @@ export function buildDocumentMethods( type?: string; provider?: string; externalId?: string | null; + workspaceId?: string | null; title?: string; url?: string | null; status?: string; @@ -285,6 +306,7 @@ export function buildDocumentMethods( isPrimary?: boolean; summary?: string | null; metadataJson?: string | null; + lastTouchedByRunId?: string | null; }, ): number { const ts = nowIso(); @@ -294,6 +316,7 @@ export function buildDocumentMethods( ["type", "type", (v) => (v as string) ?? null], ["provider", "provider", (v) => (v as string) ?? null], ["externalId", "external_id", (v) => (v as string | null) ?? null], + ["workspaceId", "workspace_id", (v) => (v as string | null) ?? null], ["title", "title", (v) => (v as string) ?? null], ["url", "url", (v) => (v as string | null) ?? null], ["status", "status", (v) => (v as string) ?? null], @@ -301,6 +324,7 @@ export function buildDocumentMethods( ["isPrimary", "is_primary", (v) => (v ? 1 : 0)], ["summary", "summary", (v) => (v as string | null) ?? null], ["metadataJson", "metadata_json", (v) => (v as string | null) ?? null], + ["lastTouchedByRunId", "last_touched_by_run_id", (v) => (v as string | null) ?? null], ]; for (const [key, col, coerce] of map) { if (key in patch) { @@ -329,9 +353,9 @@ 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 FROM work_products WHERE id = ? AND company_id = ?", + "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 = ?", ).get(workProductId, companyId) as - | { provider: string; external_id: string | null; url: string | null; task_id: string | null; created_by_run_id: string | null } + | { 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 } | undefined; if (!row) return null; @@ -340,7 +364,13 @@ export function buildDocumentMethods( } if (row.provider !== "filesystem" || !row.external_id) return null; - const workspaceRoot = resolveWorkspaceRoot(getDb, companyId, row.task_id, row.created_by_run_id); + const workspaceRoot = resolveWorkspaceRoot( + getDb, + companyId, + row.workspace_id, + row.task_id, + row.last_touched_by_run_id ?? row.created_by_run_id, + ); if (!workspaceRoot) return null; const absolute = joinWorkspaceRelative(workspaceRoot, row.external_id); @@ -351,19 +381,23 @@ export function buildDocumentMethods( listWorkProductsForTask(taskId: string, companyId: string) { return readAll<{ id: string; company_id: string; project_id: string | null; task_id: string; + workspace_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; + summary: string | null; metadata_json: string | null; + 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", taskId, companyId, ).map((r) => ({ id: r.id, companyId: r.company_id, projectId: r.project_id, taskId: r.task_id, + workspaceId: r.workspace_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, + lastTouchedByRunId: r.last_touched_by_run_id, createdAt: r.created_at, updatedAt: r.updated_at, })); }, @@ -379,19 +413,23 @@ export function buildDocumentMethods( listWorkProductsForCompany(companyId: string, limit = 500) { return readAll<{ id: string; company_id: string; project_id: string | null; task_id: string | null; + workspace_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; + summary: string | null; metadata_json: string | null; + 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 ?", companyId, limit, ).map((r) => ({ id: r.id, companyId: r.company_id, projectId: r.project_id, taskId: r.task_id, + workspaceId: r.workspace_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, + lastTouchedByRunId: r.last_touched_by_run_id, createdAt: r.created_at, updatedAt: r.updated_at, })); }, diff --git a/src/main/database/knowledge.ts b/src/main/database/knowledge.ts index bcb838f8..abb2e014 100644 --- a/src/main/database/knowledge.ts +++ b/src/main/database/knowledge.ts @@ -217,9 +217,13 @@ export function buildKnowledgeMethods( ) : []; + // Pre-v45 provenance was frozen at creation; post-v45 every upsert + // refreshes `last_touched_by_run_id`. Querying by either side keeps + // the deliverable signal correct for a run that edited a pre-existing + // work product as well as a run that created a new one. const workProducts = readAll<{ id: string }>( - "select id from work_products where company_id = ? and created_by_run_id = ? limit 1", - companyId, runId, + "select id from work_products where company_id = ? and (last_touched_by_run_id = ? or created_by_run_id = ?) limit 1", + companyId, runId, runId, ); const recentComments = readAll<{ body: string }>( diff --git a/src/main/database/migrations/v45-v45.ts b/src/main/database/migrations/v45-v45.ts new file mode 100644 index 00000000..9ea1d51b --- /dev/null +++ b/src/main/database/migrations/v45-v45.ts @@ -0,0 +1,88 @@ +import log from "electron-log/main.js"; +import type { Migration, MigrationDeps } from "./types"; +import { columnExists, tableExists } from "./types"; + +export function createMigrationsV45V45(deps: MigrationDeps): Migration[] { + const { db } = deps; + return [ + { + // Promote work_products identity from `(company_id, provider, external_id)` + // to `(company_id, workspace_id, provider, external_id)` and replace + // the write-once `created_by_run_id` provenance with an append-only + // `last_touched_by_run_id` so the deliverable judgment, the thread + // projection, and the DocumentCenter open link all stay correct as + // a file is rewritten across multiple runs. + // + // Pre-v45 behaviour silently collapsed two workspaces' files at the + // same relative path into one row, and locked provenance at the + // first run that touched the path — later edits were invisible to + // `runProducedDeliverable`, so the task that produced the edit + // could stay pinned in-progress. + // + // Backfill strategy: derive `workspace_id` per row via `task_id -> + // tasks.workspace_id` first, then via `created_by_run_id -> + // runs.workspace_id`. Rows with neither binding (historically the + // exception, not the rule) remain NULL so nothing is dropped; the + // new uniqueness constraint is scoped to rows with a workspace so + // legacy NULL rows stay live without triggering conflicts. + // `last_touched_by_run_id` is seeded to `created_by_run_id` so the + // deliverable signal reads the same provenance pre- and post- + // migration until the next write arrives. + version: 45, + name: "work_products_workspace_identity", + fn: () => { + if (!tableExists(db, "work_products")) { + log.info("[migration v45] work_products table missing; skipping"); + return; + } + + if (!columnExists(db, "work_products", "workspace_id")) { + db.exec("ALTER TABLE work_products ADD COLUMN workspace_id TEXT"); + } + if (!columnExists(db, "work_products", "last_touched_by_run_id")) { + db.exec("ALTER TABLE work_products ADD COLUMN last_touched_by_run_id TEXT"); + } + + db.exec(` + UPDATE work_products + SET workspace_id = ( + SELECT tasks.workspace_id FROM tasks + WHERE tasks.id = work_products.task_id + ) + WHERE workspace_id IS NULL + AND task_id IS NOT NULL + `); + + db.exec(` + UPDATE work_products + SET workspace_id = ( + SELECT runs.workspace_id FROM runs + WHERE runs.id = work_products.created_by_run_id + ) + WHERE workspace_id IS NULL + AND created_by_run_id IS NOT NULL + `); + + db.exec(` + UPDATE work_products + SET last_touched_by_run_id = created_by_run_id + WHERE last_touched_by_run_id IS NULL + AND created_by_run_id IS NOT NULL + `); + + db.exec(` + CREATE UNIQUE INDEX IF NOT EXISTS idx_work_products_identity + ON work_products(company_id, workspace_id, provider, external_id) + WHERE workspace_id IS NOT NULL AND external_id IS NOT NULL + `); + + db.exec(` + CREATE INDEX IF NOT EXISTS idx_work_products_last_touched + ON work_products(company_id, last_touched_by_run_id) + `); + + log.info("[migration v45] work_products identity + provenance upgraded"); + }, + }, + ]; +} diff --git a/src/main/database/schema.ts b/src/main/database/schema.ts index baaec707..88765d94 100644 --- a/src/main/database/schema.ts +++ b/src/main/database/schema.ts @@ -9,6 +9,7 @@ import { createMigrationsV41V41 } from "./migrations/v41-v41"; import { createMigrationsV42V42 } from "./migrations/v42-v42"; import { createMigrationsV43V43 } from "./migrations/v43-v43"; import { createMigrationsV44V44 } from "./migrations/v44-v44"; +import { createMigrationsV45V45 } from "./migrations/v45-v45"; import { columnExists, nowIso, @@ -193,6 +194,7 @@ export function runMigrations( ...createMigrationsV42V42(deps), ...createMigrationsV43V43(deps), ...createMigrationsV44V44(deps), + ...createMigrationsV45V45(deps), ]; for (const migration of migrations) { diff --git a/src/main/database/snapshot.ts b/src/main/database/snapshot.ts index 19e3c215..6c3d14c4 100644 --- a/src/main/database/snapshot.ts +++ b/src/main/database/snapshot.ts @@ -130,14 +130,16 @@ export function buildSnapshotMethods( tagsJson: r.tags_json, latestRevisionId: r.latest_revision_id ?? null, createdAt: r.created_at, updatedAt: r.updated_at, })), workProducts: readAll( - `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`, + `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`, ...companyArgs, ).map((r) => ({ id: r.id, companyId: r.company_id, projectId: r.project_id, taskId: r.task_id, + workspaceId: r.workspace_id ?? null, 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, + lastTouchedByRunId: r.last_touched_by_run_id ?? null, createdAt: r.created_at, updatedAt: r.updated_at, })), knowledgeBase: readAll( diff --git a/src/main/worker/worker-runner.ts b/src/main/worker/worker-runner.ts index 082048ae..d3346761 100644 --- a/src/main/worker/worker-runner.ts +++ b/src/main/worker/worker-runner.ts @@ -120,6 +120,7 @@ export class WorkerRunner { companyId: job.companyId, taskId: job.taskId, modelId: job.model, + workspaceId: job.workspaceId ?? null, workspacePath: job.workspacePath ?? null, }); if (!prepareResponse.ok) { diff --git a/src/shared/types/records-extended.ts b/src/shared/types/records-extended.ts index 26b417c1..3cef438d 100644 --- a/src/shared/types/records-extended.ts +++ b/src/shared/types/records-extended.ts @@ -102,6 +102,7 @@ export interface WorkProductRecord { companyId: string; projectId: string | null; taskId: string | null; + workspaceId: string | null; type: string; provider: string; externalId: string | null; @@ -113,6 +114,7 @@ export interface WorkProductRecord { summary: string | null; metadataJson: string | null; createdByRunId: string | null; + lastTouchedByRunId: string | null; createdAt: string; updatedAt: string; }