Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions pkg/gateway/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,21 @@ func (s *Server) DiscoverCodeUnits(ctx context.Context, req *gatewayv1.DiscoverC

// ─── Build / Lint / Test ─────────────────────────────────────

// composeStatusOutput surfaces a typed runtime status message alongside raw
// agent output. Agents that already fold the actionable message into raw output
// (as Generic does) would otherwise show it twice, so the message is only
// prepended when the raw output does not already carry it.
func composeStatusOutput(message, output string) string {
message = strings.TrimSpace(message)
if message == "" || strings.Contains(output, message) {
return output
}
if output == "" {
return message
}
return message + "\n" + output
}

func (s *Server) Build(ctx context.Context, _ *gatewayv1.BuildRequest) (*gatewayv1.BuildResponse, error) {
service, err := s.executionServiceBehavior()
if err != nil {
Expand All @@ -1456,7 +1471,7 @@ func (s *Server) Build(ctx context.Context, _ *gatewayv1.BuildRequest) (*gateway
success := resp.Status != nil && resp.Status.State == runtimev0.BuildStatus_SUCCESS
output := resp.Output
if !success && resp.Status != nil {
output = resp.Status.Message + "\n" + output
output = composeStatusOutput(resp.Status.Message, output)
}
var buildErrors []*gatewayv1.BuildError
if !success {
Expand All @@ -1477,7 +1492,7 @@ func (s *Server) Lint(ctx context.Context, _ *gatewayv1.LintRequest) (*gatewayv1
success := resp.Status != nil && resp.Status.State == runtimev0.LintStatus_SUCCESS
output := resp.Output
if !success && resp.Status != nil {
output = resp.Status.Message + "\n" + output
output = composeStatusOutput(resp.Status.Message, output)
}
var lintErrors []*gatewayv1.BuildError
if !success {
Expand Down
86 changes: 81 additions & 5 deletions pkg/gateway/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ type mockCodeClient struct {
}

type mockRuntimeClient struct {
testFn func(ctx context.Context, in *runtimev0.TestRequest, opts ...grpc.CallOption) (*runtimev0.TestResponse, error)
testFn func(ctx context.Context, in *runtimev0.TestRequest, opts ...grpc.CallOption) (*runtimev0.TestResponse, error)
buildFn func(ctx context.Context, in *runtimev0.BuildRequest, opts ...grpc.CallOption) (*runtimev0.BuildResponse, error)
lintFn func(ctx context.Context, in *runtimev0.LintRequest, opts ...grpc.CallOption) (*runtimev0.LintResponse, error)
}

type mockServiceExecution struct {
Expand Down Expand Up @@ -172,17 +174,23 @@ func (m *mockRuntimeClient) Stop(context.Context, *runtimev0.StopRequest, ...grp
func (m *mockRuntimeClient) Destroy(context.Context, *runtimev0.DestroyRequest, ...grpc.CallOption) (*runtimev0.DestroyResponse, error) {
return nil, fmt.Errorf("not exercised in mock")
}
func (m *mockRuntimeClient) Build(context.Context, *runtimev0.BuildRequest, ...grpc.CallOption) (*runtimev0.BuildResponse, error) {
return nil, fmt.Errorf("not exercised in mock")
func (m *mockRuntimeClient) Build(ctx context.Context, in *runtimev0.BuildRequest, opts ...grpc.CallOption) (*runtimev0.BuildResponse, error) {
if m.buildFn == nil {
return nil, fmt.Errorf("Build not configured")
}
return m.buildFn(ctx, in, opts...)
}
func (m *mockRuntimeClient) Test(ctx context.Context, in *runtimev0.TestRequest, opts ...grpc.CallOption) (*runtimev0.TestResponse, error) {
if m.testFn == nil {
return nil, fmt.Errorf("Test not configured")
}
return m.testFn(ctx, in, opts...)
}
func (m *mockRuntimeClient) Lint(context.Context, *runtimev0.LintRequest, ...grpc.CallOption) (*runtimev0.LintResponse, error) {
return nil, fmt.Errorf("not exercised in mock")
func (m *mockRuntimeClient) Lint(ctx context.Context, in *runtimev0.LintRequest, opts ...grpc.CallOption) (*runtimev0.LintResponse, error) {
if m.lintFn == nil {
return nil, fmt.Errorf("Lint not configured")
}
return m.lintFn(ctx, in, opts...)
}
func (m *mockRuntimeClient) Information(context.Context, *runtimev0.InformationRequest, ...grpc.CallOption) (*runtimev0.InformationResponse, error) {
return nil, fmt.Errorf("not exercised in mock")
Expand Down Expand Up @@ -601,6 +609,74 @@ func TestTestPreservesStructuredRuntimeFields(t *testing.T) {
}
}

func TestBuildDoesNotDuplicateStatusMessageInOutput(t *testing.T) {
const msg = "build not available: generic agent has no language knowledge"
rt := &mockRuntimeClient{
buildFn: func(_ context.Context, _ *runtimev0.BuildRequest, _ ...grpc.CallOption) (*runtimev0.BuildResponse, error) {
return &runtimev0.BuildResponse{
Output: msg,
Status: &runtimev0.BuildStatus{State: runtimev0.BuildStatus_ERROR, Message: msg},
}, nil
},
}
s := newTestServerWithRuntime(rt)

resp, err := s.Build(context.Background(), &gatewayv1.BuildRequest{})
if err != nil {
t.Fatalf("Build: %v", err)
}
if resp.Success {
t.Fatal("Success = true, want false")
}
if resp.Output != msg {
t.Fatalf("Output = %q, want the message surfaced once", resp.Output)
}
}

func TestBuildPrependsDistinctStatusMessage(t *testing.T) {
rt := &mockRuntimeClient{
buildFn: func(_ context.Context, _ *runtimev0.BuildRequest, _ ...grpc.CallOption) (*runtimev0.BuildResponse, error) {
return &runtimev0.BuildResponse{
Output: "compiler: undefined symbol",
Status: &runtimev0.BuildStatus{State: runtimev0.BuildStatus_ERROR, Message: "build failed"},
}, nil
},
}
s := newTestServerWithRuntime(rt)

resp, err := s.Build(context.Background(), &gatewayv1.BuildRequest{})
if err != nil {
t.Fatalf("Build: %v", err)
}
if resp.Output != "build failed\ncompiler: undefined symbol" {
t.Fatalf("Output = %q, want status context preserved ahead of native output", resp.Output)
}
}

func TestLintDoesNotDuplicateStatusMessageInOutput(t *testing.T) {
const msg = "lint not available: generic agent has no language knowledge"
rt := &mockRuntimeClient{
lintFn: func(_ context.Context, _ *runtimev0.LintRequest, _ ...grpc.CallOption) (*runtimev0.LintResponse, error) {
return &runtimev0.LintResponse{
Output: msg,
Status: &runtimev0.LintStatus{State: runtimev0.LintStatus_ERROR, Message: msg},
}, nil
},
}
s := newTestServerWithRuntime(rt)

resp, err := s.Lint(context.Background(), &gatewayv1.LintRequest{})
if err != nil {
t.Fatalf("Lint: %v", err)
}
if resp.Success {
t.Fatal("Success = true, want false")
}
if resp.Output != msg {
t.Fatalf("Output = %q, want the message surfaced once", resp.Output)
}
}

func TestFix(t *testing.T) {
mock := &mockCodeClient{
fixFn: func(_ context.Context, in *codev0.FixRequest, _ ...grpc.CallOption) (*codev0.FixResponse, error) {
Expand Down
Loading