Skip to content

feat(qoder): integrate Qoder CLI as a built-in agent - #514

Merged
xintaofei merged 1 commit into
xintaofei:mainfrom
asteroida123:feat/qoder-builtin-agent
Aug 19, 2026
Merged

feat(qoder): integrate Qoder CLI as a built-in agent#514
xintaofei merged 1 commit into
xintaofei:mainfrom
asteroida123:feat/qoder-builtin-agent

Conversation

@asteroida123

Copy link
Copy Markdown
Collaborator

概述

在现有 13 个内置 agent 之外,新增第 14 个内置 agent:Qoder(阿里巴巴的编码 agent CLI)。

与需要桥接的方案不同,qoder --acp 是 CLI 官方自带的第一方 ACP 服务端,无需任何适配层。
实测 handshake 能力:支持 loadSession 与完整 sessionCapabilities(list / resume / fork /
close / delete / additionalDirectories),prompt 接受图片与 embeddedContext,MCP 走 http+sse
——会话恢复、MCP、tool 轨迹全部走原生 ACP,没有能力缺口。npx 分发
@qoder-ai/qodercli@1.1.23(node ≥ 20),安装、启动、更新由 registry 统一管理。

改动内容

  • 新增 AgentType::Qoder(wire 名 qoder,registry id qoder-cli),registry、MCP skip list、
    文件系统 root slots、委派 tool_schema、conversations / experts / office_tools、
    import_service 等枚举触点全部同步
  • 新增 parsers/qoder:读取 ~/.qoder/projects/<encoded-cwd>/<sessionId>.jsonl
    (Claude-Code 风格 chunk-log 信封),复用 claude 的 turn 分组;sidechain 子代理记录与
    尾部元数据记录排除在外;assistant 分片按 message.id 合并;transcript 水印契约与
    claude 解析器一致
  • MCP:Qoder 启动时自行读取 ~/.qoder/settings.json 顶层 mcpServers(gemini schema),
    codeg 的 MCP 设置页直接管理该文件(读改写合并,保留 CLI 自有的其他键),并加入 ACP
    forward skip list 防止双重挂载
  • 认证:qoder 账号登录(qoder login / IDE 的 qoder-browser 流程),无 API-key 环境变量;
    模型、权限模式、推理力度走标准 ACP configOptions
  • 技能:目录式 bundle(<id>/SKILL.md),根为共享 ~/.agents/skills 与项目 skills/
    .agents/skills/
  • 前端:BuiltinAgentType 类型、标签、颜色、图标(ACP registry 官方 glyph)、展示顺序、
    MCP 应用选项、委派默认值
  • README 及全部 9 个翻译同步

验证结果

  • cargo clippy --all-targets --features test-utils -- -D warnings 与 server / codeg-mcp
    模式 clippy 均无告警
  • cargo test --features test-utils 全过:含 qoder 解析器 6 个测试(真实 1.1.23 会话
    fixture、sidechain 排除、尾部元数据不污染时间戳、水印契约)与 MCP 写入器往返测试
    (保留 settings.json 无关键、跳过非法条目、精准删除)
  • pnpm eslint . / pnpm test(4168 用例)/ pnpm build 全过
  • 已对 upstream/main 最新提交(f55ebb69,v0.26.1)做真实合并验证:零冲突,合并后
    Rust 2695 个单测 + 全部集成测试、前端 4252 个用例、clippy 双模式全部通过

已知限制(Qoder 上游设计所致)

  • 会话标题在 state.json 中为 AES-GCM 加密存储,解析器从明文 transcript 的首条 human
    prompt 推导标题
  • 订阅按 credits 计量,codeg 的 usage 模型是 token 形态,仅映射 token 计数
  • 认证依赖 qoder 账号,无 API-key 环境变量路径

Qoder (Alibaba), driven by the `@qoder-ai/qodercli` npm package pinned to
1.1.23 and launched as `qoder --acp`, joins the built-in roster. The ACP
server is the CLI's own first-party implementation — no community bridge —
and the verified handshake advertises loadSession plus the full
sessionCapabilities set (list/resume/fork/close/delete), image and
embeddedContext prompts, and MCP http+sse, so the resume rung and the
codeg-mcp companion work with nothing in between. Auth is the qoder account
(`qoder login` / the IDE's qoder-browser flow); there is no API-key env, so
the agent stays off the model-provider credential cascade and the settings
auth panel keys stay empty rather than misreporting off generic
OPENAI_*/API_KEY values.

History comes from `~/.qoder/projects/<encoded-cwd>/<sessionId>.jsonl`
(relocatable via `QODER_CONFIG_DIR`), written in the Claude-Code-style
chunk-log envelope: uuid/parentUuid-chained user/assistant records with
thinking/text/tool_use/tool_result blocks, interleaved with metadata
records (`workspace-directories`, `runtime-config`, `active-leaf`,
`last-prompt`) that repeat at the file tail. The parser merges assistant
fragments that share one `message.id` (one API response streams as
thinking, tool_use and final-text records), pairs tool results by
tool_use_id, excludes `isSidechain` sub-agent internals, and derives
titles from the plaintext transcript — the sibling `state.json` keeps its
titles encrypted (AES-GCM under the machine key), so it cannot be the
source. The exact byte length read becomes `transcript_watermark`.

MCP rides qoder's own config: the CLI reads `mcpServers` out of
`~/.qoder/settings.json` (gemini-schema settings file, alongside
securityScan/permissions/security/model keys), so codeg's MCP settings
writes go there as read-modify-write merges that preserve every other
key, and Qoder joins the ACP forward skip list with Hermes/Kimi/Grok/
Cursor — forwarding the same servers over `session/new.mcpServers` would
double-mount them. Unlike Codex/DeepSeek there is no SSE refusal: the
handshake advertises http+sse and stdio entries are gemini-shaped.

Skills: directory bundles only (`<id>/SKILL.md`, no flat markdown), roots
`~/.agents/skills` (home) plus per-workspace `skills/` and
`.agents/skills` — there is no qoder-owned home skills directory.
`McpAppType::Qoder` serializes as `qoder`, pinned against
`AgentType::as_wire` by the existing wire-name test, and a
`qoder-cli` custom-agent entry registered before this integration is
rejected at hydration as a built-in registry-id collision.
@xintaofei

Copy link
Copy Markdown
Owner

黎总666

@xintaofei
xintaofei merged commit d3b9151 into xintaofei:main Aug 19, 2026
7 checks passed
xintaofei added a commit that referenced this pull request Aug 19, 2026
Fixes on top of #514, verified against the published bundle.

Parser: read everything the Claude and Qoder envelopes share through
parsers::claude's own extractors instead of a second spelling of the same
rules — block-array content (which every ACP-entrypoint prompt uses, so the
whole user turn and the title were being dropped), images, isMeta injections,
synthetic API-error turns, slash-command tags, and per-message.id usage
de-duplication.

On top of that, the parts that are Qoder's own:

* The transcript is an append-only message GRAPH walked back from active-leaf,
  not a list — reading it in file order splices abandoned rewind branches into
  the conversation and double-counts their tokens.
* compact_boundary records write parentUuid: null on purpose and stash the
  real predecessor in logicalParentUuid; following only parentUuid hid every
  message sent before a compaction.
* A parallel tool batch is one assistant record and one user record PER call,
  each result parented to its own call, so the results are siblings and an
  ancestor walk kept just one of them — the rest vanished and left their tool
  cards looking like calls that never returned.
* active-leaf with leafUuid: null means the branch is EMPTY (the user rewound
  the first prompt); treating it as malformed resurrected the conversation
  they had just cleared.
* custom-title / ai-title / token-stats / runtime-config.contextWindow are
  plaintext records, so titles and the context gauge no longer have to be
  guessed.

Wiring: resolve the config dir the way Qoder does (QODER_CONFIG_DIR, else
QODER_CLI_HOME / GEMINI_CLI_HOME joined with QODER_CONFIG_DIR_NAME) so codeg
and the CLI it launches agree on one directory; read skills from the roots
Qoder actually scans; authenticate with QODER_PERSONAL_ACCESS_TOKEN, the only
credential a headless install can use (QODER_API_KEY was never read, and the
empty slots wrote an env var literally named "undefined"); and alias the
upstream registry id so the picker stops offering an entry that cannot be
added.

Also serializes the tests that contend on the process-global IMPORT_GUARD, a
pre-existing race that failed CI intermittently.
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.

2 participants