@runtypelabs/persona 4.7.0 → 4.9.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 +1 -0
- package/dist/animations/glyph-cycle.cjs +2 -2
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/glyph-cycle.js +2 -2
- package/dist/animations/{types-CSmiKRVa.d.cts → types-4ROVJ1gA.d.cts} +85 -3
- package/dist/animations/{types-CSmiKRVa.d.ts → types-4ROVJ1gA.d.ts} +85 -3
- package/dist/animations/wipe.cjs +2 -2
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/chunk-5EIIHQLQ.js +1 -0
- package/dist/chunk-IO5VVUKP.js +3 -0
- package/dist/chunk-IPVK3KOM.js +1 -0
- package/dist/chunk-UPO4GUFC.js +1 -0
- package/dist/codegen.cjs +13 -13
- package/dist/codegen.js +14 -14
- package/dist/context-mentions-7S5KVUTG.js +169 -0
- package/dist/context-mentions-inline-TTCN7ZM2.js +4 -0
- package/dist/context-mentions-inline.cjs +4 -0
- package/dist/context-mentions-inline.d.cts +203 -0
- package/dist/context-mentions-inline.d.ts +203 -0
- package/dist/context-mentions-inline.js +4 -0
- package/dist/context-mentions.cjs +295 -0
- package/dist/context-mentions.d.cts +7025 -0
- package/dist/context-mentions.d.ts +7025 -0
- package/dist/context-mentions.js +295 -0
- package/dist/index.cjs +99 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1507 -182
- package/dist/index.d.ts +1507 -182
- package/dist/index.global.js +88 -56
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +99 -68
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/launcher.global.js +3 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/markdown-parsers.js +24 -24
- package/dist/plugin-kit.cjs +1 -1
- package/dist/plugin-kit.d.cts +17 -0
- package/dist/plugin-kit.d.ts +17 -0
- package/dist/plugin-kit.js +1 -1
- package/dist/runtype-tts-entry-UJAEF7NZ.js +1 -0
- package/dist/runtype-tts.js +1 -1
- package/dist/session-reconnect-JKIJBHS5.js +1 -0
- package/dist/smart-dom-reader.cjs +18 -17
- package/dist/smart-dom-reader.d.cts +1260 -16
- package/dist/smart-dom-reader.d.ts +1260 -16
- package/dist/smart-dom-reader.js +18 -17
- package/dist/testing.cjs +3 -3
- package/dist/testing.js +3 -3
- package/dist/theme-editor-preview.cjs +260 -58
- package/dist/theme-editor-preview.d.cts +1234 -15
- package/dist/theme-editor-preview.d.ts +1234 -15
- package/dist/theme-editor-preview.js +87 -58
- package/dist/theme-editor.cjs +10 -10
- package/dist/theme-editor.d.cts +1226 -15
- package/dist/theme-editor.d.ts +1226 -15
- package/dist/theme-editor.js +10 -10
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +76 -0
- package/dist/theme-reference.d.ts +76 -0
- package/dist/theme-reference.js +1 -1
- package/dist/voice-worklet-player.cjs +2 -2
- package/dist/voice-worklet-player.js +2 -2
- package/dist/webmcp-polyfill.js +2 -2
- package/dist/widget.css +1 -1
- package/package.json +16 -5
- package/src/artifacts-session.test.ts +178 -0
- package/src/client.test.ts +255 -1
- package/src/client.ts +157 -60
- package/src/components/artifact-card.test.ts +333 -0
- package/src/components/artifact-card.ts +75 -28
- package/src/components/artifact-inline.test.ts +1328 -0
- package/src/components/artifact-inline.ts +920 -0
- package/src/components/artifact-pane.test.ts +1042 -0
- package/src/components/artifact-pane.ts +440 -131
- package/src/components/artifact-preview.test.ts +1155 -0
- package/src/components/artifact-preview.ts +994 -0
- package/src/components/composer-parts.ts +3 -12
- package/src/components/context-mention-button.test.ts +70 -0
- package/src/components/context-mention-button.ts +82 -0
- package/src/components/context-mention-chip.ts +134 -0
- package/src/components/context-mention-menu.test.ts +508 -0
- package/src/components/context-mention-menu.ts +0 -0
- package/src/components/message-bubble.test.ts +175 -0
- package/src/components/message-bubble.ts +177 -19
- package/src/components/panel.ts +7 -10
- package/src/components/pill-composer-builder.test.ts +6 -2
- package/src/components/pill-composer-builder.ts +6 -6
- package/src/components/reasoning-bubble.ts +1 -13
- package/src/components/registry.ts +38 -3
- package/src/components/tool-bubble.ts +1 -13
- package/src/context-mentions-bundle.test.ts +163 -0
- package/src/context-mentions-entry.ts +185 -0
- package/src/context-mentions-inline-entry.test.ts +136 -0
- package/src/context-mentions-inline-entry.ts +226 -0
- package/src/context-mentions-inline-loader.test.ts +30 -0
- package/src/context-mentions-inline-loader.ts +36 -0
- package/src/context-mentions-inline.ts +15 -0
- package/src/context-mentions-loader.ts +32 -0
- package/src/context-mentions.ts +16 -0
- package/src/defaults.ts +1 -0
- package/src/generated/runtype-openapi-contract.ts +55 -3
- package/src/index-core.ts +46 -1
- package/src/index-global.ts +51 -0
- package/src/index.ts +8 -0
- package/src/markdown-parsers-loader.test.ts +158 -0
- package/src/markdown-parsers-loader.ts +83 -27
- package/src/plugin-kit.test.ts +40 -0
- package/src/plugin-kit.ts +39 -5
- package/src/runtime/host-layout.test.ts +163 -0
- package/src/runtime/host-layout.ts +110 -7
- package/src/runtime/init.ts +18 -61
- package/src/session.mentions.test.ts +175 -0
- package/src/session.test.ts +52 -4
- package/src/session.ts +197 -27
- package/src/smart-dom-reader.test.ts +129 -2
- package/src/smart-dom-reader.ts +127 -1
- package/src/styles/context-mention-menu-css.ts +176 -0
- package/src/styles/widget.css +1002 -152
- package/src/theme-editor/preview.ts +2 -0
- package/src/theme-editor/sections.test.ts +26 -1
- package/src/theme-editor/sections.ts +13 -5
- package/src/theme-reference.ts +2 -2
- package/src/tool-call-display-defaults.test.ts +1 -0
- package/src/types/theme.ts +79 -0
- package/src/types.ts +1041 -17
- package/src/ui.artifact-pane-gating.test.ts +636 -0
- package/src/ui.component-directive.test.ts +104 -0
- package/src/ui.composer-bar.test.ts +60 -2
- package/src/ui.detached-panel.test.ts +1049 -0
- package/src/ui.mention-submit.test.ts +235 -0
- package/src/ui.tool-display.test.ts +51 -0
- package/src/ui.ts +998 -159
- package/src/utils/artifact-custom-actions.ts +128 -0
- package/src/utils/artifact-display.test.ts +42 -0
- package/src/utils/artifact-display.ts +84 -0
- package/src/utils/artifact-file.test.ts +116 -0
- package/src/utils/artifact-file.ts +117 -0
- package/src/utils/artifact-gate.test.ts +112 -5
- package/src/utils/artifact-gate.ts +39 -14
- package/src/utils/artifact-loading-status.ts +55 -0
- package/src/utils/artifact-status-label.ts +190 -0
- package/src/utils/buttons.ts +7 -1
- package/src/utils/chunk-loader.test.ts +97 -0
- package/src/utils/chunk-loader.ts +88 -0
- package/src/utils/code-highlight.test.ts +186 -0
- package/src/utils/code-highlight.ts +400 -0
- package/src/utils/composer-contenteditable.test.ts +507 -0
- package/src/utils/composer-contenteditable.ts +626 -0
- package/src/utils/composer-document.test.ts +280 -0
- package/src/utils/composer-document.ts +293 -0
- package/src/utils/composer-history.test.ts +35 -7
- package/src/utils/composer-history.ts +30 -20
- package/src/utils/composer-input.ts +159 -0
- package/src/utils/context-mention-controller.test.ts +1215 -0
- package/src/utils/context-mention-controller.ts +1186 -0
- package/src/utils/context-mention-manager.test.ts +422 -0
- package/src/utils/context-mention-manager.ts +410 -0
- package/src/utils/context-mention-orchestrator.test.ts +538 -0
- package/src/utils/context-mention-orchestrator.ts +348 -0
- package/src/utils/icons.ts +2 -0
- package/src/utils/live-region.test.ts +108 -0
- package/src/utils/live-region.ts +94 -0
- package/src/utils/mention-channels.ts +63 -0
- package/src/utils/mention-llm-format.test.ts +91 -0
- package/src/utils/mention-llm-format.ts +79 -0
- package/src/utils/mention-matcher.test.ts +86 -0
- package/src/utils/mention-matcher.ts +221 -0
- package/src/utils/mention-token.ts +72 -0
- package/src/utils/mention-trigger.test.ts +155 -0
- package/src/utils/mention-trigger.ts +156 -0
- package/src/utils/roving-tablist.test.ts +152 -0
- package/src/utils/roving-tablist.ts +111 -0
- package/src/utils/spinner.ts +45 -0
- package/src/utils/theme.test.ts +76 -4
- package/src/utils/theme.ts +7 -0
- package/src/utils/tokens.ts +118 -11
- package/src/utils/tool-loading-animation.test.ts +32 -0
- package/src/utils/tool-loading-animation.ts +24 -0
- package/dist/chunk-DFBSCFYN.js +0 -1
- package/dist/runtype-tts-entry-HFUV2UF7.js +0 -1
- package/dist/session-reconnect-U77QFUR7.js +0 -1
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentWidgetConfig,
|
|
3
|
+
AgentWidgetContextMentionConfig,
|
|
4
|
+
AgentWidgetContextMentionItem,
|
|
5
|
+
AgentWidgetContextMentionRef,
|
|
6
|
+
AgentWidgetContextMentionSource,
|
|
7
|
+
AgentWidgetContextMentionPayload,
|
|
8
|
+
AgentWidgetMessage,
|
|
9
|
+
ContentPart,
|
|
10
|
+
} from "../types";
|
|
11
|
+
import { createMentionChip, type MentionChipParts } from "../components/context-mention-chip";
|
|
12
|
+
import { formatMentionBlock } from "./mention-llm-format";
|
|
13
|
+
|
|
14
|
+
interface PendingMention {
|
|
15
|
+
key: string;
|
|
16
|
+
source: AgentWidgetContextMentionSource;
|
|
17
|
+
item: AgentWidgetContextMentionItem;
|
|
18
|
+
ref: AgentWidgetContextMentionRef;
|
|
19
|
+
status: "resolving" | "ready" | "error";
|
|
20
|
+
payload?: AgentWidgetContextMentionPayload;
|
|
21
|
+
abort?: AbortController;
|
|
22
|
+
/** The in-flight select-time resolve (absent for `resolveOn:"submit"`). Awaited
|
|
23
|
+
* at submit so `finalize()` reuses it instead of firing a duplicate fetch. */
|
|
24
|
+
resolvePromise?: Promise<void>;
|
|
25
|
+
/** Chip DOM — chip mode only. Inline mode tracks by `ComposerMentionId` key with
|
|
26
|
+
* no chip (the token lives in the composer prose). */
|
|
27
|
+
chip?: MentionChipParts;
|
|
28
|
+
/** INLINE mode only. Reflect this mention's resolve status onto its live token
|
|
29
|
+
* element (the composer capability's `setMentionStatus`, closed over its id).
|
|
30
|
+
* Chip mode leaves this undefined — the chip carries the status instead. */
|
|
31
|
+
reportStatus?: (status: "pending" | "resolved" | "error") => void;
|
|
32
|
+
/** Command args captured at add time (empty for ordinary mentions). */
|
|
33
|
+
args: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The stored ref for a selected item — identical shape at every call site
|
|
38
|
+
* (`add`, `track`, and the controller's inline commit), so it lives here once.
|
|
39
|
+
*/
|
|
40
|
+
export function refFromItem(
|
|
41
|
+
source: AgentWidgetContextMentionSource,
|
|
42
|
+
item: AgentWidgetContextMentionItem
|
|
43
|
+
): AgentWidgetContextMentionRef {
|
|
44
|
+
return {
|
|
45
|
+
sourceId: source.id,
|
|
46
|
+
itemId: item.id,
|
|
47
|
+
label: item.label,
|
|
48
|
+
iconName: item.iconName,
|
|
49
|
+
color: item.color,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The resolved bundle gathered at submit, merged into the user message. */
|
|
54
|
+
export interface MentionSubmitBundle {
|
|
55
|
+
/**
|
|
56
|
+
* Ready-to-join, per-mention LLM blocks (mentions-first). The mention layer
|
|
57
|
+
* has already applied `contextMentions.llmFormat` (fenced/document/custom), so
|
|
58
|
+
* the session just joins these with a blank line and appends the typed prose.
|
|
59
|
+
*/
|
|
60
|
+
blocks: string[];
|
|
61
|
+
contentParts: ContentPart[];
|
|
62
|
+
/** Namespaced `{ [sourceId]: { [itemId]: context } }` for the opt-in path. */
|
|
63
|
+
context: Record<string, Record<string, unknown>>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ContextMentionManagerOptions {
|
|
67
|
+
mentionConfig: AgentWidgetContextMentionConfig;
|
|
68
|
+
/** The composer context row chips render into (created by the core orchestrator). */
|
|
69
|
+
contextRow: HTMLElement;
|
|
70
|
+
getMessages: () => AgentWidgetMessage[];
|
|
71
|
+
getConfig: () => AgentWidgetConfig;
|
|
72
|
+
/** Plain-text composer value, captured at resolve/submit time. */
|
|
73
|
+
getComposerText: () => string;
|
|
74
|
+
/** Polite live-region announcer. */
|
|
75
|
+
announce: (message: string) => void;
|
|
76
|
+
/**
|
|
77
|
+
* Assertive live-region announcer for failures (resolve errors). Falls back to
|
|
78
|
+
* the polite `announce` when the host wires only one region.
|
|
79
|
+
*/
|
|
80
|
+
announceError?: (message: string) => void;
|
|
81
|
+
/** Emit a `persona:mention:<event>` analytics DOM event. */
|
|
82
|
+
emit?: (event: string, detail: unknown) => void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const mentionKey = (sourceId: string, itemId: string) => `${sourceId}\u0000${itemId}`;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Owns the set of pending mentions: pill chip DOM, eager resolve-on-select with
|
|
89
|
+
* caching + abort, duplicate/limit checks, and the submit-time gather (resolving
|
|
90
|
+
* any `resolveOn:"submit"` sources, dropping failures). No menu/search logic —
|
|
91
|
+
* that's the controller's job.
|
|
92
|
+
*/
|
|
93
|
+
export class ContextMentionManager {
|
|
94
|
+
private readonly opts: ContextMentionManagerOptions;
|
|
95
|
+
private readonly mentions: PendingMention[] = [];
|
|
96
|
+
|
|
97
|
+
constructor(opts: ContextMentionManagerOptions) {
|
|
98
|
+
this.opts = opts;
|
|
99
|
+
this.updateRowVisibility();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private get maxMentions(): number {
|
|
103
|
+
return this.opts.mentionConfig.maxMentions ?? 8;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
hasMentions(): boolean {
|
|
107
|
+
return this.mentions.length > 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Add a selected item. Returns false (and fires `onMentionRejected`) on
|
|
112
|
+
* duplicate/limit. `args` (for server commands) is captured now and threaded
|
|
113
|
+
* into `resolve()`'s context, since the typed token is gone by submit time.
|
|
114
|
+
*/
|
|
115
|
+
add(
|
|
116
|
+
source: AgentWidgetContextMentionSource,
|
|
117
|
+
item: AgentWidgetContextMentionItem,
|
|
118
|
+
args = ""
|
|
119
|
+
): boolean {
|
|
120
|
+
const key = mentionKey(source.id, item.id);
|
|
121
|
+
if (this.mentions.some((m) => m.key === key)) {
|
|
122
|
+
return this.reject(source, item, "duplicate");
|
|
123
|
+
}
|
|
124
|
+
if (this.atLimit()) return this.reject(source, item, "limit");
|
|
125
|
+
|
|
126
|
+
const ref = refFromItem(source, item);
|
|
127
|
+
const pending: PendingMention = {
|
|
128
|
+
key,
|
|
129
|
+
source,
|
|
130
|
+
item,
|
|
131
|
+
ref,
|
|
132
|
+
status: "resolving",
|
|
133
|
+
args,
|
|
134
|
+
};
|
|
135
|
+
pending.chip = createMentionChip({
|
|
136
|
+
ref,
|
|
137
|
+
config: this.opts.mentionConfig,
|
|
138
|
+
onRemove: () => this.remove(key),
|
|
139
|
+
});
|
|
140
|
+
this.opts.contextRow.appendChild(pending.chip.el);
|
|
141
|
+
// startPending pushes into this.mentions, which updateRowVisibility reads —
|
|
142
|
+
// reversed order left the row hidden until the second chip.
|
|
143
|
+
this.startPending(pending);
|
|
144
|
+
this.updateRowVisibility();
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** True when the mention limit is already reached (inline pre-insert gate). */
|
|
149
|
+
atLimit(): boolean {
|
|
150
|
+
return this.mentions.length >= this.maxMentions;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* INLINE pre-insert gate, run BEFORE the controller inserts an atomic token so a
|
|
155
|
+
* rejected pick never leaves a stray token. Only the mention LIMIT is gated here:
|
|
156
|
+
* inline mode lets the same item appear as several prose tokens (Slack, Cursor,
|
|
157
|
+
* and Claude all let you @-mention a thing twice in one sentence), unlike chip
|
|
158
|
+
* mode's `add()`, whose chip row is an attachment list where a duplicate chip is
|
|
159
|
+
* meaningless. Each token still counts toward `maxMentions`, and `finalize()`
|
|
160
|
+
* dedupes the resolved payload by (source, item) so a repeated mention's context
|
|
161
|
+
* reaches the LLM once even though every token renders. Returns true when the
|
|
162
|
+
* mention may be inserted.
|
|
163
|
+
*/
|
|
164
|
+
admit(
|
|
165
|
+
source: AgentWidgetContextMentionSource,
|
|
166
|
+
item: AgentWidgetContextMentionItem
|
|
167
|
+
): boolean {
|
|
168
|
+
if (this.atLimit()) return this.reject(source, item, "limit");
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* INLINE mode: track a mention whose atomic token was already inserted into the
|
|
174
|
+
* composer by the contenteditable adapter, keyed by its `ComposerMentionId`. No
|
|
175
|
+
* chip; duplicate/limit were gated by `admit()` before insertion. `reportStatus`
|
|
176
|
+
* reflects resolve state onto the live token element. Starts resolve-on-select
|
|
177
|
+
* just like `add()`.
|
|
178
|
+
*/
|
|
179
|
+
track(
|
|
180
|
+
id: string,
|
|
181
|
+
source: AgentWidgetContextMentionSource,
|
|
182
|
+
item: AgentWidgetContextMentionItem,
|
|
183
|
+
args = "",
|
|
184
|
+
reportStatus?: (status: "pending" | "resolved" | "error") => void
|
|
185
|
+
): void {
|
|
186
|
+
const pending: PendingMention = {
|
|
187
|
+
key: id,
|
|
188
|
+
source,
|
|
189
|
+
item,
|
|
190
|
+
ref: refFromItem(source, item),
|
|
191
|
+
status: "resolving",
|
|
192
|
+
args,
|
|
193
|
+
reportStatus,
|
|
194
|
+
};
|
|
195
|
+
this.startPending(pending);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Fire the rejection hooks for a blocked mention and return false (add's sentinel). */
|
|
199
|
+
private reject(
|
|
200
|
+
source: AgentWidgetContextMentionSource,
|
|
201
|
+
item: AgentWidgetContextMentionItem,
|
|
202
|
+
reason: "duplicate" | "limit"
|
|
203
|
+
): false {
|
|
204
|
+
this.opts.mentionConfig.onMentionRejected?.(item, reason);
|
|
205
|
+
this.opts.emit?.("rejected", { sourceId: source.id, itemId: item.id, reason });
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Push a fully-built pending mention and kick off its resolve (shared by
|
|
211
|
+
* `add()` and `track()`): `resolveOn:"submit"` flips straight to ready; anything
|
|
212
|
+
* else starts the select-time resolve, keeping the promise so `finalize()`
|
|
213
|
+
* awaits this exact resolve rather than firing a second one.
|
|
214
|
+
*/
|
|
215
|
+
private startPending(pending: PendingMention): void {
|
|
216
|
+
this.mentions.push(pending);
|
|
217
|
+
if (pending.source.resolveOn === "submit") {
|
|
218
|
+
pending.status = "ready";
|
|
219
|
+
pending.chip?.setStatus("ready");
|
|
220
|
+
pending.reportStatus?.("resolved");
|
|
221
|
+
} else {
|
|
222
|
+
pending.resolvePromise = this.resolvePending(pending);
|
|
223
|
+
}
|
|
224
|
+
this.opts.announce(`Added ${pending.ref.label} to context`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private buildResolveContext(
|
|
228
|
+
signal: AbortSignal,
|
|
229
|
+
args: string,
|
|
230
|
+
composerText = this.opts.getComposerText()
|
|
231
|
+
) {
|
|
232
|
+
return {
|
|
233
|
+
messages: this.opts.getMessages(),
|
|
234
|
+
config: this.opts.getConfig(),
|
|
235
|
+
composerText,
|
|
236
|
+
args,
|
|
237
|
+
signal,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private async resolvePending(pending: PendingMention): Promise<void> {
|
|
242
|
+
const abort = new AbortController();
|
|
243
|
+
pending.abort = abort;
|
|
244
|
+
try {
|
|
245
|
+
const payload = await pending.source.resolve(
|
|
246
|
+
pending.item,
|
|
247
|
+
this.buildResolveContext(abort.signal, pending.args)
|
|
248
|
+
);
|
|
249
|
+
if (abort.signal.aborted) return;
|
|
250
|
+
pending.payload = payload;
|
|
251
|
+
pending.status = "ready";
|
|
252
|
+
pending.chip?.setStatus("ready", payload);
|
|
253
|
+
pending.reportStatus?.("resolved");
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (abort.signal.aborted) return;
|
|
256
|
+
pending.status = "error";
|
|
257
|
+
pending.chip?.setStatus("error");
|
|
258
|
+
// Inline tokens carry no chip: surface the failure on the token element so a
|
|
259
|
+
// dropped context is visible (finalize() silently skips failed payloads).
|
|
260
|
+
pending.reportStatus?.("error");
|
|
261
|
+
// Speak the failure through the ASSERTIVE region — the visual error state
|
|
262
|
+
// (chip/token color) is otherwise silent for screen-reader users.
|
|
263
|
+
(this.opts.announceError ?? this.opts.announce)(
|
|
264
|
+
`Couldn't attach ${pending.ref.label} to context`
|
|
265
|
+
);
|
|
266
|
+
this.opts.mentionConfig.onMentionResolveError?.(pending.item, error);
|
|
267
|
+
this.opts.emit?.("resolve-error", {
|
|
268
|
+
sourceId: pending.source.id,
|
|
269
|
+
itemId: pending.item.id,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
remove(key: string): void {
|
|
275
|
+
const index = this.mentions.findIndex((m) => m.key === key);
|
|
276
|
+
if (index === -1) return;
|
|
277
|
+
const [pending] = this.mentions.splice(index, 1);
|
|
278
|
+
pending.abort?.abort();
|
|
279
|
+
pending.chip?.el.remove();
|
|
280
|
+
this.updateRowVisibility();
|
|
281
|
+
this.opts.announce(`Removed ${pending.ref.label} from context`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Remove the most recently added chip (Backspace on an empty composer). */
|
|
285
|
+
removeLast(): boolean {
|
|
286
|
+
const last = this.mentions[this.mentions.length - 1];
|
|
287
|
+
if (!last) return false;
|
|
288
|
+
this.remove(last.key);
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
clear(): void {
|
|
293
|
+
for (const m of this.mentions) {
|
|
294
|
+
m.abort?.abort();
|
|
295
|
+
m.chip?.el.remove();
|
|
296
|
+
}
|
|
297
|
+
this.mentions.length = 0;
|
|
298
|
+
this.updateRowVisibility();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Gather everything needed at submit. Returns refs synchronously (for the
|
|
303
|
+
* echoed bubble) plus an async `finalize()` that resolves the mentions and
|
|
304
|
+
* returns the merged bundle, dropping failures.
|
|
305
|
+
*
|
|
306
|
+
* Ownership transfer: the pending mentions are DETACHED from the manager here
|
|
307
|
+
* (removed from the list, chip DOM cleared) WITHOUT aborting their in-flight
|
|
308
|
+
* select-time resolves. So the post-submit `clear()` the UI calls is a no-op
|
|
309
|
+
* that can't abort them, and `finalize()` awaits each existing resolve instead
|
|
310
|
+
* of firing a duplicate fetch. `composerText` is captured now for submit-time
|
|
311
|
+
* sources, since the composer is cleared right after.
|
|
312
|
+
*/
|
|
313
|
+
collectForSubmit(): { refs: AgentWidgetContextMentionRef[]; finalize: () => Promise<MentionSubmitBundle> } {
|
|
314
|
+
const snapshot = [...this.mentions];
|
|
315
|
+
const refs = snapshot.map((m) => m.ref);
|
|
316
|
+
const composerText = this.opts.getComposerText();
|
|
317
|
+
|
|
318
|
+
// Detach: empty the composer chip row without aborting resolves.
|
|
319
|
+
for (const m of snapshot) m.chip?.el.remove();
|
|
320
|
+
this.mentions.length = 0;
|
|
321
|
+
this.updateRowVisibility();
|
|
322
|
+
|
|
323
|
+
const finalize = async (): Promise<MentionSubmitBundle> => {
|
|
324
|
+
// Resolve every mention concurrently: submit-deferred sources resolve now;
|
|
325
|
+
// select-time sources reuse their in-flight promise (never re-fetched).
|
|
326
|
+
const payloads = await Promise.all(
|
|
327
|
+
snapshot.map(async (m): Promise<AgentWidgetContextMentionPayload | null> => {
|
|
328
|
+
try {
|
|
329
|
+
if (m.source.resolveOn === "submit") {
|
|
330
|
+
return await m.source.resolve(
|
|
331
|
+
m.item,
|
|
332
|
+
this.buildResolveContext(new AbortController().signal, m.args, composerText)
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
if (m.resolvePromise) await m.resolvePromise;
|
|
336
|
+
return m.payload ?? null; // null → select-resolve already failed/dropped
|
|
337
|
+
} catch (error) {
|
|
338
|
+
// A throwing host `onMentionResolveError` must not reject the whole
|
|
339
|
+
// bundle — guard the callback so one bad item just drops.
|
|
340
|
+
try {
|
|
341
|
+
this.opts.mentionConfig.onMentionResolveError?.(m.item, error);
|
|
342
|
+
} catch (cbError) {
|
|
343
|
+
if (typeof console !== "undefined") {
|
|
344
|
+
console.warn("[Persona] onMentionResolveError callback threw", cbError);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
this.opts.emit?.("resolve-error", {
|
|
348
|
+
sourceId: m.source.id,
|
|
349
|
+
itemId: m.item.id,
|
|
350
|
+
});
|
|
351
|
+
return null; // drop and still send
|
|
352
|
+
}
|
|
353
|
+
})
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
const blocks: string[] = [];
|
|
357
|
+
const contentParts: ContentPart[] = [];
|
|
358
|
+
const context: Record<string, Record<string, unknown>> = {};
|
|
359
|
+
// Assemble in original selection order (mentions-first block), deduping the
|
|
360
|
+
// resolved payload by (source, item): inline mode allows the same item to
|
|
361
|
+
// appear as multiple prose tokens (Slack/Cursor/Claude), but its content
|
|
362
|
+
// must reach the LLM once. `refs` (returned above) still lists every token
|
|
363
|
+
// so the sent bubble renders them all — only the bundle is deduped. The
|
|
364
|
+
// dedupe claim is taken AFTER the null-payload skip, so if one duplicate's
|
|
365
|
+
// resolve failed but another succeeded, the successful one still wins.
|
|
366
|
+
const contributed = new Set<string>();
|
|
367
|
+
for (let i = 0; i < snapshot.length; i++) {
|
|
368
|
+
const m = snapshot[i];
|
|
369
|
+
const payload = payloads[i];
|
|
370
|
+
if (!payload) continue;
|
|
371
|
+
const dedupeKey = mentionKey(m.source.id, m.item.id);
|
|
372
|
+
if (contributed.has(dedupeKey)) continue;
|
|
373
|
+
contributed.add(dedupeKey);
|
|
374
|
+
if (payload.llmAppend && payload.llmAppend.trim()) {
|
|
375
|
+
// Format each block here (the mention layer owns `mentionConfig`); the
|
|
376
|
+
// block index is its position among contributed blocks so `"document"`
|
|
377
|
+
// numbering and the function form stay 0-based and gap-free. A throwing
|
|
378
|
+
// host `llmFormat` must drop only this block, not reject the bundle.
|
|
379
|
+
try {
|
|
380
|
+
blocks.push(
|
|
381
|
+
formatMentionBlock(
|
|
382
|
+
{ label: m.ref.label, text: payload.llmAppend, ref: m.ref, item: m.item },
|
|
383
|
+
blocks.length,
|
|
384
|
+
this.opts.mentionConfig.llmFormat
|
|
385
|
+
)
|
|
386
|
+
);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
if (typeof console !== "undefined") {
|
|
389
|
+
console.warn("[Persona] context-mention llmFormat threw", error);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (payload.contentParts?.length) {
|
|
394
|
+
contentParts.push(...payload.contentParts);
|
|
395
|
+
}
|
|
396
|
+
if (payload.context) {
|
|
397
|
+
(context[m.source.id] ??= {})[m.item.id] = payload.context;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return { blocks, contentParts, context };
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
return { refs, finalize };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private updateRowVisibility(): void {
|
|
408
|
+
this.opts.contextRow.style.display = this.mentions.length > 0 ? "flex" : "none";
|
|
409
|
+
}
|
|
410
|
+
}
|