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",
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<ReturnType<typeof import("./database/documents").buildDocumentMethods>["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); }
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
},
|
||||
|
||||
@@ -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 }>(
|
||||
|
||||
@@ -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");
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<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`,
|
||||
`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<SqliteRow>(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user