From 5aa909c0b77df4ef943a14a9901f6babd1ced2fd Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 17 Jun 2025 11:40:39 -0800 Subject: [PATCH 1/3] feat: add idle AI agent status "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. --- cli/exp_mcp.go | 4 ++-- cli/exp_mcp_test.go | 4 ++-- coderd/apidoc/docs.go | 2 ++ coderd/apidoc/swagger.json | 3 ++- coderd/database/dump.sql | 3 ++- .../000338_workspace_app_status_idle.down.sql | 1 + .../000338_workspace_app_status_idle.up.sql | 1 + coderd/database/models.go | 5 ++++- coderd/workspaceagents.go | 5 ++++- codersdk/toolsdk/toolsdk.go | 6 +++--- codersdk/workspaceapps.go | 1 + docs/reference/api/builds.md | 2 ++ docs/reference/api/schemas.md | 1 + docs/reference/api/templates.md | 2 ++ site/src/api/typesGenerated.ts | 7 ++++++- site/src/modules/apps/AppStatusStateIcon.tsx | 5 +++++ .../WorkspaceAppStatus.stories.tsx | 10 ++++++++++ .../pages/WorkspacePage/AppStatuses.stories.tsx | 16 ++++++++++++++++ 18 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 coderd/database/migrations/000338_workspace_app_status_idle.down.sql create mode 100644 coderd/database/migrations/000338_workspace_app_status_idle.up.sql diff --git a/cli/exp_mcp.go b/cli/exp_mcp.go index d487af5691bca..0a1c9fcbeaf87 100644 --- a/cli/exp_mcp.go +++ b/cli/exp_mcp.go @@ -585,10 +585,10 @@ func (s *mcpServer) startWatcher(ctx context.Context, inv *serpent.Invocation) { case event := <-eventsCh: switch ev := event.(type) { case agentapi.EventStatusChange: - // If the screen is stable, assume complete. + // If the screen is stable, report idle. state := codersdk.WorkspaceAppStatusStateWorking if ev.Status == agentapi.StatusStable { - state = codersdk.WorkspaceAppStatusStateComplete + state = codersdk.WorkspaceAppStatusStateIdle } err := s.queue.Push(taskReport{ state: state, diff --git a/cli/exp_mcp_test.go b/cli/exp_mcp_test.go index 08d6fbc4e2ce6..bcfafb0204874 100644 --- a/cli/exp_mcp_test.go +++ b/cli/exp_mcp_test.go @@ -900,7 +900,7 @@ func TestExpMcpReporter(t *testing.T) { { event: makeStatusEvent(agentapi.StatusStable), expected: &codersdk.WorkspaceAppStatus{ - State: codersdk.WorkspaceAppStatusStateComplete, + State: codersdk.WorkspaceAppStatusStateIdle, Message: "doing work", URI: "https://dev.coder.com", }, @@ -948,7 +948,7 @@ func TestExpMcpReporter(t *testing.T) { { event: makeStatusEvent(agentapi.StatusStable), expected: &codersdk.WorkspaceAppStatus{ - State: codersdk.WorkspaceAppStatusStateComplete, + State: codersdk.WorkspaceAppStatusStateIdle, Message: "oops", URI: "", }, diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 1d175333c1271..6844d166d8f1d 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -18081,11 +18081,13 @@ const docTemplate = `{ "type": "string", "enum": [ "working", + "idle", "complete", "failure" ], "x-enum-varnames": [ "WorkspaceAppStatusStateWorking", + "WorkspaceAppStatusStateIdle", "WorkspaceAppStatusStateComplete", "WorkspaceAppStatusStateFailure" ] diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 9d00a7ba34c30..35d39cfbe3839 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -16522,9 +16522,10 @@ }, "codersdk.WorkspaceAppStatusState": { "type": "string", - "enum": ["working", "complete", "failure"], + "enum": ["working", "idle", "complete", "failure"], "x-enum-varnames": [ "WorkspaceAppStatusStateWorking", + "WorkspaceAppStatusStateIdle", "WorkspaceAppStatusStateComplete", "WorkspaceAppStatusStateFailure" ] diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 2a94ef0fe7b4e..17b16abc5715c 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -324,7 +324,8 @@ CREATE TYPE workspace_app_open_in AS ENUM ( CREATE TYPE workspace_app_status_state AS ENUM ( 'working', 'complete', - 'failure' + 'failure', + 'idle' ); CREATE TYPE workspace_transition AS ENUM ( diff --git a/coderd/database/migrations/000338_workspace_app_status_idle.down.sql b/coderd/database/migrations/000338_workspace_app_status_idle.down.sql new file mode 100644 index 0000000000000..bc5535cf614be --- /dev/null +++ b/coderd/database/migrations/000338_workspace_app_status_idle.down.sql @@ -0,0 +1 @@ +-- It is not possible to delete enum values. diff --git a/coderd/database/migrations/000338_workspace_app_status_idle.up.sql b/coderd/database/migrations/000338_workspace_app_status_idle.up.sql new file mode 100644 index 0000000000000..1630e3580f45c --- /dev/null +++ b/coderd/database/migrations/000338_workspace_app_status_idle.up.sql @@ -0,0 +1 @@ +ALTER TYPE workspace_app_status_state ADD VALUE IF NOT EXISTS 'idle'; diff --git a/coderd/database/models.go b/coderd/database/models.go index 6a571ffc1d0d4..ce65c4c559d65 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -2628,6 +2628,7 @@ const ( WorkspaceAppStatusStateWorking WorkspaceAppStatusState = "working" WorkspaceAppStatusStateComplete WorkspaceAppStatusState = "complete" WorkspaceAppStatusStateFailure WorkspaceAppStatusState = "failure" + WorkspaceAppStatusStateIdle WorkspaceAppStatusState = "idle" ) func (e *WorkspaceAppStatusState) Scan(src interface{}) error { @@ -2669,7 +2670,8 @@ func (e WorkspaceAppStatusState) Valid() bool { switch e { case WorkspaceAppStatusStateWorking, WorkspaceAppStatusStateComplete, - WorkspaceAppStatusStateFailure: + WorkspaceAppStatusStateFailure, + WorkspaceAppStatusStateIdle: return true } return false @@ -2680,6 +2682,7 @@ func AllWorkspaceAppStatusStateValues() []WorkspaceAppStatusState { WorkspaceAppStatusStateWorking, WorkspaceAppStatusStateComplete, WorkspaceAppStatusStateFailure, + WorkspaceAppStatusStateIdle, } } diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index ed3f554a89b75..8282eb9e7d01f 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -359,7 +359,10 @@ func (api *API) patchWorkspaceAgentAppStatus(rw http.ResponseWriter, r *http.Req } switch req.State { - case codersdk.WorkspaceAppStatusStateComplete, codersdk.WorkspaceAppStatusStateFailure, codersdk.WorkspaceAppStatusStateWorking: // valid states + case codersdk.WorkspaceAppStatusStateComplete, + codersdk.WorkspaceAppStatusStateFailure, + codersdk.WorkspaceAppStatusStateWorking, + codersdk.WorkspaceAppStatusStateIdle: // valid states default: httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid state provided.", diff --git a/codersdk/toolsdk/toolsdk.go b/codersdk/toolsdk/toolsdk.go index bb1649efa1993..3b992124005ac 100644 --- a/codersdk/toolsdk/toolsdk.go +++ b/codersdk/toolsdk/toolsdk.go @@ -191,7 +191,7 @@ Bad Tasks Use the "state" field to indicate your progress. Periodically report progress with state "working" to keep the user updated. It is not possible to send too many updates! -ONLY report a "complete" or "failure" state if you have FULLY completed the task. +ONLY report an "idle" or "failure" state if you have FULLY completed the task. `, Schema: aisdk.Schema{ Properties: map[string]any{ @@ -205,10 +205,10 @@ ONLY report a "complete" or "failure" state if you have FULLY completed the task }, "state": map[string]any{ "type": "string", - "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.", + "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.", "enum": []string{ string(codersdk.WorkspaceAppStatusStateWorking), - string(codersdk.WorkspaceAppStatusStateComplete), + string(codersdk.WorkspaceAppStatusStateIdle), string(codersdk.WorkspaceAppStatusStateFailure), }, }, diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index 556b3adb27b2e..6e95377bbaf42 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -19,6 +19,7 @@ type WorkspaceAppStatusState string const ( WorkspaceAppStatusStateWorking WorkspaceAppStatusState = "working" + WorkspaceAppStatusStateIdle WorkspaceAppStatusState = "idle" WorkspaceAppStatusStateComplete WorkspaceAppStatusState = "complete" WorkspaceAppStatusStateFailure WorkspaceAppStatusState = "failure" ) diff --git a/docs/reference/api/builds.md b/docs/reference/api/builds.md index 2a0e4b2ede1a0..c47b89d0bbba1 100644 --- a/docs/reference/api/builds.md +++ b/docs/reference/api/builds.md @@ -933,6 +933,7 @@ Status Code **200** | `sharing_level` | `organization` | | `sharing_level` | `public` | | `state` | `working` | +| `state` | `idle` | | `state` | `complete` | | `state` | `failure` | | `lifecycle_state` | `created` | @@ -1695,6 +1696,7 @@ Status Code **200** | `sharing_level` | `organization` | | `sharing_level` | `public` | | `state` | `working` | +| `state` | `idle` | | `state` | `complete` | | `state` | `failure` | | `lifecycle_state` | `created` | diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 8f548478e27a6..e0999f6bb3778 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -9686,6 +9686,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| | Value | |------------| | `working` | +| `idle` | | `complete` | | `failure` | diff --git a/docs/reference/api/templates.md b/docs/reference/api/templates.md index d695be4122951..85e865d8b4b37 100644 --- a/docs/reference/api/templates.md +++ b/docs/reference/api/templates.md @@ -2557,6 +2557,7 @@ Status Code **200** | `sharing_level` | `organization` | | `sharing_level` | `public` | | `state` | `working` | +| `state` | `idle` | | `state` | `complete` | | `state` | `failure` | | `lifecycle_state` | `created` | @@ -3233,6 +3234,7 @@ Status Code **200** | `sharing_level` | `organization` | | `sharing_level` | `public` | | `state` | `working` | +| `state` | `idle` | | `state` | `complete` | | `state` | `failure` | | `lifecycle_state` | `created` | diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 98338c24bb2d8..d536ac3a0fe5e 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -3622,11 +3622,16 @@ export interface WorkspaceAppStatus { } // From codersdk/workspaceapps.go -export type WorkspaceAppStatusState = "complete" | "failure" | "working"; +export type WorkspaceAppStatusState = + | "complete" + | "failure" + | "idle" + | "working"; export const WorkspaceAppStatusStates: WorkspaceAppStatusState[] = [ "complete", "failure", + "idle", "working", ]; diff --git a/site/src/modules/apps/AppStatusStateIcon.tsx b/site/src/modules/apps/AppStatusStateIcon.tsx index 829a8288235de..f713f49ed24b0 100644 --- a/site/src/modules/apps/AppStatusStateIcon.tsx +++ b/site/src/modules/apps/AppStatusStateIcon.tsx @@ -5,6 +5,7 @@ import { CircleAlertIcon, CircleCheckIcon, HourglassIcon, + SquareIcon, TriangleAlertIcon, } from "lucide-react"; import type { FC } from "react"; @@ -26,6 +27,10 @@ export const AppStatusStateIcon: FC = ({ const className = cn(["size-4 shrink-0", customClassName]); switch (state) { + case "idle": + return ( + + ); case "complete": return ( diff --git a/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.stories.tsx b/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.stories.tsx index d95444e658d64..43910b10e017a 100644 --- a/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.stories.tsx +++ b/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.stories.tsx @@ -39,6 +39,16 @@ export const Working: Story = { }, }; +export const Idle: Story = { + args: { + status: { + ...MockWorkspaceAppStatus, + state: "idle", + message: "Done for now", + }, + }, +}; + export const LongMessage: Story = { args: { status: { diff --git a/site/src/pages/WorkspacePage/AppStatuses.stories.tsx b/site/src/pages/WorkspacePage/AppStatuses.stories.tsx index 90be0f194fef3..0bdc94e1f7d56 100644 --- a/site/src/pages/WorkspacePage/AppStatuses.stories.tsx +++ b/site/src/pages/WorkspacePage/AppStatuses.stories.tsx @@ -48,6 +48,22 @@ export const WorkingState: Story = { }, }; +export const IdleState: Story = { + args: { + agent: mockAgent([ + { + ...MockWorkspaceAppStatus, + id: "status-8", + icon: "", + message: "Done for now", + created_at: createTimestamp(5, 20), + uri: "", + state: "idle" as const, + }, + ...MockWorkspaceAppStatuses, + ]), + }, +}; export const LongStatusText: Story = { args: { agent: mockAgent([ From aba81298efc003534533131f4f5da35a92aba531 Mon Sep 17 00:00:00 2001 From: Asher Date: Tue, 17 Jun 2025 12:13:57 -0800 Subject: [PATCH 2/3] chore: show status when message is blank The status row looks janky with just the icon. We can get into a "no message" state if the LLM never reported a status update. --- .../WorkspaceAppStatus.stories.tsx | 10 ++++++++++ .../WorkspaceAppStatus/WorkspaceAppStatus.tsx | 6 ++++-- .../WorkspacePage/AppStatuses.stories.tsx | 18 ++++++++++++++++++ site/src/pages/WorkspacePage/AppStatuses.tsx | 5 +++-- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.stories.tsx b/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.stories.tsx index 43910b10e017a..0e229467b994b 100644 --- a/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.stories.tsx +++ b/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.stories.tsx @@ -49,6 +49,16 @@ export const Idle: Story = { }, }; +export const NoMessage: Story = { + args: { + status: { + ...MockWorkspaceAppStatus, + state: "idle", + message: "", + }, + }, +}; + export const LongMessage: Story = { args: { status: { diff --git a/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.tsx b/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.tsx index 0b999f54402a8..587ae9f5b062f 100644 --- a/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.tsx +++ b/site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.tsx @@ -5,6 +5,7 @@ import { TooltipProvider, TooltipTrigger, } from "components/Tooltip/Tooltip"; +import capitalize from "lodash/capitalize"; import { AppStatusStateIcon } from "modules/apps/AppStatusStateIcon"; import { cn } from "utils/cn"; @@ -25,6 +26,7 @@ export const WorkspaceAppStatus = ({ ); } + const message = status.message || capitalize(status.state); return (
@@ -40,11 +42,11 @@ export const WorkspaceAppStatus = ({ })} /> - {status.message} + {message}
- {status.message} + {message} diff --git a/site/src/pages/WorkspacePage/AppStatuses.stories.tsx b/site/src/pages/WorkspacePage/AppStatuses.stories.tsx index 0bdc94e1f7d56..c7ec5eb56f417 100644 --- a/site/src/pages/WorkspacePage/AppStatuses.stories.tsx +++ b/site/src/pages/WorkspacePage/AppStatuses.stories.tsx @@ -64,6 +64,24 @@ export const IdleState: Story = { ]), }, }; + +export const NoMessage: Story = { + args: { + agent: mockAgent([ + { + ...MockWorkspaceAppStatus, + id: "status-8", + icon: "", + message: "", + created_at: createTimestamp(5, 20), + uri: "", + state: "idle" as const, + }, + ...MockWorkspaceAppStatuses, + ]), + }, +}; + export const LongStatusText: Story = { args: { agent: mockAgent([ diff --git a/site/src/pages/WorkspacePage/AppStatuses.tsx b/site/src/pages/WorkspacePage/AppStatuses.tsx index 35d4db46c3ac9..95e3f9c95a472 100644 --- a/site/src/pages/WorkspacePage/AppStatuses.tsx +++ b/site/src/pages/WorkspacePage/AppStatuses.tsx @@ -12,6 +12,7 @@ import { TooltipProvider, TooltipTrigger, } from "components/Tooltip/Tooltip"; +import capitalize from "lodash/capitalize"; import { timeFrom } from "utils/time"; import { @@ -77,7 +78,7 @@ export const AppStatuses: FC = ({
- {latestStatus.message} + {latestStatus.message || capitalize(latestStatus.state)}
@@ -160,7 +161,7 @@ export const AppStatuses: FC = ({ latest={false} className="size-icon-xs w-[18px]" /> - {status.message} + {status.message || capitalize(status.state)} {formattedTimestamp} From b64af42bc8bc693693b4a0a39ee94cf4f33dbcb0 Mon Sep 17 00:00:00 2001 From: Asher Date: Fri, 20 Jun 2025 13:54:06 -0800 Subject: [PATCH 3/3] Add down migration Also bump migration number since new ones have merged since. --- .../000338_workspace_app_status_idle.down.sql | 1 - .../000340_workspace_app_status_idle.down.sql | 15 +++++++++++++++ ...ql => 000340_workspace_app_status_idle.up.sql} | 0 3 files changed, 15 insertions(+), 1 deletion(-) delete mode 100644 coderd/database/migrations/000338_workspace_app_status_idle.down.sql create mode 100644 coderd/database/migrations/000340_workspace_app_status_idle.down.sql rename coderd/database/migrations/{000338_workspace_app_status_idle.up.sql => 000340_workspace_app_status_idle.up.sql} (100%) diff --git a/coderd/database/migrations/000338_workspace_app_status_idle.down.sql b/coderd/database/migrations/000338_workspace_app_status_idle.down.sql deleted file mode 100644 index bc5535cf614be..0000000000000 --- a/coderd/database/migrations/000338_workspace_app_status_idle.down.sql +++ /dev/null @@ -1 +0,0 @@ --- It is not possible to delete enum values. diff --git a/coderd/database/migrations/000340_workspace_app_status_idle.down.sql b/coderd/database/migrations/000340_workspace_app_status_idle.down.sql new file mode 100644 index 0000000000000..a5d2095b1cd4a --- /dev/null +++ b/coderd/database/migrations/000340_workspace_app_status_idle.down.sql @@ -0,0 +1,15 @@ +-- It is not possible to delete a value from an enum, so we have to recreate it. +CREATE TYPE old_workspace_app_status_state AS ENUM ('working', 'complete', 'failure'); + +-- Convert the new "idle" state into "complete". This means we lose some +-- information when downgrading, but this is necessary to swap to the old enum. +UPDATE workspace_app_statuses SET state = 'complete' WHERE state = 'idle'; + +-- Swap to the old enum. +ALTER TABLE workspace_app_statuses +ALTER COLUMN state TYPE old_workspace_app_status_state +USING (state::text::old_workspace_app_status_state); + +-- Drop the new enum and rename the old one to the final name. +DROP TYPE workspace_app_status_state; +ALTER TYPE old_workspace_app_status_state RENAME TO workspace_app_status_state; diff --git a/coderd/database/migrations/000338_workspace_app_status_idle.up.sql b/coderd/database/migrations/000340_workspace_app_status_idle.up.sql similarity index 100% rename from coderd/database/migrations/000338_workspace_app_status_idle.up.sql rename to coderd/database/migrations/000340_workspace_app_status_idle.up.sql pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy