mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 02:29:33 +01:00
fix(story-mode): a helper reporting back is heard in its own words
From review round 9: a background job's page showed only Claude Code's line 'Agent "Helper B: write extra pytest tests" finished', so what the helper said about its own work vanished, here that it had never run its tests against the real wordcount.py. The notice balloon now carries the helper's report like Claude's last words (its first sentence and the key sentences from the rest): '"Helper B: write extra pytest tests" reports: I wrote the file: tests/test_extra.py It has 18 test cases … I haven't run it against the real wordcount.py.' A report that is only a path is its file name, and absolute paths keep their last two parts, so a long path does not push the sentence that matters out of a phone's balloon. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
226a7bbbd8
commit
2ec74273f0
6 files changed
+36
-6
No files matched your search
@@ -1083,7 +1083,7 @@ A Bash call sent to the background (its result is only `Command running in backg
|
||||
A `page` carries the turn's summary fields plus:
|
||||
|
||||
- `events`: one per prompt, sentence Claude wrote and tool call. Each has `intent`, `tool`, `label` and, when the call returned, `ok` (`false` when the result was flagged as an error, or reported failed tests or lint problems a pipe kept from the exit code). Depending on the call: `edit` (an Edit/Write, hunks in Claude Code's own `structuredPatch` shape), `edits` (files a Bash call wrote through here-documents or `printf`/`echo` redirects, that a python or node script fed by a here-document opens for writing, or that Claude Code says it changed; the last two have no lines, since what changed is not in the command; an append's hunk has `atEnd: true` and no line numbers, since where it lands is not in the command), the tail of `output`, `tests` (`{ passed, failed, errors? }`, counted on the whole output before it is cut), `helper`, `files` (files a Bash call printed with `cat`/`head`/`tail`, resolved against the command's folder), `headline` (the part of a one-liner that decided its intent), `commits`, `subject` (the first commit's message, when the command spells it out with `-m` or a `git commit -F -` here-document), `ships` (`push` / `pr`), `running`, `background` and `finishedLater` (see Background work), `keyPoints` (for a sentence Claude wrote: up to two sentences from further into a long message that a reader must not lose, such as an open problem, something not yet done or checked, or a word on what you asked for; read on the whole message, past the `label`'s clip, and used by the final panel's balloon), `question` and, for prompts, `origin`. The advisor (a server tool) is an event with tool `Advisor` and intent `helper`; its answer is encrypted in the transcript, so it is never a panel of its own: the panel of the step after it (or the last one, when nothing follows) holds its events and counts them in `secondOpinion`. In plan mode the plan file under `~/.claude/plans/` is planning (intent `plan`, its numbered steps as `todos`), and `ExitPlanMode`'s result carries the plan (`output`) and your answer (`Approved` / `Sent back`).
|
||||
- `panels`: at most 12, each `{ kind, events, caption: { story, director }, balloon?, files?, notes?, helpers?, todos?, tests?, ok?, rounds?, roundsOk?, question?, live? }`, with `events` as indices into `events`. A run panel's `printed` holds the first lines its last command printed (its last lines when it failed), for the terminal card: the card is drawn on the canvas and goes into a saved image, so lines that look like secrets are left out and addresses and long token-like strings are masked. `balloon.who` is `you`, `claude` or `note` (words that start a turn but are not yours: a background job's report, another session's message). A loop's `roundsOk` lists every test run in order, `true` where it passed and `null` for one still running, so a failure inside a round that passed in the end still shows, and a live loop shows the runs that finished. Kinds: `mission`, `investigate`, `research`, `plan`, `backup`, `fix`, `check`, `loop` (fix and check rounds folded), `run`, `save`, `ask`, `compact`, `montage` (quieter panels folded to fit) and `complete`.
|
||||
- `panels`: at most 12, each `{ kind, events, caption: { story, director }, balloon?, files?, notes?, helpers?, todos?, tests?, ok?, rounds?, roundsOk?, question?, live? }`, with `events` as indices into `events`. A run panel's `printed` holds the first lines its last command printed (its last lines when it failed), for the terminal card: the card is drawn on the canvas and goes into a saved image, so lines that look like secrets are left out and addresses and long token-like strings are masked. `balloon.who` is `you`, `claude` or `note` (words that start a turn but are not yours: a background job's report, another session's message); a helper's report keeps its own words there (`"<helper>" reports: …`, its first sentence and key points, absolute paths shortened to their last two parts). A loop's `roundsOk` lists every test run in order, `true` where it passed and `null` for one still running, so a failure inside a round that passed in the end still shows, and a live loop shows the runs that finished. Kinds: `mission`, `investigate`, `research`, `plan`, `backup`, `fix`, `check`, `loop` (fix and check rounds folded), `run`, `save`, `ask`, `compact`, `montage` (quieter panels folded to fit) and `complete`.
|
||||
- `filesChanged`, `added`, `removed` (summed per edit), `helpers` (the turn's own helper calls; a report turn's prompt carries the helper it answers, which is not counted), `helpersRunning`, `noneWorked` (every tool call of the turn failed, so the last panel is no celebration; a command whose last check only found nothing, exit code 1 after its answer, does not count as failed), `durationMs`, `partial` (the turn began before the part that was read) and `truncated` (older outputs, hunks and helper texts were dropped to keep the page small: a page carries at most 120,000 characters of them, newest first; an edit that lost its lines keeps its counts and an empty `hunks`).
|
||||
|
||||
## Git worktrees of a case
|
||||
|
||||
@@ -2376,6 +2376,26 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
: lines[origin];
|
||||
// Words that started the turn but are not yours are a note, not your balloon.
|
||||
if (panel.balloon && !command && origin !== 'human') panel.balloon = { ...panel.balloon, who: 'note' };
|
||||
// A helper reporting back: its own words, not only the line saying it finished, so what it
|
||||
// did not do ("I haven't run them against the real file") is on the page.
|
||||
const report = origin === 'task' && evs[0]?.helper?.result?.trim();
|
||||
if (panel.balloon && report) {
|
||||
// A report that is only a path says which file it wrote: its name is enough.
|
||||
const said = (
|
||||
/^\S+$/.test(report) && report.includes('/') ? basename(report) : closingText(report, keyPointsOf(report))
|
||||
)
|
||||
// An absolute path is mostly room: its last two parts ("tests/test_extra.py") say which file.
|
||||
// Run on the clipped balloon (560 characters at most), so the scan stays short.
|
||||
.replace(/(?<![\w.~-])\/(?:[^\s/]+\/)+([^\s/]+\/[^\s/]+)/g, '$1');
|
||||
const name = /^Agent "(.+)" (?:finished|completed)\.?$/.exec(panel.balloon.text)?.[1];
|
||||
if (said)
|
||||
panel.balloon = {
|
||||
who: 'note',
|
||||
text: name
|
||||
? `"${name}" reports: ${said}`
|
||||
: `${panel.balloon.text.replace(/[.]?$/, '.')} Its report: ${said}`,
|
||||
};
|
||||
}
|
||||
director = command
|
||||
? `Command · ${clip(evs[0].label, 60)}`
|
||||
: `Prompt · ${plural(words, 'word')}${origin === 'human' ? '' : ` · ${origin}`}`;
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
"ok": null,
|
||||
"story": "A background job reports back.",
|
||||
"director": "Prompt · 6 words · task",
|
||||
"balloon": "Agent \"Helper A: write README.md\" finished",
|
||||
"balloon": "\"Helper A: write README.md\" reports: README.md",
|
||||
"tests": null,
|
||||
"roundsOk": null,
|
||||
"printed": null,
|
||||
@@ -147,7 +147,7 @@
|
||||
"ok": null,
|
||||
"story": "A background job reports back.",
|
||||
"director": "Prompt · 8 words · task",
|
||||
"balloon": "Agent \"Helper B: write extra pytest tests\" finished",
|
||||
"balloon": "\"Helper B: write extra pytest tests\" reports: I wrote the file: tests/test_extra.py It has 18 test cases and I worked out each expected value by hand. I haven't run it against the real wordcount.py.",
|
||||
"tests": null,
|
||||
"roundsOk": null,
|
||||
"printed": null,
|
||||
@@ -328,7 +328,7 @@
|
||||
"ok": null,
|
||||
"story": "A background job reports back.",
|
||||
"director": "Prompt · 6 words · task",
|
||||
"balloon": "Agent \"Helper: write CHANGELOG.md slowly\" finished",
|
||||
"balloon": "\"Helper: write CHANGELOG.md slowly\" reports: CHANGELOG.md",
|
||||
"tests": null,
|
||||
"roundsOk": null,
|
||||
"printed": null,
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
"ok": null,
|
||||
"story": "A background job reports back.",
|
||||
"director": "Prompt · 7 words · task",
|
||||
"balloon": "Agent \"Write README for rename script\" finished",
|
||||
"balloon": "\"Write README for rename script\" reports: I wrote the README at story-review-c/README.md. It covers the description, requirements, usage with dry-run, real-run and re-run sample output, name clashes, notes and limitations, and exit codes. The script wasn't there when I started, but it appeared while I was writing, so I read it (without running or editing it) and matched the README to it: it agrees with the spec, and I added the exact summary line (Done: N renamed, M skipped.), the -- option for a directory name starting with -, the…",
|
||||
"tests": null,
|
||||
"roundsOk": null,
|
||||
"printed": null,
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
"ok": null,
|
||||
"story": "A background job reports back.",
|
||||
"director": "Prompt · 6 words · task",
|
||||
"balloon": "Agent \"Write README for csvstat\" finished",
|
||||
"balloon": "\"Write README for csvstat\" reports: I wrote README.md and committed it on its own on feat/csvstat. The README has every section you asked for and no badges.",
|
||||
"tests": null,
|
||||
"roundsOk": null,
|
||||
"printed": null,
|
||||
|
||||
@@ -193,6 +193,16 @@ describe('Story Mode on real sessions', () => {
|
||||
expect(panel).toBeTruthy();
|
||||
});
|
||||
|
||||
it('a helper reporting back is heard in its own words, not only "finished" (round 9)', () => {
|
||||
const [, readme, extra] = pagesOf('background-helpers-reports');
|
||||
expect(readme.panels[0].balloon).toBe('"Helper A: write README.md" reports: README.md');
|
||||
// What the helper did not do is on the page, and a long path does not crowd it out.
|
||||
expect(extra.panels[0].balloon).toMatch(
|
||||
/^"Helper B: write extra pytest tests" reports: I wrote the file: tests\/test_extra\.py /
|
||||
);
|
||||
expect(extra.panels[0].balloon).toMatch(/I haven't run it against the real wordcount\.py\.$/);
|
||||
});
|
||||
|
||||
it('a blocked command ends a turn where nothing worked, without a celebration', () => {
|
||||
const last = pagesOf('teammates-then-interrupt')[4];
|
||||
expect(last.panels.map((p) => p.story)).toEqual([
|
||||
|
||||
Reference in new issue
Block a user