Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/minimax-ai/minimax-bot/.minimax-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"schemaVersion": 1,
"name": "minimax-bot",
"displayName": "MiniMax Bot",
"version": "0.7.19",
"version": "0.7.20",
"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",
Expand Down
4 changes: 4 additions & 0 deletions plugins/minimax-ai/minimax-bot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ Two bounds are worth knowing. The archive keeps at most 1,000 records, so a `req

0.7.16 started to read the character in front of the `@` as a word of any script, so a letter of a script that writes with spaces (`josé@alice`) makes an address and calls nobody. This release completes that: a full-width IME writes full-width letters and digits (`OK@某人`), and Thai, Lao, Khmer and Myanmar write a word right up against the `@` as Chinese does, so those characters do not swallow the mention either.

## 0.7.20 The queue of a group is built from its member list

A group created before 0.7.8 can hold an admin that is not on its member list. Queueing that admin hands the group a speaker it then skips, so every message without an `@` went quiet: the reply was marked cancelled and nothing explained it. The queue is now built from the member list only — the admin speaks first when it is on the list, the first member takes over when it is not, and in the `all` mode only real members are queued. An `@` still beats every reply mode.

## 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.
Expand Down
5 changes: 4 additions & 1 deletion plugins/minimax-ai/minimax-bot/miniapp/node/engine.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1230,8 +1230,11 @@ export class Engine {
groupQueue(group, text) {
const mentioned = this.mentionsIn(text, group);
if (mentioned.length) return mentioned;
// Groups written before 0.7.8 can hold an adminId that is not on the member list; queueing
// such an admin hands the drive a speaker it silently skips, so the whole group goes quiet.
const admin = group.memberIds.includes(group.adminId) ? [group.adminId] : [];
const others = group.memberIds.filter((id) => id !== group.adminId);
return group.replyMode === 'all' ? [group.adminId, ...others] : [group.adminId ?? group.memberIds[0]];
return group.replyMode === 'all' ? [...admin, ...others] : admin.length ? admin : others.slice(0, 1);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion plugins/minimax-ai/minimax-bot/miniapp/node/mcp.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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.19' },
serverInfo: { name: 'minimax-bot', version: '0.7.20' },
instructions: t('mcp.instructions'),
};
break;
Expand Down
2 changes: 1 addition & 1 deletion plugins/minimax-ai/minimax-bot/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "minimax-bot",
"version": "0.7.19",
"version": "0.7.20",
"private": true,
"type": "module",
"mcode": {
Expand Down
63 changes: 63 additions & 0 deletions plugins/minimax-ai/minimax-bot/tests/group-queue.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// @ts-check
// Who speaks first on a new group message, read the way old groups still write it.
//
// The point of this file: a group's queue is built from its member list, and only from it.
// Groups created before 0.7.8 can hold an adminId that is not on memberIds (the state was
// written before the member list carried the admin); queueing that admin hands driveGroup a
// speaker it silently skips, so with the reply mode "admin" every un-@'d message went quiet
// (issues #63, #65). These assertions pin the rule: only a member queues, an off-list admin
// falls back to the first member, and an @mention still beats every reply mode.

import assert from 'node:assert/strict';
import { test } from 'node:test';

import { Engine } from '../miniapp/node/engine.mjs';

const bots = [
{ id: 'chief', name: 'Chief' },
{ id: 'alice', name: 'Alice' },
{ id: 'bob', name: 'Bob' },
];

/** A bare engine: groupQueue is a pure selector, it only reads this.mentionsIn and this.store. */
function engine() {
const bare = /** @type {any} */ (Object.create(Engine.prototype));
bare.store = { getBot: (id) => bots.find((bot) => bot.id === id) };
return bare;
}

function group(overrides = {}) {
return { adminId: 'chief', memberIds: ['chief', 'alice', 'bob'], replyMode: 'admin', ...overrides };
}

test('an admin on the member list opens the queue, the way it always did', () => {
const app = engine();
assert.deepEqual(app.groupQueue(group(), 'any message'), ['chief']);
// "all" keeps the admin first and the rest in member order.
assert.deepEqual(app.groupQueue(group({ replyMode: 'all' }), 'any message'), ['chief', 'alice', 'bob']);
});

test('an admin off the member list falls back to the first member', () => {
const app = engine();
// The stored state of an old group: the admin is gone from the members it rules.
assert.deepEqual(app.groupQueue(group({ adminId: 'ghost' }), 'any message'), ['chief']);
assert.deepEqual(app.groupQueue(group({ adminId: undefined }), 'any message'), ['chief']);
// And in "all" the ghost admin is not queued at all — only members speak, in member order.
assert.deepEqual(app.groupQueue(group({ adminId: 'ghost', replyMode: 'all' }), 'any message'), ['chief', 'alice', 'bob']);
});

test('a memberless group queues nobody', () => {
const app = engine();
assert.deepEqual(app.groupQueue(group({ memberIds: [] }), 'any message'), []);
assert.deepEqual(app.groupQueue(group({ memberIds: [], replyMode: 'all' }), 'any message'), []);
});

test('an @mention still beats the reply mode', () => {
const app = engine();
assert.deepEqual(app.groupQueue(group(), '@alice what do you think?'), ['alice']);
assert.deepEqual(app.groupQueue(group({ replyMode: 'all' }), '@bob please review'), ['bob']);
// Several mentions keep their order, and an off-list admin mentioned by name is not a member
// so mentionsIn never matches it in the first place.
assert.deepEqual(app.groupQueue(group(), '@bob and @alice'), ['bob', 'alice']);
assert.deepEqual(app.groupQueue(group({ adminId: 'ghost' }), 'no one addressed'), ['chief']);
});
Loading