diff --git a/apps/mobile/src/features/threads/NativeChildWorkSheet.tsx b/apps/mobile/src/features/threads/NativeChildWorkSheet.tsx new file mode 100644 index 0000000000000..1133b866014d0 --- /dev/null +++ b/apps/mobile/src/features/threads/NativeChildWorkSheet.tsx @@ -0,0 +1,116 @@ +import type { + EnvironmentId, + OrchestrationV2GetNativeChildWorkResult, + OrchestrationV2NativeChildWork, + ThreadId, +} from "@t3tools/contracts"; +import { ScrollView, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +import { AppText as Text } from "../../components/ui/AppText"; +import { Button } from "../../components/ui/Button"; +import { Sheet } from "../../components/ui/Sheet"; +import { orchestrationEnvironment } from "../../state/orchestration"; +import { useEnvironmentQuery } from "../../state/query"; + +const stepLabel = (type: string) => { + const words = type.toLowerCase().replaceAll("_", " "); + return words.charAt(0).toUpperCase() + words.slice(1); +}; + +/** Read-only view of what a provider-native subagent's children did. Mount only while open. */ +export function NativeChildWorkSheet(props: { + readonly environmentId: EnvironmentId; + readonly threadId: ThreadId; + readonly onClose: () => void; +}) { + const insets = useSafeAreaInsets(); + const query = useEnvironmentQuery( + orchestrationEnvironment.nativeChildWork({ + environmentId: props.environmentId, + input: { threadId: props.threadId }, + }), + ); + const children = query.data?.children; + return ( + + + + Read only. These subagents cannot take messages. + + {query.error !== null ? ( + + {query.error} + + ) : children === undefined ? ( + Loading… + ) : children.length === 0 ? ( + + The provider has no record of which subagents this call started. + + ) : ( + children.map((child) => ) + )} + + + + + Subagent work + Read only. These subagents cannot take messages. + + {open ? : null} + + + ); +} + +function NativeChildWorkContent(props: { + readonly environmentId: EnvironmentId; + readonly threadId: ThreadId; +}) { + const query = useEnvironmentQuery( + orchestrationEnvironment.nativeChildWork({ + environmentId: props.environmentId, + input: { threadId: props.threadId }, + }), + ); + const children = query.data?.children; + return ( + <> + + {query.error !== null ? ( +

{query.error}

+ ) : children === undefined ? ( +

Loading…

+ ) : children.length === 0 ? ( +

+ The provider has no record of which subagents this call started. +

+ ) : ( + children.map((child) => ) + )} +
+ + + + + ); +} + +function NativeChild(props: { readonly child: OrchestrationV2NativeChildWork }) { + const { child } = props; + return ( +
+

{child.role || "Subagent"}

+

+ {child.prompt} +

+ {child.truncated ? ( +

Earlier steps are not shown.

+ ) : null} + {child.steps === null ? ( +

No transcript yet.

+ ) : ( +
    + {child.steps.map((step) => ( + + ))} +
+ )} +
+ ); +} + +function NativeChildStep(props: { readonly step: OrchestrationV2NativeChildStep }) { + const { step } = props; + return ( +
  • + + {stepLabel(step.type)} + {step.status !== null && step.status !== "DONE" ? ` · ${stepLabel(step.status)}` : ""} + + {step.toolCalls.length === 0 ? null : ( + + {step.toolCalls.map((call) => `${call.name} ${call.input}`).join("\n")} + + )} + {step.text === null ? null : ( +
    +          {step.text}
    +        
    + )} +
  • + ); +} diff --git a/apps/web/src/components/chat/ProviderSubagentBar.tsx b/apps/web/src/components/chat/ProviderSubagentBar.tsx index 6aa8799c70b7e..1b74e43284f23 100644 --- a/apps/web/src/components/chat/ProviderSubagentBar.tsx +++ b/apps/web/src/components/chat/ProviderSubagentBar.tsx @@ -4,6 +4,7 @@ import { } from "@t3tools/client-runtime/state/thread-execution"; import { isOrchestrationV2WorkActive } from "@t3tools/contracts"; import { ArrowUpLeftIcon } from "lucide-react"; +import type { ReactNode } from "react"; import { tickTextContent } from "~/lib/tickTextContent"; import type { ProviderInstanceEntry } from "../../providerInstances"; @@ -17,6 +18,7 @@ export function ProviderSubagentBar(props: { readonly effortLabel: string | null; readonly status: ProviderSubagentStatus | null; readonly onOpenParent: (() => void) | null; + readonly actions?: ReactNode; }) { const { status } = props; const live = status !== null && isOrchestrationV2WorkActive(status.status); @@ -64,6 +66,7 @@ export function ProviderSubagentBar(props: { Runs on its own + {props.actions} {props.onOpenParent ? (