Skip to content

fix(minimax-bot): sort the job rows before the fold takes them, clarify the README - #52

Merged
MyPrototypeWhat merged 5 commits into
MiniMax-AI:mainfrom
July-X:docs/minimax-bot-clarify-queue-fold
Oct 21, 2026
Merged

MyPrototypeWhat merged 5 commits into
MiniMax-AI:mainfrom
July-X:docs/minimax-bot-clarify-queue-fold

Conversation

@July-X

@July-X July-X commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

This is the follow-up to #51, which merged the fold of the background job queue and the flat reply bubbles. Two things were still open after the merge, and both are fixed here:

  • The README said the strip folds "when more than three are queued". The fold counts every job the strip lists, the ones that are already working included.
  • The rows the fold keeps were picked by status but drawn in the order the jobs arrived in, so a queued job could sit above a job that was already working. The rows are sorted before the fold takes them now.

What changed

  • The rows of the job strip are sorted before the fold takes the first three of them. The status comes first: the jobs already working, then the ones waiting for another job, then the queued ones. Jobs of the same kind are ordered by the time they arrived, the moment they started or the moment they were queued. A status the strip does not know yet comes last, so a new one cannot scramble the order. The strip sorts the rows in every case, so a queued job never sits above a job that is already working and the rows do not swap places when the fourth job arrives.
  • The Background work paragraph in the README now says what the fold does: it counts every job the strip lists, and it keeps the first three of them in the open.
  • The version moves from 0.7.6 to 0.7.7.

Test environment and results

  • MiniMax Code 3.1.2 on Windows 10.0.19045, Node.js 22.
  • npm run check in the repository root reports no errors and no warnings, and the 69 contract tests pass.
  • The package test suite passes 202 of 205 tests. The three failures are environmental and reproduce without this change:
    • "restart after questionnaire preserves the next group member before publishing completion" and "restart after dependency verification preserves the next group member before publishing completion" time out after 12 s on this machine. The same two fail on the unmodified baseline.
    • "avatar library persists imports, deduplicates concurrent uploads and reuses them across Bots and restarts" fails the same way on the unmodified baseline: one of the four parallel imports comes back with another status.
  • The client reliability suite passes 32 of 32, including the case that checks the order of the rows the fold keeps, that a strip of two jobs still puts the running job first, and that two queued jobs keep the order they arrived in.
  • Measured in a real browser (headless Chrome over CDP) against a seeded chat of 30 jobs: the folded strip shows the three jobs that are already working and "还有 27 个任务"; opening the queue draws all 30 rows, the six running ones first, then the three waiting for another job, then the twenty-one queued ones.

Unverified

  • The layout was measured in headless Chrome at one window size. Other window sizes and a narrow sidebar were not checked.
  • Only a seeded queue was folded. A queue that grows while it is open was not watched.
  • The two 12 s timeouts above were left alone. They come from the speed of this machine, not from this pull request.

这是 #51 的后续。#51 合并了后台任务队列的折叠和回复气泡的平铺。合并之后还开着两件事,这两件都在这里修掉:

  • README 写的是"超过三个在排队时折叠",但折叠算的是条上列出的每一个任务,在跑的那些也算。
  • 折叠挑哪三行是按状态挑的,画出来却仍按任务到达的顺序,所以一个还在排队的任务可能排在正在跑的任务上面。现在先排序再取。

改了什么

  • 任务条的行先排序,再由折叠取走前三个。状态在前:正在跑的、等着子任务的、排队的。同一类任务之间按到达时间排,也就是它开始的时刻或排队的时刻。条上还没见过的状态排在最后,所以以后加新状态也不会把顺序打乱。所有情况下都排序,所以还在排队的任务不会排在正在跑的任务上面,第四个任务进来时行也不会换位置。
  • README 的 Background work 那段现在说的是折叠实际的行为:它算条上列出的每一个任务,并把前三个留在外面。
  • 版本 0.7.6 → 0.7.7。

测试环境与结果

  • MiniMax Code 3.1.2、Windows 10.0.19045、Node.js 22。
  • 仓库根目录 npm run check 无错误无警告,69 条契约用例通过。
  • 包内测试 205 条通过 202 条,3 条失败与本改动无关,未改动的基线同样失败:
    • "restart after questionnaire …" 和 "restart after dependency verification …" 在本机 12 秒超时,基线上同样失败。
    • "avatar library persists imports …" 在未改动的基线上同样失败:四个并发导入里有一个返回了别的状态。
  • 客户端用例 32 条全过,含"折叠取的行按什么顺序"、"两个任务时正在跑的那个仍然在前"、"两个排队的任务保持到达顺序"这几处断言。
  • 真实浏览器(headless Chrome + CDP)配 30 个任务的种子数据实测:折叠态显示三个正在跑的任务和"还有 27 个任务";展开后画 30 行,6 个正在跑的排最前,3 个等子任务的其次,21 个排队的最后。

未验证

  • 版式只在 headless Chrome 的一个窗口尺寸下量过,其他窗口尺寸和窄侧栏没查。
  • 只测了种子队列,展开期间队列继续变长的情况没观察。
  • 上面两条 12 秒超时没动,是本机速度问题,不是这个 PR 带来的。

The fold of the job queue kept the rows in the order the jobs arrived
in, and only used the status to decide which three rows stay in the
open. A queued job could therefore sit above a job that was already
working, and the strip read as if the queue had not started.

The rows are sorted before the fold takes the first three now: the jobs
that are already working come first, the ones waiting for another job
next, the queued ones last. The sort is stable, so jobs of the same
kind keep the order they arrived in.

A strip that fits in the open rows has nothing to fold and keeps the
order the official build draws, so the rows of a quiet chat are the
official ones.
The Background work paragraph said the strip folds "when more than three
are queued". The fold counts every job the strip lists, the ones that
are already working included, and it keeps the first three of them in
the open. The sentence now says that.

@hetaoBackend hetaoBackend left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the follow-up. The change is small and correct: the comparator is safe (renderHelpers already filters to the three statuses in HELPER_STATUS_ORDER), the version is bumped consistently in plugin.json, package.json and mcp.mjs, and the new assertions fail against the current main app.js and pass with this change. One design question and two nits inline, none blocking.

const keep = new Set(ranked.slice(0, HELPER_ROWS_PREVIEW));
return items.filter((item) => keep.has(item));
function orderedHelperRows(items, foldable) {
if (!foldable) return items;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The order now depends on how many jobs are listed, and that causes two things:

  • With three or fewer jobs, a queued job can still sit above a running one, which is the situation this PR describes as the bug. The new assertion plain[0] = Queued task 1 in the test locks that in as expected behaviour.
  • When the fourth job arrives the whole strip reorders, and when an open queue drops back to three the rows return to arrival order. The same rows visibly swap places at the threshold.

With three or fewer jobs, sorting does not change which rows are shown, only their order, and the PR treats the sorted order as the better one. Could we always sort (and drop the foldable parameter)? If matching the official order below the threshold is a hard requirement, a comment explaining what it buys would help the next reader.

return items.filter((item) => keep.has(item));
function orderedHelperRows(items, foldable) {
if (!foldable) return items;
return [...items].sort((a, b) => HELPER_STATUS_ORDER[a.status] - HELPER_STATUS_ORDER[b.status]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: the commit message and PR description say jobs of the same kind "keep the order they arrived in", but the input here is [...state.helpers, ...state.tasks] (line 1254), so the pre-sort order is helpers first, then tasks, not chronological. The stable sort keeps order within helpers and within tasks, but a queued helper created later still sorts ahead of an earlier queued task. The test shows this too: Answer the ticket starts after every helper. Either add a tie-breaker such as (a.startedAt ?? a.createdAt) - (b.startedAt ?? b.createdAt), or reword the claim.

- **Group chats**: the group admin answers by default. `@Name` calls on a specific member, and a Bot that `@`-mentions another member in its reply hands the turn over. Members with nothing to add reply `[PASS]` and are skipped. Each member keeps a separate session per group, and groups cannot see private chats. There is no fixed reply cap. Discussions finish naturally, or wait for more instructions after repeated handoffs without tool progress.
- **Hand-offs between Bots**: `message_bot` hands work to another Bot and immediately returns an admission receipt; the completed result returns to the source chat. `post_group` is **private chat only**: it posts to a group and returns immediately while the discussion continues according to the group's reply mode. From a private chat, Chief can post to any group without joining it; other Bots can only post to groups they belong to. During a group turn, `post_group` is disabled for everyone, including Chief and admins, for both same-group and cross-group posts. Use `@Name` in a reply to hand off within the group; start cross-group work from a private chat.
- **Background work**: when an isolated execution context is useful, a Bot calls `start_helper` to run it in the background in a fresh, separate session; the Bot's turn ends at once and you can keep chatting. The Bot simply says something like "let me look into it" without internal jargon or IDs. When the job finishes, its report comes back to the Bot as a new turn, and the Bot reviews it and passes it on in its own words (in groups too, where it can keep handing off with `@`). At most 2 background jobs run at once (one slot is always kept free for chat) and the rest queue; each Bot can have at most 4 queued or running. Running jobs are shown above the chat input and can be stopped at any time; when more than three are queued, the jobs that are already working stay in the open and the rest fold into one line that says how many are left, and one button below the rows opens the whole queue; tool approvals from a background job appear in its chat; jobs still running when the Mini App restarts are marked interrupted. Bots can also manage jobs with `list_helpers` / `stop_helper`. "Allow background helpers automatically" is on by default in Bot settings; when off, a Bot only does so when you explicitly ask.
- **Background work**: when an isolated execution context is useful, a Bot calls `start_helper` to run it in the background in a fresh, separate session; the Bot's turn ends at once and you can keep chatting. The Bot simply says something like "let me look into it" without internal jargon or IDs. When the job finishes, its report comes back to the Bot as a new turn, and the Bot reviews it and passes it on in its own words (in groups too, where it can keep handing off with `@`). At most 2 background jobs run at once (one slot is always kept free for chat) and the rest queue; each Bot can have at most 4 queued or running. Running jobs are shown above the chat input and can be stopped at any time; when more than three jobs are listed, the strip sorts them by how far along they are, the ones already working first and the ones waiting for another job next, keeps those first three in the open and folds the rest into one line that says how many are left, and one button below the rows opens the whole queue; tool approvals from a background job appear in its chat; jobs still running when the Mini App restarts are marked interrupted. Bots can also manage jobs with `list_helpers` / `stop_helper`. "Allow background helpers automatically" is on by default in Bot settings; when off, a Bot only does so when you explicitly ask.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit on wording: in "…keeps those first three in the open…" it is unclear what those refers to; "keeps the first three in the open" reads more clearly. The sentence also gives working and waiting jobs a position but not queued ones, so "and queued ones last" would complete the order.

@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.

Thanks for the quick follow-up! This is a clean fix for #51.

  • The sort is correct: Array.prototype.sort is stable, so jobs with the same status stay in their original order, and running → waiting_for_tasks → queued come first before the top 3 are taken.
  • When there are ≤3 rows, orderedHelperRows returns items untouched, so the original order is kept. The new test checks this.
  • An empty list returns early. With exactly 3 rows the strip isn't foldable, so nothing is reordered and no fold button shows.
  • The version is 0.7.7 in all three places: package.json, .minimax-plugin/plugin.json, and serverInfo in mcp.mjs.
  • The README wording now matches the code: the fold counts every listed job, running ones included.
  • The change only touches minimax-bot.

Locally npm run check passes (Node 22), and the plugin's node --test tests passes 205/205 (Node 20).

One small, non-blocking suggestion (inline): HELPER_STATUS_ORDER[status] is undefined for a status outside the map, so the comparator would return NaN and the order would be undefined. Today renderHelpers filters items down to the three known statuses, so this can't happen yet. A fallback rank such as ?? 99 would keep it safe if a new status is added later.

return items.filter((item) => keep.has(item));
function orderedHelperRows(items, foldable) {
if (!foldable) return items;
return [...items].sort((a, b) => HELPER_STATUS_ORDER[a.status] - HELPER_STATUS_ORDER[b.status]);

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.

Nit (non-blocking): a status missing from HELPER_STATUS_ORDER gives undefined, so the comparator returns NaN and the order becomes undefined. The filter in renderHelpers only lets the three known statuses through today, but a fallback would keep this safe later:

Suggested change
return [...items].sort((a, b) => HELPER_STATUS_ORDER[a.status] - HELPER_STATUS_ORDER[b.status]);
const rank = (item) => HELPER_STATUS_ORDER[item.status] ?? Number.MAX_SAFE_INTEGER;
return [...items].sort((a, b) => rank(a) - rank(b));

The rows of the strip were sorted only while the fold was in play, so a
queued job could sit above a job that was already working when three
jobs or fewer were listed, and the rows swapped places when the fourth
one arrived. The strip sorts them now, always.

The status comes first: the jobs already working, then the ones waiting
for another job, then the queued ones. Jobs of the same kind are
ordered by the time they arrived, which is the moment they started or
the moment they were queued. A status the strip does not know yet comes
last, so a new one cannot scramble the order.
The Background work paragraph described the fold but not the order the
rows are in. It now says that the strip orders the jobs by how far
along they are and keeps the first three in the open.
@July-X

July-X commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, both. All four comments are addressed in the two commits on top.

Always sort (hetaoBackend). The strip sorts the rows in every case now, and the foldable parameter is gone. A queued job can no longer sit above a job that is already working while three jobs or fewer are listed, and the rows no longer swap places when the fourth job arrives. The cost is that a quiet strip of one or two jobs is drawn in status order rather than the order the jobs arrived in. We took it: the strip is our own layer, the sorted order is the one this PR describes as the better one, and one order for the whole strip is easier to follow than one order below the threshold and another above it.

Arrival time as the tie-breaker (hetaoBackend). Jobs of the same kind are ordered by the time they arrived, the moment they started or the moment they were queued. The claim in the commit message and in the description now holds on its own, and the pre-sort order, helpers first and then tasks, no longer decides it.

README wording (hetaoBackend). "keeps the first three in the open" instead of "those first three", and the order now names the queued ones last.

Fallback rank (MyPrototypeWhat). Taken as written: helperRank() returns Number.MAX_SAFE_INTEGER for a status the map does not have, so a new status cannot make the comparator return NaN.

The test changed with it: the assertion that a queued job stays on top below the threshold is gone, the strip of two jobs now asserts that the running job comes first, and a new case checks that two queued jobs keep the order they arrived in.

@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.

Thanks for the quick update! The new commits look good to me.

  • helperRank() falls back to Number.MAX_SAFE_INTEGER, so a status the strip doesn't know yet sorts last instead of giving a NaN comparison.
  • The arrival tie-break (startedAt ?? createdAt ?? 0) is safe. Both fields are numeric Date.now() values everywhere they are set, ?? also covers null, and 0 is a sensible floor when both are missing. Within the same status the order is now deterministic: oldest first.
  • Always sorting, even when ≤3 rows are listed, makes the strip consistent, and the README now describes exactly that: working, then waiting, then queued, with the first three kept in the open.
  • The version is still 0.7.7 in package.json, plugin.json, and serverInfo, and the change stays within minimax-bot.

Locally npm run check passes (Node 22), and the plugin's node --test tests passes 205/205 (Node 20).

@MyPrototypeWhat
MyPrototypeWhat merged commit 6d0ef1f into MiniMax-AI:main Oct 21, 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.

4 participants