Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: display startup script logs while agent is starting#19530

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
BrunoQuaresma merged 8 commits intomainfrombq/handle-agent-being-ready-for-tasks
Aug 26, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
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
77 changes: 57 additions & 20 deletionssite/src/pages/TaskPage/TaskPage.stories.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,17 +2,17 @@ import {
MockFailedWorkspace,
MockStartingWorkspace,
MockStoppedWorkspace,
MockTemplate,
MockWorkspace,
MockWorkspaceAgent,
MockWorkspaceAgentLogSource,
MockWorkspaceAgentReady,
MockWorkspaceAgentStarting,
MockWorkspaceApp,
MockWorkspaceAppStatus,
MockWorkspaceResource,
mockApiError,
} from "testHelpers/entities";
import { withProxyProvider } from "testHelpers/storybook";
import { withProxyProvider, withWebSocket } from "testHelpers/storybook";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { API } from "api/api";
import type {
Workspace,
WorkspaceApp,
Expand DownExpand Up@@ -61,56 +61,93 @@ export const WaitingOnBuild: Story = {
},
};

export constWaitingOnBuildWithTemplate: Story = {
export constFailedBuild: Story = {
beforeEach: () => {
spyOn(API, "getTemplate").mockResolvedValue(MockTemplate);
spyOn(data, "fetchTask").mockResolvedValue({
prompt: "Create competitors page",
workspace:MockStartingWorkspace,
workspace:MockFailedWorkspace,
});
},
};

export constWaitingOnStatus: Story = {
export constTerminatedBuild: Story = {
beforeEach: () => {
spyOn(data, "fetchTask").mockResolvedValue({
prompt: "Create competitors page",
workspace: {
...MockWorkspace,
latest_app_status: null,
},
workspace: MockStoppedWorkspace,
});
},
};

export constFailedBuild: Story = {
export constTerminatedBuildWithStatus: Story = {
beforeEach: () => {
spyOn(data, "fetchTask").mockResolvedValue({
prompt: "Create competitors page",
workspace: MockFailedWorkspace,
workspace: {
...MockStoppedWorkspace,
latest_app_status: MockWorkspaceAppStatus,
},
});
},
};

export constTerminatedBuild: Story = {
export constWaitingOnStatus: Story = {
beforeEach: () => {
spyOn(data, "fetchTask").mockResolvedValue({
prompt: "Create competitors page",
workspace: MockStoppedWorkspace,
workspace: {
...MockWorkspace,
latest_app_status: null,
latest_build: {
...MockWorkspace.latest_build,
resources: [
{ ...MockWorkspaceResource, agents: [MockWorkspaceAgentReady] },
],
},
},
});
},
};

export constTerminatedBuildWithStatus: Story = {
export constWaitingStartupScripts: Story = {
beforeEach: () => {
spyOn(data, "fetchTask").mockResolvedValue({
prompt: "Create competitors page",
workspace: {
...MockStoppedWorkspace,
latest_app_status: MockWorkspaceAppStatus,
...MockWorkspace,
latest_build: {
...MockWorkspace.latest_build,
has_ai_task: true,
resources: [
{ ...MockWorkspaceResource, agents: [MockWorkspaceAgentStarting] },
],
},
},
});
},
decorators: [withWebSocket],
parameters: {
webSocket: [
{
event: "message",
data: JSON.stringify(
[
"\x1b[91mCloning Git repository...",
"\x1b[2;37;41mStarting Docker Daemon...",
"\x1b[1;95mAdding some 🧙magic🧙...",
"Starting VS Code...",
"\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 1475 0 1475 0 0 4231 0 --:--:-- --:--:-- --:--:-- 4238",
].map((line, index) => ({
id: index,
level: "info",
output: line,
source_id: MockWorkspaceAgentLogSource.id,
created_at: new Date("2024-01-01T12:00:00Z").toISOString(),
})),
),
},
],
},
};

export const SidebarAppHealthDisabled: Story = {
Expand DownExpand Up@@ -223,7 +260,7 @@ const mockResources = (
...MockWorkspaceResource,
agents: [
{
...MockWorkspaceAgent,
...MockWorkspaceAgentReady,
apps: [
...(props?.apps ?? []),
{
Expand Down
126 changes: 98 additions & 28 deletionssite/src/pages/TaskPage/TaskPage.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
import { API } from "api/api";
import { getErrorDetail, getErrorMessage } from "api/errors";
import { template as templateQueryOptions } from "api/queries/templates";
import type { Workspace, WorkspaceStatus } from "api/typesGenerated";
import type {
Workspace,
WorkspaceAgent,
WorkspaceStatus,
} from "api/typesGenerated";
import isChromatic from "chromatic/isChromatic";
import { Button } from "components/Button/Button";
import { Loader } from "components/Loader/Loader";
import { Margins } from "components/Margins/Margins";
import { ScrollArea } from "components/ScrollArea/ScrollArea";
import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs";
import { ArrowLeftIcon, RotateCcwIcon } from "lucide-react";
import { AgentLogs } from "modules/resources/AgentLogs/AgentLogs";
import { useAgentLogs } from "modules/resources/useAgentLogs";
import { AI_PROMPT_PARAMETER_NAME, type Task } from "modules/tasks/tasks";
import { WorkspaceBuildLogs } from "modules/workspaces/WorkspaceBuildLogs/WorkspaceBuildLogs";
import { type FC, type ReactNode,useEffect, useRef } from "react";
import { type FC, type ReactNode,useLayoutEffect, useRef } from "react";
import { Helmet } from "react-helmet-async";
import { useQuery } from "react-query";
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
import { Link as RouterLink, useParams } from "react-router";
import type { FixedSizeList } from "react-window";
import { pageTitle } from "utils/page";
import {
getActiveTransitionStats,
Expand DownExpand Up@@ -87,6 +94,7 @@ const TaskPage = () => {
}

let content: ReactNode = null;
const agent = selectAgent(task);

if (waitingStatuses.includes(task.workspace.latest_build.status)) {
content = <TaskBuildingWorkspace task={task} />;
Expand DownExpand Up@@ -132,6 +140,8 @@ const TaskPage = () => {
</div>
</Margins>
);
} else if (agent && ["created", "starting"].includes(agent.lifecycle_state)) {
content = <TaskStartingAgent agent={agent} />;
} else {
content = (
<PanelGroup autoSaveId="task" direction="horizontal">
Expand DownExpand Up@@ -182,7 +192,7 @@ const TaskBuildingWorkspace: FC<TaskBuildingWorkspaceProps> = ({ task }) => {

const scrollAreaRef = useRef<HTMLDivElement>(null);
// biome-ignore lint/correctness/useExhaustiveDependencies: this effect should run when build logs change
useEffect(() => {
useLayoutEffect(() => {
if (isChromatic()) {
return;
}
Expand All@@ -196,34 +206,86 @@ const TaskBuildingWorkspace: FC<TaskBuildingWorkspaceProps> = ({ task }) => {
}, [buildLogs]);

return (
<section className="w-full h-full flex justify-center items-center p-6 overflow-y-auto">
<div className="flex flex-col gap-6 items-center w-full">
<header className="flex flex-col items-center text-center">
<h3 className="m-0 font-medium text-content-primary text-xl">
Starting your workspace
</h3>
<div className="text-content-secondary">
Your task will be running in a few moments
<section className="p-16 overflow-y-auto">
<div className="flex justify-center items-center w-full">
<div className="flex flex-col gap-6 items-center w-full">
<header className="flex flex-col items-center text-center">
<h3 className="m-0 font-medium text-content-primary text-xl">
Starting your workspace
</h3>
<p className="text-content-secondary m-0">
Your task will be running in a few moments
</p>
</header>

<div className="w-full max-w-screen-lg flex flex-col gap-4 overflow-hidden">
<WorkspaceBuildProgress
workspace={task.workspace}
transitionStats={transitionStats}
variant="task"
/>

<ScrollArea
ref={scrollAreaRef}
className="h-96 border border-solid border-border rounded-lg"
>
<WorkspaceBuildLogs
sticky
className="border-0 rounded-none"
logs={buildLogs ?? []}
/>
</ScrollArea>
</div>
</header>
</div>
</div>
</section>
);
};

type TaskStartingAgentProps = {
agent: WorkspaceAgent;
};

<div className="w-full max-w-screen-lg flex flex-col gap-4 overflow-hidden">
<WorkspaceBuildProgress
workspace={task.workspace}
transitionStats={transitionStats}
variant="task"
/>
const TaskStartingAgent: FC<TaskStartingAgentProps> = ({ agent }) => {
const logs = useAgentLogs(agent, true);
const listRef = useRef<FixedSizeList>(null);

<ScrollArea
ref={scrollAreaRef}
className="h-96 border border-solid border-border rounded-lg"
>
<WorkspaceBuildLogs
sticky
className="border-0 rounded-none"
logs={buildLogs ?? []}
/>
</ScrollArea>
useLayoutEffect(() => {
if (listRef.current) {
listRef.current.scrollToItem(logs.length - 1, "end");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Do you know what the scroll behavior is for the React Window component? As in, do you know if the scroll is instant, or if it does an animation?

Because if it's instant, we could swap inuseLayoutEffect to reduce any risk of screen flickering

Copy link
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It is instant, and we can move to useuseLayoutEffect for sure.

}
}, [logs]);

return (
<section className="p-16 overflow-y-auto">
<div className="flex justify-center items-center w-full">
<div className="flex flex-col gap-8 items-center w-full">
<header className="flex flex-col items-center text-center">
<h3 className="m-0 font-medium text-content-primary text-xl">
Running startup scripts
</h3>
<p className="text-content-secondary m-0">
Your task will be running in a few moments
</p>
</header>

<div className="w-full max-w-screen-lg flex flex-col gap-4 overflow-hidden">
<div className="h-96 border border-solid border-border rounded-lg">
<AgentLogs
logs={logs.map((l) => ({
id: l.id,
level: l.level,
output: l.output,
sourceId: l.source_id,
time: l.created_at,
}))}
sources={agent.log_sources}
height={96 * 4}
width="100%"
ref={listRef}
/>
</div>
</div>
</div>
</div>
</section>
Expand DownExpand Up@@ -265,3 +327,11 @@ export const data = {
} satisfies Task;
},
};

function selectAgent(task: Task) {
const agents = task.workspace.latest_build.resources
.flatMap((r) => r.agents)
.filter((a) => !!a);

return agents.at(0);
}
2 changes: 1 addition & 1 deletionsite/src/pages/TaskPage/TaskTopbar.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,7 +22,7 @@ type TaskTopbarProps = { task: Task };

exportconstTaskTopbar:FC<TaskTopbarProps>=({ task})=>{
return(
<headerclassName="flex items-center px-3 py-4 border-solid border-border border-0 border-b">
<headerclassName="flexflex-shrink-0items-center px-3 py-4 border-solid border-border border-0 border-b">
<TooltipProvider>
<Tooltip>
<TooltipTriggerasChild>
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp