前言

这原本是在内网发布的文章,但发现没什么数据敏感的内容,稍微脱敏一下也顺手在这发了,Qoder本身已经是面向大众的一款产品

个别名词补充:

  • HSF -> 可以看作Dubbo

  • Aone -> 集团的一个中台

  • aone-kit -> 集团中台搞得cli,汇聚了各种能力,其中包括调用MCP的能力


  • 不知道目前大家在使用集团内部的一些云Agent/AI平台时有没有遇到如下问题:

    • 2026-04-18-jtmuffhn.png
  • 以上结论是在使用过内部的IdeaLabAone AgentMobiusAI百炼应用飞猪内部FAI等平台后得出的

  • 反观"本地"的一些Agent就十分的聪明,比如大家早就耳濡目染的Clade Code、前段时间在集团内正式全面公测的Qoder,它们在本地运行时的表现无论是任务规划、执行准确性都非常接近我预期的理想状态,并且对非Coding的任务也是如此

  • 于是我就冒出一个想法:为什么我不能让Clade Code/Qoder成为我的云Agent呢?如果能成那么是不是可以解决上面所有缺陷

  • 而这篇文章就是告诉你:能的,本文主要是分享基于qodercli的实现方式 + 中间的一些踩坑经验


接入效果

  • 基于部署到云上机器的前提,可以对接钉钉的AI助理/机器人,这样就可以实现类似OpenClaw的交互理念:只需一台手机,一个办公通讯软件,不需要自己办公电脑开着机,更不需要再手动打开Qoder/QoderWorker/悟空等,即可安排AI帮我干活(说白了就是让Qoder cosplay🦞)

  • 单聊连续对话:

  • 群聊@对话:

需要注意的是:实现AI助理单聊、群聊如果要实现连续对话有额外要求,本篇只提及单聊连续对话的"技巧",这会在后续踩坑记录中提及


接入步骤

如何部署到云端

  • 实际上就是安装qodercli,只不过刚开始有这个想法的时候,是有两种方式的,这里稍微再提一嘴:

  • 第一种:基于qodercli的print模式,利用管道也可以实现流式返回结果

  • 第二种:对接qodercli的ACP模式,尽管这种协议通常是用来对接IDE的

  • 对于上面两种方式我都各自写了个demo,只是后来听说QoderWorker也是基于qodercli的ACP实现的,就抱着美好的幻想果断选择了第二种(到后来发现好像也没那么美好)


  • OK,进入正题,首先是qodercli在应用上的安装,我目前是使用node的方式安装的,在应用启动脚本添加了如下内容:

        # 指定 Node.js 版本(可通过环境变量 NODE_VERSION 覆盖)
        NODE_VERSION=${NODE_VERSION:-v22.22.1}

        # 检查是否已安装 NVM
        if [ ! -d "$HOME/.nvm" ]; then
            echo "[NODE-INSTALL] INFO: Installing NVM..."
            curl -o- https://anpm.alibaba-inc.com/open/nvm/install.sh | bash
        fi

        # 加载 NVM
        export NVM_DIR="$HOME/.nvm"
        [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"

        # 安装指定版本的 Node.js
        echo "[NODE-INSTALL] INFO: Installing Node.js ${NODE_VERSION}..."
        nvm install ${NODE_VERSION}

        # 全局安装 tnpm
        echo "[NODE-INSTALL] INFO: Installing tnpm globally..."
        npm i -g tnpm@latest-6 --registry=http://xxx(这原本一个内网的npm源地址)

        # 更新 PATH
        export PATH="$NVM_DIR/versions/node/${NODE_VERSION}/bin:$PATH"

        # 安装或升级 qodercli
        echo "[qodercli] INFO: Installing/upgrading qodercli via npm..."
        tnpm install -g @qoder-ai/qodercli@latest

        echo "[qodercli] INFO: qodercli version: $(qodercli --version 2>/dev/null || echo 'unknown')"

一个小Tips:如果在内网使用curl方式安装qoder,大概率会失败,需要额外申请安全外联,但使用内网的tnpm就没有这个约束

  • 除此之外,还需要在docker文件中设置cli的环境:

ENV PATH "$PATH:/home/admin/.nvm/versions/node/v22.22.1/bin/"

  • 确保qodercli成功安装后,就是接入ACP Client了,有关于ACP相关的资料可以参考官方文档:Agent Client Protocol

  • 文档里提供了各语言的SDK,这其中也包括Java的,只是不看不知道,一看才知道这玩意有多冷门(刚发现这东西的时候star才个位数,并且到目前为止上升趋势也不是很明显):

  • 由于我们这里是直接使用qodercli --acp作为服务端,所以只需要引入acp客户端(也就是core)的依赖即可:

<dependency>
    <groupId>com.agentclientprotocol</groupId>
    <artifactId>acp-core</artifactId>
    <version>0.9.0</version>
</dependency>
// 官方示例是gemini
var params = AgentParameters.builder("gemini")
        .arg("--experimental-acp")
        .build();

// 我们改成qodercli即可
var params = AgentParameters.builder("qodercli")
        .arg("--acp")
        .build();

如何接入钉钉AI助手

  • 目前钉钉AI助手支持两种接入方式(这个能力还只有内部的阿里钉支持):

    • TPP - 本质上是复用小蜜的Agent模板(需要TPP服务,看着有点麻烦)

    • MAS - 自主提供http接口处理用户消息回调

  • 这里使用的是第二种方式,只需要在配置自己应用的域名即可,还可以设置灰度:

  • OK,如果你想要使用钉钉助手对接qodercli的acp,上面的操作已经能跑通了,但随后你很快就会发现很多奇怪的问题,这也是我接下来要重点讲的内容


踩坑 & 解决

单机session问题

  • 当你尝试跟已经搭建好的AI助理对话,可能对话个没几轮就会出现无回复/报错的情况,像下面这样:

  • 出现上述问题的原因并不是因为qodercli或者acp框架出bug了,而是因为原本你是在跟A机器的qodercli对话的,但后面的消息打到B机器上去了,两台机器的qodercli不共享同一个session,所以出现异常

  • 那这个问题要如何修复呢?我们首先要搞懂AI助理的奇葩设计思路

    • 三个接口职责:createThread(获取ACP的sessionId)createMessage(包含sessionId+用户信息)createRun(只包含sessionId,没有用户消息)

  • 重点注意createRun这一步不包含用户的消息,但sse流式输出又是这一步负责的,这就要求你在createMessage就要对用户消息进行暂存,直到后续createRun消耗掉为止(个人感觉真的是非常奇葩的设计🥲

  • 由于三个请求可能打到不同机器,要解决上面的问题,我们需要做两件事:固定同一个sessionId路由的ip + 用户消息暂存:

  • 消息暂存不包含在图中,这块自由发挥,可以靠ip路由本地存储,也可以tair/数据库存储,一般情况下存个30s非常足够了


  • 下面是具体代码实现,首先是createThread层,acp返回的sessionId拼上IP地址:

  • 中间createMessage层不在赘述,跳到createRun层,身为HSF泛化调用糕手,ip重定向我使用的是HSF本身支持指定ip + 流式调用的方式:

  • 在HSF流式调用的返回中,再嵌套sse的流式返回:

dingAiAssistantServiceRemote.executeRun(threadId, new StreamObserver<CreateRunSseData>() {
  @Override
  public void onNext(CreateRunSseData data) {
    // sse 输出
  }

  @Override
  public void onError(Throwable throwable) {
    // sse 输出
  }

  @Override
  public void onCompleted() {
    // sse 输出
  }
});
  • 还有一种情况会导致即便你已经实现了上面逻辑也还是会抛session not found的异常,那就是机器重启,不知道QoderWorker是如何实现这个记忆传递的,目前看起来重启后acp传入上一次的sessionId似乎都不认了,好在目前我使用的场景中也不需要这么复杂的记忆机制,就把这个问题暂时放一边了


MCP延迟发现问题

  • 如果你在自己的demo代码中尝试注入MCP后立马对话,又或者在群里 @AI助手 对话,有很大概率会出现「Agent找不到你配置的MCP」这个问题,之前在Qoder的群里也有人反馈过类似的(但无人应答)

  • 以上这些情况其实都是一个问题:MCP发现是异步的,而模型的回复不会等待MCP就绪

    • 为什么ACP框架注入MCP后立刻对话调用不到MCP?因为MCP还没连接成功

    • 为什么群聊 @AI助手 无法调用MCP?同上,群聊每一次对话都是新的session,session刚启动MCP未就绪

    • 为什么第一次对话找不到,第二次就找到了?因为第一次时MCP还没就绪,第二次接着对话就绪了

  • 对于这个问题,目前并没有非常根本的解决办法,即便我尝试在ACP框架基础之上增加了MCP探测等待的能力,但随着MCP增多,等待时间越长,依旧不是一个很好的办法

  • 所以我决定将MCP的调用也CLI化,如果你不懂这是什么意思,请继续看下一个问题的解决


MCP对上下文侵占的问题(aone-kit)

  • 经常使用"古法MCP"的同学肯定经历过这样一个场景:我的Agent引入很多MCP后,随便一句话模型context的窗口就直接占了一半,举个夸张点的例子就像下面这样:

  • 并且后续随着mcp工具的增多,Agent执行任务的正确性开始逐步降低(尤其是对于小模型而言)

  • 大家肯定知道这是因为MCP的工具描述在每轮对话时都会注入到上下文导致了模型注意力的分散,也肯定知道去年就开始兴起的skill机制(渐进式披露)能很好的解决这个问题

  • 但这其中会有一个悖论问题:

  • 那这个问题要如何解呢?引入CLI,使用CLI调用MCP就好了(这也是目前很多内/外部AI平台不支持的能力)

  • 这样的区别在于:"古法"MCP是从一开始就全部加载进上下文而Skill+内置CLI方式中,MCP是伴随着SKill被加入到上下文的,符合渐进式披露的原则

  • 一开始有了这一层想法后,我是想直接抄起Golang + MCP SDK准备自己写一个这样的CLI的,但由于业务繁忙暂时先搁置了一阵子,后来又内部做了一些调研,发现了aone-kit的注入能力,于是选择躺平接入(


aone-kit 介绍 & 接入

  • 由于aone-kit官方的文档目前写得比较简略,在一些复杂的skill中不仔细比对都看不出差别,以至于很多人可能都没感受到它的"隐藏"能力,所以我这里就稍微说的详细一点

  • 在aone-kit最近更新的文档中有这样一段话(早期的文档甚至没有这段话,只知道悟空AI是支持的):

  • 这个注入到底是什么?要回答这个问题,你可以先按照下面的步骤操作(内网相关的,就是一些链接无需在意):

    • 打开这个skill:https://xxx.com/skill/empid-2-name-v2?tab=code 你会发现确实按照语法写了,但仍旧平平无奇

    • 那么请你先安装aone-kitnpm install -g @ali/aone-kit --registry=https://xxx

    • 使用aone-kit安装这个skillaone-kit skill install empid-2-name-v2@latest --location ./

    • 再查看本地安装的SKILL.md注意要翻到最下面,就能看出区别了,出现了一段新的「工具调用说明」

  • 没错,本质上这个"注入"操作就是会把你SKILL.md按格式声明的标签全部再增加多一层工具调用说明,我这里因为只使用到一个工具所以可用工具列表里面只有一个,如果声明了多个就会有多个,类似下面这样:

  • 这里还是开头的查询花名为例,给Qoder只安装skill,但是不引入MCP,并且开启工具调用打印,就可以看到如下效果:

  • 会发现它自己就会使用cli来请求MCP了

  • 到这一步,你的Agent应该只会执行红线部分内容,至于为什么我这命令前后还多了一坨奇怪的东西,这个问题下一个部分马上就解答= =


  • 额外小补充:目前aone-kit支持对包含子目录的skill实现注入了

  • 比如你的skill是下面这个结构,那么aone-kit早期的版本中是不支持子目录的.md注入的

  • 但现在最新版本的aone-kit也支持上了,所以也是提醒一下如果上面的操作结果跟我的不太一样,请先确保aone-kit更新到最新版本


Qoder Bash出现no content问题

  • 好不容易调通了aone-kit的流程,但没过多久就又发现了新的问题

  • 当使用aone-kit调用的mcp返回的数据单行长度过大时,Qoder的Bash工具会卡住一阵子,最后返回no content,如下图所示:

  • 不只是我自己的MCP有问题,Sunfire的也会出现,基本确认这个是通病,当单行数据超过一定字符时就会出现:

    • 想复现上面问题的可以使用这个skill: https://xxx(一个获取traceId调用链路的skill,返回大小5MB),给个调用链路稍微长点的traceId就能复现(至少到qodercli 0.1.39版本还是存在)

  • 但换成我手动执行一遍no content的命令是能正常返回,目前只能诊断为是Qoder的Bash工具做了某种限制

  • 这个问题我给Aone、Qoder都反馈过了,但到文章发布日期两边都没啥动作,所以目前的解法就是让输出重定向到本地文件中,尽管qodercli的acp模式能力 < qodercli tui模式,但像AGENTS.md、rules这种东西还是支持的

  • 我在AGENTS.md中加入了如下内容:

## aone-kit call-tool 使用规范

执行任何 `aone-kit call-tool` 命令时,必须遵循以下规则:

1. **严禁**直接在终端输出结果,以下方式严禁使用:
   ```bash
   # 错误示例 - 严禁使用
   aone-kit call-tool <tool-id> '<args>' --provider aone
   aone-kit call-tool <tool-id> '<args>' 2>&1 | head -300
   ```

2. **必须**将输出重定向到临时文件再读取:
   ```bash
   TMPFILE=$(mktemp /tmp/aone-kit-XXXXXXXX) && aone-kit call-tool <tool-id> '<args>' --provider aone > "$TMPFILE" 2>&1 && echo "输出已保存到: $TMPFILE"
   ```

3. 使用 Read 工具读取 `$TMPFILE` 内容进行分析处理

4. 分析完成后立即清理临时文件:
   ```bash
   rm -f "$TMPFILE"
   ```

### 原因

agent 对命令输出结果大小有限制,大量输出会导致长时间卡顿,最终可能无结果返回。临时文件方式可以绕过该限制,确保结果完整获取。

---

## 写文件规则
- 所有输出文件必须写入 `/tmp` 目录
  • 配置完后再次询问Agent,它就有这个上下文(之后执行任务也会遵守这个规则就像上面工具实际执行的命令一样):

  • 这里也顺便吐槽一下,Qoder在使用古法MCP时发现返回过大是会存到本地文件再截断读取的,但是到cli的Bash就不会🤣


权限问题

  • 让qoder成为云Agent,最重要的一个目的就是想让它发挥出除个人助手以外的职责,其中必须的一步就是能让其他人也能直接通过钉钉对话使用

  • 首先是钉钉AI助手层面,控制好可见范围,像我的话后续预计会缩小成周边团队:

  • 然后是文件权限层面,Qoder是支持控制哪些目录读/写的,在~/.qoder/settings.json加入如下配置即可:

{
  "permissions": {
    "allow": [
      "Read(/**)",
      "Edit(/tmp/**)"
    ],
    "deny": [
      "Edit(/**)"
    ]
  }
}
  • 这样能防止像openclaw权限全开的情况下误删除系统文件的情况


泼泼凉水

  • 如文章开头所说,让Qoder成为云Agent真的能完美解决问题吗?实际体验过后确实解决了很多痛点,但仍有一些让我士气下降的点


ACP模式下的阉割

  • 虽说QoderWorker就是基于qodercli实现的产物,但走过一遍之后才发现,这个"基于"的占比着实是有点小,我上面提提到过qodercli的acp模式能力 < tui模式的,下面就说说一些我体验到的阉割点。

无法指定模型

  • 在qodercli的tui模式中,是可以使用/model切换模型后,持久化配置到~/.qoder/settings.json中,之后使用qodercli都生效的

  • 但这种方式就是对acp模式无效,acp模式始终会使用auto,之前群里也有人反馈过这个问题,但很多个版本过去了依旧无解


指令阉割

  • 在tui模式下,支持很多指令:

  • 但这其中绝大部分都无法在acp下使用,尤其是/quest一类,如果要支持spec一类的体验,目测就是要手动把openSpec一类的markdown安装到commonds里面了


qodercli可能成为弃子?

  • 首先是IDE,有spec、专家团的能力,官网也在实时更新发版,估计过不了多久就内置Harness Engineering的能力了

  • 反观cli的更新日志还停留在20天前,感觉无人在意🤣

  • 结合上面tui跟acp体验不一致的问题,确实有点担心cli、acp的能力会越来越赶不上,未来不知道会不会出现像现在老平台架构不支持skill的情况

这对比让我想到现在钉钉和飞书的演进方向区别,钉钉推出了悟空AI更偏向GUI交互,而飞书则是推出飞书CLI偏向让AI来掌控


额度的"怨言"

  • 超过2W额度就要跳到P9线下审批还是有点毒,这个周期稍重度使用后,2周就用了1W6,现在畏手畏脚的还是来到的1W8+,实在有点不够用(更别说以目前文章的方式使用Qoder又会造成更多的消耗):

  • 以官网2W Credits = 100$计算,这个成本也就约等于Aone上部署5~6台4c8g机器的1个月的账单金额,但两者的审批成本完全不在一个层次


各方案对比

调研结果截止至2026-04-10(由于涉及一些内部平台的名称,统一改成A、B、C称呼了,百炼则不再赘述)

skill支持

workspace

任务速度

任务准度

支持模型范围

备注

A Agent / A Claw

  • ✅ 都支持

  • ⚠️ 但对含子目录、cli 类skill支持有限

  • ✅ 都支持

  • A Meta - 快

  • A Claw- 慢

  • 依赖模型,从qwen3升到qwen3.6体感提升比较明显

  • 旗舰模型体验是:高

  • 默认只有qwen每日1000次,其它的基本要走ideaLab预算申请

  • A Claw无法用于api调用

  • A Agent在api模式下不支持ask模式,agent模式很慢,比较适合一些不需要即时响应的任务

  • ⚠️ 需要注意的是A Agent已经暂停使用API接入的方式了

B Agent/ B Claw

  • ❌ B Agent- 不支持

  • ⚠️ B Claw - 说支持,没参与内测不太清楚支持到哪种程度

  • ❌ B Agen- 不支持

  • ⚠️ B Claw- 不清楚

  • 不清楚

  • 不清楚

  • 不清楚

  • 审批最难的一关,我个人刻板印象是:集团内负责采购的供应商

C 平台

  • ❌ 老的workflow模式 - 不支持

  • ⚠️ Agent模式也内测,没参与不清楚

  • ❌ workflow模式 - 不支持

  • ⚠️ Agent模式 - 不清楚

  • workflow模式 - 不支持

  • Agent模式 - 不清楚

  • workflow模式 - 快

  • Agent模式 - 不清楚

  • 同B Agent

  • 没记错是使用B作为供应商的

D 平台

  • ✅ 支持

  • ⚠️ 但对含子目录、cli 类skill支持有限

  • ❌ 不支持

  • 旗舰模型体验:高

  • 主流模型都支持,如GPT5.x、Opus 4.6、Gemini 3、Qwen、Kimi等

  • 一天的请求量不能太高,百来次还是ok的

qodercli --acp

  • ✅ 完美支持

  • ✅ 支持

  • 锁死auto模式:高

  • 只能用auto

  • 注意消耗的还是个人的额度

最后

  • 目前这种方式之所以称之为"邪道",是因为我个人觉得这样做有点脱离Qoder的设计目标,但这样确实能比较快速地解决当前一些比较痛的问题

  • 虽然我写了这篇文章,但并不推荐所有人都这样干,只建议作为一种思路拓展参考,原因如下:

    • 一是qodercli的acp模式也是经过阉割的,实际上也并非完全体

    • 二是前段时间CC的源码泄露事件,必然会带动AI平台快速迭代几版,可能不久后所有的痛点都能在平台上直接解决,作为使用者大可不必过于折腾,像FAI最近出现的Beta版Agent应该就是有所动作了

  • 另外除了上述这种接入方式外,还支持再套一层,比如🦞新增的能力的acpx,感兴趣的可以还可以再折腾折腾