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: add display name field for tasks#20856

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
ssncferreira merged 15 commits intomainfromssncferreira/feat-task-display-name
Nov 25, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
15 commits
Select commitHold shift + click to select a range
9ecbe62
feat: add display name field for tasks
ssncferreiraNov 21, 2025
39d3805
chore: improve comments
ssncferreiraNov 24, 2025
69b2e9d
Merge remote-tracking branch 'origin/main' into ssncferreira/feat-tas…
ssncferreiraNov 24, 2025
ac0a1f9
chore: update system prompt to generate task display name first
ssncferreiraNov 24, 2025
75277dc
feat: allow optional display_name in task creation request
ssncferreiraNov 24, 2025
28023b7
chore: improve generateFromPrompt regex
ssncferreiraNov 24, 2025
4a6db77
Merge remote-tracking branch 'origin/main' into ssncferreira/feat-tas…
ssncferreiraNov 24, 2025
b5176c6
chore: add logging to task name generation fallback logic
ssncferreiraNov 24, 2025
4f878bd
Merge remote-tracking branch 'origin/main' into ssncferreira/feat-tas…
ssncferreiraNov 24, 2025
40c5ef3
chore: add comments to regex operations
ssncferreiraNov 24, 2025
44e6868
chore: backfill display name to initial prompt
ssncferreiraNov 25, 2025
144e196
Merge remote-tracking branch 'origin/main' into ssncferreira/feat-tas…
ssncferreiraNov 25, 2025
da340dd
chore: fix make gen
ssncferreiraNov 25, 2025
7e4f048
Merge remote-tracking branch 'origin/main' into ssncferreira/feat-tas…
ssncferreiraNov 25, 2025
ab81b40
fix: add display name to TaskTable
ssncferreiraNov 25, 2025
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
8 changes: 5 additions & 3 deletionscli/exp_task_status_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -189,6 +189,7 @@ func Test_TaskStatus(t *testing.T) {
"owner_id": "00000000-0000-0000-0000-000000000000",
"owner_name": "me",
"name": "exists",
"display_name": "Task exists",
"template_id": "00000000-0000-0000-0000-000000000000",
"template_version_id": "00000000-0000-0000-0000-000000000000",
"template_name": "",
Expand DownExpand Up@@ -220,9 +221,10 @@ func Test_TaskStatus(t *testing.T) {
switch r.URL.Path {
case "/api/experimental/tasks/me/exists":
httpapi.Write(ctx, w, http.StatusOK, codersdk.Task{
ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"),
Name: "exists",
OwnerName: "me",
ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"),
Name: "exists",
DisplayName: "Task exists",
OwnerName: "me",
WorkspaceAgentHealth: &codersdk.WorkspaceAgentHealth{
Healthy: true,
},
Expand Down
35 changes: 21 additions & 14 deletionscoderd/aitasks.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,8 +13,9 @@ import (
"github.com/google/uuid"
"golang.org/x/xerrors"

"cdr.dev/slog"
"github.com/coder/coder/v2/coderd/taskname"

aiagentapi "github.com/coder/agentapi-sdk-go"
"github.com/coder/coder/v2/coderd/audit"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbtime"
Expand All@@ -24,12 +25,9 @@ import (
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/rbac/policy"
"github.com/coder/coder/v2/coderd/searchquery"
"github.com/coder/coder/v2/coderd/taskname"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/coder/v2/codersdk"

aiagentapi "github.com/coder/agentapi-sdk-go"
)

// @Summary Create a new AI task
Expand DownExpand Up@@ -111,18 +109,25 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
}
}

if taskName == "" {
taskName = taskname.GenerateFallback()
taskDisplayName := strings.TrimSpace(req.DisplayName)
if taskDisplayName != "" {
if len(taskDisplayName) > 64 {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Display name must be 64 characters or less.",
})
return
}
}

if anthropicAPIKey := taskname.GetAnthropicAPIKeyFromEnv(); anthropicAPIKey != "" {
anthropicModel := taskname.GetAnthropicModelFromEnv()
// Generate task name and display name if either is not provided
if taskName == "" || taskDisplayName == "" {
generatedTaskName := taskname.Generate(ctx, api.Logger, req.Input)

generatedName, err := taskname.Generate(ctx, req.Input, taskname.WithAPIKey(anthropicAPIKey), taskname.WithModel(anthropicModel))
if err != nil {
api.Logger.Error(ctx, "unable to generate task name", slog.Error(err))
} else {
taskName = generatedName
}
if taskName == "" {
taskName = generatedTaskName.Name
}
if taskDisplayName == "" {
taskDisplayName = generatedTaskName.DisplayName
}
}

Expand DownExpand Up@@ -215,6 +220,7 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
OrganizationID: templateVersion.OrganizationID,
OwnerID: owner.ID,
Name: taskName,
DisplayName: taskDisplayName,
WorkspaceID: uuid.NullUUID{}, // Will be set after workspace creation.
TemplateVersionID: templateVersion.ID,
TemplateParameters: []byte("{}"),
Expand DownExpand Up@@ -304,6 +310,7 @@ func taskFromDBTaskAndWorkspace(dbTask database.Task, ws codersdk.Workspace) cod
OwnerName: dbTask.OwnerUsername,
OwnerAvatarURL: dbTask.OwnerAvatarUrl,
Name: dbTask.Name,
DisplayName: dbTask.DisplayName,
TemplateID: ws.TemplateID,
TemplateVersionID: dbTask.TemplateVersionID,
TemplateName: ws.TemplateName,
Expand Down
56 changes: 49 additions & 7 deletionscoderd/aitasks_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1049,14 +1049,17 @@ func TestTasksCreate(t *testing.T) {
t.Parallel()

tests := []struct {
name string
taskName string
expectFallbackName bool
expectError string
name string
taskName string
taskDisplayName string
expectFallbackName bool
expectFallbackDisplayName bool
expectError string
}{
{
name: "ValidName",
taskName: "a-valid-task-name",
name: "ValidName",
taskName: "a-valid-task-name",
expectFallbackDisplayName: true,
},
{
name: "NotValidName",
Expand All@@ -1066,8 +1069,37 @@ func TestTasksCreate(t *testing.T) {
{
name: "NoNameProvided",
taskName: "",
taskDisplayName: "A valid task display name",
expectFallbackName: true,
},
{
name: "ValidDisplayName",
taskDisplayName: "A valid task display name",
expectFallbackName: true,
},
{
name: "NotValidDisplayName",
taskDisplayName: "This is a task display name with a length greater than 64 characters.",
expectError: "Display name must be 64 characters or less.",
},
{
name: "NoDisplayNameProvided",
taskName: "a-valid-task-name",
taskDisplayName: "",
expectFallbackDisplayName: true,
},
{
name: "ValidNameAndDisplayName",
taskName: "a-valid-task-name",
taskDisplayName: "A valid task display name",
},
{
name: "NoNameAndDisplayNameProvided",
taskName: "",
taskDisplayName: "",
expectFallbackName: true,
expectFallbackDisplayName: true,
},
}

for _, tt := range tests {
Expand DownExpand Up@@ -1098,6 +1130,7 @@ func TestTasksCreate(t *testing.T) {
TemplateVersionID: template.ActiveVersionID,
Input: "Some prompt",
Name: tt.taskName,
DisplayName: tt.taskDisplayName,
})
if tt.expectError == "" {
require.NoError(t, err)
Expand All@@ -1111,8 +1144,17 @@ func TestTasksCreate(t *testing.T) {
if !tt.expectFallbackName {
require.Equal(t, tt.taskName, task.Name)
}

// Then: We expect the correct display name to have been picked.
require.NotEmpty(t, task.DisplayName)
if !tt.expectFallbackDisplayName {
require.Equal(t, tt.taskDisplayName, task.DisplayName)
}
} else {
require.ErrorContains(t, err, tt.expectError)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
require.Equal(t, apiErr.Message, tt.expectError)
}
})
}
Expand Down
6 changes: 6 additions & 0 deletionscoderd/apidoc/docs.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

6 changes: 6 additions & 0 deletionscoderd/apidoc/swagger.json
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

6 changes: 5 additions & 1 deletioncoderd/database/dbgen/dbgen.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,8 @@ import (
"testing"
"time"

"cdr.dev/slog"

"github.com/google/uuid"
"github.com/sqlc-dev/pqtype"
"github.com/stretchr/testify/require"
Expand DownExpand Up@@ -1582,11 +1584,13 @@ func Task(t testing.TB, db database.Store, orig database.TaskTable) database.Tas
parameters = json.RawMessage([]byte("{}"))
}

taskName := taskname.Generate(genCtx, slog.Make(), orig.Prompt)
task, err := db.InsertTask(genCtx, database.InsertTaskParams{
ID: takeFirst(orig.ID, uuid.New()),
OrganizationID: orig.OrganizationID,
OwnerID: orig.OwnerID,
Name: takeFirst(orig.Name, taskname.GenerateFallback()),
Name: takeFirst(orig.Name, taskName.Name),
DisplayName: takeFirst(orig.DisplayName, taskName.DisplayName),
WorkspaceID: orig.WorkspaceID,
TemplateVersionID: orig.TemplateVersionID,
TemplateParameters: parameters,
Expand Down
6 changes: 5 additions & 1 deletioncoderd/database/dump.sql
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
-- Drop view first before removing the display_name column from tasks
DROP VIEW IF EXISTS tasks_with_status;

-- Remove display_name column from tasks
ALTER TABLE tasks DROP COLUMN display_name;

-- Recreate view without the display_name column.
-- This restores the view to its previous state after removing display_name from tasks.
CREATE VIEW
Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

It seems adding the view recreation is required on both migrations, because otherwise sqlc would not generate the Task structure withDisplayName on up. AFAIU, since our queries all usetasks_with_status view and not thetasks table, PostgreSQL doesn't automatically update the view definition. That is why we need to force PostgreSQL to re-expand thetasks.* wildcard and pick up the new column. Let me know if there is a cleaner way of doing this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Unfortunately this is the way ™️

ssncferreira reacted with confused emoji
Copy link
Member

Choose a reason for hiding this comment

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

We need to get this implemented in our sqlc fork:Emyrk/sqlc#1

ssncferreira reacted with eyes emoji
tasks_with_status
AS
SELECT
tasks.*,
CASE
WHEN tasks.workspace_id IS NULL OR latest_build.job_status IS NULL THEN 'pending'::task_status

WHEN latest_build.job_status = 'failed' THEN 'error'::task_status

WHEN latest_build.transition IN ('stop', 'delete')
AND latest_build.job_status = 'succeeded' THEN 'paused'::task_status

WHEN latest_build.transition = 'start'
AND latest_build.job_status = 'pending' THEN 'initializing'::task_status

WHEN latest_build.transition = 'start' AND latest_build.job_status IN ('running', 'succeeded') THEN
CASE
WHEN agent_status.none THEN 'initializing'::task_status
WHEN agent_status.connecting THEN 'initializing'::task_status
WHEN agent_status.connected THEN
CASE
WHEN app_status.any_unhealthy THEN 'error'::task_status
WHEN app_status.any_initializing THEN 'initializing'::task_status
WHEN app_status.all_healthy_or_disabled THEN 'active'::task_status
ELSE 'unknown'::task_status
END
ELSE 'unknown'::task_status
END

ELSE 'unknown'::task_status
END AS status,
task_app.*,
task_owner.*
FROM
tasks
CROSS JOIN LATERAL (
SELECT
vu.username AS owner_username,
vu.name AS owner_name,
vu.avatar_url AS owner_avatar_url
FROM visible_users vu
WHERE vu.id = tasks.owner_id
) task_owner
LEFT JOIN LATERAL (
SELECT workspace_build_number, workspace_agent_id, workspace_app_id
FROM task_workspace_apps task_app
WHERE task_id = tasks.id
ORDER BY workspace_build_number DESC
LIMIT 1
) task_app ON TRUE
LEFT JOIN LATERAL (
SELECT
workspace_build.transition,
provisioner_job.job_status,
workspace_build.job_id
FROM workspace_builds workspace_build
JOIN provisioner_jobs provisioner_job ON provisioner_job.id = workspace_build.job_id
WHERE workspace_build.workspace_id = tasks.workspace_id
AND workspace_build.build_number = task_app.workspace_build_number
) latest_build ON TRUE
CROSS JOIN LATERAL (
SELECT
COUNT(*) = 0 AS none,
bool_or(workspace_agent.lifecycle_state IN ('created', 'starting')) AS connecting,
bool_and(workspace_agent.lifecycle_state = 'ready') AS connected
FROM workspace_agents workspace_agent
WHERE workspace_agent.id = task_app.workspace_agent_id
) agent_status
CROSS JOIN LATERAL (
SELECT
bool_or(workspace_app.health = 'unhealthy') AS any_unhealthy,
bool_or(workspace_app.health = 'initializing') AS any_initializing,
bool_and(workspace_app.health IN ('healthy', 'disabled')) AS all_healthy_or_disabled
FROM workspace_apps workspace_app
WHERE workspace_app.id = task_app.workspace_app_id
) app_status
WHERE
tasks.deleted_at IS NULL;
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp