Skip to content
Open
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.26",
"version": "0.7.28",
"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
5 changes: 5 additions & 0 deletions plugins/minimax-ai/minimax-bot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ The hand-over between two Bots of a group carried the tail of the previous reply

- 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.28 A reset that cannot be saved keeps the transcript

- **Start new chat**, deleting a Bot and deleting a group all empty the transcript and then write the state that forgets the chat. When that state write failed, the empty transcript was already on disk: the request reported a failure, the chat was still listed, and its history was gone for good.
- The state is now written first, and the in-memory session entry is dropped only after that write succeeded. A failure leaves the chat, its messages and its session cursor as they were, so the same action can simply be tried again.

## 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
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.26' },
serverInfo: { name: 'minimax-bot', version: '0.7.28' },
instructions: t('mcp.instructions'),
};
break;
Expand Down
10 changes: 8 additions & 2 deletions plugins/minimax-ai/minimax-bot/miniapp/node/store.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -620,12 +620,18 @@ export class Store {

async deleteChat(chatId) {
await this.getMessages(chatId);
// Empty the transcript only after the state that forgets this chat is on disk. The messages are
// the one thing here that cannot be rebuilt: truncating them first means a failed save leaves
// the chat listed and the history already gone. In the opposite order a failed save leaves both
// intact, so the caller can simply try again. The in-memory entry is dropped only once the write
// succeeded, so a failure cannot leave memory ahead of the disk either.
await this.enqueue(async () => {
const { [chatId]: forgotten, ...sessions } = this.state.sessions;
await writeJsonAtomic(this.statePath, { ...this.state, sessions });
this.state.sessions = sessions;
await writeJsonAtomic(this.messagePath(chatId), []);
this.messageCache.set(chatId, []);
delete this.state.sessions[chatId];
});
await this.save();
}

/** Sidebar summary of one chat, read from the warm cache. */
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.26",
"version": "0.7.28",
"private": true,
"type": "module",
"mcode": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { appendFile, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { appendFile, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { Store } from '../miniapp/node/store.mjs';
Expand Down Expand Up @@ -83,6 +83,24 @@ test('concurrent appends and reactions retain every update and assign unique ord
assert.equal((await restored.getMessages(chat)).length, 1);
});

test('a reset that cannot be saved leaves the transcript on disk instead of emptying it', async (t) => {
const { dir, store, chat } = await fixture(t);
const kept = await store.appendMessage(chat, { role: 'user', text: 'Worth keeping' });
await store.writeChain;

// Put a directory where state.json belongs so the state write fails, and record what the
// transcript looked like before the reset was attempted.
const statePath = join(dir, 'state.json');
await rm(statePath, { force: true });
await mkdir(statePath, { recursive: true });

await assert.rejects(store.deleteChat(chat));
// The chat is still listed because its state never reached disk, so its history has to survive
// too: emptying the transcript first is what made a failed reset destroy the conversation.
const onDisk = JSON.parse(await readFile(store.messagePath(chat), 'utf8'));
assert.equal(onDisk.filter((message) => message.id === kept.id).length, 1);
});

test('legacy sequence migration preserves text/tool order and stable cursors across restarts', async (t) => {
const { dir, store, chat } = await fixture(t);
const parts = [{ type: 'text', text: 'The current conclusion: ' }, { type: 'tool', name: 'build', state: 'ok' }, { type: 'text', text: 'The current conclusion: publish after the build passes.' }];
Expand Down
Loading