Skip to content

Commit 58a3ae2

Browse files
v0.6.59: gpt 5.5, security hardening, parallel subagents rendering
2 parents d6c1bc2 + 50e74f7 commit 58a3ae2

15 files changed

Lines changed: 560 additions & 253 deletions

File tree

apps/sim/app/api/auth/shopify/authorize/route.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
3232
const returnUrl = request.nextUrl.searchParams.get('returnUrl')
3333

3434
if (!shopDomain) {
35-
const returnUrlParam = returnUrl ? encodeURIComponent(returnUrl) : ''
35+
const safeReturnUrl =
36+
returnUrl && isSameOrigin(returnUrl) ? encodeURIComponent(returnUrl) : ''
37+
const returnUrlJsLiteral = JSON.stringify(safeReturnUrl)
3638
return new NextResponse(
3739
`<!DOCTYPE html>
3840
<html>
@@ -120,7 +122,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
120122
</div>
121123
122124
<script>
123-
const returnUrl = '${returnUrlParam}';
125+
const returnUrl = ${returnUrlJsLiteral};
124126
function handleSubmit(e) {
125127
e.preventDefault();
126128
let shop = document.getElementById('shop').value.trim().toLowerCase();

apps/sim/app/api/credential-sets/invite/[token]/route.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { and, eq } from 'drizzle-orm'
1212
import { type NextRequest, NextResponse } from 'next/server'
1313
import { getSession } from '@/lib/auth'
1414
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
15+
import { normalizeEmail } from '@/lib/invitations/core'
1516
import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server'
1617

1718
const logger = createLogger('CredentialSetInviteToken')
@@ -111,6 +112,21 @@ export const POST = withRouteHandler(
111112
return NextResponse.json({ error: 'Invitation has expired' }, { status: 410 })
112113
}
113114

115+
if (invitation.email) {
116+
const sessionEmail = session.user.email
117+
if (!sessionEmail || normalizeEmail(sessionEmail) !== normalizeEmail(invitation.email)) {
118+
logger.warn('Rejected credential set invitation accept due to email mismatch', {
119+
invitationId: invitation.id,
120+
credentialSetId: invitation.credentialSetId,
121+
userId: session.user.id,
122+
})
123+
return NextResponse.json(
124+
{ error: 'This invitation was sent to a different email address' },
125+
{ status: 403 }
126+
)
127+
}
128+
}
129+
114130
const existingMember = await db
115131
.select()
116132
.from(credentialSetMember)

apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx

Lines changed: 130 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -156,32 +156,86 @@ function toToolData(tc: NonNullable<ContentBlock['toolCall']>): ToolCallData {
156156
*/
157157
function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {
158158
const segments: MessageSegment[] = []
159-
let group: AgentGroupSegment | null = null
160-
const pushGroup = (nextGroup: AgentGroupSegment, isOpen = false) => {
161-
segments.push({ ...nextGroup, isOpen })
159+
const groupsByKey = new Map<string, AgentGroupSegment>()
160+
let activeGroupKey: string | null = null
161+
162+
const groupKey = (name: string, parentToolCallId: string | undefined) =>
163+
parentToolCallId ? `${name}:${parentToolCallId}` : `${name}:legacy`
164+
165+
const resolveGroupKey = (name: string, parentToolCallId: string | undefined) => {
166+
if (parentToolCallId) return groupKey(name, parentToolCallId)
167+
if (activeGroupKey && groupsByKey.get(activeGroupKey)?.agentName === name) {
168+
return activeGroupKey
169+
}
170+
for (const [key, g] of groupsByKey) {
171+
if (g.agentName === name && g.isOpen) return key
172+
}
173+
return groupKey(name, undefined)
174+
}
175+
176+
const ensureGroup = (
177+
name: string,
178+
parentToolCallId: string | undefined
179+
): { group: AgentGroupSegment; created: boolean } => {
180+
const key = resolveGroupKey(name, parentToolCallId)
181+
const existing = groupsByKey.get(key)
182+
if (existing) return { group: existing, created: false }
183+
const group: AgentGroupSegment = {
184+
type: 'agent_group',
185+
id: `agent-${key}-${segments.length}`,
186+
agentName: name,
187+
agentLabel: resolveAgentLabel(name),
188+
items: [],
189+
isDelegating: false,
190+
isOpen: false,
191+
}
192+
segments.push(group)
193+
groupsByKey.set(key, group)
194+
return { group, created: true }
195+
}
196+
197+
const findGroupForSubagentChunk = (
198+
parentToolCallId: string | undefined
199+
): AgentGroupSegment | undefined => {
200+
if (parentToolCallId) {
201+
for (const [key, g] of groupsByKey) {
202+
if (key.endsWith(`:${parentToolCallId}`)) return g
203+
}
204+
return undefined
205+
}
206+
if (activeGroupKey) return groupsByKey.get(activeGroupKey)
207+
return undefined
208+
}
209+
210+
const flushLanes = () => {
211+
for (const g of groupsByKey.values()) {
212+
g.isOpen = false
213+
g.isDelegating = false
214+
}
215+
groupsByKey.clear()
216+
activeGroupKey = null
162217
}
163218

164219
for (let i = 0; i < blocks.length; i++) {
165220
const block = blocks[i]
166221

167222
if (block.type === 'subagent_text' || block.type === 'subagent_thinking') {
168-
if (!block.content || !group) continue
169-
group.isDelegating = false
170-
const lastItem = group.items[group.items.length - 1]
223+
if (!block.content) continue
224+
const g = findGroupForSubagentChunk(block.parentToolCallId)
225+
if (!g) continue
226+
g.isDelegating = false
227+
const lastItem = g.items[g.items.length - 1]
171228
if (lastItem?.type === 'text') {
172229
lastItem.content += block.content
173230
} else {
174-
group.items.push({ type: 'text', content: block.content })
231+
g.items.push({ type: 'text', content: block.content })
175232
}
176233
continue
177234
}
178235

179236
if (block.type === 'thinking') {
180237
if (!block.content?.trim()) continue
181-
if (group) {
182-
pushGroup(group)
183-
group = null
184-
}
238+
flushLanes()
185239
const last = segments[segments.length - 1]
186240
if (last?.type === 'thinking' && last.endedAt === undefined) {
187241
last.content += block.content
@@ -201,21 +255,19 @@ function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {
201255
if (block.type === 'text') {
202256
if (!block.content) continue
203257
if (block.subagent) {
204-
if (group && group.agentName === block.subagent) {
205-
group.isDelegating = false
206-
const lastItem = group.items[group.items.length - 1]
258+
const g = groupsByKey.get(resolveGroupKey(block.subagent, block.parentToolCallId))
259+
if (g) {
260+
g.isDelegating = false
261+
const lastItem = g.items[g.items.length - 1]
207262
if (lastItem?.type === 'text') {
208263
lastItem.content += block.content
209264
} else {
210-
group.items.push({ type: 'text', content: block.content })
265+
g.items.push({ type: 'text', content: block.content })
211266
}
212267
continue
213268
}
214269
}
215-
if (group) {
216-
pushGroup(group)
217-
group = null
218-
}
270+
flushLanes()
219271
const last = segments[segments.length - 1]
220272
if (last?.type === 'text') {
221273
last.content += block.content
@@ -228,34 +280,23 @@ function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {
228280
if (block.type === 'subagent') {
229281
if (!block.content) continue
230282
const key = block.content
231-
if (group && group.agentName === key) continue
232-
233-
const dispatchToolName = SUBAGENT_DISPATCH_TOOLS[key]
234283
let inheritedDelegation = false
235-
if (group && dispatchToolName) {
236-
const last: AgentGroupItem | undefined = group.items[group.items.length - 1]
237-
if (last?.type === 'tool' && last.data.toolName === dispatchToolName) {
238-
inheritedDelegation = !isToolDone(last.data.status) && Boolean(last.data.streamingArgs)
239-
group.items.pop()
240-
}
241-
if (group.items.length > 0) {
242-
pushGroup(group)
284+
const dispatchToolName = SUBAGENT_DISPATCH_TOOLS[key]
285+
if (dispatchToolName) {
286+
const mship = groupsByKey.get(groupKey('mothership', undefined))
287+
if (mship) {
288+
const last = mship.items[mship.items.length - 1]
289+
if (last?.type === 'tool' && last.data.toolName === dispatchToolName) {
290+
inheritedDelegation = !isToolDone(last.data.status) && Boolean(last.data.streamingArgs)
291+
mship.items.pop()
292+
}
243293
}
244-
group = null
245-
} else if (group) {
246-
pushGroup(group)
247-
group = null
248-
}
249-
250-
group = {
251-
type: 'agent_group',
252-
id: `agent-${key}-${i}`,
253-
agentName: key,
254-
agentLabel: resolveAgentLabel(key),
255-
items: [],
256-
isDelegating: inheritedDelegation,
257-
isOpen: false,
258294
}
295+
groupsByKey.delete(groupKey('mothership', undefined))
296+
const { group: g } = ensureGroup(key, block.parentToolCallId)
297+
if (inheritedDelegation) g.isDelegating = true
298+
g.isOpen = true
299+
activeGroupKey = resolveGroupKey(key, block.parentToolCallId)
259300
continue
260301
}
261302

@@ -267,95 +308,75 @@ function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {
267308
const isDispatch = SUBAGENT_KEYS.has(tc.name) && !tc.calledBy
268309

269310
if (isDispatch) {
270-
if (!group || group.agentName !== tc.name) {
271-
if (group) {
272-
pushGroup(group)
273-
group = null
274-
}
275-
group = {
276-
type: 'agent_group',
277-
id: `agent-${tc.name}-${i}`,
278-
agentName: tc.name,
279-
agentLabel: resolveAgentLabel(tc.name),
280-
items: [],
281-
isDelegating: false,
282-
isOpen: false,
283-
}
284-
}
285-
group.isDelegating = isDelegatingTool(tc)
311+
groupsByKey.delete(groupKey('mothership', undefined))
312+
const { group: g } = ensureGroup(tc.name, tc.id)
313+
g.isDelegating = isDelegatingTool(tc)
314+
g.isOpen = g.isDelegating
286315
continue
287316
}
288317

289318
const tool = toToolData(tc)
290319

291-
if (tc.calledBy && group && group.agentName === tc.calledBy) {
292-
group.isDelegating = false
293-
group.items.push({ type: 'tool', data: tool })
294-
} else if (tc.calledBy) {
295-
if (group) {
296-
pushGroup(group)
297-
group = null
298-
}
299-
group = {
300-
type: 'agent_group',
301-
id: `agent-${tc.calledBy}-${i}`,
302-
agentName: tc.calledBy,
303-
agentLabel: resolveAgentLabel(tc.calledBy),
304-
items: [{ type: 'tool', data: tool }],
305-
isDelegating: false,
306-
isOpen: false,
307-
}
320+
if (tc.calledBy) {
321+
const { group: g, created } = ensureGroup(tc.calledBy, block.parentToolCallId)
322+
g.isDelegating = false
323+
if (created && block.parentToolCallId) g.isOpen = true
324+
g.items.push({ type: 'tool', data: tool })
325+
activeGroupKey = resolveGroupKey(tc.calledBy, block.parentToolCallId)
308326
} else {
309-
if (group && group.agentName === 'mothership') {
310-
group.items.push({ type: 'tool', data: tool })
311-
} else {
312-
if (group) {
313-
pushGroup(group)
314-
group = null
315-
}
316-
group = {
317-
type: 'agent_group',
318-
id: `agent-mothership-${i}`,
319-
agentName: 'mothership',
320-
agentLabel: 'Mothership',
321-
items: [{ type: 'tool', data: tool }],
322-
isDelegating: false,
323-
isOpen: false,
324-
}
325-
}
327+
const { group: g } = ensureGroup('mothership', undefined)
328+
g.items.push({ type: 'tool', data: tool })
326329
}
327330
continue
328331
}
329332

330333
if (block.type === 'options') {
331334
if (!block.options?.length) continue
332-
if (group) {
333-
pushGroup(group)
334-
group = null
335-
}
335+
flushLanes()
336336
segments.push({ type: 'options', items: block.options })
337337
continue
338338
}
339339

340340
if (block.type === 'subagent_end') {
341-
if (group) {
342-
pushGroup(group)
343-
group = null
341+
if (block.parentToolCallId) {
342+
for (const [key, g] of groupsByKey) {
343+
if (key.endsWith(`:${block.parentToolCallId}`)) {
344+
g.isOpen = false
345+
g.isDelegating = false
346+
}
347+
}
348+
if (activeGroupKey?.endsWith(`:${block.parentToolCallId}`)) {
349+
activeGroupKey = null
350+
}
351+
} else {
352+
for (const [key, g] of groupsByKey) {
353+
if (key.endsWith(':legacy') && g.agentName !== 'mothership') {
354+
g.isOpen = false
355+
g.isDelegating = false
356+
}
357+
}
358+
if (activeGroupKey?.endsWith(':legacy')) {
359+
activeGroupKey = null
360+
}
344361
}
345362
continue
346363
}
347364

348365
if (block.type === 'stopped') {
349-
if (group) {
350-
pushGroup(group)
351-
group = null
352-
}
366+
flushLanes()
353367
segments.push({ type: 'stopped' })
354368
}
355369
}
356370

357-
if (group) pushGroup(group, true)
358-
return segments
371+
const visibleSegments = segments.filter(
372+
(segment) =>
373+
segment.type !== 'agent_group' ||
374+
segment.items.length > 0 ||
375+
segment.isDelegating ||
376+
segment.isOpen
377+
)
378+
379+
return visibleSegments
359380
}
360381

361382
/**
@@ -428,12 +449,6 @@ export function MessageContent({
428449
isStreaming &&
429450
!hasTrailingContent &&
430451
(lastSegment.type === 'thinking' || hasSubagentEnded || allLastGroupToolsDone)
431-
const lastOpenSubagentGroupId = [...segments]
432-
.reverse()
433-
.find(
434-
(segment): segment is AgentGroupSegment =>
435-
segment.type === 'agent_group' && segment.agentName !== 'mothership' && segment.isOpen
436-
)?.id
437452

438453
return (
439454
<div className='space-y-[10px]'>
@@ -488,8 +503,8 @@ export function MessageContent({
488503
items={segment.items}
489504
isDelegating={segment.isDelegating}
490505
isStreaming={isStreaming}
491-
autoCollapse={allToolsDone && hasFollowingText}
492-
defaultExpanded={segment.id === lastOpenSubagentGroupId}
506+
autoCollapse={!segment.isOpen && allToolsDone && hasFollowingText}
507+
defaultExpanded={segment.isOpen}
493508
/>
494509
</div>
495510
)

0 commit comments

Comments
 (0)