Repository navigation
feat(timeline): tool calls read as the web showed them, on every surface - #944
Conversation
Upstream T3 Code closed the assistant's text segment on every tool update, so a background command reporting progress cut a table in two (#13386). The MC keys streamed text by message in every adapter (ACP, Codex, Claude, Pi, Muse) and never closes it on a tool update, so the scenario passes as it stands; it now runs on the MC against the ACP fake. Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: adeebahmad01 <52380344+adeebahmad01@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Some providers report a command completed when its output says the file or command was missing, or that it exited non-zero. Clients never get a command's output, so the MC reads it as the command finishes and sets outputIndicatesFailure, which the wire already forwards. Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A tool call's details now show what the call did, by type: - a command's exit, as "Process exited with code N" - a file search's matches - a web search's results, linked only when the address is http(s) - a to-do list's done and open steps - a changed file's relative path, counts, renames and "Open diff" - an image the agent viewed, signed as a workspace file only when it is inside the workspace An answered question previews its answer on the call's row, and a command echoed back as its output is not shown twice. The changed-files card hides zero counts. Right-clicking a file offers the file menu, but only when the workspace can be located. A long open group scrolls inside its own window: it reopens where the user left it, and follows new calls only while the user is at its end. Untags 15 @desktop scenarios and 4 @desktop @mobile ones (now @backlog-mobile) in features/timeline/tool-calls.feature. Co-authored-by: Alex Southwell <saphid@gmail.com> Co-authored-by: Bilal Bakr <62337003+Bil0000@users.noreply.github.com> Co-authored-by: Craig <11655707+CodeZeno@users.noreply.github.com> Co-authored-by: Henry Zhang <113233555+caezium@users.noreply.github.com> Co-authored-by: Jake Leventhal <jakeleventhal@me.com> Co-authored-by: Jono Kemball <Noojuno@users.noreply.github.com> Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com> Co-authored-by: maria <maria@kuuro.net> Co-authored-by: Mark Griffin <mrmg@deflexion.net> Co-authored-by: Mike Olson <mwolson@member.fsf.org> Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com> Co-authored-by: ss <69873514+sandersonstabo@users.noreply.github.com> Co-authored-by: Theo Browne <me@t3.gg> Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com> Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Co-authored-by: xxashxx-svg <xxanshxx9@gmail.com> Co-authored-by: Yash Singh <saiansh2525@gmail.com> Co-authored-by: Ymit24 <christianryansmith@gmail.com> Co-authored-by: Yukun Shan <92423096+nateEc@users.noreply.github.com> Co-authored-by: Zortos <zortosdev@proton.me> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… agent did A settled turn's group of calls now collapses to one sentence (commands and file changes first, at most two kinds named, the rest counted, failed calls apart, a ✗ when one failed) and opens into each call: a command as it was typed with its exit code, a file relative to the workspace. HAL-C2's own tools are named for what they do, and a completed command whose output says it failed, or a HAL-C2 tool that answered with an error, shows as failed. Groups in a stopped or failed turn start open. Co-authored-by: Bilal Bakr <62337003+Bil0000@users.noreply.github.com> Co-authored-by: Jake Leventhal <jakeleventhal@me.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: maria <maria@kuuro.net> Co-authored-by: Theo Browne <me@t3.gg> Co-authored-by: Yash Singh <saiansh2525@gmail.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A provider can report a command as completed even though its output says it failed (a non-zero exit code, "command failed"). The MC drops command output from the wire, so clients cannot tell. TurnWriter now sets outputIndicatesFailure on the item when CommandOutput.failed?/1 reads a failure from the output, as the legacy toolOutput helper did. Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… did
Activity group summaries now cover HAL-C2's own tools (display names, every
provider's name prefix, other servers left alone), integrations and named
sources, thoughts, the "other actions" count, "Tried to" for all-failed
groups, and failures read from a command's output or a HAL-C2 tool's error
answer. A running turn's last command reads by the program it runs ("Running
git", "Ran npm", "Failed cargo") through shells and set-up wrappers, with the
raw command kept in the call's details. The live verb is folded into the
TimelineModel property so a settle redraws only when the label changes.
Untags the desktop summary and command-naming scenarios in
features/timeline/tool-calls.feature.
Co-authored-by: Bilal Bakr <62337003+Bil0000@users.noreply.github.com>
Co-authored-by: Jake Leventhal <jakeleventhal@me.com>
Co-authored-by: Jono Kemball <Noojuno@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: maria <maria@kuuro.net>
Co-authored-by: Mike Olson <mwolson@member.fsf.org>
Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com>
Co-authored-by: Theo Browne <me@t3.gg>
Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com>
Co-authored-by: xxashxx-svg <xxanshxx9@gmail.com>
Co-authored-by: Yash Singh <saiansh2525@gmail.com>
Co-authored-by: Yukun Shan <92423096+nateEc@users.noreply.github.com>
Co-authored-by: Zortos <zortosdev@proton.me>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…backlog # Conflicts: # apps/server-ex/lib/hal_c2/orchestration/turn_writer.ex # features/timeline/tool-calls.feature
A finished turn's group holding one call showed a summary of it ("Ran 1
command") on the TUI, where the web and the desktop show the call's own
line. Only groups of two or more items are summarised now; a lone call is
its row again, its failure marked on it.
The two summary outlines exercised the singular wording through lone
calls, so each group now also holds a thought, which a summary does not
count, and both outlines pass on the desktop and the TUI.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Both merged desktop branches defined "the user opens the call's details",
so every scenario using it was ambiguous. The details steps keep it and
open a settled turn's fold first. A running turn's latest command now
reads by its program ("Ran npm"), so "the details show the command once"
no longer expects the line to read the command itself.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The phone drew a thread's tool calls exactly as the desktop does, so none of tool-calls.feature ran on it, and the activity log the phone had always had (one live row while a turn runs, long press to copy, failures and markers on their own rows, no lost or carded calls) was gone. TimelineModel gains a brief mode, which ThreadStore sets on every timeline when the phone layout shows (MobileApp's layout.desktop interceptor). In it, a running turn ends in a Thinking row unless its latest call is running or succeeded, a settled turn drops calls that never ended, the workspace's preparation is left out, and a delegation whose subagent has a card is not listed again. The Timeline brick copies a call or a failure on a long press and says "Copied" for a moment, and the failure row always shows its time there. The desktop keeps its behaviour by default. The phone now runs timeline/tool-calls.feature through ToolCallSteps.cpp, which drives the real phone screen against the fake MC. Co-authored-by: Alex Southwell <saphid@gmail.com> Co-authored-by: Chris Deeming <chris@xenforo.com> Co-authored-by: Gabriel De Andrade <30420087+gabrielelpidio@users.noreply.github.com> Co-authored-by: James C <134711311+Exotic209093@users.noreply.github.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: Julius Marminge <julius@mac.lan> Co-authored-by: maria <maria@kuuro.net> Co-authored-by: maria-rcks <maria@kuuro.net> Co-authored-by: Mina Yacoub <56601613+myacoub91@users.noreply.github.com> Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com> Co-authored-by: Theo Browne <me@t3.gg> Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Co-authored-by: Yash Singh <saiansh2525@gmail.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The phone's activity log (TimelineModel::brief) now runs under the same command model as the desktop timeline: a settled turn leaves out calls that never ended, and a running latest turn ends in "Thinking" unless its last call is running or went well, or its reply is streaming. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ff opens The phone's work-group summaries had no steps for the kinds of work and HAL-C2's own tools, and a turn's changes or one changed file had nowhere to open on the phone. The Diff tab now takes the phone's thread screen like the Files tab does, and Back closes it. The phone's steps cover every kind of work the desktop's do, a group led by a thought, and opening a turn's diff or a single file's. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…pp's own icon The MC names each call's own icon (a website, an integration's themed logo, an app on the environment's machine), but the Qt clients only ever drew the kind's generic icon. TimelineModel now gives each call an icon address for each appearance, chosen as the old mobile app did: the provider's favicon, a site whose favicon is illegible in that appearance, then the origin's favicon.ico. An app's icon is signed through assets.createUrl like an attachment, and only the work rows showing it redraw when it lands. The timeline draws the call's own icon over the kind's once it loads, and keeps the kind's when it cannot load. The phone's scenario now runs green, and the timeline property signs the app's icon among its commands. Co-authored-by: Alex Southwell <saphid@gmail.com> Co-authored-by: Chris Deeming <chris@xenforo.com> Co-authored-by: Felipe Franco <67972456+fe-franco@users.noreply.github.com> Co-authored-by: Gabriel De Andrade <30420087+gabrielelpidio@users.noreply.github.com> Co-authored-by: James C <134711311+Exotic209093@users.noreply.github.com> Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-authored-by: maria <maria@kuuro.net> Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com> Co-authored-by: Theo Browne <me@t3.gg> Co-authored-by: Yash Singh <saiansh2525@gmail.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
olafura
left a comment
There was a problem hiding this comment.
3 findings: 1 medium, 2 low.
This takes the last 65 tool-call scenarios off backlog across the MC, the shared Qt timeline, the phone's brief mode and the TUI. It matches the description, and the brief mode, the icon signing and the HAL-C2 tool inventory read correctly. The biggest risk is the MC's new outputIndicatesFailure heuristic. The MC writes it into the event log for good. It reads only the first 32 KB, so it misses an exit code printed at the end of a long output. It also marks a successful cat or grep as failed when the text it prints contains a failure phrase.
Not run
- Desktop and phone tst_Features, and the tst_TimelineModelProp property test: they need a full Qt build; the PR reports them passing, and I reviewed the code by reading it
Blocked on the reviewing machine (a setup problem there, not in this change)
- mise exec -- mix test test/hal_c2/turn_writer_test.exs: (Mix) Can't continue due to errors on dependencies: bandit, slack_elixir, tz and others aren't available ("run mix deps.get") in this review checkout
Ran 2 checks
bun test src/toolSummary.test.ts (apps/tui): 11 pass, 0 failelixir -r lib/hal_c2/orchestration/command_output.ex: failed? on 63 KB of output ending in 'Exit code 1', and on a source line containing 'ENOENT': false for the long output (trailer missed), true for the ENOENT line (false positive)
Findings (3)
- 🟡 Medium:
apps/server-ex/lib/hal_c2/orchestration/command_output.ex:20— The comment says a long output shows how it ended near its start, but trailers such as `<e… - 🔵 Low:
apps/server-ex/lib/hal_c2/orchestration/command_output.ex:9— The@failurespatterns match any output that merely contains the words. A completed `cat… - 🔵 Low:
apps/desktop-qt/src/native/TimelineModel.cpp:1505— Each time aworkspace-file:image is signed, every work row redraws, not just the rows t…
The phone's timeline has one mode: main's TimelineModel::Folds::Phone now also carries what this branch's `brief` did (a running turn ends in one live row, a settled turn drops calls that never ended, the workspace's preparation and carded delegations are left out), and the `brief` flag and ThreadStore::setBriefTimelines are gone. The phone's diff, copy and setEntity/changeEntity changes take main's one implementation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
olafura
left a comment
There was a problem hiding this comment.
No findings.
Since the last review the only new commit is the merge with main. It folds this branch's phone "brief" mode into main's TimelineModel::Folds::Phone, and the merge holds together. In project() the brief rules are now gated on phone(): settled turns drop calls that never ended, carded delegations and workspace preparation are hidden, and a running turn ends in one live row. Main's failed-turn rule (one call per row) and its keep-the-first-reply rule are kept alongside. ThreadStore::setFolds and MobileApp's layout interceptor replace setBriefTimelines. tst_TimelineModelProp folds the separate brief() property into the main property with a Layout command, so both modes and the switch between them are now exercised. Hold-to-copy still goes through Shell.setClipboardText, so copy() now returning a string does not affect it. Nothing references brief any more.
None of my three earlier findings is fixed; the code is the same:
CommandOutput.failed?/1reads only the first 32 KB, so an exit-code trailer at the end of a long output is missed.- Its patterns match any output that merely contains words like ENOENT, and the result is written to the event log.
setAttachmentUrlstill redraws every work row for eachworkspace-file:signing.
The first two are the biggest risk, because the wrong outputIndicatesFailure is persisted.
Not run
- mise prop:desktop tst_TimelineModelProp and the desktop/phone tst_Features runs after the merge: A full Qt build is needed; the merge was checked by reading the model next to its RapidCheck spec, which match.
- mix features timeline/*.feature: The MC files in this PR are unchanged since the last review; turn_writer.ex changed only through main's commits.
Ran 2 checks
git diff 2c05b094a HEAD restricted to the PR's files: The only change is the merge with main; command_output.ex is unchanged, and so is the setAttachmentUrl branch for workspace-file images.grep for brief/setBrief/briefChanged in apps/desktop-qt and apps/mobile-qt: Nothing left over from the rename.
…at both ends A long output's exit code sits at its end, which the first 32 KB missed, and a file that merely mentioned ENOENT or "command not found" marked a successful read failed, for good, in the event log. The check now reads the first and last 16 KB and counts a missing file or command only on a line that starts with it or with a program's name and a colon. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
olafura
left a comment
There was a problem hiding this comment.
1 finding: 1 medium.
The new commit narrows CommandOutput.failed?/1: a missing-file or missing-command phrase now counts only at the start of a line or after a program: prefix, and a long output is read at both ends, so an exit code at the end is still seen. My previous review listed no open findings, so there is nothing to carry over. Reading both ends is correct. The new prefix only allows a bare word, though, so path-prefixed shell errors such as /bin/bash: line 1: foo: command not found and /usr/bin/env: 'python': No such file or directory no longer mark the command failed.
Not run
- mise exec -- mix test test/hal_c2/turn_writer_test.exs: The review checkout has no fetched Hex deps (
mix runstopped with "run mix deps.get"), and fetching them was outside the review's scope.
Ran 1 check
elixir $TMPDIR/t2.exs (the new @missing regex on sample lines):/bin/bash: line 1: … command not found,./run.sh: …and/usr/bin/env-style lines do not match;bash: line 1: …,Makefile:12: no such fileandnote: … ENOENTdo
`/bin/bash: line 1: foo: command not found` and `/usr/bin/env: 'python': No such file or directory` read as a success, since only a bare program name was allowed before the colon. A program's path is allowed now, and the colon must be followed by a space, which keeps out a grep match's `path:line:` (`Makefile:12: …`). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
olafura
left a comment
There was a problem hiding this comment.
1 finding: 1 medium.
The new commit lets a diagnostic that names a program by its path (/bin/bash: line 1: …, ./run.sh: …, /usr/bin/env: …) fail a finished command, which fixes my previous finding. The new prefix now accepts / and ., though, so a grep or rg match printed without line numbers and followed by a space now reads as a failure. That is the false positive the old prefix kept out.
Blocked on the reviewing machine (a setup problem there, not in this change)
- mise exec -- mix test test/hal_c2/turn_writer_test.exs: deps missing in this checkout: "the dependency is not available, run mix deps.get" (e.g. tz)
Ran 2 checks
git diff 888dc64d7 HEAD: one commit: the@missingregex prefix changed, plus 3 new failure cases and 1 new success case in turn_writer_test.exselixir -e (new @missing regex against grep-style lines):lib/foo.ex: {:error, :enoent} -> :okandsrc/a.ts: // no such file herematch (would fail the command);lib/foo.ex:12: enoentdoes not
`grep -r` and piped `rg` print `path: content`, which the path prefix read as a diagnostic. A path counts now only when what follows it reads as a shell's or env's report (`line 1:`, `'python':`); a bare program name counts as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
olafura
left a comment
There was a problem hiding this comment.
Approved
1 finding: 1 low.
The new commit narrows CommandOutput's diagnostic prefix. It now takes either a bare program name followed by : or a path followed by : line N: or a quoted name, so search output like path:content from grep without -n, or from piped rg, no longer marks a command failed. That addresses my previous finding. The main risk left is at the edges of the prefix rule: grep matches in extensionless top-level files can still count as failures, and older bash's /bin/bash: foo: command not found now goes unnoticed. Neither should block the merge.
Blocked on the reviewing machine (a setup problem there, not in this change)
- mise exec -- mix test test/hal_c2/turn_writer_test.exs: the dependency is not available, run "mix deps.get" (tz missing from this review checkout); I did not fetch deps
Ran 2 checks
git diff 3b40f466a HEAD: one commit: the regex change in command_output.ex plus two new negative cases in turn_writer_test.exselixir script matching the new @missing regex against sample lines:lib/foo.ex: {:error, :enoent}→ no match (the fix works);/bin/bash: line 1: ...and/usr/bin/env: 'python': ...→ match;Makefile: # no such file here→ match (false positive);/bin/bash: foo: command not found→ no match (missed)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
olafura
left a comment
There was a problem hiding this comment.
Approved
No findings.
The only new commit since my last review (4549da8) changes no code. It extends the comment above @missing in command_output.ex to name the two edges I raised earlier: a hit in an extensionless file at the search root, like Makefile: # no such file, still marks the command failed, and pre-5.1 /bin/bash: foo: command not found does not. My earlier finding is settled by accepting both edges and documenting them. The comment matches the regex, so both trade-offs are now explicit and nothing new is open.
Not run
- mix test for turn_writer and command_output: The new commit only changes a comment, so the tests have nothing new to cover.
Ran 1 check
git diff 72f7f0bd9a7a9cd688e42331437868fa1586cbcc HEAD: One file changed: a 4-line comment edit in apps/server-ex/lib/hal_c2/orchestration/command_output.ex. The regex and the code are unchanged.
The result's tap emitted linkActivated with one argument and its step spied on a one-argument signal the brick does not have, so the scenario could not see the link open. It passes `external` (false) as every other link does, and the step listens for the signal the brick declares. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
olafura
left a comment
There was a problem hiding this comment.
Approved
No findings.
Since the last review, one commit landed: tapping a web result now emits the brick's two-argument linkActivated(link, false), the same way every other link in the timeline does. Its step now spies on linkActivated(QString, bool). Before this, the one-argument call didn't match the signal the brick declares, so the scenario could never see the link open. The fix is correct and consistent with the brick's other link paths. The previous review raised no findings, so nothing carries over, and the new commit introduces no new risk.
Not run
- tst_Features for the timeline details web-result scenario: needs a desktop-qt native build. The change is a two-line signature match that reading the code settles.
Ran 2 checks
git diff 4549da83e..HEAD: one commit: Timeline.qml:1832 and TimelineDetailsSteps.cpp:267grep linkActivated( in desktop-qt and mobile-qt QML and tests: every call to root.linkActivated in Timeline.qml and Markdown.qml now passes two arguments, matchingsignal linkActivated(string link, bool external)at Timeline.qml:74. The only QSignalSpy on it uses the (QString, bool) signature.
Main's prop test starts runs in "starting"; the model gave only running turns the live thinking row, while TimelineModel (as the web) counts a starting session as working. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
olafura
left a comment
There was a problem hiding this comment.
Approved
No findings.
Since the last review there is one test-only commit. In the property model, the phone's live "Thinking" row now shows for a latest turn that is starting as well as running. That matches TimelineModel.cpp:1327, where kRunning includes starting, and it matches main's prop generator, which starts runs as starting. The previous review had no findings, so there is nothing to follow up. The remaining gap is small: kRunning also counts preparing and waiting, which the model doesn't handle, but the generator never produces those statuses today, so the property can't diverge from the code yet.
Not run
- mise prop:desktop TimelineModelProp: This is a two-line change to the model predicate, and reading it against the implementation's status set settled it. Building the Qt prop suite wasn't needed for that.
Ran 2 checks
git diff 48eeb0e64 HEAD: One file changed: apps/desktop-qt/tests/prop/tst_TimelineModelProp.cpp, 4 lines added and 3 removedgrep starting/kRunning in TimelineModel.cpp and tst_TimelineModelProp.cpp: The implementation's kRunning set covers preparing/starting/running/waiting. The prop generator only produces starting/running, and the model now accepts both.
… show it Every work row redrew for each signing, and asked again for any image whose signing had failed. Rows now redraw only when one of their calls shows the image and its address changed, as an app's icon already did. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
olafura
left a comment
There was a problem hiding this comment.
Approved
No findings.
The new commit (ee67940) brings workspace-image signing into line with app-icon signing. A signed workspace-file: address now redraws only the work rows that have an entry with that imageId, and only when the address or its expiry changed. Before, it redrew every work row, and the QML then asked again for images whose signing had failed. The key it matches on is the one entryFor writes (TimelineModel.cpp:1745), so the change is correct. My previous review had no findings, so nothing carries over. The one remaining gap is small: the new SignImage branch of the property test only checks that an image no call viewed redraws nothing. Nothing in the property model checks the other direction, that a row showing the image does redraw.
Not run
- mise prop:desktop tst_TimelineModelProp: the change is small and a read settles it; the author reports running the property suite
Ran 2 checks
git diff 3807633b4 HEAD: one commit, TimelineModel.cpp setAttachmentUrl and the tst_TimelineModelProp SignIcon commandgrep imageId / workspace-file: in TimelineModel.cpp: entries write imageId = "workspace-file:" + path, the same key setAttachmentUrl now matches on
The merge left StreamingSteps and ToolCallSteps each defining "the user is looking at a thread in", "it reads" and "the turn completes", so every phone timeline scenario stopped on an ambiguous step. Main's background step is the only one now; "it reads" and "the turn completes" ask StreamingSteps first, as FilesSteps asks ToolCallSteps for a viewed image. A failed turn stays open on the phone, so the copy scenario opens a fold only when there is one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
olafura
left a comment
There was a problem hiding this comment.
Approved
No findings.
This PR takes the tool-call scenarios off the backlog on the MC, desktop, phone and TUI. The new commit (f72a018) removes the step definitions the phone's StreamingSteps and ToolCallSteps both defined: "the user is looking at a thread in", "it reads" and "the turn completes". StreamingSteps now owns the first. For the other two, StreamingSteps handles its own turn and leaves the rest to ToolCallSteps: "it reads" goes to StreamingSteps once a duration was shown, and "the turn completes" once a run was started. My previous review had no open findings. The remaining risk is small: tool-calls scenarios now open their thread through StreamingSteps' setup, which also pins the clock and locale and sets a stream handle. The PR's phone run table may predate this commit, so rerun tool-calls.feature and streaming.feature on the phone before merging.
Not run
- Phone tst_Features for timeline/streaming.feature and timeline/tool-calls.feature at f72a018: Building and running the Qt feature tests was out of scope for this incremental test-only review. The author's run table may predate this commit.
Ran 2 checks
git diff ee67940817b8c6dc1857275757c9d1f3373a96cd HEAD: one test-only commit touching apps/mobile-qt/tests/features/StreamingSteps.cpp and ToolCallSteps.cppgrep 'the turn completes' / 'it reads' across features/: In tool-calls.feature, the one scenario that uses "the turn completes" (changed files) never calls startRun, so Turn.run stays empty and that step falls through to ToolCallSteps. The "it reads" scenarios in tool-calls never set durationShown, so they also fall through.
…acklog # Conflicts: # apps/mobile-qt/src/MobileApp.cpp
olafura
left a comment
There was a problem hiding this comment.
Approved
No findings.
Since f72a018 the only new commit is the merge of origin/main (135d4b0). The one conflict, in apps/mobile-qt/src/MobileApp.cpp, is resolved correctly. It keeps main's TerminalController::setSingleScreen (and drops the old setDrawn call) and the branch's ThreadStore::setFolds(Folds::Phone/Desktop), both at startup and in the layout.desktop interceptor, and the comment now covers both. My previous review raised no findings, so nothing is carried over. The biggest remaining risk is untested: main's Run-from-code-block steps in StreamingSteps.cpp now sit next to the branch's ToolCallSteps and Phone fold mode, and I couldn't build or run the phone feature suite here. I checked by reading that their step patterns don't overlap and their helpers live in separate anonymous namespaces. The PR description's note that the brief/fold reconciliation is still to come in the merge is now stale; it is done.
Not run
- mobile tst_Features on timeline/streaming.feature and timeline/tool-calls.feature after the merge: this checkout has no Qt build directory, and building the phone app was out of scope for re-reviewing a merge commit
Ran 3 checks
git show --cc 135d4b067: one conflicted hunk in MobileApp.cpp: it keeps main's setSingleScreen and the branch's setFolds, in both places they are calledcomm of files changed on both sides since the merge base: 5 files; I read the 4 that merged without a conflict (ThreadScreen.qml, FilesSteps.cpp, StreamingSteps.cpp, tst_Features.cpp). Each carries only main's changes, and none clashes with the branchgrep step patterns and namespaces in StreamingSteps.cpp / ToolCallSteps.cpp / TerminalSteps.cpp: main's new code-block Run steps have no ambiguous overlap with the tool-call steps; the two files'collecthelpers are each in their own anonymous namespace
65 scenarios in
features/timeline/tool-calls.featurewere still@backlogor@backlog-<surface>: how tool calls read in a thread's timeline, as the web client and the old mobile app showed them, plus upstream pingdotgg/t3code#2829 and #13386. This makes all of them pass on the surfaces they name and takes their tags off; the file has no backlog left.What changed
CommandOutput.failed?/1, reading at most the first 32 KB). A running tool's progress keeps the agent's message whole (#13386; the MC already did, so this adds the steps).TimelineModel,TimelineSummary,Timeline.qml):Verified
tst_Features, every timeline globtst_Features,tool-calls.featuretool-calls.featuretimeline/*.featureturn_writertests, format, compile with warnings as errorstst_TimelineModelPropgained the phone's brief mode and icon signing. Each property was checked to fail when its fix is reverted.test_sensibleTitleAndSize). Phone 105 passed.Known failures left: the phone's full feature run fails 8 scenarios that were failing before this branch: #561, #230, #722 and the new #938. The phone's QML test entry uses the wrong test runner on some machines: #939.
Merged with main: main's phone fold mode (
TimelineModel::Folds::Phone) now carries this branch's brief rules: one live row for a running turn, and unfinished calls, workspace preparation and already-carded delegations left out once a turn settles. The separatebriefflag is gone. The phone's diff review and copy notice come from main, one implementation each.Review fixes:
Done by Claude Opus 5.5 in Claude Code, running in HAL-C2.
🤖 Generated with Claude Code
Open in [code]smith · Need help on this PR? Tag
@codesmith-botwith what you need.