mirror of
http://10.0.2.1:3031/sauer/claude-code.git
synced 2026-06-30 17:46:58 +10:00
206 lines
24 KiB
TypeScript
206 lines
24 KiB
TypeScript
|
|
import { c as _c } from "react/compiler-runtime";
|
||
|
|
import type { TextBlockParam } from '@anthropic-ai/sdk/resources/index.mjs';
|
||
|
|
import figures from 'figures';
|
||
|
|
import * as React from 'react';
|
||
|
|
import { TEAMMATE_MESSAGE_TAG } from '../../constants/xml.js';
|
||
|
|
import { Ansi, Box, Text, type TextProps } from '../../ink.js';
|
||
|
|
import { toInkColor } from '../../utils/ink.js';
|
||
|
|
import { jsonParse } from '../../utils/slowOperations.js';
|
||
|
|
import { isShutdownApproved } from '../../utils/teammateMailbox.js';
|
||
|
|
import { MessageResponse } from '../MessageResponse.js';
|
||
|
|
import { tryRenderPlanApprovalMessage } from './PlanApprovalMessage.js';
|
||
|
|
import { tryRenderShutdownMessage } from './ShutdownMessage.js';
|
||
|
|
import { tryRenderTaskAssignmentMessage } from './TaskAssignmentMessage.js';
|
||
|
|
type Props = {
|
||
|
|
addMargin: boolean;
|
||
|
|
param: TextBlockParam;
|
||
|
|
isTranscriptMode?: boolean;
|
||
|
|
};
|
||
|
|
type ParsedMessage = {
|
||
|
|
teammateId: string;
|
||
|
|
content: string;
|
||
|
|
color?: string;
|
||
|
|
summary?: string;
|
||
|
|
};
|
||
|
|
const TEAMMATE_MSG_REGEX = new RegExp(`<${TEAMMATE_MESSAGE_TAG}\\s+teammate_id="([^"]+)"(?:\\s+color="([^"]+)")?(?:\\s+summary="([^"]+)")?>\\n?([\\s\\S]*?)\\n?<\\/${TEAMMATE_MESSAGE_TAG}>`, 'g');
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Parse all teammate messages from XML format:
|
||
|
|
* <teammate-message teammate_id="alice" color="red" summary="Brief update">message content</teammate-message>
|
||
|
|
* Supports multiple messages in a single text block.
|
||
|
|
*/
|
||
|
|
function parseTeammateMessages(text: string): ParsedMessage[] {
|
||
|
|
const messages: ParsedMessage[] = [];
|
||
|
|
// Use matchAll to find all matches (this is a RegExp method, not child_process)
|
||
|
|
for (const match of text.matchAll(TEAMMATE_MSG_REGEX)) {
|
||
|
|
if (match[1] && match[4]) {
|
||
|
|
messages.push({
|
||
|
|
teammateId: match[1],
|
||
|
|
color: match[2],
|
||
|
|
// may be undefined
|
||
|
|
summary: match[3],
|
||
|
|
// may be undefined
|
||
|
|
content: match[4].trim()
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return messages;
|
||
|
|
}
|
||
|
|
function getDisplayName(teammateId: string): string {
|
||
|
|
if (teammateId === 'leader') {
|
||
|
|
return 'leader';
|
||
|
|
}
|
||
|
|
return teammateId;
|
||
|
|
}
|
||
|
|
export function UserTeammateMessage({
|
||
|
|
addMargin,
|
||
|
|
param: {
|
||
|
|
text
|
||
|
|
},
|
||
|
|
isTranscriptMode
|
||
|
|
}: Props): React.ReactNode {
|
||
|
|
const messages = parseTeammateMessages(text).filter(msg => {
|
||
|
|
// Pre-filter shutdown lifecycle messages to avoid empty wrapper
|
||
|
|
// Box elements creating blank lines between model turns
|
||
|
|
if (isShutdownApproved(msg.content)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
const parsed = jsonParse(msg.content);
|
||
|
|
if (parsed?.type === 'teammate_terminated') return false;
|
||
|
|
} catch {
|
||
|
|
// Not JSON, keep the message
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
});
|
||
|
|
if (messages.length === 0) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
return <Box flexDirection="column" marginTop={addMargin ? 1 : 0} width="100%">
|
||
|
|
{messages.map((msg_0, index) => {
|
||
|
|
const inkColor = toInkColor(msg_0.color);
|
||
|
|
const displayName = getDisplayName(msg_0.teammateId);
|
||
|
|
|
||
|
|
// Try to render as plan approval message (request or response)
|
||
|
|
const planApprovalElement = tryRenderPlanApprovalMessage(msg_0.content, displayName);
|
||
|
|
if (planApprovalElement) {
|
||
|
|
return <React.Fragment key={index}>{planApprovalElement}</React.Fragment>;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Try to render as shutdown message (request or rejected)
|
||
|
|
const shutdownElement = tryRenderShutdownMessage(msg_0.content);
|
||
|
|
if (shutdownElement) {
|
||
|
|
return <React.Fragment key={index}>{shutdownElement}</React.Fragment>;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Try to render as task assignment message
|
||
|
|
const taskAssignmentElement = tryRenderTaskAssignmentMessage(msg_0.content);
|
||
|
|
if (taskAssignmentElement) {
|
||
|
|
return <React.Fragment key={index}>{taskAssignmentElement}</React.Fragment>;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Try to parse as structured JSON message
|
||
|
|
let parsedIdleNotification: {
|
||
|
|
type?: string;
|
||
|
|
} | null = null;
|
||
|
|
try {
|
||
|
|
parsedIdleNotification = jsonParse(msg_0.content);
|
||
|
|
} catch {
|
||
|
|
// Not JSON
|
||
|
|
}
|
||
|
|
|
||
|
|
// Hide idle notifications - they are processed silently
|
||
|
|
if (parsedIdleNotification?.type === 'idle_notification') {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Task completed notification - show which task was completed
|
||
|
|
if (parsedIdleNotification?.type === 'task_completed') {
|
||
|
|
const taskCompleted = parsedIdleNotification as {
|
||
|
|
type: string;
|
||
|
|
from: string;
|
||
|
|
taskId: string;
|
||
|
|
taskSubject?: string;
|
||
|
|
};
|
||
|
|
return <Box key={index} flexDirection="column" marginTop={1}>
|
||
|
|
<Text color={inkColor}>{`@${displayName}${figures.pointer}`}</Text>
|
||
|
|
<MessageResponse>
|
||
|
|
<Text color="success">✓</Text>
|
||
|
|
<Text>
|
||
|
|
{' '}
|
||
|
|
Completed task #{taskCompleted.taskId}
|
||
|
|
{taskCompleted.taskSubject && <Text dimColor> ({taskCompleted.taskSubject})</Text>}
|
||
|
|
</Text>
|
||
|
|
</MessageResponse>
|
||
|
|
</Box>;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Default: plain text message (truncated)
|
||
|
|
return <TeammateMessageContent key={index} displayName={displayName} inkColor={inkColor} content={msg_0.content} summary={msg_0.summary} isTranscriptMode={isTranscriptMode} />;
|
||
|
|
})}
|
||
|
|
</Box>;
|
||
|
|
}
|
||
|
|
type TeammateMessageContentProps = {
|
||
|
|
displayName: string;
|
||
|
|
inkColor: TextProps['color'];
|
||
|
|
content: string;
|
||
|
|
summary?: string;
|
||
|
|
isTranscriptMode?: boolean;
|
||
|
|
};
|
||
|
|
export function TeammateMessageContent(t0) {
|
||
|
|
const $ = _c(14);
|
||
|
|
const {
|
||
|
|
displayName,
|
||
|
|
inkColor,
|
||
|
|
content,
|
||
|
|
summary,
|
||
|
|
isTranscriptMode
|
||
|
|
} = t0;
|
||
|
|
const t1 = `@${displayName}${figures.pointer}`;
|
||
|
|
let t2;
|
||
|
|
if ($[0] !== inkColor || $[1] !== t1) {
|
||
|
|
t2 = <Text color={inkColor}>{t1}</Text>;
|
||
|
|
$[0] = inkColor;
|
||
|
|
$[1] = t1;
|
||
|
|
$[2] = t2;
|
||
|
|
} else {
|
||
|
|
t2 = $[2];
|
||
|
|
}
|
||
|
|
let t3;
|
||
|
|
if ($[3] !== summary) {
|
||
|
|
t3 = summary && <Text> {summary}</Text>;
|
||
|
|
$[3] = summary;
|
||
|
|
$[4] = t3;
|
||
|
|
} else {
|
||
|
|
t3 = $[4];
|
||
|
|
}
|
||
|
|
let t4;
|
||
|
|
if ($[5] !== t2 || $[6] !== t3) {
|
||
|
|
t4 = <Box>{t2}{t3}</Box>;
|
||
|
|
$[5] = t2;
|
||
|
|
$[6] = t3;
|
||
|
|
$[7] = t4;
|
||
|
|
} else {
|
||
|
|
t4 = $[7];
|
||
|
|
}
|
||
|
|
let t5;
|
||
|
|
if ($[8] !== content || $[9] !== isTranscriptMode) {
|
||
|
|
t5 = isTranscriptMode && <Box paddingLeft={2}><Text><Ansi>{content}</Ansi></Text></Box>;
|
||
|
|
$[8] = content;
|
||
|
|
$[9] = isTranscriptMode;
|
||
|
|
$[10] = t5;
|
||
|
|
} else {
|
||
|
|
t5 = $[10];
|
||
|
|
}
|
||
|
|
let t6;
|
||
|
|
if ($[11] !== t4 || $[12] !== t5) {
|
||
|
|
t6 = <Box flexDirection="column" marginTop={1}>{t4}{t5}</Box>;
|
||
|
|
$[11] = t4;
|
||
|
|
$[12] = t5;
|
||
|
|
$[13] = t6;
|
||
|
|
} else {
|
||
|
|
t6 = $[13];
|
||
|
|
}
|
||
|
|
return t6;
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJUZXh0QmxvY2tQYXJhbSIsImZpZ3VyZXMiLCJSZWFjdCIsIlRFQU1NQVRFX01FU1NBR0VfVEFHIiwiQW5zaSIsIkJveCIsIlRleHQiLCJUZXh0UHJvcHMiLCJ0b0lua0NvbG9yIiwianNvblBhcnNlIiwiaXNTaHV0ZG93bkFwcHJvdmVkIiwiTWVzc2FnZVJlc3BvbnNlIiwidHJ5UmVuZGVyUGxhbkFwcHJvdmFsTWVzc2FnZSIsInRyeVJlbmRlclNodXRkb3duTWVzc2FnZSIsInRyeVJlbmRlclRhc2tBc3NpZ25tZW50TWVzc2FnZSIsIlByb3BzIiwiYWRkTWFyZ2luIiwicGFyYW0iLCJpc1RyYW5zY3JpcHRNb2RlIiwiUGFyc2VkTWVzc2FnZSIsInRlYW1tYXRlSWQiLCJjb250ZW50IiwiY29sb3IiLCJzdW1tYXJ5IiwiVEVBTU1BVEVfTVNHX1JFR0VYIiwiUmVnRXhwIiwicGFyc2VUZWFtbWF0ZU1lc3NhZ2VzIiwidGV4dCIsIm1lc3NhZ2VzIiwibWF0Y2giLCJtYXRjaEFsbCIsInB1c2giLCJ0cmltIiwiZ2V0RGlzcGxheU5hbWUiLCJVc2VyVGVhbW1hdGVNZXNzYWdlIiwiUmVhY3ROb2RlIiwiZmlsdGVyIiwibXNnIiwicGFyc2VkIiwidHlwZSIsImxlbmd0aCIsIm1hcCIsImluZGV4IiwiaW5rQ29sb3IiLCJkaXNwbGF5TmFtZSIsInBsYW5BcHByb3ZhbEVsZW1lbnQiLCJzaHV0ZG93bkVsZW1lbnQiLCJ0YXNrQXNzaWdubWVudEVsZW1lbnQiLCJwYXJzZWRJZGxlTm90aWZpY2F0aW9uIiwidGFza0NvbXBsZXRlZCIsImZyb20iLCJ0YXNrSWQiLCJ0YXNrU3ViamVjdCIsInBvaW50ZXIiLCJUZWFtbWF0ZU1lc3NhZ2VDb250ZW50UHJvcHMiLCJUZWFtbWF0ZU1lc3NhZ2VDb250ZW50IiwidDAiLCIkIiwiX2MiLCJ0MSIsInQyIiwidDMiLCJ0NCIsInQ1IiwidDYiXSwic291cmNlcyI6WyJVc2VyVGVhbW1hdGVNZXNzYWdlLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgdHlwZSB7IFRleHRCbG9ja1BhcmFtIH0gZnJvbSAnQGFudGhyb3BpYy1haS9zZGsvcmVzb3VyY2VzL2luZGV4Lm1qcydcbmltcG9ydCBmaWd1cmVzIGZyb20gJ2ZpZ3VyZXMnXG5pbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IFRFQU1NQVRFX01FU1NBR0VfVEFHIH0gZnJvbSAnLi4vLi4vY29uc3RhbnRzL3htbC5qcydcbmltcG9ydCB7IEFuc2ksIEJveCwgVGV4dCwgdHlwZSBUZXh0UHJvcHMgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQgeyB0b0lua0NvbG9yIH0gZnJvbSAnLi4vLi4vdXRpbHMvaW5rLmpzJ1xuaW1wb3J0IHsganNvblBhcnNlIH0gZnJvbSAnLi4vLi4vdXRpbHMvc2xvd09wZXJhdGlvbnMuanMnXG5pbXBvcnQgeyBpc1NodXRkb3duQXBwcm92ZWQgfSBmcm9tICcuLi8uLi91dGlscy90ZWFtbWF0ZU1haWxib3guanMnXG5pbXBvcnQgeyBNZXNzYWdlUmVzcG9uc2UgfSBmcm9tICcuLi9NZXNzYWdlUmVzcG9uc2UuanMnXG5pbXBvcnQgeyB0cnlSZW5kZXJQbGFuQXBwcm92YWxNZXNzYWdlIH0gZnJvbSAnLi9QbGFuQXBwcm92YWxNZXNzYWdlLmpzJ1xuaW1wb3J0IHsgdHJ5UmVuZGVyU2h1dGRvd25NZXNzYWdlIH0gZnJvbSAnLi9TaHV0ZG93bk1lc3NhZ2UuanMnXG5pbXBvcnQgeyB0cnlSZW5kZXJUYXNrQXNzaWdubWVudE1lc3NhZ2UgfSBmcm9tICcuL1Rhc2tBc3NpZ25tZW50TWVzc2FnZS5qcydcblxudHlwZSBQcm9wcyA9IHtcbiAgYWRkTWFyZ2luOiBib29sZWFuXG4gIHBhcmFtOiBUZXh0QmxvY2tQYXJhbVxuICBpc1RyYW5zY3JpcHRNb2RlPzogYm9vbGVhblxufVxuXG50eXBlIFBhcnNlZE1lc3NhZ2UgPSB7XG4gIHRlYW1tYXRlSWQ6IHN0cmluZ1xuICBjb250ZW50OiBzdHJpbmdcbiAgY29sb3I/OiBzdHJpbmdcbiAgc3VtbWFyeT86IHN0cmluZ1xufVxuXG5jb25zdCBURUFNTUFURV9NU0dfUkVHRVggPSBuZXcgUmVnRXhwKFxuICBgPCR7VEVBTU1BVEVfTUVTU0FHRV9UQUd9XFxcXHMrdGVhbW1hdGVfaWQ9XCIoW15cIl0rKVwiKD86XFxcXHMrY29sb3I9XCIoW15cIl0rKVwiKT8oPzpcXFxccytzdW1tYXJ5PVwiKFteXCJdKylcIik/PlxcXFxuPyhbXFxcXHNcXFxcU10qPylcXFxcbj88XFxcXC8ke1RFQU1NQVRFX01FU1NBR0VfVEFHfT5gLFxuICAnZycsXG4pXG5cbi8qKlxuICogUGFyc2UgYWxsIHRlYW1tYXRlIG1lc3NhZ2VzIGZyb20gWE1MIGZvcm1hdDpcbiAqIDx0ZWFtbWF0ZS1tZXNzYWdlIHRlYW1tYXRlX2lkPVwiYWxpY2VcIiBjb2xvcj1cInJlZFwiIHN1bW1hcnk9XCJCcmllZiB1cGRhdGVcIj5tZXNzYWdlIGNvbnRlbnQ8L3RlYW1tYXRlLW1lc3NhZ2U+XG4gKiBTdXBwb3J0cyBtdWx0aXBsZSBtZXNzYWdlcyBpbiBhIHNpbmdsZSB0ZXh0IGJsb2NrLlxuICovXG5mdW5jdGlvbiBwYXJzZVRlYW1tYXRlTWVzc2FnZXModGV4dDogc3RyaW5nKTogUGFyc2VkTWVzc2FnZVtdIHtcbiAgY29uc3QgbWVzc2FnZXM6IFBhcnNlZE1lc3NhZ2VbXSA9IFtdXG4gIC8vIFVzZSBtYXRjaEFsbCB0byBmaW5kIGFsbCBtYXRjaGVzICh0aGlzIGlzIGEgUmVnRXhwIG1ldGhvZCwgbm90IGNoaWxkX3Byb2Nlc3MpXG4gIGZvciAoY29uc3QgbWF0Y2ggb2YgdGV4dC5tYXRjaEFsbChURUFNTUFURV9NU0dfUkVHRVgpKSB7XG4gICAgaWYgKG1hdGNoWzFdICYmIG1hdGNoWzRdKSB7XG4gICAgICBtZXNzYWdlcy5wdXNoKHtcbiAgICAgICAgdGVhbW1hdGVJZDogbWF0Y2hbMV0sXG4gICAgICAgIGNvbG9yOiBtYXRjaFsyXSwgLy8gbWF5IGJlIHVuZGVmaW5lZFxuICAgICAgICBzdW1tYXJ5OiBtYXRjaFszXSwgLy8gbWF5IGJlIHVuZGVmaW5lZFxuICAgICAgICBjb250ZW50OiBtYXRjaFs0XS50cmltKCksXG4gICAgICB9KVxuICAgIH1cbiAgfVxuXG4gIHJldHVybiBtZXNzYWdlc1xufVxuXG5mdW5jdGlvbiBnZXREaXNwbGF5TmFtZSh0ZWFtbWF0ZUlkOiBzdHJpbmcpOiBzdHJpbmcge1xuICBpZiAodGVhbW1hdGVJZCA9PT0gJ2xlYWRlcicpIHtcbiAgICByZXR1cm4gJ2xlYWRlcidcbiAgfVxuICByZXR1cm4gdGVhbW1hdGVJZFxufVxuXG5leHBvcnQgZnVuY3Rpb24gVXNlclRlYW1tYXRlTWVzc2FnZSh7XG4gIGFkZE1hcmdpbixcbiAgcGFyYW06IHsgdGV4dCB9LFx
|