[REMOTE-2661] Allow a debug agent in a retained setup-failure session (warp client) - #14916
Conversation
Implements the warpdotdev/warp (Rust client) half of REMOTE-2661: a debug agent can be started inside a retained post-setup-failure session without reopening the run, clearing its failure record, or losing the idle timer to a silent agent turn. - Add a `DebugWindowController` in `AgentDriver` that pins the post-failure idle window for the duration of a debug turn and re-arms the full interval on any terminal conversation outcome, idempotent by turn (conversation) id. - Tag a debug conversation bootstrapped with no server token into a retained setup-failure session with `TaskSyncMode::PreserveTerminalSetupFailure`, so `LocalAgentTaskSyncModel` keeps reporting its conversation id but never derives task lifecycle state from it. - Add `AIQueryRouting::RetainedSetupFailureDebug` so both the tombstone's own input and an already-attached live viewer submit through the authenticated run follow-up service instead of the direct viewer prompt path or the local agent. - Reject a no-token agent prompt from a viewer on the sharer side while retaining a setup-failed run, closing the bypass an old or modified viewer could use to start a conversation directly. - Add a "Debug" tombstone CTA for an authorized caller when the client-side eligibility proxy and an open debug window are met. Co-Authored-By: Warp Agent <agent@warp.dev>
…#14231)
Switches from the client-side eligibility proxy to the server-computed
debug_agent_available capability now returned by GET /agent/runs/{id}
and the list endpoint (warp-server#14231), and reads the paired
status_message.debug_agent_active display flag. Both default to false
via #[serde(default)] so an older server fails closed.
is_open_for_setup_failure_debug_bootstrap now ANDs debug_agent_available
in alongside the existing client-observable conditions; the server
remains the sole authority regardless. Adds end-to-end coverage proving
the debug tombstone CTA and AIQueryRouting::RetainedSetupFailureDebug
only activate when the real field is true.
Co-Authored-By: Warp Agent <agent@warp.dev>
Adds `debugAgentActive` as a top-level field on `UpdateAgentTaskInput` (sibling to `sessionDebugUntil`, confirmed against the server's implementation on factory/remote-2661-server-setup-failure-debug), threaded through the AIClient trait, the cynic mutation type, and the vendored GraphQL schema mirror. DebugWindowController's pin/unpin transitions in arm_setup_failure_debug_window now publish Some(true)/Some(false) alongside the deadline; the throttle on publish_debug_window_deadline no longer suppresses a debug_agent_active transition, since a pin/unpin is a discrete state change rather than a sliding value. All other update_agent_task call sites pass None (untouched). Co-Authored-By: Warp Agent <agent@warp.dev>
Replaces the sharer's flawed local-cache/is_sharer heuristic for a
no-token REMOTE-2661 debug bootstrap prompt with a real server
authorization callback, per the review's decision.
- Adds the setupFailureDebugAuthorization GraphQL query (schema mirror
+ cynic query type) matching the server's implementation on
factory/remote-2661-server-setup-failure-debug: input {taskId,
workloadToken, participantFirebaseUid} -> {authorized: Boolean!}.
- Adds AIClient::setup_failure_debug_authorization and wires it into
ServerApi.
- The sharer resolves the requesting participant's firebase_uid from
the already-local session-sharing presence list
(PresenceManager::viewer_firebase_uid), issues its own workload
token, and calls the server before honoring a no-token prompt
against an eligible retained setup-failure session. Every outcome
other than an explicit Ok(true) — unresolvable UID, denial,
UserFacingError, or a failed call — rejects the prompt. There is no
local fallback.
- Extracts the authorization decision into
is_setup_failure_debug_prompt_authorized, a pure async function
testable independent of workload-token issuance (which isn't
mockable in this test harness), with 5 regression tests covering:
authorized principal accepted, unauthorized participant rejected,
server error rejected, unresolvable participant rejected without
calling the server, and failed workload-token issuance rejected
without calling the server.
Co-Authored-By: Warp Agent <agent@warp.dev>
End-to-end verification: 4/4 required states proven against a real local stackRun 1. Setup fails, run FAILED with its message. 2. Debug prompt reaches an agent in the broken environment. 3. Run still FAILED afterwards, message and code intact. Sampled continuously and again 8 minutes later: 4. Timer refreshes, pins, re-arms. Sampled every 2s: before, Reproduction. Branches: warp-server Scaffolding — everything stubbed or patched, none of it in the feature's path.
Items 4–8 are environment plumbing. Items 1–3 are the only ones touching a runtime path, and none is inside the retained-session or debug-window logic under verification. Why there is no screenshot. The Oz web app could not be reached honestly. The app shell serves at |
Fixes a defect found in live verification: warp-server can redeliver the same logical no-token setup-failure debug bootstrap prompt (same idempotency key) when an earlier delivery's acknowledgement is lost, e.g. on the warp-server <-> session-sharing-server hop. The sharer had no way to recognize a redelivery, so each one created an independent conversation, and BlocklistAIController::send_query's existing cancel-active-conversation-before-send behavior meant each new delivery cancelled the previous one's in-flight stream. A participant watching the first conversation that appeared would see it go dead while the real response eventually landed in a different, invisible conversation -- or never land at all if redeliveries kept arriving faster than one attempt could finish. Fix: record the local conversation created for a task's no-token bootstrap prompt (SharedSessionState::pending_setup_failure_debug_bootstraps), and reuse it for a later no-token submission on the same task rather than starting a new one. The lookup is self-invalidating: it only returns a hit while the recorded conversation is still live and has not yet been assigned a server conversation token. Once tokened, the bootstrap has durably succeeded, so a later no-token submission is a distinct request, not a retry, and correctly falls through to starting fresh. Verified safe to reuse even while the recorded conversation's prior attempt is still streaming: send_query already cancels whatever is the terminal surface's active conversation before sending a new query to it -- the same handling an ordinary "user sends a second message before the first finishes" submission gets. Reuse only changes which conversation a repeated bootstrap prompt lands in, never whether an in-flight attempt gets cancelled, and this decision happens synchronously on the app's single event loop, so there is no window for a concurrent handler to observe a half-cancelled conversation. Extracted the reuse decision into a pure `reusable_bootstrap_conversation` function (mirroring the is_setup_failure_debug_prompt_authorized pattern from the finding-1 fix) since BlocklistAIController has no existing test harness in this codebase to exercise the full wiring through. Added 4 regression tests covering: no prior conversation (first delivery), a retry reusing an untokened conversation, a tokened conversation never being reused, and a vanished/dropped conversation never being reused. Also makes a redelivery observable per review request: reuse and fresh-start both log an info line naming which happened. Co-Authored-By: Warp Agent <agent@warp.dev>
Adds a test proving the actual property the bootstrap-retry defect
broke: cancelling an in-flight exchange with
CancellationReason::FollowUpSubmitted { is_for_same_conversation: true }
(exactly what BlocklistAIController::send_query does when a retry
reuses the recorded conversation) keeps the conversation InProgress
rather than visibly flipping it to Cancelled, and the retry's own
exchange -- submitted to that same conversation -- runs to completion
and reports Success.
This complements the existing reusable_bootstrap_conversation tests,
which only proved the conversation-identity decision (reuse vs. new)
without proving an answer actually arrives. No existing test in this
file previously covered the composite "cancel then resend on the same
conversation completes" property; the closest,
optimistic_cli_subagent_completion_with_in_flight_stream_reports_success,
only exercises the cancellation half.
Full end-to-end coverage through the real send_shared_session_query /
send_query / ResponseStream::new path remains out of reach in this
harness: ResponseStream::new dispatches a real network request via
ServerApiProvider, which is why every existing test in this file
injects a stream with register_mock_stream_for_test instead of driving
send_query directly.
Co-Authored-By: Warp Agent <agent@warp.dev>
…wer-input subscription Response to a line-count audit requested after review. - Removed DebugWindowController::force_close and its test. It carried #[allow(dead_code)] and had no production call site: its own doc comment noted the sandbox-deadline select! in AgentDriver::run already achieves the same effect on shutdown. Speculative API for a caller that doesn't exist is not something the spec should require; raised with the orchestrator to correct TECH.md if needed. - Factored the duplicated viewer-input refresh subscription out of arm_debug_window and arm_setup_failure_debug_window into a shared subscribe_to_viewer_input_refresh helper. Both installed an identical terminal_driver subscription matching TerminalDriverEvent::SharedSessionViewerInput, logged the same trigger=viewer_input line, and called publish_debug_window_deadline the same way — differing only in which refresh function they passed (IdleTimeoutSender::refresh vs. the pin-aware DebugWindowController::refresh_from_last_armed). The extraction takes that refresh function as a closure parameter, so the pin-aware difference between the two call sites is preserved exactly, not flattened. - Merged debug_window_controller_refresh_from_last_armed_is_inert_while_pinned and debug_window_controller_refresh_idle_is_inert_while_pinned into one debug_window_controller_refresh_is_inert_while_pinned test: both asserted the identical guarantee (a refresh entry point is a no-op while pinned) against the two different refresh methods. Left the three idempotency tests (duplicate pin, duplicate finish, finish of an unknown turn) and the flag-off/authorization/redelivery-outcome tests untouched, per instruction not to make a redelivery-adjacent test failure harder to localize. cargo nextest run -p warp -E 'test(debug_window) | test(setup_failure) | test(cloud_conversation_continuation) | ...' — 83/83 passed. cargo clippy -p warp --lib --tests -- -D warnings — clean. ./script/format — applied, no behavior change. git diff --check — clean. Co-Authored-By: Warp Agent <agent@warp.dev>
…ibility check
The retained-setup-failure tombstone's Debug CTA (and the pre-existing
ContinueInCloud CTA it shares its eligibility check with) required
task_creator_access() to match the exact literal creator UID before a
conversation exists for the task. `RunCreatorInfo`/`TaskPrincipalInfo`
is only ever a natural person or service account (see
RunCreatorInfoType on the server) -- creator never represents a team.
Team ownership lives in a separate field (RunItem.scope on the public
API) that the client never deserialized. The practical effect: any
team member other than the exact person who happened to create the
run saw a silent no-CTA tombstone, with no error, even though the
server's own CanAccessTask authorization (used both at follow-up
submission and at debug-agent bootstrap authorization time) would
genuinely have allowed them.
This affects both CTAs that share task_creator_access, since it was
inherited unchanged from the pre-existing ContinueInCloud path rather
than written for REMOTE-2661; fixing the shared predicate improves
both naturally rather than diverging their behavior.
- Added AmbientAgentTask::scope (TaskScope { scope_type, uid }),
deserializing the server's existing RunItem.scope field the client
never consumed.
- task_creator_access now also accepts a viewer who belongs to the
run's owning team (scope.type == "Team"), via the same
UserWorkspaces::team_from_uid_across_all_workspaces check
conversation_access() already uses for the post-conversation-exists
case elsewhere in this file. Falls back to the exact-creator check
unchanged for a personal (non-team) run.
- Does not touch the server-side CanAccessTask authorization boundary
or the injection-time authorization check; this only aligns what
the client is willing to *show* with what the server was already
willing to *allow*.
Added two tests: a team member who is not the literal creator gets
the Debug CTA on a team-owned retained setup-failure run; a viewer on
an unrelated team still gets no CTA.
cargo nextest run -p warp (targeted REMOTE-2661 sweep, 85/85; full
cloud_conversation_continuation suite, 32/32) -- all passed.
cargo clippy -p warp --lib --tests -- -D warnings -- clean.
./script/format -- no diff beyond intended changes. git diff --check
-- clean.
Co-Authored-By: Warp Agent <agent@warp.dev>
…r silently fall back on unknown eligibility
Two independent, requester-discovered gaps in the retained-setup-failure
debug flow, on top of the already-shipped tombstone/live-viewer routing.
1. `/agent`/`/cloud-agent` declare `Availability::NOT_CLOUD_AGENT`, so
they are unavailable in any cloud/ambient pane -- including a
retained setup-failure debug pane, which is exactly the surface the
feature's own request text describes typing `/agent why did my
setup fail` into. Typing the command there previously did nothing
visible: the slash command menu doesn't offer it, and forcing it
through would have tried to start a brand-new *local* conversation
via `EnterAgentView`, which is wrong inside a retained *cloud*
debug session.
Fix: `GuiSlashCommandDataSource::availability` no longer marks a
retained-setup-failure-debug-eligible pane as `CLOUD_AGENT` (a new
`is_retained_setup_failure_debug_editable_for_task` helper answers
the eligibility question from just the task id, reusing the same
check `resolve_ai_query_routing` already trusts). `/agent`/`/new`'s
execution arm now checks the shared routing function first and, if
it resolves to `RetainedSetupFailureDebug`, emits
`Event::SubmitSetupFailureDebugFollowup` directly instead of
`EnterAgentView` -- the exact same authenticated follow-up path the
pane's own (no-slash-command) input already used correctly.
2. `resolve_ai_query_routing`'s retained-setup-failure-debug check can
only answer "eligible" once the task has actually been fetched into
`AgentConversationsModel`. An absent task was previously
indistinguishable from a genuinely ineligible one at that call
site, so an ambient viewer whose task hadn't been fetched yet (e.g.
joined via a direct session link rather than through the task list)
silently fell through to the ordinary live-viewer path -- which is
exactly the path this feature closes.
Fix, two parts:
- Eagerly warm the task cache at shared-session join time
(`get_or_async_fetch_task_data`), as soon as the ambient task id
is known -- well before a viewer can type anything, instead of
leaving routing correctness dependent on incidental task-list
poll timing.
- `maybe_route_ai_query_to_remote_target` no longer treats "unknown
eligibility" the same as "known ineligible" for an attached
ambient viewer: if the task isn't cached yet, it blocks the
submission with a clear toast and (re)triggers the fetch, instead
of silently proceeding to the direct viewer prompt path.
New tests: two team-authorization tests were already in place from the
prior session; this change adds
`maybe_route_ai_query_to_remote_target_blocks_ambient_viewer_with_unresolved_task`,
proving the guard blocks the submission, never forwards a prompt, and
surfaces a toast, for the specific "eligibility resolvable but not yet
cached" case that motivated this fix.
cargo nextest run -p warp: 210/210 (targeted REMOTE-2661 + slash
command sweep), 5/5 (routing-specific), 715/715 (broader sweep) --
all passed. cargo clippy -p warp --lib --tests -- -D warnings -- clean.
./script/format -- no diff beyond intended changes. git diff --check --
clean.
Co-Authored-By: Warp Agent <agent@warp.dev>
…e debug bootstrap The sharer's AgentPromptRequested handling for a no-token, purpose-tagged bootstrap request (REMOTE-2661) authorized and started the conversation, but never told session-sharing-server which conversation resulted. The server's inject-message endpoint needs that conversation ID back to resolve the caller's synchronous wait, so every bootstrap injection was indistinguishable from a failed one and kept getting retried. - Network::send_agent_prompt_acknowledgement / set_pending_bootstrap_ack / take_pending_bootstrap_ack (sharer): new pending-ack correlation state and the upstream message that reports a bootstrap's resulting conversation token. - terminal_view_adaptor.rs: registers the pending ack right after accepting a purpose-tagged request (both the is_sharer fast path and the post-authorization path), and sends the acknowledgement once BlocklistAIHistoryEvent::ConversationServerTokenAssigned reports the new conversation's token. - idempotency_key threaded through every send_agent_prompt_rejection call site so a bootstrap rejection is recorded under the same key a retry looks up. - Viewer-side network.rs updated for the new required purpose/idempotency_key fields on AgentPromptRequest (always None for an ordinary live-viewer prompt) and the new AgentPromptFailureReason::NotEligibleForPurpose variant. Depends on warpdotdev/session-sharing-protocol#65 for the new AgentPromptPurpose/idempotency_key/AcknowledgeAgentPromptRequest wire types; pinned to that PR's branch commit until it merges. cargo check -p warp: clean. cargo check -p warp --tests: clean. cargo clippy -p warp --lib -- -D warnings: clean. cargo fmt --check -p warp: clean. Co-Authored-By: Warp Agent <agent@warp.dev>
|
Pushed a follow-up commit closing the last gap in the retained setup-failure debug flow: the sharer authorized and started a debug conversation for a This required corresponding changes in two upstream repos:
This repo's commit adds the sharer-side piece: it registers a pending-ack correlation when accepting a purpose-tagged request, and sends the acknowledgement once
|
| serde_with = "^3.21" | ||
| serde_yaml = "0.8" | ||
| session-sharing-protocol = { git = "https://github.com/warpdotdev/session-sharing-protocol.git", rev = "b30fdd06379a3d073b398eabd106abed5b443aae" } | ||
| # TODO(REMOTE-2661): point back at main once warpdotdev/session-sharing-protocol#65 merges. |
There was a problem hiding this comment.
make sure this gets reverted
There was a problem hiding this comment.
Not reverted yet, deliberately — leaving this open. The pin is now 4888e758, the head of session-sharing-protocol#65, which is where AgentPromptRequest.purpose and AgentPromptFailureReason::NotEligibleForPurpose are gone; this client will not compile against main until #65 merges. The TODO(REMOTE-2661) above it is the reminder to point back at main at that moment, and the merge-order warning at the top of the PR description names the same gate.
Responding as wilson: Open session · View factory task
There was a problem hiding this comment.
Overview
Client leg of REMOTE-2661, reviewed for size after a request to trim the feature's ~5.4k added lines. The largest single cut in the whole feature lives here, and the read also turned up a routing predicate that diverges from the spec.
Concerns
- Retained-session routing stops working after the first bootstrap (important).
AmbientAgentTask::is_open_for_setup_failure_debug_bootstraprequiresconversation_id().is_none()(app/src/ai/ambient_agents/task.rs:330-365), and bothis_retained_setup_failure_debug_editableandresolve_ai_query_routinggate on it (cloud_conversation_continuation.rs:106-124,191-201). Once the first bootstrap persists a conversation ID, an attached viewer stops resolving toRetainedSetupFailureDebugand falls back to the directLiveRemoteVmpath — butPRODUCT.md§7 and TECH "Update the Warp client tombstone and routing" require every later prompt to reuse the conversation through the authenticated run-follow-up service, never the direct viewer path. Split bootstrap eligibility from retained-session routing so the latter stays true for an eligible retained failure after a token exists. - Same predicate ignores the pinned window (important). It requires an unexpired deadline rather than also honoring
debug_agent_activewhile a turn has the timer pinned, whichPRODUCT.md§1 and §24 both call for. A long autonomous turn that pins past the displayed deadline would lose its own entry point. - A second idempotency mechanism guarding one failure mode (~375-390 lines, suggestion). The task-ID→conversation retry map at
app/src/ai/blocklist/controller/shared_session.rs:31-50,850-1097is ~125-135 production lines, plusshared_session_tests.rs(68) andcontroller_tests.rs:425-608(184).TECH.md:96calls it "a second, independent safeguard" that "does not replace the protocol-level idempotency key", and this PR's own body concedes it does not prevent the failure on its own; it is keyed only bytask_id, not the transmitted key, and stops reusing once the token is assigned, so it is strictly weaker than the service contract. Oncesession-sharing-protocol#65andsession-sharing-server#484are merged, deployed, and their retry contract proven, this is the biggest safe cut in the feature — but only in that order. - A 184-line test that does not exercise what it is filed under (suggestion).
controller_tests.rs:425-608(cancelling_and_resending_within_one_conversation_reports_success) never reaches the new task-ID map or reuse path; it drives pre-existing same-conversation cancellation and would have passed before this PR. Reduce it to a small contract test, or delete it with the map above. Every other added test earns its place under the test-value gate — the timer pin/re-arm cases, the sync-mode regression, the routing capability and team-member cases, and the authorization true/deny/error/no-UID/no-token cases each pin a distinct spec requirement. - Comment volume and eligibility logging (~120-150 lines, suggestion). The non-test diff adds 417
//////lines:controller/shared_session.rs+68,driver.rs+62,terminal_view_adaptor.rs+47,ambient_agents/task.rs+46, continuation routing +35. Keep the why-level comments on the authorization boundary and the timer invariant; drop the call-path recounting and issue history perAGENTS.md. Separately,task.rs:330-365andcloud_conversation_continuation.rs:262-304emitwarn!on every eligibility evaluation on a routing/input path — that is breadcrumb-level noise at warn severity and carries no feature behavior. - Duplicated unresolved-task-cache policy (~40-55 lines, suggestion). The same "cache unresolved, block and fetch" handling appears at
app/src/terminal/input.rs:4197-4247andapp/src/terminal/input/slash_commands/mod.rs:485-535, after eager warming atshared_session/viewer/terminal_manager.rs:848-859. Keep the fail-closed behavior, but collapse the duplicated model locking, lookup, fetch, and toast into oneInputhelper used by both ordinary submit and/agent//new, and assert it once at the helper level.
Not trim candidates: the timer pin/re-arm controller (PRODUCT.md §18-24 requires idempotence by turn ID and a pin while at least one turn is active, so the turn-keyed set is not gratuitous), TaskSyncMode::PreserveTerminalSetupFailure, TaskScope/team access, and setupFailureDebugAuthorization — I looked for an existing workload-token query the sharer could reuse for a task+participant check and found none on master. Its ~250 production/schema lines are proportionate to a credit-spend authorization boundary; its four fail-closed inputs already converge through one .unwrap_or(false).
Verdict
Checks: build not run, tests not run, CI not checked, visual proof n/a — this was a read-only size analysis, so the two routing findings above are unverified against a running client.
Found: 0 critical, 2 important, 4 suggestions, 0 nits
Of the +2,462 here, 934 lines (37.9%) are test-only and 1,528 are non-test; conservative non-overlapping trim is ~550 lines, leaving a floor near ~1.9k.
Responding as wilson: Open session · View factory task
Resolves three conflicts: - `terminal_manager.rs`: union of the two import additions. - `view.rs`: union of the two re-export lists. - `cloud_conversation_continuation.rs`: adopt master's rename of `task_creator_access` to `task_ownership_access` and its new `completed_child_conversation_access` wrapper, keeping this branch's owning-team membership check inside the renamed function.
Bumps the protocol pin to warpdotdev/session-sharing-protocol#65 head (4888e758), where `AgentPromptRequest.purpose` and `AgentPromptFailureReason::NotEligibleForPurpose` no longer exist. Replaces the `purpose`-tag gate on registering a bootstrap acknowledgement with the predicate session-sharing-server itself applies: an idempotency key with no conversation to continue. The two must agree -- a bootstrap the server waits on but the sharer never acknowledges is treated as an undelivered injection and retried until the caller's deadline -- and both sides evaluate it against the parsed token, so an absent or unparseable conversation id counts as no conversation on either side.
Splits bootstrap eligibility from retained-session routing, so a later prompt keeps going through the authenticated follow-up service once the first bootstrap has persisted a conversation ID (PRODUCT.md §7) instead of falling back to the direct viewer path, and keeps the session eligible while a debug turn pins the idle timer past the last published deadline (§1, §24). Both are covered by new routing tests. Removes the client task-ID bootstrap-reuse map. session-sharing-server#484's Redis claim, keyed by the transmitted idempotency key, is now the durable guarantee against a duplicate bootstrap conversation; the map was keyed by task ID, stopped reusing once a token existed, and is redundant against it. The conversation is still tagged PreserveTerminalSetupFailure. Its two test files go with it: the pure reuse-decision tests, and a 184-line outcome test that only drove pre-existing same-conversation cancellation. Sends the setup-failure state, status message, and the debug window's first deadline in one update, closing the interval where the server saw a terminal setup failure without knowing a debug window was opening. Collapses the duplicated "cache unresolved, block and fetch" handling in ordinary submit and /agent into one Input helper, with its regression folded to a single assertion at the helper. Fail-closed behavior is unchanged. Drops the eligibility warn! breadcrumbs from the routing and input paths and trims call-path recounting from the comments.
Three test-only breakages from the master merge, where files new on master construct types this branch added fields to (and vice versa): - `orchestration_child_tracker_tests.rs` and `child_agent/materialization_tests.rs` build `AmbientAgentTask` literals and need this branch's `debug_agent_available` and `scope`. Both are `false`/`None`, which is the fail-closed default and irrelevant to what either test asserts. - `controller_tests.rs` builds a `StreamFinished` and needs master's new `request_charges`, matching the sibling literal already in the same file. `cargo check -p warp --lib --tests` passes with these.
`routing_allows_live_input_only_for_executable_shared_session_role` built a bare app with no singletons, and panicked with "Cannot get singleton model of type AgentConversationsModel that was never registered". Pre-existing on this branch rather than new: `resolve_ai_query_routing` has evaluated retained-debug eligibility -- which reads `AgentConversationsModel` -- before the live-viewer branch since the original feature commits, so the same call sits at this spot in b78530e. It went unnoticed only because the branch did not build, so nothing ran the test. Registering the model empty is the right fixture, and is what the routing tests added alongside it already do: with no task row, an ordinary live viewer must still route to `LiveRemoteVm`, which is exactly what this test asserts.
The synchronous inject contract it existed to satisfy is gone: warp-server now marks a bootstrap follow-up delivered on HTTP success like every other follow-up, and learns the conversation ID through the ordinary ApplyClientUpdates task-sync path the sharer already reports on. Removes register_bootstrap_ack_if_bootstrap_request and its two call sites, the AcknowledgeAgentPromptRequest send at ConversationServerTokenAssigned, and Network's pending-ack bookkeeping.
With the durable bootstrap outcome record gone from session-sharing-server, the key a rejection echoed back had no consumer: the claim is taken and looked up server-side from the inject request body, never from the field forwarded to the sharer. Nothing in warp read the key for behavior even before this - every use was a pass-through onto the wire. That removes warp's last dependency on session-sharing-protocol#65, which is closing, so the pin reverts to main (b30fdd06) - already what master tracks. Cargo.lock reverts wholesale, which also undoes two unrelated resolution drifts the branch had picked up (windows-core and heck).
This branch dropped master's `#[cfg(not(target_family = "wasm"))]` from the import, but the only use in the file, conversation_is_cloud_oz_for_slash_command, is itself wasm-gated. The result was a hard `-D warnings` failure on the wasm clippy target. It went unnoticed because GitHub skips CI for draft PRs, so no build had ever run against this branch on any target.
…-warp-client # Conflicts: # app/src/ai/agent_sdk/driver_tests.rs # app/src/terminal/input/slash_commands/data_source/gui.rs # skills-lock.json
…-warp-client # Conflicts: # app/src/ai/agent_sdk/driver_tests.rs # app/src/ai/blocklist/agent_view/agent_input_footer/mod.rs # app/src/terminal/view.rs
|
An unexpected error has occurred: managed MCP server 01a01a21-3ec9-71a1-99f2-f13b1773c920 is not active |
Description
What: An authorized user can run a debug agent inside the retained sandbox of a cloud run that failed during environment setup. The debug agent does not reopen the run and does not clear its failure record. A silent agent turn does not consume the idle timer.
Why: A run that fails during setup keeps its session alive for a bounded debug window (REMOTE-2208). But there is no way to get an agent into that session. If you start an agent the ordinary way, it creates a fresh conversation and flips the run back to "in progress". That destroys the failure record that you want to debug.
How: A no-token prompt against a retained, eligible session bootstraps a debug conversation through the server's authenticated follow-up path, not the direct viewer path. The client suppresses task-state reporting for that conversation, so the conversation cannot overwrite the failure record. Each debug turn pins the idle timer for its duration, so an autonomous agent does not lose its own session.
Server companion:
warp-server#14231. Deploy the server PR first. Against an older server, the client fails closed (debug_agent_availabledefaults tofalse). Thus an early client deploy is inert, not wrong.Reviewing guide
Suggested order:
task.rs— Start withis_setup_failure_debug_session_open. All downstream code trusts this one field.cloud_conversation_continuation.rs— Readresolve_ai_query_routingand the new routing variant. Check the priority order (retained-debug check before live-viewer check).terminal_view_adaptor.rs— Read theAgentPromptRequestedhandler as a whole, not as a diff, because a brace moved. This handler is the security boundary.driver.rs— ReadDebugWindowControllerandarm_setup_failure_debug_window. This is the most complex state machine in the PR. Check the pin and unpin transitions against theConversationStatusvariants.conversation.rs/local_agent_task_sync_model.rs— ReadTaskSyncMode::PreserveTerminalSetupFailureand the locations that set and read it.input.rs/slash_commands/mod.rs— Read the submission entry points. Check that/agentand plain input resolve to the same routing.Architecture overview
Six pieces, each with one job:
AmbientAgentTask::is_setup_failure_debug_session_open,task.rs) — The client trusts one server-computed boolean:debug_agent_available. The server knows the failure state, error code, feature flags, and window status from the same snapshot. The client does not re-derive this data.cloud_conversation_continuation.rs) — Adds theAIQueryRouting::RetainedSetupFailureDebugvariant. The resolver checks it before ordinary live-viewer routing. Thus every entry point (plain input,/agent, an attached viewer) converges on the authenticated follow-up service. No entry point uses the direct viewer-prompt path or a local conversation.terminal_view_adaptor.rs+ the newsetupFailureDebugAuthorizationGraphQL query) — The sharer cannot authenticate the requester itself. Thus the sharer honors a no-token prompt only after an explicittruefrom the server. There is no local fallback.driver.rs,DebugWindowController) — Runs inside the agent process. A debug turn pins the idle window open for its duration and re-arms it on completion. This is necessary because no viewer keystroke refreshes the window during an autonomous turn.TaskSyncModeinconversation.rs,local_agent_task_sync_model.rs) — The debug conversation reports its ID (the injection and transcript APIs need it). But the conversation never derives task state from its own status. Thus it cannot overwrite the original failure record. The server independently guards the same rule.driver.rs) — The driver sends the terminal failure state, the status message, and the first debug deadline in one server update. Thus the server never sees a failure without a debug-window deadline.Testing
Unit tests cover:
DebugWindowControllerpin, re-arm, and idempotenceThe sharer authorization decision (accept, deny, error, no token)
Routing eligibility gated on the real
debug_agent_availablefield, with team-owned runs includedA later prompt that reuses the existing debug conversation
TaskSyncModesuppression of task-state derivation.I have manually tested my changes locally with
./script/runAgent Mode