Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0a483ea

Browse files
authored
feat: add idle app status (#18415)
"Idle" is more accurate than "complete" since: 1. AgentAPI only knows if the screen is active; it has no way of knowing if the task is complete. 2. The LLM might be done with its current prompt, but that does not mean the task is complete either (it likely needs refinement). The "complete" state will be reserved for future definition. Additionally, in the case where the screen goes idle but the LLM never reported a status update, we can get an idle icon without a message, and it looks kinda janky in the UI so if there is no message I display the state text. Closes coder/internal#699
1 parent 0258f1d commit 0a483ea

20 files changed

+115
-16
lines changed

cli/exp_mcp.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -585,10 +585,10 @@ func (s *mcpServer) startWatcher(ctx context.Context, inv *serpent.Invocation) {
585585
case event := <-eventsCh:
586586
switch ev := event.(type) {
587587
case agentapi.EventStatusChange:
588-
// If the screen is stable, assume complete.
588+
// If the screen is stable, report idle.
589589
state := codersdk.WorkspaceAppStatusStateWorking
590590
if ev.Status == agentapi.StatusStable {
591-
state = codersdk.WorkspaceAppStatusStateComplete
591+
state = codersdk.WorkspaceAppStatusStateIdle
592592
}
593593
err := s.queue.Push(taskReport{
594594
state: state,

cli/exp_mcp_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,7 @@ func TestExpMcpReporter(t *testing.T) {
900900
{
901901
event: makeStatusEvent(agentapi.StatusStable),
902902
expected: &codersdk.WorkspaceAppStatus{
903-
State: codersdk.WorkspaceAppStatusStateComplete,
903+
State: codersdk.WorkspaceAppStatusStateIdle,
904904
Message: "doing work",
905905
URI: "https://dev.coder.com",
906906
},
@@ -948,7 +948,7 @@ func TestExpMcpReporter(t *testing.T) {
948948
{
949949
event: makeStatusEvent(agentapi.StatusStable),
950950
expected: &codersdk.WorkspaceAppStatus{
951-
State: codersdk.WorkspaceAppStatusStateComplete,
951+
State: codersdk.WorkspaceAppStatusStateIdle,
952952
Message: "oops",
953953
URI: "",
954954
},

coderd/apidoc/docs.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dump.sql

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- It is not possible to delete a value from an enum, so we have to recreate it.
2+
CREATE TYPE old_workspace_app_status_state AS ENUM ('working', 'complete', 'failure');
3+
4+
-- Convert the new "idle" state into "complete". This means we lose some
5+
-- information when downgrading, but this is necessary to swap to the old enum.
6+
UPDATE workspace_app_statuses SET state = 'complete' WHERE state = 'idle';
7+
8+
-- Swap to the old enum.
9+
ALTER TABLE workspace_app_statuses
10+
ALTER COLUMN state TYPE old_workspace_app_status_state
11+
USING (state::text::old_workspace_app_status_state);
12+
13+
-- Drop the new enum and rename the old one to the final name.
14+
DROP TYPE workspace_app_status_state;
15+
ALTER TYPE old_workspace_app_status_state RENAME TO workspace_app_status_state;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TYPE workspace_app_status_state ADD VALUE IF NOT EXISTS 'idle';

coderd/database/models.go

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/workspaceagents.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,10 @@ func (api *API) patchWorkspaceAgentAppStatus(rw http.ResponseWriter, r *http.Req
359359
}
360360

361361
switch req.State {
362-
case codersdk.WorkspaceAppStatusStateComplete, codersdk.WorkspaceAppStatusStateFailure, codersdk.WorkspaceAppStatusStateWorking: // valid states
362+
case codersdk.WorkspaceAppStatusStateComplete,
363+
codersdk.WorkspaceAppStatusStateFailure,
364+
codersdk.WorkspaceAppStatusStateWorking,
365+
codersdk.WorkspaceAppStatusStateIdle: // valid states
363366
default:
364367
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
365368
Message: "Invalid state provided.",

codersdk/toolsdk/toolsdk.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ Bad Tasks
191191
Use the "state" field to indicate your progress. Periodically report
192192
progress with state "working" to keep the user updated. It is not possible to send too many updates!
193193
194-
ONLY report a "complete" or "failure" state if you have FULLY completed the task.
194+
ONLY report an "idle" or "failure" state if you have FULLY completed the task.
195195
`,
196196
Schema: aisdk.Schema{
197197
Properties: map[string]any{
@@ -205,10 +205,10 @@ ONLY report a "complete" or "failure" state if you have FULLY completed the task
205205
},
206206
"state": map[string]any{
207207
"type": "string",
208-
"description": "The state of your task. This can be one of the following: working, complete, or failure. Select the state that best represents your current progress.",
208+
"description": "The state of your task. This can be one of the following: working, idle, or failure. Select the state that best represents your current progress.",
209209
"enum": []string{
210210
string(codersdk.WorkspaceAppStatusStateWorking),
211-
string(codersdk.WorkspaceAppStatusStateComplete),
211+
string(codersdk.WorkspaceAppStatusStateIdle),
212212
string(codersdk.WorkspaceAppStatusStateFailure),
213213
},
214214
},

codersdk/workspaceapps.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type WorkspaceAppStatusState string
1919

2020
const (
2121
WorkspaceAppStatusStateWorking WorkspaceAppStatusState = "working"
22+
WorkspaceAppStatusStateIdle WorkspaceAppStatusState = "idle"
2223
WorkspaceAppStatusStateComplete WorkspaceAppStatusState = "complete"
2324
WorkspaceAppStatusStateFailure WorkspaceAppStatusState = "failure"
2425
)

docs/reference/api/builds.md

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/api/schemas.md

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/api/templates.md

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/src/api/typesGenerated.ts

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/src/modules/apps/AppStatusStateIcon.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
CircleAlertIcon,
66
CircleCheckIcon,
77
HourglassIcon,
8+
SquareIcon,
89
TriangleAlertIcon,
910
} from "lucide-react";
1011
import type { FC } from "react";
@@ -26,6 +27,10 @@ export const AppStatusStateIcon: FC<AppStatusStateIconProps> = ({
2627
const className = cn(["size-4 shrink-0", customClassName]);
2728

2829
switch (state) {
30+
case "idle":
31+
return (
32+
<SquareIcon className={cn(["text-content-secondary", className])} />
33+
);
2934
case "complete":
3035
return (
3136
<CircleCheckIcon className={cn(["text-content-success", className])} />

site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.stories.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,26 @@ export const Working: Story = {
3939
},
4040
};
4141

42+
export const Idle: Story = {
43+
args: {
44+
status: {
45+
...MockWorkspaceAppStatus,
46+
state: "idle",
47+
message: "Done for now",
48+
},
49+
},
50+
};
51+
52+
export const NoMessage: Story = {
53+
args: {
54+
status: {
55+
...MockWorkspaceAppStatus,
56+
state: "idle",
57+
message: "",
58+
},
59+
},
60+
};
61+
4262
export const LongMessage: Story = {
4363
args: {
4464
status: {

site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
TooltipProvider,
66
TooltipTrigger,
77
} from "components/Tooltip/Tooltip";
8+
import capitalize from "lodash/capitalize";
89
import { AppStatusStateIcon } from "modules/apps/AppStatusStateIcon";
910
import { cn } from "utils/cn";
1011

@@ -25,6 +26,7 @@ export const WorkspaceAppStatus = ({
2526
);
2627
}
2728

29+
const message = status.message || capitalize(status.state);
2830
return (
2931
<div className="flex flex-col text-content-secondary">
3032
<TooltipProvider>
@@ -40,11 +42,11 @@ export const WorkspaceAppStatus = ({
4042
})}
4143
/>
4244
<span className="whitespace-nowrap max-w-72 overflow-hidden text-ellipsis text-sm text-content-primary font-medium">
43-
{status.message}
45+
{message}
4446
</span>
4547
</div>
4648
</TooltipTrigger>
47-
<TooltipContent>{status.message}</TooltipContent>
49+
<TooltipContent>{message}</TooltipContent>
4850
</Tooltip>
4951
</TooltipProvider>
5052
<span className="text-xs first-letter:uppercase block pl-6">

site/src/pages/WorkspacePage/AppStatuses.stories.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,40 @@ export const WorkingState: Story = {
4848
},
4949
};
5050

51+
export const IdleState: Story = {
52+
args: {
53+
agent: mockAgent([
54+
{
55+
...MockWorkspaceAppStatus,
56+
id: "status-8",
57+
icon: "",
58+
message: "Done for now",
59+
created_at: createTimestamp(5, 20),
60+
uri: "",
61+
state: "idle" as const,
62+
},
63+
...MockWorkspaceAppStatuses,
64+
]),
65+
},
66+
};
67+
68+
export const NoMessage: Story = {
69+
args: {
70+
agent: mockAgent([
71+
{
72+
...MockWorkspaceAppStatus,
73+
id: "status-8",
74+
icon: "",
75+
message: "",
76+
created_at: createTimestamp(5, 20),
77+
uri: "",
78+
state: "idle" as const,
79+
},
80+
...MockWorkspaceAppStatuses,
81+
]),
82+
},
83+
};
84+
5185
export const LongStatusText: Story = {
5286
args: {
5387
agent: mockAgent([

site/src/pages/WorkspacePage/AppStatuses.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
TooltipProvider,
1313
TooltipTrigger,
1414
} from "components/Tooltip/Tooltip";
15+
import capitalize from "lodash/capitalize";
1516
import { timeFrom } from "utils/time";
1617

1718
import {
@@ -77,7 +78,7 @@ export const AppStatuses: FC<AppStatusesProps> = ({
7778
<div className="text-sm font-medium text-content-primary flex items-center gap-2 ">
7879
<AppStatusStateIcon state={latestStatus.state} latest />
7980
<span className="block flex-1 whitespace-nowrap overflow-hidden text-ellipsis">
80-
{latestStatus.message}
81+
{latestStatus.message || capitalize(latestStatus.state)}
8182
</span>
8283
</div>
8384
<span className="text-xs text-content-secondary first-letter:uppercase block pl-[26px]">
@@ -160,7 +161,7 @@ export const AppStatuses: FC<AppStatusesProps> = ({
160161
latest={false}
161162
className="size-icon-xs w-[18px]"
162163
/>
163-
{status.message}
164+
{status.message || capitalize(status.state)}
164165
</span>
165166
<span className="text-2xs text-content-secondary first-letter:uppercase block pl-[26px]">
166167
{formattedTimestamp}

0 commit comments

Comments
 (0)