Files
PaperClipAI/packages/adapter-utils
DottaandClaude Opus 5.5 b0fa2ef9bf feat(task-chat): show web searches, model switches, waits and verification checks as dedicated rows (#19262)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents run through the native runner. The runner turns harness
activity into typed canonical events such as `research.*`,
`model.route.changed`, `wait.*` and the run's own verification checks.
> - The task chat flattened these events into generic tool rows. A web
search showed as "Ran Research eventType: research.started, summary: …".
The run's verification checks were dropped completely.
> - Live tests found a second break: the Rust runner core forwarded
Codex `webSearch` items without their query, action or results. The
canonical `research.*` events therefore had `query: null`.
> - This pull request keeps the full payload for these families (as
#19252 did for plans), adds small dedicated rows, and keeps the
web-search fields through the durable runner.
> - The benefit is that users see what the agent searched for, which
model actually ran, how long a wait lasts, and whether each check
passed, without opening the run log.

## Linked Issues or Issue Description

Refs #19252 (same pattern for the plan card)

**Subsystem affected**
Task chat activity rows (UI), the native run-event transcript mapping,
and the Rust runner core Codex normalizer.

**Problem or motivation**
Provider activity that the runner already maps to canonical events
reaches the task chat as unreadable generic rows. Verification checks
never appear. For Codex, the web-search query and action are lost before
they reach the canonical event.

**Proposed solution**
Keep `research`, `model_identity`, `artifact`, `review` and `wait`
events as `provider_activity` entries with their full payload. Project
the runner's `verification` items as a new `verification` family. Render
dedicated rows: a web search with its quoted query and source-domain
chips, a model switch as `from → to · reason`, a wait with a live
countdown, and green/red check lines. Keep bounded web-search and sleep
fields in the Rust normalizer.

**Alternatives considered**
A separate "Checks" block beside the final answer. We kept the checks in
the activity group, because the group summary already leads with "Check
failed" when a check fails.

## What Changed

- `ui/src/components/transcript/native-run-events.ts`: keep research,
model, artifact, review and wait events as `provider_activity` with the
full payload. Map `item.completed` items of kind `verification` to the
new `verification` family. Context, hook, safety and delegation stay on
the generic path, because other work items own them.
- `packages/adapter-utils/src/types.ts`,
`ui/src/components/task-chat/task-chat-model.ts`,
`ui/src/lib/issue-chat-messages.ts`: add the `verification`
provider-activity family.
- `ui/src/components/task-chat/task-chat-activity-presentation.ts`: add
labels: "Searched the web “query”", "Opened a web page host/path",
"Switched models a → b · reason", "Waiting 30s" / "Waited 30s", and
"Check passed" / "Check failed" / "Check not run".
- `ui/src/components/task-chat/TaskChatRunnerActivityGroup.tsx` and
`TaskChatProtocolActivityRow.tsx`: show up to three source-domain chips
under a finished search, a live countdown for a running wait, and a
check line with a tinted icon and its detail.
- `ui/src/components/task-chat/completed-activity-summary.ts`: the group
summary leads with "Check failed" when a check fails, and says "Switched
models" for a reroute.
- `ui/src/components/task-chat/transcript-adapter.ts`: verification
detail keys, and a `startedAt` timestamp that stays across lifecycle
merges (used by the countdown).
-
`packages/paperclip-runner/runner/crates/runner-core/src/provider_events.rs`:
keep bounded `query`, `action` and http(s) `results` for Codex
`webSearch` items, and `durationMs` for `sleep` items. Other item types
do not change.
- `ui/storybook/stories/tasks/task-chat-provider-activity.stories.tsx`:
new story. Its fixtures are persisted run events that go through the
production transcript path.

## Verification

- `pnpm --filter @paperclipai/ui exec vitest run
src/components/transcript src/components/task-chat
src/components/TaskChatThread.test.tsx src/adapters/paperclip-runner
src/lib`: 275 files and 3669 tests pass.
- `cargo test -p paperclip-runner-core`: all tests pass, including the
new `keeps_bounded_web_search_and_sleep_identity`.
- UI typecheck is clean. `pnpm check:token-gates` is clean. `cargo fmt
--check` is clean.
- Storybook: open "Tasks and conversations / … / Task chat / Provider
activity". It shows a settled run, a live search, a live wait countdown
and a narrow layout.
- Live test on a local worktree instance with a real Codex agent
(gpt-5.6-sol, Codex app-server 0.160). The task asked for a web search,
a parser with tests, a pause, and two checks. The task chat showed
"Searched the web “ISO 8601 week date first week Thursday conversion
calendar date”" and two green check rows ("node --test week-date.test.js
· 8 tests passed; 0 failed.", "node --check week-date.js"). The group
summary read "Ran checks". Before the runner fix, the same search had
`query: null`.

Not verified live:
- The source-domain chips. Codex 0.160 sends `results: null`, because it
keeps search results on the server. The chips appear only when a
provider reports sources. Unit tests and the story cover them.
- The wait countdown and the model-switch row. In the live run Codex
paused with a shell `sleep`, not a native `sleep` item, and no reroute
occurred. Unit tests and the story cover both.
- Claude. Its WebSearch and WebFetch calls arrive as generic tool
executions, because ACPX drops the tool metadata. They show as tool
rows, as before. The ACPX `_meta` work is separate.

## Risks

- Low risk. The change only affects how canonical events display. The
generic path stays for the families that this PR does not change.
- The Rust normalizer now persists the web-search query and the source
URLs/titles in the run events. This data was already intended for the
canonical `research.v1` event, and it is bounded (4,000 characters per
text field, 64 results, http(s) URLs only).
- A search that Codex reports with `action: other` and no query shows as
"Searched the web" with no subject.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use
(shell, file edit, browser automation) and extended reasoning.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:37:05 +00:00
..

@paperclipai/adapter-utils

Shared utilities for Paperclip adapters: process spawning, environment injection, sandbox/SSH transport, workspace sync, and the round-trip helpers that move code between the local execution-workspace cwd and wherever the agent actually runs.

For the adapter-author guide see docs/adapters/creating-an-adapter.md and the in-repo notes at packages/adapters/AUTHORING.md.

Sandbox bridge deadlines

Command-managed bridge control operations (queue reads, input delivery, setup, and cleanup) have a host-enforced deadline of at most 30 seconds per shell command. A shorter configured timeout still applies. These operations cannot inherit the agent run's hours-long lifetime or wait forever on a provider that ignores its timeout. Long-lived agent session commands keep their own limits.

A control timeout reports a transport failure. It does not prove that the remote command stopped and does not replay an uncertain write. The process session closes failed input delivery and uses its existing shutdown path; normal execution settlement must still verify termination.

No-remote-git contract

The local execution-workspace cwd is the only persistence boundary across runs. No adapter may depend on a git remote for cross-run state.

Adapters that run the agent on a different host should use the SSH round-trip helpers in src/ssh.ts:

  • prepareWorkspaceForSshExecution({ spec, localDir, remoteDir }) — bundles the local cwd (tracked files, dirty edits, untracked additions, and the git history needed to reconstruct it) to remoteDir before the run starts. Runs with no git remote configured.
  • restoreWorkspaceFromSshExecution({ spec, localDir, remoteDir, ... }) — syncs the remote cwd back into localDir after the run, including any new commits the agent created. Also runs with no git remote configured.

prepareRemoteManagedRuntime in src/remote-managed-runtime.ts wraps both calls for adapters that want a per-run remote workspace and an automatic restoreWorkspace() finally hook.

The invariant is pinned by the no-remote-git contract case in src/ssh-fixture.test.ts, which asserts that a remote-only commit propagates to the local worktree through the prepare → restore round-trip with no git remote configured at any point. Do not regress that test.