@agent-native/core 0.78.0 → 0.78.2

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 (96) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +15 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +269 -237
  5. package/corpus/core/src/agent/types.ts +22 -0
  6. package/corpus/core/src/client/AgentPanel.tsx +8 -0
  7. package/corpus/core/src/client/AssistantChat.tsx +40 -0
  8. package/corpus/core/src/client/agent-chat.ts +173 -0
  9. package/corpus/core/src/client/composer/TiptapComposer.tsx +316 -60
  10. package/corpus/core/src/client/composer/extensions/MentionReference.tsx +3 -0
  11. package/corpus/core/src/client/composer/types.ts +22 -0
  12. package/corpus/core/src/client/index.ts +5 -0
  13. package/corpus/core/src/server/agent-chat-plugin.ts +10 -0
  14. package/corpus/core/src/server/request-context.ts +8 -0
  15. package/corpus/templates/analytics/server/plugins/agent-chat.ts +13 -0
  16. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +10 -2
  17. package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +14 -7
  18. package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +12 -0
  19. package/corpus/templates/assets/AGENTS.md +18 -3
  20. package/corpus/templates/assets/actions/_image-model-default.ts +24 -0
  21. package/corpus/templates/assets/actions/dismiss-variant-slots.ts +15 -22
  22. package/corpus/templates/assets/actions/generate-asset.ts +30 -23
  23. package/corpus/templates/assets/actions/generate-image-batch.ts +76 -29
  24. package/corpus/templates/assets/actions/generate-image.ts +46 -30
  25. package/corpus/templates/assets/actions/generate-video.ts +18 -3
  26. package/corpus/templates/assets/actions/list-assets.ts +39 -8
  27. package/corpus/templates/assets/actions/list-libraries.ts +24 -2
  28. package/corpus/templates/assets/actions/navigate.ts +1 -1
  29. package/corpus/templates/assets/actions/open-asset-picker.ts +38 -1
  30. package/corpus/templates/assets/actions/refresh-generation-run.ts +10 -0
  31. package/corpus/templates/assets/actions/save-generated-image.ts +21 -19
  32. package/corpus/templates/assets/actions/variant-slots.ts +131 -28
  33. package/corpus/templates/assets/actions/view-screen.ts +9 -0
  34. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +425 -0
  35. package/corpus/templates/assets/app/components/layout/Header.tsx +3 -1
  36. package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
  37. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +4 -6
  38. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +47 -21
  39. package/corpus/templates/assets/app/i18n-data.ts +1815 -0
  40. package/corpus/templates/assets/app/lib/libraries.ts +6 -0
  41. package/corpus/templates/assets/app/lib/picker-chat-handoff.ts +71 -0
  42. package/corpus/templates/assets/app/routes/_index.tsx +4 -0
  43. package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
  44. package/corpus/templates/assets/app/routes/audit.tsx +1 -1
  45. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +1557 -2164
  46. package/corpus/templates/assets/app/routes/brand-kits._index.tsx +7 -191
  47. package/corpus/templates/assets/app/routes/brand-kits.tsx +1 -5
  48. package/corpus/templates/assets/app/routes/libraries.tsx +4 -4
  49. package/corpus/templates/assets/app/routes/library.$id.tsx +9 -7
  50. package/corpus/templates/assets/app/routes/library.tsx +1454 -175
  51. package/corpus/templates/assets/server/db/index.ts +1 -1
  52. package/corpus/templates/assets/server/lib/generation.ts +86 -15
  53. package/corpus/templates/assets/server/plugins/agent-chat.ts +196 -0
  54. package/corpus/templates/assets/server/plugins/db.ts +5 -0
  55. package/corpus/templates/assets/shared/api.ts +6 -3
  56. package/dist/agent/production-agent.d.ts.map +1 -1
  57. package/dist/agent/production-agent.js +50 -29
  58. package/dist/agent/production-agent.js.map +1 -1
  59. package/dist/agent/types.d.ts +21 -0
  60. package/dist/agent/types.d.ts.map +1 -1
  61. package/dist/agent/types.js.map +1 -1
  62. package/dist/client/AgentPanel.d.ts +5 -1
  63. package/dist/client/AgentPanel.d.ts.map +1 -1
  64. package/dist/client/AgentPanel.js +2 -2
  65. package/dist/client/AgentPanel.js.map +1 -1
  66. package/dist/client/AssistantChat.d.ts +6 -0
  67. package/dist/client/AssistantChat.d.ts.map +1 -1
  68. package/dist/client/AssistantChat.js +15 -3
  69. package/dist/client/AssistantChat.js.map +1 -1
  70. package/dist/client/agent-chat.d.ts +32 -0
  71. package/dist/client/agent-chat.d.ts.map +1 -1
  72. package/dist/client/agent-chat.js +113 -0
  73. package/dist/client/agent-chat.js.map +1 -1
  74. package/dist/client/composer/TiptapComposer.d.ts +2 -1
  75. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  76. package/dist/client/composer/TiptapComposer.js +228 -56
  77. package/dist/client/composer/TiptapComposer.js.map +1 -1
  78. package/dist/client/composer/extensions/MentionReference.d.ts.map +1 -1
  79. package/dist/client/composer/extensions/MentionReference.js +3 -0
  80. package/dist/client/composer/extensions/MentionReference.js.map +1 -1
  81. package/dist/client/composer/types.d.ts +21 -0
  82. package/dist/client/composer/types.d.ts.map +1 -1
  83. package/dist/client/composer/types.js.map +1 -1
  84. package/dist/client/index.d.ts +1 -1
  85. package/dist/client/index.d.ts.map +1 -1
  86. package/dist/client/index.js +1 -1
  87. package/dist/client/index.js.map +1 -1
  88. package/dist/notifications/routes.d.ts +1 -1
  89. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  90. package/dist/server/agent-chat-plugin.js +5 -0
  91. package/dist/server/agent-chat-plugin.js.map +1 -1
  92. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  93. package/dist/server/request-context.d.ts +8 -0
  94. package/dist/server/request-context.d.ts.map +1 -1
  95. package/dist/server/request-context.js.map +1 -1
  96. package/package.json +1 -1
@@ -31,8 +31,17 @@ import {
31
31
  reasoningEffortLabel,
32
32
  type ReasoningEffort,
33
33
  } from "../../shared/reasoning-effort.js";
34
- import { sendToAgentChat, type AgentChatContextItem } from "../agent-chat.js";
34
+ import {
35
+ AgentComposerReference,
36
+ normalizeAgentComposerReference,
37
+ sendToAgentChat,
38
+ type AgentChatContextItem,
39
+ AgentComposerReferenceInsertPayload,
40
+ AGENT_CHAT_INSERT_REFERENCE_EVENT,
41
+ AGENT_CHAT_INSERT_REFERENCE_MESSAGE_TYPE,
42
+ } from "../agent-chat.js";
35
43
  import { tryDelegateBuildRequestToBuilder } from "../builder-frame.js";
44
+ import { isTrustedBuilderMessage } from "../builder-frame.js";
36
45
  import {
37
46
  Popover,
38
47
  PopoverContent,
@@ -43,6 +52,7 @@ import {
43
52
  TooltipContent,
44
53
  TooltipTrigger,
45
54
  } from "../components/ui/tooltip.js";
55
+ import { isTrustedFrameMessage } from "../frame.js";
46
56
  import { useBuilderConnectFlow } from "../settings/useBuilderStatus.js";
47
57
  import { ComposerPlusMenu } from "./ComposerPlusMenu.js";
48
58
  import { getComposerDraftKey } from "./draft-key.js";
@@ -67,10 +77,10 @@ import { useMentionSearch } from "./use-mention-search.js";
67
77
  import { useSkills } from "./use-skills.js";
68
78
  import { useVoiceDictation } from "./useVoiceDictation.js";
69
79
  import { VoiceButton, VoiceRecordingOverlay } from "./VoiceButton.js";
70
-
71
80
  export interface TiptapComposerHandle {
72
81
  focus(): void;
73
82
  setText(text: string): void;
83
+ insertReference(ref: AgentComposerReference): void;
74
84
  }
75
85
 
76
86
  export type ComposerSubmitIntent = "immediate" | "queued";
@@ -94,6 +104,120 @@ export function canSubmitComposerContent(options: {
94
104
 
95
105
  const MAX_DOCUMENT_ATTACHMENT_BYTES = 4 * 1024 * 1024;
96
106
 
107
+ function composerReferenceFromMentionItem(
108
+ item: MentionItem,
109
+ ): AgentComposerReference {
110
+ return {
111
+ label: item.label,
112
+ icon: item.icon || "file",
113
+ source: item.source,
114
+ refType: item.refType,
115
+ refId: item.refId || null,
116
+ refPath: item.refPath || null,
117
+ slotKey: item.slotKey,
118
+ slotLabel: item.slotLabel,
119
+ metadata: item.metadata,
120
+ clearsSlots: item.clearsSlots,
121
+ relatedReferences: item.relatedReferences,
122
+ };
123
+ }
124
+
125
+ function mentionReferenceAttrs(ref: AgentComposerReference) {
126
+ return {
127
+ label: ref.label,
128
+ icon: ref.icon || "file",
129
+ source: ref.source,
130
+ refType: ref.refType,
131
+ refId: ref.refId || null,
132
+ refPath: ref.refPath || null,
133
+ slotKey: ref.slotKey || null,
134
+ slotLabel: ref.slotLabel || null,
135
+ metadata: ref.metadata || null,
136
+ };
137
+ }
138
+
139
+ function referenceFromComposerReference(
140
+ ref: AgentComposerReference,
141
+ ): Reference {
142
+ return {
143
+ type:
144
+ ref.refType === "file"
145
+ ? "file"
146
+ : ref.refType === "agent"
147
+ ? "agent"
148
+ : ref.refType === "custom-agent"
149
+ ? "custom-agent"
150
+ : "mention",
151
+ path: ref.refPath || "",
152
+ name: ref.label,
153
+ source: ref.source || "",
154
+ refType: ref.refType,
155
+ refId: ref.refId || undefined,
156
+ slotKey: ref.slotKey,
157
+ slotLabel: ref.slotLabel,
158
+ metadata: ref.metadata,
159
+ };
160
+ }
161
+
162
+ function applySlotReferenceChanges(
163
+ current: AgentComposerReference[],
164
+ references: AgentComposerReference[],
165
+ ): AgentComposerReference[] {
166
+ let next = current;
167
+
168
+ const applyOne = (ref: AgentComposerReference) => {
169
+ for (const related of ref.relatedReferences ?? []) {
170
+ applyOne(related);
171
+ }
172
+ if (!ref.slotKey) return;
173
+ const cleared = new Set([ref.slotKey, ...(ref.clearsSlots ?? [])]);
174
+ next = next.filter((existing) => !cleared.has(existing.slotKey ?? ""));
175
+ next = [...next, ref];
176
+ };
177
+
178
+ for (const ref of references) {
179
+ applyOne(ref);
180
+ }
181
+
182
+ return next;
183
+ }
184
+
185
+ function removeSlotReference(
186
+ current: AgentComposerReference[],
187
+ ref: AgentComposerReference,
188
+ ): AgentComposerReference[] {
189
+ const removed = new Set([ref.slotKey, ...(ref.clearsSlots ?? [])]);
190
+ return current.filter((existing) => !removed.has(existing.slotKey ?? ""));
191
+ }
192
+
193
+ function slotReferenceTitle(ref: AgentComposerReference): string {
194
+ return ref.slotLabel ? `${ref.slotLabel}: ${ref.label}` : ref.label;
195
+ }
196
+
197
+ function metadataString(
198
+ metadata: Record<string, unknown> | undefined,
199
+ key: string,
200
+ ): string | null {
201
+ const value = metadata?.[key];
202
+ return typeof value === "string" && value.trim() ? value.trim() : null;
203
+ }
204
+
205
+ function filterMentionItemsForSlots(
206
+ items: MentionItem[],
207
+ slotReferences: AgentComposerReference[],
208
+ ): MentionItem[] {
209
+ return items.filter((item) => {
210
+ const requiredSlotKey = metadataString(item.metadata, "requiredSlotKey");
211
+ const requiredRefId = metadataString(item.metadata, "requiredRefId");
212
+ if (!requiredSlotKey || !requiredRefId) return true;
213
+ const selected = slotReferences.find(
214
+ (ref) => ref.slotKey === requiredSlotKey,
215
+ );
216
+ if (!selected?.refId) return true;
217
+ return selected.refId === requiredRefId;
218
+ });
219
+ }
220
+
97
221
  function isDocumentAttachment(value: Record<string, unknown>): boolean {
98
222
  if (value.type === "document") return true;
99
223
  const contentType = String(value.contentType ?? "").toLowerCase();
@@ -165,6 +289,26 @@ function uniquifyComposerImageFile(file: File): File {
165
289
  return new File([file], uniqueName, { type: file.type });
166
290
  }
167
291
 
292
+ function composerDocumentHasContent(doc: {
293
+ textContent: string;
294
+ descendants: (callback: (node: any) => boolean | void) => void;
295
+ }): boolean {
296
+ if (doc.textContent.trim().length > 0) return true;
297
+ let hasContent = false;
298
+ doc.descendants((node: any) => {
299
+ if (
300
+ node.type.name === "mentionReference" ||
301
+ node.type.name === "fileReference" ||
302
+ node.type.name === "skillReference"
303
+ ) {
304
+ hasContent = true;
305
+ return false;
306
+ }
307
+ return true;
308
+ });
309
+ return hasContent;
310
+ }
311
+
168
312
  export function handleComposerFileDrop(options: {
169
313
  event: Pick<DragEvent, "dataTransfer" | "preventDefault" | "stopPropagation">;
170
314
  addAttachment: (file: File) => Promise<unknown>;
@@ -1109,14 +1253,17 @@ export function TiptapComposer({
1109
1253
  const popoverRef = useRef<MentionPopoverRef>(null);
1110
1254
  const composerRuntime = useComposerRuntime();
1111
1255
  const [editorHasText, setEditorHasText] = useState(false);
1256
+ const [slotReferences, setSlotReferences] = useState<
1257
+ AgentComposerReference[]
1258
+ >([]);
1112
1259
  const composerText = useComposer((state) => state.text);
1113
1260
  const composerAttachments = useComposer((state) => state.attachments);
1114
1261
  const canSend = canSubmitComposerContent({
1115
- hasEditorContent: editorHasText,
1262
+ hasEditorContent: editorHasText || slotReferences.length > 0,
1116
1263
  attachmentCount: composerAttachments.length,
1117
1264
  disabled,
1118
1265
  });
1119
- const hasContextItems = contextItems.length > 0;
1266
+ const hasContextRows = contextItems.length > 0 || slotReferences.length > 0;
1120
1267
  const [composerMode, setComposerMode] = useState<ComposerMode | null>(null);
1121
1268
  const composerModeRef = useRef<ComposerMode | null>(null);
1122
1269
  const isMac =
@@ -1138,6 +1285,10 @@ export function TiptapComposer({
1138
1285
  popover?.type === "@" ? popover.query : "",
1139
1286
  popover?.type === "@",
1140
1287
  );
1288
+ const filteredMentionItems = useMemo(
1289
+ () => filterMentionItemsForSlots(mentionItems, slotReferences),
1290
+ [mentionItems, slotReferences],
1291
+ );
1141
1292
 
1142
1293
  const {
1143
1294
  skills,
@@ -1186,8 +1337,8 @@ export function TiptapComposer({
1186
1337
  }, [allSlashSkills, popover]);
1187
1338
 
1188
1339
  // Keep refs in sync with state
1189
- const mentionItemsRef = useRef(mentionItems);
1190
- mentionItemsRef.current = mentionItems;
1340
+ const mentionItemsRef = useRef(filteredMentionItems);
1341
+ mentionItemsRef.current = filteredMentionItems;
1191
1342
  const filteredCommandsRef = useRef(filteredCommands);
1192
1343
  filteredCommandsRef.current = filteredCommands;
1193
1344
  const filteredSkillsRef = useRef(filteredSkills);
@@ -1197,6 +1348,7 @@ export function TiptapComposer({
1197
1348
  const onTextChangeRef = useRef(onTextChange);
1198
1349
  onTextChangeRef.current = onTextChange;
1199
1350
  const initialTextKeyRef = useRef<string | number | undefined>(undefined);
1351
+ const seenReferenceInsertIdsRef = useRef<Set<string>>(new Set());
1200
1352
 
1201
1353
  const closePopover = useCallback(() => {
1202
1354
  setPopover(null);
@@ -1229,14 +1381,14 @@ export function TiptapComposer({
1229
1381
  if (initialText !== undefined) {
1230
1382
  ed.commands.setContent(plainTextToDoc(initialText));
1231
1383
  ed.commands.focus("end");
1232
- setEditorHasText(ed.state.doc.textContent.trim().length > 0);
1384
+ setEditorHasText(composerDocumentHasContent(ed.state.doc));
1233
1385
  initialTextKeyRef.current = initialTextKey ?? initialText;
1234
1386
  } else {
1235
1387
  const saved = localStorage.getItem(draftKey);
1236
1388
  if (saved) {
1237
1389
  ed.commands.setContent(saved);
1238
1390
  ed.commands.focus("end");
1239
- setEditorHasText(ed.state.doc.textContent.trim().length > 0);
1391
+ setEditorHasText(composerDocumentHasContent(ed.state.doc));
1240
1392
  }
1241
1393
  }
1242
1394
  onTextChangeRef.current?.(ed.state.doc.textContent.trim());
@@ -1245,21 +1397,7 @@ export function TiptapComposer({
1245
1397
  onUpdate: ({ editor: ed }) => {
1246
1398
  // Drive the send button's enabled state from the actual editor contents;
1247
1399
  // the composer runtime is only synced on submit, so its isEmpty lags.
1248
- let hasContent = ed.state.doc.textContent.trim().length > 0;
1249
- if (!hasContent) {
1250
- ed.state.doc.descendants((node: any) => {
1251
- if (
1252
- node.type.name === "mentionReference" ||
1253
- node.type.name === "fileReference" ||
1254
- node.type.name === "skillReference"
1255
- ) {
1256
- hasContent = true;
1257
- return false;
1258
- }
1259
- return true;
1260
- });
1261
- }
1262
- setEditorHasText(hasContent);
1400
+ setEditorHasText(composerDocumentHasContent(ed.state.doc));
1263
1401
  onTextChangeRef.current?.(ed.state.doc.textContent.trim());
1264
1402
 
1265
1403
  // Debounce-save draft to localStorage
@@ -1267,7 +1405,7 @@ export function TiptapComposer({
1267
1405
  draftTimerRef.current = setTimeout(() => {
1268
1406
  try {
1269
1407
  const html = ed.getHTML();
1270
- const isEmpty = !ed.state.doc.textContent.trim();
1408
+ const isEmpty = !composerDocumentHasContent(ed.state.doc);
1271
1409
  if (isEmpty) {
1272
1410
  localStorage.removeItem(draftKey);
1273
1411
  } else {
@@ -1503,6 +1641,106 @@ export function TiptapComposer({
1503
1641
  },
1504
1642
  });
1505
1643
 
1644
+ const insertReference = useCallback(
1645
+ (ref: AgentComposerReference) => {
1646
+ const normalized = normalizeAgentComposerReference(ref);
1647
+ const ed = editor;
1648
+ if (!normalized || !ed) return;
1649
+ if (normalized.slotKey) {
1650
+ setSlotReferences((current) =>
1651
+ applySlotReferenceChanges(current, [normalized]),
1652
+ );
1653
+ ed.commands.focus("end");
1654
+ return;
1655
+ }
1656
+ if (normalized.relatedReferences?.some((item) => item.slotKey)) {
1657
+ setSlotReferences((current) =>
1658
+ applySlotReferenceChanges(
1659
+ current,
1660
+ normalized.relatedReferences ?? [],
1661
+ ),
1662
+ );
1663
+ }
1664
+ ed.chain()
1665
+ .focus()
1666
+ .insertContent({
1667
+ type: "mentionReference",
1668
+ attrs: mentionReferenceAttrs(normalized),
1669
+ })
1670
+ .insertContent(" ")
1671
+ .run();
1672
+ setEditorHasText(true);
1673
+ },
1674
+ [editor],
1675
+ );
1676
+
1677
+ const insertReferenceIfEmpty = useCallback(
1678
+ (payload: AgentComposerReferenceInsertPayload) => {
1679
+ const insertMessageId =
1680
+ typeof payload.insertMessageId === "string"
1681
+ ? payload.insertMessageId
1682
+ : "";
1683
+ if (insertMessageId) {
1684
+ if (seenReferenceInsertIdsRef.current.has(insertMessageId)) return;
1685
+ seenReferenceInsertIdsRef.current.add(insertMessageId);
1686
+ }
1687
+ const ed = editor;
1688
+ if (!ed || disabled || composerModeRef.current) return;
1689
+ const normalized = normalizeAgentComposerReference(payload);
1690
+ if (normalized?.slotKey) {
1691
+ insertReference(normalized);
1692
+ return;
1693
+ }
1694
+ if (composerAttachments.length > 0) return;
1695
+ if (composerDocumentHasContent(ed.state.doc)) return;
1696
+ insertReference(payload);
1697
+ },
1698
+ [composerAttachments.length, disabled, editor, insertReference],
1699
+ );
1700
+
1701
+ useEffect(() => {
1702
+ if (typeof window === "undefined") return;
1703
+ const handleEvent = (event: Event) => {
1704
+ const payload = (event as CustomEvent).detail;
1705
+ const normalized = normalizeAgentComposerReference(payload);
1706
+ if (!normalized) return;
1707
+ insertReferenceIfEmpty({
1708
+ ...normalized,
1709
+ insertMessageId:
1710
+ typeof payload?.insertMessageId === "string"
1711
+ ? payload.insertMessageId
1712
+ : "",
1713
+ });
1714
+ };
1715
+ const handleMessage = (event: MessageEvent) => {
1716
+ if (!isTrustedFrameMessage(event) && !isTrustedBuilderMessage(event)) {
1717
+ return;
1718
+ }
1719
+ if (event.data?.type !== AGENT_CHAT_INSERT_REFERENCE_MESSAGE_TYPE) {
1720
+ return;
1721
+ }
1722
+ const payload = event.data.data;
1723
+ const normalized = normalizeAgentComposerReference(payload);
1724
+ if (!normalized) return;
1725
+ insertReferenceIfEmpty({
1726
+ ...normalized,
1727
+ insertMessageId:
1728
+ typeof payload?.insertMessageId === "string"
1729
+ ? payload.insertMessageId
1730
+ : "",
1731
+ });
1732
+ };
1733
+ window.addEventListener(AGENT_CHAT_INSERT_REFERENCE_EVENT, handleEvent);
1734
+ window.addEventListener("message", handleMessage);
1735
+ return () => {
1736
+ window.removeEventListener(
1737
+ AGENT_CHAT_INSERT_REFERENCE_EVENT,
1738
+ handleEvent,
1739
+ );
1740
+ window.removeEventListener("message", handleMessage);
1741
+ };
1742
+ }, [insertReferenceIfEmpty]);
1743
+
1506
1744
  useImperativeHandle(focusRef, () => ({
1507
1745
  focus() {
1508
1746
  editor?.commands.focus("end");
@@ -1513,6 +1751,7 @@ export function TiptapComposer({
1513
1751
  editor.commands.focus("end");
1514
1752
  const trimmed = editor.state.doc.textContent.trim();
1515
1753
  setEditorHasText(trimmed.length > 0);
1754
+ setSlotReferences([]);
1516
1755
  composerRuntime.setText(trimmed);
1517
1756
  onTextChangeRef.current?.(trimmed);
1518
1757
  try {
@@ -1523,6 +1762,7 @@ export function TiptapComposer({
1523
1762
  }
1524
1763
  } catch {}
1525
1764
  },
1765
+ insertReference,
1526
1766
  }));
1527
1767
 
1528
1768
  const handleSelectMode = useCallback(
@@ -1663,10 +1903,15 @@ export function TiptapComposer({
1663
1903
  const extractComposerPayload = useCallback(() => {
1664
1904
  const ed = editor;
1665
1905
  if (!ed) {
1666
- return { text: "", references: [] as Reference[] };
1906
+ return {
1907
+ text: slotReferences.map((ref) => slotReferenceTitle(ref)).join(", "),
1908
+ references: slotReferences.map(referenceFromComposerReference),
1909
+ };
1667
1910
  }
1668
1911
 
1669
- const references: Reference[] = [];
1912
+ const references: Reference[] = slotReferences.map(
1913
+ referenceFromComposerReference,
1914
+ );
1670
1915
 
1671
1916
  // Build text that preserves @mentions (getText() strips them).
1672
1917
  // Walk the document and reconstruct with @name for mention/file/skill nodes.
@@ -1691,7 +1936,10 @@ export function TiptapComposer({
1691
1936
  textParts.push("\n");
1692
1937
  }
1693
1938
  });
1694
- const text = textParts.join("").trim();
1939
+ const rawText = textParts.join("").trim();
1940
+ const text =
1941
+ rawText ||
1942
+ slotReferences.map((ref) => slotReferenceTitle(ref)).join(", ");
1695
1943
 
1696
1944
  ed.state.doc.descendants((node: any) => {
1697
1945
  if (node.type.name === "fileReference") {
@@ -1718,6 +1966,9 @@ export function TiptapComposer({
1718
1966
  source: node.attrs.source,
1719
1967
  refType: node.attrs.refType,
1720
1968
  refId: node.attrs.refId,
1969
+ slotKey: node.attrs.slotKey,
1970
+ slotLabel: node.attrs.slotLabel,
1971
+ metadata: node.attrs.metadata,
1721
1972
  });
1722
1973
  } else if (node.type.name === "skillReference") {
1723
1974
  references.push({
@@ -1730,7 +1981,7 @@ export function TiptapComposer({
1730
1981
  });
1731
1982
 
1732
1983
  return { text, references };
1733
- }, [editor]);
1984
+ }, [editor, slotReferences]);
1734
1985
 
1735
1986
  const syncComposerState = useCallback(() => {
1736
1987
  const { text, references } = extractComposerPayload();
@@ -1827,6 +2078,7 @@ export function TiptapComposer({
1827
2078
  cancelActiveVoice();
1828
2079
  ed.commands.clearContent();
1829
2080
  setEditorHasText(false);
2081
+ setSlotReferences([]);
1830
2082
  setComposerMode(null);
1831
2083
  composerModeRef.current = null;
1832
2084
  try {
@@ -1849,6 +2101,7 @@ export function TiptapComposer({
1849
2101
  cancelActiveVoice();
1850
2102
  ed.commands.clearContent();
1851
2103
  setEditorHasText(false);
2104
+ setSlotReferences([]);
1852
2105
  try {
1853
2106
  localStorage.removeItem(draftKey);
1854
2107
  } catch {}
@@ -1870,6 +2123,7 @@ export function TiptapComposer({
1870
2123
  cancelActiveVoice();
1871
2124
  ed.commands.clearContent();
1872
2125
  setEditorHasText(false);
2126
+ setSlotReferences([]);
1873
2127
  try {
1874
2128
  localStorage.removeItem(draftKey);
1875
2129
  } catch {}
@@ -1901,22 +2155,8 @@ export function TiptapComposer({
1901
2155
  const currentPos = ed.state.selection.from;
1902
2156
  // startPos is after the trigger char, so -1 to include the @ or /
1903
2157
  const deleteFrom = Math.max(0, pop.startPos - 1);
1904
- ed.chain()
1905
- .focus()
1906
- .deleteRange({ from: deleteFrom, to: currentPos })
1907
- .insertContent({
1908
- type: "mentionReference",
1909
- attrs: {
1910
- label: item.label,
1911
- icon: item.icon || "file",
1912
- source: item.source,
1913
- refType: item.refType,
1914
- refId: item.refId || null,
1915
- refPath: item.refPath || null,
1916
- },
1917
- })
1918
- .insertContent(" ")
1919
- .run();
2158
+ ed.chain().focus().deleteRange({ from: deleteFrom, to: currentPos }).run();
2159
+ insertReference(composerReferenceFromMentionItem(item));
1920
2160
  popoverStateRef.current = null;
1921
2161
  setPopover(null);
1922
2162
  }
@@ -1968,22 +2208,11 @@ export function TiptapComposer({
1968
2208
  .chain()
1969
2209
  .focus()
1970
2210
  .deleteRange({ from: deleteFrom, to: currentPos })
1971
- .insertContent({
1972
- type: "mentionReference",
1973
- attrs: {
1974
- label: item.label,
1975
- icon: item.icon || "file",
1976
- source: item.source,
1977
- refType: item.refType,
1978
- refId: item.refId || null,
1979
- refPath: item.refPath || null,
1980
- },
1981
- })
1982
- .insertContent(" ")
1983
2211
  .run();
2212
+ insertReference(composerReferenceFromMentionItem(item));
1984
2213
  closePopover();
1985
2214
  },
1986
- [editor, popover, closePopover],
2215
+ [editor, popover, closePopover, insertReference],
1987
2216
  );
1988
2217
 
1989
2218
  const handleSelectCommand = useCallback(
@@ -2131,12 +2360,39 @@ export function TiptapComposer({
2131
2360
  />
2132
2361
  </div>
2133
2362
  )}
2134
- {hasContextItems && (
2363
+ {hasContextRows && (
2135
2364
  <div
2136
2365
  data-agent-composer-variant={layoutVariant}
2137
2366
  data-agent-composer-slot="context-row"
2138
2367
  className="agent-composer-context-row flex flex-wrap gap-1.5 px-2.5 pt-2 pb-0"
2139
2368
  >
2369
+ {slotReferences.map((ref) => (
2370
+ <span
2371
+ key={ref.slotKey}
2372
+ className="inline-flex max-w-full items-center gap-1.5 rounded-md border border-border bg-background px-2 py-1 text-[11px] font-medium text-foreground shadow-sm"
2373
+ >
2374
+ <IconClipboardList className="h-3 w-3 shrink-0 text-muted-foreground" />
2375
+ {ref.slotLabel && (
2376
+ <span className="shrink-0 text-muted-foreground">
2377
+ {ref.slotLabel}
2378
+ </span>
2379
+ )}
2380
+ <span className="min-w-0 truncate">{ref.label}</span>
2381
+ <button
2382
+ type="button"
2383
+ onClick={() => {
2384
+ setSlotReferences((current) =>
2385
+ removeSlotReference(current, ref),
2386
+ );
2387
+ editor?.commands.focus("end");
2388
+ }}
2389
+ aria-label={`Remove ${slotReferenceTitle(ref)} reference`}
2390
+ className="ms-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground"
2391
+ >
2392
+ <IconX className="h-3 w-3" />
2393
+ </button>
2394
+ </span>
2395
+ ))}
2140
2396
  {contextItems.map((item) => (
2141
2397
  <span
2142
2398
  key={item.key}
@@ -2160,7 +2416,7 @@ export function TiptapComposer({
2160
2416
  data-agent-composer-variant={layoutVariant}
2161
2417
  data-agent-composer-slot="editor-wrap"
2162
2418
  className={`agent-composer-editor-wrap ${
2163
- composerMode || hasContextItems ? "px-2 pt-1 pb-1" : "px-2 pt-2 pb-1"
2419
+ composerMode || hasContextRows ? "px-2 pt-1 pb-1" : "px-2 pt-2 pb-1"
2164
2420
  }`}
2165
2421
  >
2166
2422
  <EditorContent
@@ -2233,7 +2489,7 @@ export function TiptapComposer({
2233
2489
  ref={popoverRef}
2234
2490
  type={popover?.type ?? "@"}
2235
2491
  position={popover?.position ?? null}
2236
- mentionItems={mentionItems}
2492
+ mentionItems={filteredMentionItems}
2237
2493
  skills={filteredSkills}
2238
2494
  commands={filteredCommands}
2239
2495
  hint={hint}
@@ -66,6 +66,9 @@ export const MentionReference = Node.create({
66
66
  refType: { default: "file" },
67
67
  refId: { default: null },
68
68
  refPath: { default: null },
69
+ slotKey: { default: null },
70
+ slotLabel: { default: null },
71
+ metadata: { default: null },
69
72
  };
70
73
  },
71
74
 
@@ -22,6 +22,11 @@ export interface MentionItem {
22
22
  refPath?: string;
23
23
  refId?: string;
24
24
  section?: string;
25
+ slotKey?: string;
26
+ slotLabel?: string;
27
+ metadata?: Record<string, unknown>;
28
+ clearsSlots?: string[];
29
+ relatedReferences?: MentionReferenceInsert[];
25
30
  }
26
31
 
27
32
  export interface Reference {
@@ -31,6 +36,23 @@ export interface Reference {
31
36
  source: string;
32
37
  refType?: string;
33
38
  refId?: string;
39
+ slotKey?: string;
40
+ slotLabel?: string;
41
+ metadata?: Record<string, unknown>;
42
+ }
43
+
44
+ export interface MentionReferenceInsert {
45
+ label: string;
46
+ icon?: string;
47
+ source?: string;
48
+ refType: string;
49
+ refId?: string | null;
50
+ refPath?: string | null;
51
+ slotKey?: string;
52
+ slotLabel?: string;
53
+ metadata?: Record<string, unknown>;
54
+ clearsSlots?: string[];
55
+ relatedReferences?: MentionReferenceInsert[];
34
56
  }
35
57
 
36
58
  export interface SlashCommand {
@@ -11,7 +11,9 @@ export {
11
11
  appendAgentChatContextToMessage,
12
12
  clearAgentChatContext,
13
13
  formatAgentChatContextItemsForPrompt,
14
+ insertAgentComposerReference,
14
15
  listAgentChatContext,
16
+ normalizeAgentComposerReference,
15
17
  refreshAgentChatContext,
16
18
  removeAgentChatContextItem,
17
19
  sendToAgentChat,
@@ -27,6 +29,9 @@ export {
27
29
  type AgentChatContextSetOptions,
28
30
  type AgentChatContextState,
29
31
  type AgentChatMessage,
32
+ type AgentComposerReference,
33
+ type AgentComposerReferenceInsertOptions,
34
+ type AgentComposerReferenceInsertPayload,
30
35
  } from "./agent-chat.js";
31
36
  export {
32
37
  saveAgentEngineApiKey,
@@ -6660,6 +6660,11 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
6660
6660
  refPath?: string;
6661
6661
  refId?: string;
6662
6662
  section?: string;
6663
+ slotKey?: string;
6664
+ slotLabel?: string;
6665
+ metadata?: Record<string, unknown>;
6666
+ clearsSlots?: string[];
6667
+ relatedReferences?: unknown[];
6663
6668
  }
6664
6669
 
6665
6670
  const matchesQuery = (item: MentionItemResponse) =>
@@ -6796,6 +6801,11 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
6796
6801
  refPath: item.refPath,
6797
6802
  refId: item.refId,
6798
6803
  section: provider.label,
6804
+ slotKey: item.slotKey,
6805
+ slotLabel: item.slotLabel,
6806
+ metadata: item.metadata,
6807
+ clearsSlots: item.clearsSlots,
6808
+ relatedReferences: item.relatedReferences,
6799
6809
  })),
6800
6810
  );
6801
6811
  } catch (e) {
@@ -51,6 +51,14 @@ export interface RequestRunContext {
51
51
  engine?: import("../agent/engine/types.js").AgentEngine;
52
52
  /** Model name for this run (set by onEngineResolved). */
53
53
  model?: string;
54
+ /**
55
+ * True when this run is executing inside the durable background-function
56
+ * worker (the `_process-run` self-dispatch), not the synchronous foreground
57
+ * request. Template `extraContext` / system-prompt builders can read this to
58
+ * skip heavy, hang-prone enrichment (large data-dictionary DB reads, etc.)
59
+ * in the worker so it reliably claims its run within the setup budget.
60
+ */
61
+ isBackgroundWorker?: boolean;
54
62
  /** Tool calls made so far in the current agent loop. */
55
63
  toolCalls?: Array<{ name: string; input: unknown }>;
56
64
  /** Tool results returned so far in the current agent loop. */