Merge pull request #416 from znygugeyx-ctrl/feat/subagent-fork-render

feat: 参考 claude code 官方实现,改进 sub agent 以及 fork agent 的渲染方式
This commit is contained in:
znygugeyx-ctrl
2026-05-06 09:57:52 +08:00
committed by GitHub
parent c4e9efb7a8
commit 5c107e5f8c
11 changed files with 488 additions and 27 deletions

View File

@@ -30,6 +30,7 @@ type Props = {
inProgressToolCallCount?: number;
lookups: ReturnType<typeof buildMessageLookups>;
isTranscriptMode?: boolean;
defaultCollapsed?: boolean;
};
export function AssistantToolUseMessage({
@@ -45,6 +46,7 @@ export function AssistantToolUseMessage({
inProgressToolCallCount,
lookups,
isTranscriptMode,
defaultCollapsed,
}: Props): React.ReactNode {
const terminalSize = useTerminalSize();
const [theme] = useTheme();
@@ -167,6 +169,7 @@ export function AssistantToolUseMessage({
</Box>
{!isResolved &&
!isQueued &&
!defaultCollapsed &&
(isClassifierChecking ? (
<MessageResponse height={1}>
<Text dimColor>

View File

@@ -3,28 +3,31 @@
*/
import type { TextBlockParam } from '@anthropic-ai/sdk/resources/index.mjs';
import * as React from 'react';
import { Box, Text } from '@anthropic/ink';
import { FORK_BOILERPLATE_TAG, FORK_DIRECTIVE_PREFIX } from '../../constants/xml.js';
import { extractTag } from '../../utils/messages.js';
import { UserPromptMessage } from './UserPromptMessage.js';
type Props = {
addMargin: boolean;
param: TextBlockParam;
isTranscriptMode?: boolean;
timestamp?: string;
};
export function UserForkBoilerplateMessage({ param, addMargin }: Props): React.ReactNode {
const text = param.text;
const extracted = extractTag(text, 'fork-boilerplate');
if (!extracted) {
return null;
}
const firstLine = extracted.trim().split('\n')[0] ?? '';
const preview = firstLine.length > 80 ? firstLine.slice(0, 77) + '...' : firstLine;
export function UserForkBoilerplateMessage({ param, addMargin, isTranscriptMode, timestamp }: Props): React.ReactNode {
if (!extractTag(param.text, FORK_BOILERPLATE_TAG)) return null;
const closeTag = `</${FORK_BOILERPLATE_TAG}>`;
const afterTag = param.text.slice(param.text.indexOf(closeTag) + closeTag.length).trimStart();
const userPrompt = afterTag.startsWith(FORK_DIRECTIVE_PREFIX)
? afterTag.slice(FORK_DIRECTIVE_PREFIX.length)
: afterTag;
return (
<Box flexDirection="row" marginTop={addMargin ? 1 : 0}>
<Text dimColor>[fork] </Text>
<Text>{preview}</Text>
</Box>
<UserPromptMessage
addMargin={addMargin}
param={{ type: 'text', text: userPrompt }}
isTranscriptMode={isTranscriptMode}
timestamp={timestamp}
/>
);
}

View File

@@ -4,6 +4,7 @@ import * as React from 'react';
import { NO_CONTENT_MESSAGE } from '../../constants/messages.js';
import {
COMMAND_MESSAGE_TAG,
FORK_BOILERPLATE_TAG,
LOCAL_COMMAND_CAVEAT_TAG,
TASK_NOTIFICATION_TAG,
TEAMMATE_MESSAGE_TAG,
@@ -124,16 +125,21 @@ export function UserTextMessage({
}
// Fork child's first message: collapse the rules/format boilerplate, show
// only the directive. FORK_BOILERPLATE_TAG is inlined so the import doesn't
// ship in external builds where feature('FORK_SUBAGENT') is false.
if (feature('FORK_SUBAGENT')) {
if (param.text.includes('<fork-boilerplate>')) {
/* eslint-disable @typescript-eslint/no-require-imports */
const { UserForkBoilerplateMessage } =
require('./UserForkBoilerplateMessage.js') as typeof import('./UserForkBoilerplateMessage.js');
/* eslint-enable @typescript-eslint/no-require-imports */
return <UserForkBoilerplateMessage addMargin={addMargin} param={param} />;
}
// only the user prompt. Independent of FORK_SUBAGENT flag — the fork agent
// transcript always needs to render the prompt as a normal user bubble.
if (param.text.includes(`<${FORK_BOILERPLATE_TAG}>`)) {
/* eslint-disable @typescript-eslint/no-require-imports */
const { UserForkBoilerplateMessage } =
require('./UserForkBoilerplateMessage.js') as typeof import('./UserForkBoilerplateMessage.js');
/* eslint-enable @typescript-eslint/no-require-imports */
return (
<UserForkBoilerplateMessage
addMargin={addMargin}
param={param}
isTranscriptMode={isTranscriptMode}
timestamp={timestamp}
/>
);
}
// Cross-session UDS message (from another Claude session's SendMessage).