From a603dafd28b03503906e002cfb29904245c48bfa Mon Sep 17 00:00:00 2001 From: EdgeMLHacker <126883848+Apageoflove@users.noreply.github.com> Date: Wed, 11 Nov 2026 18:13:48 +0800 Subject: [PATCH] fix(minimax-bot): the approval claim names the Host condition it depends on --- .../minimax-bot/.minimax-plugin/plugin.json | 2 +- plugins/minimax-ai/minimax-bot/README.md | 6 +- .../minimax-bot/miniapp/node/mcp.mjs | 2 +- plugins/minimax-ai/minimax-bot/package.json | 2 +- .../tests/readme-approval-claim.test.mjs | 104 ++++++++++++++++++ 5 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 plugins/minimax-ai/minimax-bot/tests/readme-approval-claim.test.mjs diff --git a/plugins/minimax-ai/minimax-bot/.minimax-plugin/plugin.json b/plugins/minimax-ai/minimax-bot/.minimax-plugin/plugin.json index d4e01de..bfc0594 100644 --- a/plugins/minimax-ai/minimax-bot/.minimax-plugin/plugin.json +++ b/plugins/minimax-ai/minimax-bot/.minimax-plugin/plugin.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "name": "minimax-bot", "displayName": "MiniMax Bot", - "version": "0.7.25", + "version": "0.7.26", "description": "Turn MiniMax Code into an always-on team of Bots. A Chief Bot helps you build the team; in group chats the admin answers and @mentions hand work to other members. Every Bot has its own octopus avatar and memory, with scheduled tasks and usage stats.", "author": "MiniMax Code", "icon": "icon.png", diff --git a/plugins/minimax-ai/minimax-bot/README.md b/plugins/minimax-ai/minimax-bot/README.md index fc5ff27..17ce0f5 100644 --- a/plugins/minimax-ai/minimax-bot/README.md +++ b/plugins/minimax-ai/minimax-bot/README.md @@ -91,6 +91,10 @@ The hand-over between two Bots of a group carried the tail of the previous reply - A failure whose cause the plugin cannot read — a Host error it has no sentence for — now has a sentence of its own: the turn did not finish and the reason was not identified. Where the Host throws that error, the text it came with stays out of the chat, so a session id or a service name is not shown to the user. A Host that reports the failure as a result keeps showing the text it sent. - The errors the plugin raises itself keep the message they were built with, and every code it knows keeps its own sentence. No code was added or renamed, and nothing is retried differently: only the words a failed turn shows changed. +## 0.7.26 The approval claim names the Host condition it depends on + +- The approval claim in **Agent turns** mixed the plugin's own behaviour with the Host's. The plugin never approves a call by itself — that part was true and stays true — but whether approval is asked for at all is decided by the Host's permission mode, and a Host that never asks for approval never shows the card. The old sentence stated the first half as if it also settled the second, so it read as a promise about an outcome the plugin does not control. It now names the Host condition, and keeps the plugin's own side: when approval is needed, the plugin never approves it by itself. + ## 0.7.10 Optional group notes - A group can keep a note of what it said. After every round the group records the closing lines of the replies, and every Bot of the group gets that record in its standing brief. @@ -195,7 +199,7 @@ Avatars are stored in the Mini App's `dataDir/avatars/`. `GET /api/avatars` list ## Data & access -- **Agent turns**: every Bot reply, scheduled task and background job is an Agent turn run through `context.agent`, with the Agent's full capabilities (files, shell, Skills and MCP, including this plugin's own MCP tools). A tool call that needs approval is shown in the page or in the session's sidebar tab; it runs only after you choose **Allow once**. Nothing is approved automatically. A group with a relay limit spends one more Agent turn on the round that reaches the limit: the wrap-up, which replaces the blocked hand-off. A group note spends no turn at all: it is written from the replies the round already produced. Every turn is counted once in the usage panel, background jobs included. +- **Agent turns**: every Bot reply, scheduled task and background job is an Agent turn run through `context.agent`, with the Agent's full capabilities (files, shell, Skills and MCP, including this plugin's own MCP tools). A tool call that needs approval is shown in the page or in the session's sidebar tab; it runs only after you choose **Allow once**. Whether a call needs approval at all is decided by the Host's permission mode; when it is needed, the plugin never approves it by itself. A group with a relay limit spends one more Agent turn on the round that reaches the limit: the wrap-up, which replaces the blocked hand-off. A group note spends no turn at all: it is written from the replies the round already produced. Every turn is counted once in the usage panel, background jobs included. - **Files written**: only inside the Mini App's `dataDir`: `state.json`, `messages/`, `usage.jsonl` (one row per Agent turn), `memory/` (including its journal, archive and backups), `uploads/` (your attachments) and `avatars/`. A group's note is part of that group's record in `state.json`, so it is removed with the group. - **Files read outside `dataDir`**: - `/api/media` serves a local file (up to 1 GiB) only when its absolute path appears in a Bot message of that same chat. diff --git a/plugins/minimax-ai/minimax-bot/miniapp/node/mcp.mjs b/plugins/minimax-ai/minimax-bot/miniapp/node/mcp.mjs index 1d32ebc..884b4cc 100644 --- a/plugins/minimax-ai/minimax-bot/miniapp/node/mcp.mjs +++ b/plugins/minimax-ai/minimax-bot/miniapp/node/mcp.mjs @@ -662,7 +662,7 @@ export function createMcpHandler({ team, engine }) { result = { protocolVersion: PROTOCOL_VERSIONS.includes(requested) ? requested : PROTOCOL_VERSIONS[0], capabilities: { tools: { listChanged: false } }, - serverInfo: { name: 'minimax-bot', version: '0.7.25' }, + serverInfo: { name: 'minimax-bot', version: '0.7.26' }, instructions: t('mcp.instructions'), }; break; diff --git a/plugins/minimax-ai/minimax-bot/package.json b/plugins/minimax-ai/minimax-bot/package.json index 3543c11..d81662b 100644 --- a/plugins/minimax-ai/minimax-bot/package.json +++ b/plugins/minimax-ai/minimax-bot/package.json @@ -1,6 +1,6 @@ { "name": "minimax-bot", - "version": "0.7.25", + "version": "0.7.26", "private": true, "type": "module", "mcode": { diff --git a/plugins/minimax-ai/minimax-bot/tests/readme-approval-claim.test.mjs b/plugins/minimax-ai/minimax-bot/tests/readme-approval-claim.test.mjs new file mode 100644 index 0000000..b5724d4 --- /dev/null +++ b/plugins/minimax-ai/minimax-bot/tests/readme-approval-claim.test.mjs @@ -0,0 +1,104 @@ +// @ts-check +// The approval claim in the README, written as executable assertions. +// +// The point of this file: the sentence that describes approval is checked, not read. +// +// 1. the Agent-turns bullet says the condition its claim depends on (the Host's +// permission mode) and says what the plugin still guarantees — that when approval is +// asked for it never approves by itself. Appending an "approved automatically" +// qualifier while keeping the old promise fails, and so does flipping the claim to +// "the plugin approves it by itself"; +// 2. the plugin version is one value in the manifest, in package.json and in the +// serverInfo the MCP server reports, so no release ships as three different things; +// 3. the README has a changelog entry for that version, after the 0.7.24 entry, with a +// bullet of its own that talks about the approval claim — a renamed heading alone is +// not an entry. + +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { test } from 'node:test'; +import { join } from 'node:path'; + +const root = join(import.meta.dirname, '..'); + +/** @param {string} name */ +const readText = (name) => readFileSync(join(root, name), 'utf8'); + +/** @param {string} name */ +const readJson = (name) => JSON.parse(readText(name)); + +/** + * The body of a numbered changelog section: its bullets, without the heading line, up to + * the next `## ` heading. + * + * `heading` may be the start of a heading (`## 0.7.24 `) or its whole text including a + * topic (`## 0.7.26 The approval claim`): a version number alone does not identify a + * section, because two candidates can carry the same number and the first one found is + * not necessarily the entry being checked. + * @param {string} readme + * @param {string} heading the heading prefix to match, e.g. `## 0.7.24 ` + */ +function sectionOf(readme, heading) { + const lines = readme.split('\n'); + const start = lines.findIndex((line) => line.startsWith(heading)); + assert.notEqual(start, -1, `README has no section starting with ${JSON.stringify(heading)}`); + const next = lines.findIndex((line, index) => index > start && line.startsWith('## ')); + const end = next === -1 ? lines.length : next; + return { at: start, heading: lines[start], body: lines.slice(start + 1, end).join('\n') }; +} + +test('the Agent-turns bullet makes the approval claim conditional on the Host permission mode', () => { + const readme = readText('README.md'); + const bullet = readme.split('\n').find((line) => line.startsWith('- **Agent turns**')); + assert.ok(bullet, 'README has an Agent turns bullet'); + + // It has to name the condition its claim depends on, in the README's own words: the + // Host decides whether approval is asked for at all. + assert.match(bullet, /the Host's permission mode/i); + // And it has to keep the plugin's own side of the claim, as the reviewed clause: when + // approval is asked for, the plugin never approves it by itself. + assert.match(bullet, /when it is needed, the plugin never approves it by itself/i); + // The old unconditional promise must be gone, including the variants that phrase the + // same promise ("nothing is ever approved automatically", "nothing gets approved ..."). + assert.doesNotMatch(bullet, /\bnothing (?:is|is ever|gets) (?:ever )?approved automatically\b/i); + // And the claim must not be flipped: the plugin does not approve the call by itself. + assert.doesNotMatch(bullet, /plugin approves (?:it|the call|a call) by itself/i); + // Same guard in the words a later edit would actually reach for. The reviewed sentence + // uses neither, so these cannot fire on the text this test is written for: nothing in + // the bullet says a call is approved without a person, and nothing says the plugin can + // approve one. + assert.doesNotMatch(bullet, /\bautomatically\b/i); + assert.doesNotMatch(bullet, /(?:can|may|will|would|does)\s+approve/i); +}); + +test('the plugin version is the same value in the manifest, package.json and serverInfo', () => { + const version = readJson('.minimax-plugin/plugin.json').version; + assert.match(version, /^\d+\.\d+\.\d+$/); + + assert.equal(readJson('package.json').version, version); + + const server = readText('miniapp/node/mcp.mjs'); + const found = server.match(/serverInfo:\s*\{[^}]*version:\s*'([0-9]+\.[0-9]+\.[0-9]+)'/); + assert.ok(found, 'mcp.mjs reports a serverInfo version'); + assert.equal(found[1], version); +}); + +test('the README has a changelog entry for this version, after the 0.7.24 one', () => { + const readme = readText('README.md'); + const version = readJson('.minimax-plugin/plugin.json').version; + + const previous = sectionOf(readme, '## 0.7.24 '); + // The release under review is found by its version alone, so a later release with a + // topic of its own stays green. + const current = sectionOf(readme, `## ${version} `); + assert.ok(current.at > previous.at, `the ${version} entry comes after the 0.7.24 entry`); + + // The approval claim is pinned to this release's own section, named literally: it is + // this changelog entry that has to talk about it, not whichever section a later + // version happens to put first. A renamed heading alone is not an entry either — the + // approval claim has to be talked about in a bullet, not in the heading. + const documented = sectionOf(readme, '## 0.7.26 '); + const bullets = documented.body.split('\n').filter((line) => line.startsWith('- ')); + assert.ok(bullets.length >= 1, 'the 0.7.26 entry has at least one bullet, not just a heading'); + assert.match(bullets.join('\n'), /approval/u); +});