mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Simplify completed turn work summary
This commit is contained in:
parent
14bb1bf4ee
commit
a10f18f0ed
6 changed files with 32 additions and 137 deletions
|
|
@ -1,21 +1,12 @@
|
|||
export { messageStreamSemanticClassifications } from "./classify";
|
||||
export {
|
||||
messageStreamIsApprovalResult,
|
||||
messageStreamIsAssistantResponse,
|
||||
messageStreamIsCommandEvidence,
|
||||
messageStreamIsContextCompaction,
|
||||
messageStreamIsCoordinationProgress,
|
||||
messageStreamIsGoalChange,
|
||||
messageStreamIsHookEvidence,
|
||||
messageStreamIsPermissionDecision,
|
||||
messageStreamIsProposedPlan,
|
||||
messageStreamIsReasoningProgress,
|
||||
messageStreamIsReviewResult,
|
||||
messageStreamIsTaskProgress,
|
||||
messageStreamIsToolEvidence,
|
||||
messageStreamIsTurnInitiator,
|
||||
messageStreamIsTurnSteer,
|
||||
messageStreamIsUserInputResult,
|
||||
messageStreamIsWorkspaceResult,
|
||||
messageStreamRenderFamily,
|
||||
} from "./predicates";
|
||||
|
|
|
|||
|
|
@ -27,14 +27,6 @@ export function messageStreamIsTurnSteer(classification: MessageStreamSemanticCl
|
|||
);
|
||||
}
|
||||
|
||||
export function messageStreamIsProposedPlan(classification: MessageStreamSemanticClassification): boolean {
|
||||
return messageStreamHasMeaning(classification, "dialogue", "proposal");
|
||||
}
|
||||
|
||||
export function messageStreamIsAssistantResponse(classification: MessageStreamSemanticClassification): boolean {
|
||||
return messageStreamHasMeaning(classification, "dialogue", "response");
|
||||
}
|
||||
|
||||
export function messageStreamIsWorkspaceResult(classification: MessageStreamSemanticClassification): boolean {
|
||||
return messageStreamHasMeaning(classification, "workspace", "result");
|
||||
}
|
||||
|
|
@ -51,60 +43,29 @@ export function messageStreamIsReasoningProgress(classification: MessageStreamSe
|
|||
return classification.item.kind === "reasoning";
|
||||
}
|
||||
|
||||
export function messageStreamIsContextCompaction(classification: MessageStreamSemanticClassification): boolean {
|
||||
return classification.item.kind === "contextCompaction";
|
||||
}
|
||||
|
||||
export function messageStreamIsCommandEvidence(classification: MessageStreamSemanticClassification): boolean {
|
||||
return classification.item.kind === "command";
|
||||
}
|
||||
|
||||
export function messageStreamIsToolEvidence(classification: MessageStreamSemanticClassification): boolean {
|
||||
return classification.item.kind === "tool";
|
||||
}
|
||||
|
||||
export function messageStreamIsHookEvidence(classification: MessageStreamSemanticClassification): boolean {
|
||||
return classification.item.kind === "hook";
|
||||
}
|
||||
|
||||
export function messageStreamIsApprovalResult(classification: MessageStreamSemanticClassification): boolean {
|
||||
return classification.item.kind === "approvalResult";
|
||||
}
|
||||
|
||||
export function messageStreamIsUserInputResult(classification: MessageStreamSemanticClassification): boolean {
|
||||
return classification.item.kind === "userInputResult";
|
||||
}
|
||||
|
||||
export function messageStreamIsReviewResult(classification: MessageStreamSemanticClassification): boolean {
|
||||
return classification.item.kind === "reviewResult";
|
||||
}
|
||||
|
||||
export function messageStreamIsGoalChange(classification: MessageStreamSemanticClassification): boolean {
|
||||
return classification.item.kind === "goal";
|
||||
}
|
||||
|
||||
export function messageStreamRenderFamily(classification: MessageStreamSemanticClassification): MessageStreamRenderFamily | null {
|
||||
if (classification.item.kind === "message" || classification.item.kind === "system" || messageStreamIsUserInputResult(classification)) {
|
||||
return "text";
|
||||
}
|
||||
if (
|
||||
messageStreamIsCommandEvidence(classification) ||
|
||||
messageStreamIsWorkspaceResult(classification) ||
|
||||
messageStreamIsToolEvidence(classification) ||
|
||||
messageStreamIsHookEvidence(classification) ||
|
||||
messageStreamIsGoalChange(classification) ||
|
||||
messageStreamIsApprovalResult(classification) ||
|
||||
messageStreamIsReviewResult(classification)
|
||||
) {
|
||||
return "toolResult";
|
||||
}
|
||||
if (
|
||||
messageStreamIsTaskProgress(classification) ||
|
||||
messageStreamIsCoordinationProgress(classification) ||
|
||||
messageStreamIsReasoningProgress(classification) ||
|
||||
messageStreamIsContextCompaction(classification)
|
||||
) {
|
||||
return "work";
|
||||
switch (classification.item.kind) {
|
||||
case "message":
|
||||
case "system":
|
||||
case "userInputResult":
|
||||
return "text";
|
||||
case "command":
|
||||
case "fileChange":
|
||||
case "tool":
|
||||
case "hook":
|
||||
case "goal":
|
||||
case "approvalResult":
|
||||
case "reviewResult":
|
||||
return "toolResult";
|
||||
case "taskProgress":
|
||||
case "agent":
|
||||
case "reasoning":
|
||||
case "contextCompaction":
|
||||
return "work";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,11 @@
|
|||
import type { MessageStreamItem } from "../../domain/message-stream/items";
|
||||
import { pathRelativeToRoot } from "../../domain/message-stream/format/path-labels";
|
||||
import {
|
||||
messageStreamIsApprovalResult,
|
||||
messageStreamIsAssistantResponse,
|
||||
messageStreamIsCommandEvidence,
|
||||
messageStreamIsContextCompaction,
|
||||
messageStreamIsCoordinationProgress,
|
||||
messageStreamIsGoalChange,
|
||||
messageStreamIsHookEvidence,
|
||||
messageStreamIsPermissionDecision,
|
||||
messageStreamIsProposedPlan,
|
||||
messageStreamIsReasoningProgress,
|
||||
messageStreamIsReviewResult,
|
||||
messageStreamIsTaskProgress,
|
||||
messageStreamIsToolEvidence,
|
||||
messageStreamIsTurnInitiator,
|
||||
messageStreamIsTurnSteer,
|
||||
messageStreamIsUserInputResult,
|
||||
messageStreamIsWorkspaceResult,
|
||||
messageStreamSemanticClassifications,
|
||||
} from "../../domain/message-stream/semantics";
|
||||
|
|
@ -103,7 +92,7 @@ export function messageStreamLayoutBlocks(
|
|||
type: "activityGroup",
|
||||
id: `turn-${turnId}-activity`,
|
||||
turnId,
|
||||
summary: turnActivitySummary(groupItems),
|
||||
summary: "Work details",
|
||||
items: groupItems,
|
||||
});
|
||||
}
|
||||
|
|
@ -218,55 +207,6 @@ function autoReviewSummariesForTurns(items: readonly MessageStreamSemanticClassi
|
|||
return byTurn;
|
||||
}
|
||||
|
||||
function turnActivitySummary(items: readonly GroupedActivity[]): string {
|
||||
const parts = [
|
||||
countActivityLabel(
|
||||
items,
|
||||
(item) => item.type === "item" && messageStreamIsAssistantResponse(item.classification),
|
||||
"response",
|
||||
"responses",
|
||||
),
|
||||
countActivityLabel(items, (item) => item.type === "item" && messageStreamIsProposedPlan(item.classification), "plan", "plans"),
|
||||
countActivityLabel(items, (item) => item.type === "steering", "steer", "steers"),
|
||||
countActivityLabel(items, (item) => item.type === "item" && messageStreamIsTaskProgress(item.classification), "task progress"),
|
||||
countActivityLabel(items, (item) => item.type === "item" && messageStreamIsCoordinationProgress(item.classification), "agent"),
|
||||
countActivityLabel(items, (item) => item.type === "item" && messageStreamIsCommandEvidence(item.classification), "command"),
|
||||
countActivityLabel(items, (item) => item.type === "item" && messageStreamIsWorkspaceResult(item.classification), "file change"),
|
||||
countActivityLabel(items, (item) => item.type === "item" && messageStreamIsToolEvidence(item.classification), "tool"),
|
||||
countActivityLabel(items, (item) => item.type === "item" && messageStreamIsHookEvidence(item.classification), "hook"),
|
||||
countActivityLabel(
|
||||
items,
|
||||
(item) => item.type === "item" && messageStreamIsReasoningProgress(item.classification),
|
||||
"thought",
|
||||
"thought notes",
|
||||
),
|
||||
countActivityLabel(
|
||||
items,
|
||||
(item) => item.type === "item" && messageStreamIsContextCompaction(item.classification),
|
||||
"context compaction",
|
||||
),
|
||||
countActivityLabel(items, (item) => item.type === "item" && messageStreamIsApprovalResult(item.classification), "approval"),
|
||||
countActivityLabel(items, (item) => item.type === "item" && messageStreamIsUserInputResult(item.classification), "input"),
|
||||
countActivityLabel(items, (item) => item.type === "item" && messageStreamIsReviewResult(item.classification), "review"),
|
||||
countActivityLabel(items, (item) => item.type === "item" && messageStreamIsGoalChange(item.classification), "goal"),
|
||||
].filter((part): part is string => Boolean(part));
|
||||
|
||||
if (parts.length === 0) return "Work details";
|
||||
return `Work details: ${parts.join(", ")}`;
|
||||
}
|
||||
|
||||
function countActivityLabel(
|
||||
items: readonly GroupedActivity[],
|
||||
predicate: (item: GroupedActivity) => boolean,
|
||||
label: string,
|
||||
pluralLabel = `${label}s`,
|
||||
): string | null {
|
||||
const count = items.filter(predicate).length;
|
||||
if (count === 0) return null;
|
||||
if (count === 1) return label;
|
||||
return `${String(count)} ${pluralLabel}`;
|
||||
}
|
||||
|
||||
function textForMessageStreamItem(item: MessageStreamItem): string {
|
||||
return "text" in item && typeof item.text === "string" ? item.text : "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -916,7 +916,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
|
||||
const blocks = messageStreamLayoutBlocks(items, null);
|
||||
expect(blocks.map((block) => block.type)).toEqual(["item", "activityGroup", "item"]);
|
||||
expect(blocks[1]).toMatchObject({ summary: "Work details: command, thought" });
|
||||
expect(blocks[1]).toMatchObject({ summary: "Work details" });
|
||||
});
|
||||
|
||||
it("groups completed hook and review logs before the final assistant message", () => {
|
||||
|
|
@ -950,7 +950,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
|
||||
expect(blocks.map((block) => (block.type === "item" ? block.item.id : block.id))).toEqual(["u1", "turn-t1-activity", "a1"]);
|
||||
expect(blocks[1]).toMatchObject({
|
||||
summary: "Work details: command, hook, thought, 2 reviews",
|
||||
summary: "Work details",
|
||||
items: [
|
||||
{ item: { id: "hook-1" } },
|
||||
{ item: { id: "c1" } },
|
||||
|
|
@ -1021,7 +1021,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
const blocks = messageStreamLayoutBlocks(items, null);
|
||||
expect(blocks.map((block) => block.type)).toEqual(["item", "activityGroup", "item"]);
|
||||
expect(blocks[1]).toMatchObject({
|
||||
summary: "Work details: 2 responses, command, file change",
|
||||
summary: "Work details",
|
||||
items: [{ item: { id: "a1" } }, { item: { id: "c1" } }, { item: { id: "a2" } }, { item: { id: "f1" } }],
|
||||
});
|
||||
expect(blocks[2]).toMatchObject({ item: { id: "a3" } });
|
||||
|
|
@ -1069,7 +1069,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
|
||||
expect(blocks.map((block) => (block.type === "item" ? block.item.id : block.id))).toEqual(["u1", "u2", "turn-t1-activity", "a1"]);
|
||||
expect(blocks[2]).toMatchObject({
|
||||
summary: "Work details: steer, 2 commands",
|
||||
summary: "Work details",
|
||||
items: [
|
||||
{ type: "item", item: { id: "c1" } },
|
||||
{
|
||||
|
|
@ -1084,7 +1084,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
});
|
||||
});
|
||||
|
||||
it("pluralizes multiple steering markers as steers in completed turn work details", () => {
|
||||
it("keeps multiple steering markers in completed turn work details", () => {
|
||||
const items: MessageStreamItem[] = [
|
||||
{ id: "u1", kind: "message", messageKind: "user", role: "user", text: "do it", turnId: "t1" },
|
||||
{ id: "u2", kind: "message", messageKind: "user", role: "user", text: "also check tests", turnId: "t1" },
|
||||
|
|
@ -1104,7 +1104,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
const activityGroup = blocks.find((block) => block.type === "activityGroup");
|
||||
|
||||
expect(activityGroup).toMatchObject({
|
||||
summary: "Work details: 2 steers",
|
||||
summary: "Work details",
|
||||
items: [
|
||||
{ type: "steering", id: "steer-activity-u2" },
|
||||
{ type: "steering", id: "steer-activity-u3" },
|
||||
|
|
@ -1158,7 +1158,7 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
expect(blocks.map((block) => (block.type === "item" ? block.item.id : block.id))).toEqual(["u1", "plan-progress-t1", "a1"]);
|
||||
});
|
||||
|
||||
it("summarizes task progress and agent activity separately from tools", () => {
|
||||
it("groups task progress and agent activity inside completed turn work details", () => {
|
||||
const items: MessageStreamItem[] = [
|
||||
{ id: "u1", kind: "message", messageKind: "user", role: "user", text: "do it", turnId: "t1" },
|
||||
{
|
||||
|
|
@ -1198,7 +1198,10 @@ describe("display block grouping keeps work logs subordinate to conversation mes
|
|||
];
|
||||
|
||||
const blocks = messageStreamLayoutBlocks(items, null);
|
||||
expect(blocks[1]).toMatchObject({ summary: "Work details: task progress, agent" });
|
||||
expect(blocks[1]).toMatchObject({
|
||||
summary: "Work details",
|
||||
items: [{ item: { id: "plan-progress-t1" } }, { item: { id: "agent-1" } }],
|
||||
});
|
||||
});
|
||||
|
||||
it("summarizes active subagent states while a turn is running", () => {
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ describe("message stream rendering and message actions", () => {
|
|||
),
|
||||
).toEqual(["item:u1", "activity:turn-t1-activity", "item:a1"]);
|
||||
const activitySummary = parent.querySelector<HTMLElement>('[data-codex-panel-block-key="activity:turn-t1-activity"] summary');
|
||||
expect(activitySummary?.textContent).toBe("Work details: hook");
|
||||
expect(activitySummary?.textContent).toBe("Work details");
|
||||
expect(activitySummary?.tabIndex).toBe(-1);
|
||||
unmountUiRootInAct(parent);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -330,7 +330,7 @@ describe("work log renderer decisions", () => {
|
|||
const element = renderMessageBlockElement(expectPresent(blocks.find((block) => block.key === "activity:turn-turn-activity")));
|
||||
|
||||
expect(element).toBeDefined();
|
||||
expect(element.querySelector(":scope > summary")?.textContent).toBe("Work details: hook");
|
||||
expect(element.querySelector(":scope > summary")?.textContent).toBe("Work details");
|
||||
expect(element.querySelector(".codex-panel__tool-item--hook")?.classList.contains("codex-panel__execution--completed")).toBe(true);
|
||||
expect(element.querySelector(".codex-panel__tool-summary")?.textContent).toBe("postToolUse: Formatted 1 file.");
|
||||
expect(element.querySelector(".codex-panel__meta-grid")?.textContent).toContain("statuscompleted");
|
||||
|
|
|
|||
Loading…
Reference in a new issue