From 5b9d6ae3fc2922021254c3f6e0efb49049fe5af8 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 17 Sep 2025 11:47:18 +0100 Subject: [PATCH 01/11] fix(pr/shared): add `DisableProgress` field to `FindOptions` Signed-off-by: Babak K. Shandiz --- pkg/cmd/pr/shared/finder.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/pr/shared/finder.go b/pkg/cmd/pr/shared/finder.go index 7d66d60f3..2861627a9 100644 --- a/pkg/cmd/pr/shared/finder.go +++ b/pkg/cmd/pr/shared/finder.go @@ -103,6 +103,8 @@ type FindOptions struct { // States lists the possible PR states to scope the PR-for-branch lookup to. States []string + DisableProgress bool + Detector fd.Detector } @@ -197,7 +199,7 @@ func (f *finder) Find(opts FindOptions) (*api.PullRequest, ghrepo.Interface, err } // TODO(josebalius): Should we be guarding here? - if f.progress != nil { + if !opts.DisableProgress && f.progress != nil { f.progress.StartProgressIndicator() defer f.progress.StopProgressIndicator() } From 1f5cbc5dff886b2e9d861ad55b553a009d29a4e1 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 17 Sep 2025 11:47:49 +0100 Subject: [PATCH 02/11] test(pr/shared): assert `FindOptions.DisableProgress` is respected Signed-off-by: Babak K. Shandiz --- pkg/cmd/pr/shared/finder_test.go | 93 ++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 9 deletions(-) diff --git a/pkg/cmd/pr/shared/finder_test.go b/pkg/cmd/pr/shared/finder_test.go index 470709480..3f9ba6f50 100644 --- a/pkg/cmd/pr/shared/finder_test.go +++ b/pkg/cmd/pr/shared/finder_test.go @@ -190,9 +190,11 @@ type args struct { baseRepoFn func() (ghrepo.Interface, error) branchFn func() (string, error) gitConfigClient stubGitConfigClient + progress *stubProgressIndicator selector string fields []string baseBranch string + disableProgress bool } func TestFind(t *testing.T) { @@ -228,12 +230,13 @@ func TestFind(t *testing.T) { } tests := []struct { - name string - args args - httpStub func(*httpmock.Registry) - wantPR int - wantRepo string - wantErr bool + name string + args args + httpStub func(*httpmock.Registry) + wantUseProgress bool + wantPR int + wantRepo string + wantErr bool }{ { name: "number argument", @@ -824,6 +827,51 @@ func TestFind(t *testing.T) { wantPR: 13, wantRepo: "https://github.com/OWNER/REPO", }, + { + name: "number argument, with non nil-progress indicator", + args: args{ + selector: "13", + fields: []string{"id", "number"}, + baseRepoFn: stubBaseRepoFn(ghrepo.New("ORIGINOWNER", "REPO"), nil), + branchFn: func() (string, error) { + return "blueberries", nil + }, + progress: &stubProgressIndicator{}, + }, + httpStub: func(r *httpmock.Registry) { + r.Register( + httpmock.GraphQL(`query PullRequestByNumber\b`), + httpmock.StringResponse(`{"data":{"repository":{ + "pullRequest":{"number":13} + }}}`)) + }, + wantPR: 13, + wantRepo: "https://github.com/ORIGINOWNER/REPO", + wantUseProgress: true, + }, + { + name: "number argument, with non-nil progress indicator and DisableProgress set", + args: args{ + selector: "13", + fields: []string{"id", "number"}, + baseRepoFn: stubBaseRepoFn(ghrepo.New("ORIGINOWNER", "REPO"), nil), + branchFn: func() (string, error) { + return "blueberries", nil + }, + progress: &stubProgressIndicator{}, + disableProgress: true, + }, + httpStub: func(r *httpmock.Registry) { + r.Register( + httpmock.GraphQL(`query PullRequestByNumber\b`), + httpmock.StringResponse(`{"data":{"repository":{ + "pullRequest":{"number":13} + }}}`)) + }, + wantPR: 13, + wantRepo: "https://github.com/ORIGINOWNER/REPO", + wantUseProgress: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -847,11 +895,25 @@ func TestFind(t *testing.T) { }, nil), } + if tt.args.progress != nil { + f.progress = tt.args.progress + } + pr, repo, err := f.Find(FindOptions{ - Selector: tt.args.selector, - Fields: tt.args.fields, - BaseBranch: tt.args.baseBranch, + Selector: tt.args.selector, + Fields: tt.args.fields, + BaseBranch: tt.args.baseBranch, + DisableProgress: tt.args.disableProgress, }) + + if tt.args.progress != nil { + if tt.args.progress.startCalled != tt.wantUseProgress { + t.Errorf("progress was (not) used as expected") + } else if tt.args.progress.startCalled != tt.args.progress.stopCalled { + t.Errorf("progress was started but not stopped") + } + } + if (err != nil) != tt.wantErr { t.Errorf("Find() error = %v, wantErr %v", err, tt.wantErr) return @@ -947,3 +1009,16 @@ func (s stubGitConfigClient) PushRevision(ctx context.Context, branchName string } return s.pushRevisionFn(ctx, branchName) } + +type stubProgressIndicator struct { + startCalled bool + stopCalled bool +} + +func (s *stubProgressIndicator) StartProgressIndicator() { + s.startCalled = true +} + +func (s *stubProgressIndicator) StopProgressIndicator() { + s.stopCalled = true +} From 897c2b12b4ce421056b22733121560df46f69709 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 17 Sep 2025 11:50:08 +0100 Subject: [PATCH 03/11] fix(agent-task view): disable PR finder progress indicator Signed-off-by: Babak K. Shandiz --- pkg/cmd/agent-task/view/view.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/agent-task/view/view.go b/pkg/cmd/agent-task/view/view.go index 832c20806..3ac19e5ff 100644 --- a/pkg/cmd/agent-task/view/view.go +++ b/pkg/cmd/agent-task/view/view.go @@ -200,8 +200,9 @@ func viewRun(opts *ViewOptions) error { if prID == 0 { findOptions := prShared.FindOptions{ - Selector: opts.SelectorArg, - Fields: []string{"id", "url", "fullDatabaseId"}, + Selector: opts.SelectorArg, + Fields: []string{"id", "url", "fullDatabaseId"}, + DisableProgress: true, } pr, repo, err := opts.Finder.Find(findOptions) From 87b310a8812b469640c3b4a64ba72c4095cb93d8 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 17 Sep 2025 11:58:04 +0100 Subject: [PATCH 04/11] fix(agent-task view): display completed sessions as "Ready for review" Signed-off-by: Babak K. Shandiz --- pkg/cmd/agent-task/shared/display.go | 2 +- pkg/cmd/agent-task/view/view_test.go | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/cmd/agent-task/shared/display.go b/pkg/cmd/agent-task/shared/display.go index e841f4c41..76559b1bf 100644 --- a/pkg/cmd/agent-task/shared/display.go +++ b/pkg/cmd/agent-task/shared/display.go @@ -32,7 +32,7 @@ func SessionStateString(state string) string { case "in_progress": return "In Progress" case "completed": - return "Completed" + return "Ready for review" case "failed": return "Failed" case "idle": diff --git a/pkg/cmd/agent-task/view/view_test.go b/pkg/cmd/agent-task/view/view_test.go index 1d4a8b2cc..905b4be59 100644 --- a/pkg/cmd/agent-task/view/view_test.go +++ b/pkg/cmd/agent-task/view/view_test.go @@ -230,7 +230,7 @@ func Test_viewRun(t *testing.T) { } }, wantOut: heredoc.Doc(` - Completed • fix something • OWNER/REPO#101 + Ready for review • fix something • OWNER/REPO#101 Started on behalf of octocat about 6 hours ago For detailed session logs, try: @@ -267,7 +267,7 @@ func Test_viewRun(t *testing.T) { } }, wantOut: heredoc.Doc(` - Completed • fix something • OWNER/REPO#101 + Ready for review • fix something • OWNER/REPO#101 Started about 6 hours ago For detailed session logs, try: @@ -299,7 +299,7 @@ func Test_viewRun(t *testing.T) { } }, wantOut: heredoc.Doc(` - Completed + Ready for review Started on behalf of octocat about 6 hours ago For detailed session logs, try: @@ -325,7 +325,7 @@ func Test_viewRun(t *testing.T) { } }, wantOut: heredoc.Doc(` - Completed + Ready for review Started about 6 hours ago For detailed session logs, try: @@ -507,7 +507,7 @@ func Test_viewRun(t *testing.T) { } }, wantOut: heredoc.Doc(` - Completed • fix something • OWNER/REPO#101 + Ready for review • fix something • OWNER/REPO#101 Started on behalf of octocat about 6 hours ago For detailed session logs, try: @@ -587,7 +587,7 @@ func Test_viewRun(t *testing.T) { ) }, wantOut: heredoc.Doc(` - Completed • fix something • OWNER/REPO#101 + Ready for review • fix something • OWNER/REPO#101 Started on behalf of octocat about 6 hours ago For detailed session logs, try: @@ -669,7 +669,7 @@ func Test_viewRun(t *testing.T) { ) }, wantOut: heredoc.Doc(` - Completed • fix something • OWNER/REPO#101 + Ready for review • fix something • OWNER/REPO#101 Started on behalf of octocat about 6 hours ago For detailed session logs, try: @@ -890,7 +890,7 @@ func Test_viewRun(t *testing.T) { } }, wantOut: heredoc.Doc(` - Completed + Ready for review Started on behalf of octocat about 6 hours ago To follow session logs, try: @@ -947,7 +947,7 @@ func Test_viewRun(t *testing.T) { } }, wantOut: heredoc.Doc(` - Completed + Ready for review Started on behalf of octocat about 6 hours ago (rendered:) From 49ba21a366b4784132d7ee13750c925aff105c3e Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 17 Sep 2025 12:20:40 +0100 Subject: [PATCH 05/11] fix(agent-task view): display session duration Signed-off-by: Babak K. Shandiz --- pkg/cmd/agent-task/view/view.go | 9 ++ pkg/cmd/agent-task/view/view_test.go | 152 ++++++++++++++++----------- 2 files changed, 99 insertions(+), 62 deletions(-) diff --git a/pkg/cmd/agent-task/view/view.go b/pkg/cmd/agent-task/view/view.go index 3ac19e5ff..7d992ed87 100644 --- a/pkg/cmd/agent-task/view/view.go +++ b/pkg/cmd/agent-task/view/view.go @@ -306,6 +306,15 @@ func printSession(opts *ViewOptions, session *capi.Session) { fmt.Fprintf(opts.IO.Out, "Started %s\n", text.FuzzyAgo(time.Now(), session.CreatedAt)) } + var durationNote string + if session.CompletedAt.After(session.CreatedAt) { + durationNote = fmt.Sprintf("Duration %s", session.CompletedAt.Sub(session.CreatedAt).Round(time.Second).String()) + } + + if durationNote != "" { + fmt.Fprintf(opts.IO.Out, "%s\n", cs.Muted(durationNote)) + } + if !opts.Log { fmt.Fprintln(opts.IO.Out, "") fmt.Fprintf(opts.IO.Out, "For detailed session logs, try:\ngh agent-task view '%s' --log\n", session.ID) diff --git a/pkg/cmd/agent-task/view/view_test.go b/pkg/cmd/agent-task/view/view_test.go index 905b4be59..28d63db3e 100644 --- a/pkg/cmd/agent-task/view/view_test.go +++ b/pkg/cmd/agent-task/view/view_test.go @@ -159,6 +159,7 @@ func TestNewCmdList(t *testing.T) { func Test_viewRun(t *testing.T) { sampleDate := time.Now().Add(-6 * time.Hour) // 6h ago + sampleCompletedAt := sampleDate.Add(5 * time.Minute) tests := []struct { name string @@ -212,9 +213,10 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -232,6 +234,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review • fix something • OWNER/REPO#101 Started on behalf of octocat about 6 hours ago + Duration 5m0s For detailed session logs, try: gh agent-task view 'some-session-id' --log @@ -252,9 +255,10 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -269,6 +273,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review • fix something • OWNER/REPO#101 Started about 6 hours ago + Duration 5m0s For detailed session logs, try: gh agent-task view 'some-session-id' --log @@ -289,9 +294,10 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, User: &api.GitHubUser{ Login: "octocat", }, @@ -301,6 +307,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review Started on behalf of octocat about 6 hours ago + Duration 5m0s For detailed session logs, try: gh agent-task view 'some-session-id' --log @@ -318,15 +325,17 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, }, nil } }, wantOut: heredoc.Doc(` Ready for review Started about 6 hours ago + Duration 5m0s For detailed session logs, try: gh agent-task view 'some-session-id' --log @@ -360,9 +369,10 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, // User data is irrelevant in this case }, nil } @@ -382,9 +392,10 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -488,9 +499,10 @@ func Test_viewRun(t *testing.T) { assert.Equal(t, defaultLimit, limit) return []*capi.Session{ { - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -509,6 +521,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review • fix something • OWNER/REPO#101 Started on behalf of octocat about 6 hours ago + Duration 5m0s For detailed session logs, try: gh agent-task view 'some-session-id' --log @@ -538,10 +551,11 @@ func Test_viewRun(t *testing.T) { assert.Equal(t, defaultLimit, limit) return []*capi.Session{ { - ID: "some-session-id", - Name: "session one", - State: "completed", - CreatedAt: sampleDate, + ID: "some-session-id", + Name: "session one", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -555,10 +569,11 @@ func Test_viewRun(t *testing.T) { }, }, { - ID: "some-other-session-id", - Name: "session two", - State: "completed", - CreatedAt: sampleDate, + ID: "some-other-session-id", + Name: "session two", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -589,6 +604,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review • fix something • OWNER/REPO#101 Started on behalf of octocat about 6 hours ago + Duration 5m0s For detailed session logs, try: gh agent-task view 'some-session-id' --log @@ -620,10 +636,11 @@ func Test_viewRun(t *testing.T) { assert.Equal(t, defaultLimit, limit) return []*capi.Session{ { - ID: "some-session-id", - Name: "session one", - State: "completed", - CreatedAt: sampleDate, + ID: "some-session-id", + Name: "session one", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -637,10 +654,11 @@ func Test_viewRun(t *testing.T) { }, }, { - ID: "some-other-session-id", - Name: "session two", - State: "completed", - CreatedAt: sampleDate, + ID: "some-other-session-id", + Name: "session two", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -671,6 +689,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review • fix something • OWNER/REPO#101 Started on behalf of octocat about 6 hours ago + Duration 5m0s For detailed session logs, try: gh agent-task view 'some-session-id' --log @@ -723,9 +742,10 @@ func Test_viewRun(t *testing.T) { assert.Equal(t, defaultLimit, limit) return []*capi.Session{ { - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -764,10 +784,11 @@ func Test_viewRun(t *testing.T) { assert.Equal(t, defaultLimit, limit) return []*capi.Session{ { - ID: "some-session-id", - Name: "session one", - State: "completed", - CreatedAt: sampleDate, + ID: "some-session-id", + Name: "session one", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -779,10 +800,11 @@ func Test_viewRun(t *testing.T) { // User data is irrelevant in this case }, { - ID: "some-other-session-id", - Name: "session two", - State: "completed", - CreatedAt: sampleDate, + ID: "some-other-session-id", + Name: "session two", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -823,10 +845,11 @@ func Test_viewRun(t *testing.T) { assert.Equal(t, defaultLimit, limit) return []*capi.Session{ { - ID: "some-session-id", - Name: "session one", - State: "completed", - CreatedAt: sampleDate, + ID: "some-session-id", + Name: "session one", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -838,10 +861,11 @@ func Test_viewRun(t *testing.T) { // User data is irrelevant in this case }, { - ID: "some-other-session-id", - Name: "session two", - State: "completed", - CreatedAt: sampleDate, + ID: "some-other-session-id", + Name: "session two", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -870,9 +894,10 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, User: &api.GitHubUser{ Login: "octocat", }, @@ -892,6 +917,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review Started on behalf of octocat about 6 hours ago + Duration 5m0s To follow session logs, try: gh agent-task view 'some-session-id' --log --follow @@ -913,9 +939,10 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, User: &api.GitHubUser{ Login: "octocat", }, @@ -949,6 +976,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review Started on behalf of octocat about 6 hours ago + Duration 5m0s (rendered:) (rendered:) From fe95cd86f15897c19858546c6b6561650cc0c311 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 17 Sep 2025 13:27:59 +0100 Subject: [PATCH 06/11] fix(agent-task/capi): add `PremiumRequests` field to `Session` Signed-off-by: Babak K. Shandiz --- pkg/cmd/agent-task/capi/sessions.go | 93 +++--- pkg/cmd/agent-task/capi/sessions_test.go | 350 ++++++++++++----------- 2 files changed, 238 insertions(+), 205 deletions(-) diff --git a/pkg/cmd/agent-task/capi/sessions.go b/pkg/cmd/agent-task/capi/sessions.go index 7f16b4ae5..f4f12254d 100644 --- a/pkg/cmd/agent-task/capi/sessions.go +++ b/pkg/cmd/agent-task/capi/sessions.go @@ -27,21 +27,22 @@ var ErrSessionNotFound = errors.New("not found") // session is an in-flight agent task type session struct { - ID string `json:"id"` - Name string `json:"name"` - UserID int64 `json:"user_id"` - AgentID int64 `json:"agent_id"` - Logs string `json:"logs"` - State string `json:"state"` - OwnerID uint64 `json:"owner_id"` - RepoID uint64 `json:"repo_id"` - ResourceType string `json:"resource_type"` - ResourceID int64 `json:"resource_id"` - LastUpdatedAt time.Time `json:"last_updated_at,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - CompletedAt time.Time `json:"completed_at,omitempty"` - EventURL string `json:"event_url"` - EventType string `json:"event_type"` + ID string `json:"id"` + Name string `json:"name"` + UserID int64 `json:"user_id"` + AgentID int64 `json:"agent_id"` + Logs string `json:"logs"` + State string `json:"state"` + OwnerID uint64 `json:"owner_id"` + RepoID uint64 `json:"repo_id"` + ResourceType string `json:"resource_type"` + ResourceID int64 `json:"resource_id"` + LastUpdatedAt time.Time `json:"last_updated_at,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + CompletedAt time.Time `json:"completed_at,omitempty"` + EventURL string `json:"event_url"` + EventType string `json:"event_type"` + PremiumRequests float64 `json:"premium_requests"` } // A shim of a full pull request because looking up by node ID @@ -66,21 +67,22 @@ type sessionPullRequest struct { // Session is a hydrated in-flight agent task type Session struct { - ID string - Name string - UserID int64 - AgentID int64 - Logs string - State string - OwnerID uint64 - RepoID uint64 - ResourceType string - ResourceID int64 - LastUpdatedAt time.Time - CreatedAt time.Time - CompletedAt time.Time - EventURL string - EventType string + ID string + Name string + UserID int64 + AgentID int64 + Logs string + State string + OwnerID uint64 + RepoID uint64 + ResourceType string + ResourceID int64 + LastUpdatedAt time.Time + CreatedAt time.Time + CompletedAt time.Time + EventURL string + EventType string + PremiumRequests float64 PullRequest *api.PullRequest User *api.GitHubUser @@ -475,20 +477,21 @@ func generateUserNodeID(userID int64) string { func fromAPISession(s session) *Session { return &Session{ - ID: s.ID, - Name: s.Name, - UserID: s.UserID, - AgentID: s.AgentID, - Logs: s.Logs, - State: s.State, - OwnerID: s.OwnerID, - RepoID: s.RepoID, - ResourceType: s.ResourceType, - ResourceID: s.ResourceID, - LastUpdatedAt: s.LastUpdatedAt, - CreatedAt: s.CreatedAt, - CompletedAt: s.CompletedAt, - EventURL: s.EventURL, - EventType: s.EventType, + ID: s.ID, + Name: s.Name, + UserID: s.UserID, + AgentID: s.AgentID, + Logs: s.Logs, + State: s.State, + OwnerID: s.OwnerID, + RepoID: s.RepoID, + ResourceType: s.ResourceType, + ResourceID: s.ResourceID, + LastUpdatedAt: s.LastUpdatedAt, + CreatedAt: s.CreatedAt, + CompletedAt: s.CompletedAt, + EventURL: s.EventURL, + EventType: s.EventType, + PremiumRequests: s.PremiumRequests, } } diff --git a/pkg/cmd/agent-task/capi/sessions_test.go b/pkg/cmd/agent-task/capi/sessions_test.go index e67ed2ab1..45984dfec 100644 --- a/pkg/cmd/agent-task/capi/sessions_test.go +++ b/pkg/cmd/agent-task/capi/sessions_test.go @@ -77,7 +77,8 @@ func TestListSessionsForViewer(t *testing.T) { "repo_id": 1000, "resource_type": "pull", "resource_id": 2000, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 } ] }`, @@ -125,17 +126,18 @@ func TestListSessionsForViewer(t *testing.T) { wantOut: []*Session{ { - ID: "sess1", - Name: "Build artifacts", - UserID: 1, - AgentID: 2, - Logs: "", - State: "completed", - OwnerID: 10, - RepoID: 1000, - ResourceType: "pull", - ResourceID: 2000, - CreatedAt: sampleDate, + ID: "sess1", + Name: "Build artifacts", + UserID: 1, + AgentID: 2, + Logs: "", + State: "completed", + OwnerID: 10, + RepoID: 1000, + ResourceType: "pull", + ResourceID: 2000, + CreatedAt: sampleDate, + PremiumRequests: 0.1, PullRequest: &api.PullRequest{ ID: "PR_node", FullDatabaseID: "2000", @@ -186,7 +188,8 @@ func TestListSessionsForViewer(t *testing.T) { "repo_id": 1000, "resource_type": "", "resource_id": 0, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 } ] }`, @@ -218,17 +221,18 @@ func TestListSessionsForViewer(t *testing.T) { wantOut: []*Session{ { - ID: "sess1", - Name: "Build artifacts", - UserID: 1, - AgentID: 2, - Logs: "", - State: "completed", - OwnerID: 10, - RepoID: 1000, - ResourceType: "", - ResourceID: 0, - CreatedAt: sampleDate, + ID: "sess1", + Name: "Build artifacts", + UserID: 1, + AgentID: 2, + Logs: "", + State: "completed", + OwnerID: 10, + RepoID: 1000, + ResourceType: "", + ResourceID: 0, + CreatedAt: sampleDate, + PremiumRequests: 0.1, User: &api.GitHubUser{ Login: "octocat", Name: "Octocat", @@ -264,7 +268,8 @@ func TestListSessionsForViewer(t *testing.T) { "repo_id": 1000, "resource_type": "pull", "resource_id": 2000, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 } ] }`, @@ -295,7 +300,8 @@ func TestListSessionsForViewer(t *testing.T) { "repo_id": 1000, "resource_type": "pull", "resource_id": 2001, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 } ] }`, @@ -358,17 +364,18 @@ func TestListSessionsForViewer(t *testing.T) { }, wantOut: []*Session{ { - ID: "sess1", - Name: "Build artifacts", - UserID: 1, - AgentID: 2, - Logs: "", - State: "completed", - OwnerID: 10, - RepoID: 1000, - ResourceType: "pull", - ResourceID: 2000, - CreatedAt: sampleDate, + ID: "sess1", + Name: "Build artifacts", + UserID: 1, + AgentID: 2, + Logs: "", + State: "completed", + OwnerID: 10, + RepoID: 1000, + ResourceType: "pull", + ResourceID: 2000, + CreatedAt: sampleDate, + PremiumRequests: 0.1, PullRequest: &api.PullRequest{ ID: "PR_node", FullDatabaseID: "2000", @@ -391,17 +398,18 @@ func TestListSessionsForViewer(t *testing.T) { }, }, { - ID: "sess2", - Name: "Build artifacts", - UserID: 1, - AgentID: 2, - Logs: "", - State: "completed", - OwnerID: 10, - RepoID: 1000, - ResourceType: "pull", - ResourceID: 2001, - CreatedAt: sampleDate, + ID: "sess2", + Name: "Build artifacts", + UserID: 1, + AgentID: 2, + Logs: "", + State: "completed", + OwnerID: 10, + RepoID: 1000, + ResourceType: "pull", + ResourceID: 2001, + CreatedAt: sampleDate, + PremiumRequests: 0.1, PullRequest: &api.PullRequest{ ID: "PR_node", FullDatabaseID: "2001", @@ -467,7 +475,8 @@ func TestListSessionsForViewer(t *testing.T) { "repo_id": 1000, "resource_type": "pull", "resource_id": 2000, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 } ] }`, @@ -591,7 +600,8 @@ func TestListSessionsForRepo(t *testing.T) { "repo_id": 1000, "resource_type": "pull", "resource_id": 2000, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 } ] }`, @@ -638,17 +648,18 @@ func TestListSessionsForRepo(t *testing.T) { }, wantOut: []*Session{ { - ID: "sess1", - Name: "Build artifacts", - UserID: 1, - AgentID: 2, - Logs: "", - State: "completed", - OwnerID: 10, - RepoID: 1000, - ResourceType: "pull", - ResourceID: 2000, - CreatedAt: sampleDate, + ID: "sess1", + Name: "Build artifacts", + UserID: 1, + AgentID: 2, + Logs: "", + State: "completed", + OwnerID: 10, + RepoID: 1000, + ResourceType: "pull", + ResourceID: 2000, + CreatedAt: sampleDate, + PremiumRequests: 0.1, PullRequest: &api.PullRequest{ ID: "PR_node", FullDatabaseID: "2000", @@ -699,7 +710,8 @@ func TestListSessionsForRepo(t *testing.T) { "repo_id": 1000, "resource_type": "", "resource_id": 0, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 } ] }`, @@ -731,17 +743,18 @@ func TestListSessionsForRepo(t *testing.T) { wantOut: []*Session{ { - ID: "sess1", - Name: "Build artifacts", - UserID: 1, - AgentID: 2, - Logs: "", - State: "completed", - OwnerID: 10, - RepoID: 1000, - ResourceType: "", - ResourceID: 0, - CreatedAt: sampleDate, + ID: "sess1", + Name: "Build artifacts", + UserID: 1, + AgentID: 2, + Logs: "", + State: "completed", + OwnerID: 10, + RepoID: 1000, + ResourceType: "", + ResourceID: 0, + CreatedAt: sampleDate, + PremiumRequests: 0.1, User: &api.GitHubUser{ Login: "octocat", Name: "Octocat", @@ -777,7 +790,8 @@ func TestListSessionsForRepo(t *testing.T) { "repo_id": 1000, "resource_type": "pull", "resource_id": 2000, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 } ] }`, @@ -808,7 +822,8 @@ func TestListSessionsForRepo(t *testing.T) { "repo_id": 1000, "resource_type": "pull", "resource_id": 2001, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 } ] }`, @@ -871,17 +886,18 @@ func TestListSessionsForRepo(t *testing.T) { }, wantOut: []*Session{ { - ID: "sess1", - Name: "Build artifacts", - UserID: 1, - AgentID: 2, - Logs: "", - State: "completed", - OwnerID: 10, - RepoID: 1000, - ResourceType: "pull", - ResourceID: 2000, - CreatedAt: sampleDate, + ID: "sess1", + Name: "Build artifacts", + UserID: 1, + AgentID: 2, + Logs: "", + State: "completed", + OwnerID: 10, + RepoID: 1000, + ResourceType: "pull", + ResourceID: 2000, + CreatedAt: sampleDate, + PremiumRequests: 0.1, PullRequest: &api.PullRequest{ ID: "PR_node", FullDatabaseID: "2000", @@ -904,17 +920,18 @@ func TestListSessionsForRepo(t *testing.T) { }, }, { - ID: "sess2", - Name: "Build artifacts", - UserID: 1, - AgentID: 2, - Logs: "", - State: "completed", - OwnerID: 10, - RepoID: 1000, - ResourceType: "pull", - ResourceID: 2001, - CreatedAt: sampleDate, + ID: "sess2", + Name: "Build artifacts", + UserID: 1, + AgentID: 2, + Logs: "", + State: "completed", + OwnerID: 10, + RepoID: 1000, + ResourceType: "pull", + ResourceID: 2001, + CreatedAt: sampleDate, + PremiumRequests: 0.1, PullRequest: &api.PullRequest{ ID: "PR_node", FullDatabaseID: "2001", @@ -980,7 +997,8 @@ func TestListSessionsForRepo(t *testing.T) { "repo_id": 1000, "resource_type": "pull", "resource_id": 2000, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 } ] }`, @@ -1107,7 +1125,8 @@ func TestListSessionsByResourceID(t *testing.T) { "repo_id": 1000, "resource_type": "pull", "resource_id": 2000, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 } ] }`, @@ -1155,17 +1174,18 @@ func TestListSessionsByResourceID(t *testing.T) { wantOut: []*Session{ { - ID: "sess1", - Name: "Build artifacts", - UserID: 1, - AgentID: 2, - Logs: "", - State: "completed", - OwnerID: 10, - RepoID: 1000, - ResourceType: "pull", - ResourceID: 2000, - CreatedAt: sampleDate, + ID: "sess1", + Name: "Build artifacts", + UserID: 1, + AgentID: 2, + Logs: "", + State: "completed", + OwnerID: 10, + RepoID: 1000, + ResourceType: "pull", + ResourceID: 2000, + CreatedAt: sampleDate, + PremiumRequests: 0.1, PullRequest: &api.PullRequest{ ID: "PR_node", FullDatabaseID: "2000", @@ -1216,7 +1236,8 @@ func TestListSessionsByResourceID(t *testing.T) { "repo_id": 1000, "resource_type": "pull", "resource_id": 2000, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 } ] }`, @@ -1247,7 +1268,8 @@ func TestListSessionsByResourceID(t *testing.T) { "repo_id": 1000, "resource_type": "pull", "resource_id": 2001, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 } ] }`, @@ -1310,17 +1332,18 @@ func TestListSessionsByResourceID(t *testing.T) { }, wantOut: []*Session{ { - ID: "sess1", - Name: "Build artifacts", - UserID: 1, - AgentID: 2, - Logs: "", - State: "completed", - OwnerID: 10, - RepoID: 1000, - ResourceType: "pull", - ResourceID: 2000, - CreatedAt: sampleDate, + ID: "sess1", + Name: "Build artifacts", + UserID: 1, + AgentID: 2, + Logs: "", + State: "completed", + OwnerID: 10, + RepoID: 1000, + ResourceType: "pull", + ResourceID: 2000, + CreatedAt: sampleDate, + PremiumRequests: 0.1, PullRequest: &api.PullRequest{ ID: "PR_node", FullDatabaseID: "2000", @@ -1343,17 +1366,18 @@ func TestListSessionsByResourceID(t *testing.T) { }, }, { - ID: "sess2", - Name: "Build artifacts", - UserID: 1, - AgentID: 2, - Logs: "", - State: "completed", - OwnerID: 10, - RepoID: 1000, - ResourceType: "pull", - ResourceID: 2001, - CreatedAt: sampleDate, + ID: "sess2", + Name: "Build artifacts", + UserID: 1, + AgentID: 2, + Logs: "", + State: "completed", + OwnerID: 10, + RepoID: 1000, + ResourceType: "pull", + ResourceID: 2001, + CreatedAt: sampleDate, + PremiumRequests: 0.1, PullRequest: &api.PullRequest{ ID: "PR_node", FullDatabaseID: "2001", @@ -1419,7 +1443,8 @@ func TestListSessionsByResourceID(t *testing.T) { "repo_id": 1000, "resource_type": "pull", "resource_id": 2000, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 } ] }`, @@ -1538,7 +1563,8 @@ func TestGetSession(t *testing.T) { "repo_id": 1000, "resource_type": "pull", "resource_id": 2000, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 }`, sampleDateString, )), @@ -1582,17 +1608,18 @@ func TestGetSession(t *testing.T) { ) }, wantOut: &Session{ - ID: "some-uuid", - Name: "Build artifacts", - UserID: 1, - AgentID: 2, - Logs: "", - State: "completed", - OwnerID: 10, - RepoID: 1000, - ResourceType: "pull", - ResourceID: 2000, - CreatedAt: sampleDate, + ID: "some-uuid", + Name: "Build artifacts", + UserID: 1, + AgentID: 2, + Logs: "", + State: "completed", + OwnerID: 10, + RepoID: 1000, + ResourceType: "pull", + ResourceID: 2000, + CreatedAt: sampleDate, + PremiumRequests: 0.1, PullRequest: &api.PullRequest{ ID: "PR_node", FullDatabaseID: "2000", @@ -1633,7 +1660,8 @@ func TestGetSession(t *testing.T) { "repo_id": 1000, "resource_type": "", "resource_id": 0, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 }`, sampleDateString, )), @@ -1661,17 +1689,18 @@ func TestGetSession(t *testing.T) { ) }, wantOut: &Session{ - ID: "some-uuid", - Name: "Build artifacts", - UserID: 1, - AgentID: 2, - Logs: "", - State: "completed", - OwnerID: 10, - RepoID: 1000, - ResourceType: "", - ResourceID: 0, - CreatedAt: sampleDate, + ID: "some-uuid", + Name: "Build artifacts", + UserID: 1, + AgentID: 2, + Logs: "", + State: "completed", + OwnerID: 10, + RepoID: 1000, + ResourceType: "", + ResourceID: 0, + CreatedAt: sampleDate, + PremiumRequests: 0.1, User: &api.GitHubUser{ Login: "octocat", Name: "Octocat", @@ -1696,7 +1725,8 @@ func TestGetSession(t *testing.T) { "repo_id": 1000, "resource_type": "pull", "resource_id": 2000, - "created_at": "%[1]s" + "created_at": "%[1]s", + "premium_requests": 0.1 }`, sampleDateString, )), From 99a61618df2f1f941a12e73604fd0a167d8895a3 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 17 Sep 2025 13:29:38 +0100 Subject: [PATCH 07/11] fix(agent-task view): display premium requests used Signed-off-by: Babak K. Shandiz --- pkg/cmd/agent-task/view/view.go | 17 ++- pkg/cmd/agent-task/view/view_test.go | 196 +++++++++++++++------------ 2 files changed, 119 insertions(+), 94 deletions(-) diff --git a/pkg/cmd/agent-task/view/view.go b/pkg/cmd/agent-task/view/view.go index 7d992ed87..c7474c12d 100644 --- a/pkg/cmd/agent-task/view/view.go +++ b/pkg/cmd/agent-task/view/view.go @@ -7,6 +7,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "time" "github.com/MakeNowJust/heredoc" @@ -306,13 +307,19 @@ func printSession(opts *ViewOptions, session *capi.Session) { fmt.Fprintf(opts.IO.Out, "Started %s\n", text.FuzzyAgo(time.Now(), session.CreatedAt)) } - var durationNote string - if session.CompletedAt.After(session.CreatedAt) { - durationNote = fmt.Sprintf("Duration %s", session.CompletedAt.Sub(session.CreatedAt).Round(time.Second).String()) + additionalNotes := make([]string, 0, 2) + + if session.PremiumRequests > 0 { + s := strings.TrimSuffix(fmt.Sprintf("%.1f", session.PremiumRequests), ".0") + additionalNotes = append(additionalNotes, fmt.Sprintf("Used %s premium request(s)", s)) } - if durationNote != "" { - fmt.Fprintf(opts.IO.Out, "%s\n", cs.Muted(durationNote)) + if session.CompletedAt.After(session.CreatedAt) { + additionalNotes = append(additionalNotes, fmt.Sprintf("Duration %s", session.CompletedAt.Sub(session.CreatedAt).Round(time.Second).String())) + } + + if len(additionalNotes) > 0 { + fmt.Fprintf(opts.IO.Out, "%s\n", cs.Muted(strings.Join(additionalNotes, " • "))) } if !opts.Log { diff --git a/pkg/cmd/agent-task/view/view_test.go b/pkg/cmd/agent-task/view/view_test.go index 28d63db3e..08ffaa1ba 100644 --- a/pkg/cmd/agent-task/view/view_test.go +++ b/pkg/cmd/agent-task/view/view_test.go @@ -213,10 +213,11 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -234,7 +235,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review • fix something • OWNER/REPO#101 Started on behalf of octocat about 6 hours ago - Duration 5m0s + Used 1.5 premium request(s) • Duration 5m0s For detailed session logs, try: gh agent-task view 'some-session-id' --log @@ -255,10 +256,11 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -273,7 +275,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review • fix something • OWNER/REPO#101 Started about 6 hours ago - Duration 5m0s + Used 1.5 premium request(s) • Duration 5m0s For detailed session logs, try: gh agent-task view 'some-session-id' --log @@ -294,10 +296,11 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, User: &api.GitHubUser{ Login: "octocat", }, @@ -307,7 +310,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review Started on behalf of octocat about 6 hours ago - Duration 5m0s + Used 1.5 premium request(s) • Duration 5m0s For detailed session logs, try: gh agent-task view 'some-session-id' --log @@ -325,17 +328,18 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, }, nil } }, wantOut: heredoc.Doc(` Ready for review Started about 6 hours ago - Duration 5m0s + Used 1.5 premium request(s) • Duration 5m0s For detailed session logs, try: gh agent-task view 'some-session-id' --log @@ -369,10 +373,11 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, // User data is irrelevant in this case }, nil } @@ -392,10 +397,11 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -499,10 +505,11 @@ func Test_viewRun(t *testing.T) { assert.Equal(t, defaultLimit, limit) return []*capi.Session{ { - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -521,7 +528,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review • fix something • OWNER/REPO#101 Started on behalf of octocat about 6 hours ago - Duration 5m0s + Used 1.5 premium request(s) • Duration 5m0s For detailed session logs, try: gh agent-task view 'some-session-id' --log @@ -551,11 +558,12 @@ func Test_viewRun(t *testing.T) { assert.Equal(t, defaultLimit, limit) return []*capi.Session{ { - ID: "some-session-id", - Name: "session one", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-session-id", + Name: "session one", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -569,11 +577,12 @@ func Test_viewRun(t *testing.T) { }, }, { - ID: "some-other-session-id", - Name: "session two", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-other-session-id", + Name: "session two", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -604,7 +613,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review • fix something • OWNER/REPO#101 Started on behalf of octocat about 6 hours ago - Duration 5m0s + Used 1.5 premium request(s) • Duration 5m0s For detailed session logs, try: gh agent-task view 'some-session-id' --log @@ -636,11 +645,12 @@ func Test_viewRun(t *testing.T) { assert.Equal(t, defaultLimit, limit) return []*capi.Session{ { - ID: "some-session-id", - Name: "session one", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-session-id", + Name: "session one", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -654,11 +664,12 @@ func Test_viewRun(t *testing.T) { }, }, { - ID: "some-other-session-id", - Name: "session two", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-other-session-id", + Name: "session two", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -689,7 +700,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review • fix something • OWNER/REPO#101 Started on behalf of octocat about 6 hours ago - Duration 5m0s + Used 1.5 premium request(s) • Duration 5m0s For detailed session logs, try: gh agent-task view 'some-session-id' --log @@ -742,10 +753,11 @@ func Test_viewRun(t *testing.T) { assert.Equal(t, defaultLimit, limit) return []*capi.Session{ { - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -784,11 +796,12 @@ func Test_viewRun(t *testing.T) { assert.Equal(t, defaultLimit, limit) return []*capi.Session{ { - ID: "some-session-id", - Name: "session one", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-session-id", + Name: "session one", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -800,11 +813,12 @@ func Test_viewRun(t *testing.T) { // User data is irrelevant in this case }, { - ID: "some-other-session-id", - Name: "session two", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-other-session-id", + Name: "session two", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -845,11 +859,12 @@ func Test_viewRun(t *testing.T) { assert.Equal(t, defaultLimit, limit) return []*capi.Session{ { - ID: "some-session-id", - Name: "session one", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-session-id", + Name: "session one", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -861,11 +876,12 @@ func Test_viewRun(t *testing.T) { // User data is irrelevant in this case }, { - ID: "some-other-session-id", - Name: "session two", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-other-session-id", + Name: "session two", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, PullRequest: &api.PullRequest{ Title: "fix something", Number: 101, @@ -894,10 +910,11 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, User: &api.GitHubUser{ Login: "octocat", }, @@ -917,7 +934,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review Started on behalf of octocat about 6 hours ago - Duration 5m0s + Used 1.5 premium request(s) • Duration 5m0s To follow session logs, try: gh agent-task view 'some-session-id' --log --follow @@ -939,10 +956,11 @@ func Test_viewRun(t *testing.T) { m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { assert.Equal(t, "some-session-id", id) return &capi.Session{ - ID: "some-session-id", - State: "completed", - CreatedAt: sampleDate, - CompletedAt: sampleCompletedAt, + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 1.5, User: &api.GitHubUser{ Login: "octocat", }, @@ -976,7 +994,7 @@ func Test_viewRun(t *testing.T) { wantOut: heredoc.Doc(` Ready for review Started on behalf of octocat about 6 hours ago - Duration 5m0s + Used 1.5 premium request(s) • Duration 5m0s (rendered:) (rendered:) From 661817de50d1561077ea615c73f27f2620e2f7f8 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 17 Sep 2025 13:59:13 +0100 Subject: [PATCH 08/11] fix(agent-task view): omit session overview in log mode Signed-off-by: Babak K. Shandiz --- pkg/cmd/agent-task/view/view.go | 6 ++---- pkg/cmd/agent-task/view/view_test.go | 11 ----------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/pkg/cmd/agent-task/view/view.go b/pkg/cmd/agent-task/view/view.go index c7474c12d..b751201db 100644 --- a/pkg/cmd/agent-task/view/view.go +++ b/pkg/cmd/agent-task/view/view.go @@ -277,11 +277,11 @@ func viewRun(opts *ViewOptions) error { } } - printSession(opts, session) - if opts.Log { return printLogs(opts, capiClient, session.ID) } + + printSession(opts, session) return nil } @@ -362,7 +362,6 @@ func printLogs(opts *ViewOptions, capiClient capi.CapiClient, sessionID string) return raw, nil } - fmt.Fprintln(opts.IO.Out, "") return renderer.Follow(fetcher, opts.IO.Out, opts.IO) } @@ -371,7 +370,6 @@ func printLogs(opts *ViewOptions, capiClient capi.CapiClient, sessionID string) return fmt.Errorf("failed to fetch session logs: %w", err) } - fmt.Fprintln(opts.IO.Out, "") _, err = renderer.Render(raw, opts.IO.Out, opts.IO) return err } diff --git a/pkg/cmd/agent-task/view/view_test.go b/pkg/cmd/agent-task/view/view_test.go index 08ffaa1ba..d39ac0a41 100644 --- a/pkg/cmd/agent-task/view/view_test.go +++ b/pkg/cmd/agent-task/view/view_test.go @@ -932,13 +932,6 @@ func Test_viewRun(t *testing.T) { } }, wantOut: heredoc.Doc(` - Ready for review - Started on behalf of octocat about 6 hours ago - Used 1.5 premium request(s) • Duration 5m0s - - To follow session logs, try: - gh agent-task view 'some-session-id' --log --follow - (rendered:) `), }, @@ -992,10 +985,6 @@ func Test_viewRun(t *testing.T) { } }, wantOut: heredoc.Doc(` - Ready for review - Started on behalf of octocat about 6 hours ago - Used 1.5 premium request(s) • Duration 5m0s - (rendered:) (rendered:) `), From 6927d642f6b87fe24d6d06af367f421235ec18e1 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 18 Sep 2025 10:16:00 +0100 Subject: [PATCH 09/11] fix(agent-task/shared): make capitalised status names consistent Signed-off-by: Babak K. Shandiz --- pkg/cmd/agent-task/shared/display.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/agent-task/shared/display.go b/pkg/cmd/agent-task/shared/display.go index 76559b1bf..99ae91c43 100644 --- a/pkg/cmd/agent-task/shared/display.go +++ b/pkg/cmd/agent-task/shared/display.go @@ -30,7 +30,7 @@ func SessionStateString(state string) string { case "queued": return "Queued" case "in_progress": - return "In Progress" + return "In progress" case "completed": return "Ready for review" case "failed": @@ -38,9 +38,9 @@ func SessionStateString(state string) string { case "idle": return "Idle" case "waiting_for_user": - return "Waiting for User" + return "Waiting for user" case "timed_out": - return "Timed Out" + return "Timed out" case "cancelled": return "Cancelled" default: From f0a0c4b85686b7973ddb10d4fc9fa61401de56ca Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 18 Sep 2025 10:18:40 +0100 Subject: [PATCH 10/11] fix(agent-task view): display zero premium requests Signed-off-by: Babak K. Shandiz --- pkg/cmd/agent-task/view/view.go | 15 ++--- pkg/cmd/agent-task/view/view_test.go | 83 ++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 10 deletions(-) diff --git a/pkg/cmd/agent-task/view/view.go b/pkg/cmd/agent-task/view/view.go index b751201db..2ff09386a 100644 --- a/pkg/cmd/agent-task/view/view.go +++ b/pkg/cmd/agent-task/view/view.go @@ -307,20 +307,15 @@ func printSession(opts *ViewOptions, session *capi.Session) { fmt.Fprintf(opts.IO.Out, "Started %s\n", text.FuzzyAgo(time.Now(), session.CreatedAt)) } - additionalNotes := make([]string, 0, 2) - - if session.PremiumRequests > 0 { - s := strings.TrimSuffix(fmt.Sprintf("%.1f", session.PremiumRequests), ".0") - additionalNotes = append(additionalNotes, fmt.Sprintf("Used %s premium request(s)", s)) - } + usedPremiumRequests := strings.TrimSuffix(fmt.Sprintf("%.1f", session.PremiumRequests), ".0") + usedPremiumRequestsNote := fmt.Sprintf("Used %s premium request(s)", usedPremiumRequests) + var durationNote string if session.CompletedAt.After(session.CreatedAt) { - additionalNotes = append(additionalNotes, fmt.Sprintf("Duration %s", session.CompletedAt.Sub(session.CreatedAt).Round(time.Second).String())) + durationNote = fmt.Sprintf(" • Duration %s", session.CompletedAt.Sub(session.CreatedAt).Round(time.Second).String()) } - if len(additionalNotes) > 0 { - fmt.Fprintf(opts.IO.Out, "%s\n", cs.Muted(strings.Join(additionalNotes, " • "))) - } + fmt.Fprintf(opts.IO.Out, "%s%s\n", cs.Muted(usedPremiumRequestsNote), cs.Muted(durationNote)) if !opts.Log { fmt.Fprintln(opts.IO.Out, "") diff --git a/pkg/cmd/agent-task/view/view_test.go b/pkg/cmd/agent-task/view/view_test.go index d39ac0a41..b8f872560 100644 --- a/pkg/cmd/agent-task/view/view_test.go +++ b/pkg/cmd/agent-task/view/view_test.go @@ -345,6 +345,89 @@ func Test_viewRun(t *testing.T) { gh agent-task view 'some-session-id' --log `), }, + { + name: "with session id, success, with zero premium requests (tty)", + tty: true, + opts: ViewOptions{ + SelectorArg: "some-session-id", + SessionID: "some-session-id", + }, + capiStubs: func(t *testing.T, m *capi.CapiClientMock) { + m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { + assert.Equal(t, "some-session-id", id) + return &capi.Session{ + ID: "some-session-id", + State: "completed", + CreatedAt: sampleDate, + CompletedAt: sampleCompletedAt, + PremiumRequests: 0, + PullRequest: &api.PullRequest{ + Title: "fix something", + Number: 101, + URL: "https://github.com/OWNER/REPO/pull/101", + Repository: &api.PRRepository{ + NameWithOwner: "OWNER/REPO", + }, + }, + User: &api.GitHubUser{ + Login: "octocat", + }, + }, nil + } + }, + wantOut: heredoc.Doc(` + Ready for review • fix something • OWNER/REPO#101 + Started on behalf of octocat about 6 hours ago + Used 0 premium request(s) • Duration 5m0s + + For detailed session logs, try: + gh agent-task view 'some-session-id' --log + + View this session on GitHub: + https://github.com/OWNER/REPO/pull/101/agent-sessions/some-session-id + `), + }, + { + name: "with session id, success, duration not available (tty)", + tty: true, + opts: ViewOptions{ + SelectorArg: "some-session-id", + SessionID: "some-session-id", + }, + capiStubs: func(t *testing.T, m *capi.CapiClientMock) { + m.GetSessionFunc = func(_ context.Context, id string) (*capi.Session, error) { + assert.Equal(t, "some-session-id", id) + return &capi.Session{ + ID: "some-session-id", + State: "in_progress", + CreatedAt: sampleDate, + PremiumRequests: 1.5, + PullRequest: &api.PullRequest{ + Title: "fix something", + Number: 101, + URL: "https://github.com/OWNER/REPO/pull/101", + Repository: &api.PRRepository{ + NameWithOwner: "OWNER/REPO", + }, + }, + User: &api.GitHubUser{ + Login: "octocat", + }, + }, nil + } + }, + wantOut: heredoc.Doc(` + In progress • fix something • OWNER/REPO#101 + Started on behalf of octocat about 6 hours ago + Used 1.5 premium request(s) + + For detailed session logs, try: + gh agent-task view 'some-session-id' --log + + View this session on GitHub: + https://github.com/OWNER/REPO/pull/101/agent-sessions/some-session-id + `), + }, { name: "with session id, not found, web mode (tty)", tty: true, From 6f698407fb0380871143f7799161e45e96a739c3 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 18 Sep 2025 10:25:49 +0100 Subject: [PATCH 11/11] docs(pr/shared): add a `TODO` on decoupling PR finder from IO Signed-off-by: Babak K. Shandiz --- pkg/cmd/pr/shared/finder.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/cmd/pr/shared/finder.go b/pkg/cmd/pr/shared/finder.go index 2861627a9..1879fff62 100644 --- a/pkg/cmd/pr/shared/finder.go +++ b/pkg/cmd/pr/shared/finder.go @@ -198,6 +198,7 @@ func (f *finder) Find(opts FindOptions) (*api.PullRequest, ghrepo.Interface, err return nil, nil, err } + // TODO: Decouple the PR finder from IO // TODO(josebalius): Should we be guarding here? if !opts.DisableProgress && f.progress != nil { f.progress.StartProgressIndicator()