@agent-native/creative-context 0.3.0 → 0.5.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/README.md +51 -9
- package/agent-native.package.json +4 -0
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +8 -0
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/list-context-memberships.d.ts +11 -0
- package/dist/actions/list-context-memberships.d.ts.map +1 -0
- package/dist/actions/list-context-memberships.js +47 -0
- package/dist/actions/list-context-memberships.js.map +1 -0
- package/dist/actions/list-creative-contexts.d.ts +12 -0
- package/dist/actions/list-creative-contexts.d.ts.map +1 -0
- package/dist/actions/list-creative-contexts.js +28 -0
- package/dist/actions/list-creative-contexts.js.map +1 -0
- package/dist/actions/manage-context-membership.d.ts +64 -0
- package/dist/actions/manage-context-membership.d.ts.map +1 -0
- package/dist/actions/manage-context-membership.js +70 -0
- package/dist/actions/manage-context-membership.js.map +1 -0
- package/dist/actions/manage-context-pack.d.ts +3 -0
- package/dist/actions/manage-context-pack.js +6 -0
- package/dist/actions/manage-context-pack.js.map +1 -1
- package/dist/actions/manage-context-source.js +4 -11
- package/dist/actions/manage-context-source.js.map +1 -1
- package/dist/actions/manage-creative-context.d.ts +28 -0
- package/dist/actions/manage-creative-context.d.ts.map +1 -0
- package/dist/actions/manage-creative-context.js +65 -0
- package/dist/actions/manage-creative-context.js.map +1 -0
- package/dist/actions/process-context-purge.d.ts +16 -1
- package/dist/actions/process-context-purge.js +2 -1
- package/dist/actions/process-context-purge.js.map +1 -1
- package/dist/actions/search-creative-context.d.ts +1 -0
- package/dist/actions/search-creative-context.js +2 -1
- package/dist/actions/search-creative-context.js.map +1 -1
- package/dist/client/CreativeContextChip.d.ts +5 -1
- package/dist/client/CreativeContextChip.d.ts.map +1 -1
- package/dist/client/CreativeContextChip.js +36 -7
- package/dist/client/CreativeContextChip.js.map +1 -1
- package/dist/client/CreativeContextPanel.d.ts.map +1 -1
- package/dist/client/CreativeContextPanel.js +406 -89
- package/dist/client/CreativeContextPanel.js.map +1 -1
- package/dist/client/CreativeContextShareTab.d.ts +56 -0
- package/dist/client/CreativeContextShareTab.d.ts.map +1 -0
- package/dist/client/CreativeContextShareTab.js +213 -0
- package/dist/client/CreativeContextShareTab.js.map +1 -0
- package/dist/client/actions.d.ts +210 -0
- package/dist/client/actions.d.ts.map +1 -1
- package/dist/client/actions.js +318 -0
- package/dist/client/actions.js.map +1 -1
- package/dist/client/application-state.d.ts +2 -0
- package/dist/client/application-state.d.ts.map +1 -1
- package/dist/client/application-state.js +25 -2
- package/dist/client/application-state.js.map +1 -1
- package/dist/client/index.d.ts +4 -3
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +4 -3
- 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 +77 -0
- package/dist/client/messages.js.map +1 -1
- package/dist/connectors/types.d.ts +2 -2
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/schema/index.d.ts +1592 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +95 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/migrations.d.ts.map +1 -1
- package/dist/schema/migrations.js +80 -0
- package/dist/schema/migrations.js.map +1 -1
- package/dist/server/generation-artifact-access.js +2 -0
- package/dist/server/generation-artifact-access.js.map +1 -1
- package/dist/server/generation-context.d.ts +12 -4
- package/dist/server/generation-context.d.ts.map +1 -1
- package/dist/server/generation-context.js +129 -7
- package/dist/server/generation-context.js.map +1 -1
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +16 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/isolated-a2a.d.ts +3 -0
- package/dist/server/isolated-a2a.d.ts.map +1 -1
- package/dist/server/isolated-a2a.js +3 -1
- package/dist/server/isolated-a2a.js.map +1 -1
- package/dist/server/media.d.ts +1 -1
- package/dist/server/media.d.ts.map +1 -1
- package/dist/server/media.js +24 -9
- package/dist/server/media.js.map +1 -1
- package/dist/server/native-resource-capture.d.ts +78 -0
- package/dist/server/native-resource-capture.d.ts.map +1 -0
- package/dist/server/native-resource-capture.js +94 -0
- package/dist/server/native-resource-capture.js.map +1 -0
- package/dist/server/public-serialization.js +1 -1
- package/dist/server/public-serialization.js.map +1 -1
- package/dist/server/retrieval.d.ts +1 -0
- package/dist/server/retrieval.d.ts.map +1 -1
- package/dist/server/retrieval.js +2 -0
- package/dist/server/retrieval.js.map +1 -1
- package/dist/server/safe-native-preview.d.ts +15 -0
- package/dist/server/safe-native-preview.d.ts.map +1 -0
- package/dist/server/safe-native-preview.js +149 -0
- package/dist/server/safe-native-preview.js.map +1 -0
- package/dist/store/brand.d.ts.map +1 -1
- package/dist/store/brand.js +2 -16
- package/dist/store/brand.js.map +1 -1
- package/dist/store/content.d.ts +1 -0
- package/dist/store/content.d.ts.map +1 -1
- package/dist/store/content.js +84 -20
- package/dist/store/content.js.map +1 -1
- package/dist/store/contexts.d.ts +216 -0
- package/dist/store/contexts.d.ts.map +1 -0
- package/dist/store/contexts.js +1341 -0
- package/dist/store/contexts.js.map +1 -0
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +1 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/packs.d.ts +3 -0
- package/dist/store/packs.d.ts.map +1 -1
- package/dist/store/packs.js +126 -9
- package/dist/store/packs.js.map +1 -1
- package/dist/store/purge.d.ts.map +1 -1
- package/dist/store/purge.js +103 -6
- package/dist/store/purge.js.map +1 -1
- package/dist/store/sources.d.ts.map +1 -1
- package/dist/store/sources.js +2 -15
- package/dist/store/sources.js.map +1 -1
- package/dist/store/suggestions.d.ts.map +1 -1
- package/dist/store/suggestions.js +3 -1
- package/dist/store/suggestions.js.map +1 -1
- package/dist/types.d.ts +77 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -1
- package/dist/types.js.map +1 -1
- package/docs/skills/creative-context/SKILL.md +13 -5
- package/package.json +3 -3
- package/src/actions/index.ts +8 -0
- package/src/actions/job-public-serialization.spec.ts +21 -0
- package/src/actions/list-context-memberships.ts +56 -0
- package/src/actions/list-creative-contexts.spec.ts +38 -0
- package/src/actions/list-creative-contexts.ts +32 -0
- package/src/actions/manage-context-membership.ts +80 -0
- package/src/actions/manage-context-pack.ts +6 -0
- package/src/actions/manage-context-source.ts +8 -11
- package/src/actions/manage-creative-context.ts +73 -0
- package/src/actions/process-context-purge.ts +4 -1
- package/src/actions/search-creative-context.ts +2 -1
- package/src/client/CreativeContextChip.spec.ts +32 -0
- package/src/client/CreativeContextChip.tsx +83 -14
- package/src/client/CreativeContextPanel.tsx +1947 -885
- package/src/client/CreativeContextShareTab.spec.ts +70 -0
- package/src/client/CreativeContextShareTab.tsx +657 -0
- package/src/client/actions.spec.ts +177 -0
- package/src/client/actions.ts +566 -0
- package/src/client/application-state.spec.ts +5 -0
- package/src/client/application-state.ts +30 -2
- package/src/client/index.ts +33 -0
- package/src/client/messages.ts +95 -0
- package/src/connectors/types.ts +6 -8
- package/src/eval/library-flow.acceptance.spec.tsx +1 -0
- package/src/schema/index.ts +116 -0
- package/src/schema/migrations.ts +80 -0
- package/src/server/generation-artifact-access.ts +1 -0
- package/src/server/generation-context-a2a.spec.ts +133 -0
- package/src/server/generation-context.ts +153 -7
- package/src/server/index.spec.ts +15 -0
- package/src/server/index.ts +16 -0
- package/src/server/isolated-a2a.ts +4 -1
- package/src/server/media.spec.ts +30 -2
- package/src/server/media.ts +31 -12
- package/src/server/native-resource-capture.spec.ts +121 -0
- package/src/server/native-resource-capture.ts +198 -0
- package/src/server/public-serialization.spec.ts +1 -0
- package/src/server/public-serialization.ts +1 -1
- package/src/server/retrieval.ts +3 -0
- package/src/server/safe-native-preview.spec.ts +29 -0
- package/src/server/safe-native-preview.ts +200 -0
- package/src/store/access.integration.spec.ts +1116 -0
- package/src/store/brand.ts +2 -21
- package/src/store/content.ts +217 -17
- package/src/store/contexts.ts +1826 -0
- package/src/store/index.ts +1 -0
- package/src/store/packs.ts +279 -24
- package/src/store/purge.ts +139 -6
- package/src/store/sources.ts +2 -18
- package/src/store/suggestions.ts +3 -1
- package/src/types.ts +95 -0
|
@@ -5,6 +5,10 @@ const mocks = vi.hoisted(() => ({
|
|
|
5
5
|
getContextPack: vi.fn(),
|
|
6
6
|
getCreativeContextItem: vi.fn(),
|
|
7
7
|
listAccessibleSearchDocuments: vi.fn(),
|
|
8
|
+
listCreativeContexts: vi.fn(),
|
|
9
|
+
getCreativeContextById: vi.fn(),
|
|
10
|
+
getCreativeContextAppBinding: vi.fn(),
|
|
11
|
+
createContextPack: vi.fn(),
|
|
8
12
|
performCreativeContextSearch: vi.fn(),
|
|
9
13
|
recordLocal: vi.fn(),
|
|
10
14
|
getLocal: vi.fn(),
|
|
@@ -26,6 +30,10 @@ vi.mock("../store/index.js", () => ({
|
|
|
26
30
|
getContextPack: mocks.getContextPack,
|
|
27
31
|
getCreativeContextItem: mocks.getCreativeContextItem,
|
|
28
32
|
listAccessibleSearchDocuments: mocks.listAccessibleSearchDocuments,
|
|
33
|
+
listCreativeContexts: mocks.listCreativeContexts,
|
|
34
|
+
getCreativeContextById: mocks.getCreativeContextById,
|
|
35
|
+
getCreativeContextAppBinding: mocks.getCreativeContextAppBinding,
|
|
36
|
+
createContextPack: mocks.createContextPack,
|
|
29
37
|
}));
|
|
30
38
|
|
|
31
39
|
vi.mock("../store/generation.js", () => ({
|
|
@@ -46,6 +54,9 @@ vi.mock("./isolated-a2a.js", () => ({
|
|
|
46
54
|
limit: input.limit,
|
|
47
55
|
contextPackId: input.contextPackId,
|
|
48
56
|
contextPackSource: input.contextPackSource,
|
|
57
|
+
...(input.selectedContextId
|
|
58
|
+
? { selectedContextId: input.selectedContextId }
|
|
59
|
+
: {}),
|
|
49
60
|
})),
|
|
50
61
|
}));
|
|
51
62
|
|
|
@@ -78,6 +89,9 @@ describe("generation context isolated A2A routing", () => {
|
|
|
78
89
|
async (_identity, _target, operation) => `cap-${operation}`,
|
|
79
90
|
);
|
|
80
91
|
mocks.callA2A.mockResolvedValue(emptyRemoteContext);
|
|
92
|
+
mocks.listCreativeContexts.mockResolvedValue({ contexts: [] });
|
|
93
|
+
mocks.getCreativeContextById.mockResolvedValue(null);
|
|
94
|
+
mocks.getCreativeContextAppBinding.mockResolvedValue(null);
|
|
81
95
|
});
|
|
82
96
|
|
|
83
97
|
it("routes bounded resolve, validate, read, and record operations remotely", async () => {
|
|
@@ -146,4 +160,123 @@ describe("generation context isolated A2A routing", () => {
|
|
|
146
160
|
expect(mocks.performCreativeContextSearch).toHaveBeenCalled();
|
|
147
161
|
expect(mocks.callA2A).not.toHaveBeenCalled();
|
|
148
162
|
});
|
|
163
|
+
|
|
164
|
+
it("fuses Default with the selected specialty and snapshots the provenance", async () => {
|
|
165
|
+
mocks.hasA2A.mockReturnValue(false);
|
|
166
|
+
mocks.readAppState.mockResolvedValue({
|
|
167
|
+
contextMode: "auto",
|
|
168
|
+
selectedContextId: "specialty-1",
|
|
169
|
+
});
|
|
170
|
+
mocks.listCreativeContexts.mockResolvedValue({
|
|
171
|
+
contexts: [{ id: "default-1", kind: "default" }],
|
|
172
|
+
});
|
|
173
|
+
mocks.getCreativeContextById.mockResolvedValue({
|
|
174
|
+
id: "specialty-1",
|
|
175
|
+
kind: "specialty",
|
|
176
|
+
});
|
|
177
|
+
mocks.performCreativeContextSearch
|
|
178
|
+
.mockResolvedValueOnce({
|
|
179
|
+
results: [
|
|
180
|
+
{
|
|
181
|
+
itemId: "base-item",
|
|
182
|
+
itemVersionId: "base-v1",
|
|
183
|
+
kind: "slide",
|
|
184
|
+
title: "Default evidence",
|
|
185
|
+
score: 0.5,
|
|
186
|
+
reasons: ["default"],
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
})
|
|
190
|
+
.mockResolvedValueOnce({
|
|
191
|
+
results: [
|
|
192
|
+
{
|
|
193
|
+
itemId: "specialty-item",
|
|
194
|
+
itemVersionId: "specialty-v1",
|
|
195
|
+
kind: "slide",
|
|
196
|
+
title: "Specialty evidence",
|
|
197
|
+
score: 0.5,
|
|
198
|
+
reasons: ["specialty"],
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
});
|
|
202
|
+
mocks.createContextPack.mockResolvedValue({ id: "snapshot-1" });
|
|
203
|
+
|
|
204
|
+
await expect(
|
|
205
|
+
resolveGenerationCreativeContext({ role: "slides", query: "launch" }),
|
|
206
|
+
).resolves.toMatchObject({ contextPackId: "snapshot-1" });
|
|
207
|
+
expect(mocks.performCreativeContextSearch).toHaveBeenNthCalledWith(
|
|
208
|
+
1,
|
|
209
|
+
expect.objectContaining({ contextId: "default-1", snapshot: false }),
|
|
210
|
+
);
|
|
211
|
+
expect(mocks.performCreativeContextSearch).toHaveBeenNthCalledWith(
|
|
212
|
+
2,
|
|
213
|
+
expect.objectContaining({ contextId: "specialty-1", snapshot: false }),
|
|
214
|
+
);
|
|
215
|
+
expect(mocks.createContextPack).toHaveBeenCalledWith(
|
|
216
|
+
expect.objectContaining({
|
|
217
|
+
baseContextId: "default-1",
|
|
218
|
+
specialtyContextId: "specialty-1",
|
|
219
|
+
selectionReason: "explicit specialty selection",
|
|
220
|
+
}),
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("uses one matching specialty when no explicit or app binding exists", async () => {
|
|
225
|
+
mocks.hasA2A.mockReturnValue(false);
|
|
226
|
+
mocks.listCreativeContexts.mockResolvedValue({
|
|
227
|
+
contexts: [
|
|
228
|
+
{ id: "default-1", kind: "default", name: "Default" },
|
|
229
|
+
{
|
|
230
|
+
id: "marketing-1",
|
|
231
|
+
kind: "specialty",
|
|
232
|
+
name: "Marketing",
|
|
233
|
+
description: "Campaigns, launches, and demand generation",
|
|
234
|
+
},
|
|
235
|
+
{ id: "product-1", kind: "specialty", name: "Product" },
|
|
236
|
+
],
|
|
237
|
+
});
|
|
238
|
+
mocks.performCreativeContextSearch
|
|
239
|
+
.mockResolvedValueOnce({
|
|
240
|
+
results: [
|
|
241
|
+
{
|
|
242
|
+
itemId: "base-item",
|
|
243
|
+
itemVersionId: "base-v1",
|
|
244
|
+
kind: "slide",
|
|
245
|
+
title: "Default evidence",
|
|
246
|
+
score: 0.5,
|
|
247
|
+
reasons: ["default"],
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
})
|
|
251
|
+
.mockResolvedValueOnce({ results: [] });
|
|
252
|
+
mocks.createContextPack.mockResolvedValue({ id: "snapshot-1" });
|
|
253
|
+
|
|
254
|
+
await resolveGenerationCreativeContext({
|
|
255
|
+
role: "slides",
|
|
256
|
+
query: "Build the marketing launch deck",
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(mocks.performCreativeContextSearch).toHaveBeenNthCalledWith(
|
|
260
|
+
2,
|
|
261
|
+
expect.objectContaining({ contextId: "marketing-1" }),
|
|
262
|
+
);
|
|
263
|
+
expect(mocks.createContextPack).toHaveBeenCalledWith(
|
|
264
|
+
expect.objectContaining({
|
|
265
|
+
specialtyContextId: "marketing-1",
|
|
266
|
+
selectionReason: "semantic specialty match",
|
|
267
|
+
}),
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("forwards a selected specialty to isolated resolution", async () => {
|
|
272
|
+
mocks.readAppState.mockResolvedValue({
|
|
273
|
+
contextMode: "auto",
|
|
274
|
+
selectedContextId: "specialty-1",
|
|
275
|
+
});
|
|
276
|
+
await resolveGenerationCreativeContext({ role: "design", query: "hero" });
|
|
277
|
+
expect(mocks.callA2A).toHaveBeenCalledWith(
|
|
278
|
+
"resolve",
|
|
279
|
+
expect.objectContaining({ selectedContextId: "specialty-1" }),
|
|
280
|
+
);
|
|
281
|
+
});
|
|
149
282
|
});
|
|
@@ -6,13 +6,18 @@ import {
|
|
|
6
6
|
recordGenerationCreativeContext as recordGenerationCreativeContextLocal,
|
|
7
7
|
} from "../store/generation.js";
|
|
8
8
|
import {
|
|
9
|
+
createContextPack,
|
|
10
|
+
getCreativeContextAppBinding,
|
|
11
|
+
getCreativeContextById,
|
|
9
12
|
getContextPack,
|
|
10
13
|
getCreativeContextItem,
|
|
14
|
+
listCreativeContexts,
|
|
11
15
|
listAccessibleSearchDocuments,
|
|
12
16
|
} from "../store/index.js";
|
|
13
17
|
import type {
|
|
14
18
|
CreativeContextElementProvenance,
|
|
15
19
|
CreativeContextReuseLabel,
|
|
20
|
+
CreativeContextSummary,
|
|
16
21
|
} from "../types.js";
|
|
17
22
|
import {
|
|
18
23
|
assertGenerationArtifactAccess,
|
|
@@ -32,9 +37,56 @@ import {
|
|
|
32
37
|
UNTRUSTED_REFERENCE_ROLE,
|
|
33
38
|
} from "./untrusted-reference.js";
|
|
34
39
|
|
|
35
|
-
export type CreativeGenerationRole =
|
|
40
|
+
export type CreativeGenerationRole =
|
|
41
|
+
| "slides"
|
|
42
|
+
| "design"
|
|
43
|
+
| "assets"
|
|
44
|
+
| "content"
|
|
45
|
+
| "analytics";
|
|
36
46
|
export type CreativeContextModeOverride = "off";
|
|
37
47
|
|
|
48
|
+
const SPECIALTY_STOP_WORDS = new Set([
|
|
49
|
+
"and",
|
|
50
|
+
"for",
|
|
51
|
+
"from",
|
|
52
|
+
"into",
|
|
53
|
+
"our",
|
|
54
|
+
"the",
|
|
55
|
+
"this",
|
|
56
|
+
"with",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
function specialtyTokens(value: string) {
|
|
60
|
+
return new Set(
|
|
61
|
+
value
|
|
62
|
+
.toLocaleLowerCase()
|
|
63
|
+
.split(/[^\p{L}\p{N}]+/u)
|
|
64
|
+
.filter((token) => token.length >= 3 && !SPECIALTY_STOP_WORDS.has(token)),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function selectSemanticSpecialty(
|
|
69
|
+
contexts: readonly CreativeContextSummary[],
|
|
70
|
+
query: string,
|
|
71
|
+
): CreativeContextSummary | null {
|
|
72
|
+
const queryText = query.toLocaleLowerCase();
|
|
73
|
+
const queryTokens = specialtyTokens(query);
|
|
74
|
+
let best: { context: CreativeContextSummary; score: number } | null = null;
|
|
75
|
+
for (const context of contexts) {
|
|
76
|
+
if (context.kind !== "specialty") continue;
|
|
77
|
+
const name = context.name.toLocaleLowerCase().trim();
|
|
78
|
+
const nameTokens = specialtyTokens(context.name);
|
|
79
|
+
const descriptionTokens = specialtyTokens(context.description ?? "");
|
|
80
|
+
let score = name.length >= 3 && queryText.includes(name) ? 4 : 0;
|
|
81
|
+
for (const token of queryTokens) {
|
|
82
|
+
if (nameTokens.has(token)) score += 2;
|
|
83
|
+
else if (descriptionTokens.has(token)) score += 1;
|
|
84
|
+
}
|
|
85
|
+
if (score >= 2 && (!best || score > best.score)) best = { context, score };
|
|
86
|
+
}
|
|
87
|
+
return best?.context ?? null;
|
|
88
|
+
}
|
|
89
|
+
|
|
38
90
|
function defaultArtifactAccessTarget(
|
|
39
91
|
identity: GenerationArtifactIdentity,
|
|
40
92
|
): GenerationArtifactAccessTarget | undefined {
|
|
@@ -110,6 +162,8 @@ export interface ResolveGenerationCreativeContextInput {
|
|
|
110
162
|
limit?: number;
|
|
111
163
|
contextPackId?: string;
|
|
112
164
|
contextPackSource?: "explicit" | "inherited";
|
|
165
|
+
/** Forwarded by isolated callers; local callers normally use app state. */
|
|
166
|
+
selectedContextId?: string | null;
|
|
113
167
|
contextModeOverride?: CreativeContextModeOverride;
|
|
114
168
|
}
|
|
115
169
|
|
|
@@ -201,6 +255,7 @@ export async function resolveGenerationCreativeContext(
|
|
|
201
255
|
)) as {
|
|
202
256
|
contextMode?: "auto" | "off";
|
|
203
257
|
pinnedPackId?: string | null;
|
|
258
|
+
selectedContextId?: string | null;
|
|
204
259
|
} | null;
|
|
205
260
|
if (state?.contextMode === "off") {
|
|
206
261
|
return resolveGenerationCreativeContextLocal(input);
|
|
@@ -213,6 +268,9 @@ export async function resolveGenerationCreativeContext(
|
|
|
213
268
|
contextPackId: state.pinnedPackId,
|
|
214
269
|
contextPackSource: "inherited" as const,
|
|
215
270
|
}),
|
|
271
|
+
...(state?.selectedContextId
|
|
272
|
+
? { selectedContextId: state.selectedContextId }
|
|
273
|
+
: {}),
|
|
216
274
|
});
|
|
217
275
|
}
|
|
218
276
|
return resolveGenerationCreativeContextLocal(input);
|
|
@@ -240,6 +298,7 @@ export async function resolveGenerationCreativeContextLocal(
|
|
|
240
298
|
const state = (await readAppState("creative-context").catch(() => null)) as {
|
|
241
299
|
contextMode?: "auto" | "off";
|
|
242
300
|
pinnedPackId?: string | null;
|
|
301
|
+
selectedContextId?: string | null;
|
|
243
302
|
} | null;
|
|
244
303
|
if (state?.contextMode === "off") {
|
|
245
304
|
if (
|
|
@@ -265,17 +324,104 @@ export async function resolveGenerationCreativeContextLocal(
|
|
|
265
324
|
"query is required when no exact contextPackId is supplied",
|
|
266
325
|
);
|
|
267
326
|
}
|
|
268
|
-
const
|
|
327
|
+
const selectedContextId = input.selectedContextId ?? state?.selectedContextId;
|
|
328
|
+
const [contexts, selected, bound] = await Promise.all([
|
|
329
|
+
listCreativeContexts({ limit: 100 }),
|
|
330
|
+
selectedContextId ? getCreativeContextById(selectedContextId) : null,
|
|
331
|
+
selectedContextId
|
|
332
|
+
? Promise.resolve(null)
|
|
333
|
+
: getCreativeContextAppBinding(input.role),
|
|
334
|
+
]);
|
|
335
|
+
const base =
|
|
336
|
+
selected?.kind === "default"
|
|
337
|
+
? selected
|
|
338
|
+
: (contexts.contexts.find((context) => context.kind === "default") ??
|
|
339
|
+
null);
|
|
340
|
+
const semantic =
|
|
341
|
+
!selected && !bound
|
|
342
|
+
? selectSemanticSpecialty(contexts.contexts, input.query)
|
|
343
|
+
: null;
|
|
344
|
+
const specialty =
|
|
345
|
+
selected?.kind === "specialty"
|
|
346
|
+
? selected
|
|
347
|
+
: bound?.kind === "specialty"
|
|
348
|
+
? bound
|
|
349
|
+
: semantic;
|
|
350
|
+
const searchInput = {
|
|
269
351
|
query: input.query,
|
|
270
352
|
limit: Math.max(1, Math.min(20, input.limit ?? 8)),
|
|
271
353
|
maxPerSource: 3,
|
|
272
|
-
snapshot:
|
|
273
|
-
|
|
354
|
+
snapshot: false,
|
|
355
|
+
};
|
|
356
|
+
const [baseSearch, specialtySearch] = await Promise.all([
|
|
357
|
+
base
|
|
358
|
+
? performCreativeContextSearch({ ...searchInput, contextId: base.id })
|
|
359
|
+
: performCreativeContextSearch(searchInput),
|
|
360
|
+
specialty
|
|
361
|
+
? performCreativeContextSearch({
|
|
362
|
+
...searchInput,
|
|
363
|
+
contextId: specialty.id,
|
|
364
|
+
})
|
|
365
|
+
: Promise.resolve(null),
|
|
366
|
+
]);
|
|
367
|
+
const fused = new Map<string, (typeof baseSearch.results)[number]>();
|
|
368
|
+
for (const result of baseSearch.results)
|
|
369
|
+
fused.set(`${result.itemId}:${result.itemVersionId}`, result);
|
|
370
|
+
for (const result of specialtySearch?.results ?? []) {
|
|
371
|
+
const key = `${result.itemId}:${result.itemVersionId}`;
|
|
372
|
+
const existing = fused.get(key);
|
|
373
|
+
fused.set(key, {
|
|
374
|
+
...result,
|
|
375
|
+
score: Math.max(
|
|
376
|
+
result.score + 0.15,
|
|
377
|
+
existing?.score ?? Number.NEGATIVE_INFINITY,
|
|
378
|
+
),
|
|
379
|
+
reasons: [...(existing?.reasons ?? []), "specialty context boost"],
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
const results = [...fused.values()]
|
|
383
|
+
.sort((a, b) => b.score - a.score)
|
|
384
|
+
.slice(0, searchInput.limit);
|
|
385
|
+
if (!results.length) {
|
|
386
|
+
return {
|
|
387
|
+
contextMode: "auto" as const,
|
|
388
|
+
contextPackId: null,
|
|
389
|
+
reuseLabels: [],
|
|
390
|
+
results: [],
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
const pack = await createContextPack({
|
|
394
|
+
name: `${input.role}: ${input.query.slice(0, 100)}`,
|
|
395
|
+
description: "Immutable governed-context generation snapshot.",
|
|
396
|
+
contextMode: "auto",
|
|
397
|
+
baseContextId: base?.id ?? null,
|
|
398
|
+
specialtyContextId: specialty?.id ?? null,
|
|
399
|
+
selectionReason: specialty
|
|
400
|
+
? selected?.id === specialty.id
|
|
401
|
+
? "explicit specialty selection"
|
|
402
|
+
: bound?.id === specialty.id
|
|
403
|
+
? "app specialty binding"
|
|
404
|
+
: "semantic specialty match"
|
|
405
|
+
: base
|
|
406
|
+
? "Default context"
|
|
407
|
+
: "legacy accessible corpus fallback",
|
|
408
|
+
request: {
|
|
409
|
+
query: input.query,
|
|
410
|
+
role: input.role,
|
|
411
|
+
baseContextId: base?.id ?? null,
|
|
412
|
+
specialtyContextId: specialty?.id ?? null,
|
|
413
|
+
},
|
|
414
|
+
members: results.map((result) => ({
|
|
415
|
+
itemId: result.itemId,
|
|
416
|
+
itemVersionId: result.itemVersionId,
|
|
417
|
+
reason: result.reasons.join("; ") || "governed context match",
|
|
418
|
+
score: result.score,
|
|
419
|
+
})),
|
|
274
420
|
});
|
|
275
421
|
return {
|
|
276
422
|
contextMode: "auto" as const,
|
|
277
|
-
contextPackId:
|
|
278
|
-
reuseLabels:
|
|
423
|
+
contextPackId: pack.id,
|
|
424
|
+
reuseLabels: results.map((result) => ({
|
|
279
425
|
itemId: result.itemId,
|
|
280
426
|
itemVersionId: result.itemVersionId,
|
|
281
427
|
kind: result.kind,
|
|
@@ -285,7 +431,7 @@ export async function resolveGenerationCreativeContextLocal(
|
|
|
285
431
|
.trim(),
|
|
286
432
|
dataRole: UNTRUSTED_REFERENCE_ROLE,
|
|
287
433
|
})),
|
|
288
|
-
results
|
|
434
|
+
results,
|
|
289
435
|
};
|
|
290
436
|
}
|
|
291
437
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getShareableResource } from "@agent-native/core/sharing";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import "./index.js";
|
|
5
|
+
|
|
6
|
+
describe("creative context shareable registrations", () => {
|
|
7
|
+
it("requires org membership for creative context pack user shares", () => {
|
|
8
|
+
const registration = getShareableResource("creative-context-pack");
|
|
9
|
+
|
|
10
|
+
expect(registration).toMatchObject({
|
|
11
|
+
allowPublic: false,
|
|
12
|
+
requireOrgMemberForUserShares: true,
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
});
|
package/src/server/index.ts
CHANGED
|
@@ -41,6 +41,17 @@ registerWorkspaceConnectionLifecycleListener(async (event) => {
|
|
|
41
41
|
|
|
42
42
|
function registerCreativeContextShareables(): void {
|
|
43
43
|
const getDb = () => getCreativeContext().getDb();
|
|
44
|
+
registerShareableResource({
|
|
45
|
+
type: "creative-context",
|
|
46
|
+
resourceTable: schema.creativeContexts,
|
|
47
|
+
sharesTable: schema.creativeContextShares,
|
|
48
|
+
displayName: "Creative context",
|
|
49
|
+
titleColumn: "name",
|
|
50
|
+
getResourcePath: getCreativeContextResourcePath,
|
|
51
|
+
getDb,
|
|
52
|
+
allowPublic: false,
|
|
53
|
+
requireOrgMemberForUserShares: true,
|
|
54
|
+
});
|
|
44
55
|
registerShareableResource({
|
|
45
56
|
type: "creative-context-source",
|
|
46
57
|
resourceTable: schema.contextSources,
|
|
@@ -70,6 +81,7 @@ function registerCreativeContextShareables(): void {
|
|
|
70
81
|
getResourcePath: getCreativeContextResourcePath,
|
|
71
82
|
getDb,
|
|
72
83
|
allowPublic: false,
|
|
84
|
+
requireOrgMemberForUserShares: true,
|
|
73
85
|
});
|
|
74
86
|
}
|
|
75
87
|
|
|
@@ -128,6 +140,10 @@ export * from "./prompt-provider.js";
|
|
|
128
140
|
export * from "./retrieval.js";
|
|
129
141
|
export * from "./untrusted-reference.js";
|
|
130
142
|
export * from "./media.js";
|
|
143
|
+
export * from "./native-resource-capture.js";
|
|
144
|
+
export * from "./safe-native-preview.js";
|
|
145
|
+
export { serializePrivateBlobHandle } from "../connectors/private-artifacts.js";
|
|
146
|
+
export { resolveNativeContextCloneReference } from "../store/contexts.js";
|
|
131
147
|
export {
|
|
132
148
|
CREATIVE_CONTEXT_BACKGROUND_PROCESSOR_ROUTE,
|
|
133
149
|
CREATIVE_CONTEXT_IMPORT_PROCESSOR_ROUTE,
|
|
@@ -95,10 +95,11 @@ const resolvedContextSchema = z
|
|
|
95
95
|
const resolvePayloadSchema = z
|
|
96
96
|
.object({
|
|
97
97
|
query: z.string().max(1_000).optional(),
|
|
98
|
-
role: z.enum(["slides", "design", "assets", "content"]),
|
|
98
|
+
role: z.enum(["slides", "design", "assets", "content", "analytics"]),
|
|
99
99
|
limit: z.number().int().min(1).max(20).optional(),
|
|
100
100
|
contextPackId: boundedId.optional(),
|
|
101
101
|
contextPackSource: z.enum(["explicit", "inherited"]).optional(),
|
|
102
|
+
selectedContextId: boundedId.nullable().optional(),
|
|
102
103
|
})
|
|
103
104
|
.strict();
|
|
104
105
|
const validatePayloadSchema = z
|
|
@@ -387,6 +388,7 @@ export type IsolatedResolvePayload = {
|
|
|
387
388
|
limit?: number;
|
|
388
389
|
contextPackId?: string;
|
|
389
390
|
contextPackSource?: "explicit" | "inherited";
|
|
391
|
+
selectedContextId?: string | null;
|
|
390
392
|
};
|
|
391
393
|
|
|
392
394
|
export function isolatedResolvePayload(
|
|
@@ -398,6 +400,7 @@ export function isolatedResolvePayload(
|
|
|
398
400
|
limit: input.limit,
|
|
399
401
|
contextPackId: input.contextPackId,
|
|
400
402
|
contextPackSource: input.contextPackSource,
|
|
403
|
+
selectedContextId: input.selectedContextId ?? undefined,
|
|
401
404
|
};
|
|
402
405
|
}
|
|
403
406
|
|
package/src/server/media.spec.ts
CHANGED
|
@@ -6,6 +6,7 @@ const mocks = vi.hoisted(() => ({
|
|
|
6
6
|
runWithRequestContext: vi.fn(async (_context, fn) => fn()),
|
|
7
7
|
readPrivateArtifact: vi.fn(async () => new Uint8Array([1, 2, 3])),
|
|
8
8
|
getCreativeContextItem: vi.fn(),
|
|
9
|
+
readPendingCreativeContextMedia: vi.fn(),
|
|
9
10
|
}));
|
|
10
11
|
|
|
11
12
|
vi.mock("@agent-native/core/server", () => ({
|
|
@@ -31,6 +32,7 @@ vi.mock("../connectors/private-artifacts.js", () => ({
|
|
|
31
32
|
|
|
32
33
|
vi.mock("../store/index.js", () => ({
|
|
33
34
|
getCreativeContextItem: mocks.getCreativeContextItem,
|
|
35
|
+
readPendingCreativeContextMedia: mocks.readPendingCreativeContextMedia,
|
|
34
36
|
}));
|
|
35
37
|
|
|
36
38
|
vi.mock("./context.js", () => ({
|
|
@@ -43,7 +45,7 @@ function event() {
|
|
|
43
45
|
return {
|
|
44
46
|
req: {
|
|
45
47
|
method: "GET",
|
|
46
|
-
url: "http://app.example/_agent-native/creative-context/media?itemId=item-1",
|
|
48
|
+
url: "http://app.example/_agent-native/creative-context/media?itemId=item-1&itemVersionId=version-1",
|
|
47
49
|
headers: new Headers({ origin: "http://app.example" }),
|
|
48
50
|
},
|
|
49
51
|
};
|
|
@@ -62,6 +64,7 @@ describe("creative context media route", () => {
|
|
|
62
64
|
version: { id: "version-1" },
|
|
63
65
|
media: [],
|
|
64
66
|
});
|
|
67
|
+
mocks.readPendingCreativeContextMedia.mockReset().mockResolvedValue(null);
|
|
65
68
|
await createCreativeContextMediaPlugin()({});
|
|
66
69
|
});
|
|
67
70
|
|
|
@@ -86,7 +89,32 @@ describe("creative context media route", () => {
|
|
|
86
89
|
);
|
|
87
90
|
expect(mocks.getCreativeContextItem).toHaveBeenCalledWith(
|
|
88
91
|
"item-1",
|
|
89
|
-
|
|
92
|
+
"version-1",
|
|
90
93
|
);
|
|
91
94
|
});
|
|
95
|
+
|
|
96
|
+
it("uses the narrow pending-submission path only after generic item access fails", async () => {
|
|
97
|
+
mocks.getSession.mockResolvedValue({
|
|
98
|
+
email: "bob@example.test",
|
|
99
|
+
orgId: "org-1",
|
|
100
|
+
});
|
|
101
|
+
mocks.getCreativeContextItem.mockResolvedValue(null);
|
|
102
|
+
mocks.readPendingCreativeContextMedia.mockResolvedValue({
|
|
103
|
+
itemId: "item-1",
|
|
104
|
+
itemVersionId: "version-1",
|
|
105
|
+
mediaId: null,
|
|
106
|
+
storageKey: "creative-context-blob:v1:example",
|
|
107
|
+
mimeType: "image/png",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const response = await mocks.handler!(event());
|
|
111
|
+
|
|
112
|
+
expect(response.status).toBe(200);
|
|
113
|
+
expect(mocks.readPendingCreativeContextMedia).toHaveBeenCalledWith({
|
|
114
|
+
itemId: "item-1",
|
|
115
|
+
itemVersionId: "version-1",
|
|
116
|
+
mediaId: undefined,
|
|
117
|
+
});
|
|
118
|
+
expect(await response.text()).not.toContain("creative-context-blob");
|
|
119
|
+
});
|
|
92
120
|
});
|
package/src/server/media.ts
CHANGED
|
@@ -11,7 +11,10 @@ import {
|
|
|
11
11
|
readPrivateArtifact,
|
|
12
12
|
} from "../connectors/private-artifacts.js";
|
|
13
13
|
import { CREATIVE_CONTEXT_MEDIA_ROUTE } from "../media-url.js";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
getCreativeContextItem,
|
|
16
|
+
readPendingCreativeContextMedia,
|
|
17
|
+
} from "../store/index.js";
|
|
15
18
|
import { getCreativeContext } from "./context.js";
|
|
16
19
|
|
|
17
20
|
const SAFE_MIME_TYPES = new Set([
|
|
@@ -20,6 +23,8 @@ const SAFE_MIME_TYPES = new Set([
|
|
|
20
23
|
"image/webp",
|
|
21
24
|
"image/gif",
|
|
22
25
|
"image/svg+xml",
|
|
26
|
+
"video/mp4",
|
|
27
|
+
"video/webm",
|
|
23
28
|
]);
|
|
24
29
|
|
|
25
30
|
export function createCreativeContextMediaPlugin(): NitroPluginDef {
|
|
@@ -95,23 +100,37 @@ export async function readCreativeContextMedia(input: {
|
|
|
95
100
|
}
|
|
96
101
|
if (!itemId) throw new Error("Creative context media was not found");
|
|
97
102
|
const detail = await getCreativeContextItem(itemId, itemVersionId);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
const pending = detail
|
|
104
|
+
? null
|
|
105
|
+
: await readPendingCreativeContextMedia({
|
|
106
|
+
mediaId: input.mediaId,
|
|
107
|
+
itemId,
|
|
108
|
+
itemVersionId,
|
|
109
|
+
});
|
|
110
|
+
if (!detail && !pending)
|
|
111
|
+
throw new Error("Creative context media is not accessible");
|
|
112
|
+
const media =
|
|
113
|
+
detail && input.mediaId
|
|
114
|
+
? (detail.media.find((entry) => entry.id === input.mediaId) ?? null)
|
|
115
|
+
: null;
|
|
116
|
+
const storageKey = detail
|
|
117
|
+
? (media?.storageKey ?? detail.item.thumbnailBlobRef)
|
|
118
|
+
: pending?.storageKey;
|
|
119
|
+
const handle = parsePrivateBlobHandle(storageKey);
|
|
105
120
|
if (!handle) throw new Error("Creative context media has no private blob");
|
|
106
121
|
return {
|
|
107
122
|
data: await readPrivateArtifact(
|
|
108
123
|
handle,
|
|
109
124
|
getCreativeContext().connectorContext,
|
|
110
125
|
),
|
|
111
|
-
mimeType:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
126
|
+
mimeType:
|
|
127
|
+
media?.mimeType ??
|
|
128
|
+
(detail ? detail.version.mimeType : pending?.mimeType) ??
|
|
129
|
+
handle.mimeType ??
|
|
130
|
+
"application/octet-stream",
|
|
131
|
+
itemId: detail?.item.id ?? pending!.itemId,
|
|
132
|
+
itemVersionId: detail?.version.id ?? pending!.itemVersionId,
|
|
133
|
+
mediaId: media?.id ?? pending?.mediaId ?? null,
|
|
115
134
|
media,
|
|
116
135
|
};
|
|
117
136
|
}
|