Files
buzz/desktop/src/features/agents/ui/agentSessionPlanChecklist.ts
ss-dev-01 7cec752d7a feat(desktop): add conversation transcript variant for focus-mode agent sessions
**Category:** feature
**User Impact:** In the wide/standalone agent session panel the observer
transcript now reads like a conversation — the human's prompt as a
right-aligned bubble, the agent's reply as unboxed prose at a comfortable
measure, reasoning behind a "Thinking…/Thought for Ns" disclosure, the plan
as a live checklist, and session/status noise receded to quiet dividers.

**Problem:** The transcript had two presentations — the dense `default`
activity feed and `compactPreview` — both tuned for a narrow panel. Read at
full width they are a scan surface, not something you read a turn in.

**Solution:** A third, purely additive `conversation` variant, threaded
through `ManagedAgentSessionPanel` and `AgentSessionThreadPanel` as an
optional prop with a width-based default. Tool items deliberately route
through the same `ToolActivity` presenter as `default` so the parallel
tool-chain-card work merges cleanly, and the drawer/shell is untouched.

The byte-for-byte guarantee for the existing variants is enforced by a
captured-markup fixture, not by inspection: the baseline was produced by
mounting the same transcript on unmodified code, and the test fails on any
drift in `default` or `compactPreview` output.

Co-authored-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
2026-08-24 11:45:32 -07:00

55 lines
1.9 KiB
TypeScript

/**
* Parse the markdown checklist a `plan` transcript item carries into structured
* entries, so the `conversation` variant can render it as a checklist card
* instead of a block of markdown.
*
* The text is produced by `formatPlanEntry` in agentSessionTranscriptHelpers:
* `- [x] content` for completed entries, `- [ ] content` otherwise, with an
* ` (in progress)` suffix when the ACP entry status is `in_progress`. Adapters
* that send free-form `content` text instead of `entries[]` yield text with no
* checklist lines at all — those return no entries and the card falls back to
* rendering the raw markdown.
*/
export type PlanChecklistEntry = {
label: string;
status: "completed" | "in_progress" | "pending";
};
export type PlanChecklist = {
completedCount: number;
entries: PlanChecklistEntry[];
};
const CHECKLIST_LINE = /^\s*[-*]\s+\[([ xX])\]\s*(.*)$/;
const IN_PROGRESS_SUFFIX = /\s*\(in progress\)\s*$/i;
export function parsePlanChecklist(text: string): PlanChecklist {
const entries: PlanChecklistEntry[] = [];
for (const line of text.split(/\r?\n/)) {
const match = line.match(CHECKLIST_LINE);
if (!match) continue;
const completed = match[1].toLowerCase() === "x";
const rawLabel = match[2].trim();
const inProgress = !completed && IN_PROGRESS_SUFFIX.test(rawLabel);
entries.push({
label: rawLabel.replace(IN_PROGRESS_SUFFIX, "").trim(),
status: completed ? "completed" : inProgress ? "in_progress" : "pending",
});
}
return {
completedCount: entries.filter((entry) => entry.status === "completed")
.length,
entries,
};
}
/** "2/5 complete" progress caption, or null when there is nothing to count. */
export function formatPlanChecklistProgress(
checklist: PlanChecklist,
): string | null {
if (checklist.entries.length === 0) return null;
return `${checklist.completedCount}/${checklist.entries.length} complete`;
}