@agent-native/toolkit 0.198.4 → 0.198.5
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/dist/app/agentkit/react/components.d.ts +4 -2
- package/dist/app/agentkit/react/components.d.ts.map +1 -1
- package/dist/app/agentkit/react/components.js +12 -6
- package/dist/app/agentkit/react/components.js.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.js +22 -12
- package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
- package/dist/composer/PromptComposer.d.ts +3 -0
- package/dist/composer/PromptComposer.d.ts.map +1 -1
- package/dist/composer/PromptComposer.js +2 -2
- package/dist/composer/PromptComposer.js.map +1 -1
- package/dist/composer/TiptapComposer.d.ts +4 -1
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +8 -4
- package/dist/composer/TiptapComposer.js.map +1 -1
- package/package.json +3 -3
- package/src/app/agentkit/react/components.interaction.spec.tsx +93 -0
- package/src/app/agentkit/react/components.spec.tsx +1 -2
- package/src/app/agentkit/react/components.tsx +29 -4
- package/src/app/agentkit/react/composer-context.spec.tsx +7 -0
- package/src/app/chat/AgentKitAssistantChat.spec.tsx +38 -1
- package/src/app/chat/AgentKitAssistantChat.tsx +32 -11
- package/src/composer/PromptComposer.tsx +7 -0
- package/src/composer/TiptapComposer.spec.ts +26 -4
- package/src/composer/TiptapComposer.tsx +12 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/toolkit",
|
|
3
|
-
"version": "0.198.
|
|
3
|
+
"version": "0.198.5",
|
|
4
4
|
"description": "Reusable app-building UI and helpers for Agent-Native apps.",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -665,7 +665,7 @@
|
|
|
665
665
|
"y-protocols": "^1.0.7",
|
|
666
666
|
"yjs": "^13.6.33",
|
|
667
667
|
"zod": "^4.3.6",
|
|
668
|
-
"@agent-native/agentkit": "^0.198.
|
|
668
|
+
"@agent-native/agentkit": "^0.198.5"
|
|
669
669
|
},
|
|
670
670
|
"devDependencies": {
|
|
671
671
|
"@excalidraw/excalidraw": "0.18.1",
|
|
@@ -686,7 +686,7 @@
|
|
|
686
686
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
687
687
|
"vite": "8.1.0",
|
|
688
688
|
"vitest": "^4.1.5",
|
|
689
|
-
"@agent-native/core": "^0.198.
|
|
689
|
+
"@agent-native/core": "^0.198.5"
|
|
690
690
|
},
|
|
691
691
|
"peerDependencies": {
|
|
692
692
|
"@agent-native/core": ">=0.196.0",
|
|
@@ -95,6 +95,97 @@ describe("AgentKitChat interactions", () => {
|
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
+
it("announces async preflight when the transcript slot is custom", async () => {
|
|
99
|
+
const preflight = Promise.withResolvers<boolean>();
|
|
100
|
+
const client = new AgentKitClient({
|
|
101
|
+
transport: {
|
|
102
|
+
async startRun() {
|
|
103
|
+
return { runId: "run-preflight" };
|
|
104
|
+
},
|
|
105
|
+
async *subscribeToRun() {},
|
|
106
|
+
async cancelRun() {},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
const composerRef = createRef<TiptapComposerHandle>();
|
|
110
|
+
const container = document.createElement("div");
|
|
111
|
+
document.body.appendChild(container);
|
|
112
|
+
const root = createRoot(container);
|
|
113
|
+
const actEnvironment = globalThis as typeof globalThis & {
|
|
114
|
+
IS_REACT_ACT_ENVIRONMENT?: boolean;
|
|
115
|
+
};
|
|
116
|
+
const previousActEnvironment = actEnvironment.IS_REACT_ACT_ENVIRONMENT;
|
|
117
|
+
actEnvironment.IS_REACT_ACT_ENVIRONMENT = true;
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
await act(async () => {
|
|
121
|
+
root.render(
|
|
122
|
+
<AgentKitProvider
|
|
123
|
+
controller={client}
|
|
124
|
+
threadId="thread-preflight"
|
|
125
|
+
slots={{
|
|
126
|
+
transcript: ({ children }) =>
|
|
127
|
+
createElement(
|
|
128
|
+
"div",
|
|
129
|
+
{ "data-custom-transcript": true },
|
|
130
|
+
children,
|
|
131
|
+
),
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
<AgentKitChat
|
|
135
|
+
composerProps={{
|
|
136
|
+
composerRef,
|
|
137
|
+
autoFocus: false,
|
|
138
|
+
modelStatusChecksEnabled: false,
|
|
139
|
+
onBeforeSubmit: () => preflight.promise,
|
|
140
|
+
voiceEnabled: false,
|
|
141
|
+
}}
|
|
142
|
+
/>
|
|
143
|
+
</AgentKitProvider>,
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
await act(async () => composerRef.current!.setText("Check this message"));
|
|
147
|
+
|
|
148
|
+
const editor = container.querySelector<HTMLElement>(
|
|
149
|
+
'[contenteditable="true"]',
|
|
150
|
+
)!;
|
|
151
|
+
const send = container.querySelector<HTMLButtonElement>(
|
|
152
|
+
'[data-agent-composer-slot="send-button"]',
|
|
153
|
+
)!;
|
|
154
|
+
await act(async () => {
|
|
155
|
+
send.click();
|
|
156
|
+
await Promise.resolve();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(editor.textContent).toBe("");
|
|
160
|
+
expect(container.querySelector('[role="status"]')?.textContent).toBe(
|
|
161
|
+
"Thinking",
|
|
162
|
+
);
|
|
163
|
+
expect(send.disabled).toBe(true);
|
|
164
|
+
expect(send.getAttribute("aria-busy")).toBeNull();
|
|
165
|
+
expect(send.querySelector(".animate-spin")).toBeNull();
|
|
166
|
+
|
|
167
|
+
await act(async () => {
|
|
168
|
+
preflight.resolve(false);
|
|
169
|
+
await preflight.promise;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
expect(editor.textContent).toBe("Check this message");
|
|
173
|
+
expect(send.disabled).toBe(false);
|
|
174
|
+
expect(container.querySelector('[role="status"]')).toBeNull();
|
|
175
|
+
} finally {
|
|
176
|
+
preflight.resolve(false);
|
|
177
|
+
await act(async () => root.unmount());
|
|
178
|
+
await client.shutdown();
|
|
179
|
+
container.remove();
|
|
180
|
+
localStorage.removeItem(getComposerDraftKey("agentkit:thread-preflight"));
|
|
181
|
+
if (previousActEnvironment === undefined) {
|
|
182
|
+
delete actEnvironment.IS_REACT_ACT_ENVIRONMENT;
|
|
183
|
+
} else {
|
|
184
|
+
actEnvironment.IS_REACT_ACT_ENVIRONMENT = previousActEnvironment;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
98
189
|
it.each(["default", "compact"] as const)(
|
|
99
190
|
"clears the %s composer before startRun resolves and preserves the next draft",
|
|
100
191
|
async (layoutVariant) => {
|
|
@@ -160,6 +251,8 @@ describe("AgentKitChat interactions", () => {
|
|
|
160
251
|
).toContain("First prompt");
|
|
161
252
|
expect(client.getThread("thread-latency").activeRunIds).toEqual([]);
|
|
162
253
|
expect(editor.textContent).toBe("");
|
|
254
|
+
expect(send.getAttribute("aria-busy")).toBeNull();
|
|
255
|
+
expect(send.querySelector(".animate-spin")).toBeNull();
|
|
163
256
|
|
|
164
257
|
await act(async () => composerRef.current!.setText("Next draft"));
|
|
165
258
|
await act(async () => started.resolve({ runId: "run-latency" }));
|
|
@@ -476,8 +476,7 @@ describe("AgentKitChat", () => {
|
|
|
476
476
|
expect(source).toContain(
|
|
477
477
|
"const submissionBlocked = Boolean(submissionDisabled) || command.pending",
|
|
478
478
|
);
|
|
479
|
-
expect(source).toContain("submitting={command.pending}");
|
|
480
|
-
expect(source).toContain("submissionDisabled={submissionBlocked}");
|
|
479
|
+
expect(source).not.toContain("submitting={command.pending}");
|
|
481
480
|
expect(source).toContain("if (disabled)");
|
|
482
481
|
expect(source).toContain(
|
|
483
482
|
"willQueue={active && queueWhileRunning && canQueue}",
|
|
@@ -3054,6 +3054,7 @@ export interface AgentKitComposerProps extends Omit<
|
|
|
3054
3054
|
| "extraActionButton"
|
|
3055
3055
|
| "onSubmit"
|
|
3056
3056
|
| "onBeforeSubmit"
|
|
3057
|
+
| "onSubmissionPendingChange"
|
|
3057
3058
|
| "onAttachmentError"
|
|
3058
3059
|
| "interceptBuildRequestsForBuilder"
|
|
3059
3060
|
| "planModeDisabled"
|
|
@@ -3068,6 +3069,8 @@ export interface AgentKitComposerProps extends Omit<
|
|
|
3068
3069
|
composerRef?: { current: TiptapComposerHandle | null };
|
|
3069
3070
|
threadId?: string;
|
|
3070
3071
|
className?: string;
|
|
3072
|
+
/** The host transcript renders its own accessible submission status. */
|
|
3073
|
+
announcePendingSubmission?: boolean;
|
|
3071
3074
|
queueWhileRunning?: boolean;
|
|
3072
3075
|
showModelSelector?: boolean;
|
|
3073
3076
|
/** Controlled execution mode for agent-native act/plan workflows. */
|
|
@@ -3087,6 +3090,7 @@ function hasActiveRuns(thread: AgentThreadState): boolean {
|
|
|
3087
3090
|
export function AgentKitComposer({
|
|
3088
3091
|
threadId: requestedThreadId,
|
|
3089
3092
|
className,
|
|
3093
|
+
announcePendingSubmission = true,
|
|
3090
3094
|
queueWhileRunning = true,
|
|
3091
3095
|
showModelSelector = true,
|
|
3092
3096
|
slashCommands,
|
|
@@ -3135,6 +3139,7 @@ export function AgentKitComposer({
|
|
|
3135
3139
|
extraActionButton,
|
|
3136
3140
|
onSubmit: onSubmitOverride,
|
|
3137
3141
|
onBeforeSubmit,
|
|
3142
|
+
onSubmissionPendingChange,
|
|
3138
3143
|
onAttachmentError,
|
|
3139
3144
|
interceptBuildRequestsForBuilder,
|
|
3140
3145
|
planModeDisabled,
|
|
@@ -3191,6 +3196,15 @@ export function AgentKitComposer({
|
|
|
3191
3196
|
threadId,
|
|
3192
3197
|
);
|
|
3193
3198
|
const submissionBlocked = Boolean(submissionDisabled) || command.pending;
|
|
3199
|
+
const [submissionPending, setSubmissionPending] = useState(false);
|
|
3200
|
+
const handleSubmissionPendingChange = useCallback(
|
|
3201
|
+
(pending: boolean) => {
|
|
3202
|
+
setSubmissionPending(pending);
|
|
3203
|
+
onSubmissionPendingChange?.(pending);
|
|
3204
|
+
},
|
|
3205
|
+
[onSubmissionPendingChange],
|
|
3206
|
+
);
|
|
3207
|
+
const suggestionSubmitBlocked = submissionBlocked || submissionPending;
|
|
3194
3208
|
const localComposerRef = useRef<TiptapComposerHandle>(null);
|
|
3195
3209
|
const composerRef = hostComposerRef ?? localComposerRef;
|
|
3196
3210
|
const selectedSuggestionRef = useRef<
|
|
@@ -3513,7 +3527,7 @@ export function AgentKitComposer({
|
|
|
3513
3527
|
) => {
|
|
3514
3528
|
if (
|
|
3515
3529
|
disabled ||
|
|
3516
|
-
|
|
3530
|
+
suggestionSubmitBlocked ||
|
|
3517
3531
|
!suggestionsCapability.enabled ||
|
|
3518
3532
|
selectedSuggestionRef.current ||
|
|
3519
3533
|
!isCurrentAgentSuggestion(controller.getThread(threadId), suggestion)
|
|
@@ -3536,6 +3550,15 @@ export function AgentKitComposer({
|
|
|
3536
3550
|
const suggestions = selectAgentSuggestions(thread);
|
|
3537
3551
|
return (
|
|
3538
3552
|
<div className={`agentkit-composer-stack ${className ?? ""}`}>
|
|
3553
|
+
{announcePendingSubmission && submissionPending ? (
|
|
3554
|
+
<span
|
|
3555
|
+
className="agentkit-visually-hidden"
|
|
3556
|
+
role="status"
|
|
3557
|
+
aria-live="polite"
|
|
3558
|
+
>
|
|
3559
|
+
{labels.activityBuckets?.thinking ?? "Thinking"}
|
|
3560
|
+
</span>
|
|
3561
|
+
) : null}
|
|
3539
3562
|
{queueCapability.visible ? (
|
|
3540
3563
|
Queue ? (
|
|
3541
3564
|
<Queue
|
|
@@ -3611,14 +3634,14 @@ export function AgentKitComposer({
|
|
|
3611
3634
|
<Suggestions
|
|
3612
3635
|
suggestions={suggestions}
|
|
3613
3636
|
threadId={threadId}
|
|
3614
|
-
pending={
|
|
3637
|
+
pending={suggestionSubmitBlocked}
|
|
3615
3638
|
onSelect={selectSuggestion}
|
|
3616
3639
|
/>
|
|
3617
3640
|
) : (
|
|
3618
3641
|
<AgentSuggestionBar
|
|
3619
3642
|
suggestions={suggestions.map((suggestion) => ({
|
|
3620
3643
|
...suggestion,
|
|
3621
|
-
disabled:
|
|
3644
|
+
disabled: suggestionSubmitBlocked || Boolean(disabled),
|
|
3622
3645
|
}))}
|
|
3623
3646
|
ariaLabel={labels.suggestions}
|
|
3624
3647
|
onSelect={selectSuggestion}
|
|
@@ -3646,7 +3669,6 @@ export function AgentKitComposer({
|
|
|
3646
3669
|
imageModelMenu={imageModelMenu}
|
|
3647
3670
|
autoFocus={autoFocus}
|
|
3648
3671
|
composerRef={composerRef}
|
|
3649
|
-
submitting={command.pending}
|
|
3650
3672
|
willQueue={active && queueWhileRunning && canQueue}
|
|
3651
3673
|
showModelSelector={showModelSelector && canSelectModel}
|
|
3652
3674
|
availableModels={availableModels}
|
|
@@ -3669,8 +3691,11 @@ export function AgentKitComposer({
|
|
|
3669
3691
|
initialTextKey={composerInitialTextKey}
|
|
3670
3692
|
onTextChange={onTextChange}
|
|
3671
3693
|
extraActionButton={extraActionButton}
|
|
3694
|
+
sendButtonDisabled={submissionPending}
|
|
3672
3695
|
onBeforeSubmit={onBeforeSubmit}
|
|
3696
|
+
onSubmissionPendingChange={handleSubmissionPendingChange}
|
|
3673
3697
|
getSubmitFailureDraftScope={getSubmitFailureDraftScope}
|
|
3698
|
+
clearOnSubmitImmediately
|
|
3674
3699
|
onAttachmentError={reportAttachmentError}
|
|
3675
3700
|
interceptBuildRequestsForBuilder={interceptBuildRequestsForBuilder}
|
|
3676
3701
|
planModeDisabled={planModeDisabled}
|
|
@@ -317,6 +317,7 @@ describe("AgentKit composer context submission", () => {
|
|
|
317
317
|
expect(capture.props).toMatchObject({
|
|
318
318
|
contextItems: source,
|
|
319
319
|
contextMenuItems,
|
|
320
|
+
clearOnSubmitImmediately: true,
|
|
320
321
|
onRemoveContextItem,
|
|
321
322
|
onInspectContextItem,
|
|
322
323
|
onRetryContextItem,
|
|
@@ -332,6 +333,7 @@ describe("AgentKit composer context submission", () => {
|
|
|
332
333
|
];
|
|
333
334
|
let pending: void | Promise<void>;
|
|
334
335
|
await act(async () => {
|
|
336
|
+
capture.props!.onSubmissionPendingChange?.(true);
|
|
335
337
|
pending = capture.props!.onSubmit("Review", [], references, {
|
|
336
338
|
contextItems: source,
|
|
337
339
|
composerModeContext: "Use scheduling tools for this request.",
|
|
@@ -341,6 +343,9 @@ describe("AgentKit composer context submission", () => {
|
|
|
341
343
|
expect(beforeSend).toHaveBeenCalledOnce();
|
|
342
344
|
expect(runtime.startRun).not.toHaveBeenCalled();
|
|
343
345
|
expect(runtime.queueMessage).not.toHaveBeenCalled();
|
|
346
|
+
expect(container.querySelector('[role="status"]')?.textContent).toBe(
|
|
347
|
+
"Thinking",
|
|
348
|
+
);
|
|
344
349
|
expect(Object.isFrozen(saved)).toBe(true);
|
|
345
350
|
expect(Object.isFrozen(saved!.contextItems![0])).toBe(true);
|
|
346
351
|
expect(Object.isFrozen(saved!.references[0].metadata)).toBe(true);
|
|
@@ -349,7 +354,9 @@ describe("AgentKit composer context submission", () => {
|
|
|
349
354
|
await act(async () => {
|
|
350
355
|
release();
|
|
351
356
|
await pending;
|
|
357
|
+
capture.props!.onSubmissionPendingChange?.(false);
|
|
352
358
|
});
|
|
359
|
+
expect(container.querySelector('[role="status"]')).toBeNull();
|
|
353
360
|
const input =
|
|
354
361
|
intent === "queued"
|
|
355
362
|
? runtime.queueMessage.mock.calls[0][0]
|
|
@@ -796,11 +796,27 @@ describe("AgentKitAssistantChat host behavior", () => {
|
|
|
796
796
|
|
|
797
797
|
expect(chatMocks.composerProps.disabled).toBe(false);
|
|
798
798
|
expect(chatMocks.composerProps.submissionDisabled).toBe(true);
|
|
799
|
+
expect(chatMocks.composerProps.announcePendingSubmission).toBe(false);
|
|
799
800
|
expect(container.querySelector('[role="status"]')?.textContent).toBe(
|
|
800
801
|
"agentChat.status.thinking",
|
|
801
802
|
);
|
|
802
803
|
});
|
|
803
804
|
|
|
805
|
+
it("keeps the pending announcement when the transcript has no visible user message", async () => {
|
|
806
|
+
chatMocks.history = { isSubmissionInFlight: true };
|
|
807
|
+
chatMocks.thread.messages = [
|
|
808
|
+
{
|
|
809
|
+
id: "assistant-last",
|
|
810
|
+
role: "assistant",
|
|
811
|
+
parts: [{ type: "text", text: "Earlier response" }],
|
|
812
|
+
},
|
|
813
|
+
];
|
|
814
|
+
await mount(baseProps());
|
|
815
|
+
|
|
816
|
+
expect(chatMocks.composerProps.announcePendingSubmission).toBe(true);
|
|
817
|
+
expect(container.querySelector('[role="status"]')).toBeNull();
|
|
818
|
+
});
|
|
819
|
+
|
|
804
820
|
it("gives JS callers a migration error for the removed createAdapter prop", async () => {
|
|
805
821
|
const errors: Error[] = [];
|
|
806
822
|
container = document.createElement("div");
|
|
@@ -957,7 +973,6 @@ describe("AgentKitAssistantChat host behavior", () => {
|
|
|
957
973
|
.querySelector<HTMLElement>("[contenteditable]")
|
|
958
974
|
?.getAttribute("contenteditable"),
|
|
959
975
|
).toBe("true");
|
|
960
|
-
|
|
961
976
|
await act(async () => composer.setText("Next draft"));
|
|
962
977
|
await act(async () => started.resolve({ runId: "run-latency" }));
|
|
963
978
|
|
|
@@ -1007,6 +1022,28 @@ describe("AgentKitAssistantChat host behavior", () => {
|
|
|
1007
1022
|
},
|
|
1008
1023
|
);
|
|
1009
1024
|
|
|
1025
|
+
it("disables host suggestions while async submission is pending", async () => {
|
|
1026
|
+
const suggestion = seedFollowup();
|
|
1027
|
+
await mount(baseProps());
|
|
1028
|
+
const chips = () =>
|
|
1029
|
+
[...container.querySelectorAll("button")].filter(
|
|
1030
|
+
(button) => button.textContent === suggestion.label,
|
|
1031
|
+
);
|
|
1032
|
+
expect(chips().length).toBeGreaterThan(0);
|
|
1033
|
+
expect(chips().some((button) => button.disabled)).toBe(false);
|
|
1034
|
+
|
|
1035
|
+
await act(async () => {
|
|
1036
|
+
chatMocks.composerProps.onSubmissionPendingChange(true);
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
expect(chips().every((button) => button.disabled)).toBe(true);
|
|
1040
|
+
|
|
1041
|
+
await act(async () => {
|
|
1042
|
+
chatMocks.composerProps.onSubmissionPendingChange(false);
|
|
1043
|
+
});
|
|
1044
|
+
expect(chips().every((button) => !button.disabled)).toBe(true);
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1010
1047
|
it.each([undefined, "context-chips", "after-composer", "hidden"] as const)(
|
|
1011
1048
|
"preserves initial starters with the real composer (placement: %s)",
|
|
1012
1049
|
async (suggestionPlacement) => {
|
|
@@ -511,6 +511,9 @@ interface AgentKitSurfaceContextValue {
|
|
|
511
511
|
setupBouncePulse: number;
|
|
512
512
|
bounceSetupCard: () => void;
|
|
513
513
|
isSubmissionInFlight: boolean;
|
|
514
|
+
composerSubmissionPending: boolean;
|
|
515
|
+
onComposerSubmissionPendingChange: (pending: boolean) => void;
|
|
516
|
+
isThinkingVisibleInTranscript: boolean;
|
|
514
517
|
contextItems: AgentChatContextItem[];
|
|
515
518
|
suggestions: AgentSuggestionInput[];
|
|
516
519
|
showSuggestions: boolean;
|
|
@@ -1149,6 +1152,8 @@ const AgentKitAssistantChatBody = forwardRef<
|
|
|
1149
1152
|
const isRestoring =
|
|
1150
1153
|
history?.isRestoring === true || props.threadRestore.status === "loading";
|
|
1151
1154
|
const isSubmissionInFlight = history?.isSubmissionInFlight === true;
|
|
1155
|
+
const [composerSubmissionPending, setComposerSubmissionPending] =
|
|
1156
|
+
useState(false);
|
|
1152
1157
|
const [authError, setAuthError] = useState<{
|
|
1153
1158
|
sessionExpired?: boolean;
|
|
1154
1159
|
} | null>(null);
|
|
@@ -1295,6 +1300,11 @@ const AgentKitAssistantChatBody = forwardRef<
|
|
|
1295
1300
|
[thread.messages],
|
|
1296
1301
|
);
|
|
1297
1302
|
const isRunning = hasActiveAgentRuns(thread);
|
|
1303
|
+
const lastMessage = thread.messages.at(-1);
|
|
1304
|
+
const isThinkingVisibleInTranscript =
|
|
1305
|
+
(isRunning || isSubmissionInFlight) &&
|
|
1306
|
+
lastMessage?.role === "user" &&
|
|
1307
|
+
lastMessage.metadata?.hideUserMessage !== true;
|
|
1298
1308
|
const isThreadRunning = useCallback(
|
|
1299
1309
|
() => hasActiveAgentRuns(controller.getThread(threadId)),
|
|
1300
1310
|
[controller, threadId],
|
|
@@ -2597,13 +2607,14 @@ const AgentKitAssistantChatBody = forwardRef<
|
|
|
2597
2607
|
);
|
|
2598
2608
|
const submitSuggestion = useCallback(
|
|
2599
2609
|
(suggestion: AgentSuggestionInput) => {
|
|
2610
|
+
if (composerSubmissionPending) return;
|
|
2600
2611
|
const handler = suggestionSubmitRef.current;
|
|
2601
2612
|
if (!handler || handler.threadId !== threadId) return;
|
|
2602
2613
|
void handler.submit(suggestion).catch((error) => {
|
|
2603
2614
|
dispatchSetupRequiredEvent(error, props.tabId, threadId);
|
|
2604
2615
|
});
|
|
2605
2616
|
},
|
|
2606
|
-
[props.tabId, threadId],
|
|
2617
|
+
[composerSubmissionPending, props.tabId, threadId],
|
|
2607
2618
|
);
|
|
2608
2619
|
const retryDeferredSubmission = useCallback(async () => {
|
|
2609
2620
|
if (!deferredProviderSubmissionFailureId) return;
|
|
@@ -2783,6 +2794,9 @@ const AgentKitAssistantChatBody = forwardRef<
|
|
|
2783
2794
|
setupBouncePulse,
|
|
2784
2795
|
bounceSetupCard,
|
|
2785
2796
|
isSubmissionInFlight,
|
|
2797
|
+
composerSubmissionPending,
|
|
2798
|
+
onComposerSubmissionPendingChange: setComposerSubmissionPending,
|
|
2799
|
+
isThinkingVisibleInTranscript,
|
|
2786
2800
|
contextItems,
|
|
2787
2801
|
voiceTranscriptMessages,
|
|
2788
2802
|
selectionLength,
|
|
@@ -2967,7 +2981,8 @@ function AgentKitEmptyState({ threadId }: { threadId: string }) {
|
|
|
2967
2981
|
disabled={
|
|
2968
2982
|
!surface.canChat ||
|
|
2969
2983
|
surface.props.composerDisabled ||
|
|
2970
|
-
surface.props.composerSubmissionDisabled
|
|
2984
|
+
surface.props.composerSubmissionDisabled ||
|
|
2985
|
+
surface.composerSubmissionPending
|
|
2971
2986
|
}
|
|
2972
2987
|
onClick={() => surface.submitSuggestion(suggestion)}
|
|
2973
2988
|
className="w-full rounded-xl border border-border/70 bg-card/60 px-3 py-2.5 text-left text-[13px] text-muted-foreground shadow-sm transition-colors hover:border-border hover:bg-card hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
@@ -3065,11 +3080,6 @@ function AgentKitTranscript({ children, threadId }: AgentKitRegionRenderProps) {
|
|
|
3065
3080
|
const threadMessageIds = new Set(
|
|
3066
3081
|
thread.messages.map((message) => message.id),
|
|
3067
3082
|
);
|
|
3068
|
-
const lastMessage = thread.messages.at(-1);
|
|
3069
|
-
const showThinking =
|
|
3070
|
-
(surface.isRunning || surface.isSubmissionInFlight) &&
|
|
3071
|
-
lastMessage?.role === "user" &&
|
|
3072
|
-
lastMessage.metadata?.hideUserMessage !== true;
|
|
3073
3083
|
const pendingVoiceMessages = surface.voiceTranscriptMessages.filter(
|
|
3074
3084
|
(message) => !threadMessageIds.has(message.id),
|
|
3075
3085
|
);
|
|
@@ -3090,7 +3100,8 @@ function AgentKitTranscript({ children, threadId }: AgentKitRegionRenderProps) {
|
|
|
3090
3100
|
!surface.canChat ||
|
|
3091
3101
|
surface.props.composerDisabled ||
|
|
3092
3102
|
surface.props.composerSubmissionDisabled ||
|
|
3093
|
-
surface.isSubmissionInFlight
|
|
3103
|
+
surface.isSubmissionInFlight ||
|
|
3104
|
+
surface.composerSubmissionPending
|
|
3094
3105
|
}
|
|
3095
3106
|
onSelect={surface.submitSuggestion}
|
|
3096
3107
|
className="agentkit-host-suggestions"
|
|
@@ -3246,7 +3257,7 @@ function AgentKitTranscript({ children, threadId }: AgentKitRegionRenderProps) {
|
|
|
3246
3257
|
threadId={threadId}
|
|
3247
3258
|
/>
|
|
3248
3259
|
))}
|
|
3249
|
-
{
|
|
3260
|
+
{surface.isThinkingVisibleInTranscript ? (
|
|
3250
3261
|
<div
|
|
3251
3262
|
role="status"
|
|
3252
3263
|
aria-live="polite"
|
|
@@ -3395,6 +3406,9 @@ function AgentKitComposerSurface({
|
|
|
3395
3406
|
isRunning,
|
|
3396
3407
|
isRestoring,
|
|
3397
3408
|
isSubmissionInFlight,
|
|
3409
|
+
composerSubmissionPending,
|
|
3410
|
+
onComposerSubmissionPendingChange,
|
|
3411
|
+
isThinkingVisibleInTranscript,
|
|
3398
3412
|
hasRenderedMessages,
|
|
3399
3413
|
threadRestore,
|
|
3400
3414
|
suggestions,
|
|
@@ -3430,6 +3444,9 @@ function AgentKitComposerSurface({
|
|
|
3430
3444
|
isRunning: boolean;
|
|
3431
3445
|
isRestoring: boolean;
|
|
3432
3446
|
isSubmissionInFlight: boolean;
|
|
3447
|
+
composerSubmissionPending: boolean;
|
|
3448
|
+
onComposerSubmissionPendingChange: (pending: boolean) => void;
|
|
3449
|
+
isThinkingVisibleInTranscript: boolean;
|
|
3433
3450
|
hasRenderedMessages: boolean;
|
|
3434
3451
|
setupBouncePulse: number;
|
|
3435
3452
|
bounceSetupCard: () => void;
|
|
@@ -3675,7 +3692,8 @@ function AgentKitComposerSurface({
|
|
|
3675
3692
|
!canChat ||
|
|
3676
3693
|
props.composerDisabled ||
|
|
3677
3694
|
props.composerSubmissionDisabled ||
|
|
3678
|
-
isSubmissionInFlight
|
|
3695
|
+
isSubmissionInFlight ||
|
|
3696
|
+
composerSubmissionPending
|
|
3679
3697
|
}
|
|
3680
3698
|
onSelect={submitSuggestion}
|
|
3681
3699
|
className="agentkit-home-suggestions"
|
|
@@ -3749,6 +3767,8 @@ function AgentKitComposerSurface({
|
|
|
3749
3767
|
requireAgentEngine={false}
|
|
3750
3768
|
onTextChange={onTextChange}
|
|
3751
3769
|
onBeforeSubmit={onBeforeSubmit}
|
|
3770
|
+
announcePendingSubmission={!isThinkingVisibleInTranscript}
|
|
3771
|
+
onSubmissionPendingChange={onComposerSubmissionPendingChange}
|
|
3752
3772
|
contextItems={visibleContextItems}
|
|
3753
3773
|
contextMenuItems={composerContext?.menuItems}
|
|
3754
3774
|
onRemoveContextItem={(key) => {
|
|
@@ -3892,7 +3912,8 @@ function AgentKitComposerSurface({
|
|
|
3892
3912
|
!canChat ||
|
|
3893
3913
|
props.composerDisabled ||
|
|
3894
3914
|
props.composerSubmissionDisabled ||
|
|
3895
|
-
isSubmissionInFlight
|
|
3915
|
+
isSubmissionInFlight ||
|
|
3916
|
+
composerSubmissionPending
|
|
3896
3917
|
}
|
|
3897
3918
|
onSelect={submitSuggestion}
|
|
3898
3919
|
className="agentkit-home-suggestions"
|
|
@@ -106,6 +106,7 @@ export interface PromptComposerProps {
|
|
|
106
106
|
) => void | Promise<void>;
|
|
107
107
|
/** Return false to stop a submit before it reaches the host runtime. */
|
|
108
108
|
onBeforeSubmit?: () => boolean | Promise<boolean>;
|
|
109
|
+
onSubmissionPendingChange?: (pending: boolean) => void;
|
|
109
110
|
/** Scope where a failed submission should be recovered after the host forks. */
|
|
110
111
|
getSubmitFailureDraftScope?: () => string | null;
|
|
111
112
|
/** Handle file paste/drop errors in the host chat surface. */
|
|
@@ -118,6 +119,8 @@ export interface PromptComposerProps {
|
|
|
118
119
|
disabled?: boolean;
|
|
119
120
|
/** Block all submission paths while allowing draft, file, and context staging. */
|
|
120
121
|
submissionDisabled?: boolean;
|
|
122
|
+
/** Disable only the send control while the submission is being accepted. */
|
|
123
|
+
sendButtonDisabled?: boolean;
|
|
121
124
|
/** Prevent submission while preserving editor focus and draft entry. */
|
|
122
125
|
submitting?: boolean;
|
|
123
126
|
/** Present the primary action as queueing instead of immediate send. */
|
|
@@ -608,6 +611,7 @@ function PromptComposerInner({
|
|
|
608
611
|
ariaLabel,
|
|
609
612
|
disabled,
|
|
610
613
|
submissionDisabled,
|
|
614
|
+
sendButtonDisabled,
|
|
611
615
|
submitting,
|
|
612
616
|
willQueue = false,
|
|
613
617
|
onDisabledClick,
|
|
@@ -680,6 +684,7 @@ function PromptComposerInner({
|
|
|
680
684
|
imageModelMenu,
|
|
681
685
|
composerRef,
|
|
682
686
|
onBeforeSubmit,
|
|
687
|
+
onSubmissionPendingChange,
|
|
683
688
|
getSubmitFailureDraftScope,
|
|
684
689
|
onAttachmentError,
|
|
685
690
|
interceptBuildRequestsForBuilder,
|
|
@@ -890,6 +895,7 @@ function PromptComposerInner({
|
|
|
890
895
|
disabled={disabled}
|
|
891
896
|
contextControlsDisabled={engineSubmissionBlocked}
|
|
892
897
|
submissionDisabled={submissionDisabled || engineSubmissionBlocked}
|
|
898
|
+
sendButtonDisabled={sendButtonDisabled}
|
|
893
899
|
submitting={submitting}
|
|
894
900
|
willQueue={willQueue}
|
|
895
901
|
maxDocumentAttachmentBytes={maxDocumentAttachmentBytes}
|
|
@@ -899,6 +905,7 @@ function PromptComposerInner({
|
|
|
899
905
|
initialTextKey={initialTextKey}
|
|
900
906
|
onSubmit={handleSubmit}
|
|
901
907
|
onBeforeSubmit={onBeforeSubmit}
|
|
908
|
+
onSubmissionPendingChange={onSubmissionPendingChange}
|
|
902
909
|
getSubmitFailureDraftScope={getSubmitFailureDraftScope}
|
|
903
910
|
onAttachmentError={onAttachmentError}
|
|
904
911
|
interceptBuildRequestsForBuilder={interceptBuildRequestsForBuilder}
|
|
@@ -2166,7 +2166,13 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2166
2166
|
resolveSubmit = resolve;
|
|
2167
2167
|
});
|
|
2168
2168
|
const onBeforeSubmit = vi.fn(() => preflight);
|
|
2169
|
-
const
|
|
2169
|
+
const onSubmissionPendingChange = vi.fn();
|
|
2170
|
+
const onSubmit = vi.fn<NonNullable<TiptapComposerProps["onSubmit"]>>(
|
|
2171
|
+
async (_text, _references, _attachments, options) => {
|
|
2172
|
+
await submission;
|
|
2173
|
+
options?.onLocalSubmit?.();
|
|
2174
|
+
},
|
|
2175
|
+
);
|
|
2170
2176
|
const focusRef = React.createRef<TiptapComposerHandle>();
|
|
2171
2177
|
|
|
2172
2178
|
function Harness() {
|
|
@@ -2180,6 +2186,7 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2180
2186
|
React.createElement(TiptapComposer, {
|
|
2181
2187
|
focusRef,
|
|
2182
2188
|
onBeforeSubmit,
|
|
2189
|
+
onSubmissionPendingChange,
|
|
2183
2190
|
onSubmit,
|
|
2184
2191
|
clearOnSubmitImmediately: true,
|
|
2185
2192
|
includeDefaultSlashSkills: false,
|
|
@@ -2209,6 +2216,7 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2209
2216
|
|
|
2210
2217
|
expect(editor.textContent).toBe("");
|
|
2211
2218
|
expect(onSubmit).not.toHaveBeenCalled();
|
|
2219
|
+
expect(onSubmissionPendingChange).toHaveBeenLastCalledWith(true);
|
|
2212
2220
|
act(() => focusRef.current?.setText("follow-up prompt"));
|
|
2213
2221
|
|
|
2214
2222
|
await act(async () => {
|
|
@@ -2225,6 +2233,8 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2225
2233
|
);
|
|
2226
2234
|
expect(editor.textContent).toBe("follow-up prompt");
|
|
2227
2235
|
await act(async () => resolveSubmit());
|
|
2236
|
+
expect(editor.textContent).toBe("follow-up prompt");
|
|
2237
|
+
expect(onSubmissionPendingChange).toHaveBeenLastCalledWith(false);
|
|
2228
2238
|
});
|
|
2229
2239
|
|
|
2230
2240
|
it("restores a prompt when async preflight declines it", async () => {
|
|
@@ -2337,10 +2347,11 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2337
2347
|
|
|
2338
2348
|
it("clears immediately while submitting and restores the draft on failure", async () => {
|
|
2339
2349
|
let rejectSubmit!: (error: Error) => void;
|
|
2340
|
-
const onSubmit = vi.fn(
|
|
2341
|
-
() =>
|
|
2350
|
+
const onSubmit = vi.fn<NonNullable<TiptapComposerProps["onSubmit"]>>(
|
|
2351
|
+
(_text, _references, _attachments, options) =>
|
|
2342
2352
|
new Promise<void>((_resolve, reject) => {
|
|
2343
2353
|
rejectSubmit = reject;
|
|
2354
|
+
options?.onLocalSubmit?.();
|
|
2344
2355
|
}),
|
|
2345
2356
|
);
|
|
2346
2357
|
const focusRef = React.createRef<TiptapComposerHandle>();
|
|
@@ -2384,6 +2395,7 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2384
2395
|
|
|
2385
2396
|
expect(onSubmit).toHaveBeenCalledOnce();
|
|
2386
2397
|
expect(editor.textContent).toBe("");
|
|
2398
|
+
act(() => focusRef.current?.setText("follow-up prompt"));
|
|
2387
2399
|
|
|
2388
2400
|
await act(async () => {
|
|
2389
2401
|
rejectSubmit(new Error("network unavailable"));
|
|
@@ -2391,7 +2403,17 @@ describe("TiptapComposer slash commands", () => {
|
|
|
2391
2403
|
await Promise.resolve();
|
|
2392
2404
|
});
|
|
2393
2405
|
|
|
2394
|
-
expect(
|
|
2406
|
+
expect(
|
|
2407
|
+
[...editor.querySelectorAll("p")].map(
|
|
2408
|
+
(paragraph) => paragraph.textContent,
|
|
2409
|
+
),
|
|
2410
|
+
).toEqual(["send this once", "follow-up prompt"]);
|
|
2411
|
+
expect(localStorage.getItem(getComposerDraftKey())).toContain(
|
|
2412
|
+
"send this once",
|
|
2413
|
+
);
|
|
2414
|
+
expect(localStorage.getItem(getComposerDraftKey())).toContain(
|
|
2415
|
+
"follow-up prompt",
|
|
2416
|
+
);
|
|
2395
2417
|
});
|
|
2396
2418
|
|
|
2397
2419
|
it("restores the persisted draft when an immediate submit fails after unmount", async () => {
|