Skip to content

feat(minimax-bot): 群接力的跳数上限与摘要交接(默认关闭) - #68

Merged
MyPrototypeWhat merged 3 commits into
MiniMax-AI:mainfrom
Apageoflove:feat/minimax-bot-relay-guard
Oct 26, 2026
Merged

MyPrototypeWhat merged 3 commits into
MiniMax-AI:mainfrom
Apageoflove:feat/minimax-bot-relay-guard

Conversation

@Apageoflove

@Apageoflove Apageoflove commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

给群接力加两道护栏,都是群自己的字段,默认都是关的:maxHops 填 0 表示不限,和现在完全一样;handover 默认 false。

跳数上限说的是一条消息引发的接力链最多几个 Bot 说话(上限 20,面板和接口夹的是同一个范围)。到上限时不再 @ 别人,群里给一条系统提示(已达接力上限(N 跳),本轮讨论在此收口),并让刚说话的那个 Bot 收个口。被拦下的那一跳不执行,但收口自己就是一次模型回合,所以到上限的一轮实际跑 limit+1 次模型回合;这一回合带着 taskId 进去,所以从任务派发进来的群接力,收口也挂在原任务下。

摘要交接是把上一位的结论放进下一位的提示词里,做法是把上一位回复的尾部带过去。下一位因此不用再翻一遍工具重新挖同一件事。它只是提示词里的一段文本,不额外起模型回合、不额外调工具、不写记忆。上一位的原话本来就在"新消息"里,交接块是把结论再强调一遍,模型自己判断要不要用。

这两个设置是面板专属的:我在群设置里填,MCP 的 update_group 里没有这两个参数,模型即使硬塞也不生效。理由是上限属于成本与安全的设定,不该由正在接力的那一方自己决定要不要继续接。相应地,模型能看到的工具目录与 list_team 的返回,跟改之前逐字节相同。

上限算的是这一轮一共几个 Bot 说话,初始就在队列里的成员也算进去——"所有人回复"的群里,这个上限连第一轮的几位成员一起算。

为什么做这个:官方 README 自己写着 There is no fixed reply cap。我在 mcode 里对官方版跑了一套 8 步对照,第 4 步只是群里 @ 了一个成员问标题怎么起,结果一轮接力跑了 4 跳(考据→执笔→考据→执笔)、313 秒、6 次工具调用,最后也没落到一个能用的标题上。

测过什么:上游 0.7.8 自己的 216 条测试,214 过。另外 2 条(头像库的并发上传、写入失败后重试)在我这台机器上单独跑通过、全量跑失败,上游原版全量跑也是同样这 2 条,所以不是这次改动带来的。我给这个功能补了 18 个测试用例:上限填 0 等于不限,写坏的值读作不限而不是把群静音,超过 20 会被夹住,护栏关着时群里的常驻简报与群提示词都与官方版逐字一致(中英文各一次),maxHops=2 时四跳链只跳两跳并收口,收口那一回合自己失败时链路照样停在两跳、通知仍在,模型改不了这两个设置。

边界:宿主 permissionMode 是 bypassPermissions,工具权限的执行门一次都不会触发,这套护栏是行为约束,不是安全边界。我只核了上游 0.7.8 的代码,其他版本未验证。默认跳数给多少、收口时要不要在群里明说,这是我仅个人判断、不是结论,需要确认——我在 issue #66 问过。实跑环境:zcode 里的 m3.1 flash 迭代代码,在 mcode 里对着官方 bot 和自己改的 bot-lab 做 8 步对照,机器是 linux-ubuntu-24.04。

@MyPrototypeWhat MyPrototypeWhat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢这个 PR。两道护栏默认关闭、只放在面板里,relay-guard.mjs 写成纯函数,测试也补齐了。我在本地拉了 711fa19 跑了一遍,下面是几点意见。

需要修改

1. 护栏关闭时,英文界面下的群常驻简报不再逐字一致

我实际生成了护栏全关时的群常驻简报,拿 711fa19 和 main(0.7.8)的输出逐字节比较:英文下多了一个空格(main 是 "...from a private chat.\n\n## Team",这个 PR 是 "...from a private chat. \n\n## Team"),中文一致。原因在 plugins/minimax-ai/minimax-bot/miniapp/node/engine.mjs:381:数组末尾总会追加 relayGuard ? t('brief.groupOneOwner') : '',然后整体 join(t('brief.sentenceJoin'))。中文的 brief.sentenceJoin 是空串(strings.mjs:245),英文是一个空格(strings.mjs:554),所以护栏关闭时英文末尾会多一个空格。这和 PR 描述里"护栏关着时与官方版逐字一致"的说法不符,已有会话升级后也会看到简报变化。群提示词(groupPrompt)本身中英文都一致。

建议只在需要时才把这一句放进数组,例如:

[
  t('brief.groupNoPost'),
  ...(relayGuard ? [t('brief.groupOneOwner')] : []),
].join(t('brief.sentenceJoin'))

另外,tests/relay-guard.test.mjs:169 那条"逐字一致"的测试比较的是同一份新代码在开/关两种状态下的输出,所以没能发现这个问题。建议补一个断言:护栏关闭时简报不以空格结尾(或者直接和一份 0.7.8 的基准字符串比较),中英文各测一次。

可选建议

  • 收口回合实际是第 N+1 回合,而且没带 taskId:描述和 README.md:36 说收口"替代被拦下的那一跳,不是额外多一轮"。但看实现,到上限时被拦下的那一跳并没有执行(engine.mjs:1177、engine.mjs:1217),而 convergeGroup 会让最后发言的 Bot 再跑一次模型回合(engine.mjs:341),所以一轮实际跑 limit+1 次模型回合;README.md:105 写的 "spends one extra Agent turn" 反而更贴近实现。另外,链里其他回合都会传 taskId: options.taskId(engine.mjs:1190),收口回合的调用(engine.mjs:341,参数从 engine.mjs:1230 传入)没有带 taskId,由任务派发进来的群接力,收口这一回合就不会挂在原任务下。建议把描述/README 统一成一种说法,并给收口回合也带上 taskId。
  • 服务端没有上限:面板 panels.js 里的输入框写了 max: 20,但 relay-guard.mjs:58 的 hopLimit 只处理了下限,通过 HTTP 接口直接 PATCH 一个很大的数也会被接受。可以在 hopLimit 里也夹一下上限,和面板保持一致。
  • replyMode 为 all 时的行为:engine.mjs:1177 的检查在队列里还有人时就会收口。"所有人回复"模式下初始队列本身就有多个成员,这时上限会截掉后面的成员,并让最后一个发言的成员做收口。如果这是有意为之,在面板提示或 README 里提一句会比较清楚;如果不是,可以只统计由 @ 交接产生的跳数。
  • 交接内容可能重复:上一位的回复本身已经出现在下一位提示词的"新消息"里(groupPrompt 会列出来),交接块又把它的尾部带了一遍。这不影响正确性,只是在描述里说明一下交接块的定位(强调结论),能帮使用者判断要不要打开它。
  • 版本号:这个 PR 把版本升到 0.7.9,#56 目前也是 0.7.9。两者谁后合入,谁就需要再升一个版本(plugin.json、package.json、mcp.mjs 里的 serverInfo 以及 README 的版本小节),这里先提个醒。

我这边核对过的

  • 改动只在插件目录内,没有读写 dataDir 以外的东西,没有新增文件读写或网络调用。
  • 模型这一侧改不了这两个设置:MCP 的 create_group / update_group 都没有新增参数(mcp.mjs 里的 schema 和参数转发都没变),list_team 返回的字段也没变,硬塞 max_hops / handover 不会生效,测试里也覆盖到了。面板走的 HTTP PATCH /api/groups/:id(server.mjs:615)会把请求体原样交给 updateGroup,和其他群字段一样,这是面板自己的写入路径。
  • 护栏关闭时 groupPrompt 不加任何内容,接力循环的行为和之前一致(limit 为 0 时两处新判断都不会触发)。
  • 新测试放在插件自己的 tests/ 里,仓库根目录 test/ 没有改动。
  • 本地结果(Node 22):先在插件的 tests/ 里执行 npm ci,再跑 node --test tests/*.test.mjs,231/231 通过;不装依赖时会有 7 个 UI/jsdom 相关的测试文件失败,main 上也是同样这 7 个。仓库根目录 npm run check 通过 validate,69 条测试全部通过。

主要就是第 1 点,改完我再看一眼。

…guardrails; clamp the hop limit; file the wrap-up under its task
@Apageoflove

Apageoflove commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor Author

按你说的第一条改了,另外几条也一并处理了,麻烦再看一眼。

英文多一个空格那条:one-owner 那句现在只在护栏开着时才进数组(就是你写的那种展开写法),不再拿一个空串占位,所以 join 的分隔符不会多出来。我把上游 0.7.8 和这个分支各自生成的群常驻简报拉出来逐字节比了一遍,英文和中文现在都一样(英文 6021 字符、中文 3095 字符,两边完全一致)。测试也补了:护栏关着时简报里不能出现那句 one owner、结尾不能是空白,而且群那一段必须正好是基线那几句用本语言的分隔符接起来——中英文各测一次。我自己的门禁里也补了一个专门比常驻简报的场景,之前只比了群提示词那一段,所以这个洞才漏过去。

收口回合那条:描述是我写错了。被拦下的那一跳确实不执行,但收口自己就是一次模型回合,所以到上限的一轮实际跑 limit+1 次回合,README 里那句 one extra Agent turn 反而是准的。我把 PR 描述、README 的 0.7.9 小节和代码注释都统一成 limit+1 这个说法。taskId 也补上了:收口那一回合现在带着 taskId 进去,所以从任务派发进来的群接力,收口也挂在原任务下。

上限没夹那条:hopLimit 现在把上限夹到 20,跟面板输入框的范围一致,不管这个值是面板填的还是直接 PATCH 进来的,补了一条断言。

replyMode=all 那条:行为我留着没改——上限算的是这一轮一共几个 Bot 说话,初始就在队列里的成员也算进去,跟「最多几个 Bot 说话」是一个意思。我在面板提示和 README 的 0.7.9 小节里都写明了这句。如果你们的口径是只数 @ 交接产生的跳数,我改起来很快,这是仅个人判断,需要确认。

交接内容可能重复那条:行为也没改,定位在文档里说清楚了——上一位的原话本来就在「新消息」里,交接块是把它的结论再强调一遍,模型自己判断要不要用。面板提示和 README 都加了这句。

版本号那条:按你说的保持 0.7.9。#56 现在在 0.7.8,今天没有真冲突;哪边先合,后合的那个再往后升一位。

边界照旧,正文里那几条没变。

@MyPrototypeWhat MyPrototypeWhat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 9f37a28.

Thanks for the fixes. The three items from the last pass are in good shape:

  1. Standing brief with the guardrails off. buildStandingBrief only spreads brief.groupOneOwner when the guard is on (engine.mjs:385-388), so the English brief.sentenceJoin space is no longer left hanging. With both knobs off, the group part is the same four baseline sentences joined by the language separator, then ## Team / ## 团队 with no trailing space. The new test covers en and zh against that baseline (tests/relay-guard.test.mjs:177-201).
  2. Hop limit clamped to 20. HOP_LIMIT_MAX = 20 and hopLimit uses Math.min(…, HOP_LIMIT_MAX) for both number and digit-string inputs (relay-guard.mjs:50-72). Create/update go through the same helper (team.mjs:337, :370), so a large PATCH value is stored as 20. Covered by tests/relay-guard.test.mjs:95-101.
  3. Wrap-up carries taskId. convergeGroup passes taskId: options.taskId into runBotTurn (engine.mjs:349-351), and the group close path forwards it (engine.mjs:1237-1239). The new test files the wrap-up under task_abc (tests/relay-guard.test.mjs:204-219). README / code comments now say a round that hits the limit runs limit + 1 Agent turns.

Defaults stay off, the MCP catalogue still omits max_hops / handover, and changes stay inside the plugin tree. Locally on Node 20.19.2: plugin suite 236 pass / 0 fail (node --test plugins/minimax-ai/minimax-bot/tests/*.test.mjs); scripts/validate.mjs reports plugins/minimax-ai/minimax-bot: OK and repository: OK; root test/**/*.test.mjs is 69/69 pass. GitHub check runs for this head were empty / pending when I looked (mergeable_state unstable).

Optional

  1. PR description still has the old wrap-up wording. The GitHub PR body still says the wrap-up “替代被拦下的那一跳,不是额外多一轮” (not an extra turn). README and the code comments on this head correctly say limit + 1. Worth aligning the PR body with README so reviewers don’t get two stories.

  2. Version vs #56. This PR correctly ships as 0.7.9. #56 is still on 0.7.8 on its current head, so there is no live clash today; if both land, the later merge should bump past whatever is already on main (plugin.json, package.json, mcp.mjs serverInfo, and the README version section).

  3. convergeGroup JSDoc. The options typedef still lists sourceMessageId / sourceMessageIds / signal and not taskId (engine.mjs:333), while the implementation now uses it. A one-line typedef update would match the code.

@Apageoflove

Apageoflove commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor Author

三条可选的我都处理了。你抓的正文那句我确实漏了——代码注释和 README 那一轮改了,PR 正文还写着旧说法,现在正文也统一成 limit+1 了(测试数也改成 18)。版本号按你说的保持 0.7.9,#56 现在还在 0.7.8,就没冲突,哪边先合我再往后升一位(plugin.json、package.json、mcp.mjs 的 serverInfo、README 的版本小节四处一起)。convergeGroup 的 typedef 补上 taskId 了(engine.mjs:325),刚推的 8cebd83 就是这一行,没强推。

本地这边同步的数:插件套件 236 过 / 0 挂,validate 报 minimax-bot: OK 和 repository: OK,根测试 69/69——和你跑的一致,我这轮在 Node 22.23.3 上跑的,你那边 20.19.2,两个版本都对得上。CI 那个 check 在 03:40 也跑起来了,success,mergeable_state 现在是 clean。

麻烦你再看一眼。

@MyPrototypeWhat
MyPrototypeWhat merged commit feffbaa into MiniMax-AI:main Oct 26, 2026
1 check 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.

2 participants