Merge remote-tracking branch 'origin/main' into foundry-terminal-pane

# Conflicts:
#	factory/packages/backend/src/driver.ts
#	factory/packages/backend/src/integrations/sandbox-agent/client.ts
#	factory/packages/backend/test/helpers/test-driver.ts
#	factory/packages/frontend/src/components/mock-layout.tsx
#	pnpm-lock.yaml
#	sdks/react/src/ProcessTerminal.tsx
This commit is contained in:
Nathan Flurry 2026-03-10 23:59:58 -07:00
commit b00c0109d0
288 changed files with 7048 additions and 9134 deletions

View file

@ -33,10 +33,7 @@ const DEFAULT_CLASS_NAMES: AgentConversationClassNames = {
const cx = (...values: Array<string | false | null | undefined>) => values.filter(Boolean).join(" ");
const mergeClassNames = (
defaults: AgentConversationClassNames,
overrides?: Partial<AgentConversationClassNames>,
): AgentConversationClassNames => ({
const mergeClassNames = (defaults: AgentConversationClassNames, overrides?: Partial<AgentConversationClassNames>): AgentConversationClassNames => ({
root: cx(defaults.root, overrides?.root),
transcript: cx(defaults.transcript, overrides?.transcript),
emptyState: cx(defaults.emptyState, overrides?.emptyState),
@ -56,8 +53,7 @@ export const AgentConversation = ({
composerProps,
}: AgentConversationProps) => {
const resolvedClassNames = mergeClassNames(DEFAULT_CLASS_NAMES, classNameOverrides);
const hasTranscriptContent =
entries.length > 0 || Boolean(transcriptProps?.sessionError) || Boolean(transcriptProps?.eventError);
const hasTranscriptContent = entries.length > 0 || Boolean(transcriptProps?.sessionError) || Boolean(transcriptProps?.eventError);
return (
<div className={cx(resolvedClassNames.root, className)} data-slot="root">
@ -74,11 +70,7 @@ export const AgentConversation = ({
</div>
) : null}
{composerProps ? (
<ChatComposer
className={cx(resolvedClassNames.composer, composerClassName)}
classNames={composerClassNames}
{...composerProps}
/>
<ChatComposer className={cx(resolvedClassNames.composer, composerClassName)} classNames={composerClassNames} {...composerProps} />
) : null}
</div>
);

View file

@ -177,10 +177,7 @@ const DEFAULT_DIVIDER_TITLES = new Set(["Session Started", "Turn Started", "Turn
const cx = (...values: Array<string | false | null | undefined>) => values.filter(Boolean).join(" ");
const mergeClassNames = (
defaults: AgentTranscriptClassNames,
overrides?: Partial<AgentTranscriptClassNames>,
): AgentTranscriptClassNames => ({
const mergeClassNames = (defaults: AgentTranscriptClassNames, overrides?: Partial<AgentTranscriptClassNames>): AgentTranscriptClassNames => ({
root: cx(defaults.root, overrides?.root),
divider: cx(defaults.divider, overrides?.divider),
dividerLine: cx(defaults.dividerLine, overrides?.dividerLine),
@ -240,10 +237,7 @@ const getMessageVariant = (entry: TranscriptEntry) => {
const getToolItemLabel = (entry: TranscriptEntry) => {
if (entry.kind === "tool") {
const statusLabel =
entry.toolStatus && entry.toolStatus !== "completed"
? ` (${entry.toolStatus.replaceAll("_", " ")})`
: "";
const statusLabel = entry.toolStatus && entry.toolStatus !== "completed" ? ` (${entry.toolStatus.replaceAll("_", " ")})` : "";
return `${entry.toolName ?? "tool"}${statusLabel}`;
}
@ -287,18 +281,12 @@ const defaultRenderPendingIndicator = () => "...";
const defaultRenderChevron = (expanded: boolean) => (expanded ? "▾" : "▸");
const defaultRenderEventLinkContent = () => "Open";
const defaultRenderPermissionIcon = () => "Permission";
const defaultRenderPermissionOptionContent = ({
label,
}: PermissionOptionRenderContext) => label;
const defaultIsDividerEntry = (entry: TranscriptEntry) =>
entry.kind === "meta" && DEFAULT_DIVIDER_TITLES.has(entry.meta?.title ?? "");
const defaultRenderPermissionOptionContent = ({ label }: PermissionOptionRenderContext) => label;
const defaultIsDividerEntry = (entry: TranscriptEntry) => entry.kind === "meta" && DEFAULT_DIVIDER_TITLES.has(entry.meta?.title ?? "");
const defaultCanOpenEvent = (entry: TranscriptEntry) => Boolean(entry.eventId);
const buildGroupedEntries = (
entries: TranscriptEntry[],
isDividerEntry: (entry: TranscriptEntry) => boolean,
): GroupedEntries[] => {
const buildGroupedEntries = (entries: TranscriptEntry[], isDividerEntry: (entry: TranscriptEntry) => boolean): GroupedEntries[] => {
const groupedEntries: GroupedEntries[] = [];
let currentToolGroup: TranscriptEntry[] = [];
@ -524,11 +512,7 @@ const ToolGroup = ({
}
return (
<div
className={cx(classNames.toolGroupContainer, hasFailed && "failed")}
data-slot="tool-group"
data-failed={hasFailed ? "true" : undefined}
>
<div className={cx(classNames.toolGroupContainer, hasFailed && "failed")} data-slot="tool-group" data-failed={hasFailed ? "true" : undefined}>
<button
type="button"
className={cx(classNames.toolGroupHeader, expanded && "expanded")}
@ -591,11 +575,7 @@ const PermissionPrompt = ({
const canReply = Boolean(onPermissionReply) && !resolved;
return (
<div
className={cx(classNames.permissionPrompt, resolved && "resolved")}
data-slot="permission-prompt"
data-resolved={resolved ? "true" : undefined}
>
<div className={cx(classNames.permissionPrompt, resolved && "resolved")} data-slot="permission-prompt" data-resolved={resolved ? "true" : undefined}>
<div className={classNames.permissionHeader} data-slot="permission-header">
<span className={classNames.permissionIcon} data-slot="permission-icon">
{renderPermissionIcon(entry)}
@ -675,14 +655,8 @@ export const AgentTranscript = ({
renderPermissionIcon = defaultRenderPermissionIcon,
renderPermissionOptionContent = defaultRenderPermissionOptionContent,
}: AgentTranscriptProps) => {
const resolvedClassNames = useMemo(
() => mergeClassNames(DEFAULT_CLASS_NAMES, classNameOverrides),
[classNameOverrides],
);
const groupedEntries = useMemo(
() => buildGroupedEntries(entries, isDividerEntry),
[entries, isDividerEntry],
);
const resolvedClassNames = useMemo(() => mergeClassNames(DEFAULT_CLASS_NAMES, classNameOverrides), [classNameOverrides]);
const groupedEntries = useMemo(() => buildGroupedEntries(entries, isDividerEntry), [entries, isDividerEntry]);
return (
<div className={cx(resolvedClassNames.root, className)} data-slot="root">
@ -771,13 +745,13 @@ export const AgentTranscript = ({
</div>
) : null}
{isThinking
? renderThinkingState?.({ agentId }) ?? (
? (renderThinkingState?.({ agentId }) ?? (
<div className={resolvedClassNames.thinkingRow} data-slot="thinking-row">
<span className={resolvedClassNames.thinkingIndicator} data-slot="thinking-indicator">
Thinking...
</span>
</div>
)
))
: null}
<div ref={endRef} className={resolvedClassNames.endAnchor} data-slot="end-anchor" />
</div>

View file

@ -24,11 +24,9 @@ export interface ChatComposerProps {
classNames?: Partial<ChatComposerClassNames>;
inputRef?: Ref<HTMLTextAreaElement>;
rows?: number;
textareaProps?: Omit<
TextareaHTMLAttributes<HTMLTextAreaElement>,
"className" | "disabled" | "onChange" | "onKeyDown" | "placeholder" | "rows" | "value"
>;
textareaProps?: Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "className" | "disabled" | "onChange" | "onKeyDown" | "placeholder" | "rows" | "value">;
renderSubmitContent?: () => ReactNode;
renderFooter?: () => ReactNode;
}
const DEFAULT_CLASS_NAMES: ChatComposerClassNames = {
@ -41,10 +39,7 @@ const DEFAULT_CLASS_NAMES: ChatComposerClassNames = {
const cx = (...values: Array<string | false | null | undefined>) => values.filter(Boolean).join(" ");
const mergeClassNames = (
defaults: ChatComposerClassNames,
overrides?: Partial<ChatComposerClassNames>,
): ChatComposerClassNames => ({
const mergeClassNames = (defaults: ChatComposerClassNames, overrides?: Partial<ChatComposerClassNames>): ChatComposerClassNames => ({
root: cx(defaults.root, overrides?.root),
form: cx(defaults.form, overrides?.form),
input: cx(defaults.input, overrides?.input),
@ -68,6 +63,7 @@ export const ChatComposer = ({
rows = 1,
textareaProps,
renderSubmitContent,
renderFooter,
}: ChatComposerProps) => {
const resolvedClassNames = mergeClassNames(DEFAULT_CLASS_NAMES, classNameOverrides);
const isSubmitDisabled = disabled || submitDisabled || (!allowEmptySubmit && message.trim().length === 0);
@ -98,6 +94,7 @@ export const ChatComposer = ({
rows={rows}
disabled={disabled}
/>
{renderFooter?.()}
<button
type="submit"
className={resolvedClassNames.submit}

View file

@ -207,9 +207,7 @@ export const ProcessTerminal = ({
setConnectionState("closed");
setExitCode(frame.exitCode ?? null);
setStatusMessage(
frame.exitCode == null ? "Process exited." : `Process exited with code ${frame.exitCode}.`
);
setStatusMessage(frame.exitCode == null ? "Process exited." : `Process exited with code ${frame.exitCode}.`);
onExit?.(frame);
});