@agent-native/creative-context 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-native.package.json +11 -0
- package/dist/actions/continue-context-import.d.ts +16 -1
- package/dist/actions/continue-context-import.js +4 -1
- package/dist/actions/continue-context-import.js.map +1 -1
- package/dist/actions/get-creative-context.d.ts +38 -0
- package/dist/actions/get-creative-context.d.ts.map +1 -1
- package/dist/actions/get-creative-context.js +59 -23
- package/dist/actions/get-creative-context.js.map +1 -1
- package/dist/actions/get-google-picker-session.d.ts +10 -0
- package/dist/actions/get-google-picker-session.d.ts.map +1 -0
- package/dist/actions/get-google-picker-session.js +38 -0
- package/dist/actions/get-google-picker-session.js.map +1 -0
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +2 -0
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/start-context-enrichment.d.ts +16 -1
- package/dist/actions/start-context-enrichment.js +6 -4
- package/dist/actions/start-context-enrichment.js.map +1 -1
- package/dist/actions/start-context-import.d.ts +16 -1
- package/dist/actions/start-context-import.js +2 -1
- package/dist/actions/start-context-import.js.map +1 -1
- package/dist/client/CreativeContextPanel.d.ts +1 -59
- package/dist/client/CreativeContextPanel.d.ts.map +1 -1
- package/dist/client/CreativeContextPanel.js +111 -36
- package/dist/client/CreativeContextPanel.js.map +1 -1
- package/dist/client/actions.d.ts +8 -0
- package/dist/client/actions.d.ts.map +1 -1
- package/dist/client/actions.js +4 -0
- package/dist/client/actions.js.map +1 -1
- package/dist/client/google-slides-picker.d.ts +19 -0
- package/dist/client/google-slides-picker.d.ts.map +1 -0
- package/dist/client/google-slides-picker.js +106 -0
- package/dist/client/google-slides-picker.js.map +1 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/messages.d.ts +1 -1
- package/dist/client/messages.d.ts.map +1 -1
- package/dist/client/messages.js +22 -0
- package/dist/client/messages.js.map +1 -1
- package/dist/connectors/google-slides.d.ts +1 -1
- package/dist/connectors/google-slides.d.ts.map +1 -1
- package/dist/connectors/google-slides.js +1 -2
- package/dist/connectors/google-slides.js.map +1 -1
- package/dist/connectors/normalize.d.ts +7 -0
- package/dist/connectors/normalize.d.ts.map +1 -1
- package/dist/connectors/normalize.js +122 -3
- package/dist/connectors/normalize.js.map +1 -1
- package/dist/schema/index.d.ts +30 -30
- package/dist/server/enrichment.d.ts +1 -0
- package/dist/server/enrichment.d.ts.map +1 -1
- package/dist/server/enrichment.js +26 -24
- package/dist/server/enrichment.js.map +1 -1
- package/dist/server/generation-context.d.ts +1 -1
- package/dist/server/generation-context.d.ts.map +1 -1
- package/dist/server/generation-context.js +1 -1
- package/dist/server/generation-context.js.map +1 -1
- package/dist/store/content.d.ts +13 -0
- package/dist/store/content.d.ts.map +1 -1
- package/dist/store/content.js +172 -1
- package/dist/store/content.js.map +1 -1
- package/dist/store/jobs.d.ts.map +1 -1
- package/dist/store/jobs.js +2 -2
- package/dist/store/jobs.js.map +1 -1
- package/dist/store/purge.d.ts.map +1 -1
- package/dist/store/purge.js +9 -8
- package/dist/store/purge.js.map +1 -1
- package/dist/store/suggestions.d.ts +1 -0
- package/dist/store/suggestions.d.ts.map +1 -1
- package/dist/store/suggestions.js +56 -38
- package/dist/store/suggestions.js.map +1 -1
- package/docs/skills/creative-context/SKILL.md +8 -0
- package/package.json +3 -3
- package/src/actions/continue-context-import.ts +4 -1
- package/src/actions/creative-context-a2a.spec.ts +98 -0
- package/src/actions/get-creative-context.spec.ts +121 -1
- package/src/actions/get-creative-context.ts +78 -29
- package/src/actions/get-google-picker-session.spec.ts +62 -0
- package/src/actions/get-google-picker-session.ts +45 -0
- package/src/actions/index.ts +2 -0
- package/src/actions/job-public-serialization.spec.ts +122 -0
- package/src/actions/start-context-enrichment.ts +6 -4
- package/src/actions/start-context-import.ts +2 -1
- package/src/client/CreativeContextPanel.tsx +163 -64
- package/src/client/actions.ts +18 -0
- package/src/client/google-slides-picker.spec.ts +36 -0
- package/src/client/google-slides-picker.ts +146 -0
- package/src/client/index.ts +2 -0
- package/src/client/messages.ts +35 -0
- package/src/client/source-config.spec.ts +24 -10
- package/src/connectors/google-slides.ts +1 -2
- package/src/connectors/normalize.spec.ts +144 -0
- package/src/connectors/normalize.ts +201 -3
- package/src/server/enrichment.spec.ts +94 -0
- package/src/server/enrichment.ts +27 -24
- package/src/server/generation-artifact-access.spec.ts +104 -0
- package/src/server/generation-context-a2a.spec.ts +20 -2
- package/src/server/generation-context.ts +6 -6
- package/src/server/isolated-a2a.spec.ts +21 -1
- package/src/store/access.integration.spec.ts +511 -26
- package/src/store/content.ts +213 -1
- package/src/store/jobs.ts +2 -0
- package/src/store/purge.ts +18 -9
- package/src/store/suggestions.ts +67 -41
package/src/store/content.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
sql,
|
|
23
23
|
} from "drizzle-orm";
|
|
24
24
|
|
|
25
|
+
import { assertContextItemSqlTextLimits } from "../connectors/normalize.js";
|
|
25
26
|
import { matchesCreativeSearchMode } from "../search/mode.js";
|
|
26
27
|
import { getCreativeContext } from "../server/context.js";
|
|
27
28
|
import type {
|
|
@@ -153,6 +154,19 @@ function mapEdge(row: any): ContextEdge {
|
|
|
153
154
|
};
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
function mediaEnrichmentSourceContentHash(
|
|
158
|
+
metadata: Record<string, unknown>,
|
|
159
|
+
fallback: string,
|
|
160
|
+
): string {
|
|
161
|
+
const derivation = metadata.__creativeContextDerivation;
|
|
162
|
+
if (!derivation || typeof derivation !== "object") return fallback;
|
|
163
|
+
const record = derivation as Record<string, unknown>;
|
|
164
|
+
return record.kind === "media-enrichment" &&
|
|
165
|
+
typeof record.sourceContentHash === "string"
|
|
166
|
+
? record.sourceContentHash
|
|
167
|
+
: fallback;
|
|
168
|
+
}
|
|
169
|
+
|
|
156
170
|
export function assertImmutableContextVersion(operation: string): never {
|
|
157
171
|
throw new Error(
|
|
158
172
|
`Creative context item versions are immutable; ${operation} must create a new version`,
|
|
@@ -195,6 +209,9 @@ async function ingestItemsAttempt(
|
|
|
195
209
|
undefined,
|
|
196
210
|
{ skipResourceBody: true },
|
|
197
211
|
);
|
|
212
|
+
for (const item of batch.items) {
|
|
213
|
+
assertContextItemSqlTextLimits(item);
|
|
214
|
+
}
|
|
198
215
|
const { getDb, schema } = getCreativeContext();
|
|
199
216
|
const actor = requireActor();
|
|
200
217
|
const timestamp = batch.completedAt ?? nowIso();
|
|
@@ -229,6 +246,8 @@ async function ingestItemsAttempt(
|
|
|
229
246
|
.select({
|
|
230
247
|
id: schema.contextItemVersions.id,
|
|
231
248
|
versionNumber: schema.contextItemVersions.versionNumber,
|
|
249
|
+
contentHash: schema.contextItemVersions.contentHash,
|
|
250
|
+
metadata: schema.contextItemVersions.metadata,
|
|
232
251
|
})
|
|
233
252
|
.from(schema.contextItemVersions)
|
|
234
253
|
.where(inArray(schema.contextItemVersions.id, currentVersionIds))
|
|
@@ -236,6 +255,15 @@ async function ingestItemsAttempt(
|
|
|
236
255
|
const versionNumberById = new Map<string, number>(
|
|
237
256
|
currentVersionRows.map((row: any) => [row.id, row.versionNumber as number]),
|
|
238
257
|
);
|
|
258
|
+
const sourceContentHashByVersionId = new Map<string, string>(
|
|
259
|
+
currentVersionRows.map((row: any) => {
|
|
260
|
+
const metadata = parseJson<Record<string, unknown>>(row.metadata, {});
|
|
261
|
+
return [
|
|
262
|
+
row.id,
|
|
263
|
+
mediaEnrichmentSourceContentHash(metadata, row.contentHash),
|
|
264
|
+
];
|
|
265
|
+
}),
|
|
266
|
+
);
|
|
239
267
|
|
|
240
268
|
const newItems: any[] = [];
|
|
241
269
|
const itemUpdates: Array<{ id: string; values: Record<string, unknown> }> =
|
|
@@ -255,7 +283,12 @@ async function ingestItemsAttempt(
|
|
|
255
283
|
|
|
256
284
|
for (const input of batch.items) {
|
|
257
285
|
const existing = existingByExternalId.get(input.externalId) as any;
|
|
258
|
-
if (
|
|
286
|
+
if (
|
|
287
|
+
existing &&
|
|
288
|
+
(existing.currentContentHash === input.contentHash ||
|
|
289
|
+
sourceContentHashByVersionId.get(existing.currentVersionId) ===
|
|
290
|
+
input.contentHash)
|
|
291
|
+
) {
|
|
259
292
|
batchVersionTargets.set(input.externalId, {
|
|
260
293
|
itemId: existing.id,
|
|
261
294
|
itemVersionId: existing.currentVersionId,
|
|
@@ -1378,6 +1411,185 @@ export async function getCreativeContextItem(
|
|
|
1378
1411
|
};
|
|
1379
1412
|
}
|
|
1380
1413
|
|
|
1414
|
+
export async function appendMediaEnrichmentVersion(input: {
|
|
1415
|
+
mediaId: string;
|
|
1416
|
+
palette: string[];
|
|
1417
|
+
contentHash: string;
|
|
1418
|
+
caption: string | null;
|
|
1419
|
+
captionStatus: "pending" | "complete" | "failed" | "not-needed";
|
|
1420
|
+
ocrText: string | null;
|
|
1421
|
+
}): Promise<{
|
|
1422
|
+
itemId: string;
|
|
1423
|
+
itemVersionId: string;
|
|
1424
|
+
mediaId: string;
|
|
1425
|
+
appended: boolean;
|
|
1426
|
+
}> {
|
|
1427
|
+
const { getDb, schema } = getCreativeContext();
|
|
1428
|
+
const rows = await getDb()
|
|
1429
|
+
.select({ itemId: schema.contextMedia.itemId })
|
|
1430
|
+
.from(schema.contextMedia)
|
|
1431
|
+
.where(eq(schema.contextMedia.id, input.mediaId))
|
|
1432
|
+
.limit(1);
|
|
1433
|
+
const itemId = rows[0]?.itemId;
|
|
1434
|
+
if (!itemId) throw new Error("Creative context media was not found");
|
|
1435
|
+
const detail = await getCreativeContextItem(itemId);
|
|
1436
|
+
if (!detail) throw new Error("Creative context media is not accessible");
|
|
1437
|
+
const target = detail.media.find(
|
|
1438
|
+
(media) =>
|
|
1439
|
+
media.id === input.mediaId ||
|
|
1440
|
+
media.metadata.__creativeContextEnrichmentSourceMediaId ===
|
|
1441
|
+
input.mediaId ||
|
|
1442
|
+
media.metadata.__creativeContextPreviousMediaId === input.mediaId,
|
|
1443
|
+
);
|
|
1444
|
+
if (!target) {
|
|
1445
|
+
throw new Error(
|
|
1446
|
+
"Creative context media enrichment can only append from the current item version",
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
if (
|
|
1450
|
+
target.caption === input.caption &&
|
|
1451
|
+
target.captionStatus === input.captionStatus &&
|
|
1452
|
+
target.ocrText === input.ocrText &&
|
|
1453
|
+
target.contentHash === input.contentHash &&
|
|
1454
|
+
stringifyJson(target.palette) === stringifyJson(input.palette)
|
|
1455
|
+
) {
|
|
1456
|
+
return {
|
|
1457
|
+
itemId,
|
|
1458
|
+
itemVersionId: detail.version.id,
|
|
1459
|
+
mediaId: target.id,
|
|
1460
|
+
appended: false,
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
const sourceContentHash = mediaEnrichmentSourceContentHash(
|
|
1465
|
+
detail.version.metadata,
|
|
1466
|
+
detail.version.contentHash,
|
|
1467
|
+
);
|
|
1468
|
+
const media = detail.media.map((entry) => {
|
|
1469
|
+
const enriched = entry.id === target.id;
|
|
1470
|
+
return {
|
|
1471
|
+
kind: entry.kind,
|
|
1472
|
+
...(entry.mimeType ? { mimeType: entry.mimeType } : {}),
|
|
1473
|
+
accessMode: entry.accessMode,
|
|
1474
|
+
...(entry.url ? { url: entry.url } : {}),
|
|
1475
|
+
...(entry.storageKey ? { storageKey: entry.storageKey } : {}),
|
|
1476
|
+
...(entry.provenanceUrl ? { provenanceUrl: entry.provenanceUrl } : {}),
|
|
1477
|
+
...(entry.altText ? { altText: entry.altText } : {}),
|
|
1478
|
+
...(enriched
|
|
1479
|
+
? { caption: input.caption ?? undefined }
|
|
1480
|
+
: { caption: entry.caption ?? undefined }),
|
|
1481
|
+
captionStatus: enriched ? input.captionStatus : entry.captionStatus,
|
|
1482
|
+
...(enriched
|
|
1483
|
+
? { ocrText: input.ocrText ?? undefined }
|
|
1484
|
+
: { ocrText: entry.ocrText ?? undefined }),
|
|
1485
|
+
palette: enriched ? input.palette : entry.palette,
|
|
1486
|
+
...(enriched
|
|
1487
|
+
? { contentHash: input.contentHash }
|
|
1488
|
+
: { contentHash: entry.contentHash ?? undefined }),
|
|
1489
|
+
...(entry.width === null ? {} : { width: entry.width }),
|
|
1490
|
+
...(entry.height === null ? {} : { height: entry.height }),
|
|
1491
|
+
...(entry.durationMs === null ? {} : { durationMs: entry.durationMs }),
|
|
1492
|
+
metadata: {
|
|
1493
|
+
...entry.metadata,
|
|
1494
|
+
__creativeContextEnrichmentSourceMediaId:
|
|
1495
|
+
entry.metadata.__creativeContextEnrichmentSourceMediaId ?? entry.id,
|
|
1496
|
+
__creativeContextPreviousMediaId: entry.id,
|
|
1497
|
+
...(enriched ? { enrichmentDerived: true } : {}),
|
|
1498
|
+
},
|
|
1499
|
+
};
|
|
1500
|
+
});
|
|
1501
|
+
const canonicalMedia = media.map((entry) => {
|
|
1502
|
+
const {
|
|
1503
|
+
__creativeContextEnrichmentSourceMediaId: _sourceMediaId,
|
|
1504
|
+
__creativeContextPreviousMediaId: _previousMediaId,
|
|
1505
|
+
...metadata
|
|
1506
|
+
} = entry.metadata;
|
|
1507
|
+
return { ...entry, metadata };
|
|
1508
|
+
});
|
|
1509
|
+
const derivedContentHash = await sha256(
|
|
1510
|
+
stringifyJson({
|
|
1511
|
+
sourceContentHash,
|
|
1512
|
+
media: canonicalMedia,
|
|
1513
|
+
}),
|
|
1514
|
+
);
|
|
1515
|
+
await ingestItems({
|
|
1516
|
+
sourceId: detail.item.sourceId,
|
|
1517
|
+
items: [
|
|
1518
|
+
{
|
|
1519
|
+
externalId: detail.item.externalId,
|
|
1520
|
+
kind: detail.item.kind,
|
|
1521
|
+
title: detail.item.title,
|
|
1522
|
+
canonicalUrl: detail.item.canonicalUrl ?? undefined,
|
|
1523
|
+
mimeType: detail.item.mimeType ?? undefined,
|
|
1524
|
+
content: detail.version.content,
|
|
1525
|
+
summary: detail.version.summary ?? undefined,
|
|
1526
|
+
contentHash: derivedContentHash,
|
|
1527
|
+
sourceModifiedAt: detail.version.sourceModifiedAt ?? undefined,
|
|
1528
|
+
sourceVersion: detail.version.sourceVersion ?? undefined,
|
|
1529
|
+
rawSnapshotBlobRef: detail.version.rawSnapshotBlobRef ?? undefined,
|
|
1530
|
+
parseStatus: detail.version.parseStatus,
|
|
1531
|
+
parseError: detail.version.parseError ?? undefined,
|
|
1532
|
+
upstreamAccess: detail.item.upstreamAccess,
|
|
1533
|
+
curationStatus: detail.item.curationStatus,
|
|
1534
|
+
curationRank: detail.item.curationRank,
|
|
1535
|
+
starred: detail.item.starred,
|
|
1536
|
+
inventoryState: detail.item.inventoryState,
|
|
1537
|
+
indexState: detail.item.indexState,
|
|
1538
|
+
tags: detail.item.tags,
|
|
1539
|
+
colors: detail.item.colors,
|
|
1540
|
+
sortOrder: detail.item.sortOrder,
|
|
1541
|
+
parentItemId: detail.item.parentItemId ?? undefined,
|
|
1542
|
+
provenance: detail.item.provenance,
|
|
1543
|
+
thumbnailBlobRef: detail.item.thumbnailBlobRef ?? undefined,
|
|
1544
|
+
metadata: {
|
|
1545
|
+
...detail.version.metadata,
|
|
1546
|
+
__creativeContextDerivation: {
|
|
1547
|
+
kind: "media-enrichment",
|
|
1548
|
+
sourceContentHash,
|
|
1549
|
+
derivedFromVersionId: detail.version.id,
|
|
1550
|
+
},
|
|
1551
|
+
},
|
|
1552
|
+
chunks: detail.chunks.map((chunk) => ({
|
|
1553
|
+
ordinal: chunk.ordinal,
|
|
1554
|
+
kind: chunk.kind,
|
|
1555
|
+
text: chunk.text,
|
|
1556
|
+
startOffset: chunk.startOffset ?? undefined,
|
|
1557
|
+
endOffset: chunk.endOffset ?? undefined,
|
|
1558
|
+
tokenCount: chunk.tokenCount ?? undefined,
|
|
1559
|
+
metadata: chunk.metadata,
|
|
1560
|
+
})),
|
|
1561
|
+
media,
|
|
1562
|
+
edges: detail.edges
|
|
1563
|
+
.filter(
|
|
1564
|
+
(edge) =>
|
|
1565
|
+
edge.relation !== "revision-of" ||
|
|
1566
|
+
edge.metadata.automatic !== true,
|
|
1567
|
+
)
|
|
1568
|
+
.map((edge) => ({
|
|
1569
|
+
relation: edge.relation,
|
|
1570
|
+
toItemId: edge.toItemId ?? undefined,
|
|
1571
|
+
toItemVersionId: edge.toItemVersionId ?? undefined,
|
|
1572
|
+
toExternalId: edge.toExternalId ?? undefined,
|
|
1573
|
+
metadata: edge.metadata,
|
|
1574
|
+
})),
|
|
1575
|
+
},
|
|
1576
|
+
],
|
|
1577
|
+
});
|
|
1578
|
+
const appended = await getCreativeContextItem(itemId);
|
|
1579
|
+
const enrichedMedia = appended?.media.find(
|
|
1580
|
+
(entry) => entry.metadata.__creativeContextPreviousMediaId === target.id,
|
|
1581
|
+
);
|
|
1582
|
+
if (!appended || !enrichedMedia) {
|
|
1583
|
+
throw new Error("Failed to append creative context media enrichment");
|
|
1584
|
+
}
|
|
1585
|
+
return {
|
|
1586
|
+
itemId,
|
|
1587
|
+
itemVersionId: appended.version.id,
|
|
1588
|
+
mediaId: enrichedMedia.id,
|
|
1589
|
+
appended: appended.version.id !== detail.version.id,
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1381
1593
|
export async function getCreativeContextItemByExternalId(input: {
|
|
1382
1594
|
sourceId: string;
|
|
1383
1595
|
externalId: string;
|
package/src/store/jobs.ts
CHANGED
|
@@ -260,6 +260,7 @@ export async function enqueueContextRebuildJob(input: {
|
|
|
260
260
|
.where(
|
|
261
261
|
and(
|
|
262
262
|
eq(schema.contextJobs.ownerEmail, actor.ownerEmail),
|
|
263
|
+
actorOrgScope(schema, actor),
|
|
263
264
|
eq(schema.contextJobs.sourceId, input.sourceId),
|
|
264
265
|
eq(schema.contextJobs.kind, "embed"),
|
|
265
266
|
inArray(schema.contextJobs.status, ["queued", "paused"]),
|
|
@@ -315,6 +316,7 @@ export async function getJob(jobId: string): Promise<ContextJob | null> {
|
|
|
315
316
|
and(
|
|
316
317
|
eq(schema.contextJobs.id, jobId),
|
|
317
318
|
eq(schema.contextJobs.ownerEmail, actor.ownerEmail),
|
|
319
|
+
actorOrgScope(schema, actor),
|
|
318
320
|
),
|
|
319
321
|
)
|
|
320
322
|
.limit(1);
|
package/src/store/purge.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { deletePostgresFtsDocuments } from "../search/postgres-fts.js";
|
|
|
12
12
|
import { getCreativeContext } from "../server/context.js";
|
|
13
13
|
import { nowIso, parseJson, stringifyJson } from "./helpers.js";
|
|
14
14
|
import { createJob } from "./jobs.js";
|
|
15
|
+
import { resolveLayoutProjectionItemId } from "./suggestions.js";
|
|
15
16
|
|
|
16
17
|
export async function purgeContextSourceArtifacts(sourceId: string) {
|
|
17
18
|
await assertAccess("creative-context-source", sourceId, "editor", undefined, {
|
|
@@ -119,15 +120,23 @@ export async function purgeContextSourceArtifacts(sourceId: string) {
|
|
|
119
120
|
)
|
|
120
121
|
.where(inArray(schema.brandDnaVersions.id, dnaVersionIds))
|
|
121
122
|
: [];
|
|
122
|
-
const promotedLayouts =
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
123
|
+
const promotedLayouts = await Promise.all(
|
|
124
|
+
(suggestions as any[])
|
|
125
|
+
.filter((suggestion) => suggestion.status === "promoted")
|
|
126
|
+
.map(async (suggestion) => {
|
|
127
|
+
const payload = parseJson<Record<string, unknown>>(
|
|
128
|
+
suggestion.payload,
|
|
129
|
+
{},
|
|
130
|
+
);
|
|
131
|
+
return {
|
|
132
|
+
suggestionId: suggestion.id as string,
|
|
133
|
+
projectionItemId: await resolveLayoutProjectionItemId(
|
|
134
|
+
suggestion.id,
|
|
135
|
+
payload.projectionItemId,
|
|
136
|
+
),
|
|
137
|
+
};
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
131
140
|
for (const layout of promotedLayouts) {
|
|
132
141
|
await projections?.layoutTemplate?.demote(layout);
|
|
133
142
|
}
|
package/src/store/suggestions.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
2
2
|
import { and, desc, eq } from "drizzle-orm";
|
|
3
3
|
|
|
4
|
+
import { assertContextItemSqlTextLimits } from "../connectors/normalize.js";
|
|
4
5
|
import { reassembleNativeCreativeArtifact } from "../native-artifact-reassembly.js";
|
|
5
6
|
import { nativeCreativeArtifactFromMetadata } from "../native-artifact.js";
|
|
6
7
|
import { getCreativeContext } from "../server/context.js";
|
|
@@ -134,6 +135,8 @@ export async function proposeCreativeContextSuggestion(input: {
|
|
|
134
135
|
return mapSuggestion(existing[0]);
|
|
135
136
|
}
|
|
136
137
|
const timestamp = nowIso();
|
|
138
|
+
const payload = { ...(input.payload ?? {}) };
|
|
139
|
+
if (input.kind === "layout-template") delete payload.projectionItemId;
|
|
137
140
|
const row = {
|
|
138
141
|
id: newId("ccsg"),
|
|
139
142
|
kind: input.kind,
|
|
@@ -142,7 +145,7 @@ export async function proposeCreativeContextSuggestion(input: {
|
|
|
142
145
|
itemId: input.itemId,
|
|
143
146
|
itemVersionId: detail.version.id,
|
|
144
147
|
reason: input.reason ?? null,
|
|
145
|
-
payload: stringifyJson(
|
|
148
|
+
payload: stringifyJson(payload),
|
|
146
149
|
createdAt: timestamp,
|
|
147
150
|
updatedAt: timestamp,
|
|
148
151
|
ownerEmail: actor.ownerEmail,
|
|
@@ -195,6 +198,19 @@ export async function decideCanonicalLogoSuggestion(input: {
|
|
|
195
198
|
|
|
196
199
|
const PROMOTED_LAYOUT_SOURCE = "Promoted layout templates";
|
|
197
200
|
|
|
201
|
+
export async function resolveLayoutProjectionItemId(
|
|
202
|
+
suggestionId: string,
|
|
203
|
+
candidateId: unknown,
|
|
204
|
+
): Promise<string | null> {
|
|
205
|
+
if (typeof candidateId !== "string" || !candidateId) return null;
|
|
206
|
+
const detail = await getCreativeContextItem(candidateId);
|
|
207
|
+
if (!detail || detail.item.kind !== "layout_template") return null;
|
|
208
|
+
const provenance = detail.item.provenance as Record<string, unknown>;
|
|
209
|
+
return provenance.promotedFromSuggestionId === suggestionId
|
|
210
|
+
? detail.item.id
|
|
211
|
+
: null;
|
|
212
|
+
}
|
|
213
|
+
|
|
198
214
|
async function loadCompiledLayoutSource(itemId: string, itemVersionId: string) {
|
|
199
215
|
const detail = await getCreativeContextItem(itemId, itemVersionId);
|
|
200
216
|
if (!detail) throw new Error("Layout source version is no longer accessible");
|
|
@@ -236,14 +252,52 @@ export async function applyLayoutTemplateSuggestion(input: {
|
|
|
236
252
|
"Layout-template projection is not configured for this app; keep the proposal pending",
|
|
237
253
|
);
|
|
238
254
|
}
|
|
239
|
-
let projectionItemId =
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
255
|
+
let projectionItemId = await resolveLayoutProjectionItemId(
|
|
256
|
+
row.id,
|
|
257
|
+
payload.projectionItemId,
|
|
258
|
+
);
|
|
243
259
|
const source =
|
|
244
260
|
input.operation === "promote"
|
|
245
261
|
? await loadCompiledLayoutSource(row.itemId, row.itemVersionId)
|
|
246
262
|
: null;
|
|
263
|
+
const projectionItem = source
|
|
264
|
+
? {
|
|
265
|
+
externalId: `layout-template:${row.id}`,
|
|
266
|
+
kind: "layout_template",
|
|
267
|
+
title: source!.detail.item.title,
|
|
268
|
+
content: source!.compiled.html,
|
|
269
|
+
summary: source!.detail.version.summary ?? undefined,
|
|
270
|
+
mimeType: "text/html",
|
|
271
|
+
contentHash: await sha256(
|
|
272
|
+
`${row.id}:${source!.detail.version.id}:${source!.compiled.html}`,
|
|
273
|
+
),
|
|
274
|
+
upstreamAccess: "available" as const,
|
|
275
|
+
curationStatus: "included" as const,
|
|
276
|
+
curationRank: "canonical" as const,
|
|
277
|
+
provenance: {
|
|
278
|
+
...source!.detail.item.provenance,
|
|
279
|
+
promotedFromSuggestionId: row.id,
|
|
280
|
+
promotedFromItemId: source!.detail.item.id,
|
|
281
|
+
promotedFromItemVersionId: source!.detail.version.id,
|
|
282
|
+
},
|
|
283
|
+
metadata: {
|
|
284
|
+
promotedFromSuggestionId: row.id,
|
|
285
|
+
nativeArtifact: {
|
|
286
|
+
...source!.compiled.artifact,
|
|
287
|
+
childExternalIds: undefined,
|
|
288
|
+
manifest: undefined,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
edges: source!.compiled.evidence.map((evidence) => ({
|
|
292
|
+
relation: "derived-from",
|
|
293
|
+
toItemId: evidence.itemId,
|
|
294
|
+
toItemVersionId: evidence.itemVersionId,
|
|
295
|
+
})),
|
|
296
|
+
}
|
|
297
|
+
: null;
|
|
298
|
+
if (projectionItem) {
|
|
299
|
+
assertContextItemSqlTextLimits(projectionItem);
|
|
300
|
+
}
|
|
247
301
|
if (input.operation === "promote" && !projectionItemId) {
|
|
248
302
|
const sources = await listContextSources({ kind: "manual", limit: 100 });
|
|
249
303
|
let projectionSource = sources.sources.find(
|
|
@@ -259,41 +313,7 @@ export async function applyLayoutTemplateSuggestion(input: {
|
|
|
259
313
|
}
|
|
260
314
|
const ingested = await ingestItems({
|
|
261
315
|
sourceId: projectionSource.id,
|
|
262
|
-
items: [
|
|
263
|
-
{
|
|
264
|
-
externalId: `layout-template:${row.id}`,
|
|
265
|
-
kind: "layout_template",
|
|
266
|
-
title: source!.detail.item.title,
|
|
267
|
-
content: source!.compiled.html,
|
|
268
|
-
summary: source!.detail.version.summary ?? undefined,
|
|
269
|
-
mimeType: "text/html",
|
|
270
|
-
contentHash: await sha256(
|
|
271
|
-
`${row.id}:${source!.detail.version.id}:${source!.compiled.html}`,
|
|
272
|
-
),
|
|
273
|
-
upstreamAccess: "available",
|
|
274
|
-
curationStatus: "included",
|
|
275
|
-
curationRank: "canonical",
|
|
276
|
-
provenance: {
|
|
277
|
-
...source!.detail.item.provenance,
|
|
278
|
-
promotedFromSuggestionId: row.id,
|
|
279
|
-
promotedFromItemId: source!.detail.item.id,
|
|
280
|
-
promotedFromItemVersionId: source!.detail.version.id,
|
|
281
|
-
},
|
|
282
|
-
metadata: {
|
|
283
|
-
promotedFromSuggestionId: row.id,
|
|
284
|
-
nativeArtifact: {
|
|
285
|
-
...source!.compiled.artifact,
|
|
286
|
-
childExternalIds: undefined,
|
|
287
|
-
manifest: undefined,
|
|
288
|
-
},
|
|
289
|
-
},
|
|
290
|
-
edges: source!.compiled.evidence.map((evidence) => ({
|
|
291
|
-
relation: "derived-from",
|
|
292
|
-
toItemId: evidence.itemId,
|
|
293
|
-
toItemVersionId: evidence.itemVersionId,
|
|
294
|
-
})),
|
|
295
|
-
},
|
|
296
|
-
],
|
|
316
|
+
items: [projectionItem!],
|
|
297
317
|
});
|
|
298
318
|
projectionItemId = ingested.itemIds[0] ?? null;
|
|
299
319
|
if (!projectionItemId) throw new Error("Failed to promote layout template");
|
|
@@ -308,11 +328,17 @@ export async function applyLayoutTemplateSuggestion(input: {
|
|
|
308
328
|
});
|
|
309
329
|
} else {
|
|
310
330
|
const { getDb, schema } = getCreativeContext();
|
|
331
|
+
const actor = requireActor();
|
|
311
332
|
if (projectionItemId) {
|
|
312
333
|
await getDb()
|
|
313
334
|
.update(schema.contextItems)
|
|
314
335
|
.set({ status: "deprecated", updatedAt: nowIso() })
|
|
315
|
-
.where(
|
|
336
|
+
.where(
|
|
337
|
+
and(
|
|
338
|
+
eq(schema.contextItems.id, projectionItemId),
|
|
339
|
+
eq(schema.contextItems.ownerEmail, actor.ownerEmail),
|
|
340
|
+
),
|
|
341
|
+
);
|
|
316
342
|
}
|
|
317
343
|
await adapter.demote({ suggestionId: row.id, projectionItemId });
|
|
318
344
|
}
|