Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e0ab895
feat(mobile): show dynamic workflows in the Agents sheet
Bil0000 Oct 12, 2026
08bea6c
fix(mobile): exclude idle agents from done count
Bil0000 Oct 12, 2026
9f71d86
feat(mobile): share Stop controls for agents and workflows
Bil0000 Oct 12, 2026
e0ac919
fix(mobile): refine agent Stop button layout
Bil0000 Oct 12, 2026
5a2a539
fix(server): reset workflow members when the run changes
Bil0000 Oct 13, 2026
6538baa
fix(mobile): make agent Stop controls compact
Bil0000 Oct 13, 2026
58ff19c
fix(server): clear workflow run metadata on restart
Bil0000 Oct 13, 2026
f82eb01
fix(workflows): retain member effort and speed after completion
Bil0000 Oct 14, 2026
9963635
feat(mobile): show compact workflow phase cards
Bil0000 Oct 14, 2026
996206f
fix(workflows): isolate member state between runs
Bil0000 Oct 14, 2026
19b8791
fix(mobile): resolve workflow conflicts with main
Bil0000 Oct 16, 2026
8b05ab9
chore: merge main for mobile design system
Bil0000 Oct 22, 2026
c394249
refactor(mobile): use shared workflow UI controls
Bil0000 Oct 22, 2026
fe52f17
fix(mobile): support Stop in React Compiler
Bil0000 Oct 22, 2026
260ec51
fix(mobile): restore workflow coordinator chat access
Bil0000 Oct 22, 2026
2b07a9b
fix(mobile): remove workflow coordinator chat button
Bil0000 Oct 24, 2026
107a533
chore: split shared workflow fixes from mobile PR
Bil0000 Oct 24, 2026
c544264
Merge upstream main into mobile workflow cards
Bil0000 Oct 26, 2026
2f98056
refactor(workflows): keep web phase helpers local
Bil0000 Oct 26, 2026
f826de6
fix(workflows): retain history and isolate repeated runs
Bil0000 Oct 26, 2026
5c7342d
chore: update mobile workflows to current main
Bil0000 Oct 26, 2026
0d8b5dc
chore(mobile): sync main and narrow workflow scope
Bil0000 Oct 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions apps/mobile/src/features/threads/SubagentRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export function SubagentRow(props: {
readonly environmentId: EnvironmentId;
readonly subagent: SubagentRowSubagent;
readonly elapsed: ReactNode;
readonly reserveMetadataAction?: boolean;
}) {
const presentation = resolveSubagentRowPresentation(props.subagent);
const detail = subagentCardDetail(presentation.detail);
Expand Down Expand Up @@ -75,7 +76,11 @@ export function SubagentRow(props: {
<SymbolView name="chevron.right" size={12} tintColorClassName="accent-icon-subtle" />
) : null}
</View>
<SubagentMetadata environmentId={props.environmentId} subagent={props.subagent} />
<SubagentMetadata
environmentId={props.environmentId}
subagent={props.subagent}
className={props.reserveMetadataAction ? "pr-9" : undefined}
/>
{detail ? (
<Text
numberOfLines={3}
Expand All @@ -96,6 +101,7 @@ export function SubagentRow(props: {
function SubagentMetadata(props: {
readonly environmentId: EnvironmentId;
readonly subagent: SubagentRowSubagent;
readonly className?: string;
}) {
const { environmentId, subagent } = props;
const config = useEnvironmentServerConfig(environmentId);
Expand All @@ -119,7 +125,7 @@ function SubagentMetadata(props: {
childProject,
});
return (
<View className="min-w-0 flex-row items-center gap-1.5">
<View className={cn("min-w-0 flex-row items-center gap-1.5", props.className)}>
<ProviderIcon
provider={provider?.driver ?? subagent.driver}
iconUrl={provider?.iconUrl}
Expand Down
86 changes: 86 additions & 0 deletions apps/mobile/src/features/threads/SubagentStopButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useAtomValue } from "@effect/atom-react";
import type { EnvironmentId, OrchestrationV2Subagent } from "@t3tools/contracts";
import { useState } from "react";
import { Alert, Pressable, View } from "react-native";

import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { SymbolView } from "../../components/AppSymbol";
import { cn } from "../../lib/cn";
import { orchestrationEnvironment } from "../../state/orchestration";
import { threadEnvironment } from "../../state/threads";
import { useAtomCommand } from "../../state/use-atom-command";
import {
resolveSubagentRowPresentation,
resolveSubagentStopTarget,
} from "./threadAgentsPresentation";

export function SubagentStopButton(props: {
readonly environmentId: EnvironmentId;
readonly subagent: OrchestrationV2Subagent;
readonly className?: string;
}) {
const target = resolveSubagentStopTarget(props.subagent);
const canStop = useAtomValue(
orchestrationEnvironment.stopWorkflow.permissionAtom(props.environmentId),
);
const stopWorkflow = useAtomCommand(orchestrationEnvironment.stopWorkflow);
const interruptTurn = useAtomCommand(threadEnvironment.interruptTurn);
const [stopping, setStopping] = useState(false);

if (target === null || !canStop) return null;

const workflow = target.kind === "workflow";
const label =
props.subagent.workflow?.name ?? resolveSubagentRowPresentation(props.subagent).title;
const confirmStop = () =>
Alert.alert(
workflow ? "Stop workflow?" : "Stop agent?",
workflow ? `Every running agent in ${label} stops.` : `Stop ${label}?`,
[
{ text: "Cancel", style: "cancel" },
{
text: "Stop",
style: "destructive",
onPress: async () => {
setStopping(true);
try {
const result =
target.kind === "workflow"
? await stopWorkflow({ environmentId: props.environmentId, input: target.input })
: await interruptTurn({
environmentId: props.environmentId,
input: target.input,
});
if (result._tag === "Failure") {
Alert.alert(workflow ? "Could not stop workflow" : "Could not stop agent");
}
} finally {
setStopping(false);
}
},
},
],
);

return (
<Pressable
accessibilityRole="button"
accessibilityLabel={`Stop ${workflow ? "workflow" : "agent"} ${label}`}
accessibilityState={{ disabled: stopping, busy: stopping }}
disabled={stopping}
onPress={confirmStop}
className={cn(
"size-11 items-center justify-center active:opacity-70 disabled:opacity-50",
props.className,
)}
>
<View className="size-6 items-center justify-center rounded-full bg-danger">
{stopping ? (
<AppActivityIndicator size="small" />
) : (
<SymbolView name="stop.fill" size={9} tintColorClassName="accent-danger-foreground" />
)}
</View>
</Pressable>
);
}
82 changes: 60 additions & 22 deletions apps/mobile/src/features/threads/ThreadAgentsSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { useAtomValue } from "@effect/atom-react";
import {
deriveWorkflowGroups,
projectedSubagentsToRuntime,
} from "@t3tools/client-runtime/state/subagentRuntime";
import type { ThreadTurnSubagents } from "@t3tools/client-runtime/state/thread-subagents";
import {
isOrchestrationV2WorkActive,
Expand All @@ -10,7 +14,7 @@ import { deriveSubagentElapsedMs, formatDuration } from "@t3tools/shared/orchest
import { StackActions, useNavigation, type StaticScreenProps } from "@react-navigation/native";
import * as DateTime from "effect/DateTime";
import * as Haptics from "expo-haptics";
import { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { Platform, Pressable, ScrollView, View } from "react-native";
import { Screen, ScreenStack, ScreenStackHeaderConfig } from "react-native-screens";
import { useSafeAreaInsets } from "react-native-safe-area-context";
Expand All @@ -21,6 +25,9 @@ import { useUniwindTheme } from "../../lib/useUniwindTheme";
import { environmentThreadDetails } from "../../state/threads";
import { NativeStackScreenOptions, nativeHeaderScrollEdgeEffects } from "../../native/StackHeader";
import { SubagentRow } from "./SubagentRow";
import { SubagentStopButton } from "./SubagentStopButton";
import { ThreadWorkflowGroup } from "./ThreadWorkflowGroup";
import { resolveSubagentStopTarget } from "./threadAgentsPresentation";
import { MaterialScreenContent } from "../../components/MaterialScreenContent";
import { useAdaptiveWorkspaceLayout } from "../layout/AdaptiveWorkspaceLayout";

Expand All @@ -41,6 +48,16 @@ export function ThreadAgentsSheet({ route }: StaticScreenProps<AgentsTarget>) {
const turn = useThreadTurnSubagents(target);
const subagents = turn?.subagents ?? [];
const hasLiveAgent = (turn?.liveCount ?? 0) > 0;
const workflowGroups = useMemo(
() =>
new Map(
deriveWorkflowGroups(projectedSubagentsToRuntime(subagents)).map((group) => [
group.workflow.id,
group,
]),
),
[subagents],
);

const openChildThread = (childThreadId: ThreadId) => {
void Haptics.selectionAsync();
Expand Down Expand Up @@ -69,15 +86,27 @@ export function ThreadAgentsSheet({ route }: StaticScreenProps<AgentsTarget>) {
No agents in this turn.
</Text>
) : (
subagents.map((subagent) => (
<AgentRow
key={subagent.id}
subagent={subagent}
environmentId={target.environmentId}
tickSeconds={hasLiveAgent}
onOpen={openChildThread}
/>
))
subagents.map((subagent) => {
const group = workflowGroups.get(subagent.id);
return group === undefined ? (
<View key={subagent.id} className="border-b border-border">
<AgentRow
subagent={subagent}
environmentId={target.environmentId}
tickSeconds={hasLiveAgent}
onOpen={openChildThread}
/>
</View>
) : (
<ThreadWorkflowGroup
Comment thread
Bil0000 marked this conversation as resolved.
key={subagent.id}
environmentId={target.environmentId}
subagent={subagent}
group={group}
onOpen={openChildThread}
/>
);
})
)}
</ScrollView>
);
Expand Down Expand Up @@ -135,35 +164,44 @@ function AgentRow(props: {
const childThreadId = subagent.childThreadId;

const row = (
<View className="border-b border-border py-3.5">
<View className="py-3.5">
<SubagentRow
environmentId={props.environmentId}
subagent={subagent}
elapsed={<AgentElapsed subagent={subagent} tickSeconds={props.tickSeconds} />}
reserveMetadataAction={resolveSubagentStopTarget(subagent) !== null}
/>
</View>
);

if (childThreadId === null) {
return (
const content =
childThreadId === null ? (
<View
accessible
accessibilityHint="Provider-managed agent. Its work appears in the transcript."
>
{row}
</View>
) : (
<Pressable
accessibilityRole="link"
accessibilityHint="Opens this agent thread"
onPress={() => props.onOpen(childThreadId)}
className="active:opacity-70"
>
{row}
</Pressable>
);
}

return (
<Pressable
accessibilityRole="link"
accessibilityHint="Opens this agent's thread"
onPress={() => props.onOpen(childThreadId)}
className="active:opacity-70"
>
{row}
</Pressable>
<View className="-mr-2.5 pr-2.5">
{content}
<SubagentStopButton
environmentId={props.environmentId}
subagent={subagent}
className="absolute right-0 top-6"
/>
</View>
);
}

Expand Down
Loading
Loading