Skip to content

[WIP/reference] CAS migration: interrupt → event-based tool confirmation#105

Closed
JoshParkSJ wants to merge 1 commit intomainfrom
josh/cas-tc-migration
Closed

[WIP/reference] CAS migration: interrupt → event-based tool confirmation#105
JoshParkSJ wants to merge 1 commit intomainfrom
josh/cas-tc-migration

Conversation

@JoshParkSJ
Copy link
Copy Markdown
Contributor

Status: draft, will be closed immediately. This PR exists as a reference snapshot of an in-progress migration. Scope grew larger than wanted; restarting from main with a minimal-diff approach. Branch will not be deleted so this is recoverable later.

What this migrates

Old: agent runtime emits an interrupt event carrying UiPathConversationToolCallConfirmationValue; bridge waits via wait_for_resume; UI sends chat.interrupt_response; bridge resumes via resume(...).

New (matches recently merged PRs in sibling repos): agent emits startToolCall with requireConfirmation=true and inputSchema directly on the start event; runtime yields SUSPENDED with a placeholder API trigger so UiPathChatRuntime calls bridge.wait_for_resume(); user clicks Approve/Reject inline on the tool chip; UI sends chat.confirm_tool_call; backend calls bridge.set_tool_confirmation(UiPathConversationToolCallConfirmationEvent(...)) which unblocks wait_for_resume. UiPathChatRuntime then resumes the runtime with current_input = {interrupt_id: {approved, input}}.

Reference PRs (all merged):

Backend changes

  • pyproject.toml — version 0.0.78 → 0.0.79; uipath floor tightened to >=2.10.57, <2.11.0. uv.lock refreshed.
  • services/chat_bridge.py — full rewrite mirroring SocketIOChatBridge. Adds _tool_confirmation_event/_tool_confirmation_value, set_tool_confirmation(), wait_for_resume() returning a dict via model_dump. emit_interrupt_event is documented no-op. resume()/on_interrupt removed.
  • services/run_service.py — adds confirm_tool_call(run_id, approved, input); removes _handle_interrupt, resume_chat, on_interrupt callback, InterruptCallback alias, UiPathResumeTrigger import.
  • server/ws/protocol.py — adds ClientCommand.CHAT_CONFIRM_TOOL_CALL = \"chat.confirm_tool_call\"; removes CHAT_INTERRUPT_RESPONSE and ServerEvent.CHAT_INTERRUPT.
  • server/ws/handler.py — adds _handle_confirm_tool_call; removes _handle_interrupt_response.
  • server/ws/manager.py — removes broadcast_interrupt.
  • server/serializers.py — removes serialize_interrupt.
  • server/__init__.py — drops on_interrupt= kwarg.
  • models/data.py — removes InterruptData.
  • models/chat.py — removes the now-unsupported interrupts=[] arg from UiPathConversationMessage (mypy).

Frontend changes (src/uipath/dev/server/frontend/)

  • types/run.tsToolCall extended with require_confirmation, input_schema, confirmation. InterruptEvent removed.
  • types/ws.ts — adds chat.confirm_tool_call; removes chat.interrupt and chat.interrupt_response.
  • api/websocket.tssendConfirmToolCall(runId, toolCallId, approved, input?); removes sendInterruptResponse.
  • store/useRunStore.tsChatToolCall interface; projectToolCall helper that surfaces camelCase→snake. addChatEvent reads event.toolCall.startToolCall for requireConfirmation/inputSchema/input (which are absent from the persisted message.toolCalls entry) and merges with prior in-store state across subsequent events. Surfaces result.cancelled. Removes activeInterrupt/setActiveInterrupt.
  • store/useWebSocket.ts — removes chat.interrupt case.
  • App.tsx — uses projectToolCall.
  • components/chat/ChatInterrupt.tsx — DELETED.
  • components/chat/ChatMessage.tsx — adds ToolCallConfirmPanel rendered inline below the tool chip when require_confirmation && !confirmation && !has_result. Chip renders red `✗` when cancelled === true.
  • components/chat/ChatPanel.tsx — wires handleConfirmToolCall; derives awaitingConfirmation from messages.
  • components/runs/RunDetailsPanel.tsxawaitingConfirmation derived from chatMessages.

Demo (demo/mock_support_runtime.py)

For needs_approval=True turns: emits startToolCall(requireConfirmation=true, inputSchema=...), then yields SUSPENDED with an API UiPathResumeTrigger. On resume reads input.get(interrupt_id, {}).get(\"approved\") and emits toolCallEnd(cancelled=not approved). On rejection now yields UiPathRuntimeResult(SUCCESSFUL) so UiPathChatRuntime exits cleanly — without this it loops back into delegate.stream() and falls through to the next turn.

Tests

  • tests/test_chat_bridge.py — 5 tests covering the new bridge contract (happy path, reject, no-op interrupt, multi-confirm, disconnect unblocks waiter).
  • Full pytest suite (excluding e2e): 16 passed.
  • uv run ruff check src/ tests/ clean. uv run ruff format --check . clean. uv run mypy src/ clean.

E2E status

Flow Status
Approve panel renders inline ✅ verified (Manus headless Playwright)
chat.confirm_tool_call outbound on Approve ✅ verified
Agent resumes after Approve, phase-2 reply with SUP-4821 ✅ verified
chat.confirm_tool_call outbound on Reject ✅ verified
execute chip turns red on Reject ⚠️ fix shipped, NOT E2E-verified
Agent skips phase 2 on Reject (no leaked next-turn run) ⚠️ fix shipped, NOT E2E-verified

Why this is being closed

Migration scope outgrew the appetite for a single change. Restarting from main with the minimum delta needed to keep cross-repo tests green and the dev console functional, then layering the migration on later as a smaller follow-up.

Backup tag

backup/main-20260427-135849 was created on main before this work began.

Migrates the dev console from the old interrupt-based tool-confirmation
flow to the event-based flow that landed in uipath-python#1558,
uipath-langchain-python#703, and uipath-agents-python#420.

Status: closed as a reference. Approve flow E2E-verified; reject path
fixes shipped late and were not E2E-verified before scope was reset.
@JoshParkSJ
Copy link
Copy Markdown
Contributor Author

Closing as reference. Restarting from main with a minimum-delta scope; will reopen this scope as a separate PR once the floor changes are in.

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.

1 participant