@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.
Files changed (185) hide show
  1. package/README.md +51 -9
  2. package/agent-native.package.json +4 -0
  3. package/dist/actions/index.d.ts.map +1 -1
  4. package/dist/actions/index.js +8 -0
  5. package/dist/actions/index.js.map +1 -1
  6. package/dist/actions/list-context-memberships.d.ts +11 -0
  7. package/dist/actions/list-context-memberships.d.ts.map +1 -0
  8. package/dist/actions/list-context-memberships.js +47 -0
  9. package/dist/actions/list-context-memberships.js.map +1 -0
  10. package/dist/actions/list-creative-contexts.d.ts +12 -0
  11. package/dist/actions/list-creative-contexts.d.ts.map +1 -0
  12. package/dist/actions/list-creative-contexts.js +28 -0
  13. package/dist/actions/list-creative-contexts.js.map +1 -0
  14. package/dist/actions/manage-context-membership.d.ts +64 -0
  15. package/dist/actions/manage-context-membership.d.ts.map +1 -0
  16. package/dist/actions/manage-context-membership.js +70 -0
  17. package/dist/actions/manage-context-membership.js.map +1 -0
  18. package/dist/actions/manage-context-pack.d.ts +3 -0
  19. package/dist/actions/manage-context-pack.js +6 -0
  20. package/dist/actions/manage-context-pack.js.map +1 -1
  21. package/dist/actions/manage-context-source.js +4 -11
  22. package/dist/actions/manage-context-source.js.map +1 -1
  23. package/dist/actions/manage-creative-context.d.ts +28 -0
  24. package/dist/actions/manage-creative-context.d.ts.map +1 -0
  25. package/dist/actions/manage-creative-context.js +65 -0
  26. package/dist/actions/manage-creative-context.js.map +1 -0
  27. package/dist/actions/process-context-purge.d.ts +16 -1
  28. package/dist/actions/process-context-purge.js +2 -1
  29. package/dist/actions/process-context-purge.js.map +1 -1
  30. package/dist/actions/search-creative-context.d.ts +1 -0
  31. package/dist/actions/search-creative-context.js +2 -1
  32. package/dist/actions/search-creative-context.js.map +1 -1
  33. package/dist/client/CreativeContextChip.d.ts +5 -1
  34. package/dist/client/CreativeContextChip.d.ts.map +1 -1
  35. package/dist/client/CreativeContextChip.js +36 -7
  36. package/dist/client/CreativeContextChip.js.map +1 -1
  37. package/dist/client/CreativeContextPanel.d.ts.map +1 -1
  38. package/dist/client/CreativeContextPanel.js +406 -89
  39. package/dist/client/CreativeContextPanel.js.map +1 -1
  40. package/dist/client/CreativeContextShareTab.d.ts +56 -0
  41. package/dist/client/CreativeContextShareTab.d.ts.map +1 -0
  42. package/dist/client/CreativeContextShareTab.js +213 -0
  43. package/dist/client/CreativeContextShareTab.js.map +1 -0
  44. package/dist/client/actions.d.ts +210 -0
  45. package/dist/client/actions.d.ts.map +1 -1
  46. package/dist/client/actions.js +318 -0
  47. package/dist/client/actions.js.map +1 -1
  48. package/dist/client/application-state.d.ts +2 -0
  49. package/dist/client/application-state.d.ts.map +1 -1
  50. package/dist/client/application-state.js +25 -2
  51. package/dist/client/application-state.js.map +1 -1
  52. package/dist/client/index.d.ts +4 -3
  53. package/dist/client/index.d.ts.map +1 -1
  54. package/dist/client/index.js +4 -3
  55. package/dist/client/index.js.map +1 -1
  56. package/dist/client/messages.d.ts +1 -1
  57. package/dist/client/messages.d.ts.map +1 -1
  58. package/dist/client/messages.js +77 -0
  59. package/dist/client/messages.js.map +1 -1
  60. package/dist/connectors/types.d.ts +2 -2
  61. package/dist/connectors/types.d.ts.map +1 -1
  62. package/dist/schema/index.d.ts +1592 -0
  63. package/dist/schema/index.d.ts.map +1 -1
  64. package/dist/schema/index.js +95 -0
  65. package/dist/schema/index.js.map +1 -1
  66. package/dist/schema/migrations.d.ts.map +1 -1
  67. package/dist/schema/migrations.js +80 -0
  68. package/dist/schema/migrations.js.map +1 -1
  69. package/dist/server/generation-artifact-access.js +2 -0
  70. package/dist/server/generation-artifact-access.js.map +1 -1
  71. package/dist/server/generation-context.d.ts +12 -4
  72. package/dist/server/generation-context.d.ts.map +1 -1
  73. package/dist/server/generation-context.js +129 -7
  74. package/dist/server/generation-context.js.map +1 -1
  75. package/dist/server/index.d.ts +4 -0
  76. package/dist/server/index.d.ts.map +1 -1
  77. package/dist/server/index.js +16 -0
  78. package/dist/server/index.js.map +1 -1
  79. package/dist/server/isolated-a2a.d.ts +3 -0
  80. package/dist/server/isolated-a2a.d.ts.map +1 -1
  81. package/dist/server/isolated-a2a.js +3 -1
  82. package/dist/server/isolated-a2a.js.map +1 -1
  83. package/dist/server/media.d.ts +1 -1
  84. package/dist/server/media.d.ts.map +1 -1
  85. package/dist/server/media.js +24 -9
  86. package/dist/server/media.js.map +1 -1
  87. package/dist/server/native-resource-capture.d.ts +78 -0
  88. package/dist/server/native-resource-capture.d.ts.map +1 -0
  89. package/dist/server/native-resource-capture.js +94 -0
  90. package/dist/server/native-resource-capture.js.map +1 -0
  91. package/dist/server/public-serialization.js +1 -1
  92. package/dist/server/public-serialization.js.map +1 -1
  93. package/dist/server/retrieval.d.ts +1 -0
  94. package/dist/server/retrieval.d.ts.map +1 -1
  95. package/dist/server/retrieval.js +2 -0
  96. package/dist/server/retrieval.js.map +1 -1
  97. package/dist/server/safe-native-preview.d.ts +15 -0
  98. package/dist/server/safe-native-preview.d.ts.map +1 -0
  99. package/dist/server/safe-native-preview.js +149 -0
  100. package/dist/server/safe-native-preview.js.map +1 -0
  101. package/dist/store/brand.d.ts.map +1 -1
  102. package/dist/store/brand.js +2 -16
  103. package/dist/store/brand.js.map +1 -1
  104. package/dist/store/content.d.ts +1 -0
  105. package/dist/store/content.d.ts.map +1 -1
  106. package/dist/store/content.js +84 -20
  107. package/dist/store/content.js.map +1 -1
  108. package/dist/store/contexts.d.ts +216 -0
  109. package/dist/store/contexts.d.ts.map +1 -0
  110. package/dist/store/contexts.js +1341 -0
  111. package/dist/store/contexts.js.map +1 -0
  112. package/dist/store/index.d.ts +1 -0
  113. package/dist/store/index.d.ts.map +1 -1
  114. package/dist/store/index.js +1 -0
  115. package/dist/store/index.js.map +1 -1
  116. package/dist/store/packs.d.ts +3 -0
  117. package/dist/store/packs.d.ts.map +1 -1
  118. package/dist/store/packs.js +126 -9
  119. package/dist/store/packs.js.map +1 -1
  120. package/dist/store/purge.d.ts.map +1 -1
  121. package/dist/store/purge.js +103 -6
  122. package/dist/store/purge.js.map +1 -1
  123. package/dist/store/sources.d.ts.map +1 -1
  124. package/dist/store/sources.js +2 -15
  125. package/dist/store/sources.js.map +1 -1
  126. package/dist/store/suggestions.d.ts.map +1 -1
  127. package/dist/store/suggestions.js +3 -1
  128. package/dist/store/suggestions.js.map +1 -1
  129. package/dist/types.d.ts +77 -0
  130. package/dist/types.d.ts.map +1 -1
  131. package/dist/types.js +9 -1
  132. package/dist/types.js.map +1 -1
  133. package/docs/skills/creative-context/SKILL.md +13 -5
  134. package/package.json +3 -3
  135. package/src/actions/index.ts +8 -0
  136. package/src/actions/job-public-serialization.spec.ts +21 -0
  137. package/src/actions/list-context-memberships.ts +56 -0
  138. package/src/actions/list-creative-contexts.spec.ts +38 -0
  139. package/src/actions/list-creative-contexts.ts +32 -0
  140. package/src/actions/manage-context-membership.ts +80 -0
  141. package/src/actions/manage-context-pack.ts +6 -0
  142. package/src/actions/manage-context-source.ts +8 -11
  143. package/src/actions/manage-creative-context.ts +73 -0
  144. package/src/actions/process-context-purge.ts +4 -1
  145. package/src/actions/search-creative-context.ts +2 -1
  146. package/src/client/CreativeContextChip.spec.ts +32 -0
  147. package/src/client/CreativeContextChip.tsx +83 -14
  148. package/src/client/CreativeContextPanel.tsx +1947 -885
  149. package/src/client/CreativeContextShareTab.spec.ts +70 -0
  150. package/src/client/CreativeContextShareTab.tsx +657 -0
  151. package/src/client/actions.spec.ts +177 -0
  152. package/src/client/actions.ts +566 -0
  153. package/src/client/application-state.spec.ts +5 -0
  154. package/src/client/application-state.ts +30 -2
  155. package/src/client/index.ts +33 -0
  156. package/src/client/messages.ts +95 -0
  157. package/src/connectors/types.ts +6 -8
  158. package/src/eval/library-flow.acceptance.spec.tsx +1 -0
  159. package/src/schema/index.ts +116 -0
  160. package/src/schema/migrations.ts +80 -0
  161. package/src/server/generation-artifact-access.ts +1 -0
  162. package/src/server/generation-context-a2a.spec.ts +133 -0
  163. package/src/server/generation-context.ts +153 -7
  164. package/src/server/index.spec.ts +15 -0
  165. package/src/server/index.ts +16 -0
  166. package/src/server/isolated-a2a.ts +4 -1
  167. package/src/server/media.spec.ts +30 -2
  168. package/src/server/media.ts +31 -12
  169. package/src/server/native-resource-capture.spec.ts +121 -0
  170. package/src/server/native-resource-capture.ts +198 -0
  171. package/src/server/public-serialization.spec.ts +1 -0
  172. package/src/server/public-serialization.ts +1 -1
  173. package/src/server/retrieval.ts +3 -0
  174. package/src/server/safe-native-preview.spec.ts +29 -0
  175. package/src/server/safe-native-preview.ts +200 -0
  176. package/src/store/access.integration.spec.ts +1116 -0
  177. package/src/store/brand.ts +2 -21
  178. package/src/store/content.ts +217 -17
  179. package/src/store/contexts.ts +1826 -0
  180. package/src/store/index.ts +1 -0
  181. package/src/store/packs.ts +279 -24
  182. package/src/store/purge.ts +139 -6
  183. package/src/store/sources.ts +2 -18
  184. package/src/store/suggestions.ts +3 -1
  185. 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 = "slides" | "design" | "assets" | "content";
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 search = await performCreativeContextSearch({
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: true,
273
- contextPackName: `${input.role}: ${input.query.slice(0, 100)}`,
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: search.contextPackId,
278
- reuseLabels: search.results.map((result) => ({
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: search.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
+ });
@@ -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
 
@@ -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
- undefined,
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
  });
@@ -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 { getCreativeContextItem } from "../store/index.js";
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
- if (!detail) throw new Error("Creative context media is not accessible");
99
- const media = input.mediaId
100
- ? detail.media.find((entry) => entry.id === input.mediaId)
101
- : null;
102
- const handle = parsePrivateBlobHandle(
103
- media?.storageKey ?? detail.item.thumbnailBlobRef,
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: media?.mimeType ?? handle.mimeType ?? "application/octet-stream",
112
- itemId: detail.item.id,
113
- itemVersionId: detail.version.id,
114
- mediaId: media?.id ?? null,
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
  }