@base44/superagent-native 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +12 -20
- package/lib/commonjs/AgentSettingsPanel.js +32 -15
- package/lib/commonjs/AgentSettingsPanel.js.map +1 -1
- package/lib/commonjs/AttachmentPickerStatusModal.js +2 -2
- package/lib/commonjs/AttachmentPickerStatusModal.js.map +1 -1
- package/lib/commonjs/ConversationChat.js +27 -11
- package/lib/commonjs/ConversationChat.js.map +1 -1
- package/lib/commonjs/ConversationComposer.js +10 -6
- package/lib/commonjs/ConversationComposer.js.map +1 -1
- package/lib/commonjs/ConversationScreen.js +2 -0
- package/lib/commonjs/ConversationScreen.js.map +1 -1
- package/lib/commonjs/MarkdownText.js +1 -1
- package/lib/commonjs/MarkdownText.js.map +1 -1
- package/lib/commonjs/MessageActionBar.js +10 -3
- package/lib/commonjs/MessageActionBar.js.map +1 -1
- package/lib/commonjs/SuperagentHomeScreen.js +17 -3
- package/lib/commonjs/SuperagentHomeScreen.js.map +1 -1
- package/lib/commonjs/ToolApprovalCard.js +1 -1
- package/lib/commonjs/ToolApprovalCard.js.map +1 -1
- package/lib/commonjs/ToolCallSummary.js +5 -1
- package/lib/commonjs/ToolCallSummary.js.map +1 -1
- package/lib/commonjs/attachmentUpload.js +2 -1
- package/lib/commonjs/attachmentUpload.js.map +1 -1
- package/lib/commonjs/conversationRuntime.js +37 -2
- package/lib/commonjs/conversationRuntime.js.map +1 -1
- package/lib/commonjs/fileTreeUtils.js +7 -0
- package/lib/commonjs/fileTreeUtils.js.map +1 -1
- package/lib/commonjs/screenParts.js +3 -3
- package/lib/commonjs/styles.js +43 -43
- package/lib/commonjs/useSuperagentConversation.js +117 -34
- package/lib/commonjs/useSuperagentConversation.js.map +1 -1
- package/lib/commonjs/useSuperagentRuntime.js +79 -24
- package/lib/commonjs/useSuperagentRuntime.js.map +1 -1
- package/lib/module/AgentSettingsPanel.js +32 -15
- package/lib/module/AgentSettingsPanel.js.map +1 -1
- package/lib/module/AttachmentPickerStatusModal.js +2 -2
- package/lib/module/AttachmentPickerStatusModal.js.map +1 -1
- package/lib/module/ConversationChat.js +27 -11
- package/lib/module/ConversationChat.js.map +1 -1
- package/lib/module/ConversationComposer.js +10 -6
- package/lib/module/ConversationComposer.js.map +1 -1
- package/lib/module/ConversationScreen.js +2 -0
- package/lib/module/ConversationScreen.js.map +1 -1
- package/lib/module/MarkdownText.js +1 -1
- package/lib/module/MarkdownText.js.map +1 -1
- package/lib/module/MessageActionBar.js +10 -3
- package/lib/module/MessageActionBar.js.map +1 -1
- package/lib/module/SuperagentHomeScreen.js +18 -4
- package/lib/module/SuperagentHomeScreen.js.map +1 -1
- package/lib/module/ToolApprovalCard.js +1 -1
- package/lib/module/ToolApprovalCard.js.map +1 -1
- package/lib/module/ToolCallSummary.js +5 -1
- package/lib/module/ToolCallSummary.js.map +1 -1
- package/lib/module/attachmentUpload.js +2 -1
- package/lib/module/attachmentUpload.js.map +1 -1
- package/lib/module/conversationRuntime.js +36 -2
- package/lib/module/conversationRuntime.js.map +1 -1
- package/lib/module/fileTreeUtils.js +6 -0
- package/lib/module/fileTreeUtils.js.map +1 -1
- package/lib/module/screenParts.js +3 -3
- package/lib/module/styles.js +43 -43
- package/lib/module/useSuperagentConversation.js +118 -35
- package/lib/module/useSuperagentConversation.js.map +1 -1
- package/lib/module/useSuperagentRuntime.js +80 -25
- package/lib/module/useSuperagentRuntime.js.map +1 -1
- package/lib/typescript/commonjs/AgentSettingsPanel.d.ts.map +1 -1
- package/lib/typescript/commonjs/ConversationChat.d.ts.map +1 -1
- package/lib/typescript/commonjs/ConversationComposer.d.ts.map +1 -1
- package/lib/typescript/commonjs/ConversationScreen.d.ts +1 -1
- package/lib/typescript/commonjs/ConversationScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/SuperagentHomeScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/conversationRuntime.d.ts +3 -2
- package/lib/typescript/commonjs/conversationRuntime.d.ts.map +1 -1
- package/lib/typescript/commonjs/fileTreeUtils.d.ts +1 -0
- package/lib/typescript/commonjs/fileTreeUtils.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +1 -0
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/useSuperagentConversation.d.ts.map +1 -1
- package/lib/typescript/commonjs/useSuperagentRuntime.d.ts +3 -1
- package/lib/typescript/commonjs/useSuperagentRuntime.d.ts.map +1 -1
- package/lib/typescript/module/AgentSettingsPanel.d.ts.map +1 -1
- package/lib/typescript/module/ConversationChat.d.ts.map +1 -1
- package/lib/typescript/module/ConversationComposer.d.ts.map +1 -1
- package/lib/typescript/module/ConversationScreen.d.ts +1 -1
- package/lib/typescript/module/ConversationScreen.d.ts.map +1 -1
- package/lib/typescript/module/SuperagentHomeScreen.d.ts.map +1 -1
- package/lib/typescript/module/conversationRuntime.d.ts +3 -2
- package/lib/typescript/module/conversationRuntime.d.ts.map +1 -1
- package/lib/typescript/module/fileTreeUtils.d.ts +1 -0
- package/lib/typescript/module/fileTreeUtils.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +1 -0
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/useSuperagentConversation.d.ts.map +1 -1
- package/lib/typescript/module/useSuperagentRuntime.d.ts +3 -1
- package/lib/typescript/module/useSuperagentRuntime.d.ts.map +1 -1
- package/package.json +13 -11
- package/src/AgentSettingsPanel.tsx +28 -9
- package/src/AttachmentPickerStatusModal.tsx +2 -2
- package/src/ConversationChat.tsx +37 -9
- package/src/ConversationComposer.tsx +11 -6
- package/src/ConversationScreen.tsx +2 -0
- package/src/MarkdownText.tsx +1 -1
- package/src/MessageActionBar.tsx +9 -3
- package/src/SuperagentHomeScreen.tsx +18 -3
- package/src/ToolApprovalCard.tsx +1 -1
- package/src/ToolCallSummary.tsx +4 -1
- package/src/attachmentUpload.ts +2 -1
- package/src/conversationRuntime.ts +48 -4
- package/src/fileTreeUtils.ts +13 -0
- package/src/screenParts.tsx +3 -3
- package/src/styles.ts +43 -43
- package/src/types.ts +1 -0
- package/src/useSuperagentConversation.ts +116 -31
- package/src/useSuperagentRuntime.ts +80 -24
|
@@ -121,9 +121,13 @@ export function AgentSettingsPanel({
|
|
|
121
121
|
const [newSecretValue, setNewSecretValue] = useState('');
|
|
122
122
|
const [isModelPickerOpen, setIsModelPickerOpen] = useState(false);
|
|
123
123
|
const [pendingAction, setPendingAction] = useState<PendingAction>(null);
|
|
124
|
+
const [deletingSecretName, setDeletingSecretName] = useState<string | null>(null);
|
|
124
125
|
const [visibleSecrets, setVisibleSecrets] = useState<Set<string>>(new Set());
|
|
125
126
|
|
|
126
|
-
const permissions =
|
|
127
|
+
const permissions = useMemo(
|
|
128
|
+
() => normalizePermissions(agent.toolsPermissionConfig),
|
|
129
|
+
[agent.toolsPermissionConfig],
|
|
130
|
+
);
|
|
127
131
|
const selectedModel = useMemo(
|
|
128
132
|
() => MODEL_OPTIONS.find((model) => model.id === (agent.model || 'default')) ?? MODEL_OPTIONS[0],
|
|
129
133
|
[agent.model],
|
|
@@ -140,8 +144,12 @@ export function AgentSettingsPanel({
|
|
|
140
144
|
);
|
|
141
145
|
|
|
142
146
|
useEffect(() => {
|
|
147
|
+
// Seed guard drafts only when switching agents — not on every
|
|
148
|
+
// toolsPermissionConfig change — so in-progress guard edits aren't wiped by
|
|
149
|
+
// an unrelated permission toggle before "Save rules" is pressed.
|
|
143
150
|
setGuardDrafts(permissions.connector_guards);
|
|
144
|
-
|
|
151
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
152
|
+
}, [agent.id]);
|
|
145
153
|
|
|
146
154
|
const runAction = async (action: Exclude<PendingAction, null>, handler: () => Promise<void> | void) => {
|
|
147
155
|
if (pendingAction) {
|
|
@@ -211,11 +219,17 @@ export function AgentSettingsPanel({
|
|
|
211
219
|
return;
|
|
212
220
|
}
|
|
213
221
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
222
|
+
// Only clear the inputs after a successful save; if onSaveSecret rejects it
|
|
223
|
+
// already surfaced the error, and we keep the typed values so they aren't lost.
|
|
224
|
+
try {
|
|
225
|
+
await runAction('save-secret', async () => {
|
|
226
|
+
await onSaveSecret({ agentId: agent.id, name, value });
|
|
227
|
+
setNewSecretName('');
|
|
228
|
+
setNewSecretValue('');
|
|
229
|
+
});
|
|
230
|
+
} catch {
|
|
231
|
+
// onSaveSecret already surfaced the error; keep the form populated.
|
|
232
|
+
}
|
|
219
233
|
};
|
|
220
234
|
|
|
221
235
|
const deleteSecret = async (secret: SuperagentSecret) => {
|
|
@@ -223,7 +237,12 @@ export function AgentSettingsPanel({
|
|
|
223
237
|
return;
|
|
224
238
|
}
|
|
225
239
|
|
|
226
|
-
|
|
240
|
+
setDeletingSecretName(secret.name);
|
|
241
|
+
try {
|
|
242
|
+
await runAction('delete-secret', () => onDeleteSecret({ agentId: agent.id, name: secret.name }));
|
|
243
|
+
} finally {
|
|
244
|
+
setDeletingSecretName(null);
|
|
245
|
+
}
|
|
227
246
|
};
|
|
228
247
|
|
|
229
248
|
return (
|
|
@@ -308,7 +327,7 @@ export function AgentSettingsPanel({
|
|
|
308
327
|
onPress={() => deleteSecret(secret)}
|
|
309
328
|
style={({ pressed }) => [localStyles.iconButtonDanger, pressed && sharedStyles.pressed]}
|
|
310
329
|
>
|
|
311
|
-
{
|
|
330
|
+
{deletingSecretName === secret.name ? (
|
|
312
331
|
<ActivityIndicator color="#FCA5A5" size="small" />
|
|
313
332
|
) : (
|
|
314
333
|
<Trash2 color="#FCA5A5" size={16} strokeWidth={2.4} />
|
|
@@ -19,9 +19,9 @@ export function AttachmentPickerStatusModal({
|
|
|
19
19
|
{isError ? null : <ActivityIndicator color="#F4F4F5" style={styles.loader} />}
|
|
20
20
|
<Text style={styles.title}>{title}</Text>
|
|
21
21
|
<Text style={[styles.message, isError ? styles.error : undefined]}>{message}</Text>
|
|
22
|
-
{
|
|
22
|
+
{onClose ? (
|
|
23
23
|
<Pressable accessibilityRole="button" onPress={onClose} style={styles.closeButton}>
|
|
24
|
-
<Text style={styles.closeText}>Close</Text>
|
|
24
|
+
<Text style={styles.closeText}>{isError ? 'Close' : 'Cancel'}</Text>
|
|
25
25
|
</Pressable>
|
|
26
26
|
) : null}
|
|
27
27
|
</View>
|
package/src/ConversationChat.tsx
CHANGED
|
@@ -63,10 +63,23 @@ export function ConversationChat({
|
|
|
63
63
|
const [draft, setDraft] = useState('');
|
|
64
64
|
const [attachments, setAttachments] = useState<SuperagentMediaAttachment[]>([]);
|
|
65
65
|
const [replyTo, setReplyTo] = useState<SuperagentReplyTo | null>(null);
|
|
66
|
-
|
|
66
|
+
// Conversation failed to initialize (apiClient path): no conversation id to send
|
|
67
|
+
// against, so block sends rather than clear the composer and drop the text.
|
|
68
|
+
const conversationUnavailable = Boolean(conversation.initError && !conversation.conversationId);
|
|
69
|
+
const canSend = (draft.trim().length > 0 || attachments.length > 0)
|
|
70
|
+
&& !conversation.isSending
|
|
71
|
+
&& !conversation.isLoading
|
|
72
|
+
&& !conversationUnavailable;
|
|
67
73
|
const displayedMessages = useMemo(
|
|
68
|
-
() =>
|
|
69
|
-
|
|
74
|
+
() => {
|
|
75
|
+
if (conversation.messages.length > 0) return conversation.messages;
|
|
76
|
+
// While the real conversation is still loading, don't fabricate a welcome
|
|
77
|
+
// bubble — it would render next to the loading panel as if the agent already
|
|
78
|
+
// sent an intro.
|
|
79
|
+
if (conversation.isLoading) return [];
|
|
80
|
+
return [createWelcomeMessage(agent)];
|
|
81
|
+
},
|
|
82
|
+
[agent, conversation.messages, conversation.isLoading],
|
|
70
83
|
);
|
|
71
84
|
const tailMessage = displayedMessages[displayedMessages.length - 1];
|
|
72
85
|
const tailMessageId = tailMessage ? getMessageAutoScrollId(tailMessage, displayedMessages.length - 1) : null;
|
|
@@ -104,7 +117,10 @@ export function ConversationChat({
|
|
|
104
117
|
const selectedAttachments = attachments;
|
|
105
118
|
const selectedReplyTo = replyTo;
|
|
106
119
|
const content = draft.trim() || buildAttachmentPrompt(selectedAttachments);
|
|
107
|
-
|
|
120
|
+
// Don't send (and clear the composer) until the conversation is ready — while
|
|
121
|
+
// it's still loading or failed to init the hook can't reach the server and
|
|
122
|
+
// would drop the text.
|
|
123
|
+
if ((!content && selectedAttachments.length === 0) || conversation.isSending || conversation.isLoading || conversationUnavailable) return;
|
|
108
124
|
|
|
109
125
|
setDraft('');
|
|
110
126
|
setAttachments([]);
|
|
@@ -114,7 +130,7 @@ export function ConversationChat({
|
|
|
114
130
|
fileUrls: selectedAttachments.map((attachment) => attachment.url),
|
|
115
131
|
replyTo: selectedReplyTo ?? undefined,
|
|
116
132
|
});
|
|
117
|
-
}, [attachments, conversation, draft, replyTo]);
|
|
133
|
+
}, [attachments, conversation, conversationUnavailable, draft, replyTo]);
|
|
118
134
|
|
|
119
135
|
const addAttachments = useCallback((incoming: SuperagentMediaAttachment[]) => {
|
|
120
136
|
setAttachments((current) => [...current, ...incoming].slice(0, MAX_ATTACHMENTS));
|
|
@@ -125,7 +141,7 @@ export function ConversationChat({
|
|
|
125
141
|
}, []);
|
|
126
142
|
|
|
127
143
|
const replyToMessage = useCallback((message: SuperagentMessage) => {
|
|
128
|
-
const content = message.content
|
|
144
|
+
const content = message.content?.trim();
|
|
129
145
|
if (!content) return;
|
|
130
146
|
setReplyTo({
|
|
131
147
|
content,
|
|
@@ -133,6 +149,18 @@ export function ConversationChat({
|
|
|
133
149
|
});
|
|
134
150
|
}, []);
|
|
135
151
|
|
|
152
|
+
const composerContext = useMemo(
|
|
153
|
+
() => ({ agentId: agent.id, conversationId: conversation.conversationId }),
|
|
154
|
+
[agent.id, conversation.conversationId],
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const clearReply = useCallback(() => setReplyTo(null), []);
|
|
158
|
+
|
|
159
|
+
const removeAttachment = useCallback(
|
|
160
|
+
(index: number) => setAttachments((current) => current.filter((_, itemIndex) => itemIndex !== index)),
|
|
161
|
+
[],
|
|
162
|
+
);
|
|
163
|
+
|
|
136
164
|
return (
|
|
137
165
|
<>
|
|
138
166
|
<ScrollView
|
|
@@ -167,17 +195,17 @@ export function ConversationChat({
|
|
|
167
195
|
<ConversationComposer
|
|
168
196
|
attachments={attachments}
|
|
169
197
|
canSend={canSend}
|
|
170
|
-
context={
|
|
198
|
+
context={composerContext}
|
|
171
199
|
draft={draft}
|
|
172
200
|
isSending={conversation.isSending}
|
|
173
201
|
onAddAttachments={addAttachments}
|
|
174
202
|
onAppendTranscript={appendTranscript}
|
|
175
203
|
onChangeDraft={setDraft}
|
|
176
|
-
onClearReply={
|
|
204
|
+
onClearReply={clearReply}
|
|
177
205
|
onImportFromDrive={onImportFromDrive}
|
|
178
206
|
onPickFiles={onPickFiles}
|
|
179
207
|
onPickPhotos={onPickPhotos}
|
|
180
|
-
onRemoveAttachment={
|
|
208
|
+
onRemoveAttachment={removeAttachment}
|
|
181
209
|
onSend={sendMessage}
|
|
182
210
|
onStartLiveVoice={onStartLiveVoice}
|
|
183
211
|
onStartVoiceInput={onStartVoiceInput}
|
|
@@ -185,7 +185,8 @@ export function ConversationComposer({
|
|
|
185
185
|
isLiveVoiceActive={liveVoiceState === 'processing'}
|
|
186
186
|
isVoiceInputActive={voiceState === 'processing'}
|
|
187
187
|
onSend={onSend}
|
|
188
|
-
|
|
188
|
+
onStartVoice={context.conversationId ? (onStartLiveVoice ? startLiveVoice : onStartVoiceInput ? startVoiceInput : undefined) : undefined}
|
|
189
|
+
voiceMode={context.conversationId ? (onStartLiveVoice ? 'live' : onStartVoiceInput ? 'input' : null) : null}
|
|
189
190
|
onStop={onStop}
|
|
190
191
|
isSending={isSending}
|
|
191
192
|
/>
|
|
@@ -321,7 +322,8 @@ function ComposerActionButton({
|
|
|
321
322
|
isSending,
|
|
322
323
|
isVoiceInputActive,
|
|
323
324
|
onSend,
|
|
324
|
-
|
|
325
|
+
onStartVoice,
|
|
326
|
+
voiceMode,
|
|
325
327
|
onStop,
|
|
326
328
|
}: {
|
|
327
329
|
canSend: boolean;
|
|
@@ -330,7 +332,8 @@ function ComposerActionButton({
|
|
|
330
332
|
isSending: boolean;
|
|
331
333
|
isVoiceInputActive: boolean;
|
|
332
334
|
onSend: () => void;
|
|
333
|
-
|
|
335
|
+
onStartVoice?: () => void;
|
|
336
|
+
voiceMode: 'live' | 'input' | null;
|
|
334
337
|
onStop: () => void;
|
|
335
338
|
}) {
|
|
336
339
|
if (isSending) {
|
|
@@ -342,12 +345,14 @@ function ComposerActionButton({
|
|
|
342
345
|
}
|
|
343
346
|
|
|
344
347
|
if (isDraftEmpty) {
|
|
345
|
-
const disabled = !
|
|
348
|
+
const disabled = !onStartVoice;
|
|
349
|
+
const isActive = voiceMode === 'live' ? isLiveVoiceActive : isVoiceInputActive;
|
|
350
|
+
const label = voiceMode === 'input' ? 'Start voice input' : 'Start Live Voice';
|
|
346
351
|
return (
|
|
347
|
-
<Pressable accessibilityLabel=
|
|
352
|
+
<Pressable accessibilityLabel={label} accessibilityRole="button" disabled={disabled} onPress={onStartVoice} style={({ pressed }) => [
|
|
348
353
|
composerStyles.sendButton,
|
|
349
354
|
composerStyles.liveActionButton,
|
|
350
|
-
|
|
355
|
+
isActive && composerStyles.activeButton,
|
|
351
356
|
disabled && composerStyles.sendButtonDisabled,
|
|
352
357
|
pressed && styles.pressed,
|
|
353
358
|
]}>
|
|
@@ -31,6 +31,7 @@ export function ConversationScreen({
|
|
|
31
31
|
filePaths,
|
|
32
32
|
isLoadingAgentSettings,
|
|
33
33
|
isLoadingAutomations,
|
|
34
|
+
isLoadingChannels,
|
|
34
35
|
isLoadingCollaborators,
|
|
35
36
|
isLoadingConnectors,
|
|
36
37
|
isLoadingFiles,
|
|
@@ -246,6 +247,7 @@ export function ConversationScreen({
|
|
|
246
247
|
fileLoadFailed={fileLoadFailed}
|
|
247
248
|
filePaths={filePaths}
|
|
248
249
|
isLoadingAgentSettings={isLoadingAgentSettings}
|
|
250
|
+
isLoadingChannels={isLoadingChannels}
|
|
249
251
|
isLoadingCollaborators={isLoadingCollaborators}
|
|
250
252
|
isLoadingFiles={isLoadingFiles}
|
|
251
253
|
isLoadingAutomations={isLoadingAutomations}
|
package/src/MarkdownText.tsx
CHANGED
|
@@ -237,7 +237,7 @@ function isTableRow(line?: string) {
|
|
|
237
237
|
function isTableSeparator(line?: string) {
|
|
238
238
|
if (!line) return false;
|
|
239
239
|
const cells = splitTableRow(line);
|
|
240
|
-
return cells.length >= 2 && cells.every((cell) =>
|
|
240
|
+
return cells.length >= 2 && cells.every((cell) => /^:?-+:?$/.test(cell.replace(/\s+/g, '')));
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
function splitTableRow(line: string) {
|
package/src/MessageActionBar.tsx
CHANGED
|
@@ -95,7 +95,7 @@ function getMessageActions({
|
|
|
95
95
|
}): MessageAction[] {
|
|
96
96
|
const messageId = message.id;
|
|
97
97
|
const isPreviewMessage = messageId === 'welcome';
|
|
98
|
-
const hasContent = message.content
|
|
98
|
+
const hasContent = (message.content?.trim().length ?? 0) > 0;
|
|
99
99
|
const actions: MessageAction[] = [];
|
|
100
100
|
|
|
101
101
|
if (!isPreviewMessage && hasContent && onReplyMessage) {
|
|
@@ -112,12 +112,18 @@ function getMessageActions({
|
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
// Only content-bearing messages are deletable. Tool-only bubbles can be a
|
|
116
|
+
// merge of several tool turns under a synthetic `a:b` id the server never
|
|
117
|
+
// issued, so deleting by that id would fail.
|
|
118
|
+
if (!isPreviewMessage && hasContent && messageId && onDeleteMessage) {
|
|
116
119
|
actions.push({
|
|
117
120
|
destructive: true,
|
|
118
121
|
label: 'Delete',
|
|
119
122
|
run: async () => {
|
|
120
|
-
await onDeleteMessage(messageId);
|
|
123
|
+
const deleted = await onDeleteMessage(messageId);
|
|
124
|
+
if (deleted === false) {
|
|
125
|
+
Alert.alert('Delete failed', 'The message could not be deleted. Please try again.');
|
|
126
|
+
}
|
|
121
127
|
},
|
|
122
128
|
});
|
|
123
129
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useMemo, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
ActivityIndicator,
|
|
4
4
|
SafeAreaView,
|
|
@@ -19,6 +19,7 @@ export function SuperagentHomeScreen(props: SuperagentHomeScreenProps) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
function SuperagentHomeScreenContent({
|
|
22
|
+
activeAgentId,
|
|
22
23
|
agents = [],
|
|
23
24
|
automations,
|
|
24
25
|
automationCredits,
|
|
@@ -106,9 +107,22 @@ function SuperagentHomeScreenContent({
|
|
|
106
107
|
}: SuperagentHomeScreenProps) {
|
|
107
108
|
const [route, setRoute] = useState<SuperagentRoute>(initialRoute);
|
|
108
109
|
const [isCreating, setIsCreating] = useState(false);
|
|
110
|
+
|
|
111
|
+
// Follow externally-driven route changes (e.g. the runtime moves home after the
|
|
112
|
+
// active agent is deleted, or a deep link changes the agent). Keyed by value so
|
|
113
|
+
// it doesn't clobber internal navigation on unrelated re-renders.
|
|
114
|
+
const externalRouteKey = initialRoute.name === 'agent' ? `agent:${initialRoute.agentId}` : initialRoute.name;
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
setRoute(initialRoute);
|
|
117
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
118
|
+
}, [externalRouteKey]);
|
|
109
119
|
const latestAgent = agents[0] ?? null;
|
|
110
|
-
|
|
111
|
-
|
|
120
|
+
// automations/connectedConnectors track only the active agent, so showing their
|
|
121
|
+
// counts on the card is wrong unless the card IS the active agent. Otherwise fall
|
|
122
|
+
// back to 0 (AgentCard renders the neutral "Tools ready" / "No tasks yet").
|
|
123
|
+
const statsMatchLatest = latestAgent != null && latestAgent.id === activeAgentId;
|
|
124
|
+
const automationCount = statsMatchLatest ? (automations?.length ?? 0) : 0;
|
|
125
|
+
const connectedConnectorCount = statsMatchLatest ? (connectedConnectors?.length ?? 0) : 0;
|
|
112
126
|
|
|
113
127
|
const navigate = useCallback((nextRoute: SuperagentRoute) => {
|
|
114
128
|
setRoute(nextRoute);
|
|
@@ -151,6 +165,7 @@ function SuperagentHomeScreenContent({
|
|
|
151
165
|
|
|
152
166
|
return (
|
|
153
167
|
<ConversationScreen
|
|
168
|
+
key={route.agentId}
|
|
154
169
|
apiClient={apiClient}
|
|
155
170
|
agent={activeAgent ?? createUnknownAgent(route.agentId)}
|
|
156
171
|
automationCredits={automationCredits}
|
package/src/ToolApprovalCard.tsx
CHANGED
|
@@ -383,7 +383,7 @@ function ApprovalInput({
|
|
|
383
383
|
function getApprovalKind(toolCall: SuperagentToolCall, guardData: Record<string, unknown> | null): ApprovalKind {
|
|
384
384
|
if (guardData) return 'guard';
|
|
385
385
|
if (toolCall.name === 'setup_slack_connection' || toolCall.name === 'setup_telegram_connection') return 'channel';
|
|
386
|
-
if (toolCall.name === 'set_app_user_connector') return 'customConnector';
|
|
386
|
+
if (toolCall.name === 'set_app_user_connector' || toolCall.name === 'register_workspace_connector') return 'customConnector';
|
|
387
387
|
if (toolCall.name === 'request_oauth_authorization') return 'connector';
|
|
388
388
|
if (toolCall.name === 'install_npm_package') return 'package';
|
|
389
389
|
if (toolCall.name === 'suggest_payments_installation' || toolCall.name === 'suggest_stripe_installation') return 'payment';
|
package/src/ToolCallSummary.tsx
CHANGED
|
@@ -169,7 +169,10 @@ function DefaultToolTimeline({
|
|
|
169
169
|
|
|
170
170
|
const label = getCollapsedLabel(visibleToolCalls, isRunning);
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
// Only collapse to the bare running indicator when nothing needs the user's
|
|
173
|
+
// input. If an approval/waiting tool is present alongside a running one, fall
|
|
174
|
+
// through to the expanded timeline so the approval card stays reachable.
|
|
175
|
+
if (isRunning && !hasWaitingTool) {
|
|
173
176
|
return (
|
|
174
177
|
<View style={conversationStyles.toolCallSlot}>
|
|
175
178
|
<RunningToolIndicator label={label} />
|
package/src/attachmentUpload.ts
CHANGED
|
@@ -92,7 +92,8 @@ async function uploadNativeItem({
|
|
|
92
92
|
},
|
|
93
93
|
method: 'POST',
|
|
94
94
|
});
|
|
95
|
-
const
|
|
95
|
+
const parsed = await response.json().catch(() => null);
|
|
96
|
+
const body = parsed && typeof parsed === 'object' ? parsed : {};
|
|
96
97
|
|
|
97
98
|
if (!response.ok || typeof body.url !== 'string') {
|
|
98
99
|
throw new Error(body.message || body.detail || 'Upload failed.');
|
|
@@ -22,7 +22,15 @@ export function mergeMessage(messages: SuperagentMessage[], nextMessage: Superag
|
|
|
22
22
|
const existingIndex = messages.findIndex((message) => message.id === normalized.id);
|
|
23
23
|
if (existingIndex < 0) return [...messages, normalized];
|
|
24
24
|
|
|
25
|
-
return messages.map((message, index) =>
|
|
25
|
+
return messages.map((message, index) => {
|
|
26
|
+
if (index !== existingIndex) return message;
|
|
27
|
+
// Don't let a shorter/stale assistant payload overwrite newer streamed text
|
|
28
|
+
// the user is already reading (mirrors the onConversation snapshot merge).
|
|
29
|
+
if (message.role === 'assistant' && (message.content?.length ?? 0) > (normalized.content?.length ?? 0)) {
|
|
30
|
+
return message;
|
|
31
|
+
}
|
|
32
|
+
return normalized;
|
|
33
|
+
});
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
export function isVisibleMessage(message: SuperagentMessage, index: number) {
|
|
@@ -78,14 +86,46 @@ export function hasNewAssistantResponse(messages: SuperagentMessage[], assistant
|
|
|
78
86
|
return countAssistantResponses(messages) > assistantCountBefore;
|
|
79
87
|
}
|
|
80
88
|
|
|
89
|
+
// Merge an authoritative server snapshot with the current list, keeping a local
|
|
90
|
+
// assistant row when it already has more text than the snapshot copy — so a
|
|
91
|
+
// slightly stale fetch/event can't shorten streamed content the user is reading.
|
|
92
|
+
export function mergeConversationSnapshot(
|
|
93
|
+
current: SuperagentMessage[],
|
|
94
|
+
incoming: SuperagentMessage[],
|
|
95
|
+
): SuperagentMessage[] {
|
|
96
|
+
const currentById = new Map(current.filter((message) => message.id).map((message) => [message.id, message]));
|
|
97
|
+
const incomingIds = new Set(incoming.map((message) => message.id).filter(Boolean));
|
|
98
|
+
const incomingUserContents = new Set(
|
|
99
|
+
incoming.filter((message) => message.role === 'user').map((message) => (message.content ?? '').trim()),
|
|
100
|
+
);
|
|
101
|
+
const merged = incoming.map((message) => {
|
|
102
|
+
const local = message.id ? currentById.get(message.id) : undefined;
|
|
103
|
+
if (local && local.role === 'assistant' && (local.content?.length ?? 0) > (message.content?.length ?? 0)) {
|
|
104
|
+
return local;
|
|
105
|
+
}
|
|
106
|
+
return message;
|
|
107
|
+
});
|
|
108
|
+
// Keep local rows the snapshot hasn't caught up to (optimistic sends / messages
|
|
109
|
+
// still streaming in), dropping an optimistic user echo the snapshot already
|
|
110
|
+
// represents by content so it isn't duplicated. Callers that genuinely remove a
|
|
111
|
+
// message (delete) must drop it from local state before refreshing, or it would
|
|
112
|
+
// be resurrected here.
|
|
113
|
+
const pendingLocal = current.filter((message) =>
|
|
114
|
+
message.id
|
|
115
|
+
&& !incomingIds.has(message.id)
|
|
116
|
+
&& !(message.role === 'user' && incomingUserContents.has((message.content ?? '').trim())),
|
|
117
|
+
);
|
|
118
|
+
return [...merged, ...pendingLocal];
|
|
119
|
+
}
|
|
120
|
+
|
|
81
121
|
export async function refreshConversation(
|
|
82
122
|
apiClient: SuperagentNativeClient,
|
|
83
123
|
conversationId: string,
|
|
84
|
-
setMessages:
|
|
124
|
+
setMessages: Dispatch<SetStateAction<SuperagentMessage[]>>,
|
|
85
125
|
) {
|
|
86
126
|
const refreshed = await apiClient.getConversation(conversationId);
|
|
87
127
|
const normalizedMessages = normalizeMessages(refreshed.messages ?? []);
|
|
88
|
-
setMessages(normalizedMessages);
|
|
128
|
+
setMessages((current) => mergeConversationSnapshot(current, normalizedMessages));
|
|
89
129
|
return normalizedMessages;
|
|
90
130
|
}
|
|
91
131
|
|
|
@@ -116,7 +156,7 @@ export async function sendWithFallback(
|
|
|
116
156
|
export function pollConversation(
|
|
117
157
|
apiClient: SuperagentNativeClient,
|
|
118
158
|
conversationId: string,
|
|
119
|
-
setMessages:
|
|
159
|
+
setMessages: Dispatch<SetStateAction<SuperagentMessage[]>>,
|
|
120
160
|
setIsSending: (value: boolean) => void,
|
|
121
161
|
assistantCountBefore: number,
|
|
122
162
|
onComplete?: () => Promise<void> | void,
|
|
@@ -132,6 +172,8 @@ export function pollConversation(
|
|
|
132
172
|
const refreshedMessages = await refreshConversation(apiClient, conversationId, setMessages);
|
|
133
173
|
foundAssistantResponse = hasNewAssistantResponse(refreshedMessages, assistantCountBefore);
|
|
134
174
|
shouldStop = shouldStop || foundAssistantResponse;
|
|
175
|
+
} catch {
|
|
176
|
+
// Swallow transient poll failures; keep polling until the attempt cap.
|
|
135
177
|
} finally {
|
|
136
178
|
if (shouldStop) {
|
|
137
179
|
clearInterval(interval);
|
|
@@ -141,6 +183,8 @@ export function pollConversation(
|
|
|
141
183
|
}
|
|
142
184
|
}
|
|
143
185
|
}, 2000);
|
|
186
|
+
|
|
187
|
+
return interval;
|
|
144
188
|
}
|
|
145
189
|
|
|
146
190
|
export function getMessageCursor(message?: SuperagentMessage) {
|
package/src/fileTreeUtils.ts
CHANGED
|
@@ -15,6 +15,19 @@ export const DEFAULT_SANDBOX_FILE_PATHS = [
|
|
|
15
15
|
|
|
16
16
|
export type SuperagentFileCategory = 'code' | 'html' | 'image' | 'markdown' | 'pdf' | 'text';
|
|
17
17
|
|
|
18
|
+
export function sanitizeSandboxFilePath(path: string) {
|
|
19
|
+
// Strip absolute / `..` / `.` segments and null bytes so a picked file name can't
|
|
20
|
+
// write outside the sandbox via path traversal (defense in depth, not a substitute
|
|
21
|
+
// for the backend's own validation).
|
|
22
|
+
return path
|
|
23
|
+
.replace(/\\/g, '/')
|
|
24
|
+
.replace(/\0/g, '')
|
|
25
|
+
.split('/')
|
|
26
|
+
.map((segment) => segment.trim())
|
|
27
|
+
.filter((segment) => segment && segment !== '.' && segment !== '..')
|
|
28
|
+
.join('/');
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
export function normalizeFilePaths(paths: string[] = []) {
|
|
19
32
|
return paths
|
|
20
33
|
.filter((path) => typeof path === 'string' && path.trim().length > 0)
|
package/src/screenParts.tsx
CHANGED
|
@@ -44,7 +44,7 @@ export function AgentCard({
|
|
|
44
44
|
{preview || agent.description || 'Open your latest Superagent conversation.'}
|
|
45
45
|
</Text>
|
|
46
46
|
</View>
|
|
47
|
-
<ChevronRight color="#
|
|
47
|
+
<ChevronRight color="#A1A1AA" size={24} strokeWidth={2.5} />
|
|
48
48
|
</View>
|
|
49
49
|
<View style={styles.agentMetaRow}>
|
|
50
50
|
{meta.map((item) => (
|
|
@@ -118,7 +118,7 @@ export function AgentRow({
|
|
|
118
118
|
{preview || agent.description || 'Ready for the next instruction.'}
|
|
119
119
|
</Text>
|
|
120
120
|
</View>
|
|
121
|
-
<ChevronRight color="#
|
|
121
|
+
<ChevronRight color="#A1A1AA" size={21} strokeWidth={2.5} />
|
|
122
122
|
</Pressable>
|
|
123
123
|
);
|
|
124
124
|
}
|
|
@@ -145,7 +145,7 @@ export function CreateAgentButton({
|
|
|
145
145
|
onPress={onPress}
|
|
146
146
|
style={({ pressed }) => [styles.secondaryButton, pressed && styles.pressed]}
|
|
147
147
|
>
|
|
148
|
-
<Plus color="#
|
|
148
|
+
<Plus color="#18181B" size={18} strokeWidth={2.6} />
|
|
149
149
|
<Text style={styles.secondaryButtonText}>{isCreating ? 'Creating...' : 'New Superagent'}</Text>
|
|
150
150
|
</Pressable>
|
|
151
151
|
);
|