@agent-native/core 0.207.0-nightly-20261011124220 → 0.207.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/dist/agent/production-agent.js +23 -6
- package/dist/agent/thread-data-builder.js +7 -4
- package/dist/cli/eval.js +20 -4
- package/dist/client/analytics/index.d.ts +1 -1
- package/dist/client/analytics.d.ts +12 -3
- package/dist/client/analytics.js +51 -17
- package/dist/client/onboarding/use-onboarding.d.ts +1 -0
- package/dist/client/onboarding/use-onboarding.js +19 -6
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/eval/agent-runner.js +18 -8
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/mcp/connect-route.js +9 -4
- package/dist/observability/routes.d.ts +7 -7
- package/dist/onboarding/plugin.js +15 -45
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/store.js +25 -30
- package/dist/server/agent-discovery.js +47 -53
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
- package/dist/server/builtin-agents.d.ts +2 -0
- package/dist/server/builtin-agents.js +50 -3
- package/dist/server/credential-provider.d.ts +6 -9
- package/dist/server/credential-provider.js +33 -21
- package/dist/templates/builder-code-starter/.agents/skills/adding-a-feature/SKILL.md.patch +1 -1
- package/dist/templates/builder-code-starter/AGENTS.md.patch +3 -3
- package/dist/templates/builder-code-starter/app/components/layout/Layout.tsx.patch +6 -7
- package/dist/templates/builder-code-starter/app/hooks/use-navigation-state.ts.patch +21 -28
- package/dist/templates/builder-code-starter/app/root.tsx.patch +16 -5
- package/dist/templates/builder-code-starter/template-layer.json +4 -0
- package/dist/templates/chat/.agents/skills/secrets/SKILL.md +6 -2
- package/dist/templates/chat/AGENTS.md +3 -3
- package/dist/templates/chat/CHANGELOG.md +6 -0
- package/dist/templates/chat/app/components/chat/ChatHomeRedirect.tsx +25 -0
- package/dist/templates/chat/app/components/chat/ChatRouteContent.tsx +828 -15
- package/dist/templates/chat/app/components/home/HomePage.tsx +332 -0
- package/dist/templates/chat/app/components/layout/AgentInspector.tsx +2 -4
- package/dist/templates/chat/app/components/layout/Header.tsx +1 -1
- package/dist/templates/chat/app/components/layout/Layout.tsx +5 -6
- package/dist/templates/chat/app/components/layout/Sidebar.tsx +35 -11
- package/dist/templates/chat/app/components/ui/item.tsx +1 -0
- package/dist/templates/chat/app/components/ui/separator.tsx +1 -0
- package/dist/templates/chat/app/components/ui/skeleton.tsx +1 -0
- package/dist/templates/chat/app/hooks/use-navigation-state.ts +16 -22
- package/dist/templates/chat/app/i18n/ar-SA.ts +25 -0
- package/dist/templates/chat/app/i18n/de-DE.ts +26 -0
- package/dist/templates/chat/app/i18n/en-US.ts +25 -0
- package/dist/templates/chat/app/i18n/es-ES.ts +26 -0
- package/dist/templates/chat/app/i18n/fr-FR.ts +26 -0
- package/dist/templates/chat/app/i18n/hi-IN.ts +26 -0
- package/dist/templates/chat/app/i18n/ja-JP.ts +26 -0
- package/dist/templates/chat/app/i18n/ko-KR.ts +26 -0
- package/dist/templates/chat/app/i18n/pt-BR.ts +26 -0
- package/dist/templates/chat/app/i18n/zh-CN.ts +25 -0
- package/dist/templates/chat/app/i18n/zh-TW.ts +25 -0
- package/dist/templates/chat/app/lib/chat-paths.ts +479 -0
- package/dist/templates/chat/app/root.tsx +14 -3
- package/dist/templates/chat/app/routes/chat._index.tsx +5 -0
- package/dist/templates/chat/app/routes/home.tsx +3 -22
- package/dist/templates/chat/changelog/2026-10-10-home-handoff-recovery-capacity.md +6 -0
- package/dist/templates/default/.agents/skills/secrets/SKILL.md +6 -2
- package/dist/templates/headless/.agents/skills/secrets/SKILL.md +6 -2
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +6 -2
- package/dist/triggers/actions/manage-automation.d.ts +2 -2
- package/docs/content/deployment-environment-variables.mdx +10 -10
- package/docs/content/environment-variables.mdx +38 -6
- package/docs/content/getting-started.mdx +7 -7
- package/docs/content/multi-app-workspace.mdx +7 -3
- package/docs/content/template-chat-first-edits.mdx +46 -33
- package/package.json +4 -4
- package/src/templates/builder-code-starter/.agents/skills/adding-a-feature/SKILL.md.patch +1 -1
- package/src/templates/builder-code-starter/AGENTS.md.patch +3 -3
- package/src/templates/builder-code-starter/app/components/layout/Layout.tsx.patch +6 -7
- package/src/templates/builder-code-starter/app/hooks/use-navigation-state.ts.patch +21 -28
- package/src/templates/builder-code-starter/app/root.tsx.patch +16 -5
- package/src/templates/builder-code-starter/template-layer.json +4 -0
- package/src/templates/chat/.agents/skills/secrets/SKILL.md +6 -2
- package/src/templates/chat/AGENTS.md +3 -3
- package/src/templates/chat/CHANGELOG.md +6 -0
- package/src/templates/chat/app/components/chat/ChatHomeRedirect.tsx +25 -0
- package/src/templates/chat/app/components/chat/ChatRouteContent.tsx +828 -15
- package/src/templates/chat/app/components/home/HomePage.tsx +332 -0
- package/src/templates/chat/app/components/layout/AgentInspector.tsx +2 -4
- package/src/templates/chat/app/components/layout/Header.tsx +1 -1
- package/src/templates/chat/app/components/layout/Layout.tsx +5 -6
- package/src/templates/chat/app/components/layout/Sidebar.tsx +35 -11
- package/src/templates/chat/app/components/ui/item.tsx +1 -0
- package/src/templates/chat/app/components/ui/separator.tsx +1 -0
- package/src/templates/chat/app/components/ui/skeleton.tsx +1 -0
- package/src/templates/chat/app/hooks/use-navigation-state.ts +16 -22
- package/src/templates/chat/app/i18n/ar-SA.ts +25 -0
- package/src/templates/chat/app/i18n/de-DE.ts +26 -0
- package/src/templates/chat/app/i18n/en-US.ts +25 -0
- package/src/templates/chat/app/i18n/es-ES.ts +26 -0
- package/src/templates/chat/app/i18n/fr-FR.ts +26 -0
- package/src/templates/chat/app/i18n/hi-IN.ts +26 -0
- package/src/templates/chat/app/i18n/ja-JP.ts +26 -0
- package/src/templates/chat/app/i18n/ko-KR.ts +26 -0
- package/src/templates/chat/app/i18n/pt-BR.ts +26 -0
- package/src/templates/chat/app/i18n/zh-CN.ts +25 -0
- package/src/templates/chat/app/i18n/zh-TW.ts +25 -0
- package/src/templates/chat/app/lib/chat-paths.ts +479 -0
- package/src/templates/chat/app/root.tsx +14 -3
- package/src/templates/chat/app/routes/chat._index.tsx +5 -0
- package/src/templates/chat/app/routes/home.tsx +3 -22
- package/src/templates/chat/changelog/2026-10-10-home-handoff-recovery-capacity.md +6 -0
- package/src/templates/default/.agents/skills/secrets/SKILL.md +6 -2
- package/src/templates/headless/.agents/skills/secrets/SKILL.md +6 -2
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +6 -2
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AgentConnectionRequest,
|
|
3
3
|
AgentMessage,
|
|
4
|
+
AgentRunOptions,
|
|
5
|
+
FilePart,
|
|
4
6
|
} from "@agent-native/agentkit";
|
|
7
|
+
import { appendAgentChatContextToMessage } from "@agent-native/agentkit";
|
|
8
|
+
import { hasActiveAgentRuns } from "@agent-native/agentkit/client";
|
|
5
9
|
import { createAgentKitIntegrityReporter } from "@agent-native/core/client/agentkit-chat/integrity";
|
|
6
10
|
import { createAgentNativeAgentKitTransport } from "@agent-native/core/client/agentkit-chat/transport";
|
|
7
11
|
import {
|
|
@@ -41,15 +45,21 @@ import {
|
|
|
41
45
|
BuilderSetupCard,
|
|
42
46
|
isMissingLlmProviderRunError,
|
|
43
47
|
} from "@agent-native/toolkit/app/chat/chat/run-recovery";
|
|
44
|
-
import {
|
|
48
|
+
import type {
|
|
49
|
+
PromptComposerFile,
|
|
50
|
+
PromptComposerSubmitOptions,
|
|
51
|
+
Reference,
|
|
52
|
+
} from "@agent-native/toolkit/composer";
|
|
53
|
+
import { IconLayoutSidebarRight, IconX } from "@tabler/icons-react";
|
|
45
54
|
import {
|
|
46
55
|
useCallback,
|
|
47
56
|
useEffect,
|
|
57
|
+
useMemo,
|
|
48
58
|
useRef,
|
|
49
59
|
useState,
|
|
50
60
|
type ReactNode,
|
|
51
61
|
} from "react";
|
|
52
|
-
import { useNavigate, useParams } from "react-router";
|
|
62
|
+
import { useLocation, useNavigate, useParams } from "react-router";
|
|
53
63
|
import { toast } from "sonner";
|
|
54
64
|
|
|
55
65
|
import { Button } from "@/components/ui/button";
|
|
@@ -60,16 +70,31 @@ import {
|
|
|
60
70
|
} from "@/components/ui/tooltip";
|
|
61
71
|
import { APP_TITLE } from "@/lib/app-config";
|
|
62
72
|
import { consumeChatHomeThreadId } from "@/lib/chat-home-thread";
|
|
73
|
+
import {
|
|
74
|
+
clearFailedChatHandoff,
|
|
75
|
+
chatThreadPath,
|
|
76
|
+
readFailedChatHandoff,
|
|
77
|
+
type ChatInitialComposerOptions,
|
|
78
|
+
type FailedChatHandoffClearResult,
|
|
79
|
+
type FailedChatHandoffEnvelope,
|
|
80
|
+
type FailedChatHandoffReadResult,
|
|
81
|
+
type FailedChatHandoffWriteResult,
|
|
82
|
+
initialComposerOptionsFromState,
|
|
83
|
+
initialMessageFromState,
|
|
84
|
+
writeFailedChatHandoff,
|
|
85
|
+
} from "@/lib/chat-paths";
|
|
63
86
|
import { TAB_ID } from "@/lib/tab-id";
|
|
64
87
|
|
|
65
|
-
function chatThreadPath(threadId: string | null) {
|
|
66
|
-
return threadId ? `/chat/${encodeURIComponent(threadId)}` : "/home";
|
|
67
|
-
}
|
|
68
|
-
|
|
69
88
|
// Module scope on purpose: CoreAgentKitRoot memoizes the client on its options, so
|
|
70
89
|
// a new callback each render would rebuild the client and drop the stream.
|
|
71
90
|
const reportStreamIntegrity = createAgentKitIntegrityReporter("chat");
|
|
72
91
|
|
|
92
|
+
type FailedInitialDraft = FailedChatHandoffEnvelope & {
|
|
93
|
+
attempt: number;
|
|
94
|
+
threadId: string;
|
|
95
|
+
unsaved: boolean;
|
|
96
|
+
};
|
|
97
|
+
|
|
73
98
|
export default function ChatRouteContent({
|
|
74
99
|
initialThreadId,
|
|
75
100
|
}: {
|
|
@@ -101,6 +126,186 @@ function ChatThreadRouteContent({
|
|
|
101
126
|
}) {
|
|
102
127
|
const t = useT();
|
|
103
128
|
const [workspaceOpen, setWorkspaceOpen] = useState(false);
|
|
129
|
+
const [handoffStorageRevision, setHandoffStorageRevision] = useState(0);
|
|
130
|
+
const failedHandoffStorageFailureReportedRef = useRef(new Set<string>());
|
|
131
|
+
const reportHandoffStorageFailure = useCallback(
|
|
132
|
+
(
|
|
133
|
+
result:
|
|
134
|
+
| FailedChatHandoffReadResult
|
|
135
|
+
| FailedChatHandoffWriteResult
|
|
136
|
+
| FailedChatHandoffClearResult,
|
|
137
|
+
message: string,
|
|
138
|
+
) => {
|
|
139
|
+
if (result.status !== "invalid" && result.status !== "unavailable") {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (
|
|
143
|
+
failedHandoffStorageFailureReportedRef.current.has(resolvedThreadId)
|
|
144
|
+
) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
failedHandoffStorageFailureReportedRef.current.add(resolvedThreadId);
|
|
148
|
+
captureException(
|
|
149
|
+
result.status === "unavailable"
|
|
150
|
+
? result.cause
|
|
151
|
+
: new Error(`${message}: ${result.reason}`),
|
|
152
|
+
{ tags: { area: "chat_handoff_storage" } },
|
|
153
|
+
);
|
|
154
|
+
},
|
|
155
|
+
[resolvedThreadId],
|
|
156
|
+
);
|
|
157
|
+
const restoredHandoffResult = useMemo(
|
|
158
|
+
() => readFailedChatHandoff(resolvedThreadId),
|
|
159
|
+
[handoffStorageRevision, resolvedThreadId],
|
|
160
|
+
);
|
|
161
|
+
const restoredHandoff =
|
|
162
|
+
restoredHandoffResult.status === "found"
|
|
163
|
+
? restoredHandoffResult.handoff
|
|
164
|
+
: null;
|
|
165
|
+
const [failedInitialDrafts, setFailedInitialDrafts] = useState<
|
|
166
|
+
Map<string, FailedInitialDraft>
|
|
167
|
+
>(() => {
|
|
168
|
+
return restoredHandoff
|
|
169
|
+
? new Map([
|
|
170
|
+
[
|
|
171
|
+
resolvedThreadId,
|
|
172
|
+
{
|
|
173
|
+
text: restoredHandoff.text,
|
|
174
|
+
options: restoredHandoff.options,
|
|
175
|
+
attempt: 0,
|
|
176
|
+
threadId: resolvedThreadId,
|
|
177
|
+
unsaved: false,
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
])
|
|
181
|
+
: new Map();
|
|
182
|
+
});
|
|
183
|
+
const failedInitialDraftsRef = useRef(failedInitialDrafts);
|
|
184
|
+
const restoredForRoute = restoredHandoff;
|
|
185
|
+
const failedDraftForRoute =
|
|
186
|
+
failedInitialDrafts.get(resolvedThreadId) ??
|
|
187
|
+
(restoredForRoute
|
|
188
|
+
? {
|
|
189
|
+
text: restoredForRoute.text,
|
|
190
|
+
options: restoredForRoute.options,
|
|
191
|
+
attempt: 0,
|
|
192
|
+
threadId: resolvedThreadId,
|
|
193
|
+
unsaved: false,
|
|
194
|
+
}
|
|
195
|
+
: null);
|
|
196
|
+
const failedInitialDraftRef = useRef(failedDraftForRoute);
|
|
197
|
+
failedInitialDraftRef.current = failedDraftForRoute;
|
|
198
|
+
const failedInitialDraftAttemptRef = useRef(0);
|
|
199
|
+
const setFailedInitialDraftForThread = useCallback(
|
|
200
|
+
(threadId: string, draft: FailedInitialDraft | null) => {
|
|
201
|
+
const next = new Map(failedInitialDraftsRef.current);
|
|
202
|
+
if (draft) next.set(threadId, draft);
|
|
203
|
+
else next.delete(threadId);
|
|
204
|
+
failedInitialDraftsRef.current = next;
|
|
205
|
+
setFailedInitialDrafts(next);
|
|
206
|
+
},
|
|
207
|
+
[],
|
|
208
|
+
);
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
reportHandoffStorageFailure(
|
|
211
|
+
restoredHandoffResult,
|
|
212
|
+
"Could not restore the failed Chat handoff",
|
|
213
|
+
);
|
|
214
|
+
if (failedInitialDraftsRef.current.has(resolvedThreadId)) return;
|
|
215
|
+
if (!restoredHandoff) return;
|
|
216
|
+
setFailedInitialDraftForThread(resolvedThreadId, {
|
|
217
|
+
text: restoredHandoff.text,
|
|
218
|
+
options: restoredHandoff.options,
|
|
219
|
+
attempt: 0,
|
|
220
|
+
threadId: resolvedThreadId,
|
|
221
|
+
unsaved: false,
|
|
222
|
+
});
|
|
223
|
+
}, [
|
|
224
|
+
reportHandoffStorageFailure,
|
|
225
|
+
resolvedThreadId,
|
|
226
|
+
restoredHandoff,
|
|
227
|
+
restoredHandoffResult,
|
|
228
|
+
setFailedInitialDraftForThread,
|
|
229
|
+
]);
|
|
230
|
+
const handleInitialMessageFailure = useCallback(
|
|
231
|
+
(
|
|
232
|
+
text: string,
|
|
233
|
+
options: ChatInitialComposerOptions,
|
|
234
|
+
persistence = writeFailedChatHandoff(resolvedThreadId, text, options),
|
|
235
|
+
) => {
|
|
236
|
+
failedInitialDraftAttemptRef.current += 1;
|
|
237
|
+
reportHandoffStorageFailure(
|
|
238
|
+
persistence,
|
|
239
|
+
"Could not save the failed Chat handoff",
|
|
240
|
+
);
|
|
241
|
+
const failedDraft = {
|
|
242
|
+
text,
|
|
243
|
+
options,
|
|
244
|
+
attempt: failedInitialDraftAttemptRef.current,
|
|
245
|
+
threadId: resolvedThreadId,
|
|
246
|
+
unsaved: persistence.status !== "stored",
|
|
247
|
+
};
|
|
248
|
+
failedInitialDraftRef.current = failedDraft;
|
|
249
|
+
setFailedInitialDraftForThread(resolvedThreadId, failedDraft);
|
|
250
|
+
},
|
|
251
|
+
[
|
|
252
|
+
reportHandoffStorageFailure,
|
|
253
|
+
resolvedThreadId,
|
|
254
|
+
setFailedInitialDraftForThread,
|
|
255
|
+
],
|
|
256
|
+
);
|
|
257
|
+
const handleInitialMessageAccepted = useCallback(() => {
|
|
258
|
+
reportHandoffStorageFailure(
|
|
259
|
+
clearFailedChatHandoff(resolvedThreadId),
|
|
260
|
+
"Could not clear the accepted Chat handoff",
|
|
261
|
+
);
|
|
262
|
+
if (failedInitialDraftsRef.current.has(resolvedThreadId)) {
|
|
263
|
+
if (failedInitialDraftRef.current?.threadId === resolvedThreadId) {
|
|
264
|
+
failedInitialDraftRef.current = null;
|
|
265
|
+
}
|
|
266
|
+
setFailedInitialDraftForThread(resolvedThreadId, null);
|
|
267
|
+
}
|
|
268
|
+
setHandoffStorageRevision((revision) => revision + 1);
|
|
269
|
+
}, [
|
|
270
|
+
reportHandoffStorageFailure,
|
|
271
|
+
resolvedThreadId,
|
|
272
|
+
setFailedInitialDraftForThread,
|
|
273
|
+
]);
|
|
274
|
+
const updateFailedInitialDraft = useCallback(
|
|
275
|
+
(
|
|
276
|
+
update: Partial<FailedChatHandoffEnvelope> & {
|
|
277
|
+
options?: Partial<ChatInitialComposerOptions>;
|
|
278
|
+
},
|
|
279
|
+
) => {
|
|
280
|
+
const current = failedInitialDraftRef.current;
|
|
281
|
+
if (current?.threadId !== resolvedThreadId) return;
|
|
282
|
+
const next = {
|
|
283
|
+
...current,
|
|
284
|
+
...update,
|
|
285
|
+
options: { ...current.options, ...update.options },
|
|
286
|
+
};
|
|
287
|
+
const persistence = writeFailedChatHandoff(
|
|
288
|
+
next.threadId,
|
|
289
|
+
next.text,
|
|
290
|
+
next.options,
|
|
291
|
+
);
|
|
292
|
+
const nextDraft = {
|
|
293
|
+
...next,
|
|
294
|
+
unsaved: persistence.status !== "stored",
|
|
295
|
+
};
|
|
296
|
+
failedInitialDraftRef.current = nextDraft;
|
|
297
|
+
reportHandoffStorageFailure(
|
|
298
|
+
persistence,
|
|
299
|
+
"Could not save the failed Chat handoff",
|
|
300
|
+
);
|
|
301
|
+
setFailedInitialDraftForThread(resolvedThreadId, nextDraft);
|
|
302
|
+
},
|
|
303
|
+
[
|
|
304
|
+
reportHandoffStorageFailure,
|
|
305
|
+
resolvedThreadId,
|
|
306
|
+
setFailedInitialDraftForThread,
|
|
307
|
+
],
|
|
308
|
+
);
|
|
104
309
|
|
|
105
310
|
const [transport] = useState(() =>
|
|
106
311
|
createAgentNativeAgentKitTransport({
|
|
@@ -148,10 +353,33 @@ function ChatThreadRouteContent({
|
|
|
148
353
|
onThreadForked={(thread) => navigate(chatThreadPath(thread.id))}
|
|
149
354
|
>
|
|
150
355
|
<ChatLifecycleTracking threadId={resolvedThreadId} />
|
|
356
|
+
<ChatInitialMessage
|
|
357
|
+
threadId={resolvedThreadId}
|
|
358
|
+
hasRestoredDraft={failedDraftForRoute !== null}
|
|
359
|
+
onFailure={handleInitialMessageFailure}
|
|
360
|
+
onAccepted={handleInitialMessageAccepted}
|
|
361
|
+
/>
|
|
151
362
|
<ChatMcpConnectionResume />
|
|
152
363
|
<ChatCanvas
|
|
153
364
|
workspaceOpen={workspaceOpen}
|
|
154
365
|
setWorkspaceOpen={setWorkspaceOpen}
|
|
366
|
+
initialText={
|
|
367
|
+
failedDraftForRoute ? failedDraftForRoute.text : undefined
|
|
368
|
+
}
|
|
369
|
+
initialTextKey={
|
|
370
|
+
failedDraftForRoute
|
|
371
|
+
? `${resolvedThreadId}:${failedDraftForRoute.attempt}`
|
|
372
|
+
: undefined
|
|
373
|
+
}
|
|
374
|
+
recoveryOptions={
|
|
375
|
+
failedDraftForRoute ? failedDraftForRoute.options : undefined
|
|
376
|
+
}
|
|
377
|
+
recoveryDraftUnsaved={failedDraftForRoute?.unsaved ?? false}
|
|
378
|
+
onRecoveryOptionsChange={(options) =>
|
|
379
|
+
updateFailedInitialDraft({ options })
|
|
380
|
+
}
|
|
381
|
+
onRecoveryDraftChange={updateFailedInitialDraft}
|
|
382
|
+
onRecoverySubmitAccepted={handleInitialMessageAccepted}
|
|
155
383
|
/>
|
|
156
384
|
</CoreAgentKitRoot>
|
|
157
385
|
</CoreComposerRuntimeProvider>
|
|
@@ -193,6 +421,74 @@ type ChatRetryError = {
|
|
|
193
421
|
runId: string;
|
|
194
422
|
};
|
|
195
423
|
|
|
424
|
+
function RecoveryIncludedSources({
|
|
425
|
+
references,
|
|
426
|
+
attachments,
|
|
427
|
+
onRemoveReference,
|
|
428
|
+
onRemoveAttachment,
|
|
429
|
+
}: {
|
|
430
|
+
references: readonly Reference[];
|
|
431
|
+
attachments: readonly FilePart[];
|
|
432
|
+
onRemoveReference: (index: number) => void;
|
|
433
|
+
onRemoveAttachment: (index: number) => void;
|
|
434
|
+
}) {
|
|
435
|
+
const t = useT();
|
|
436
|
+
if (references.length === 0 && attachments.length === 0) return null;
|
|
437
|
+
return (
|
|
438
|
+
<div
|
|
439
|
+
className="flex max-w-full flex-wrap gap-1.5"
|
|
440
|
+
data-testid="recovery-included-sources"
|
|
441
|
+
>
|
|
442
|
+
{references.map((reference, index) => (
|
|
443
|
+
<span
|
|
444
|
+
key={`reference:${index}:${reference.path}`}
|
|
445
|
+
className="inline-flex min-w-0 items-center gap-1 rounded-md border border-border bg-muted/50 px-2 py-1 text-xs"
|
|
446
|
+
data-recovery-source="reference"
|
|
447
|
+
>
|
|
448
|
+
<span className="max-w-48 truncate">
|
|
449
|
+
{reference.name || reference.path}
|
|
450
|
+
</span>
|
|
451
|
+
<Button
|
|
452
|
+
type="button"
|
|
453
|
+
variant="ghost"
|
|
454
|
+
size="icon-xs"
|
|
455
|
+
className="-me-1"
|
|
456
|
+
aria-label={t("composer.removeReference", {
|
|
457
|
+
name: reference.name || reference.path,
|
|
458
|
+
})}
|
|
459
|
+
data-testid={`remove-recovery-reference-${index}`}
|
|
460
|
+
onClick={() => onRemoveReference(index)}
|
|
461
|
+
>
|
|
462
|
+
<IconX aria-hidden="true" className="size-3" />
|
|
463
|
+
</Button>
|
|
464
|
+
</span>
|
|
465
|
+
))}
|
|
466
|
+
{attachments.map((attachment, index) => (
|
|
467
|
+
<span
|
|
468
|
+
key={`attachment:${index}:${attachment.name}`}
|
|
469
|
+
className="inline-flex min-w-0 items-center gap-1 rounded-md border border-border bg-muted/50 px-2 py-1 text-xs"
|
|
470
|
+
data-recovery-source="attachment"
|
|
471
|
+
>
|
|
472
|
+
<span className="max-w-48 truncate">{attachment.name}</span>
|
|
473
|
+
<Button
|
|
474
|
+
type="button"
|
|
475
|
+
variant="ghost"
|
|
476
|
+
size="icon-xs"
|
|
477
|
+
className="-me-1"
|
|
478
|
+
aria-label={t("composer.removeAttachment", {
|
|
479
|
+
name: attachment.name,
|
|
480
|
+
})}
|
|
481
|
+
data-testid={`remove-recovery-attachment-${index}`}
|
|
482
|
+
onClick={() => onRemoveAttachment(index)}
|
|
483
|
+
>
|
|
484
|
+
<IconX aria-hidden="true" className="size-3" />
|
|
485
|
+
</Button>
|
|
486
|
+
</span>
|
|
487
|
+
))}
|
|
488
|
+
</div>
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
196
492
|
function ChatRunFailure({
|
|
197
493
|
error,
|
|
198
494
|
runId,
|
|
@@ -210,6 +506,9 @@ function ChatRunFailure({
|
|
|
210
506
|
custom?: {
|
|
211
507
|
agentNativeRecoveryAction?: unknown;
|
|
212
508
|
agentNativeRecoveryOfRunId?: unknown;
|
|
509
|
+
agentNativeQueueWhileRunning?: unknown;
|
|
510
|
+
agentNativeQueuedWhileRunActive?: unknown;
|
|
511
|
+
agentNativeInterruptActiveRun?: unknown;
|
|
213
512
|
};
|
|
214
513
|
}
|
|
215
514
|
| undefined
|
|
@@ -220,6 +519,96 @@ function ChatRunFailure({
|
|
|
220
519
|
return action !== "continue" && action !== "retry";
|
|
221
520
|
});
|
|
222
521
|
const originalRequest = userRequests[0];
|
|
522
|
+
const originalMetadata =
|
|
523
|
+
originalRequest?.metadata &&
|
|
524
|
+
typeof originalRequest.metadata === "object" &&
|
|
525
|
+
!Array.isArray(originalRequest.metadata)
|
|
526
|
+
? (originalRequest.metadata as Record<string, unknown>)
|
|
527
|
+
: {};
|
|
528
|
+
const originalMode =
|
|
529
|
+
originalMetadata.mode === "act" || originalMetadata.mode === "plan"
|
|
530
|
+
? originalMetadata.mode
|
|
531
|
+
: originalMetadata.requestMode === "act" ||
|
|
532
|
+
originalMetadata.requestMode === "plan"
|
|
533
|
+
? originalMetadata.requestMode
|
|
534
|
+
: undefined;
|
|
535
|
+
const originalComposerOptions = initialComposerOptionsFromState({
|
|
536
|
+
initialComposerOptions: {
|
|
537
|
+
...(typeof originalMetadata.engine === "string"
|
|
538
|
+
? { engine: originalMetadata.engine }
|
|
539
|
+
: {}),
|
|
540
|
+
...(typeof originalMetadata.model === "string"
|
|
541
|
+
? { model: originalMetadata.model }
|
|
542
|
+
: {}),
|
|
543
|
+
...(typeof originalMetadata.effort === "string"
|
|
544
|
+
? { effort: originalMetadata.effort }
|
|
545
|
+
: {}),
|
|
546
|
+
...(originalMode ? { mode: originalMode } : {}),
|
|
547
|
+
...(Array.isArray(originalMetadata.references)
|
|
548
|
+
? { references: originalMetadata.references }
|
|
549
|
+
: {}),
|
|
550
|
+
...(Array.isArray(originalMetadata.contextItems)
|
|
551
|
+
? { contextItems: originalMetadata.contextItems }
|
|
552
|
+
: {}),
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
const originalComposerSettings: ChatInitialComposerOptions =
|
|
556
|
+
originalComposerOptions.status === "valid"
|
|
557
|
+
? originalComposerOptions.options
|
|
558
|
+
: { mode: "act" };
|
|
559
|
+
const originalCustomMetadata = originalRequest
|
|
560
|
+
? recoveryMetadata(originalRequest)
|
|
561
|
+
: undefined;
|
|
562
|
+
const queueWhileRunning =
|
|
563
|
+
typeof originalCustomMetadata?.agentNativeQueueWhileRunning === "boolean"
|
|
564
|
+
? originalCustomMetadata.agentNativeQueueWhileRunning
|
|
565
|
+
: originalComposerSettings.intent !== "immediate";
|
|
566
|
+
const queuedWhileRunActive =
|
|
567
|
+
typeof originalCustomMetadata?.agentNativeQueuedWhileRunActive === "boolean"
|
|
568
|
+
? originalCustomMetadata.agentNativeQueuedWhileRunActive
|
|
569
|
+
: originalComposerSettings.intent === "queued";
|
|
570
|
+
const interruptActiveRun =
|
|
571
|
+
typeof originalCustomMetadata?.agentNativeInterruptActiveRun === "boolean"
|
|
572
|
+
? originalCustomMetadata.agentNativeInterruptActiveRun
|
|
573
|
+
: originalComposerSettings.steer === true;
|
|
574
|
+
const originalReferences = originalComposerSettings.references ?? [];
|
|
575
|
+
const hasOriginalExecutionSettings = Boolean(
|
|
576
|
+
originalMode ||
|
|
577
|
+
originalComposerSettings.engine ||
|
|
578
|
+
originalComposerSettings.model ||
|
|
579
|
+
originalComposerSettings.effort,
|
|
580
|
+
);
|
|
581
|
+
const originalAttachments =
|
|
582
|
+
originalRequest?.parts?.filter((part) => part.type === "file") ?? [];
|
|
583
|
+
const [retryItems, setRetryItems] = useState<{
|
|
584
|
+
runId: string;
|
|
585
|
+
references: Reference[];
|
|
586
|
+
attachments: FilePart[];
|
|
587
|
+
} | null>(null);
|
|
588
|
+
const visibleRetryItems =
|
|
589
|
+
retryItems?.runId === runId
|
|
590
|
+
? retryItems
|
|
591
|
+
: {
|
|
592
|
+
runId,
|
|
593
|
+
references: originalReferences,
|
|
594
|
+
attachments: originalAttachments,
|
|
595
|
+
};
|
|
596
|
+
const updateRetryItems = (update: {
|
|
597
|
+
references?: Reference[];
|
|
598
|
+
attachments?: FilePart[];
|
|
599
|
+
}) => {
|
|
600
|
+
setRetryError(null);
|
|
601
|
+
setRetryItems((current) => ({
|
|
602
|
+
...(current?.runId === runId
|
|
603
|
+
? current
|
|
604
|
+
: {
|
|
605
|
+
runId,
|
|
606
|
+
references: [...originalReferences],
|
|
607
|
+
attachments: originalAttachments,
|
|
608
|
+
}),
|
|
609
|
+
...update,
|
|
610
|
+
}));
|
|
611
|
+
};
|
|
223
612
|
const hasRetryForThisRun = thread.messages.some(
|
|
224
613
|
(message) =>
|
|
225
614
|
recoveryMetadata(message)?.agentNativeRecoveryAction === "retry" &&
|
|
@@ -227,8 +616,7 @@ function ChatRunFailure({
|
|
|
227
616
|
);
|
|
228
617
|
const retryFirstMessage = useCallback(async () => {
|
|
229
618
|
if (retryStartedForRunsRef.current.has(runId)) return;
|
|
230
|
-
const attachments =
|
|
231
|
-
originalRequest?.parts.filter((part) => part.type === "file") ?? [];
|
|
619
|
+
const attachments = visibleRetryItems.attachments;
|
|
232
620
|
if (attachments.some((part) => part.fileId && !part.url)) {
|
|
233
621
|
setRetryError({ code: "attachment_id_unavailable", runId });
|
|
234
622
|
return;
|
|
@@ -240,18 +628,59 @@ function ChatRunFailure({
|
|
|
240
628
|
.filter((part) => part.type === "text")
|
|
241
629
|
.map((part) => part.text)
|
|
242
630
|
.join("\n") ?? "";
|
|
631
|
+
const retryMetadata = {
|
|
632
|
+
...(originalComposerSettings.engine
|
|
633
|
+
? { engine: originalComposerSettings.engine }
|
|
634
|
+
: {}),
|
|
635
|
+
...(originalComposerSettings.model
|
|
636
|
+
? { model: originalComposerSettings.model }
|
|
637
|
+
: {}),
|
|
638
|
+
...(originalComposerSettings.effort
|
|
639
|
+
? { effort: originalComposerSettings.effort }
|
|
640
|
+
: {}),
|
|
641
|
+
...(visibleRetryItems.references.length
|
|
642
|
+
? { references: visibleRetryItems.references }
|
|
643
|
+
: {}),
|
|
644
|
+
...(originalComposerSettings.contextItems === undefined
|
|
645
|
+
? {}
|
|
646
|
+
: { contextItems: originalComposerSettings.contextItems }),
|
|
647
|
+
...(originalMode
|
|
648
|
+
? { mode: originalMode, requestMode: originalMode }
|
|
649
|
+
: {}),
|
|
650
|
+
custom: {
|
|
651
|
+
agentNativeRecoveryAction: "retry",
|
|
652
|
+
agentNativeRecoveryOfRunId: runId,
|
|
653
|
+
agentNativeQueueWhileRunning: queueWhileRunning,
|
|
654
|
+
agentNativeQueuedWhileRunActive: queuedWhileRunActive,
|
|
655
|
+
agentNativeInterruptActiveRun: interruptActiveRun,
|
|
656
|
+
},
|
|
657
|
+
};
|
|
658
|
+
const retryRunOptions: AgentRunOptions = {
|
|
659
|
+
...(originalComposerSettings.model
|
|
660
|
+
? { model: originalComposerSettings.model }
|
|
661
|
+
: {}),
|
|
662
|
+
...(originalMode ? { mode: originalMode } : {}),
|
|
663
|
+
...(originalComposerSettings.effort &&
|
|
664
|
+
!["auto", "max"].includes(originalComposerSettings.effort)
|
|
665
|
+
? {
|
|
666
|
+
reasoningEffort: originalComposerSettings.effort as NonNullable<
|
|
667
|
+
AgentRunOptions["reasoningEffort"]
|
|
668
|
+
>,
|
|
669
|
+
}
|
|
670
|
+
: {}),
|
|
671
|
+
metadata: retryMetadata,
|
|
672
|
+
};
|
|
243
673
|
let send: unknown;
|
|
244
674
|
try {
|
|
245
675
|
send = controller.sendMessage({
|
|
246
676
|
threadId,
|
|
247
677
|
text: prompt || t("chat.retryPreviousRequest"),
|
|
248
678
|
...(attachments.length ? { attachments } : {}),
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
},
|
|
679
|
+
...(hasOriginalExecutionSettings ? { options: retryRunOptions } : {}),
|
|
680
|
+
metadata: retryMetadata,
|
|
681
|
+
queueWhileRunning,
|
|
682
|
+
queuedWhileRunActive,
|
|
683
|
+
...(interruptActiveRun ? { interruptActiveRun: true } : {}),
|
|
255
684
|
});
|
|
256
685
|
} catch (error) {
|
|
257
686
|
retryStartedForRunsRef.current.delete(runId);
|
|
@@ -262,7 +691,19 @@ function ChatRunFailure({
|
|
|
262
691
|
retryStartedForRunsRef.current.delete(runId);
|
|
263
692
|
captureException(error, { tags: { area: "chat_retry" } });
|
|
264
693
|
});
|
|
265
|
-
}, [
|
|
694
|
+
}, [
|
|
695
|
+
controller,
|
|
696
|
+
originalComposerSettings,
|
|
697
|
+
queueWhileRunning,
|
|
698
|
+
queuedWhileRunActive,
|
|
699
|
+
interruptActiveRun,
|
|
700
|
+
originalMode,
|
|
701
|
+
originalRequest,
|
|
702
|
+
runId,
|
|
703
|
+
t,
|
|
704
|
+
threadId,
|
|
705
|
+
visibleRetryItems,
|
|
706
|
+
]);
|
|
266
707
|
const isFirstMessage = userRequests.length === 1 && !hasRetryForThisRun;
|
|
267
708
|
if (
|
|
268
709
|
isFirstMessage &&
|
|
@@ -279,6 +720,24 @@ function ChatRunFailure({
|
|
|
279
720
|
layout="sidebar"
|
|
280
721
|
onRetry={retryFirstMessage}
|
|
281
722
|
/>
|
|
723
|
+
<RecoveryIncludedSources
|
|
724
|
+
references={visibleRetryItems.references}
|
|
725
|
+
attachments={visibleRetryItems.attachments}
|
|
726
|
+
onRemoveReference={(index) =>
|
|
727
|
+
updateRetryItems({
|
|
728
|
+
references: visibleRetryItems.references.filter(
|
|
729
|
+
(_reference, candidate) => candidate !== index,
|
|
730
|
+
),
|
|
731
|
+
})
|
|
732
|
+
}
|
|
733
|
+
onRemoveAttachment={(index) =>
|
|
734
|
+
updateRetryItems({
|
|
735
|
+
attachments: visibleRetryItems.attachments.filter(
|
|
736
|
+
(_attachment, candidate) => candidate !== index,
|
|
737
|
+
),
|
|
738
|
+
})
|
|
739
|
+
}
|
|
740
|
+
/>
|
|
282
741
|
{retryError?.runId === runId &&
|
|
283
742
|
retryError.code === "attachment_id_unavailable" ? (
|
|
284
743
|
<p role="alert" className="mt-2 px-3 text-sm text-destructive">
|
|
@@ -291,6 +750,159 @@ function ChatRunFailure({
|
|
|
291
750
|
return <AgentRunFailure error={error} runId={runId} threadId={threadId} />;
|
|
292
751
|
}
|
|
293
752
|
|
|
753
|
+
function ChatInitialMessage({
|
|
754
|
+
threadId,
|
|
755
|
+
hasRestoredDraft,
|
|
756
|
+
onFailure,
|
|
757
|
+
onAccepted,
|
|
758
|
+
}: {
|
|
759
|
+
threadId: string;
|
|
760
|
+
hasRestoredDraft: boolean;
|
|
761
|
+
onFailure: (
|
|
762
|
+
text: string,
|
|
763
|
+
options: ChatInitialComposerOptions,
|
|
764
|
+
persistence?: FailedChatHandoffWriteResult,
|
|
765
|
+
) => void;
|
|
766
|
+
onAccepted: () => void;
|
|
767
|
+
}) {
|
|
768
|
+
const location = useLocation();
|
|
769
|
+
const navigate = useNavigate();
|
|
770
|
+
const t = useT();
|
|
771
|
+
const control = useAgentKitControl(threadId);
|
|
772
|
+
const thread = useAgentThread(threadId);
|
|
773
|
+
const sentRef = useRef(false);
|
|
774
|
+
const message = initialMessageFromState(location.state);
|
|
775
|
+
const composerOptionsResult = initialComposerOptionsFromState(location.state);
|
|
776
|
+
const invalidComposerOptions = composerOptionsResult.status === "invalid";
|
|
777
|
+
const composerOptions =
|
|
778
|
+
composerOptionsResult.status === "valid"
|
|
779
|
+
? composerOptionsResult.options
|
|
780
|
+
: undefined;
|
|
781
|
+
const engine = composerOptions?.engine;
|
|
782
|
+
const model = composerOptions?.model;
|
|
783
|
+
const effort = composerOptions?.effort;
|
|
784
|
+
const mode = composerOptions?.mode ?? "act";
|
|
785
|
+
const references = composerOptions?.references ?? [];
|
|
786
|
+
const contextItems = composerOptions?.contextItems;
|
|
787
|
+
const activeAtSubmit = hasActiveAgentRuns(thread);
|
|
788
|
+
|
|
789
|
+
useEffect(() => {
|
|
790
|
+
if (!message || sentRef.current) return;
|
|
791
|
+
if (hasRestoredDraft) {
|
|
792
|
+
sentRef.current = true;
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
sentRef.current = true;
|
|
796
|
+
const clearRouteState = () =>
|
|
797
|
+
navigate(
|
|
798
|
+
{ pathname: location.pathname, search: location.search },
|
|
799
|
+
{ replace: true, state: null },
|
|
800
|
+
);
|
|
801
|
+
if (invalidComposerOptions) {
|
|
802
|
+
clearRouteState();
|
|
803
|
+
onFailure(message, {});
|
|
804
|
+
toast.error(t("chat.invalidHandoffOptions"));
|
|
805
|
+
captureException(new Error("Invalid options in initial Chat handoff"), {
|
|
806
|
+
tags: { area: "chat_initial_message" },
|
|
807
|
+
});
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const options = composerOptions ?? {};
|
|
811
|
+
const persistence = writeFailedChatHandoff(threadId, message, options);
|
|
812
|
+
if (persistence.status !== "stored") {
|
|
813
|
+
clearRouteState();
|
|
814
|
+
onFailure(message, options, persistence);
|
|
815
|
+
if (
|
|
816
|
+
persistence.status !== "invalid" ||
|
|
817
|
+
persistence.reason !== "payload-too-large"
|
|
818
|
+
) {
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
} else {
|
|
822
|
+
// Router state survives a reload, so drop it before sending or a refresh
|
|
823
|
+
// cannot distinguish a pending send from a failed one.
|
|
824
|
+
clearRouteState();
|
|
825
|
+
}
|
|
826
|
+
const context = [
|
|
827
|
+
composerOptions?.composerModeContext,
|
|
828
|
+
contextItems?.map((item) => item.context).join("\n\n"),
|
|
829
|
+
]
|
|
830
|
+
.filter(Boolean)
|
|
831
|
+
.join("\n\n");
|
|
832
|
+
const requestText = context
|
|
833
|
+
? appendAgentChatContextToMessage(message, context)
|
|
834
|
+
: message;
|
|
835
|
+
const queuedIntent =
|
|
836
|
+
composerOptions?.intent === "queued" ||
|
|
837
|
+
(composerOptions?.intent !== "immediate" && activeAtSubmit);
|
|
838
|
+
const metadata = {
|
|
839
|
+
...(engine ? { engine } : {}),
|
|
840
|
+
...(model ? { model } : {}),
|
|
841
|
+
...(effort ? { effort } : {}),
|
|
842
|
+
...(references.length ? { references } : {}),
|
|
843
|
+
...(contextItems === undefined ? {} : { contextItems }),
|
|
844
|
+
mode,
|
|
845
|
+
requestMode: mode,
|
|
846
|
+
custom: {
|
|
847
|
+
agentNativeQueueWhileRunning: composerOptions?.intent !== "immediate",
|
|
848
|
+
agentNativeQueuedWhileRunActive: queuedIntent,
|
|
849
|
+
agentNativeInterruptActiveRun: composerOptions?.steer === true,
|
|
850
|
+
},
|
|
851
|
+
};
|
|
852
|
+
const runOptions: AgentRunOptions = {
|
|
853
|
+
...(model ? { model } : {}),
|
|
854
|
+
mode,
|
|
855
|
+
...(effort && !["auto", "max"].includes(effort)
|
|
856
|
+
? {
|
|
857
|
+
reasoningEffort: effort as NonNullable<
|
|
858
|
+
AgentRunOptions["reasoningEffort"]
|
|
859
|
+
>,
|
|
860
|
+
}
|
|
861
|
+
: {}),
|
|
862
|
+
metadata,
|
|
863
|
+
};
|
|
864
|
+
void Promise.resolve(
|
|
865
|
+
control.sendMessage({
|
|
866
|
+
text: requestText,
|
|
867
|
+
attachments: [...(composerOptions?.uploadedAttachments ?? [])],
|
|
868
|
+
options: runOptions,
|
|
869
|
+
metadata,
|
|
870
|
+
queueWhileRunning: metadata.custom.agentNativeQueueWhileRunning,
|
|
871
|
+
queuedWhileRunActive: queuedIntent,
|
|
872
|
+
...(metadata.custom.agentNativeInterruptActiveRun
|
|
873
|
+
? { interruptActiveRun: true }
|
|
874
|
+
: {}),
|
|
875
|
+
}),
|
|
876
|
+
)
|
|
877
|
+
.then(onAccepted)
|
|
878
|
+
.catch((error: unknown) => {
|
|
879
|
+
onFailure(message, composerOptions ?? {});
|
|
880
|
+
captureException(error, { tags: { area: "chat_initial_message" } });
|
|
881
|
+
toast.error(error instanceof Error ? error.message : String(error));
|
|
882
|
+
});
|
|
883
|
+
}, [
|
|
884
|
+
composerOptions,
|
|
885
|
+
control,
|
|
886
|
+
contextItems,
|
|
887
|
+
activeAtSubmit,
|
|
888
|
+
hasRestoredDraft,
|
|
889
|
+
invalidComposerOptions,
|
|
890
|
+
engine,
|
|
891
|
+
effort,
|
|
892
|
+
location.pathname,
|
|
893
|
+
location.search,
|
|
894
|
+
message,
|
|
895
|
+
model,
|
|
896
|
+
navigate,
|
|
897
|
+
onAccepted,
|
|
898
|
+
onFailure,
|
|
899
|
+
t,
|
|
900
|
+
threadId,
|
|
901
|
+
]);
|
|
902
|
+
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
|
|
294
906
|
function ChatLifecycleTracking({ threadId }: { threadId: string }) {
|
|
295
907
|
const thread = useAgentThread(threadId);
|
|
296
908
|
const lifecycleRef = useRef({
|
|
@@ -520,17 +1132,160 @@ function ChatEmptyState() {
|
|
|
520
1132
|
function ChatCanvas({
|
|
521
1133
|
workspaceOpen,
|
|
522
1134
|
setWorkspaceOpen,
|
|
1135
|
+
initialText,
|
|
1136
|
+
initialTextKey,
|
|
1137
|
+
recoveryOptions,
|
|
1138
|
+
onRecoveryOptionsChange,
|
|
1139
|
+
onRecoveryDraftChange,
|
|
1140
|
+
onRecoverySubmitAccepted,
|
|
1141
|
+
recoveryDraftUnsaved,
|
|
523
1142
|
}: {
|
|
524
1143
|
workspaceOpen: boolean;
|
|
525
1144
|
setWorkspaceOpen: (value: boolean | ((current: boolean) => boolean)) => void;
|
|
1145
|
+
initialText?: string;
|
|
1146
|
+
initialTextKey?: string;
|
|
1147
|
+
recoveryOptions?: ChatInitialComposerOptions;
|
|
1148
|
+
recoveryDraftUnsaved: boolean;
|
|
1149
|
+
onRecoveryOptionsChange: (
|
|
1150
|
+
options: Partial<ChatInitialComposerOptions>,
|
|
1151
|
+
) => void;
|
|
1152
|
+
onRecoveryDraftChange: (
|
|
1153
|
+
update: Partial<FailedChatHandoffEnvelope> & {
|
|
1154
|
+
options?: Partial<ChatInitialComposerOptions>;
|
|
1155
|
+
},
|
|
1156
|
+
) => void;
|
|
1157
|
+
onRecoverySubmitAccepted: () => void;
|
|
526
1158
|
}) {
|
|
527
1159
|
const t = useT();
|
|
1160
|
+
const { threadId } = useAgentKit();
|
|
528
1161
|
const thread = useAgentThread();
|
|
1162
|
+
const control = useAgentKitControl(threadId);
|
|
529
1163
|
const stopButton = useAgentKitStopButton({
|
|
530
1164
|
label: t("agentChat.composer.stopResponse"), // i18n-key-ignore shared framework catalog
|
|
531
1165
|
onError: (error) => toast.error(error.message),
|
|
532
1166
|
});
|
|
533
1167
|
const hasConversation = thread.messages.length > 0;
|
|
1168
|
+
const activeAtSubmit = hasActiveAgentRuns(thread);
|
|
1169
|
+
|
|
1170
|
+
const retryInitialDraft = useCallback(
|
|
1171
|
+
async (
|
|
1172
|
+
text: string,
|
|
1173
|
+
files: PromptComposerFile[],
|
|
1174
|
+
references: Reference[],
|
|
1175
|
+
options: PromptComposerSubmitOptions,
|
|
1176
|
+
) => {
|
|
1177
|
+
if (!recoveryOptions) return;
|
|
1178
|
+
const contextItems = options.contextItems ?? recoveryOptions.contextItems;
|
|
1179
|
+
const composerModeContext =
|
|
1180
|
+
options.composerModeContext ?? recoveryOptions.composerModeContext;
|
|
1181
|
+
const context = [
|
|
1182
|
+
composerModeContext,
|
|
1183
|
+
contextItems?.map((item) => item.context).join("\n\n"),
|
|
1184
|
+
]
|
|
1185
|
+
.filter(Boolean)
|
|
1186
|
+
.join("\n\n");
|
|
1187
|
+
const requestText = context
|
|
1188
|
+
? appendAgentChatContextToMessage(text, context)
|
|
1189
|
+
: text;
|
|
1190
|
+
const runMode = recoveryOptions.mode ?? "act";
|
|
1191
|
+
const model = options.model ?? recoveryOptions.model;
|
|
1192
|
+
const engine = options.engine ?? recoveryOptions.engine;
|
|
1193
|
+
const effort = options.effort ?? recoveryOptions.effort;
|
|
1194
|
+
const mergedReferences = [
|
|
1195
|
+
...(recoveryOptions.references ?? []),
|
|
1196
|
+
...references,
|
|
1197
|
+
].filter(
|
|
1198
|
+
(reference, index, all) =>
|
|
1199
|
+
all.findIndex(
|
|
1200
|
+
(candidate) =>
|
|
1201
|
+
candidate.type === reference.type &&
|
|
1202
|
+
candidate.source === reference.source &&
|
|
1203
|
+
candidate.path === reference.path &&
|
|
1204
|
+
candidate.refId === reference.refId,
|
|
1205
|
+
) === index,
|
|
1206
|
+
);
|
|
1207
|
+
const retryIntent = options.intent ?? recoveryOptions.intent;
|
|
1208
|
+
const queuedIntent =
|
|
1209
|
+
retryIntent === "queued" ||
|
|
1210
|
+
(retryIntent === undefined && activeAtSubmit);
|
|
1211
|
+
const retrySteer = options.steer ?? recoveryOptions.steer;
|
|
1212
|
+
const retryOptions: ChatInitialComposerOptions = {
|
|
1213
|
+
...recoveryOptions,
|
|
1214
|
+
mode: runMode,
|
|
1215
|
+
...(model ? { model } : {}),
|
|
1216
|
+
...(engine ? { engine } : {}),
|
|
1217
|
+
...(effort ? { effort } : {}),
|
|
1218
|
+
...(retryIntent === undefined ? {} : { intent: retryIntent }),
|
|
1219
|
+
...(retrySteer === undefined ? {} : { steer: retrySteer }),
|
|
1220
|
+
...(contextItems === undefined ? {} : { contextItems }),
|
|
1221
|
+
...(composerModeContext === undefined ? {} : { composerModeContext }),
|
|
1222
|
+
references: mergedReferences,
|
|
1223
|
+
uploadedAttachments: recoveryOptions.uploadedAttachments ?? [],
|
|
1224
|
+
};
|
|
1225
|
+
onRecoveryDraftChange({ text, options: retryOptions });
|
|
1226
|
+
const uploadedAttachments = files.length
|
|
1227
|
+
? await control.uploadFiles(
|
|
1228
|
+
files.map((file) => ({
|
|
1229
|
+
name: file.name,
|
|
1230
|
+
mediaType: file.type || "application/octet-stream",
|
|
1231
|
+
size: file.size,
|
|
1232
|
+
body: file,
|
|
1233
|
+
})),
|
|
1234
|
+
)
|
|
1235
|
+
: [];
|
|
1236
|
+
const acceptedOptions = {
|
|
1237
|
+
...retryOptions,
|
|
1238
|
+
uploadedAttachments: [
|
|
1239
|
+
...(recoveryOptions.uploadedAttachments ?? []),
|
|
1240
|
+
...uploadedAttachments,
|
|
1241
|
+
],
|
|
1242
|
+
};
|
|
1243
|
+
onRecoveryDraftChange({ text, options: acceptedOptions });
|
|
1244
|
+
const metadata = {
|
|
1245
|
+
...(engine ? { engine } : {}),
|
|
1246
|
+
...(model ? { model } : {}),
|
|
1247
|
+
...(effort ? { effort } : {}),
|
|
1248
|
+
...(mergedReferences.length ? { references: mergedReferences } : {}),
|
|
1249
|
+
...(contextItems === undefined ? {} : { contextItems }),
|
|
1250
|
+
mode: runMode,
|
|
1251
|
+
requestMode: runMode,
|
|
1252
|
+
};
|
|
1253
|
+
const runOptions: AgentRunOptions = {
|
|
1254
|
+
...(model ? { model } : {}),
|
|
1255
|
+
mode: runMode,
|
|
1256
|
+
...(effort && !["auto", "max"].includes(effort)
|
|
1257
|
+
? {
|
|
1258
|
+
reasoningEffort: effort as NonNullable<
|
|
1259
|
+
AgentRunOptions["reasoningEffort"]
|
|
1260
|
+
>,
|
|
1261
|
+
}
|
|
1262
|
+
: {}),
|
|
1263
|
+
metadata,
|
|
1264
|
+
};
|
|
1265
|
+
await control.sendMessage({
|
|
1266
|
+
text: requestText,
|
|
1267
|
+
attachments: [
|
|
1268
|
+
...(recoveryOptions.uploadedAttachments ?? []),
|
|
1269
|
+
...uploadedAttachments,
|
|
1270
|
+
],
|
|
1271
|
+
options: runOptions,
|
|
1272
|
+
metadata,
|
|
1273
|
+
queueWhileRunning: retryIntent !== "immediate",
|
|
1274
|
+
queuedWhileRunActive: queuedIntent,
|
|
1275
|
+
...(retrySteer ? { interruptActiveRun: true } : {}),
|
|
1276
|
+
onLocalSubmit: options.onLocalSubmit,
|
|
1277
|
+
});
|
|
1278
|
+
onRecoverySubmitAccepted();
|
|
1279
|
+
},
|
|
1280
|
+
[
|
|
1281
|
+
activeAtSubmit,
|
|
1282
|
+
control,
|
|
1283
|
+
threadId,
|
|
1284
|
+
onRecoveryDraftChange,
|
|
1285
|
+
onRecoverySubmitAccepted,
|
|
1286
|
+
recoveryOptions,
|
|
1287
|
+
],
|
|
1288
|
+
);
|
|
534
1289
|
|
|
535
1290
|
useEffect(() => {
|
|
536
1291
|
if (!hasConversation) setWorkspaceOpen(false);
|
|
@@ -563,6 +1318,64 @@ function ChatCanvas({
|
|
|
563
1318
|
emptyComposerPlacement="center"
|
|
564
1319
|
composerProps={{
|
|
565
1320
|
requireAgentEngine: true,
|
|
1321
|
+
initialText,
|
|
1322
|
+
initialTextKey,
|
|
1323
|
+
...(recoveryOptions
|
|
1324
|
+
? {
|
|
1325
|
+
mode: recoveryOptions.mode ?? "act",
|
|
1326
|
+
selectedModel: recoveryOptions.model,
|
|
1327
|
+
selectedEngine: recoveryOptions.engine,
|
|
1328
|
+
selectedEffort: recoveryOptions.effort,
|
|
1329
|
+
contextItems: recoveryOptions.contextItems,
|
|
1330
|
+
onModeChange: (mode: "act" | "plan") =>
|
|
1331
|
+
onRecoveryOptionsChange({ mode }),
|
|
1332
|
+
onModelChange: (model: string, engine: string) =>
|
|
1333
|
+
onRecoveryOptionsChange({ model, engine }),
|
|
1334
|
+
onEffortChange: (
|
|
1335
|
+
effort: NonNullable<ChatInitialComposerOptions["effort"]>,
|
|
1336
|
+
) => onRecoveryOptionsChange({ effort }),
|
|
1337
|
+
onRemoveContextItem: (key: string) =>
|
|
1338
|
+
onRecoveryOptionsChange({
|
|
1339
|
+
contextItems: recoveryOptions.contextItems?.filter(
|
|
1340
|
+
(item) => item.key !== key,
|
|
1341
|
+
),
|
|
1342
|
+
}),
|
|
1343
|
+
onTextChange: (text: string) => onRecoveryDraftChange({ text }),
|
|
1344
|
+
toolbarSlot: (
|
|
1345
|
+
<>
|
|
1346
|
+
{recoveryDraftUnsaved ? (
|
|
1347
|
+
<p
|
|
1348
|
+
className="px-2 text-xs text-destructive"
|
|
1349
|
+
data-testid="chat-recovery-draft-unsaved"
|
|
1350
|
+
role="status"
|
|
1351
|
+
>
|
|
1352
|
+
{t("chat.recoveryDraftUnsaved")}
|
|
1353
|
+
</p>
|
|
1354
|
+
) : null}
|
|
1355
|
+
<RecoveryIncludedSources
|
|
1356
|
+
references={recoveryOptions.references ?? []}
|
|
1357
|
+
attachments={recoveryOptions.uploadedAttachments ?? []}
|
|
1358
|
+
onRemoveReference={(index) =>
|
|
1359
|
+
onRecoveryOptionsChange({
|
|
1360
|
+
references: recoveryOptions.references?.filter(
|
|
1361
|
+
(_reference, candidate) => candidate !== index,
|
|
1362
|
+
),
|
|
1363
|
+
})
|
|
1364
|
+
}
|
|
1365
|
+
onRemoveAttachment={(index) =>
|
|
1366
|
+
onRecoveryOptionsChange({
|
|
1367
|
+
uploadedAttachments:
|
|
1368
|
+
recoveryOptions.uploadedAttachments?.filter(
|
|
1369
|
+
(_attachment, candidate) => candidate !== index,
|
|
1370
|
+
),
|
|
1371
|
+
})
|
|
1372
|
+
}
|
|
1373
|
+
/>
|
|
1374
|
+
</>
|
|
1375
|
+
),
|
|
1376
|
+
onSubmit: retryInitialDraft,
|
|
1377
|
+
}
|
|
1378
|
+
: {}),
|
|
566
1379
|
stopButton,
|
|
567
1380
|
queueWhileRunning: true,
|
|
568
1381
|
autoFocus: true,
|