Skip to content

Collect LRC liveness signals so the agent stops killing healthy commands - #14777

Merged
evelyn-with-warp merged 18 commits into
masterfrom
evelyn/fix-lrc-hang
Aug 30, 2026
Merged

evelyn-with-warp merged 18 commits into
masterfrom
evelyn/fix-lrc-hang

Conversation

@evelyn-with-warp

@evelyn-with-warp evelyn-with-warp commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

Description

In some tbench traces with LRC, the CLI subagent judges a long-running command's liveness from the terminal grid alone, so a command that redirects to a file, suppresses output, or computes silently looks identical to a hang and gets Ctrl-C'd

This PR adds an LrcActivityMonitor that samples the command's process activity every 5 seconds (CPU delta, I/O write delta, live-process count, aggregate state) and attaches it to LRC snapshots. On the wire the payload is LongRunningShellCommandActivity: since_last_activity as a google.protobuf.Duration plus a process tier whose state is an enum.

All reading of the OS process table lives in execute/lrc_activity/sampler.rs, which is the only place sysinfo is used. Pids cross that boundary as plain u32, so the monitor's accounting stays target-agnostic and wasm — which has no local process table — gets a sampler that observes nothing.

Two design decisions to review

  • The foreground pgid is sampled by the monitor, not by TerminalAttributesPoller. The existing fd poller is the obvious source, but it only runs during password-prompt detection: gated on notification settings, never started on the TUI surface, and stopped after its first positive reading — so it would essentially never be publishing during an LRC. The monitor calls tcgetpgrp on its own sampler instead, using the new ShellProcessInfo on TerminalModel. (foreground_pgid, ShellProcessInfo)

  • The activity payload is withheld until the tree has actually been sampled. Blocks register lazily on their first report(), so the first snapshot is built before the sampler has run; emitting an all-zero-but-present reading there would claim "0 live processes, 0 CPU" about a healthy command at exactly the poll where cancellation is decided. take_report returns None until a real sample exists, so that snapshot simply carries no activity section — an all-zero tree observed later is still reported, since "the tree exited" is a real answer. (Per review on the proto PR, absence of the payload now carries this meaning; the earlier in-band signals_unavailable marker is gone.) (take_report)

Incidental: uint32 → uint64 token counters

Re-pinning the proto past warpdotdev/warp-proto-apis#353 unavoidably picks up warpdotdev/warp-proto-apis#369, which widens ModelTokenUsage.total_tokens and the token_usage_by_category values from uint32 to uint64. Master had not absorbed that yet, so the adaptation rides along here.

It is handled at the proto boundary only — the persisted and displayed counters stay u32. Widening those would ripple through ~9 files including view rendering and a large number of tests, which belongs with #369's own migration rather than this PR.

  • Outbound (crates/persistence/src/model.rs): lossless u64::from(...). to_proto_combined now sums in u64, which also removes a latent overflow in the three-way add.
  • Inbound (app/src/ai/agent/conversation.rs): a narrow_token_count helper saturating via u32::try_from(..).unwrap_or(u32::MAX), and the accumulations use saturating_add. Saturating rather than unwrap_or_default() so an oversized count cannot surface as a plausible-looking small total. This matches the existing convention in get_conversation_usage.rs, which already narrows with u32::try_from.

Linked Issue

  • No linked issue — came out of a staging trace investigation (d2fc50d9, f6d8b53a, 0af93eee).
  • N/A — no UI surface changes, so no screenshots.

Testing

  • Not manually run via ./script/run — end-to-end behavior needs the server half, which is gated off in prod (see Rollout).

Unit coverage, all passing at the current tip (f911ee51e5):

  • cargo test -p warp --lib lrc_activity — 19 tests: activity detection via CPU/IO/pid churn, per-report reset, no report until first sample, exited-tree reporting, remote gating.
  • cargo test -p ai action_result — 12 tests: proto conversion incl. Duration round-trip and enum-state mapping.
  • cargo test -p warp --lib conversation:: (64) and cargo test -p persistence (26) — cover the token-counter conversions above.

Also clean: cargo check -p warp --all-targets, cargo fmt --check, cargo clippy -p warp --all-targets -- -D warnings, and the wasm gate cargo clippy --locked --target wasm32-unknown-unknown -p warp --lib -- -D warnings.

End-to-end replay evals (file-redirect, silent compute, genuine hang) are still outstanding.

Rollout

Proto dependency is unblocked: warpdotdev/warp-proto-apis#353 has merged, and Cargo.toml:352 now pins the resulting main commit 2bd5df2e3a73ada980d1e9c613ce369232e8c41d (previously the PR-branch rev 65ff6b9). Server counterpart: warpdotdev/warp-server#13955.

Client-side sampling is gated on FeatureFlag::LrcActivitySignal (dogfood-on), and reported as unsupported on Windows (no controlling-terminal process group to narrow the tree with) and on wasm (no local process table). The wire payload is additive with every field optional, and the server half is behind lrc_activity_signals (off in prod).

Agent Mode

  • Warp Agent Mode - This PR was created via Warp's AI Agent Mode

Co-Authored-By: Warp Agent agent@warp.dev

evelyn-with-warp and others added 4 commits August 5, 2026 16:31
The CLI subagent decides whether a long-running command is hung from a
terminal grid snapshot alone. Commands that redirect output to a file,
suppress it, or compute silently look identical to a hung process, so the
subagent Ctrl-Cs them.

This adds three tiers of client-collected evidence to every LRC snapshot:
terminal output hash, process-tree CPU/state/count, and growth of the files
the command writes to. A 1 Hz sampler runs only while an agent-monitored LRC
is in flight, so `seconds_since_last_activity` stays wall-clock accurate
regardless of the agent's poll cadence and ordinary terminal use pays
nothing.

The process and file tiers describe the local machine, so remote sessions
report the grid tier and set `signals_unavailable` rather than reporting
absence of evidence the client never had a way to gather.

Co-Authored-By: Warp Agent <agent@warp.dev>
`warp_tui` re-exports `RequestCommandOutputResult`, so its tool call label
tests construct the snapshot variant directly.

Co-Authored-By: Warp Agent <agent@warp.dev>
Every scalar in the activity payload encodes with implicit presence, so the
server can only distinguish "inspected and idle" from "not inspected" by
whether the process submessage itself is present.

A command is registered with the monitor when its first snapshot is built,
and the sampler only visits commands that are already registered, so the
first snapshot was reporting a present submessage whose counters were still
at their initial zero. That is the signature of a process tree with nothing
left running, produced for every healthy command on its very first poll —
precisely the false reading this whole change exists to prevent.

The process tier is now withheld until the sampler has actually observed it,
and `signals_unavailable` covers that window as well as remote sessions. An
all-zero tier that *was* observed is still reported, since an exited tree is
a real answer.

Also re-pins warp-proto-apis to 0a12f58b, which documents this presence
contract in attachment.proto.

Co-Authored-By: Warp Agent <agent@warp.dev>
Picks up the documented presence contract for the activity scalars and the
narrowed meaning of `signals_unavailable`. Comment-only upstream, so the
generated Rust API is unchanged.

Co-Authored-By: Warp Agent <agent@warp.dev>
@cla-bot cla-bot Bot added the cla-signed label Aug 6, 2026
@evelyn-with-warp

Copy link
Copy Markdown
Contributor Author

/oz-review

@warp-for-oss

warp-for-oss Bot commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

@evelyn-with-warp

I'm re-reviewing this pull request in response to a review request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@warp-for-oss warp-for-oss Bot left a comment

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.

Overview

This PR adds client-side long-running-command activity signals by sampling terminal output, process-tree activity, and detected redirect targets, then plumbing those signals through the agent API result types.

Concerns

  • Process churn detection only compares the number of live processes, so same-count PID replacement can still make active silent workloads look idle.
  • Redirect target tails expand data collection from terminal-visible output to arbitrary file contents; size/mtime-style metadata is sufficient for liveness without sending redirected data.
  • The tail reader can follow a redirect target that has been swapped to a symlink after sampling, undermining the no-symlink guarantee.

Security

  • Redirected file tails can include private exports or logs the user intentionally kept out of terminal output.
  • File::open follows symlinks, so the sampled regular-file check does not prevent reading a different local file at report time.

Verdict

Found: 0 critical, 3 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

// Process churn is itself progress: a build spawning and reaping
// compilers may never accumulate much CPU in any single process.
let count_changed = self.process.live_process_count != previous_count;
saw_activity |= cpu_delta > 0 || io_delta > 0 || count_changed;

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.

⚠️ [IMPORTANT] Comparing only live_process_count misses churn where one PID exits and another starts between samples with the same count; because new PIDs contribute no baseline CPU delta, those active silent workloads can still look idle. Track PID-set changes rather than just count changes.

path: file.path.to_string_lossy().into_owned(),
size_bytes: size,
size_delta_bytes: size as i64 - file.size_at_last_report as i64,
tail: read_tail(&file.path),

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.

⚠️ [IMPORTANT] [SECURITY] This sends the tail of every detected redirect target to the agent/server, including data exports a user intentionally redirected away from the terminal. Use size/mtime-style metadata for liveness and only include file contents behind a much narrower opt-in or log-specific gate.

fn read_tail(path: &Path) -> Option<String> {
use std::io::{Read, Seek, SeekFrom};

let mut file = std::fs::File::open(path).ok()?;

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.

⚠️ [IMPORTANT] [SECURITY] File::open follows symlinks even though sampling used symlink_metadata, so a target swapped to a symlink after sampling can make the snapshot read an arbitrary local file. Recheck immediately before opening or use a no-follow open path.

evelyn-with-warp and others added 4 commits August 7, 2026 18:29
The output and file activity tiers fired rarely in practice, and real
work reflects in the process tier anyway. Track the proto change that
removed them from LongRunningCommandActivity (warp-proto-apis
21dc6c9e46cb09b5724f134f881beda3d29cd68f):

- seconds_since_last_activity is now driven only by process-tree
  activity (CPU delta, IO write delta, or pid-set churn).
- signals_unavailable now only marks the first-snapshot case, before
  the sampler has observed the tree; it always accompanies an absent
  process submessage.
- Remote sessions stop reporting activity entirely (activity absent on
  the wire, same as an older client). Previously they reported only the
  output tier, and with that tier gone an ever-growing
  seconds_since_last_activity would falsely suggest idleness.

Delete the lrc_redirect module (it existed solely to find file-tier
write targets), drop LrcFileActivity and the removed-field conversions,
and rewrite the activity tests around the process tier, first-snapshot
withholding, and remote monitor gating.

Co-Authored-By: Warp Agent <agent@warp.dev>
@evelyn-with-warp
evelyn-with-warp marked this pull request as ready for review August 18, 2026 18:27
@warp-for-oss

warp-for-oss Bot commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

@evelyn-with-warp

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@warp-for-oss warp-for-oss Bot left a comment

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.

Overview

This PR adds LRC process-activity sampling, carries the activity payload through agent result conversions, and adds unit coverage for activity accumulation and wire conversion.

Concerns

  • Shell-executed long-running commands are still invisible to the sampler because the process tree excludes the shell itself; see the inline comment.
  • This is a user-facing behavioral change, but the PR description does not include screenshots, a screen recording, or an equivalent end-to-end replay artifact showing the file-redirect, silent-compute, and genuine-hang cases working. Please attach that evidence before landing.

Verdict

Found: 0 critical, 2 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

/// siblings rather than descendants of the group leader, so the group is matched
/// per process rather than by walking down from the leader.
fn command_process_tree(system: &System, shell_pid: Pid, foreground_pgid: Option<u32>) -> Vec<Pid> {
let descendants = descendants_of(system, shell_pid);

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.

⚠️ [IMPORTANT] Shell builtins and functions run in the shell process itself, but this tree starts with descendants only, so silent commands like while true; do :; done still report an empty process tree and can be cancelled as hung. Include the shell when it owns the foreground job, or otherwise account for shell-executed commands, before relying on descendants-only sampling.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — confirmed and fixed.

The trace was as you describe: descendants_of excludes the shell pid, so a builtin-only command left command_process_tree empty, collect_process_sample still returned Some with per_pid: [], and take_report emitted a present process tier with live_process_count: 0 and signals_unavailable: false. That is the encoding for an exited tree, so the busy command was reported as the strongest available evidence that nothing was running.

command_process_tree now compares the shell's own process group against the pty's foreground group and includes the shell when the two match, which is exactly the case where a builtin or shell function is the foreground job. When an external job holds the terminal the groups differ and behavior is unchanged, so the genuine "tree exited" reading is preserved. Added the_shell_joins_the_tree_when_it_holds_the_terminal and the_shell_stays_out_of_the_tree_when_another_job_holds_the_terminal to cover both directions.

Windows still falls back to descendants-only, since foreground_pgid has no controlling-terminal group to read there.

…terminal

Builtins and shell functions run in the shell process itself, so a
descendants-only tree reported an observed-empty process tree for a busy
command, which reads as an exited tree rather than as silent work.

Co-Authored-By: Warp <agent@warp.dev>
Comment on lines +202 to +206
state.system.refresh_processes_specifics(
ProcessesToUpdate::All,
true, /* remove_dead_processes */
ProcessRefreshKind::nothing().with_cpu().with_disk_usage(),
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This applies the expensive refresh kind (with_cpu().with_disk_usage()) to the entire process table at 1 Hz, when we only care about the command's tree. Could we split discovery from measurement?

// Phase A — discovery: enumerate the table, sample nothing.
state.system.refresh_processes_specifics(
    ProcessesToUpdate::All,
    true,
    ProcessRefreshKind::nothing(),
);
let tree = command_process_tree(&state.system, shell_pid, foreground_pgid(shell));

// Phase B — measurement: expensive kind, only for the tree.
state.system.refresh_processes_specifics(
    ProcessesToUpdate::Some(&tree),
    true,
    ProcessRefreshKind::nothing().with_cpu().with_disk_usage(),
);

That makes the expensive work O(command tree) instead of O(all processes), and matches the existing pattern: SystemInfo only ever measures scoped pids, and all_processes_refresh_kind() deliberately samples nothing when enumerating the full table. The delta logic is already compatible since it uses cumulative accumulated_cpu_time().

This is also a Windows stability concern, not just perf: see the comment on SystemInfo::all_processes_refresh_kind (app/src/system/info.rs) — per-process CPU sampling across the full table fires an IPI per pid and can trip the DPC watchdog and bugcheck high-core-count machines. That's why the only existing full-table scan in the codebase runs once per process lifetime and samples nothing. Nothing gates this path off Windows today (only foreground_pgid/getpgid are unix-only). The two-phase split resolves that structurally, since CPU/disk are only ever sampled for tree members.

use crate::terminal::model::terminal_model::ShellProcessInfo;

/// How often liveness signals are sampled while a monitored command is active.
pub const SAMPLE_INTERVAL: Duration = Duration::from_secs(1);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How'd we come to the 1s interval? We have an existing process sample that runs on the Warp process and it runs every 5s. I'd feel a bit better about following the existing precedent as to not regress anything performance-wise. Also, LRC snapshots don't come that frequently, so I think the extra time won't affect quality much.

let shell = shell?;
let shell_pid = Pid::from_u32(shell.pid);

let mut state = self.state.lock();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The monitor lock is held across the entire refresh + tree walk + getpgid syscalls here, and report() takes this same lock while the terminal model FairMutex is held. So worst case, a snapshot build stalls the terminal lock for a full tick's duration.

With the refresh scoped down this is probably cheap enough to be moot, but shrinking the lock scope (refresh into locals, then lock briefly to fold) — or a comment explaining why the bound is acceptable — would make the terminal-lock exposure explicit.

Comment on lines +191 to +192
let keep_sampling = !state.blocks.is_empty() || state.armed_actions > 0;
state.sampler_running = keep_sampling;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: once keep_sampling goes false, state.system retains a full process-table snapshot for the lifetime of the executor. Worth clearing it here (e.g. state.system = System::new()) so we only hold that memory while actively monitoring.

evelyn-with-warp and others added 2 commits August 24, 2026 16:47
Re-pins warp-proto-apis to f89d145, which merges proto main (web search cost) into the LRC activity proto branch.

Co-Authored-By: Warp <agent@warp.dev>

@danielpeng2 danielpeng2 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorry, I didn't mean to approve this yet. Can we make sure these changes are gated behind a feature flag?

Another thing we should do is make sure these are tested on all platforms (MacOS, Windows, Linux) before enabling in prod, since the process sampling code will work differently on different platforms. There should be folks on the team with Windows and Linux laptops that we can ask to sanity check.

@evelyn-with-warp

Copy link
Copy Markdown
Contributor Author

Sorry, I didn't mean to approve this yet. Can we make sure these changes are gated behind a feature flag?

Another thing we should do is make sure these are tested on all platforms (MacOS, Windows, Linux) before enabling in prod, since the process sampling code will work differently on different platforms. There should be folks on the team with Windows and Linux laptops that we can ask to sanity check.

Gated the change in 9f487ca

evelyn-with-warp and others added 3 commits August 26, 2026 12:33
This reverts commit a7034d6.

Co-Authored-By: Warp <agent@warp.dev>
evelyn-with-warp added a commit to warpdotdev/warp-proto-apis that referenced this pull request Aug 30, 2026
…hot (#353)

## Description
Adds a `LongRunningShellCommandActivity` message to
`LongRunningShellCommandSnapshot` (`activity`, field 6, additive and
optional) in `apis/multi_agent/v1/attachment.proto`, carrying
client-sampled liveness data for a long-running command:

- **`since_last_activity`** — wall-clock `Duration` since the process
tree last showed activity (CPU consumption, I/O writes, or process
churn). Sampled client-side every ~5s independently of the agent's
polling cadence, so it stays accurate even when agent reads are far
apart. Resolution is the sampling interval.
- **`process`** — `ProcessActivity` submessage with the CPU-time delta,
a coarse aggregate tree `State` enum (`RUNNING`, `SLEEPING`,
`DISK_WAIT`, `STOPPED`, `ZOMBIE`, `UNKNOWN`), live process count, and
I/O-write-bytes delta. The `*_delta` fields accumulate over the whole
window since the previous snapshot, not just the latest sample.

Collection status is conveyed by message-level presence rather than
flags: an absent `activity` means no reading was taken (unsupported
client, remote session, or nothing sampled yet); an absent `process`
means that tier was not collected; an all-zero `ProcessActivity` is a
real reading (a quiet or exited tree). Scalar presence is deliberately
not part of the contract because the Rust bindings are generated through
a proto3 rewrite, where unset scalars are indistinguishable from
defaults.

## Why we need this
Make the changes in client and server work:
client change: warpdotdev/warp#14777
server change: warpdotdev/warp-server#13955

## Sensitive Fields
The new messages contain no string fields (process state is a closed
enum), so there is nothing to mark `(sensitive)`.

- [x] I have ensured that all string fields that contain sensitive
information are marked as `sensitive`.

Co-Authored-By: Warp Agent <agent@warp.dev>

---------

Co-authored-by: Warp Agent <agent@warp.dev>
Co-authored-by: Oz <oz-agent@warp.dev>
evelyn-with-warp and others added 2 commits August 29, 2026 20:32
Re-pin warp-proto-apis to 2bd5df2e3a73ada980d1e9c613ce369232e8c41d, the
squash merge of warpdotdev/warp-proto-apis#353, now that it has landed on
main. This also pulls in #369, which widens ModelTokenUsage.total_tokens
and token_usage_by_category from uint32 to uint64.

Adapt at the proto boundary rather than widening the persisted token
counters: convert losslessly on the way out in persistence, and narrow
saturatingly on the way in when accumulating conversation usage.

Co-Authored-By: Warp <agent@warp.dev>
`sysinfo` is a `cfg(not(target_family = "wasm"))` dependency, but
`lrc_activity` imported it unconditionally, breaking both wasm CI jobs.

Move every use of the process table into `lrc_activity/sampler.rs`, which
provides the real sampler off wasm and one that observes nothing on wasm.
Pids cross that boundary as plain `u32`, so the monitor's accounting — and
its tests — stay target-agnostic.

Liveness signals are now also reported as unsupported on wasm, which has no
local process tree to describe.

Co-Authored-By: Warp <agent@warp.dev>
@evelyn-with-warp
evelyn-with-warp merged commit 76cfd2c into master Aug 30, 2026
31 checks passed
@evelyn-with-warp
evelyn-with-warp deleted the evelyn/fix-lrc-hang branch August 30, 2026 05:22
TranscriptionFactory pushed a commit to TranscriptionFactory/warp that referenced this pull request Aug 31, 2026
…nds (warpdotdev#14777)

In some tbench traces with LRC, the CLI subagent judges a long-running
command's liveness from the terminal grid alone, so a command that
redirects to a file, suppresses output, or computes silently looks
identical to a hang and gets Ctrl-C'd

This PR adds an `LrcActivityMonitor` that samples the command's process
activity every 5 seconds (CPU delta, I/O write delta, live-process
count, aggregate state) and attaches it to LRC snapshots. On the wire
the payload is `LongRunningShellCommandActivity`: `since_last_activity`
as a `google.protobuf.Duration` plus a `process` tier whose `state` is
an enum.

All reading of the OS process table lives in
`execute/lrc_activity/sampler.rs`, which is the only place `sysinfo` is
used. Pids cross that boundary as plain `u32`, so the monitor's
accounting stays target-agnostic and wasm — which has no local process
table — gets a sampler that observes nothing.

- **The foreground pgid is sampled by the monitor, not by
`TerminalAttributesPoller`.** The existing fd poller is the obvious
source, but it only runs during password-prompt detection: gated on
notification settings, never started on the TUI surface, and stopped
after its first positive reading — so it would essentially never be
publishing during an LRC. The monitor calls `tcgetpgrp` on its own
sampler instead, using the new `ShellProcessInfo` on `TerminalModel`.
([`foreground_pgid`](https://github.com/warpdotdev/warp/blob/f911ee51e5/app/src/ai/blocklist/action_model/execute/lrc_activity/sampler.rs#L212-L233),
[`ShellProcessInfo`](https://github.com/warpdotdev/warp/blob/f911ee51e5/app/src/terminal/model/terminal_model.rs#L535-L551))

- **The activity payload is withheld until the tree has actually been
sampled.** Blocks register lazily on their first `report()`, so the
first snapshot is built before the sampler has run; emitting an
all-zero-but-present reading there would claim "0 live processes, 0 CPU"
about a healthy command at exactly the poll where cancellation is
decided. `take_report` returns `None` until a real sample exists, so
that snapshot simply carries no `activity` section — an all-zero tree
observed *later* is still reported, since "the tree exited" is a real
answer. (Per review on the proto PR, absence of the payload now carries
this meaning; the earlier in-band `signals_unavailable` marker is gone.)
([`take_report`](https://github.com/warpdotdev/warp/blob/f911ee51e5/app/src/ai/blocklist/action_model/execute/lrc_activity.rs#L253-L276))

Re-pinning the proto past warpdotdev/warp-proto-apis#353 unavoidably
picks up warpdotdev/warp-proto-apis#369, which widens
`ModelTokenUsage.total_tokens` and the `token_usage_by_category` values
from `uint32` to `uint64`. Master had not absorbed that yet, so the
adaptation rides along here.

It is handled **at the proto boundary only** — the persisted and
displayed counters stay `u32`. Widening those would ripple through ~9
files including view rendering and a large number of tests, which
belongs with warpdotdev#369's own migration rather than this PR.

- Outbound (`crates/persistence/src/model.rs`): lossless
`u64::from(...)`. `to_proto_combined` now sums in `u64`, which also
removes a latent overflow in the three-way add.
- Inbound (`app/src/ai/agent/conversation.rs`): a `narrow_token_count`
helper saturating via `u32::try_from(..).unwrap_or(u32::MAX)`, and the
accumulations use `saturating_add`. Saturating rather than
`unwrap_or_default()` so an oversized count cannot surface as a
plausible-looking small total. This matches the existing convention in
`get_conversation_usage.rs`, which already narrows with `u32::try_from`.

- [ ] No linked issue — came out of a staging trace investigation
(`d2fc50d9`, `f6d8b53a`, `0af93eee`).
- [ ] N/A — no UI surface changes, so no screenshots.

- [ ] Not manually run via `./script/run` — end-to-end behavior needs
the server half, which is gated off in prod (see Rollout).

Unit coverage, all passing at the current tip (`f911ee51e5`):
- `cargo test -p warp --lib lrc_activity` — 19 tests: activity detection
via CPU/IO/pid churn, per-report reset, no report until first sample,
exited-tree reporting, remote gating.
- `cargo test -p ai action_result` — 12 tests: proto conversion incl.
Duration round-trip and enum-state mapping.
- `cargo test -p warp --lib conversation::` (64) and `cargo test -p
persistence` (26) — cover the token-counter conversions above.

Also clean: `cargo check -p warp --all-targets`, `cargo fmt --check`,
`cargo clippy -p warp --all-targets -- -D warnings`, and the wasm gate
`cargo clippy --locked --target wasm32-unknown-unknown -p warp --lib --
-D warnings`.

End-to-end replay evals (file-redirect, silent compute, genuine hang)
are still outstanding.

Proto dependency is unblocked: warpdotdev/warp-proto-apis#353 has
merged, and `Cargo.toml:352` now pins the resulting `main` commit
`2bd5df2e3a73ada980d1e9c613ce369232e8c41d` (previously the PR-branch rev
`65ff6b9`). Server counterpart: warpdotdev/warp-server#13955.

Client-side sampling is gated on `FeatureFlag::LrcActivitySignal`
(dogfood-on), and reported as unsupported on Windows (no
controlling-terminal process group to narrow the tree with) and on wasm
(no local process table). The wire payload is additive with every field
optional, and the server half is behind `lrc_activity_signals` (off in
prod).

- [x] Warp Agent Mode - This PR was created via Warp's AI Agent Mode

<!-- CHANGELOG-NONE: user-visible behavior only changes when the
server-side lrc_activity_signals flag is enabled, so the changelog entry
belongs with that rollout rather than this client release. -->

Co-Authored-By: Warp Agent <agent@warp.dev>

---------

Co-authored-by: Warp Agent <agent@warp.dev>
(cherry picked from commit 76cfd2c)
zhangyu1818 pushed a commit to zhangyu1818/warply that referenced this pull request Aug 31, 2026
…arpdotdev#14777)

Adapted port of upstream LRC activity monitoring for this ACP-only fork:
local process-tree sampler (CPU/IO/pid churn, foreground-pgid narrowing)
attached to long-running command snapshots, ShellProcessInfo on
TerminalModel, LrcActivitySignal wired through the cargo-feature
pipeline, and activity rendered into the ACP follow-up prompt Display
boundary replacing upstream's proto wire path. Proto re-pin, server-API
conversion files, token-counter widening, and warp_tui tests omitted
(fork-absent surfaces).
evelyn-with-warp pushed a commit that referenced this pull request Sep 1, 2026
## Description
Re-enable local LRC process-activity sampling on Windows by dropping the
`target_os = "windows"` exclusion from
`lrc_activity_signals_supported()`. Wasm stays excluded;
`FeatureFlag::LrcActivitySignal` still gates collection.

Windows was turned off in #14777 because the shell-in-tree tests were
Unix-only. The sampler already compiles there: `foreground_pgid` is
always `None`, so the tree is descendants-only, matching the Unix
missing-pgid fallback. Two-phase sysinfo refresh is unchanged (CPU/disk
only on the command tree).

No proto or warp-server changes. `LongRunningShellCommandActivity` and
server ingestion already exist with no OS filter.

## Linked Issue
- [ ] The linked issue is labeled `ready-to-spec` or
`ready-to-implement`. #15689 is labeled `factory:wilson` only.
- [ ] Where appropriate, screenshots or a short video of the
implementation are included below (especially for user-visible or UI
changes). Non-visual sampling change; visual proof was not requested.

#15689

## Testing
- [ ] I have manually tested my changes locally with `./script/run`. Not
a UI change.

Unix: `cargo nextest run -p warp -E 'test(execute::lrc_activity::tests)
or test(execute::shell_command::tests)'` — 20 passed.
Format: `./script/format`. Clippy: `cargo clippy -p warp --all-targets
--tests -- -D warnings`.

@evelyn-with-warp confirmed the feature on a Windows machine, including
expected debug-log output during testing.

## Agent Mode
- [ ] Warp Agent Mode - This PR was created via Warp's AI Agent Mode

CHANGELOG-NONE

<!-- warp:pr-description-artifacts start -->
<!-- warp:pr-description-artifacts end -->

---------

Co-authored-by: warp-agent-staging[bot] <240773466+warp-agent-staging[bot]@users.noreply.github.com>
TranscriptionFactory pushed a commit to TranscriptionFactory/warp that referenced this pull request Sep 28, 2026
## Description
Re-enable local LRC process-activity sampling on Windows by dropping the
`target_os = "windows"` exclusion from
`lrc_activity_signals_supported()`. Wasm stays excluded;
`FeatureFlag::LrcActivitySignal` still gates collection.

Windows was turned off in warpdotdev#14777 because the shell-in-tree tests were
Unix-only. The sampler already compiles there: `foreground_pgid` is
always `None`, so the tree is descendants-only, matching the Unix
missing-pgid fallback. Two-phase sysinfo refresh is unchanged (CPU/disk
only on the command tree).

No proto or warp-server changes. `LongRunningShellCommandActivity` and
server ingestion already exist with no OS filter.

## Linked Issue
- [ ] The linked issue is labeled `ready-to-spec` or
`ready-to-implement`. warpdotdev#15689 is labeled `factory:wilson` only.
- [ ] Where appropriate, screenshots or a short video of the
implementation are included below (especially for user-visible or UI
changes). Non-visual sampling change; visual proof was not requested.

warpdotdev#15689

## Testing
- [ ] I have manually tested my changes locally with `./script/run`. Not
a UI change.

Unix: `cargo nextest run -p warp -E 'test(execute::lrc_activity::tests)
or test(execute::shell_command::tests)'` — 20 passed.
Format: `./script/format`. Clippy: `cargo clippy -p warp --all-targets
--tests -- -D warnings`.

@evelyn-with-warp confirmed the feature on a Windows machine, including
expected debug-log output during testing.

## Agent Mode
- [ ] Warp Agent Mode - This PR was created via Warp's AI Agent Mode

CHANGELOG-NONE

<!-- warp:pr-description-artifacts start -->
<!-- warp:pr-description-artifacts end -->

---------

Co-authored-by: warp-agent-staging[bot] <240773466+warp-agent-staging[bot]@users.noreply.github.com>
(cherry picked from commit 09f0c2b)
zhangyu1818-bot added a commit to zhangyu1818/warply that referenced this pull request Oct 1, 2026
…arpdotdev#14777)

Adapted port of upstream LRC activity monitoring for this ACP-only fork:
local process-tree sampler (CPU/IO/pid churn, foreground-pgid narrowing)
attached to long-running command snapshots, ShellProcessInfo on
TerminalModel, LrcActivitySignal wired through the cargo-feature
pipeline, and activity rendered into the ACP follow-up prompt Display
boundary replacing upstream's proto wire path. Proto re-pin, server-API
conversion files, token-counter widening, and warp_tui tests omitted
(fork-absent surfaces).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants