Skip to content

merge Mcp branch#207

Merged
xintaofei merged 29 commits into
mainfrom
mcp
May 24, 2026
Merged

merge Mcp branch#207
xintaofei merged 29 commits into
mainfrom
mcp

Conversation

@xintaofei
Copy link
Copy Markdown
Owner

No description provided.

xintaofei and others added 29 commits May 22, 2026 22:16
… delegation

Adds migration m20260522_000001 with two nullable text columns on conversation;
mirrors them into ConversationSummary / DbConversationSummary (plus surfaces the
existing parent_id on the public summary); fills None at all 8 parser sites and
the 3 existing ActiveModel construction sites. Adds round-trip tests covering
both the populated and default-null cases.

Phase 1 of multi-agent delegation; see docs/superpowers/specs/2026-05-22-multi-agent-delegation-design.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…versationLinked

- Extend ConversationLinked payload with optional parent_conversation_id (i32) and
  parent_tool_use_id (String). Both serialize-skip when None so existing wire
  consumers see no change.
- New variants DelegationStarted / DelegationCompleted with a DelegationResultSummary
  tagged enum (ok { duration_ms } / err { error_code }).
- Patch all 7 ConversationLinked construction/pattern sites + extend the
  apply_event match in SessionState to ignore the new delegation events
  (broker handles them out of band).
- Document the meta["codeg.delegation"] convention on ToolCallState.

Phase 2 of multi-agent delegation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New module acp/delegation/ with the spawner trait + DelegationLink carrier.
  The trait is the surface DelegationBroker depends on; production wires up via
  a new ConnectionManagerSpawner wrapper (manager + DB), tests use MockSpawner.
- Extend send_prompt_linked with optional delegation arg; on the new-row branch
  it flows into create_with_delegation (new conversation_service entry point)
  and into the ConversationLinked event's parent_* fields.
- Return type widened to Result<Option<i32>, AcpError> so the spawner trait can
  hand the bound conversation id back to the broker; non-broker callers ignore
  the value.
- Trait mock + 3 unit tests cover queued spawn/send results and unqueued-fail.

Phase 3 of multi-agent delegation; broker arrives in Phase 4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cascade

Drives a single delegate_to_agent call end-to-end against the Phase 3
ConnectionSpawner trait:

- broker.rs: handle_request → depth pre-check → spawn → send_prompt_linked
  → park oneshot → race timeout vs complete_call. complete_call disconnects
  the child (v1 one-shot). cancel_by_parent fans out cancel+disconnect to
  every pending child of a given parent_connection_id.
- types.rs: DelegationRequest / DelegationOutcome / DelegationError with
  stable wire-format error codes for the MCP tool_result payload.
- depth.rs: generic async parent-chain walker, saturates at cap so a
  corrupted chain can't unbounded-walk the DB.
- DbDepthLookup implements ConversationDepthLookup over AppDatabase; tests
  use an in-memory MockDepth + MockSpawner with no DB or runtime ACP.

14 new tests cover config round-trip, disabled fast-path, happy path with
in-flight complete_call, spawn/send failure mapping, timeout cleanup,
parent-cancel cascade, and depth limit rejection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nd-trip

Companion side of the multi-agent delegation flow. Caller agents launch
codeg-mcp from their MCP config; the LLM sees a single `delegate_to_agent`
tool whose tools/call gets forwarded to the main-process broker.

- src/bin/codeg_mcp.rs: thin stdio loop, plain argv parsing (no clap), exits
  cleanly on EOF.
- acp/delegation/companion.rs: JSON-RPC 2.0 dispatch (initialize, tools/list,
  tools/call), notification filtering, MCP tool_result rendering. Pulled
  into the lib so handle_line() is unit-testable without process spawning.
- acp/delegation/transport.rs: length-prefixed JSON frame writer/reader plus
  cross-platform client_round_trip (UDS on unix, named pipe on windows).
  16 MiB frame cap, in-memory + real-UDS tests.
- acp/delegation/tool_schema.json: embedded delegate_to_agent schema with
  all 6 agent_type variants.

Packaged as a third binary in the existing crate (matching codeg-server)
rather than a workspace member, to avoid duplicating broker types and to
share target/. New tests: 12 (3 transport + 9 companion). Full lib suite
passes 419 tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gs end-to-end

This is the integration phase: the Phase 5 codeg-mcp companion now reaches a
running broker via UDS, the LLM-issued delegate_to_agent ToolUse spawns a child
session, and its TurnComplete resolves the parent's tool_use_id.

New modules:
- acp/delegation/listener.rs: UDS / named-pipe accept loop, token registry
  (register/revoke/lookup/revoke_by_parent), ParentSessionLookup trait,
  process() with token + parent-conn + agent_type + task validation.
  7 unit tests cover the rejection paths and a duplex-stream happy path.
- commands/delegation.rs: persistence layer for the 3 settings keys
  (enabled / depth_limit / default_timeout_seconds) backed by app_metadata,
  Tauri commands get/set_delegation_settings, clamp helper, broker
  re-application on save. 4 unit tests round-trip storage + clamp behavior.
- tests/delegation_e2e_uds.rs: full UDS round-trip with a mock spawner —
  happy path + invalid-token rejection.

Wiring:
- AppState gains delegation_broker / delegation_tokens /
  delegation_socket_path; app_state::build_delegation_stack() constructs the
  full stack (broker, depth lookup, token registry, PID-scoped socket) and
  installs the injection onto ConnectionManager via a OnceLock-backed
  install_delegation so all 5 spawn_agent call sites pick it up without
  parameter threading.
- Both codeg-server bootstrap and Tauri lib.rs setup() build the stack,
  apply_persisted_config() before listener bind, and spawn run(). Tauri
  also .manage()s broker/tokens/socket-path for HTTP/Tauri command lookup.
- connection.rs: run_connection + spawn_agent_connection gained an
  Option<DelegationInjection> arg. After init handshake, inject_codeg_delegate_mcp
  registers a per-launch token, appends a stdio McpServer entry pointing at
  the codeg-mcp binary colocated next to current_exe, and stashes the token
  on SessionState.delegation_token. On run_connection exit the token is
  revoked and broker.cancel_by_parent fans out cascade-cancel.
- ConnectionManagerParentLookup impl in manager.rs reads the live session
  state to answer "what conversation is parent X currently in?"
- send_prompt_linked emits AcpEvent::DelegationStarted on the new-row branch
  when a DelegationLink is supplied (Task 6.7).
- lifecycle.rs: handle_event / handle_event_with_retry /
  connection_worker_loop / lifecycle_subscriber_task all take
  Option<Arc<DelegationBroker>>. On TurnComplete for a delegation child,
  forward_turn_complete_to_broker maps stop_reason → DelegationOutcome,
  calls broker.complete_call (which disconnects the child per v1 one-shot
  semantics), and emits AcpEvent::DelegationCompleted. On Disconnected /
  Error, forward_disconnect_to_broker calls cancel_by_child_connection so
  the parent's tool_use_id doesn't dangle.
- SessionState gains last_assistant_text — captured at TurnComplete just
  before live_message is cleared so the broker outcome carries real text
  instead of an empty stub.
- broker: new cancel_by_child_connection method, symmetric to
  cancel_by_parent.

Tests: 430 lib passing (+11 vs Phase 5), 2 e2e UDS, clippy clean across
desktop + server features.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Delegation sub-sessions live in the DB as conversations with parent_id set.
Without a filter they'd appear as top-level rows in the workspace list,
duplicating what their parent's ToolCallBlock will render inline (Phase 8).

* `list_all` gains include_children: bool — defaults to false so the
  workspace list automatically hides delegation children.
* New `list_children(parent_id)` service + `list_child_conversations`
  Tauri command / HTTP handler / route, oldest-first, soft-delete aware.
* DbConversationSummary mirror adds parent_id / parent_tool_use_id /
  delegation_call_id so the frontend can render the parent ↔ child link.
* api.ts + tauri.ts: listAllConversations gains includeChildren param,
  new listChildConversations(parentId).
* 4 service tests + 2 command tests cover default-exclude, opt-in
  include, child-scoping, and soft-delete invisibility.

Frontend UI toggle deferred to Phase 8 where the inline child rendering
under the parent's ToolCallBlock lands — separate top-level toggle would
just be developer scaffolding until that view exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the parent ↔ child delegation rendering for `delegate_to_agent`
ToolCallBlocks. The wire events fire on the child's connection stream,
so the parent's UI needs a global subscription to resolve a binding by
parent_tool_use_id — that's the new DelegationContext.

* AcpEvent mirror gains `delegation_started` / `delegation_completed`
  (+ DelegationResultSummary discriminated by `kind`).
* DelegationProvider — one global `acp://event` subscription, builds
  Map<parent_tool_use_id, DelegationBinding>, status transitions
  running → ok/err.  Mounted between AcpConnectionsProvider and the
  ConversationRuntimeProvider so the binding outlives a single chat tab.
* useDelegatedSubSession — resolves the binding + fetches the child
  conversation detail lazily (only on expand).  Internal useReducer
  state machine to keep `react-hooks/set-state-in-effect` happy.
* DelegatedSubThread — collapsed header (agent label + status badge +
  last assistant text snippet) and expand-to-preview body listing the
  child's turns.  Status badge uses static error-code keys; the Rust
  DelegationError taxonomy maps 1:1 to the next-intl keys.
* content-parts-renderer routes `delegate_to_agent` to the new component
  when the tool-call has a toolCallId; otherwise falls through.
* i18n: en + zh-CN + zh-TW fully translated; ar/de/es/fr/ja/ko/pt get
  English placeholders for parity (Phase 9 localizes properly).
* Test: vitest covers the four user-visible states (no binding,
  running, error code, expand-on-click).

Deferred from plan Task 8.3 / 8.7:
* Permission inline routing — the existing per-connection permission
  store needs broader plumbing to surface child-permission requests
  on the parent ToolCallBlock.  Tackle in Phase 9.
* `ToolCallState.meta["codeg.delegation"]` live state — the backend
  doesn't yet stamp delegation markers on tool-call meta, so the
  pre-binding "Delegating to X…" status has no source field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the multi-agent delegation feature: a settings panel that lets
users toggle the kill switch / depth / timeout, a chat-channel summary
so Slack/Telegram/etc. relays render delegation calls as one line each
instead of dumping the MCP tool I/O, and the i18n coverage that was
missing for the AcpAgentSettings.multiAgent block in all 10 locales.

* `web/handlers/delegation.rs` (new) + router routes — the HTTP mirror
  of `get_delegation_settings` / `set_delegation_settings` Tauri
  commands.  Server mode needs them too, otherwise CodeG-server users
  have no way to tune the broker.
* `lib/api.ts` — `getDelegationSettings` / `setDelegationSettings`
  wrappers + `DelegationSettings` type mirror.  No `tauri.ts` entry
  needed; `getTransport().call()` already routes both modes.
* `components/settings/delegation-settings.tsx` (new) — a self-contained
  section mounted under `/settings/agents` as a sibling of the
  per-agent settings.  Lives in its own file because delegation is
  a global feature and the existing acp-agent-settings is 7.8k lines.
  Server-side clamp values are mirrored back into the inputs after
  save so the UI always reflects what was actually persisted.
* `chat_channel/session_event_subscriber.rs` — delegation-aware relay:
  fires "🤖 Delegating to {agent}…" on ToolCall and "✅/❌ {agent}:
  {preview}" on ToolCallUpdate completed.  Skips the generic
  `>> {detail}` line for these calls so users see one delegation
  status, not three.  +7 unit tests cover the matchers and outcome
  formatters (ok / err code / empty / long-text truncation).
* i18n — en/zh-CN/zh-TW carry the full localized copy for the
  settings panel; the other 7 locales get the English fallback to
  keep parity tests green (next translator pass can fill them in).

Verification:
  cargo test --features test-utils --lib            → 443 passed
  cargo clippy --all-targets --features test-utils  → clean
  cargo check --no-default-features --bin codeg-server --bin codeg-mcp
  pnpm test                                         → 177 passed
  pnpm eslint .                                     → clean
  pnpm build                                        → static export ok

Manual acceptance (per spec §15.3) — pending user-driven runs:
  * Claude Code → Codex flow
  * Codex → Claude Code reverse flow
  * Cancel cascade (parent cancel → child stops < 1s)
  * Timeout (30s setting + slow task → error_code timeout)
  * Depth limit (3-deep chain at depth=2 → error_code depth_limit;
    bump depth to 3 → succeeds)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* settings/agents page: wrap in a scrolling container so the global
  delegation section is reachable below AcpAgentSettings (which still
  claims h-full for its grid layout).
* broker: ACP clients (Codex/Claude Code) don't populate
  `_meta.tool_use_id`, so the companion no longer rejects calls without
  it; instead, the lifecycle subscribes to parent ToolCall events and
  pushes their tool_call_ids into a per-connection FIFO that the broker
  claims when an MCP round-trip arrives (UUID fallback when empty).
* tool-call renderer: render `delegate_to_agent` invocations as a
  dedicated AgentIcon-driven card with the task text pulled directly
  from the LLM input — even before the DelegationStarted event lands.
  Tool-name normalization picks up the `mcp__<server>__delegate_to_agent`
  prefix.
* i18n: three new strings (`delegatedLabel`, `unknownAgent`,
  `waitingForChild`) localized for zh-CN / zh-TW / ja / ko / es / de /
  fr / pt / ar; English-only fallback dropped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…kdown

* tool-kind-classifier: `isAgentLikeToolName` now also matches
  `delegate_to_agent` and `mcp__<server>__delegate_to_agent`, so the
  delegation card breaks the tool-call run instead of folding into a
  consecutive-tool-group capsule.
* DelegatedSubThread: drop the shadow, remove the in-header outcome
  summary line (collapsed card now shows only agent + task), and parse
  the broker's `{kind, text, code, message}` outcome so the expanded
  body renders the actual `text` (or `message`) as markdown via
  MessageResponse instead of a raw JSON string. Sub-thread turn bodies
  also render markdown now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Drop the static "· delegated" label — it was the only decoration on
  the header row and there's no other type/status to disambiguate
  against, so it added visual noise without information.
* Remove the inner bordered/tinted box around the parsed outcome.
  Markdown body now sits directly under the divider; error variants
  use the destructive text color instead of a frame.
* Rework the expanded-body state machine so the rendered content
  prioritizes what's already in hand: child detail turns → parsed
  outcome from the parent's tool_result → fetch in flight → "still
  running and no output yet". Previously the body keyed off the live
  delegation binding, so when the DelegationStarted event raced the
  MCP round-trip the body stayed stuck on "Waiting for the child
  agent to start…" even though the broker had returned text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sing

The lifecycle's `is_delegation_tool_title` and the chat-channel relay's
`is_delegation_title` only matched the exact MCP method name. ACP `title`
is free-form text the host agent composes; in practice Codex and Claude
Code prefix it (`mcp__codeg-delegate__delegate_to_agent`, `Run mcp__…`)
and other hosts rephrase it entirely (`Delegate to codex`). When neither
literal matched, the lifecycle never registered the parent tool_call_id
into the broker's pending queue → the broker fell back to a UUID
placeholder for `parent_tool_use_id` → the frontend binding indexed by
the real `part.toolCallId` never resolved → the expanded card sat on
"Waiting for the child agent to start…" indefinitely.

* lifecycle.rs: rename to `is_delegation_invocation` and match by
  substring on the normalized title OR by raw_input shape (presence of
  both `agent_type` and `task` string fields). raw_input is a near-zero
  false-positive signal because that pair only co-occurs on the
  delegate_to_agent schema.
* chat-channel relay: same substring widening on its local
  `is_delegation_title` (it already had a raw_input fallback for the
  completion side).
* card status: drop the `&& output` guard on the output-available branch
  so the card flips to "ok" the moment the tool reports completed —
  even if `output` is empty / not yet joined — instead of staying on
  "running" and showing the waiting line.
* i18n: drop the now-unused `delegatedLabel` key from all 10 locales.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inline DelegatedSubThread now stitches every child text segment append-only
under a persistent "sub-agent running…" indicator, filtering out tool calls
and thinking blocks so the parent UI only ever shows the visible result —
later segments never overwrite earlier ones.

Parent ToolUse snapshots carry meta.codeg.delegation (new meta_writer
module + meta field threaded through models, parsers, ACP types), so a
post-refresh remount rebinds to the child connection without depending
on the live event surviving.

Broker matches each MCP round-trip to the parent's real ACP tool_call_id
via a pending queue with a brief race-wait, and the lifecycle dispatcher
forwards ToolCall events so that queue actually fills — the card always
binds to the real tool_use_id rather than falling back to a synthetic one.

i18n consolidates the delegation strings around subAgentRunning across
all 10 locales.
…nnels

Every shipping path now lands codeg-mcp next to codeg-server / codeg so
locate_codeg_mcp_binary()'s exe-sibling lookup finds it. Without this,
the delegate_to_agent MCP tool was silently disabled in production
builds because the binary was only produced for local cargo runs.

* Tauri desktop: declare codeg-mcp as bundle.externalBin in
  tauri.conf.json. beforeBuildCommand / beforeDevCommand run
  src-tauri/scripts/prepare-sidecars.mjs to cargo-build the companion
  for the active target triple and stage it as
  src-tauri/binaries/codeg-mcp-<triple>{.exe}. build.rs writes a
  zero-byte placeholder when the staged file is missing so plain
  cargo check / clippy succeed without going through the prepare step,
  with a cargo:warning if production paths skip the wrapper.
* CI release matrix: build-tauri runs prepare-sidecars per matrix
  target before tauri-action, then asserts the file is staged.
  build-server builds and tarballs both binaries and smoke-tests
  codeg-mcp --help on native targets.
* Docker: Dockerfile builds both binaries; Dockerfile.ci copies both
  per-arch into /usr/local/bin/ with a FATAL guard if either is
  missing from the build context.
* install.sh / install.ps1: install both binaries from the release
  archive, scan PATH for either as shadowing conflicts, and verify
  codeg-mcp lands executable next to codeg-server.

Runtime locator (acp/connection.rs) falls back across CODEG_MCP_BIN
-> current_exe sibling -> which::which and returns Option, gated by an
is_executable_file check (regular file + Unix +x bit). On None,
inject_codeg_delegate_mcp skips MCP injection with a stderr warning
rather than registering a phantom McpServerStdio that would break
stricter agents at session start.

DB: parent_tool_use_id and delegation_call_id, queried by the
include_children filter and list_child_conversations, get
idx_conversation_parent_tool_use_id and
idx_conversation_delegation_call_id in the m20260522 migration to
keep lookups O(log N) as session history grows.
- Recognize delegate_to_agent regardless of the host's server prefix
  or separator (mcp__codeg-delegate__, codeg-delegate/, dot/colon
  variants) so the parent always lands on the DelegatedSubThread card.
- Surface the task line by walking common JSON-RPC wrappers (arguments,
  input, params, payload, _meta) plus double-encoded payloads; warn
  with a truncated sample when no known wrapper matches.
- Render the broker outcome as markdown by unwrapping the MCP
  CallToolResult envelope (structuredContent) and the Codex
  "Wall time:" prefix; inherit the outer isError flag.
- Accumulate the child's live assistant text across turns so prior
  segments stay visible after STATUS_CHANGED resets liveMessage.
Remove the decorative welcome backdrop and use a shared attached input frame for folder and branch selectors.

Align chat input spacing and reduce the default composer height.
…ancel to children

Broker now emits DelegationCompleted at all terminal exits (complete_call,
timeout, channel-drop, cancel-by-child, cancel-by-parent) via a dedicated
event emitter, so frontend bindings flip out of "running" on failure paths.
Parent connection cancels and non-end_turn turn completions cascade an ACP
cancel to in-flight child delegations, preventing leaked child state.
…arent cancel cascade

Child turn-failure stop reasons (refusal / max_tokens / max_turn_requests /
empty / unknown) now surface as distinct DelegationError variants with
dedicated wire codes and localized labels in all ten supported locales,
replacing the generic "subagent_error" fallback. The parent connection's
four cascade-cancel sites dispatch cancel_by_parent via tokio::spawn so a
slow child teardown can't stall the parent's message loop.
…g and delegation

New /settings/general nav entry (slot #2, after Appearance) hosts the
default-terminal-shell picker, the Windows-only hardware-acceleration
toggle, and the multi-agent delegation panel. /settings/system now
carries only version updates, network proxy, and language, and
/settings/agents is back to AcpAgentSettings on its own. Delegation
section header swapped to the Bubbles icon.

i18n: 23 terminal/rendering/restart keys moved from SystemSettings to
GeneralSettings across all ten locales, with loadFailed copied into
both namespaces. Backend label_key docstrings updated to resolve under
GeneralSettings.*.
…itter fanout

The delegation status badge now renders translated labels for ar / de / es /
fr / ja / ko / pt, replacing the English placeholders for every running / ok /
error key including the granular child-failure codes. Broker test suite gains
two integration tests that exercise the production ConnectionManagerEventEmitter
against a real ConnectionManager: one asserts DelegationCompleted fans out to
both the per-connection stream and the InternalEventBus, the other pins the
silent no-op behavior when the parent connection is gone.
Move the focus-within ring from the outer wrapper onto the composer with ring-inset so focusing the textarea no longer lights up the folder/branch picker row beneath it. Drop the outer wrapper's border, deepen its background to bg-muted/60 for a clearer tray under the input, and give the composer its own full border. Trim the picker row to py-1 and align its leading padding with the composer's button row via pl-2.
@xintaofei xintaofei merged commit bccb588 into main May 24, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant