Skip to content

feat(frontend): parallel-approval hold, reload integrity, and context-driven config sections#5426

Closed
ardaerzin wants to merge 11 commits into
mainfrom
fe-enhance/approval-ui-onbig
Closed

feat(frontend): parallel-approval hold, reload integrity, and context-driven config sections#5426
ardaerzin wants to merge 11 commits into
mainfrom
fe-enhance/approval-ui-onbig

Conversation

@ardaerzin

Copy link
Copy Markdown
Contributor

What & why

Agent-chat HITL approvals had three sharp edges, and the config panel made it hard to see or trust an uncommitted change. This branch fixes both, plus a couple of small UI/hosting follow-ups.

Two of the approval bugs only surfaced with parallel tool calls and on session reload, so they were easy to miss in a single-tool happy path:

  • A turn requesting several tools at once showed one approval dialog; the sibling gates silently stuck and the agent proceeded without them.
  • Reloading a session that had paused on an approval corrupted the restored transcript — the parked turn re-emitted done and the resume re-persisted the user turn, so the reloaded chat dropped tool results or re-asked a prompt.

Changes

Runner — parallel approvals & reload integrity

  • Hold parallel approval gates and resume them together. Instead of pausing on the first gate and dropping siblings, the runner collects all gates raised in a short window (AGENTA_RUNNER_APPROVAL_COLLECT_MS, default 800ms), parks them, and resumes them as one batch — every sibling is answered.
  • Render a deferred sibling as a retry nudge, not an error. On cold replay a tool that was skipped because the turn paused for another approval now reads as "call it again," so the model retries instead of giving up.
  • Don't corrupt a parked+resumed turn's persisted transcript — the paused run no longer double-emits done, and resume no longer re-persists the user turn.

Agent-chat FE — approval friction & restore

  • Reduce approval friction: an "always allow this tool" toggle (per-tool, commit_revision stays gated) and correct batch resolve — arming the toggle and clicking Approve clears the covered sibling gates in one step, instead of stepping 1 of 3 → 1 of 2.
  • Restore a parked+resumed approval correctly on reload — the transcript→messages adapter uses a transcript-global tool map and keeps the paused draft open, so a settled gate restores as resolved and an unresumed one as still-awaiting.

Config sections — see & trust what changed

  • Context-driven config sections: a section with uncommitted changes shows a "what changed" body inline (before → after, with restore), and a missing provider key surfaces the key field inline (the only blocking case that auto-opens a section).
  • Reusable changed-path + focus primitives and shared animation primitives (HeightCollapse, ConfigAccordionSection) underpin the above.
  • Reconciled onto main's single-accordion layout (main's remove-config-view-options removed the tabs/cards view modes).

Misc

  • fix(ui): use the size prop for TriggerDeliveriesDrawer width.
  • chore(hosting): Claude Code harness config in the EE dev compose.

Testing

  • Runner: tsc clean; unit suite 1206/1206 (the lone flake is a pre-existing timing test in relay-watch.test.ts, untouched here, green in isolation). New coverage for parked/resumed approvals and the deferred-sibling nudge.
  • FE: @agenta/entity-ui tsc clean; @agenta/oss tsc at baseline (no new errors); pnpm lint-fix clean. New package unit tests for sectionChanges, toolPermission, formatCommitted, and the transcript-restore adapter.

Notes for reviewers

  • Draft: opened for review; not yet live-verified end-to-end against a running stack.
  • Branches multiple areas (runner / agent-chat / config / ui / hosting) but each commit is scoped to one, so the history reads cleanly commit-by-commit.

When the model fires several tool calls that each need approval, the runner
pauses on one and force-settles the rest with DEFERRED_NOT_EXECUTED. On cold
replay messageTranscript rendered that as `[<tool> error: ...]`, which the
model read as a denial and abandoned the call. Render it instead as the same
"call it again with the same arguments" nudge an approved-but-unexecuted call
gets, so the model re-issues it and its own gate surfaces.
@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jul 21, 2026 10:17am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1b3756c7-314c-413d-ba48-3ce0d16ea3ac

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Runner approval lifecycle

Layer / File(s) Summary
Approval contracts and pause scheduling
services/runner/src/engines/sandbox_agent/...
Approval state now supports multiple parked gates, configurable collection windows, deferred sibling tracking, and debounced pauses.
Multi-gate turn execution and resume
services/runner/src/engines/sandbox_agent/run-turn.ts, services/runner/src/server.ts
Turns record, park, validate, and resume approval batches, including interaction-token handling and deferred sibling routing.
Telemetry and transcript behavior
services/runner/src/tracing/otel.ts, services/runner/src/engines/sandbox_agent/transcript.ts, services/runner/src/responder.ts
Paused stop reasons and settled tool counts are emitted, and deferred tool results render retry instructions.
Runner tests
services/runner/tests/unit/*
Tests cover multi-gate parking, collection timing, resume payloads, deferred siblings, stream events, and transcript rendering.

Transcript replay and approval dock

Layer / File(s) Summary
Cross-turn transcript replay
web/oss/src/components/AgentChatSlice/assets/*
Tool parts are shared across drafts so paused approval requests link to later results without duplication.
Batch approval actions and notices
web/oss/src/components/AgentChatSlice/components/ApprovalDock.tsx, web/oss/src/components/Playground/Components/*
Approval resolution is latched across batches, always-allow actions update configuration, and provider/configuration notices use collapsible presentation.

Agent configuration workflows

Layer / File(s) Summary
Section changes and scoped revert
web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/*, web/packages/agenta-entity-ui/src/drawers/shared/*
Section changes, focused paths, committed values, and immutable revert operations are centralized and exposed through context providers.
Tool permission configuration
web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/toolPermission.ts, PiAutoApproveControl.tsx, *PermissionsControl.tsx
Grantable gates are classified and mapped to per-tool permissions or harness allow-list entries.
Focus-aware configuration panels
web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/*
Model, provider, sandbox, and advanced controls render according to focused or changed paths and expose inline details and revert actions.
Configuration tests
web/packages/agenta-entity-ui/tests/unit/*
Tests cover path classification, revert behavior, committed-value formatting, and permission updates.

Shared UI primitives

Layer / File(s) Summary
Signals and presentation primitives
web/packages/agenta-shared/src/state/*, web/packages/agenta-ui/src/components/*
Shared atoms, collapse behavior, accordion state, pulsing indicators, and related exports support the configuration and notice flows.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

  • Agenta-AI/agenta#5382 — Implements the same multi-gate approval model across runner parking, interaction emission, and resume handling.
  • Agenta-AI/agenta#5041 — Changes the same approval responder, pause controller, and resume gating pipeline.
  • Agenta-AI/agenta#5158 — Extends the Claude ACP approval park/resume flow in the same runner components.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: parallel approvals, reload fixes, and context-driven config sections.
Description check ✅ Passed The description matches the implemented runner, UI, config, and hosting changes in the diff.
Docstring Coverage ✅ Passed Docstring coverage is 67.69% which is sufficient. The required threshold is 60.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fe-enhance/approval-ui-onbig

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai 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.

Actionable comments posted: 5

🧹 Nitpick comments (4)
web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/toolPermission.ts (1)

187-187: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Preserve reference equality when no permissions change.

Both permission-update functions recreate the configuration object graph even when the requested permission matches the current state. Returning the original parameters object preserves reference equality, preventing unnecessary React re-renders downstream.

  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/toolPermission.ts#L187-L187: return parameters directly when allowed === has.
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/toolPermission.ts#L224-L229: return parameters directly when the tool's existing permission matches the requested one.
♻️ Proposed fixes

For withHarnessToolAllow:

-    if (allowed === has) return wrap({...template, harness: {...harness, permissions}})
+    if (allowed === has) return parameters

For withToolPermission:

-    const entry = isRecord(tools[i]) ? {...(tools[i] as Record<string, unknown>)} : {}
-    if (permission === undefined) delete entry.permission
-    else entry.permission = permission
-    const nextTools = tools.slice()
-    nextTools[i] = entry
-    return wrap({...template, tools: nextTools})
+    const currentEntry = tools[i] as Record<string, unknown>
+    if (currentEntry.permission === permission) return parameters
+
+    const entry = {...currentEntry}
+    if (permission === undefined) delete entry.permission
+    else entry.permission = permission
+
+    const nextTools = tools.slice()
+    nextTools[i] = entry
+    return wrap({...template, tools: nextTools})
web/oss/src/components/AgentChatSlice/hooks/useAlwaysAllowTool.tsx (1)

1-128: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cross-module hook placement.

This hook is now imported by AlwaysAllowedNotice.tsx in Playground/Components, outside its own AgentChatSlice module. Per coding guidelines, module-specific code shared across modules should move to a root hooks location rather than staying under a feature module's hooks/ folder.

As per coding guidelines: "Keep module-specific code within its module; move functionality shared across modules to the appropriate root components, hooks, assets, or types location."

Source: Coding guidelines

web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx (1)

120-135: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the dead variant prop variant is unused here, and both branches render the same drawer body. If this is meant to cover both inline and drawer sections, drop the prop and tighten the JSDoc; otherwise add an inline-specific body.

services/runner/src/engines/sandbox_agent/run-turn.ts (1)

478-481: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Sequential respondPermission per gate — consider whether this must be serialized.

Each gate is answered with a sequential await inside the loop. The comment explains the harness is blocked on all of them, so correctness isn't at risk, but if respondPermission calls are independent network/IPC round-trips, batching them (e.g., Promise.all) would cut resume latency proportional to gate count. Low priority given current batch sizes are likely small.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 27d9cbec-d4b6-48bd-8acd-59b6208108b5

📥 Commits

Reviewing files that changed from the base of the PR and between dd19a60 and a7524a8.

⛔ Files ignored due to path filters (1)
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (55)
  • hosting/docker-compose/ee/docker-compose.dev.yml
  • services/runner/src/engines/sandbox_agent/acp-interactions.ts
  • services/runner/src/engines/sandbox_agent/environment-setup.ts
  • services/runner/src/engines/sandbox_agent/pause.ts
  • services/runner/src/engines/sandbox_agent/run-turn.ts
  • services/runner/src/engines/sandbox_agent/runtime-contracts.ts
  • services/runner/src/engines/sandbox_agent/transcript.ts
  • services/runner/src/responder.ts
  • services/runner/src/server.ts
  • services/runner/src/tracing/otel.ts
  • services/runner/tests/unit/session-keepalive-approval.test.ts
  • services/runner/tests/unit/stream-events.test.ts
  • services/runner/tests/unit/transcript.test.ts
  • web/oss/src/components/AgentChatSlice/assets/transcriptToMessages.test.ts
  • web/oss/src/components/AgentChatSlice/assets/transcriptToMessages.ts
  • web/oss/src/components/AgentChatSlice/components/ApprovalDock.tsx
  • web/oss/src/components/AgentChatSlice/components/RevealCollapse.tsx
  • web/oss/src/components/AgentChatSlice/hooks/useAlwaysAllowTool.tsx
  • web/oss/src/components/Playground/Components/AgentCommitNotice.tsx
  • web/oss/src/components/Playground/Components/AlwaysAllowedNotice.tsx
  • web/oss/src/components/Playground/Components/MainLayout/index.tsx
  • web/oss/src/components/Playground/Components/ProviderKeyNotice.tsx
  • web/oss/tailwind.config.ts
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentOperationsSections.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/ClaudePermissionsControl.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/PiAutoApproveControl.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/SandboxPermissionControl.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/SectionQuickAction.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/ProviderCredentialsSection.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/ProviderKeyField.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/SectionChangeBody.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/sectionChanges.ts
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/index.ts
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/toolPermission.ts
  • web/packages/agenta-entity-ui/src/DrillInView/components/PlaygroundConfigSection.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/index.ts
  • web/packages/agenta-entity-ui/src/drawers/shared/ChangedPathsContext.tsx
  • web/packages/agenta-entity-ui/src/drawers/shared/FocusPathsContext.tsx
  • web/packages/agenta-entity-ui/src/drawers/shared/RailField.tsx
  • web/packages/agenta-entity-ui/src/drawers/shared/index.ts
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerDeliveriesDrawer.tsx
  • web/packages/agenta-entity-ui/tests/unit/formatCommitted.test.ts
  • web/packages/agenta-entity-ui/tests/unit/sectionChanges.test.ts
  • web/packages/agenta-entity-ui/tests/unit/toolPermission.test.ts
  • web/packages/agenta-shared/src/state/draftConfigChangeSignal.ts
  • web/packages/agenta-shared/src/state/index.ts
  • web/packages/agenta-shared/src/state/providerKeyAddedSignal.ts
  • web/packages/agenta-ui/package.json
  • web/packages/agenta-ui/src/components/HeightCollapse.tsx
  • web/packages/agenta-ui/src/components/presentational/index.ts
  • web/packages/agenta-ui/src/components/presentational/section/ConfigAccordionSection.tsx
  • web/packages/agenta-ui/src/components/presentational/section/index.tsx
  • web/packages/agenta-ui/src/components/presentational/section/useRecentFlag.ts

private readonly pausedToolCallIds = new Set<string>();
private resolvePause: (() => void) | undefined;
private eventDrain: Promise<void> = Promise.resolve();
private pauseTimer: ReturnType<typeof setTimeout> | undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Debounce timer is never cancelled on non-paused turn exit — can destroy a later turn's live session.

pauseTimer is only cleared inside pause()/schedulePause() themselves. If a turn that armed this timer exits via any other path (run-limit trip, thrown error, etc.) before the collect window elapses, the timer keeps running against this now-orphaned controller. Since env (in run-turn.ts) is a pooled/reused object whose parkedApprovals is reset at the top of the next runTurn call, the stale timer's eventual pause() call can find env.parkedApprovals.length === 0 and fall through to env.mcpAbort.abort() + env.sandbox.destroySession?.(env.session.id) — tearing down a completely different, currently-live turn's session.

Add a cancellation method that only clears the timer (no side effects), and invoke it from run-turn.ts's exit paths that did not pause (this requires hoisting pause out of the try block so finally can reach it, similar to otel/activeTurn).

🔒 Proposed fix
+  /** Cancel any armed collect-window timer without pausing. Call on every turn-exit path
+   * that did NOT pause, so a stale timer from THIS turn can never fire against whatever
+   * turn/session is live on the shared `env` afterwards. */
+  cancelSchedule(): void {
+    if (this.pauseTimer) {
+      clearTimeout(this.pauseTimer);
+      this.pauseTimer = undefined;
+    }
+  }
+
   pause(): void {
-    let otel: ReturnType<typeof createSandboxAgentOtel> | undefined;
-    let activeTurn: CurrentTurn | undefined;
+    let otel: ReturnType<typeof createSandboxAgentOtel> | undefined;
+    let activeTurn: CurrentTurn | undefined;
+    let pause: PendingApprovalPauseController | undefined;
...
-    const pause = new PendingApprovalPauseController(() => {
+    pause = new PendingApprovalPauseController(() => {
   } finally {
     runLimits.dispose();
+    if (pause && !pause.active) pause.cancelSchedule();
     await activeTurn?.toolRelay?.stop().catch(() => {});
     if (activeTurn) activeTurn.toolRelay = undefined;
   }

Also applies to: 28-58

Comment on lines 342 to 348
<Button
type="primary"
loading={responding}
loading={responding && resolveSource === "one"}
onClick={() => respond(true)}
>
{renderer?.approveLabel ?? "Approve"}
</Button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Disable Approve while the batch is resolving. It stays enabled during approveAll, so the primary action looks available even though respond() ignores it. Add disabled={responding} to match the other buttons.

Comment on lines +47 to +55
/**
* The committed value, as a reader recognises it rather than as it travels.
*
* The classifier stores scalars through `JSON.stringify` (`commitDiff/classify.ts`), which is right
* for comparing but wrong for showing: it renders an empty list as `[]` and a rule list as
* `["Terminal","Write"]` — wire syntax the control itself never displays. Unpack it back to what the
* field shows (one rule per line, matching the textarea), and name the empty cases instead of
* printing punctuation at someone.
*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Condense verbose comments.

These comments explain design rationales and expected behavior in detail, which violates the coding guideline: "Keep in-code comments to at most one short line; use longer comments only for genuinely surprising constraints." As per coding guidelines, please keep them brief.

  • web/packages/agenta-entity-ui/src/drawers/shared/RailField.tsx#L47-L55: Shorten this JSDoc to a single summary line, as it describes standard presentation logic rather than a surprising constraint.
  • web/packages/agenta-entity-ui/src/drawers/shared/RailField.tsx#L78-L85: Shorten this JSDoc to a single line.
  • web/packages/agenta-entity-ui/src/drawers/shared/RailField.tsx#L126-L128: Condense this architectural note to a single line.
  • web/packages/agenta-entity-ui/src/drawers/shared/RailField.tsx#L138-L147: Remove or condense these design rationale notes to a single short line.
📍 Affects 1 file
  • web/packages/agenta-entity-ui/src/drawers/shared/RailField.tsx#L47-L55 (this comment)
  • web/packages/agenta-entity-ui/src/drawers/shared/RailField.tsx#L78-L85
  • web/packages/agenta-entity-ui/src/drawers/shared/RailField.tsx#L126-L128
  • web/packages/agenta-entity-ui/src/drawers/shared/RailField.tsx#L138-L147

Source: Coding guidelines

Comment on lines +417 to +433
const sandboxChanged = useHasChangedUnder("sandbox")
const runnerChanged = useHasChangedUnder("runner")
const harnessChanged = useHasChangedUnder("harness")
const permissionsChanged = runnerChanged || harnessChanged
const changedIndicator = (changed: boolean) =>
changed ? ({tone: "draft", tooltip: "Unsaved changes in this group."} as const) : undefined

// Section-scoped undo: restore every changed property in this group to its committed value. The
// per-row dot reverts ONE property; this is "undo the whole group". Null when nothing changed or
// the surface offers no revert, so the header keeps its normal actions.
const revertSandbox = useRevertUnder("sandbox")
const revertRunner = useRevertUnder("runner")
const revertHarness = useRevertUnder("harness")
const revertPermissions = useCallback(() => {
revertRunner?.()
revertHarness?.()
}, [revertRunner, revertHarness])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the target file and search for the change-tracking helpers.
ast-grep outline web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx --view expanded || true
printf '\n--- matches ---\n'
rg -n "useHasChangedUnder|useRevertUnder|useHasFocusUnder|permissionsChanged|permissionsInFocus|harness\.kind|runner\.permissions|harness\.permissions" web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx

Repository: Agenta-AI/agenta

Length of output: 3543


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the provider/helpers that define how dot-paths are flattened.
rg -n "flatten|ChangedPathsProvider|useHasChangedUnder|useRevertUnder|useHasFocusUnder|ChangedPaths" web/packages/agenta-entity-ui/src web/packages/agenta-entity-ui -g '!**/dist/**' -g '!**/build/**'

Repository: Agenta-AI/agenta

Length of output: 20490


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- ChangedPathsContext.tsx ---'
sed -n '1,180p' web/packages/agenta-entity-ui/src/drawers/shared/ChangedPathsContext.tsx

printf '\n%s\n' '--- FocusPathsContext.tsx ---'
sed -n '1,140p' web/packages/agenta-entity-ui/src/drawers/shared/FocusPathsContext.tsx

printf '\n%s\n' '--- useModelHarness.tsx around the relevant section ---'
sed -n '400,520p' web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx

printf '\n%s\n' '--- sectionChanges.ts around harness permissions revert docs ---'
sed -n '130,210p' web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/sectionChanges.ts

Repository: Agenta-AI/agenta

Length of output: 18545


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the structure of the Advanced section to see what "runner" and "harness" cover.
sed -n '520,1045p' web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx

Repository: Agenta-AI/agenta

Length of output: 24824


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- runner references in useModelHarness.tsx ---'
rg -n '\brunner\b|runner\.' web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx

printf '\n%s\n' '--- runner references in surrounding agentTemplate files ---'
rg -n '\brunner\b|runner\.' web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate -g '!**/dist/**' -g '!**/build/**'

printf '\n%s\n' '--- sectionChanges helpers around path handling ---'
sed -n '1,140p' web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/sectionChanges.ts

Repository: Agenta-AI/agenta

Length of output: 9113


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the runner shape and any other runner-owned paths in this hook.
sed -n '90,160p' web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx
printf '\n%s\n' '--- runner-owned paths in the file ---'
rg -n '\brunner(\.[A-Za-z0-9_]+)+' web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx
printf '\n%s\n' '--- harness-owned paths in the file ---'
rg -n '\bharness(\.[A-Za-z0-9_]+)+' web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx

Repository: Agenta-AI/agenta

Length of output: 4563


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the commit-diff section mapping for the Advanced group and any docs/comments that specify
# whether the "runner" group is intentionally broad or only permission-specific.
rg -n 'runner / sandbox / harness|runner\.permissions|harness\.permissions|params"?: "advanced"|Advanced group|Permissions' \
  web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate \
  web/packages/agenta-entity-ui/src/drawers/shared \
  web/packages/agenta-entity-ui/tests/unit

Repository: Agenta-AI/agenta

Length of output: 11892


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# See whether any other runner properties exist in this area besides runner.permissions.*.
rg -n '\brunner\.[A-Za-z0-9_.]+' web/packages/agenta-entity-ui/src web/packages/agenta-entity-ui/tests/unit -g '!**/dist/**' -g '!**/build/**'

Repository: Agenta-AI/agenta

Length of output: 2629


Scope the Permissions group to harness.permissions useHasChangedUnder("harness"), useRevertUnder("harness"), and useHasFocusUnder("harness") all catch harness.kind, so a harness selection change lights the Permissions header, gets reverted with it, and pulls the group into focus. Keep the runner policy on runner.permissions.default, but narrow the harness side to harness.permissions and use that same prefix for focus.

Comment on lines +501 to +545
const changeBodyFor = useCallback(
(key: PanelSectionKey) => {
const paths = sectionChanges.draft.pathsUnder()
// `model` (llm.* + harness.kind) and `params`/Advanced (everything else) are separate
// classifier buckets — see SECTION_ID_TO_PANEL_KEY.
const isModelPath = (p: string) => p.startsWith("llm.") || p === "harness.kind"
const sectionPaths =
key === "advanced"
? paths.filter((p) => !isModelPath(p))
: paths.filter(isModelPath)
if (!sectionPaths.length) return null
return (
<SectionChangeBody
paths={sectionPaths}
onOpenDetails={() => openSectionDrawer(key as "model-harness" | "advanced")}
disabled={disabled}
changes={panelChangedPaths}
>
<ModelHarnessSectionBody
section={key as "model-harness" | "advanced"}
variant="inline"
schema={schema}
config={config}
onChange={onChange}
disabled={disabled}
withTooltip={withTooltip}
revisionId={revisionId}
savedHarnessValue={savedHarnessValue}
/>
</SectionChangeBody>
)
},
[
sectionChanges.draft,
openSectionDrawer,
panelChangedPaths,
disabled,
schema,
config,
onChange,
withTooltip,
revisionId,
savedHarnessValue,
],
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm which sections emit scalarChanges dot-paths (the leak trigger).
fd classify.ts -p commitDiff --exec rg -nP -C3 'scalarChanges|SectionId|instructions|agents_md' {}

Repository: Agenta-AI/agenta

Length of output: 252


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the target file structure and locate relevant symbols.
ast-grep outline web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx --view expanded || true

echo
echo "---- nearby references ----"
rg -n "pathsUnder\(|sectionsByKey|scalarChanges|SECTION_ID_TO_PANEL_KEY|openSectionDrawer|changeBodyFor" \
  web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx \
  web/packages/agenta-entity-ui/src -S

Repository: Agenta-AI/agenta

Length of output: 7046


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== sectionChanges.ts =="
sed -n '1,180p' web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/sectionChanges.ts

echo
echo "== AgentTemplateControl.tsx slice =="
sed -n '480,560p' web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx

echo
echo "== pathsUnder usage =="
rg -n "pathsUnder\(" web/packages/agenta-entity-ui/src -S

Repository: Agenta-AI/agenta

Length of output: 11500


Advanced is using the wrong change bucket. pathsUnder() returns every changed scalar path, so the advanced branch also picks up instructions/tools/mcp/skills edits and expands Advanced even when Advanced itself didn’t change. Scope this to the section’s own scalarChanges instead.

@ardaerzin
ardaerzin requested a review from mmabrouk July 21, 2026 09:53
ardaerzin added 10 commits July 21, 2026 12:13
The harness delivers parallel tool-call permission requests concurrently
(~0.5s apart), but the runner paused on the first and dropped the rest, so a
multi-file approval took one round-trip per file. Collect-then-pause: emit every
concurrent gate and debounce the pause (env AGENTA_RUNNER_APPROVAL_COLLECT_MS,
default 800ms) so the whole batch parks together; the resume answers every gate
on the live session before the blocked prompt continues. A gate that arrives
after the window still falls back to the cold retry-nudge path.
One CSS-native, reduced-motion-proof collapse primitive (HeightCollapse) for
everything that enters/leaves the composer region and config pane, plus the
config-section header shimmer + draft/agent indicator tones on
ConfigAccordionSection (and a useRecentFlag helper). Migrates RevealCollapse and
AgentCommitNotice onto it so the motion language is consistent. Adds the motion
dependency.
Reusable, dependency-free primitives for showing 'what changed' in a config
surface: ChangedPathsContext (which dot-paths have uncommitted changes + a
host-supplied revert), FocusPathsContext (narrow a surface to just the paths
that matter — the same idea as rendering an input only when needed), and
RailField opting into both via a path. sectionChanges maps the commit-diff
classifier to the config panel's sections. Adds the draft-change and
provider-key-added signal atoms the config pane consumes.
…nline provider key

Drawer-backed config sections (Model & harness, Advanced) surface only what
needs attention inline instead of routing to the drawer: a missing provider key
shows the key field right there, and uncommitted changes show the changed
controls (focus-filtered via FocusPaths/ChangedPaths), each row marked and
revertable, with a link out to the full drawer. Wires the sandbox/harness
permission controls and the provider-credentials/key fields with their
dot-paths; adds the provider-key-added notice.
…olve

Approval dock gains an 'Always allow <tool> for this agent' toggle that writes a
per-tool permission (gateway/custom-function) or harness allow-rule into the
draft config (toolPermission helpers), so the tool stops gating on resume and
future runs; a contained notice offers Undo. Answering resolves the whole parked
batch in one step — 'Approve all', or 'Approve' with the toggle on also clears
that tool's sibling gates — instead of stepping through 1-of-N.
A paused turn persisted a terminal `done` and the resume run re-persisted the
inbound user message. On reload that split the parked gate from the resume that
settles it (gate stuck at 'Awaiting approval') and duplicated the user turn.
Tag the paused turn's `done` with stopReason so replay can tell it isn't a real
boundary, and skip re-persisting the user turn on a resume run (only a fresh
user message is recorded, via tailIsFreshUserMessage).
transcriptToMessages keyed tool parts per-draft and closed a turn on any `done`,
so a gate parked in one run and settled in the resume run landed in different
drafts — the settling tool_result was dropped and the gate stuck at 'Awaiting
approval'. Key tool parts transcript-wide (a resume's result settles the earlier
gate in place) and don't treat a paused `done` as a turn boundary. Defense in
depth alongside the runner-side persist fix — un-sticks the gate even for records
written before it.
@mmabrouk

Copy link
Copy Markdown
Member

Hi Arda, thank you for this work. The machinery ruling and every other decision here are explained, with their reasons, in a handoff document written for you: https://github.com/Agenta-AI/agenta/blob/plan/concurrent-approvals/docs/design/agent-workflows/projects/sessions-takeover/arda-handoff.md

Your UX and configuration work has been ported, with your authorship preserved, onto a new branch, fe-enhance/approval-ui-onstack, which was cut from the top of the approvals stack. That branch is yours to continue on. This branch, approval-ui-onbig, stays on origin untouched as the archive of your version. Please read the handoff for the full context before picking things back up.

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.

2 participants