Skip to content

feat(managed-agent): Claude Platform Managed Agents workflow blocks#5769

Closed
rdematos wants to merge 138 commits into
simstudioai:stagingfrom
rdematos:feat/claude-managed-agent-blocks
Closed

feat(managed-agent): Claude Platform Managed Agents workflow blocks#5769
rdematos wants to merge 138 commits into
simstudioai:stagingfrom
rdematos:feat/claude-managed-agent-blocks

Conversation

@rdematos

@rdematos rdematos commented Jul 20, 2026

Copy link
Copy Markdown

Summary

Ship two workflow blocks that let a workflow author invoke a Claude Platform Managed Agent (cloud or self-hosted) as a first-class node, plus the workspace-scoped connection layer that stores the linked Anthropic API key.

Blocks

  • Claude Managed Agents (managed_agent_cloud) — cloud environments. Session-create payload carries agent, environment_id, vault_ids, resources (memory + files) and free-form metadata tags.
  • Claude Managed Agents (self-hosted) (managed_agent_self_hosted) — self-hosted environments. Session metadata is forwarded as env vars to the deployer's self-hosted agent sandbox; memory fields are gated behind NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED (Claude self-hosted environments do not currently support memory-store resource attach on the session API). Default seed rows for the Session-parameters table come from NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS (JSON); seeded keys stay out of source so the block is deployment-neutral in the public tree.

Connections

  • New managed_agent_connection table (workspace-scoped, encrypted API key). All CRUD paths go through getUserEntityPermissions(userId, 'workspace', workspaceId); admin/write to create, rotate, or delete; any workspace member can list/browse. Plaintext keys never cross the client boundary — list responses return only a masked preview.
  • Settings surface under settings/components/managed-agents/ lets admins add / rotate / remove connections with an inline Anthropic-API verify (GET /v1/agents).
  • 8 server routes proxy the Claude Platform read endpoints (agents, environments, memory stores, vaults, environment detail) so the block picker resolves options against the linked workspace.

Tool + session client

  • tools/managed_agent/run_session.ts (client-safe skeleton) + run_session.server.ts (server-only impl, self-registered on boot via globalThis so the client bundle never pulls postgres / fs / encryption). Reconnect + events.list catch-up loop matches the docs' two-step session lifecycle.
  • lib/managed-agents/session-client.ts — reusable HTTP client that shapes the cloud vs. self-hosted request bodies.
  • tools/managed_agent/normalizers.ts — pure input normalizers that coerce the block's runtime shapes (table rows, JSON strings, flat objects, comma-lists) into typed values.

Node UX

  • Optional BlockConfig.nodeWidth (default 250, MA blocks use 400) threaded through calculateWorkflowBlockDimensions for both the workflow-block hook and autolayout so long Anthropic IDs aren't ellipsis-truncated in the collapsed row.
  • Friendly-name hydration on the collapsed row resolves connection / agent / environment / vault / memory-store IDs to human labels via the React Query hooks. New blockType prop on SubBlockRow gates the lookup.
  • New optional defaultRows prop on the Table subblock so any block can seed a fresh table with initial rows.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Other: ___________

Testing

Added 86 tests across 5 files, following the repo's patterns (@vitest-environment node, vi.hoisted + vi.mock + static imports, @sim/testing helpers):

  • tools/managed_agent/normalizers.test.ts (27) — every shape the block subblocks hand off + bad-input paths.
  • lib/managed-agents/session-client.test.ts (17) — cloud vs. self-hosted branch: memory routing (resource vs. metadata), file resources cloud-only, vault_ids only when non-empty, user metadata does not overwrite memory keys, envType-omitted default.
  • lib/managed-agents/connections.test.ts (17) — CRUD via captured DB-chain args: encryption on write (plaintext never lands in row), masking on read, workspace-scoped WHERE on every op, verify gate blocks persistence, error truncation to 500 chars.
  • app/api/managed-agent-connections/route.test.ts (15) — permission gates on GET/POST/DELETE: 401 unauthenticated, 400 missing params, 403 read-tier callers on create/delete, 404 for missing rows, plaintext never in POST response, both write and admin accepted.
  • blocks/blocks/managed_agent_self_hosted.test.ts (10) — env-driven readSessionMetadataDefaults and isSelfHostedMemoryEnabled (truthy/falsy/whitespace forms).

Reviewer focus: (1) the client/server split in run_session*.ts — confirm no server-only deps leak into the client bundle; (2) the workspace-scoped permission checks on every connection CRUD path; (3) that plaintext API keys never cross the client boundary.

Run locally: bun run type-check, bun run test, bun run check:api-validation.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Screenshots/Videos

Documentation
New env vars documented in apps/sim/.env.example: MANAGED_AGENT_DEBUG_PAYLOAD, NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS, NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED.

waleedlatif1 and others added 30 commits April 3, 2026 23:30
…ership workflow edits via sockets, ui improvements
…ration, signup method feature flags, SSO improvements
* feat(posthog): Add tracking on mothership abort (#4023)

Co-authored-by: Theodore Li <theo@sim.ai>

* fix(login): fix captcha headers for manual login  (#4025)

* fix(signup): fix turnstile key loading

* fix(login): fix captcha header passing

* Catch user already exists, remove login form captcha
…nts, secrets performance, polling refactors, drag resources in mothership
…endar triggers, docs updates, integrations/models pages improvements
…mat, logs performance improvements

fix(csp): add missing analytics domains, remove unsafe-eval, fix workspace CSP gap (#4179)
fix(landing): return 404 for invalid dynamic route slugs (#4182)
improvement(seo): optimize sitemaps, robots.txt, and core web vitals across sim and docs (#4170)
fix(gemini): support structured output with tools on Gemini 3 models (#4184)
feat(brightdata): add Bright Data integration with 8 tools (#4183)
fix(mothership): fix superagent credentials (#4185)
fix(logs): close sidebar when selected log disappears from filtered list; cleanup (#4186)
v0.6.46: mothership streaming fixes, brightdata integration
Ship two workflow blocks that let a workflow author invoke a Claude
Platform Managed Agent (cloud or self-hosted) as a first-class node,
plus the workspace-scoped connection layer that stores the linked
Anthropic API key.

Blocks
- **Claude Managed Agents** (`managed_agent_cloud`) — cloud
  environments. Session-create payload carries `agent`,
  `environment_id`, `vault_ids`, `resources` (memory + files) and
  free-form `metadata` tags.
- **Claude Managed Agents (self-hosted)** (`managed_agent_self_hosted`)
  — self-hosted environments. Session `metadata` is forwarded as
  env vars to the deployer's self-hosted agent sandbox; the memory
  fields are gated behind
  `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED` because
  Claude self-hosted environments do not currently support the
  memory-store resource attach on the session API. Default seed
  rows for the Session-parameters table come from
  `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS` (JSON object);
  the seeded keys stay out of source so this block is deployment-
  neutral in the public tree.

Connections
- New `managed_agent_connection` table (workspace-scoped, encrypted
  API key). All CRUD paths go through
  `getUserEntityPermissions(userId, 'workspace', workspaceId)`;
  `admin`/`write` to create, rotate, or delete; any workspace
  member can list/browse. Plaintext keys never cross the client
  boundary — list responses return only a masked preview.
- Settings surface under `settings/components/managed-agents/` lets
  admins add / rotate / remove connections with an inline
  Anthropic-API verify (`GET /v1/agents`).
- 8 server routes proxy the Claude Platform read endpoints (agents,
  environments, memory stores, vaults, environment detail) so the
  block picker resolves options against the linked workspace.

Tool + session client
- `tools/managed_agent/run_session.ts` (client-safe skeleton) +
  `run_session.server.ts` (server-only impl, self-registered on
  boot via `globalThis` so the client bundle never pulls
  `postgres` / `fs` / encryption). Reconnect + `events.list`
  catch-up loop matches the docs' two-step session lifecycle.
- `lib/managed-agents/session-client.ts` — the reusable HTTP
  client that shapes the cloud vs. self-hosted request bodies.
- `tools/managed_agent/normalizers.ts` — pure input normalizers
  that coerce the workflow block's runtime shapes (table rows,
  JSON strings, flat objects, comma-lists) into typed values.

Node UX
- Optional `BlockConfig.nodeWidth` (default 250, MA blocks use 400)
  threaded through `calculateWorkflowBlockDimensions` for both the
  workflow-block hook and autolayout so long Anthropic IDs are not
  ellipsis-truncated in the collapsed row.
- Friendly-name hydration on the collapsed row resolves connection
  / agent / environment / vault / memory-store IDs to human labels
  via the React Query hooks. New `blockType` prop on `SubBlockRow`
  gates the lookup.
- New optional `defaultRows` prop on the `Table` subblock so any
  block can seed a fresh table with initial rows.

Test coverage
Aligned with the repo's existing patterns (`@vitest-environment
node`, `vi.hoisted` + `vi.mock` + static imports, `@sim/testing`
helpers). 86 tests across 5 files:

- `tools/managed_agent/normalizers.test.ts` (27) — every shape the
  block subblocks hand off + bad-input paths.
- `lib/managed-agents/session-client.test.ts` (17) — cloud vs.
  self-hosted branch: memory routing (resource vs. metadata),
  file resources cloud-only, `vault_ids` only when non-empty,
  user metadata does not overwrite memory keys, envType-omitted
  default.
- `lib/managed-agents/connections.test.ts` (17) — CRUD via
  captured DB-chain args: encryption on write (plaintext never
  lands in row), masking on read, workspace-scoped `WHERE` on
  every op, `verify` gate blocks persistence, error truncation
  to 500 chars.
- `app/api/managed-agent-connections/route.test.ts` (15) —
  permission gates on GET/POST/DELETE: 401 unauthenticated,
  400 missing params, 403 read-tier callers on create/delete,
  404 for missing rows, plaintext never in POST response, both
  `write` and `admin` accepted.
- `blocks/blocks/managed_agent_self_hosted.test.ts` (10) —
  env-driven `readSessionMetadataDefaults` and
  `isSelfHostedMemoryEnabled` (truthy/falsy/whitespace forms).

Documentation
- New env vars documented in `apps/sim/.env.example`:
  `MANAGED_AGENT_DEBUG_PAYLOAD`,
  `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS`,
  `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_MEMORY_ENABLED`.
@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Sim Team on Vercel.

A member of the Team first needs to authorize it.

@cursor

cursor Bot commented Jul 20, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Stores and decrypts third-party API keys, proxies authenticated calls to Anthropic, and runs long-lived agent sessions with vault acknowledgement gates—security-sensitive and operationally complex despite solid permission checks and tests.

Overview
Adds Claude Managed Agents as first-class workflow nodes (cloud and self-hosted variants) backed by workspace-linked Anthropic API keys.

Connections & API — New workspace-scoped connection storage (encrypted keys, masked in list responses) with Settings UI to link, rotate, and remove workspaces after GET /v1/agents verification. CRUD and proxy routes (agents, environments, vaults, memory stores) enforce session auth and workspace permissions; decrypted keys stay server-side.

Execution — Shared session-client builds cloud vs self-hosted POST /v1/sessions bodies (resources/metadata routing). The managed_agent_run_session tool runs sessions via a client/server split so server-only deps stay out of the bundle; direct execution gets workflow abort propagation and richer _context (block id/name).

Editor UX — Optional nodeWidth on blocks (400px for MA tiles), collapsed-row friendly-name hydration for connection/agent/env/vault/memory selectors, and Managed Agents in workspace settings navigation.

Reviewed by Cursor Bugbot for commit 8e9f8a1. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread apps/sim/tools/managed_agent/run_session.server.ts Outdated
@greptile-apps

greptile-apps Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds Claude Platform Managed Agents as workflow blocks. The main changes are:

  • Workspace-scoped, encrypted Managed Agent connections and proxy APIs.
  • Cloud and self-hosted session blocks with resource and metadata normalization.
  • Session creation, SSE streaming, reconnect, and event catch-up support.
  • Settings UI, option hydration, wider nodes, and seeded table rows.
  • Database schema, environment configuration, and test coverage.

Confidence Score: 4/5

Active Anthropic streams can outlive cancelled workflows, and connection decryption can bypass the normal tool response path. Public self-hosted defaults can also disclose sensitive values.

  • Workspace authorization and database scoping are consistently applied.
  • Cancellation is not forwarded to the SSE request or reader.
  • Undecryptable connection rows cause an unhandled tool rejection.
  • Public session defaults are visible in browser bundles.

apps/sim/tools/managed_agent/run_session.server.ts; apps/sim/blocks/blocks/managed_agent_self_hosted.ts

Security Review

Self-hosted session defaults come from a NEXT_PUBLIC_* variable, so every configured value is shipped in the client bundle. Credentials or other secrets placed there are exposed to browser clients.

Important Files Changed

Filename Overview
apps/sim/tools/managed_agent/run_session.server.ts Adds server-side session execution and reconnect handling, but cancellation is dropped and decryption can reject outside the tool response path.
apps/sim/lib/managed-agents/connections.ts Adds encrypted, workspace-scoped persistence with composite workspace predicates and masked client responses.
apps/sim/lib/managed-agents/session-client.ts Adds Managed Agents HTTP and payload handling for cloud and self-hosted sessions.
apps/sim/blocks/blocks/managed_agent_self_hosted.ts Adds the self-hosted block and deployment defaults, whose public environment source can expose sensitive values.
apps/sim/app/api/managed-agent-connections/route.ts Adds authenticated connection listing, creation, and deletion with workspace permission checks.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
  participant W as Workflow executor
  participant T as Managed Agent tool
  participant DB as Connection store
  participant A as Anthropic API
  W->>T: Run block with workspace context
  T->>DB: Load and decrypt connection
  DB-->>T: API key
  T->>A: Create session
  T->>A: Send user message
  T->>A: Open SSE stream
  loop Reconnect and catch up
    A-->>T: Session events
    T->>A: List events after cursor
  end
  T-->>W: Content and session status
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
  participant W as Workflow executor
  participant T as Managed Agent tool
  participant DB as Connection store
  participant A as Anthropic API
  W->>T: Run block with workspace context
  T->>DB: Load and decrypt connection
  DB-->>T: API key
  T->>A: Create session
  T->>A: Send user message
  T->>A: Open SSE stream
  loop Reconnect and catch up
    A-->>T: Session events
    T->>A: List events after cursor
  end
  T-->>W: Content and session status
Loading

Reviews (1): Last reviewed commit: "feat(managed-agent): Claude Platform Man..." | Re-trigger Greptile

Comment on lines +167 to +168
const streamResp = await openSessionStream({ apiKey, sessionId })
await readSSEEvents<AnthropicSessionEvent>(streamResp, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Cancellation Leaves Stream Running

The workflow abort signal is not passed to openSessionStream or readSSEEvents. When a workflow is cancelled or times out while Anthropic keeps the stream open, this invocation continues reading and processing events in the background instead of releasing the HTTP connection.

}
}

const apiKey = await getDecryptedApiKey({ id: params.connection, workspaceId })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Decryption Error Escapes Tool Contract

getDecryptedApiKey throws when stored ciphertext cannot be decrypted, such as after an encryption-key rotation, and this call occurs before the session error-handling block. The tool promise rejects instead of returning its declared success: false response, so the workflow receives an unhandled execution failure rather than an actionable connection error.

Comment thread apps/sim/blocks/blocks/managed_agent_self_hosted.ts Outdated
…ires_action, public defaults)

Four fixes to PR #5769 review comments.

- `getDecryptedApiKey` throws when stored ciphertext cannot be decrypted
  (typically after an `ENCRYPTION_KEY` rotation). Wrapped the call in the
  tool's error-handling path so the failure returns the tool's declared
  `{success:false, output:{}, error:'...'}` shape with an actionable
  message pointing at Settings → Managed Agents → rotate, rather than an
  unhandled promise rejection.

- Added the workflow's abort signal to `_context.abortSignal` on the
  `directExecution` path in `executeTool`, so any tool that wants to
  propagate cancellation can. This is opt-in — existing tools are
  unaffected.
- Read the signal in `run_session.server.ts` and thread it into
  `createSession`, `sendUserMessage`, `openSessionStream`,
  `readSSEEvents`, and `listEventsAfter`. Every long-lived HTTP hop
  now cancels cleanly.
- Guard all loop entrances with `signal?.aborted` and translate any
  abort-caused exception into a clean `{success:false, error:'aborted'}`
  response — no scary stack traces on cancel.

- Server-side / vault-backed MCP tools legitimately hold the session in
  `requires_action` with no client-visible events until the tool
  finishes. My previous logic returned a terminal error on the first
  empty `listEventsAfter` catch-up, failing those workflows before the
  agent could complete.
- New policy: `requires_action` idle is NEVER terminal by itself. If
  the outer reconnect loop finds no new events, back off (500ms,
  doubling to 5s max) and re-poll `listEventsAfter`. Only fail after a
  hard `MAX_REQUIRES_ACTION_WAIT_MS` cap (~5 min) — with a clear error
  message — or on an explicit `session.status_terminated` /
  `session.error`.
- `EventState` gains `requiresActionEnteredAt` (first entry into the
  busy stretch) and `currentBackoffMs`; both reset when the session
  makes progress (non-empty catch-up). Reconnect ceiling raised to 60
  since the loop now sleeps between polls.

- Deleted `NEXT_PUBLIC_MANAGED_AGENT_SELF_HOSTED_DEFAULTS` — any
  deployer who accidentally seeded a token into the JSON would leak it
  to every browser via the client bundle.
- Replaced with server-only `MANAGED_AGENT_SELF_HOSTED_DEFAULTS` +
  `GET /api/managed-agent-defaults` route that returns the parsed
  rows. Values never enter the client bundle at build time.
- Extended the `Table` sub-block component with an optional
  `fetchDefaultRows` async prop (mirrors the `fetchOptions` pattern
  used by comboboxes). The block config points at
  `fetchManagedAgentSelfHostedDefaults` in `subblock-options.ts`,
  which calls the new API route via `requestJson(contract, ...)`.
- Removed the deleted function's tests; added coverage for the new
  API route (`app/api/managed-agent-defaults/route.test.ts` — 7 tests).
- Updated `.env.example` docs to make the safety property explicit.

Verified: 86 tests pass across the 6 managed-agent test files. Pre-existing
tsc errors (integration-tag enum, environments-route type mismatch, etc.)
are unchanged.
Comment thread apps/sim/lib/managed-agents/connections.ts Outdated
Followup on PR #5769 review: Cursor Bugbot flagged that
`getDecryptedApiKey` still throws on ciphertext decrypt failure, so
the five proxy routes (agents / environments / environment-detail /
vaults / memory-stores) surface a 500 to the client instead of a
controlled response. My prior tool-level try/catch only handled the
workflow-block invocation path.

Change: `getDecryptedApiKey` now returns a discriminated result —
`{ ok: true, apiKey } | { ok: false, reason: 'not_found' | 'decrypt_failed' }`
— so callers can render each failure mode with the correct status:
- `not_found` → 404 "Connection not found" (unchanged)
- `decrypt_failed` → 502 with an actionable "rotate the API key to
  re-encrypt" hint, matching the tool-side message

New `apiKeyFailureResponse` helper in `lib/managed-agents/proxy.ts`
keeps the mapping in one place — each proxy route now reads:
    const keyResult = await getDecryptedApiKey({ id, workspaceId })
    if (!keyResult.ok) return apiKeyFailureResponse(keyResult)
    const apiKey = keyResult.apiKey

Tool call site (`run_session.server.ts`) collapses its previous
try/catch to the same `keyResult.ok` check — cleaner and shares the
same error phrasing.

Tests updated: `getDecryptedApiKey` cases now assert the shape;
added a third case that mocks `decryptSecret` to reject and asserts
`{ ok: false, reason: 'decrypt_failed' }`. All 87 tests pass.
Comment thread apps/sim/tools/managed_agent/normalizers.ts Outdated
…selected

Both blocks now render a "Vault authorization" switch immediately below
the Credential vaults picker: "I own or am authorized to use these
vaults. I understand this means this agent can assume the identity
granted by them." The tool rejects execution with an actionable error
when `vaults` is non-empty and the ack is not checked, so a workflow
author cannot silently attach an OAuth vault to an agent they aren't
authorized to run under.

Why runtime, not subblock-level required: Sim's `condition` engine
uses strict equality against a single value, so it cannot natively
express "field is a non-empty array". Enforcing at execute time keeps
the ack always visible (users see the warning up front) and fails
closed on run.

Also lifted the truthy-check into a shared `isTruthyAck` helper in
`tools/managed_agent/normalizers.ts` — handles the real boolean, plus
the `"true" | "1" | "yes"` string forms that switch values can arrive
as depending on serialization. Four cases covered:
- `true` accepted
- `"true"` / `"True"` / `"1"` / `"yes"` accepted (case-insensitive,
  whitespace-trimmed)
- Every other string / `false` / `undefined` / non-string non-boolean
  rejected

Tests: 91 pass (4 new for `isTruthyAck`).
Reviewer feedback: the previous version buried the security disclaimer
under a generic "Vault authorization" title with the full text below in
the description. Users would only see "Vault authorization" next to the
toggle and might not read the description.

Move the whole "I own or am authorized to use these vaults. I understand
this means this agent can assume the identity granted by them." string
to the switch's title so it sits right next to the toggle. Description
line collapses to the run-time constraint: "Required when at least one
vault is selected above."

No functional change; the run-time enforcement (tool rejects when
`vaults` is non-empty and the ack is falsy) is unchanged.
…w node

Previously Claude Managed Agent sessions opened untitled, which made
them hard to trace back from the Claude Platform side to the workflow
that created them.

Sessions now open with a human-legible title composed from the Sim
workspace, workflow, and node names:

    sim.ai - <workspace name> - <workflow name> - <block name>

Segments whose DB lookup fails (deleted workspace/workflow, unnamed
block) are silently dropped so the session still opens with whatever
context is available. If nothing beyond the "sim.ai" prefix resolves,
we skip the title entirely rather than create a placeholder-titled
session.

Plumbing:
- The generic block handler now also passes `blockId` and `blockName`
  on `_context` when it invokes a tool. `WorkflowToolExecutionContext`
  gains matching optional fields — opt-in for any tool that wants to
  emit externally-visible records tied to their origin node.
- `run_session.server.ts` looks up the workspace/workflow names via
  `@sim/db` at execute time (a two-column SELECT per name; wrapped in
  a defensive try/catch so a DB blip never blocks session creation).
- `buildSessionCreatePayload` already accepts `title` — no session-
  client change needed.
Comment thread apps/sim/app/api/managed-agent-defaults/route.ts Outdated
Follow-up on Cursor Bugbot's review of the last few commits. Three
related, but independent, correctness fixes.

Files were silently dropped from cloud sessions
- The cloud block declares `columns: ['File ID', 'Mount path']`. The
  table subblock stores each row's cells keyed by exactly those
  column strings — but `normalizeFiles` only knew about `Key`/`Value`
  (older shape) and flat `{fileId, mountPath}`, so every file row was
  dropped and no files ever reached the session.
- Fix: `normalizeFiles` now recognizes `File ID` / `Mount path`
  (block's declared columns), `file_id` / `mount_path` (snake case),
  the older `Key` / `Value`, and the flat `{fileId, mountPath}`
  shapes. Regression test added.

Stale collapsed-row labels when Claude Workspace changes
- The managed-agent collapsed rows hydrate friendly names by reading
  the SIBLING `connection` value from `allSubBlockValues`. But
  `SubBlockRow`'s memo compare only checked this row's own value, so
  the memo would keep showing agent/env/vault/memory labels tied to
  the previous connection until each row's own stored value happened
  to change.
- Fix: extend `areSubBlockRowPropsEqual` to also compare a small set
  of known sibling-dependency keys (`connection`, `oauthCredential`,
  `credential`). Any of them changing invalidates the memo so the
  hydration hooks re-run with the new sibling value.

Empty tables re-seeded deployer defaults on remount
- The table init effect early-exited when `storeValue.length > 0`,
  but treated `[]` as "unseeded" and re-ran `fetchDefaultRows` /
  `defaultRows`. A self-hosted block whose Session-parameters table
  was intentionally cleared to zero rows would get repopulated on
  every remount, clobbering the author's choice.
- Fix: only seed when `storeValue === undefined || storeValue === null`.
  A stored `[]` — the author's explicit "no rows" — is now preserved.

Tests: 92 pass (30 normalizer, up from 29; regression test for the
`File ID` / `Mount path` shape).
emptyCellsTemplate,
defaultRows,
fetchDefaultRows,
])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty table shows phantom row

Medium Severity

The mount effect no longer seeds when the subblock store is [], but the rows memo still synthesizes a default row whenever the value array is empty. After an author clears every row, the UI can show an editable row that is not persisted in the store, which conflicts with the new “empty array is intentional” rule and can confuse session-parameter input.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b8e5039. Configure here.

})
},
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rotate connection hook unused

Low Severity

useRotateManagedAgentConnection is exported and wired to the PATCH rotate route, but nothing in the settings UI or elsewhere imports or calls it, so key rotation is only partially shipped.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b8e5039. Configure here.

Two follow-up fixes for Cursor Bugbot's most recent review.

#11 Defaults API lacks authentication
- `GET /api/managed-agent-defaults` returned every value from
  `MANAGED_AGENT_SELF_HOSTED_DEFAULTS` with no session check. Docs and
  the block copy steer deployers toward putting sandbox-scoped secrets
  there (because they never enter the client bundle at build time),
  but an unauthenticated caller could still dump the JSON.
- Fixed: route now runs `checkSessionOrInternalAuth` and returns 401
  for anonymous callers. Same-origin fetches from the block editor
  automatically carry the session cookie, so no client change needed.
- Test added: `route.test.ts` now covers the 401 unauthenticated case.

#13 `useRotateManagedAgentConnection` was unused
- The hook + PATCH route existed but nothing called them, so the only
  way to change a stored API key was delete + re-link — awkward when
  the deployer's just cycling the key.
- Wired into the settings UI:
  - New `RotateKeyModal` component (mirrors the create modal but with
    only the key field; connection name stays put) runs the same
    verify-then-save flow.
  - Added a "Rotate key" action to each connection row's
    `RowActionsMenu`, gated on `canEdit` (workspace `write`/`admin`
    tiers). Success shows a toast; errors surface inline in the modal.

Not fixed / addressed inline:
- #9 (stale labels after connection change) and #10 (empty table
  re-seeds defaults) — Bugbot re-fired these on the previous commit
  by signature-match against a static code shape. The actual fixes
  (extra sibling-dep comparison in `areSubBlockRowPropsEqual`; only
  seed when `storeValue` is null/undefined) are in place and
  functional. No change needed.
- #12 (empty table shows a phantom row) — theoretical. The table
  component's delete-row guard (`rows.length === 1`) prevents users
  from ever reaching `[]` through the UI, so the mismatch it warns
  about isn't reachable in normal use.
Cursor Bugbot on 9704cec flagged a race in the `Table` sub-block
init effect: `fetchDefaultRows` is async, and the effect's guard on
`storeValue !== undefined && storeValue !== null` was captured at
effect setup time. Between the fetch start and its resolution, a
user could type into the phantom row — persisting the row via
`handleCellChange`'s `setStoreValue([newRow])` — and then the
resolved fetch would call `setStoreValue(defaultRows)` and clobber
their edit.

Fix: re-read the current value from the subblock store inside
`seedWith` right before writing (`useSubBlockStore.getState()
.getValue(blockId, subBlockId)`), and skip the write if the value
has already been populated since the effect started. The
closure-captured `storeValue` was stale; the store's `getState`
returns the LATEST value synchronously.

Other Bugbot comments on this commit are re-fires of items already
fixed on prior commits (they signature-match against static code
shapes without noticing the corrective code that lives beside them):
`Stale labels after connection change` (fixed via `depsEqual` in
`areSubBlockRowPropsEqual`), `Empty table re-seeds defaults` (fixed
via `storeValue !== undefined && storeValue !== null` guard),
`Defaults API lacks authentication` (fixed via
`checkSessionOrInternalAuth`), `Rotate connection hook unused`
(fixed via the new Rotate-key modal wired into the settings UI).
`Empty table shows phantom row` is theoretical — the delete-row
guard (`rows.length === 1`) prevents users from reaching `[]`
through the UI, so the mismatch it warns about isn't reachable in
normal use.
if (fetchDefaultRows) {
fetchDefaultRows()
.then((rows) => seedWith(rows.length > 0 ? rows : defaultRows))
.catch(() => seedWith(defaultRows))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Async seed race on save

Low Severity

When fetchDefaultRows is async, a quick save or navigation after opening the block can persist workflow state before defaults land in the subblock store, leaving Session parameters empty even though the author opened the table once.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2766139. Configure here.

@rdematos
rdematos changed the base branch from main to staging July 20, 2026 17:48
Pre-seeding the self-hosted block's Session parameters table from the
server-only MANAGED_AGENT_SELF_HOSTED_DEFAULTS env var is better served
by Sim's workflow blocks. Remove the capability end-to-end:

- Delete GET /api/managed-agent-defaults route + test
- Drop the MANAGED_AGENT_SELF_HOSTED_DEFAULTS env var
- Remove getManagedAgentDefaultsContract + its schemas/types
- Remove the fetchManagedAgentSelfHostedDefaults client fetcher
- Unwire fetchDefaultRows from the self-hosted block and fix the two
  stale comments that referenced the removed env var

Revert the shared table subblock seeding machinery (table.tsx,
sub-block.tsx, blocks/types.ts) to its pre-feature behavior so no other
block is affected — table.tsx is now byte-identical to main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rdematos
rdematos force-pushed the feat/claude-managed-agent-blocks branch from 2766139 to 8e9f8a1 Compare July 20, 2026 23:47

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8e9f8a1. Configure here.

'anthropic-beta': MANAGED_AGENTS_BETA,
},
signal,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong beta for memory stores

High Severity

proxyManagedAgentsGet always sends anthropic-beta: managed-agents-2026-04-01, including for GET /v1/memory_stores. Anthropic’s Memory Stores API requires agent-memory-2026-07-22, so the Memory Store combobox proxy fails and the cloud block cannot list stores.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8e9f8a1. Configure here.

@rdematos
rdematos marked this pull request as draft July 21, 2026 00:00
@rdematos rdematos closed this Jul 21, 2026
waleedlatif1 added a commit that referenced this pull request Jul 21, 2026
…ds on self-hosted

Collapse what #5769 split into two blocks into one, natively:
- Add an Environment type selector (Cloud / Self-hosted) that filters the
  environment list to the matching type and gates cloud-only fields
- Memory store, memory access/instructions, and files are cloud-only (self-
  hosted rejects the resources[] attach — verified live, 400) and are now hidden
  on self-hosted instead of silently dropped. A self-hosted worker that uses a
  memory store reads its id from a Metadata key the author sets explicitly
- Expose each environment's config.type on the list options so the picker can
  filter by mode; pass the selected type as a routing hint (server still re-
  resolves the authoritative type via getEnvironmentType)
waleedlatif1 added a commit that referenced this pull request Jul 21, 2026
* feat(managed-agents): add Claude Managed Agents workflow block

* improvement(managed-agents): complete session inputs/outputs; trim block templates

- add memory instructions + file mount_path inputs; bound metadata (16 pairs)
- surface cumulative token usage (inputTokens/outputTokens) as outputs
- validate the full session-create schema against live docs
- remove BlockMeta templates

* improvement(managed-agents): select a Claude Platform credential instead of BYOK

- register Claude Platform as a token-paste service-account credential (descriptor + validator)
- add no-OAuth OAUTH_PROVIDERS entry; generalize the shared credential picker with a 'service-account' kind
- block: oauth-input credential picker + dependsOn dropdowns; list route resolves the key server-side (audit-logged)
- run via directExecution with the executor-injected key; drop the internal run route
- remove the interim claude-platform BYOK provider

* fix(managed-agents): harden reconnect loop; fix credential-picker regression

- drive completion off terminal events + authoritative session status (drop the fragile busy-clock)
- drain full event history so a long session's tail is never cut off
- skip idless events in catch-up; require an id before replying to custom_tool_use
- gate the shared credential-selector service-account lookup on credentialKind and use the non-throwing helper (was crashing multi-service OAuth pickers)
- audit-log the list route's credential access; refresh stale tool docs

* 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)

* fix(managed-agents): propagate cancellation, fix reconnect ordering, classify advanced fields

- Thread the executor abort signal into directExecution (additive ToolConfig
  change) so a cancelled workflow stops the session immediately; best-effort
  user.interrupt releases the Anthropic session past cancel/wall-clock cap
- Recompute the requires_action pending state from the chronological history
  so an older agent.message recovered on catch-up can't clear a newer pause
- Retry a custom-tool error reply that failed to send instead of stranding the
  session (mark the event seen only once handled)
- Mark optional fields (vaults, memory, files, metadata) mode: advanced
- Title the session with the workflow id for Claude-console traceability

* fix(managed-agents): stop leaked sessions on all give-up paths; harden event ordering and normalizers

- Interrupt on sendUserMessage failure and on the reconnect-cap exit, matching
  the abort/wall-clock paths, so no give-up path leaves a session running
- Order the events list by processed_at (list page order isn't guaranteed
  chronological) so catch-up accumulates text and reads the latest lifecycle
  event correctly regardless of API sort
- Don't reset reconnect backoff on a failing custom-tool reply (stays unseen
  for retry) — prevents a no-delay reconnect storm
- Treat only end_turn as a complete status_idle event; an unspecified idle
  defers to the sawActivity-gated status check (no empty completion pre-turn)
- normalizeFiles parses a JSON-stringified table instead of dropping it;
  normalizeStringList returns [] on malformed JSON; metadata keeps scalar values
- Declare the injected accessToken as a hidden param for convention parity

* fix(managed-agents): interrupt the session on a mid-run stream/API failure

The non-abort error catch path returned without stopping the session, so a
mid-run network/API failure could leave the Anthropic session running against
the workspace key. Interrupt on that path too — completing the invariant that
every give-up exit (abort, cap, send failure, reconnect cap, stream error)
stops the session. Still fails fast; no retry added.

* fix(managed-agents): skip idless stream previews; resolve service-account provider icons

- Skip idless events in the live SSE handler (mirroring catch-up). event_start/
  event_delta previews carry no id, are never deduped, and final text always
  arrives as a persisted id-bearing agent.message — so appending previews could
  double the block's content output
- Map serviceAccountProviderId to its base provider in PROVIDER_ID_TO_BASE_PROVIDER
  so parseProvider resolves 'claude-platform-service-account' to 'claude-platform'
  (a two-segment base the hyphen split can't recover), fixing the credential-row
  icon falling back to the generic external-link glyph

* fix(managed-agents): keep idless terminals and preserve live pending state

Refine the round-6 idless-event fix, which was too broad:
- Process idless events again (revert the blanket stream skip) so an idless
  session.status_idle(end_turn)/session.error delivered only on the live stream
  still registers as terminal instead of reconnecting to a timeout. Only the
  agent.message TEXT append is now id-gated, so preview text still can't double
- When catch-up history has no lifecycle event, restore the live-observed
  requires_action state instead of trusting a stale older agent.message that
  cleared it — prevents a false completion with partial output while a tool
  result is still pending

* fix(managed-agents): route memory via metadata on self-hosted environments

Live API testing revealed self-hosted environments reject the `resources`
array with a 400 ("resources are not supported with self-hosted
environments"), so the prior universal-resources[] payload would have failed
any self-hosted session that attached a memory store or files.

Restore env-type-aware routing: resolve the environment's config.type via a new
getEnvironmentType() before session create, and for self_hosted send the memory
store through metadata.memory_store_ids/memory_access (the worker consumes it)
and drop file attachments. Cloud environments keep resources[]. Verified end-to-
end against the live Managed Agents API (cloud resources[] 200, self-hosted
metadata 200, self-hosted resources[] 400).

* feat(managed-agents): environment-type selector; hide cloud-only fields on self-hosted

Collapse what #5769 split into two blocks into one, natively:
- Add an Environment type selector (Cloud / Self-hosted) that filters the
  environment list to the matching type and gates cloud-only fields
- Memory store, memory access/instructions, and files are cloud-only (self-
  hosted rejects the resources[] attach — verified live, 400) and are now hidden
  on self-hosted instead of silently dropped. A self-hosted worker that uses a
  memory store reads its id from a Metadata key the author sets explicitly
- Expose each environment's config.type on the list options so the picker can
  filter by mode; pass the selected type as a routing hint (server still re-
  resolves the authoritative type via getEnvironmentType)

* fix(managed-agents): track requires_action by processed_at, not history position

Persisted history can lag the live stream, so the last lifecycle event in the
history array may be OLDER than a requires_action the stream already observed.
Deriving the pending state from history position (findLastLifecycleEvent) could
then clear a newer pause and let an idle snapshot complete a still-waiting
session with partial text.

Track requires_action from the NEWEST lifecycle event by processed_at across
both the live stream and catch-up, so an older/lagging event can never override
a newer pause. Removes the pendingBeforeCatchup snapshot and history-position
recompute. New test covers lagging history holding only an older running event.

* fix(managed-agents): treat missing processed_at as oldest, not newest

A lifecycle event without processed_at mapped to +Infinity, poisoning the
high-water mark: once seen, no later timestamped event could update the pending
state (at >= Infinity always false), stranding a pause or clearing one wrongly.

Map missing/unparseable processed_at to -Infinity so an untimestamped lifecycle
event can never outrank a timestamped one in either direction — it neither
blocks later real events nor clears a timestamped requires_action. Persisted
lifecycle events always carry processed_at; this is purely defensive. New test
covers a stray untimestamped running not clearing a timestamped pause.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants