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.
This commit is contained in:
1 parent
d1483dcd5d
commit
d9b35d73ab
12 files changed
+179
-25
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user