Skip to content

Commit 6898f40

Browse files
committed
fix(managed-agents): address review round 2
- don't complete on idle status while a requires_action is still outstanding - vaults: combobox → dropdown so multiSelect actually attaches multiple vaults - auto-select a freshly-pasted service-account credential (onCreated through the connect modal)
1 parent 3a0dbb9 commit 6898f40

6 files changed

Lines changed: 44 additions & 8 deletions

File tree

apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/connect-service-account-modal.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ interface ConnectServiceAccountModalProps {
108108
credentialDisplayName?: string
109109
/** Existing description, used to seed reconnect-capable modals. */
110110
credentialDescription?: string
111+
/** Called with the new credential id after a successful create (token-paste providers). */
112+
onCreated?: (credentialId: string) => void
111113
}
112114

113115
/**
@@ -132,6 +134,7 @@ export function ConnectServiceAccountModal({
132134
credentialId,
133135
credentialDisplayName,
134136
credentialDescription,
137+
onCreated,
135138
}: ConnectServiceAccountModalProps) {
136139
const clientCredentialDescriptor = getClientCredentialAccountDescriptor(serviceAccountProviderId)
137140
if (clientCredentialDescriptor) {
@@ -162,6 +165,7 @@ export function ConnectServiceAccountModal({
162165
credentialId={credentialId}
163166
initialDisplayName={credentialDisplayName}
164167
initialDescription={credentialDescription}
168+
onCreated={onCreated}
165169
/>
166170
)
167171
}

apps/sim/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal/token-service-account-modal.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ interface TokenServiceAccountModalProps {
7272
credentialId?: string
7373
initialDisplayName?: string
7474
initialDescription?: string
75+
/** Called with the new credential id after a successful create (not reconnect). */
76+
onCreated?: (credentialId: string) => void
7577
}
7678

7779
/**
@@ -91,6 +93,7 @@ export function TokenServiceAccountModal({
9193
credentialId,
9294
initialDisplayName,
9395
initialDescription,
96+
onCreated,
9497
}: TokenServiceAccountModalProps) {
9598
const [apiToken, setApiToken] = useState('')
9699
const [domain, setDomain] = useState('')
@@ -139,14 +142,15 @@ export function TokenServiceAccountModal({
139142
description: description.trim() || undefined,
140143
})
141144
} else {
142-
await createCredential.mutateAsync({
145+
const created = await createCredential.mutateAsync({
143146
workspaceId,
144147
type: 'service_account',
145148
providerId: descriptor.providerId,
146149
...secretFields,
147150
displayName: displayName.trim() || undefined,
148151
description: description.trim() || undefined,
149152
})
153+
onCreated?.(created.credential.id)
150154
}
151155
onOpenChange(false)
152156
} catch (err: unknown) {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/credential-selector.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -432,16 +432,17 @@ export function CredentialSelector({
432432
{showServiceAccountModal && serviceAccountService?.serviceAccountProviderId && (
433433
<ConnectServiceAccountModal
434434
open={showServiceAccountModal}
435-
onOpenChange={(open) => {
436-
setShowServiceAccountModal(open)
437-
if (!open) refetchCredentials()
438-
}}
435+
onOpenChange={setShowServiceAccountModal}
439436
workspaceId={workspaceId}
440437
serviceAccountProviderId={
441438
serviceAccountService.serviceAccountProviderId as ServiceAccountProviderId
442439
}
443440
serviceName={serviceAccountService.name}
444441
serviceIcon={serviceAccountService.icon}
442+
onCreated={(newCredentialId) => {
443+
setStoreValue(newCredentialId)
444+
refetchCredentials()
445+
}}
445446
/>
446447
)}
447448
</div>

apps/sim/blocks/blocks/managed_agent.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,10 @@ export const ManagedAgentBlock: BlockConfig = {
7575
{
7676
id: 'vaults',
7777
title: 'Credential vaults',
78-
type: 'combobox',
78+
type: 'dropdown',
7979
required: false,
8080
placeholder: 'Optional — pick zero or more OAuth vaults',
81-
commandSearchable: true,
81+
searchable: true,
8282
multiSelect: true,
8383
options: [],
8484
dependsOn: ['credential'],

apps/sim/lib/managed-agents/run-session.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,23 @@ describe('runManagedAgentSession', () => {
121121
expect(mocks.sleep).toHaveBeenCalled() // backed off while running
122122
})
123123

124+
it('does not complete on idle status while a requires_action is outstanding', async () => {
125+
// Stream 1: text then a requires_action idle (pending), then closes.
126+
// Reconnect: nothing new, status idle — but must NOT complete (pending).
127+
// Stream 2: end_turn → complete.
128+
scriptStreamBatches([
129+
[msg('e1', 'partial'), idle('r1', 'requires_action')],
130+
[idle('e2', 'end_turn')],
131+
])
132+
mocks.getSession.mockResolvedValue({ status: 'idle' })
133+
134+
const result = await runManagedAgentSession({ ...BASE })
135+
136+
expect(result.ok).toBe(true)
137+
expect(result.content).toBe('partial')
138+
expect(mocks.openSessionStream).toHaveBeenCalledTimes(2) // reopened rather than completing early
139+
})
140+
124141
it('does not complete on a fresh idle before the agent has started', async () => {
125142
// Stream closes immediately with nothing; catch-up empty; status idle but no
126143
// activity yet → must NOT complete. Then it starts and finishes.

apps/sim/lib/managed-agents/run-session.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ export async function runManagedAgentSession(
121121
const startedAt = Date.now()
122122
let backoffMs = RECONNECT_BACKOFF_START_MS
123123
let sawActivity = false
124+
// A `requires_action` idle is a pending pause (waiting on a tool result), not
125+
// a finished turn. Tracking it prevents the quiet-status path from reporting
126+
// an in-progress session complete; it clears once the session resumes.
127+
let requiresActionOutstanding = false
124128
let terminal: Terminal | null = null
125129

126130
const process = async (event: AnthropicSessionEvent): Promise<boolean> => {
@@ -132,6 +136,12 @@ export async function runManagedAgentSession(
132136
) {
133137
sawActivity = true
134138
}
139+
if (event.type === 'session.status_running' || event.type === 'agent.message') {
140+
requiresActionOutstanding = false
141+
}
142+
if (event.type === 'session.status_idle' && event.stop_reason?.type === 'requires_action') {
143+
requiresActionOutstanding = true
144+
}
135145
const outcome = await handleEvent({ event, assistantText, apiKey, sessionId, signal })
136146
if (outcome) {
137147
terminal = outcome
@@ -190,7 +200,7 @@ export async function runManagedAgentSession(
190200
terminal = { status: 'error', reason: 'Session terminated.' }
191201
break
192202
}
193-
if (snapshot?.status === 'idle' && sawActivity) {
203+
if (snapshot?.status === 'idle' && sawActivity && !requiresActionOutstanding) {
194204
terminal = { status: 'complete' }
195205
break
196206
}

0 commit comments

Comments
 (0)