fix(story-mode): a second opinion in front of helpers neither splits them nor counts as one

Since second opinions ride on the next panel, an advisor call sorted to
the front of a helper panel tripped the old rule that kept advisor
panels apart, so "advisor, helper A, helper B" drew two helper panels,
and the first counted the advisor as a helper ("Claude calls in 2
helpers" for one helper). The rule has no job left and is gone; a
riding second opinion is never counted as a helper. One recorded page
changes: "Claude sends 1 helper off to work in the background."

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-18 16:23:11 +02:00
1 parent 6a6c7c0949
commit 277cc88148
3 files changed
+30 -7

No files matched your search

+3 -6
View File
@@ -2104,11 +2104,7 @@ function groupPanels(events: StoryEvent[]): {
const kind = ev.todos && ev.intent === 'say' ? 'plan' : PANEL_FOR[ev.intent];
if (!kind) return;
const last = panels[panels.length - 1];
const advisorMix =
kind === 'backup' &&
last?.kind === 'backup' &&
(events[last.events[0]].tool === 'Advisor') !== (ev.tool === 'Advisor');
if (last && last.kind === kind && MERGEABLE.has(kind) && !advisorMix) {
if (last && last.kind === kind && MERGEABLE.has(kind)) {
last.events.push(idx);
takeOpinions(last);
if (pendingSay && !last.balloon) last.balloon = { who: 'claude', text: pendingSay };
@@ -2424,8 +2420,9 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
break;
}
case 'backup': {
// A second opinion riding on this panel is no helper of its own.
const helpers = draft.events
.filter((i) => events[i].helper)
.filter((i) => events[i].helper && (!panel.secondOpinion || events[i].tool !== 'Advisor'))
.map((i) => ({
name: (events[i].helper as StoryHelper).name,
type: (events[i].helper as StoryHelper).type,
@@ -51,7 +51,7 @@
{
"kind": "backup",
"ok": null,
"story": "Claude calls in 2 helpers.",
"story": "Claude sends 1 helper off to work in the background.",
"director": "Agent · general-purpose · 6 calls inside · after 1 second opinion",
"balloon": null,
"tests": null,
+26
View File
@@ -1312,6 +1312,32 @@ describe('buildStoryPage', () => {
);
});
it('a second opinion in front of two helpers neither splits them nor counts as one', () => {
const advisor = {
type: 'assistant',
timestamp: '2026-10-18T09:41:20.000Z',
message: {
content: [
{ type: 'server_tool_use', id: 'srv1', name: 'advisor', input: {} },
{ type: 'advisor_tool_result', tool_use_id: 'srv1', content: { type: 'advisor_redacted_result' } },
],
},
};
const page = pageOf([
prompt('go'),
advisor,
tool('a1', 'Agent', { description: 'A', subagent_type: 'general-purpose', prompt: 'x' }),
result('a1', 'done A'),
tool('a2', 'Agent', { description: 'B', subagent_type: 'general-purpose', prompt: 'y' }),
result('a2', 'done B'),
end(1000),
]);
const backups = page.panels.filter((p) => p.kind === 'backup');
expect(backups).toHaveLength(1);
expect(backups[0]).toMatchObject({ secondOpinion: 1, caption: { story: 'Claude calls in 2 helpers.' } });
expect(backups[0].helpers?.map((h) => h.task)).toEqual(['A', 'B']);
});
it('the advisor (a server tool) is a helper giving a second opinion', () => {
const page = pageOf([
prompt('think hard'),