@agent-native/toolkit 0.209.0 → 0.209.1-nightly-20261015093558

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 (51) hide show
  1. package/dist/app/chat/AgentSidebar.d.ts.map +1 -1
  2. package/dist/app/chat/AgentSidebar.js +22 -11
  3. package/dist/app/chat/AgentSidebar.js.map +1 -1
  4. package/dist/app/review/InlineMarkdown.d.ts +2 -0
  5. package/dist/app/review/InlineMarkdown.d.ts.map +1 -1
  6. package/dist/app/review/InlineMarkdown.js +25 -7
  7. package/dist/app/review/InlineMarkdown.js.map +1 -1
  8. package/dist/app/settings/useBuilderStatus.d.ts.map +1 -1
  9. package/dist/app/settings/useBuilderStatus.js +1 -0
  10. package/dist/app/settings/useBuilderStatus.js.map +1 -1
  11. package/dist/composer/MentionChip.d.ts +14 -0
  12. package/dist/composer/MentionChip.d.ts.map +1 -0
  13. package/dist/composer/MentionChip.js +11 -0
  14. package/dist/composer/MentionChip.js.map +1 -0
  15. package/dist/composer/MentionPopover.d.ts +5 -0
  16. package/dist/composer/MentionPopover.d.ts.map +1 -1
  17. package/dist/composer/MentionPopover.js +55 -44
  18. package/dist/composer/MentionPopover.js.map +1 -1
  19. package/dist/composer/PromptComposer.d.ts +6 -1
  20. package/dist/composer/PromptComposer.d.ts.map +1 -1
  21. package/dist/composer/PromptComposer.js +3 -3
  22. package/dist/composer/PromptComposer.js.map +1 -1
  23. package/dist/composer/TiptapComposer.d.ts +18 -2
  24. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  25. package/dist/composer/TiptapComposer.js +60 -12
  26. package/dist/composer/TiptapComposer.js.map +1 -1
  27. package/dist/composer/extensions/MentionReference.d.ts.map +1 -1
  28. package/dist/composer/extensions/MentionReference.js +16 -5
  29. package/dist/composer/extensions/MentionReference.js.map +1 -1
  30. package/dist/composer/index.d.ts +2 -1
  31. package/dist/composer/index.d.ts.map +1 -1
  32. package/dist/composer/index.js +1 -0
  33. package/dist/composer/index.js.map +1 -1
  34. package/dist/ui/button-group.js +2 -2
  35. package/dist/ui/button-group.js.map +1 -1
  36. package/package.json +4 -4
  37. package/src/app/chat/AgentSidebar.spec.tsx +339 -2
  38. package/src/app/chat/AgentSidebar.tsx +25 -10
  39. package/src/app/review/InlineMarkdown.spec.tsx +25 -0
  40. package/src/app/review/InlineMarkdown.tsx +29 -13
  41. package/src/app/settings/useBuilderStatus.spec.tsx +54 -0
  42. package/src/app/settings/useBuilderStatus.ts +1 -0
  43. package/src/composer/MentionChip.tsx +46 -0
  44. package/src/composer/MentionPopover.tsx +49 -7
  45. package/src/composer/PromptComposer.text-references.spec.tsx +198 -0
  46. package/src/composer/PromptComposer.tsx +12 -0
  47. package/src/composer/TiptapComposer.tsx +100 -10
  48. package/src/composer/extensions/MentionReference.tsx +22 -14
  49. package/src/composer/index.ts +2 -0
  50. package/src/ui/button-group.spec.tsx +4 -0
  51. package/src/ui/button-group.tsx +2 -2
@@ -183,7 +183,12 @@ export interface TiptapComposerHandle {
183
183
  * triggers such as `@` open their menus as if the person typed them.
184
184
  */
185
185
  insertTextAtCursor?(text: string): void;
186
- setText(text: string): void;
186
+ /**
187
+ * Replace the draft. Each reference becomes a chip in place of
188
+ * `text.slice(start, end)`; one that overlaps another or crosses a line
189
+ * break stays text.
190
+ */
191
+ setText(text: string, references?: readonly ComposerTextReference[]): void;
187
192
  /** Submit replacement text with the current attachments and context, without editing the draft on failure. */
188
193
  submitWithText(text: string): Promise<boolean>;
189
194
  /** Submit the current draft as if the person pressed send. */
@@ -207,6 +212,13 @@ export interface ComposerTextSelection {
207
212
  direction: "forward" | "backward" | "none";
208
213
  }
209
214
 
215
+ /** A chip standing for `text.slice(start, end)` of a draft's plain text. */
216
+ export interface ComposerTextReference {
217
+ start: number;
218
+ end: number;
219
+ reference: AgentComposerReference;
220
+ }
221
+
210
222
  export type ComposerSubmitIntent = "immediate" | "queued";
211
223
 
212
224
  export const DEFAULT_VOICE_DICTATION_ENABLED = false;
@@ -1066,6 +1078,8 @@ export interface TiptapComposerProps {
1066
1078
  initialText?: string;
1067
1079
  /** Stable key used to re-apply the seeded text. */
1068
1080
  initialTextKey?: string | number;
1081
+ /** Chips placed into `initialText`, as `setText` takes them. */
1082
+ initialReferences?: readonly ComposerTextReference[];
1069
1083
  /**
1070
1084
  * When provided, called instead of composerRuntime.send(). Used for queue
1071
1085
  * mode and standalone prompt popovers. Receives the live composer
@@ -1095,6 +1109,9 @@ export interface TiptapComposerProps {
1095
1109
  /** Called whenever the plain editor text changes. */
1096
1110
  onTextChange?: (text: string) => void;
1097
1111
  mentionItems?: MentionItem[];
1112
+ /** Why some of `mentionItems` could not load, shown in the @ popover. */
1113
+ mentionItemsError?: string;
1114
+ onRetryMentionItems?: () => void;
1098
1115
  mentionPopoverDensity?: "default" | "stacked";
1099
1116
  includeDefaultMentionSearch?: boolean;
1100
1117
  onReferencesChange?: (references: Reference[]) => void;
@@ -1239,14 +1256,53 @@ export interface TiptapComposerProps {
1239
1256
  onAttachmentError?: (message: string) => void;
1240
1257
  }
1241
1258
 
1242
- function plainTextToDoc(text: string) {
1243
- const lines = text.length > 0 ? text.split(/\r?\n/) : [""];
1259
+ interface TextChip {
1260
+ start: number;
1261
+ end: number;
1262
+ attrs: ReturnType<typeof mentionReferenceAttrs>;
1263
+ }
1264
+
1265
+ function plainTextToDoc(text: string, chips: readonly TextChip[] = []) {
1266
+ const ordered = [...chips].sort((a, b) => a.start - b.start);
1267
+ const lines: { start: number; end: number }[] = [];
1268
+ let lineStart = 0;
1269
+ for (const separator of text.matchAll(/\r?\n/g)) {
1270
+ lines.push({ start: lineStart, end: separator.index });
1271
+ lineStart = separator.index + separator[0].length;
1272
+ }
1273
+ lines.push({ start: lineStart, end: text.length });
1244
1274
  return {
1245
1275
  type: "doc",
1246
- content: lines.map((line) => ({
1247
- type: "paragraph",
1248
- content: line ? [{ type: "text", text: line }] : [],
1249
- })),
1276
+ content: lines.map((line) => {
1277
+ const content: Record<string, unknown>[] = [];
1278
+ let cursor = line.start;
1279
+ const addText = (end: number) => {
1280
+ if (end > cursor) {
1281
+ content.push({ type: "text", text: text.slice(cursor, end) });
1282
+ }
1283
+ };
1284
+ for (const chip of ordered) {
1285
+ if (
1286
+ chip.start < cursor ||
1287
+ line.end < chip.end ||
1288
+ chip.end <= chip.start
1289
+ )
1290
+ continue;
1291
+ addText(chip.start);
1292
+ content.push({ type: "mentionReference", attrs: chip.attrs });
1293
+ cursor = chip.end;
1294
+ }
1295
+ addText(line.end);
1296
+ // Drafts are reported trimmed, so a chip that ends one lost the space
1297
+ // picking it added; without it the next word typed joins the chip's text.
1298
+ if (
1299
+ cursor === text.length &&
1300
+ content.at(-1)?.type === "mentionReference"
1301
+ ) {
1302
+ content.push({ type: "text", text: " " });
1303
+ }
1304
+ return { type: "paragraph", content };
1305
+ }),
1250
1306
  };
1251
1307
  }
1252
1308
 
@@ -2779,6 +2835,7 @@ export function TiptapComposer({
2779
2835
  focusRef,
2780
2836
  initialText,
2781
2837
  initialTextKey,
2838
+ initialReferences,
2782
2839
  onSubmit,
2783
2840
  onEmptySubmit,
2784
2841
  onBeforeSubmit,
@@ -2835,6 +2892,8 @@ export function TiptapComposer({
2835
2892
  interceptBuildRequestsForBuilder = false,
2836
2893
  onAttachmentError,
2837
2894
  mentionItems: hostMentionItems = [],
2895
+ mentionItemsError,
2896
+ onRetryMentionItems,
2838
2897
  mentionPopoverDensity = "default",
2839
2898
  includeDefaultMentionSearch = true,
2840
2899
  onReferencesChange,
@@ -2849,6 +2908,20 @@ export function TiptapComposer({
2849
2908
  !(attachmentsEnabled && attachButton);
2850
2909
  const adapters = useComposerRuntimeAdapters();
2851
2910
  const t = adapters.translate!;
2911
+ const initialReferencesRef = useRef(initialReferences);
2912
+ initialReferencesRef.current = initialReferences;
2913
+ const textChips = useCallback(
2914
+ (references: readonly ComposerTextReference[] = []): TextChip[] =>
2915
+ references.flatMap(({ start, end, reference }) => {
2916
+ const normalized = adapters.agentChat!.normalizeReference!(
2917
+ reference,
2918
+ ) as AgentComposerReference | null;
2919
+ return normalized
2920
+ ? [{ start, end, attrs: mentionReferenceAttrs(normalized) }]
2921
+ : [];
2922
+ }),
2923
+ [adapters],
2924
+ );
2852
2925
  const sendButtonTooltip = t(getComposerSendTooltipKey(willQueue), {
2853
2926
  defaultValue: willQueue ? "Queue message" : "Send message",
2854
2927
  });
@@ -3632,6 +3705,14 @@ export function TiptapComposer({
3632
3705
  popoverRef.current?.moveDown();
3633
3706
  return true;
3634
3707
  }
3708
+ if (
3709
+ (event.key === "Enter" ||
3710
+ (event.key === "Tab" && !event.shiftKey)) &&
3711
+ popoverRef.current?.activateRetry()
3712
+ ) {
3713
+ event.preventDefault();
3714
+ return true;
3715
+ }
3635
3716
  // A mention comes only from an explicit pick of a highlighted row. With
3636
3717
  // nothing highlighted, even while a search is pending, the "@" is
3637
3718
  // plain text: Enter submits it and a late result changes nothing.
@@ -4032,7 +4113,11 @@ export function TiptapComposer({
4032
4113
  );
4033
4114
  if (from > 1 && before !== "" && !/\s/.test(before)) inserted = ` @`;
4034
4115
  }
4116
+ // The popover anchors on the selection right after this insert, but an
4117
+ // execCommand edit reaches the editor state only once ProseMirror reads
4118
+ // the DOM back, so an @ goes through the editor directly.
4035
4119
  if (
4120
+ mention ||
4036
4121
  typeof document.execCommand !== "function" ||
4037
4122
  !document.execCommand("insertText", false, inserted)
4038
4123
  ) {
@@ -4052,9 +4137,9 @@ export function TiptapComposer({
4052
4137
  popoverStateRef.current = state;
4053
4138
  setPopover(state);
4054
4139
  },
4055
- setText(text: string) {
4140
+ setText(text: string, references?: readonly ComposerTextReference[]) {
4056
4141
  if (!isComposerEditorUsable(editor)) return;
4057
- editor.commands.setContent(plainTextToDoc(text));
4142
+ editor.commands.setContent(plainTextToDoc(text, textChips(references)));
4058
4143
  editor.commands.focus("end");
4059
4144
  const trimmed = editor.getText({ blockSeparator: "\n" }).trim();
4060
4145
  setEditorHasText(trimmed.length > 0);
@@ -5799,7 +5884,9 @@ export function TiptapComposer({
5799
5884
  return;
5800
5885
  } else if (initialTextKeyRef.current !== key) {
5801
5886
  initialTextKeyRef.current = key;
5802
- editor.commands.setContent(plainTextToDoc(initialText));
5887
+ editor.commands.setContent(
5888
+ plainTextToDoc(initialText, textChips(initialReferencesRef.current)),
5889
+ );
5803
5890
  } else {
5804
5891
  return;
5805
5892
  }
@@ -5821,6 +5908,7 @@ export function TiptapComposer({
5821
5908
  initialText,
5822
5909
  initialTextKey,
5823
5910
  scheduleComposerDraftPersist,
5911
+ textChips,
5824
5912
  updateSlotReferences,
5825
5913
  ]);
5826
5914
 
@@ -6228,6 +6316,8 @@ export function TiptapComposer({
6228
6316
  commands={filteredCommands}
6229
6317
  hint={hint}
6230
6318
  isLoading={popover?.type === "@" ? mentionsLoading : skillsLoading}
6319
+ error={mentionItemsError}
6320
+ onRetry={onRetryMentionItems}
6231
6321
  query={popover?.query ?? ""}
6232
6322
  onSelectMention={handleSelectMention}
6233
6323
  onSelectSkill={handleSelectSkill}
@@ -1,9 +1,12 @@
1
1
  import { mergeAttributes, Node } from "@tiptap/core";
2
2
  import { NodeViewWrapper, ReactNodeViewRenderer } from "@tiptap/react";
3
3
 
4
- import { MentionItemMedia } from "../MentionItemMedia.js";
4
+ import { MentionChip } from "../MentionChip.js";
5
5
  import type { MentionItemMedia as MentionItemMediaValue } from "../types.js";
6
6
 
7
+ const NAME_START = /^[\p{L}\p{M}\p{N}_]/u;
8
+ const NAME_END = /[\p{L}\p{M}\p{N}_]$/u;
9
+
7
10
  function parseMentionItemMedia(
8
11
  value: string | null,
9
12
  ): MentionItemMediaValue | null {
@@ -46,21 +49,16 @@ function parseMentionItemMedia(
46
49
  const MentionReferenceComponent = ({ node }: { node: any }) => {
47
50
  return (
48
51
  <NodeViewWrapper as="span" className="inline">
49
- <span
50
- className="inline-flex items-center gap-1 rounded-md border border-input bg-muted/50 px-1.5 py-0.5 text-xs font-medium text-foreground align-middle mx-0.5 max-w-[200px] select-none"
52
+ <MentionChip
53
+ className="select-none"
51
54
  title={node.attrs.refPath || node.attrs.refId || node.attrs.label}
52
55
  data-mention-reference=""
53
56
  data-mention-ref-type={node.attrs.refType || undefined}
54
57
  data-mention-ref-id={node.attrs.refId || undefined}
55
- >
56
- <MentionItemMedia
57
- icon={node.attrs.icon}
58
- media={node.attrs.media}
59
- size="sm"
60
- fallbackIcon="stack"
61
- />
62
- <span className="truncate">{node.attrs.label}</span>
63
- </span>
58
+ icon={node.attrs.icon}
59
+ media={node.attrs.media}
60
+ label={node.attrs.label}
61
+ />
64
62
  </NodeViewWrapper>
65
63
  );
66
64
  };
@@ -106,8 +104,18 @@ export const MentionReference = Node.create({
106
104
  ];
107
105
  },
108
106
 
109
- renderText({ node }) {
110
- return `@${node.attrs.label}`;
107
+ renderText({ node, parent, index }) {
108
+ const text = `@${node.attrs.label}`;
109
+ // Text matches a mention only as a whole @name token, so a chip right
110
+ // against a word would read as no mention, or as a later one.
111
+ const before = parent.maybeChild(index - 1);
112
+ const after = parent.maybeChild(index + 1);
113
+ const leading = before?.isText && NAME_END.test(before.text ?? "");
114
+ const trailing =
115
+ NAME_END.test(text) &&
116
+ ((after?.isText && NAME_START.test(after.text ?? "")) ||
117
+ after?.type === node.type);
118
+ return `${leading ? " " : ""}${text}${trailing ? " " : ""}`;
111
119
  },
112
120
 
113
121
  addNodeView() {
@@ -59,6 +59,7 @@ export {
59
59
  type ComposerDraftSnapshot,
60
60
  type ComposerSubmitIntent,
61
61
  type TiptapComposerHandle,
62
+ type ComposerTextReference,
62
63
  type ComposerTextSelection,
63
64
  type TiptapComposerProps,
64
65
  type TiptapComposerSubmitOptions,
@@ -128,6 +129,7 @@ export {
128
129
  type AgentPromptAttachment,
129
130
  type ReadAgentPromptAttachmentOptions,
130
131
  } from "./prompt-attachments.js";
132
+ export { MentionChip, type MentionChipProps } from "./MentionChip.js";
131
133
  export { MentionPopover } from "./MentionPopover.js";
132
134
  export { useMentionSearch } from "./use-mention-search.js";
133
135
  export {
@@ -101,6 +101,10 @@ describe("ButtonGroup", () => {
101
101
  ":not([data-slot=button-group-separator]+*)",
102
102
  ),
103
103
  ).toBe(true);
104
+ expect(horizontal?.className).toContain(
105
+ ":not(:has(~:not([data-button-group-ignore])))",
106
+ );
107
+ expect(horizontal?.className).not.toContain("nth-last-child");
104
108
  expect(nested?.className).toContain("border-s-current/20");
105
109
  expect(
106
110
  horizontal?.querySelector('[data-slot="button-group-separator"]'),
@@ -11,9 +11,9 @@ const buttonGroupVariants = cva(
11
11
  variants: {
12
12
  orientation: {
13
13
  horizontal:
14
- "[&>*:first-child:not([data-slot=button-group]):not([data-button-group-ignore]):not(select[aria-hidden=true])]:rounded-s-md [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*)]:rounded-s-none [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*)[class~='border']]:border-s-0 [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*):not([data-slot=button-group-separator]):not([data-slot=button-group-separator]+*):not([class~='border'])]:border-s [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*):not([data-slot=button-group-separator]):not([data-slot=button-group-separator]+*):not([class~='border'])]:border-s-current/20 [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:last-child):not([data-slot=button-group]):not(:has(+[data-slot=button-group]))]:rounded-e-none [&>*:last-child:not([data-slot=button-group]):not([data-button-group-ignore]):not(select[aria-hidden=true])]:rounded-e-md has-[>input[data-button-group-ignore]]:[&>*:nth-last-child(2)]:!rounded-e-md",
14
+ "[&>*:first-child:not([data-slot=button-group]):not([data-button-group-ignore]):not(select[aria-hidden=true])]:rounded-s-md [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*)]:rounded-s-none [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*)[class~='border']]:border-s-0 [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*):not([data-slot=button-group-separator]):not([data-slot=button-group-separator]+*):not([class~='border'])]:border-s [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*):not([data-slot=button-group-separator]):not([data-slot=button-group-separator]+*):not([class~='border'])]:border-s-current/20 [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):has(~:not([data-button-group-ignore])):not([data-slot=button-group]):not(:has(+[data-slot=button-group]))]:rounded-e-none [&>*:not(:has(~:not([data-button-group-ignore]))):not([data-slot=button-group]):not([data-button-group-ignore]):not(select[aria-hidden=true])]:rounded-e-md",
15
15
  vertical:
16
- "flex-col [&>*:first-child:not([data-slot=button-group]):not([data-button-group-ignore]):not(select[aria-hidden=true])]:rounded-t-md [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*)]:rounded-t-none [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*)[class~='border']]:border-t-0 [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*):not([data-slot=button-group-separator]):not([data-slot=button-group-separator]+*):not([class~='border'])]:border-t [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*):not([data-slot=button-group-separator]):not([data-slot=button-group-separator]+*):not([class~='border'])]:border-t-current/20 [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:last-child):not([data-slot=button-group]):not(:has(+[data-slot=button-group]))]:rounded-b-none [&>*:last-child:not([data-slot=button-group]):not([data-button-group-ignore]):not(select[aria-hidden=true])]:rounded-b-md has-[>input[data-button-group-ignore]]:[&>*:nth-last-child(2)]:!rounded-b-md",
16
+ "flex-col [&>*:first-child:not([data-slot=button-group]):not([data-button-group-ignore]):not(select[aria-hidden=true])]:rounded-t-md [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*)]:rounded-t-none [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*)[class~='border']]:border-t-0 [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*):not([data-slot=button-group-separator]):not([data-slot=button-group-separator]+*):not([class~='border'])]:border-t [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):not(:first-child):not([data-slot=button-group]):not([data-slot=button-group]+*):not([data-slot=button-group-separator]):not([data-slot=button-group-separator]+*):not([class~='border'])]:border-t-current/20 [&>*:not([data-button-group-ignore]):not(select[aria-hidden=true]):has(~:not([data-button-group-ignore])):not([data-slot=button-group]):not(:has(+[data-slot=button-group]))]:rounded-b-none [&>*:not(:has(~:not([data-button-group-ignore]))):not([data-slot=button-group]):not([data-button-group-ignore]):not(select[aria-hidden=true])]:rounded-b-md",
17
17
  },
18
18
  },
19
19
  defaultVariants: {