Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
293 lines
12 KiB
TypeScript
293 lines
12 KiB
TypeScript
import { memo } from "react";
|
|
import { ChevronsDown } from "lucide-react";
|
|
import { cn } from "@tutti-os/ui-system";
|
|
import styles from "../AgentGUINode.styles";
|
|
import { AgentInteractivePromptSurface } from "../AgentInteractivePromptSurface";
|
|
import { AgentComposerRegion } from "../AgentComposerRegion";
|
|
import { AgentSessionChrome } from "../AgentSessionChrome";
|
|
import { AgentComposer, type AgentComposerProps } from "../AgentComposer";
|
|
import {
|
|
AgentGoalBanner,
|
|
isGoalBannerVisible,
|
|
type AgentGoalBannerLabels
|
|
} from "../AgentGoalBanner";
|
|
import {
|
|
TuttiWorkflowDock,
|
|
type TuttiWorkflowDockLabels
|
|
} from "../TuttiWorkflowDock";
|
|
import { resolveAgentGUIInteractionDisabledReason } from "./agentGUIDetailModelHelpers";
|
|
import type {
|
|
TuttiModePlanPanelLabels,
|
|
TuttiPlanIssuePanelLabels
|
|
} from "../../../workspaceWorkflow";
|
|
import type {
|
|
AgentGUINodeViewModel,
|
|
AgentGUISessionChrome
|
|
} from "../model/agentGuiNodeTypes";
|
|
import type { AgentGUINodeViewProps } from "../AgentGUINodeView";
|
|
import type {
|
|
ChromeLabels,
|
|
InteractivePromptLabels
|
|
} from "./AgentGUINodeView.types";
|
|
import { numberValue, objectRecord, stringValue } from "./agentGUIViewUtils";
|
|
import type { AgentGUITuttiWorkflowDockController } from "./useAgentGUITuttiWorkflow";
|
|
|
|
interface AgentGUIBottomDockPaneProps {
|
|
bottomDockRef: React.RefObject<HTMLDivElement | null>;
|
|
showScrollToBottom: boolean;
|
|
scrollToBottomLabel: string;
|
|
onScrollToBottom: () => void;
|
|
// Approval / ask-user prompts lifted above the inline notice (composer stays
|
|
// visible below). Mutually exclusive with bottomDockReplacementPrompt.
|
|
bottomDockLiftedPrompt:
|
|
| AgentGUINodeViewModel["interaction"]["pendingApproval"]
|
|
| AgentGUINodeViewModel["interaction"]["pendingInteractivePrompt"];
|
|
// When set, this interactive prompt takes the composer's slot in the bottom
|
|
// dock (the composer is hidden). Closing the prompt returns the composer.
|
|
bottomDockReplacementPrompt:
|
|
| AgentGUINodeViewModel["interaction"]["pendingApproval"]
|
|
| AgentGUINodeViewModel["interaction"]["pendingInteractivePrompt"];
|
|
composerProps: AgentComposerProps;
|
|
approvalDisabledReason: string | null;
|
|
interactivePromptDisabledReason: string | null;
|
|
inlineNoticeChrome: AgentGUISessionChrome | null;
|
|
isRespondingApproval: boolean;
|
|
sessionChrome: AgentGUISessionChrome;
|
|
keyboardShortcutsEnabled: boolean;
|
|
chromeLabels: ChromeLabels;
|
|
goalBannerLabels: AgentGoalBannerLabels;
|
|
promptLabels: InteractivePromptLabels;
|
|
onSubmitApprovalOption: AgentGUINodeViewProps["actions"]["submitApprovalOption"];
|
|
onAuthLogin?: (provider?: string | null) => void;
|
|
onRetryActivation: AgentGUINodeViewProps["actions"]["retryActivation"];
|
|
onRetryInlineNotice: () => void;
|
|
onContinueInNewConversation: AgentGUINodeViewProps["actions"]["continueInNewConversation"];
|
|
onSubmitBottomDockInteractivePrompt: AgentGUINodeViewProps["actions"]["submitInteractivePrompt"];
|
|
onGoalControl: AgentGUINodeViewProps["actions"]["goalControl"];
|
|
goalPauseSupported: boolean;
|
|
tuttiWorkflowDock: AgentGUITuttiWorkflowDockController;
|
|
tuttiWorkflowDockLabels: TuttiWorkflowDockLabels;
|
|
tuttiPlanPanelLabels: TuttiModePlanPanelLabels;
|
|
tuttiPlanIssuePanelLabels: TuttiPlanIssuePanelLabels;
|
|
}
|
|
|
|
export const AgentGUIBottomDockPane = memo(function AgentGUIBottomDockPane({
|
|
bottomDockRef,
|
|
showScrollToBottom,
|
|
scrollToBottomLabel,
|
|
onScrollToBottom,
|
|
bottomDockLiftedPrompt,
|
|
bottomDockReplacementPrompt,
|
|
composerProps,
|
|
approvalDisabledReason,
|
|
interactivePromptDisabledReason,
|
|
inlineNoticeChrome,
|
|
isRespondingApproval,
|
|
sessionChrome,
|
|
keyboardShortcutsEnabled,
|
|
chromeLabels,
|
|
goalBannerLabels,
|
|
promptLabels,
|
|
onSubmitApprovalOption,
|
|
onAuthLogin,
|
|
onRetryActivation,
|
|
onRetryInlineNotice,
|
|
onContinueInNewConversation,
|
|
onSubmitBottomDockInteractivePrompt,
|
|
onGoalControl,
|
|
goalPauseSupported,
|
|
tuttiWorkflowDock,
|
|
tuttiWorkflowDockLabels,
|
|
tuttiPlanPanelLabels,
|
|
tuttiPlanIssuePanelLabels
|
|
}: AgentGUIBottomDockPaneProps): React.JSX.Element {
|
|
"use memo";
|
|
|
|
const liftedPromptDisabledReason = resolveAgentGUIInteractionDisabledReason({
|
|
promptKind: bottomDockLiftedPrompt?.kind,
|
|
approvalReason: approvalDisabledReason,
|
|
interactivePromptReason: interactivePromptDisabledReason
|
|
});
|
|
const replacementPromptDisabledReason =
|
|
resolveAgentGUIInteractionDisabledReason({
|
|
promptKind: bottomDockReplacementPrompt?.kind,
|
|
approvalReason: approvalDisabledReason,
|
|
interactivePromptReason: interactivePromptDisabledReason
|
|
});
|
|
|
|
// Active thread goal rides the same runtimeContext channel as account /
|
|
// rateLimits, so we read it straight off the session chrome's raw state.
|
|
const goal = objectRecord(sessionChrome.rawState?.goal);
|
|
const goalObjective = goal ? stringValue(goal.objective) : "";
|
|
const goalStatus = goal ? stringValue(goal.status) : "";
|
|
const goalTokenBudget = goal ? numberValue(goal.tokenBudget) : null;
|
|
const goalTokensUsed = goal ? numberValue(goal.tokens) : null;
|
|
const goalStartedAtUnixMs = goal ? numberValue(goal.startedAtUnixMs) : null;
|
|
const goalDurationMs = goal ? numberValue(goal.durationMs) : null;
|
|
const goalIsOptimistic = sessionChrome.rawState?.goalIsOptimistic === true;
|
|
const goalControlPending =
|
|
sessionChrome.rawState?.goalControlStatus === "pending" ||
|
|
sessionChrome.rawState?.goalControlStatus === "pending_create";
|
|
const showGoalBanner = isGoalBannerVisible(goalObjective, goalStatus);
|
|
|
|
const workflowPhase = tuttiWorkflowDock.phase;
|
|
|
|
return (
|
|
<AgentComposerRegion
|
|
regionRef={bottomDockRef}
|
|
floating={
|
|
showScrollToBottom ? (
|
|
<button
|
|
type="button"
|
|
className={cn(
|
|
styles.bottomDockScrollToBottom,
|
|
"nodrag tsh-desktop-no-drag [-webkit-app-region:no-drag]"
|
|
)}
|
|
data-testid="agent-gui-scroll-to-bottom"
|
|
aria-label={scrollToBottomLabel}
|
|
title={scrollToBottomLabel}
|
|
onPointerDown={(event) => event.stopPropagation()}
|
|
onClick={onScrollToBottom}
|
|
>
|
|
<ChevronsDown aria-hidden="true" size={15} strokeWidth={2.2} />
|
|
</button>
|
|
) : null
|
|
}
|
|
lifted={
|
|
bottomDockLiftedPrompt ? (
|
|
<div
|
|
className={styles.bottomDockPrompt}
|
|
data-testid="agent-gui-bottom-dock-active-prompt"
|
|
>
|
|
<AgentInteractivePromptSurface
|
|
prompt={bottomDockLiftedPrompt}
|
|
conversationReturn={promptLabels.conversationReturn}
|
|
embedded={true}
|
|
edgeGlow={true}
|
|
keyboardShortcuts={keyboardShortcutsEnabled}
|
|
isSubmitting={isRespondingApproval}
|
|
isInteractionDisabled={Boolean(liftedPromptDisabledReason)}
|
|
interactionDisabledReason={liftedPromptDisabledReason}
|
|
onSubmit={onSubmitBottomDockInteractivePrompt}
|
|
labels={promptLabels}
|
|
/>
|
|
</div>
|
|
) : null
|
|
}
|
|
accessories={
|
|
<>
|
|
{inlineNoticeChrome ? (
|
|
<AgentSessionChrome
|
|
chrome={inlineNoticeChrome}
|
|
approvalDisabledReason={approvalDisabledReason}
|
|
isRespondingApproval={isRespondingApproval}
|
|
onSubmitApprovalOption={onSubmitApprovalOption}
|
|
onAuthLogin={onAuthLogin}
|
|
onRetryActivation={onRetryInlineNotice}
|
|
onContinueInNewConversation={onContinueInNewConversation}
|
|
labels={chromeLabels}
|
|
/>
|
|
) : null}
|
|
<AgentSessionChrome
|
|
chrome={sessionChrome}
|
|
approvalDisabledReason={approvalDisabledReason}
|
|
isRespondingApproval={isRespondingApproval}
|
|
onSubmitApprovalOption={onSubmitApprovalOption}
|
|
onAuthLogin={onAuthLogin}
|
|
onRetryActivation={onRetryActivation}
|
|
onContinueInNewConversation={onContinueInNewConversation}
|
|
labels={chromeLabels}
|
|
/>
|
|
{showGoalBanner ? (
|
|
<AgentGoalBanner
|
|
objective={goalObjective}
|
|
status={goalStatus}
|
|
tokenBudget={goalTokenBudget ?? undefined}
|
|
tokensUsed={goalTokensUsed ?? undefined}
|
|
startedAtUnixMs={goalStartedAtUnixMs ?? undefined}
|
|
durationMs={goalDurationMs ?? undefined}
|
|
optimistic={goalIsOptimistic}
|
|
labels={goalBannerLabels}
|
|
onPauseGoal={
|
|
goalPauseSupported && !goalControlPending
|
|
? () => onGoalControl("pause")
|
|
: undefined
|
|
}
|
|
onResumeGoal={
|
|
goalPauseSupported && !goalControlPending
|
|
? () => onGoalControl("resume")
|
|
: undefined
|
|
}
|
|
onClearGoal={
|
|
goalControlPending ? undefined : () => onGoalControl("clear")
|
|
}
|
|
/>
|
|
) : null}
|
|
{workflowPhase ? (
|
|
<TuttiWorkflowDock
|
|
assignmentCatalog={tuttiWorkflowDock.assignmentCatalog}
|
|
assignmentDrafts={tuttiWorkflowDock.assignmentDrafts}
|
|
preferencePopoverLabels={{
|
|
title: composerProps.labels.tuttiBudgetTitle,
|
|
effectLabel: composerProps.labels.tuttiBudgetEffectLabel,
|
|
speedLabel: composerProps.labels.tuttiBudgetSpeedLabel,
|
|
previewHint: composerProps.labels.tuttiBudgetPreviewHint,
|
|
previewCost: composerProps.labels.tuttiBudgetPreviewCost,
|
|
previewBalance: composerProps.labels.tuttiBudgetPreviewBalance,
|
|
previewPowerful:
|
|
composerProps.labels.tuttiBudgetPreviewPowerful,
|
|
modelPreferenceLabel:
|
|
composerProps.labels.tuttiBudgetModelPreferenceLabel,
|
|
modelPreferenceCost:
|
|
composerProps.labels.tuttiBudgetModelPreferenceCost,
|
|
modelPreferenceBalance:
|
|
composerProps.labels.tuttiBudgetModelPreferenceBalance,
|
|
modelPreferencePowerful:
|
|
composerProps.labels.tuttiBudgetModelPreferencePowerful,
|
|
parallelismLabel:
|
|
composerProps.labels.tuttiBudgetParallelismLabel,
|
|
parallelismValue:
|
|
composerProps.labels.tuttiBudgetParallelismValue
|
|
}}
|
|
labels={tuttiWorkflowDockLabels}
|
|
phase={workflowPhase}
|
|
planPanelLabels={tuttiPlanPanelLabels}
|
|
planIssuePanelLabels={tuttiPlanIssuePanelLabels}
|
|
onAssignmentDraftChange={tuttiWorkflowDock.updateAssignment}
|
|
onCancelReview={tuttiWorkflowDock.cancelReview}
|
|
onTaskAction={tuttiWorkflowDock.taskAction}
|
|
onEffectChange={tuttiWorkflowDock.changeEffect}
|
|
onSpeedChange={tuttiWorkflowDock.changeSpeed}
|
|
onOpenIssue={tuttiWorkflowDock.openIssue}
|
|
onOpenTask={tuttiWorkflowDock.openTask}
|
|
onRetry={tuttiWorkflowDock.retry}
|
|
/>
|
|
) : null}
|
|
</>
|
|
}
|
|
primary={
|
|
bottomDockReplacementPrompt ? (
|
|
<div
|
|
className={styles.bottomDockPrompt}
|
|
data-testid="agent-gui-bottom-dock-active-prompt"
|
|
>
|
|
<AgentInteractivePromptSurface
|
|
prompt={bottomDockReplacementPrompt}
|
|
embedded={true}
|
|
edgeGlow={true}
|
|
keyboardShortcuts={keyboardShortcutsEnabled}
|
|
isSubmitting={isRespondingApproval}
|
|
isInteractionDisabled={Boolean(replacementPromptDisabledReason)}
|
|
interactionDisabledReason={replacementPromptDisabledReason}
|
|
onSubmit={onSubmitBottomDockInteractivePrompt}
|
|
labels={promptLabels}
|
|
/>
|
|
</div>
|
|
) : (
|
|
<AgentComposer {...composerProps} />
|
|
)
|
|
}
|
|
/>
|
|
);
|
|
});
|