Skip to content

[pull] main from xintaofei:main - #172

Merged
pull[bot] merged 17 commits into
MoozLee:mainfrom
xintaofei:main
Aug 2, 2026
Merged

[pull] main from xintaofei:main#172
pull[bot] merged 17 commits into
MoozLee:mainfrom
xintaofei:main

Conversation

@pull

@pull pull Bot commented Aug 2, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

Users queue to-do tasks against a project folder and have agents work
them off in isolated git worktrees. A new sidebar entry opens a
four-column board — To do, In progress, Needs you, Done — with a badge
counting tasks waiting on input, review or a failure. Tasks are written
with the rich composer (file references included), may override the
folder's default agent and mode, and are started one by one or all at
once under a per-folder concurrency cap; per-folder settings also carry
the merge strategy and delete-worktree defaults.

A dedicated engine, elected through its own advisory file lock
independent of automations, drives the pipeline: it mints a worktree
branched from a pinned base commit, launches a headless agent session
(resuming the task's previous conversation when one exists), flips tasks
into awaiting_input while question, permission or plan-approval requests
are pending, and settles finished turns into review with a diff snapshot
and result summary. Review offers accept, return-with-feedback and
retry; cancel and requeue work at any non-merging point, and a stop from
the chat UI counts as a cancel rather than a failure.

Accepting merges in two stages: the base branch is first merged into the
worktree — so conflicts land there for the agent or user to resolve —
then the squash or merge commit is made in the project folder behind a
preflight (right branch, unmoved HEAD, clean index) with one automatic
retry when the base advances. Merge intent is persisted before execution
and crash recovery replays it from git truth alone, so done strictly
means merged and never rolls back. Every transition is a conditional
update guarded by a per-run generation counter, making stale completions
and post-cancel events no-ops, and is recorded in an append-only event
log rendered as the task's timeline.

Removing a worktree deletes it git-first, re-parents its conversations
under the project folder and stamps their original path onto the new
conversation.origin_cwd column so session lookup keeps resolving for
agents located by cwd. Cleanup failures are retryable and never touch a
finished task.

Both runtimes wire the full work_task command surface and the
task://changed event; strings ship in all ten languages.
…ractions

Folder settings gain an auto-process switch: the engine claims to-do
tasks in board order on its own, sharing the manual concurrency budget.
Claims are compare-and-swap transitions that recheck the active count —
queued included — inside the same transaction and roll back over budget,
so a burst of triggers never overshoots the cap. Every mutation nudges
the pump immediately and the periodic tick sweeps folders with pending
work as a fallback.

Task-launched agent sessions get a new codeg-mcp feature group with two
tools. task_progress records milestones that surface live on the running
card and in the timeline; task_complete lets the agent settle its own
outcome — blocked becomes a failure carrying the agent's summary, while
success and needs_review land in review with the self-reported summary
preferred over the captured transcript tail. Verdicts are guarded by the
per-run generation counter and cleared on re-claim, and agents without
the tools settle exactly as before. The injection is derived from the
task engine's session ownership, so regular chat and automation sessions
are unaffected.

Tasks flipping into review or failed raise a system notification through
the existing channel — silent while the window is visible, and never for
statuses that merely load with the first fetch.

With a folder selected, the To do column supports drag sorting persisted
to sort_order — which is also the auto-claim order — and dropping a card
onto In progress starts it; a new work_task_reorder command backs this in
both runtimes. User and assistant messages gain a create-task action that
carries the text into a pre-filled editor on the Tasks board, resolving
worktree conversations to their parent project. The detail sheet shows
the conversation's total token usage, and conversations whose source
worktree was removed are badged in the sidebar. Strings ship in all ten
languages.
Merging gains an auto-repair loop that keeps the train moving through
conflicts. The merge dialog carries an auto-resolve flag (on by
default): when the base-resync stage hits a conflict, the merge is
aborted as before, but instead of falling back to review the task is
re-queued with a bumped run generation and the merge intent moves into
a new pending_merge column — merge_state stays a pure crash anchor for
in-flight merges. The relaunched agent receives a conflict-repair
prompt listing the conflicted files and asked to complete the merge
commit in the worktree; when its turn settles, the stored intent
re-fires the merge automatically, carrying an attempt budget of two
repair dispatches per user-initiated merge before giving up to review
with the usual conflict marker. Launch-mode resolution reads
pending_merge first, so the folder pump and the repair dispatcher can
race for the queued task and either winner composes the right prompt.
Every other exit — retry, return, cancel, failure, re-queue — clears
the intent, so a resurrected task can never fire a stale merge.

Folder settings can nominate one of the folder's saved commands as a
preflight check. When a task settles into review (and is not
auto-remerging), the engine runs the command headlessly in the
worktree and the card and detail sheet show a green/red light — red
keeps the tail of the combined output. Results are written through a
compare-and-swap guarded by both review status and the run generation,
so a slow command that finishes after the task moved on writes
nothing; the light resets whenever the task re-enters review. The
command runs without a timeout, with stdin closed to fail fast on
anything interactive.

Finished tasks — done, failed or canceled — can be archived. Archived
cards leave the board and the attention badge on both runtimes, an
eye-toggle in the toolbar reveals them dimmed with a restore action,
and retrying or re-queuing a task automatically unarchives it. A new
migration adds the three columns, the archive command is wired through
both runtimes, and strings ship in all ten languages.
…d templates

The task detail sheet gains a Transcript action that opens the task's agent
session in a wide read-only streaming viewer without opening a conversation
tab. The delegation sub-agent dialog's whole streaming core — connection-state
subscription, the live bridge into the conversation runtime store, the
blocking permission / question / plan-approval cards and the read-only message
list — is extracted into a shared LiveTranscriptView, with the sub-agent
dialog reduced to a thin shell over it. Headless work-task connections are
invisible to the frontend, so the dialog attaches the task's connection while
the task is live; the attach decision is latched at mount so a settle-driven
refetch cannot detach ahead of the final turn-complete event, and tasks that
settled long ago render the persisted transcript without attaching.
WorkTaskInfo now exposes connection_id for this.

Automations gain an action kind: enqueue a work task instead of launching a
session. The action lives inside the automation's config blob, so existing
rows keep their launch behavior with no migration. Each trigger creates a todo
task in the target folder carrying the automation's prompt and agent, and the
run settles synchronously as succeeded with the queued task number. The editor
narrows folder choices to project roots for this action, hides the
session-only isolation and branch controls, and saving without a target
folder is rejected.

Tasks can be saved as global templates (new work_task_template table) and
applied from a popover in the task editor footer: applying restores the
title, prompt and agent override, and saving under an existing name updates
that template in place instead of piling up copies.
…de it

Opening a select or a dropdown inside a dialog and then clicking elsewhere in
that dialog closed the dialog along with the menu. A nested layer that disables
outside pointer events makes Radix write `pointer-events: none` onto every
layer beneath it, so the press hit-tests through the dialog to the overlay,
which stays clickable, and the dialog reads it as an outside press. Radix's own
guard would discard it -- a layer only dismisses while it is the topmost
pointer-events-enabled one -- but Dialog and Popover hardcode
`deferPointerDownOutside`, moving that decision to the following click. By then
the press has already dismissed the menu, the menu has unmounted and the dialog
is topmost again, so the stale press passes the guard and closes it. One click,
two layers.

`useNestedLayerDismissGuard` restores the check at the moment it was meant to
run. It samples the layer's inline `pointer-events` on a capture-phase
pointerdown, ahead of Radix's own document listener, and cancels a deferred
dismiss that traces back to a press the layer was shielded from. Dialog, Sheet
and Popover content wire it in, composing onto whatever ref and
`onPointerDownOutside` the caller passed rather than displacing them -- the
scrollbar-safe dismiss guards already installed on several popovers keep
working unchanged.

Presses that land while the layer is genuinely on top are untouched, so
clicking the overlay still closes the dialog, and a second click closes it once
the menu is gone. Dismissing only the topmost layer per press is what Radix
does everywhere else: menus, selects and context menus never deferred, so their
decision was always made while the guard still held. AlertDialog cancels every
outside press already and needs nothing.
…design

Merging is now the agent's job: the merge dialog queues a merge turn in
the task's own session (squash or merge commit, with an optional
agent-written commit message) and the engine settles the outcome from
git truth — the task lands in done exactly when its branch is merged
into the base, and anything else returns it to review with the error
attached. The engine's two-stage merge and its conflict auto-repair loop
are gone; conflicts are handled inside the merge turn. Work branches are
named task/{id}, a configurable init command runs inside a freshly
created worktree before the agent starts, and per-run round markers
(work / retry / rework / merge) segment the task transcript into phases.

Task settings gain a global scope: a sentinel row (folder id 0) holds
global defaults and a folder without its own row follows it wholesale.
The settings dialog shows which source is in effect — the folder scope
carries a "global defaults / custom" switch seeded from whether the
folder's own row exists, flipping to custom starts from the values that
actually apply, and choosing global defaults saves by deleting the
folder's row. The preflight command is a plain text input (legacy
command-id selections migrate to text on load), and "process all" queues
every eligible to-do across folders.

The board is rebuilt around the window chrome: the page title sits in
the chrome strip above a hairline matching the sidebar header, the
toolbar (folder pill, filter popover, settings, process all, new task)
is borderless, and columns drop their containers — empty cells show a
hint inside a clearly visible dashed outline. Cards read by status shape
(spinner text, amber pills, a bare green check), show diffstat and
relative time together, wrap errors in a tinted container, and carry one
primary action plus a "…" menu with edit for editable states. With a
workspace background image on, the page canvas, cards, chips and empty
cells go translucent like the conversation surface.

The detail drawer orders the task brief above the agent's result, wraps
review actions in an amber acceptance panel, and adds a details grid
(branch, merge commit, change size, created/started/finished timestamps)
plus a counted changed-file list and compact timeline times. The task
editor's composer supports / slash commands (fed by the agent-options
probe, no live session needed) and Codex's $ skills, restamping inserted
skill badges when the agent changes.
An agent that ends a turn with `end_turn` and no output produced one generic
line telling the user to check the agent's configuration. Nothing on the wire
carries an error there -- the agent reported success -- so the verdict was
codeg's own inference, and it collapsed three unrelated situations into the
same sentence: a real failure the agent only reported on stderr, a protocol
mismatch where updates arrived and were silently dropped, and a turn that
legitimately carried nothing but metadata.

Each turn now runs a probe that separates agent output from metadata updates
and counts what the two drop sites discarded -- `read_update` decode failures
and dispatch-layer type mismatches -- keeping the first reason from each. The
diagnosis reports `turn_failed_empty_protocol` when anything was dropped,
`turn_failed_empty_metadata` when only plan / mode / usage updates arrived, and
keeps `turn_failed_empty` for a genuinely silent turn. The error carries a
`details` string quoting the drop counts and the tail of the agent's stderr for
that turn, falling back to the most recent lines when the turn itself was
silent. The stop reason stays `empty` in all three cases, so conversation
status, delegation errors and the transcript are unaffected.

Agent stderr goes to a per-connection in-memory ring buffer rather than a
higher tracing level, so the evidence survives the default log filter without
risking a debug log storm. Both sources are redacted as they are written, never
on the way out: stderr lines pass a token, header, cookie, URI-credential and
private-key denylist, and parser errors -- which inline payload fragments from
a channel carrying prompt text, file contents and tool arguments -- go through
a default-deny summarizer that rebuilds the error from a finite allowlist of
serde phrasings and our own schema identifiers, discarding anything it cannot
account for. The turn's notification handler is a named function returning
nothing, so a dispatch error can only mean deserialization failed.

The evidence reaches the status-bar alert and the session snapshot only. It is
kept out of the OS notification, whose payload persists outside the app, and
out of `conn.error`, which feeds the single-line composer status tooltip. A
client that attaches after the fact raises the alert once from the snapshot's
`last_error`, deduplicated per context key across re-attaches.
Every task opens its agent session in one read-only viewer, reached from
a round button on the card and from the drawer's bottom bar; the
workbench-tab "open conversation" entry is gone. The viewer resolves the
session's agent before mounting so live output renders with the right
parser, and it streams in real time from any status — attaching to a
turn already in progress hydrates it from a session snapshot before
routing the desktop firehose, which carries only future events. While
streaming, the viewer suppresses the persisted copy of the active reply
but keeps the rest of the conversation on screen, so earlier rounds of a
multi-round session stay readable, and each round divider names the
phase it opens (task run, retry, rework, merge).

Cards carry exactly one filled primary action per status — start,
cancel, merge, retry, archive, requeue, unarchive — with the secondaries
as round icon buttons in the corner instead of an overflow menu. The
detail drawer mirrors that: the status's actions sit between the result
and the details, in the slot the review acceptance panel occupies, and
the bottom bar keeps only what does not advance the task (view session,
edit, cleanup retry, delete). Removing a worktree is offered by the
delete confirmation alone.

The board remembers its filter across sessions — canceled shown,
archived hidden by default — and badges only deviations from that
default. Task settings move into the chrome strip beside the page title,
the toolbar's controls share one pill shape, and the column headers sit
tight beneath them. The default merge strategy is described without git
jargon: combine into one commit or keep full history, each with a line
explaining what lands in the branch history.

Full-page routes own the whole surface: the terminal and aux panel
collapse while tasks or automations is open and come back with the
workspace, and their toggles stay hidden there. In the task editor the
slash-command menu scrolls itself into view instead of being clipped by
the dialog.
Ten of the twelve built-in agents ship the vendor's own CLI, so a copy the
user installed by hand satisfies the launch gate as-is. Claude Code and Codex
do not: neither `claude` nor `codex` speaks ACP, so codeg installs
`@agentclientprotocol/claude-agent-acp` and `@agentclientprotocol/codex-acp`
and resolves those command names instead. Nothing in the UI said so, leaving a
user with the vendor CLI on their PATH to read "Not installed" as codeg
failing to detect it.

The registry gains a side table naming, for those two entries alone, the
vendor CLI they wrap, the config dir both read, and the installer directories
a GUI process tends to miss. Preflight resolves that CLI alongside the adapter
command and returns the pair as structured data on `PreflightResult`; the
wording lives in the frontend, so the card is localized like the version check
beside it. The probe stays on path resolution -- the settings page runs a full
preflight for every agent each time it opens -- and overlaps its two lookups,
since a failed `npm prefix -g` is never cached and would otherwise be paid
twice.

A card above Version Status now names the vendor CLI codeg found and where,
the package it needs instead, that the adapter bundles its own runtime and
leaves the existing command untouched, and that both read the same config dir
so no second sign-in is required. It carries a docs link and no install
action, the row directly below already having one. It warns while the adapter
is missing and passes -- collapsed -- once installed, so a working setup is
left alone.

Diagnostics stops reporting these two as simply not installed. It probes the
vendor CLI's version, prints it and the shared config dir next to the adapter
it is not, and returns a verdict distinguishing a missing adapter from a
missing adapter alongside a vendor CLI that is present. Node and stale-install
verdicts still take precedence, those being the real blockers.

The agent DTOs carry `is_acp_adapter` so the surfaces with no preflight result
agree with the card: the composer's blocked banner and a connect failure both
name the adapter rather than the agent, and the settings header badges the
entry as one.
Bump both adapter pins and wire the two new codex-acp surfaces.

codex-acp 1.1.8 (#351) gates Plan mode behind a review confirmation that
fires regardless of client capabilities: a `session/request_permission`
marked `_meta.codex.kind = "plan_review"` whose tool call is never
announced, followed by a bare `tool_call_update` carrying only a status
and rawOutput. Seed that tool call from the request so the update merges
into a card with an identity instead of rendering as an untitled orphan,
and report the user's decision through PlanModeCard (neutral while the
permission card is still open).

codex-acp 1.1.8 (#342) hangs `_meta.permission = {version, changes[]}` on
each permission option, every change carrying a humanized description of
what the option grants and for how long. Forward the option `_meta`
through and render those descriptions under each button.

claude-agent-acp 0.64.0 (#929, omitted from its release notes) marks the
per-question free-text "Other" elicitation field with the deliberately
un-namespaced `_meta._askUserQuestionCustomAnswer`. Recognize it
alongside the existing `__other` name heuristic, which codex-acp still
uses exclusively.

claude-agent-acp 0.64.0 carries the same claude-agent-sdk (0.3.220) and
ACP SDK (1.3.0), so Claude Code's own behavior is unchanged. Its steering
`promptRequired` opt-in is not wired; codex's `clientCapabilities.plan`
path does not apply, since sacp 11.0.0's schema has neither the
capability nor a `plan_update` session-update variant.
Live feedback rides a pull channel today: notes wait in session state
until the agent volunteers a check_user_feedback call, so delivery is at
the model's mercy. The ACP _session/steering extension is a push channel
instead: the adapter injects the note into the running turn's input
stream and the model sees it on its next step.

Eligibility is synthesized once at initialize into
SessionState.native_steering_available from three gates: the top-level
_meta.steering.supported advertisement, a per-agent registry minimum
version for the promptRequired idle opt-in (the guarantee that a note
racing the turn's end comes back unconsumed instead of spawning a
detached turn), and the running binary's agent_info.version -- strict
semver, fail closed, because launch prefers a PATH-resolved adapter over
the pinned npx package. The registry minimum is currently None for every
agent, so the channel stays off and all sessions keep the pull path:
claude-agent-acp 0.64.0's injected outcome settles the owning
session/prompt as a clean end_turn when the injection lands
mid-generation, leaving the steered continuation running with no owning
request (agentclientprotocol/claude-agent-acp#934). Re-enabling on a
fixed release is a registry one-liner plus the positive test matrices.

When the channel is on, submit_feedback routes notes to a
ConnectionCommand::Steer round-trip. Native notes record as delivered
from birth and the pull tool only ever reads pending ones, so a session
carrying both channels cannot double-deliver. The enqueue-record-emit
sequence runs in a detached task (the adapter may have consumed the text
whether or not the HTTP caller survives), the emit is deliberately
ungated (the adapter's injected reply is authoritative even when the
turn settles concurrently), and a startedNewTurn reply downgrades the
session to the pull channel. The frontend reconciles the downgrade from
both signals -- a pending result flips immediately with a notice, a
delivered result verifies against an authoritative snapshot -- behind a
latch and a connection guard so stale snapshots and late
cross-connection resolutions cannot re-upgrade.

While a turn is in flight on a steerable session, the composer's stop
control grows a split button: the primary action queues the draft (the
previously invisible Enter behavior made visible), the menu item steers
the text into the current turn, text-only -- drafts with attachments
keep queueing. A NoActiveTurn rejection re-routes the draft to the
queue; other failures keep it intact. The feedback dialog swaps its
description by channel, and ten locales gain the new strings.

Also fix two preflight adapter tests still asserting the superseded
package pins (claude-agent-acp 0.63.0, codex-acp 1.1.7).
1.1.9 (#354) coalesces streamed plan snapshots into one `plan_update`
every 150ms and flushes them at item, turn, permission, and disposal
boundaries. That path runs only for clients advertising
`clientCapabilities.plan`, which codeg does not: sacp 11.0.0's schema
has neither the capability nor the session-update variant, so Codex
plans keep arriving as `agent_message_chunk`s and nothing here changes.
Dependencies are identical to 1.1.8's and there is still no
`engines.node`, so the Node 20.0.0 floor stands.

Refresh the doc comments that name the pinned release, including the
still-absent `promptRequired` steering opt-in that keeps Codex on the
MCP pull channel, re-verified against the 1.1.9 tarball.
The page drew its own h-10 header inside the list pane, which left the
window-chrome strip above it empty and truncated the title in a narrow
column. The title now registers in WORKBENCH_ROUTE_STRIPS the way the
Tasks board's does, so both routes open with the same header rhythm and
the strip closes with its hairline.

One borderless toolbar replaces the two stacked bordered bars beneath
it: folder and enabled-state filter pills built to the Tasks board's
control metrics, and the New primary on the right carrying its label at
every pane width. The folder options are the workspace's project folders
plus any other folder an automation targets, since the editor allows a
launch_session run to point at a worktree subfolder. A filter left
pointing at a folder that no longer holds automations falls back to
"all" by derivation, so the pill cannot render blank over an empty list.

Both columns live inside a single rounded shell, split by the resize
handle's rule; the list sits on muted, the detail on card. Tone alone
cannot carry that split -- in the light theme --muted and --card are one
step apart, some 2% on screen -- and the ways to widen it would blot out
the workspace background image, so the rule stays as the divider. Every
side of the shell clears its neighbour by 1rem, the toolbar included.

Inside, the detail is rule-separated sections rather than cards: the
shell already draws the outer boundary, so boxing each block again
stacked three borders deep. Run history is one of those sections instead
of the one block with no boundary at all, the six schedule facts lose
their individual frames, and Run now / Edit sit under the title rather
than between the prompt and history blocks, where they read as belonging
to neither.

Over a workspace background image the theme --border washes out against
a light photo, leaving translucent surfaces with no readable edge. The
boosted --ws-chrome-border hairline moves from 13% to 18% and now also
covers a resize handle's resting line, limited to the inactive state so
the hover and drag tints survive. Selection in the list leans on that
outline rather than on fill, --accent and --muted being the same token
value in the light theme.
The drawer opened on a fixed robot glyph with the status chip pinned to
the far edge, and its body alternated between boxed and unboxed blocks
with no rule behind the choice. It now leads with the agent that
actually ran the task -- read from the conversation, falling back to the
task's own agent override -- and the chip rides beside the title in a
box one title line tall that centres it whatever its height, the pill
and the bare spinner text differing by some 5px. The identity line no
longer wraps, so the model stays beside the agent that ran it, and the
panel widens to 44rem to hold it.

One action panel now sits between the result and the details for every
status, review included: merge, send back and abandon join the same
list instead of rendering their own acceptance panel, so the shell never
changes shape from one status to the next -- only its tone, amber while
the task waits on the user. The primary fills the row unless an escape
hatch claims the right edge. Actions that do not advance the task, the
session viewer and edit and cleanup retry, stay in the bottom bar.

The brief and the result are filled surfaces rather than outlined ones,
the details are a ruled box whose rules run unbroken across both columns
-- the label/value spacing is cell padding, since a grid gap breaks
every line in half -- and the timeline is one phase header per status
change with its events hung off a rail, a line each. Long content, the
brief and the file list and a full diff, caps at a fixed height and
reveals on demand, measured from the content's own box so the toggle
survives expanding. UnifiedDiffPreview takes `unbounded` for that last
case: its per-file 420px scroll boxes otherwise nest a second vertical
scroll inside the dialog's own.

On the board a single 1rem rhythm runs through the toolbar's insets, the
gutters between cards and the cards' own padding, the footer divider
included. A card's border derives from --foreground rather than
--border, which is all but invisible white-on-white, and hover changes
only that colour, to the theme's primary, with no shadow. The secondary
round actions fade in on hover and on keyboard focus, opacity only, so
the row keeps its height. Column headers swap their bullet for an
upright bar and carry their count in the toolbar's pill language, and
the folder filter leads with a glyph like the Automations pill it
mirrors. `done` reads as a green pill like every other status rather
than a bare check mark.

In the editor's template popover, the entries gain a glyph and the save
entry loses padding, so both land on the same two x positions.

The feature is "Task Board" across ten locales now, marked beta, and
carries ListTodo wherever it appears: the sidebar entry, the page title,
the board's empty state and both entry points that turn a message into a
task.
@pull pull Bot locked and limited conversation to collaborators Aug 2, 2026
@pull pull Bot added the ⤵️ pull label Aug 2, 2026
@pull
pull Bot merged commit 36003a7 into MoozLee:main Aug 2, 2026
6 of 7 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant