[pull] main from xintaofei:main - #172
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 : )