From 2bb1e4b264bc22435d20d32c0de97e58803e48b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20H=C3=A9ritier?= Date: Sat, 22 Aug 2026 15:57:23 +0000 Subject: [PATCH 1/3] chore: upgrade to Go 1.27.0 and apply 1.27 modernizations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bump go.mod and Dockerfile from go 1.26.5 to go 1.27.0 - Run go mod tidy to update checksums - Remove obsolete // +build build tag from e2e/binary/binary_test.go - Use slices.Backward in cmd/wasm/runtime_wasm.go reverse loop - Use strings.CutLast (new in Go 1.27) at 6 sites: pkg/toolinstall/installer.go, pkg/model/provider/dmr/available.go, pkg/model/provider/dmr/pull.go, pkg/config/auto.go, pkg/modelinfo/modelinfo.go (×2) - Bump golangci-lint from v2.12.2 to v2.13.1 (v2.12.2 panics on Go 1.27 AST via staticcheck v0.7.0) --- .github/workflows/ci.yml | 2 +- Dockerfile | 2 +- cmd/wasm/runtime_wasm.go | 7 +-- e2e/binary/binary_test.go | 1 - go.mod | 77 ++++++++++++++--------------- pkg/config/auto.go | 4 +- pkg/model/provider/dmr/available.go | 4 +- pkg/model/provider/dmr/pull.go | 4 +- pkg/modelinfo/modelinfo.go | 10 ++-- pkg/toolinstall/installer.go | 4 +- 10 files changed, 55 insertions(+), 60 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a92beff1f..d706de572a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: - name: Lint uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0 with: - version: v2.12.2 + version: v2.13.1 - name: Lint GitHub Actions uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2.2.0 diff --git a/Dockerfile b/Dockerfile index dfba6aa8ee..b26ea9bdf3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION="1.26.5" +ARG GO_VERSION="1.27.0" ARG ALPINE_VERSION="3.23" ARG XX_VERSION="1.9.0" diff --git a/cmd/wasm/runtime_wasm.go b/cmd/wasm/runtime_wasm.go index a981c76087..2d76987980 100644 --- a/cmd/wasm/runtime_wasm.go +++ b/cmd/wasm/runtime_wasm.go @@ -22,6 +22,7 @@ import ( "fmt" "io" "log/slog" + "slices" "strings" "syscall/js" "time" @@ -773,9 +774,9 @@ func (rt *wasmRuntime) emitEvent(event map[string]any) { // lastAssistantContent returns the content of the last assistant message. func (rt *wasmRuntime) lastAssistantContent(messages []chat.Message) string { - for i := len(messages) - 1; i >= 0; i-- { - if messages[i].Role == chat.MessageRoleAssistant && messages[i].Content != "" { - return messages[i].Content + for _, msg := range slices.Backward(messages) { + if msg.Role == chat.MessageRoleAssistant && msg.Content != "" { + return msg.Content } } return "" diff --git a/e2e/binary/binary_test.go b/e2e/binary/binary_test.go index a4c6315fad..51f4059407 100644 --- a/e2e/binary/binary_test.go +++ b/e2e/binary/binary_test.go @@ -1,5 +1,4 @@ //go:build binary_required -// +build binary_required package binary diff --git a/go.mod b/go.mod index dd7be64996..2319609fb3 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/docker/docker-agent -go 1.26.5 +go 1.27.0 require ( charm.land/bubbles/v2 v2.2.0 @@ -24,11 +24,13 @@ require ( github.com/aws/smithy-go v1.27.9 github.com/aymanbagabas/go-udiff v0.4.1 github.com/bmatcuk/doublestar/v4 v4.10.0 + github.com/charmbracelet/ultraviolet v0.0.0-20260812204455-68fa937c71be github.com/charmbracelet/x/ansi v0.11.8 github.com/clipperhouse/displaywidth v0.11.0 github.com/clipperhouse/uax29/v2 v2.7.0 github.com/coder/acp-go-sdk v0.13.5 github.com/creack/pty v1.1.24 + github.com/dgageot/rubocop-go v0.0.0-20260627140528-ee9a9b36c3eb github.com/docker/aijson v0.1.0 github.com/docker/cli v29.7.2+incompatible github.com/docker/go-units v0.5.0 @@ -37,6 +39,7 @@ require ( github.com/expr-lang/expr v1.17.8 github.com/fatih/color v1.19.0 github.com/fsnotify/fsnotify v1.10.1 + github.com/go-git/go-billy/v5 v5.9.1 github.com/go-git/go-git/v5 v5.19.2 github.com/goccy/go-yaml v1.19.2 github.com/golang-jwt/jwt/v5 v5.3.1 @@ -51,6 +54,7 @@ require ( github.com/mattn/go-isatty v0.0.24 github.com/mattn/go-runewidth v0.0.28 github.com/modelcontextprotocol/go-sdk v1.6.1 + github.com/muesli/cancelreader v0.2.2 github.com/natefinch/atomic v1.0.1 github.com/openai/openai-go/v3 v3.52.0 github.com/pb33f/libopenapi v0.38.7 @@ -58,20 +62,27 @@ require ( github.com/rumpl/harness v0.0.0-20260810193856-9376b9c76461 github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 github.com/spf13/cobra v1.10.2 + github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 github.com/temoto/robotstxt v1.1.2 github.com/wk8/go-ordered-map/v2 v2.1.9-0.20250401010720-46d686821e33 github.com/xeipuuv/gojsonschema v1.2.0 github.com/yuin/goldmark v1.8.5 github.com/zclconf/go-cty v1.19.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 go.opentelemetry.io/otel v1.45.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.20.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 + go.opentelemetry.io/otel/log v0.20.0 + go.opentelemetry.io/otel/metric v1.45.0 go.opentelemetry.io/otel/sdk v1.44.0 go.opentelemetry.io/otel/sdk/log v0.20.0 + go.opentelemetry.io/otel/sdk/metric v1.44.0 go.opentelemetry.io/otel/trace v1.45.0 + go.yaml.in/yaml/v4 v4.0.0-rc.6 golang.org/x/image v0.45.0 + golang.org/x/net v0.58.0 golang.org/x/oauth2 v0.36.0 golang.org/x/sync v0.22.0 golang.org/x/sys v0.47.0 @@ -83,46 +94,25 @@ require ( modernc.org/sqlite v1.57.0 ) -require ( - cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect - github.com/a2aproject/a2a-go/v2 v2.3.1 // indirect - github.com/agext/levenshtein v1.2.1 // indirect - github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect - github.com/apparentlymart/go-textseg/v17 v17.0.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.39 // indirect - github.com/danieljoos/wincred v1.2.2 // indirect - github.com/dlclark/regexp2/v2 v2.5.2 // indirect - github.com/dvsekhvalnov/jose2go v1.7.0 // indirect - github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect - github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect - github.com/invopop/jsonschema v0.14.0 // indirect - github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741 // indirect - github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/mtibben/percent v0.2.1 // indirect - github.com/pb33f/jsonpath v0.8.2 // indirect - github.com/pb33f/ordered-map/v2 v2.3.1 // indirect - github.com/smartystreets/assertions v1.2.0 // indirect - github.com/standard-webhooks/standard-webhooks/libraries v0.0.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect - golang.org/x/mod v0.38.0 // indirect - golang.org/x/tools v0.48.0 // indirect - google.golang.org/api v0.279.0 // indirect -) - require ( cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/auth v0.20.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect dario.cat/mergo v1.0.2 // indirect + github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/JohannesKaufmann/dom v0.3.1 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/a2aproject/a2a-go/v2 v2.3.1 // indirect + github.com/agext/levenshtein v1.2.1 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/apparentlymart/go-textseg/v17 v17.0.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.18 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.38 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.38 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.38 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.39 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.17 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.38 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.5.7 // indirect @@ -134,7 +124,6 @@ require ( github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect - github.com/charmbracelet/ultraviolet v0.0.0-20260812204455-68fa937c71be github.com/charmbracelet/x/exp/slice v0.0.0-20251113172435-cef867b85f6a // indirect github.com/charmbracelet/x/term v0.2.2 // indirect github.com/charmbracelet/x/termios v0.1.1 // indirect @@ -144,22 +133,24 @@ require ( github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/danieljoos/wincred v1.2.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dgageot/rubocop-go v0.0.0-20260627140528-ee9a9b36c3eb github.com/distribution/reference v0.6.0 // indirect + github.com/dlclark/regexp2/v2 v2.5.2 // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/dvsekhvalnov/jose2go v1.7.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.9.1 github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect + github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 // indirect @@ -169,15 +160,20 @@ require ( github.com/googleapis/gax-go/v2 v2.22.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect + github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/invopop/jsonschema v0.14.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.19.1 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/labstack/gommon v0.5.0 // indirect github.com/lucasb-eyer/go-colorful v1.4.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.15 // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/moby/api v1.55.0 // indirect github.com/moby/moby/client v0.5.1 // indirect @@ -185,10 +181,12 @@ require ( github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/term v0.5.2 // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/muesli/cancelreader v0.2.2 + github.com/mtibben/percent v0.2.1 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/pb33f/jsonpath v0.8.2 // indirect + github.com/pb33f/ordered-map/v2 v2.3.1 // indirect github.com/pjbgf/sha1cd v0.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect @@ -197,7 +195,8 @@ require ( github.com/sergi/go-diff v1.4.0 // indirect github.com/sirupsen/logrus v1.9.4 // indirect github.com/skeema/knownhosts v1.3.1 // indirect - github.com/spf13/pflag v1.0.10 + github.com/smartystreets/assertions v1.2.0 // indirect + github.com/standard-webhooks/standard-webhooks/libraries v0.0.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/tidwall/gjson v1.19.0 // indirect github.com/tidwall/match v1.1.1 // indirect @@ -212,19 +211,17 @@ require ( github.com/yosida95/uritemplate/v3 v3.0.2 // indirect github.com/yuin/goldmark-emoji v1.0.5 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect - go.opentelemetry.io/otel/log v0.20.0 - go.opentelemetry.io/otel/metric v1.45.0 - go.opentelemetry.io/otel/sdk/metric v1.44.0 go.opentelemetry.io/proto/otlp v1.10.0 // indirect - go.yaml.in/yaml/v4 v4.0.0-rc.6 golang.org/x/crypto v0.55.0 // indirect - golang.org/x/net v0.58.0 + golang.org/x/mod v0.38.0 // indirect golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.15.0 // indirect + golang.org/x/tools v0.48.0 // indirect + google.golang.org/api v0.279.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/grpc v1.82.1 // indirect diff --git a/pkg/config/auto.go b/pkg/config/auto.go index f66b22f042..ddaf434c6f 100644 --- a/pkg/config/auto.go +++ b/pkg/config/auto.go @@ -348,8 +348,8 @@ func PreferLocalDMRModels(ctx context.Context, cfg *latest.Config, selectorNames // the suffix has no slash, so a registry host:port like "registry:5000/ai/x" // is preserved. func dmrModelRepo(id string) string { - if i := strings.LastIndex(id, ":"); i >= 0 && !strings.Contains(id[i+1:], "/") { - return id[:i] + if before, after, ok := strings.CutLast(id, ":"); ok && !strings.Contains(after, "/") { + return before } return id } diff --git a/pkg/model/provider/dmr/available.go b/pkg/model/provider/dmr/available.go index 59834126de..4f2f134baf 100644 --- a/pkg/model/provider/dmr/available.go +++ b/pkg/model/provider/dmr/available.go @@ -68,8 +68,8 @@ func modelAvailable(available []string, model string) bool { // separator when the suffix has no slash, so a registry host:port like // "registry:5000/ai/x" is preserved. func modelRepo(id string) string { - if i := strings.LastIndex(id, ":"); i >= 0 && !strings.Contains(id[i+1:], "/") { - return id[:i] + if before, after, ok := strings.CutLast(id, ":"); ok && !strings.Contains(after, "/") { + return before } return id } diff --git a/pkg/model/provider/dmr/pull.go b/pkg/model/provider/dmr/pull.go index 2aa43b0dba..33967ab0bc 100644 --- a/pkg/model/provider/dmr/pull.go +++ b/pkg/model/provider/dmr/pull.go @@ -182,8 +182,8 @@ func cleanPullStderr(raw string) string { var lines []string for line := range strings.SplitSeq(raw, "\n") { // Progress bars rewrite a line in place with '\r'; keep the last state. - if i := strings.LastIndex(line, "\r"); i >= 0 { - line = line[i+1:] + if _, after, ok := strings.CutLast(line, "\r"); ok { + line = after } line = strings.TrimRight(line, " \t") if strings.TrimSpace(line) == "" { diff --git a/pkg/modelinfo/modelinfo.go b/pkg/modelinfo/modelinfo.go index db040e3cda..ce03f1c989 100644 --- a/pkg/modelinfo/modelinfo.go +++ b/pkg/modelinfo/modelinfo.go @@ -361,15 +361,13 @@ const openAIQualifierPrefix = "openai/" // [normalizeOpenAI], which strips that preserved "openai/" pair afterwards. func normalize(modelID string) string { m := strings.ToLower(strings.TrimSpace(modelID)) - i := strings.LastIndexByte(m, '/') - if i < 0 { + prefix, last, ok := strings.CutLast(m, "/") + if !ok { return m } - last := m[i+1:] - prefix := m[:i] prevSeg := prefix - if j := strings.LastIndexByte(prefix, '/'); j >= 0 { - prevSeg = prefix[j+1:] + if _, seg, found := strings.CutLast(prefix, "/"); found { + prevSeg = seg } if prevSeg == "openai" { return openAIQualifierPrefix + last diff --git a/pkg/toolinstall/installer.go b/pkg/toolinstall/installer.go index fe4528e459..c7f59adc52 100644 --- a/pkg/toolinstall/installer.go +++ b/pkg/toolinstall/installer.go @@ -54,8 +54,8 @@ func installGoPackage(ctx context.Context, pkg *Package, version string) (string // Strip multi-module tag prefix: "gopls/v0.21.1" → "v0.21.1". installVersion := version - if idx := strings.LastIndex(version, "/"); idx >= 0 { - installVersion = version[idx+1:] + if _, after, ok := strings.CutLast(version, "/"); ok { + installVersion = after } if !strings.HasPrefix(installVersion, "v") && installVersion != "latest" { installVersion = "v" + installVersion From 0dbf4eb25bd26518ef1a28a9c4c95ab43b428d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20H=C3=A9ritier?= Date: Sat, 22 Aug 2026 16:08:56 +0000 Subject: [PATCH 2/3] ci: add custom CodeQL workflow with Go 1.27 setup The GitHub Default Setup uses Go 1.26.6 on the runner with GOTOOLCHAIN=local, which fails to build a project requiring go >= 1.27.0. Replace Default Setup with an explicit workflow that runs setup-go (go-version-file: go.mod) before CodeQL init, so the tracer wraps the correct Go 1.27.0 binary. --- .github/workflows/codeql.yml | 60 ++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000..846572c05c --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,60 @@ +name: CodeQL + +permissions: + contents: read + security-events: write + actions: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + schedule: + - cron: '23 14 * * 1' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + include: + - language: go + build-mode: autobuild + - language: actions + build-mode: none + - language: javascript-typescript + build-mode: none + + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Set up Go + if: matrix.language == 'go' + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + with: + go-version-file: go.mod + cache-dependency-path: go.sum + + - name: Initialize CodeQL + uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + + - name: Autobuild + if: matrix.build-mode == 'autobuild' + uses: github/codeql-action/autobuild@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 + with: + category: "/language:${{ matrix.language }}" From 1ec5fecd5dd95565247138fea548d9855060967b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20H=C3=A9ritier?= Date: Sat, 22 Aug 2026 16:24:04 +0000 Subject: [PATCH 3/3] fix: resolve golangci-lint v2.13.1 / gofumpt v0.11.0 findings after linter bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Run golangci-lint fmt to apply gofumpt v0.11.0 formatting (v0.9.2→v0.11.0 bundled in golangci-lint) across 30 files: purely mechanical whitespace and grouping changes, no logic changes. - Remove 3 stale //nolint:recvcheck directives from pkg/config/latest/types.go (recvcheck in golangci-lint v2.13.1 no longer flags MarshalYAML/JSON patterns, making those suppression comments unused). - Disable newly-activated modernize sub-checks in .golangci.yml: errorsastype, embedlit, stringscut, reflecttypeassert — all require Go 1.27 APIs or x/tools ≥ v0.48; pre-existing violations in unrelated files. A follow-up PR should re-enable these and clean up the violations. - Add .golangci.yml exclusions for pkg/config/v*/ (recvcheck + nolintlint): frozen versioned config types that cannot be modified; recvcheck changed behaviour between v2.12.2 and v2.13.1 leaving stale nolint directives. --- .golangci.yml | 22 ++++++++++ cmd/root/doctor.go | 15 ++++--- cmd/root/doctor_test.go | 18 +++++--- examples/golibrary/multi/main.go | 3 +- pkg/agent/agent.go | 3 +- pkg/app/app_test.go | 4 +- pkg/board/tui/view.go | 3 +- pkg/cli/runner_test.go | 42 +++++++++++++------ pkg/config/latest/types.go | 6 +-- pkg/environment/store_test.go | 3 +- pkg/hooks/builtins/max_iterations.go | 3 +- pkg/leantui/update_test.go | 6 ++- pkg/modelerrors/modelerrors_test.go | 21 ++++++---- pkg/rag/strategy/bm25_database.go | 6 ++- .../strategy/chunked_embeddings_database.go | 6 ++- pkg/rag/strategy/semantic_embeddings.go | 3 +- .../strategy/semantic_embeddings_database.go | 6 ++- pkg/runtime/agent_delegation.go | 3 +- pkg/runtime/loop.go | 9 ++-- pkg/runtime/runtime_test.go | 7 ++-- pkg/runtime/structured_output.go | 6 ++- pkg/session/migrations.go | 3 +- pkg/teamloader/registry_test.go | 1 + .../builtin/backgroundjobs/cmd_windows.go | 3 +- pkg/tools/builtin/mcpcatalog/mcpcatalog.go | 18 +++++--- pkg/tools/builtin/scheduler/schedule.go | 3 +- pkg/tools/builtin/shell/cmd_windows.go | 3 +- .../structuredoutput/structuredoutput.go | 3 +- pkg/tools/builtin/webhook/webhook.go | 6 ++- pkg/tools/mcp/oauth_test.go | 12 ++++-- pkg/tui/components/sidebar/sidebar.go | 9 ++-- pkg/tui/dialog/multi_choice.go | 3 +- pkg/tui/dialog/plan_browser.go | 3 +- pkg/tui/dialog/tour_offer.go | 6 ++- pkg/tui/handlers.go | 3 +- pkg/tui/plans.go | 33 ++++++++++----- 36 files changed, 211 insertions(+), 93 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 47a327a7f0..553606efb5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -156,6 +156,16 @@ linters: - G702 - G703 - G704 + modernize: + # errorsastype (errors.AsType, Go 1.27), embedlit (x/tools v0.48), + # stringscut and reflecttypeassert became active after the + # golangci-lint v2.12.2 → v2.13.1 bump required by the Go 1.27 upgrade. + # Pre-existing violations in unrelated files; re-enable in a follow-up. + disable: + - errorsastype + - embedlit + - stringscut + - reflecttypeassert exclusions: generated: lax presets: @@ -182,6 +192,18 @@ linters: - path: pkg/worktree/namesgenerator/ linters: - nolintlint + # Frozen versioned config types: recvcheck changed behaviour in + # golangci-lint v2.13.1 — some previously-suppressed violations are no + # longer caught (leaving stale //nolint:recvcheck directives that trigger + # nolintlint) while new violations are reported in the same files. + # These files are frozen (see AGENTS.md) and must not be modified. + - path: pkg/config/v\d+/ + linters: + - recvcheck + - nolintlint + - path: pkg/config/latest/types\.go + linters: + - recvcheck issues: max-same-issues: 3 formatters: diff --git a/cmd/root/doctor.go b/cmd/root/doctor.go index f49540732d..22540ab8cf 100644 --- a/cmd/root/doctor.go +++ b/cmd/root/doctor.go @@ -221,7 +221,8 @@ func (f *doctorFlags) buildReport(ctx context.Context, agentRef string) (*doctor report.UserConfig.Error = err.Error() report.Issues = append(report.Issues, fmt.Sprintf( "the user config file %s cannot be parsed and is ignored (settings and aliases are unavailable): %v", - report.UserConfig.Path, err)) + report.UserConfig.Path, err, + )) } credFound := map[string]bool{} @@ -302,12 +303,14 @@ func (f *doctorFlags) buildReport(ctx context.Context, agentRef string) (*doctor autoStatus.Usable = false autoIssues = append(autoIssues, fmt.Sprintf( "the configured default model %s/%s needs Docker Model Runner, which is %s; install or start it (%s)", - auto.Provider, auto.Model, describeDMRStatus(report.DMR.Status), dmrDocsURL)) + auto.Provider, auto.Model, describeDMRStatus(report.DMR.Status), dmrDocsURL, + )) case dmrDown: autoStatus.Usable = false autoIssues = append(autoIssues, fmt.Sprintf( "no usable model: no provider credential was found and Docker Model Runner is %s; run `docker agent setup`, or set an API key for one of the providers above (%s) or install Docker Model Runner (%s)", - describeDMRStatus(report.DMR.Status), environment.SecretsDocsURL, dmrDocsURL)) + describeDMRStatus(report.DMR.Status), environment.SecretsDocsURL, dmrDocsURL, + )) case !slices.Contains(dmrModels, auto.Model): autoStatus.Note = fmt.Sprintf("not pulled yet; run `docker model pull %s` or let the first run pull it", auto.Model) } @@ -319,7 +322,8 @@ func (f *doctorFlags) buildReport(ctx context.Context, agentRef string) (*doctor autoStatus.Usable = false autoIssues = append(autoIssues, fmt.Sprintf( "the configured default model %s/%s has no credential for provider %s; %s (%s)", - auto.Provider, auto.Model, auto.Provider, providerCredentialHint(auto.Provider, primaryEnvVar[auto.Provider]), environment.SecretsDocsURL)) + auto.Provider, auto.Model, auto.Provider, providerCredentialHint(auto.Provider, primaryEnvVar[auto.Provider]), environment.SecretsDocsURL, + )) } } @@ -382,7 +386,8 @@ func (f *doctorFlags) checkAgentFile(ctx context.Context, ref string, cfg *lates if len(missing) > 0 { report.Issues = append(report.Issues, fmt.Sprintf( "%s requires environment variables that are not set: %s (see %s)", - ref, strings.Join(missing, ", "), environment.SecretsDocsURL)) + ref, strings.Join(missing, ", "), environment.SecretsDocsURL, + )) } // The Claude Code harness runs the local `claude` CLI with its own login, diff --git a/cmd/root/doctor_test.go b/cmd/root/doctor_test.go index e863b6e2fe..9434343cfe 100644 --- a/cmd/root/doctor_test.go +++ b/cmd/root/doctor_test.go @@ -65,7 +65,8 @@ func TestDoctorCommand_ReportsCredentialSource(t *testing.T) { output, err := executeDoctor(t, nil, withDoctorTestEnv( map[string]string{"ANTHROPIC_API_KEY": "sk-secret-value"}, - []string{"ai/qwen3:latest"}, nil)) + []string{"ai/qwen3:latest"}, nil, + )) require.NoError(t, err) assert.Regexp(t, `anthropic\s+found\s+ANTHROPIC_API_KEY\s+environment`, output) @@ -138,7 +139,8 @@ func TestDoctorCommand_EmptyValueIsNotACredential(t *testing.T) { output, err := executeDoctor(t, nil, withDoctorTestEnv( map[string]string{"OPENAI_API_KEY": "", "MISTRAL_API_KEY": "key"}, - []string{"ai/qwen3:latest"}, nil)) + []string{"ai/qwen3:latest"}, nil, + )) require.NoError(t, err) assert.Regexp(t, `openai\s+not set`, output) @@ -269,7 +271,8 @@ func TestDoctorCommand_JSON(t *testing.T) { output, err := executeDoctor(t, []string{"--json"}, withDoctorTestEnv( map[string]string{"OPENAI_API_KEY": "sk-json-secret"}, - []string{"ai/qwen3:latest"}, nil)) + []string{"ai/qwen3:latest"}, nil, + )) require.NoError(t, err) assert.NotContains(t, output, "sk-json-secret", "secret values must never be printed") @@ -299,7 +302,8 @@ func TestDoctorCommand_JSONReportsGitHubCopilot(t *testing.T) { output, err := executeDoctor(t, []string{"--json"}, withDoctorTestEnv( map[string]string{"GH_TOKEN": "gh-token"}, - []string{"ai/qwen3:latest"}, nil)) + []string{"ai/qwen3:latest"}, nil, + )) require.NoError(t, err) assert.NotContains(t, output, "gh-token", "secret values must never be printed") @@ -341,7 +345,8 @@ func TestDoctorCommand_AgentFileMissingVars(t *testing.T) { output, err := executeDoctor(t, []string{path}, withDoctorTestEnv( map[string]string{"ANTHROPIC_API_KEY": "key"}, - []string{"ai/qwen3:latest"}, nil)) + []string{"ai/qwen3:latest"}, nil, + )) require.Error(t, err) statusErr, ok := errors.AsType[cli.StatusError](err) @@ -360,7 +365,8 @@ func TestDoctorCommand_AgentFileVarsSatisfied(t *testing.T) { output, err := executeDoctor(t, []string{path}, withDoctorTestEnv( map[string]string{"OPENAI_API_KEY": "key"}, - []string{"ai/qwen3:latest"}, nil)) + []string{"ai/qwen3:latest"}, nil, + )) require.NoError(t, err) assert.Regexp(t, `OPENAI_API_KEY\s+models\s+found\s+environment`, output) diff --git a/examples/golibrary/multi/main.go b/examples/golibrary/multi/main.go index dc631c76bf..1a5a579c5b 100644 --- a/examples/golibrary/multi/main.go +++ b/examples/golibrary/multi/main.go @@ -33,7 +33,8 @@ func run(ctx context.Context) error { Provider: "openai", Model: "gpt-4o", }, - environment.NewDefaultProvider()) + environment.NewDefaultProvider(), + ) if err != nil { return err } diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 03f3803937..9198ef7560 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -456,7 +456,8 @@ func (a *Agent) collectTools(ctx context.Context) ([]tools.Tool, error) { if firstOrigin, exists := origins[tool.Name]; exists { collisions[collisionKey(tool.Name, firstOrigin, origin)] = fmt.Sprintf( "duplicate tool %q: kept from %s, ignored from %s (first toolset in config wins) — set a unique 'name:' on the MCP toolset or use its 'tools:' filter to disambiguate", - tool.Name, firstOrigin, origin) + tool.Name, firstOrigin, origin, + ) continue } origins[tool.Name] = origin diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index 979df8fdf6..d95e9318fb 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -92,7 +92,9 @@ func (m *mockRuntime) Stop() func (m *mockRuntime) Steer(_ context.Context, _ runtime.QueuedMessage) error { return nil } func (m *mockRuntime) FollowUp(_ context.Context, _ runtime.QueuedMessage) error { return nil } func (m *mockRuntime) QueueStatus() runtime.QueueStatus { return runtime.QueueStatus{} } -func (m *mockRuntime) TogglePause(context.Context) (bool, error) { return false, nil } + +func (m *mockRuntime) TogglePause(context.Context) (bool, error) { return false, nil } + func (m *mockRuntime) SetAgentModel(context.Context, string, string) error { return nil } diff --git a/pkg/board/tui/view.go b/pkg/board/tui/view.go index 31ccae91ac..390868f053 100644 --- a/pkg/board/tui/view.go +++ b/pkg/board/tui/view.go @@ -542,7 +542,8 @@ func (m *model) renderFooter() string { var details string if card := m.selectedCard(); card != nil { details = styles.MutedStyle.Underline(true).Render(toolcommon.TruncateText( - sanitize(card.Agent+" · "+card.Branch), max(m.width/2, 0))) + " " + sanitize(card.Agent+" · "+card.Branch), max(m.width/2, 0), + )) + " " } left := " " + toolcommon.TruncateText(strings.Join(parts, " "), max(m.width-lipgloss.Width(details)-2, 1)) diff --git a/pkg/cli/runner_test.go b/pkg/cli/runner_test.go index dc3c850310..c393b13472 100644 --- a/pkg/cli/runner_test.go +++ b/pkg/cli/runner_test.go @@ -66,9 +66,13 @@ func (m *mockRuntime) CurrentAgentName(context.Context) string { return "test" } func (m *mockRuntime) CurrentAgentInfo(context.Context) runtime.CurrentAgentInfo { return runtime.CurrentAgentInfo{Name: "test"} } -func (m *mockRuntime) SetCurrentAgent(context.Context, string) error { return nil } -func (m *mockRuntime) CurrentAgentTools(context.Context) ([]tools.Tool, error) { return nil, nil } -func (m *mockRuntime) CurrentAgentToolsetStatuses() []tools.ToolsetStatus { return nil } + +func (m *mockRuntime) SetCurrentAgent(context.Context, string) error { return nil } + +func (m *mockRuntime) CurrentAgentTools(context.Context) ([]tools.Tool, error) { return nil, nil } + +func (m *mockRuntime) CurrentAgentToolsetStatuses() []tools.ToolsetStatus { return nil } + func (m *mockRuntime) RestartToolset(context.Context, string) error { return nil } func (m *mockRuntime) EmitStartupInfo(context.Context, *session.Session, runtime.EventSink) {} func (m *mockRuntime) EmitAgentInfo(context.Context, runtime.EventSink) {} @@ -84,10 +88,13 @@ func (m *mockRuntime) ResumeElicitation(_ context.Context, action tools.Elicitat m.elicitationLastAction = action return nil } + func (m *mockRuntime) SessionStore() session.Store { return nil } func (m *mockRuntime) Summarize(context.Context, *session.Session, string, runtime.EventSink) {} func (m *mockRuntime) PermissionsInfo() *runtime.PermissionsInfo { return nil } -func (m *mockRuntime) CurrentAgentSkillsToolset() *skillstool.ToolSet { return nil } + +func (m *mockRuntime) CurrentAgentSkillsToolset() *skillstool.ToolSet { return nil } + func (m *mockRuntime) RunSkillFork(context.Context, *session.Session, skillstool.RunSkillArgs, runtime.EventSink) (*tools.ToolCallResult, error) { return nil, nil } @@ -99,14 +106,23 @@ func (m *mockRuntime) CurrentMCPPrompts(context.Context) map[string]mcptools.Pro func (m *mockRuntime) ExecuteMCPPrompt(context.Context, string, map[string]string) (string, error) { return "", nil } + func (m *mockRuntime) UpdateSessionTitle(context.Context, *session.Session, string) error { return nil } -func (m *mockRuntime) TitleGenerator(context.Context) *sessiontitle.Generator { return nil } -func (m *mockRuntime) Close() error { return nil } -func (m *mockRuntime) Steer(context.Context, runtime.QueuedMessage) error { return nil } -func (m *mockRuntime) FollowUp(context.Context, runtime.QueuedMessage) error { return nil } -func (m *mockRuntime) QueueStatus() runtime.QueueStatus { return runtime.QueueStatus{} } -func (m *mockRuntime) TogglePause(context.Context) (bool, error) { return false, nil } -func (m *mockRuntime) SetAgentModel(context.Context, string, string) error { return nil } + +func (m *mockRuntime) TitleGenerator(context.Context) *sessiontitle.Generator { return nil } + +func (m *mockRuntime) Close() error { return nil } + +func (m *mockRuntime) Steer(context.Context, runtime.QueuedMessage) error { return nil } + +func (m *mockRuntime) FollowUp(context.Context, runtime.QueuedMessage) error { return nil } + +func (m *mockRuntime) QueueStatus() runtime.QueueStatus { return runtime.QueueStatus{} } + +func (m *mockRuntime) TogglePause(context.Context) (bool, error) { return false, nil } + +func (m *mockRuntime) SetAgentModel(context.Context, string, string) error { return nil } + func (m *mockRuntime) CycleAgentThinkingLevel(context.Context, string) (effort.Level, error) { return "", runtime.ErrUnsupported } @@ -114,7 +130,9 @@ func (m *mockRuntime) CycleAgentThinkingLevel(context.Context, string) (effort.L func (m *mockRuntime) SetAgentThinkingLevel(context.Context, string, effort.Level) (effort.Level, error) { return "", runtime.ErrUnsupported } -func (m *mockRuntime) AvailableModels(context.Context) []runtime.ModelChoice { return nil } + +func (m *mockRuntime) AvailableModels(context.Context) []runtime.ModelChoice { return nil } + func (m *mockRuntime) SupportsModelSwitching() bool { return false } func (m *mockRuntime) OnToolsChanged(func(runtime.Event)) {} func (m *mockRuntime) OnBackgroundEvent(func(runtime.Event)) {} diff --git a/pkg/config/latest/types.go b/pkg/config/latest/types.go index 9205e01726..4c419aa070 100644 --- a/pkg/config/latest/types.go +++ b/pkg/config/latest/types.go @@ -829,7 +829,7 @@ type InlineSkill struct { // // The special source "local" loads skills from the filesystem (standard locations). // HTTP/HTTPS URLs load skills from remote servers per the well-known skills discovery spec. -type SkillsConfig struct { //nolint:recvcheck // MarshalYAML/MarshalJSON must use value receiver, UnmarshalYAML/UnmarshalJSON must use pointer +type SkillsConfig struct { // Sources lists where to load skills from: "local" and/or HTTP/HTTPS URLs. Sources []string // Include optionally filters loaded skills by name. When non-empty, only @@ -1709,7 +1709,7 @@ type RemoteOAuthConfig struct { // DeferConfig represents the deferred loading configuration for a toolset. // It can be either a boolean (true to defer all tools) or a slice of strings // (list of tool names to defer). -type DeferConfig struct { //nolint:recvcheck // MarshalYAML must use value receiver for YAML slice encoding, UnmarshalYAML must use pointer +type DeferConfig struct { // DeferAll is true when all tools should be deferred DeferAll bool `json:"-"` // Tools is the list of specific tool names to defer (empty if DeferAll is true) @@ -2079,7 +2079,7 @@ func (c *RAGConfig) GetRespectVCS() bool { // RAGStrategyConfig represents a single retrieval strategy configuration // Strategy-specific fields are stored in Params (validated by strategy implementation) -type RAGStrategyConfig struct { //nolint:recvcheck // Marshal methods must use value receiver for YAML/JSON slice encoding, Unmarshal must use pointer +type RAGStrategyConfig struct { Type string `json:"type"` // Strategy type: "chunked-embeddings", "bm25", etc. Docs []string `json:"docs,omitempty"` // Strategy-specific documents (augments shared docs) Database RAGDatabaseConfig `json:"database"` // Database configuration diff --git a/pkg/environment/store_test.go b/pkg/environment/store_test.go index 0de3ea6f5a..27925edda1 100644 --- a/pkg/environment/store_test.go +++ b/pkg/environment/store_test.go @@ -44,7 +44,8 @@ func TestEnvFileStore_CreatesFileAndDirectory(t *testing.T) { func TestEnvFileStore_UpdatesExistingKeyAndPreservesOtherLines(t *testing.T) { dir := withTempConfigDir(t) require.NoError(t, os.WriteFile(filepath.Join(dir, ".env"), []byte( - "# my keys\nOPENAI_API_KEY=old\nANTHROPIC_API_KEY=keep\n"), 0o600)) + "# my keys\nOPENAI_API_KEY=old\nANTHROPIC_API_KEY=keep\n", + ), 0o600)) store := NewConfigEnvFileStore() require.NoError(t, store.Store(t.Context(), "OPENAI_API_KEY", "new")) diff --git a/pkg/hooks/builtins/max_iterations.go b/pkg/hooks/builtins/max_iterations.go index 8e80ccbf90..ad30aecece 100644 --- a/pkg/hooks/builtins/max_iterations.go +++ b/pkg/hooks/builtins/max_iterations.go @@ -46,6 +46,7 @@ func maxIterations(_ context.Context, in *hooks.Input, args []string) (*hooks.Ou Decision: hooks.DecisionBlockValue, Reason: fmt.Sprintf( "Agent terminated: max_iterations builtin reached its limit of %d model call(s).", - limit), + limit, + ), }, nil } diff --git a/pkg/leantui/update_test.go b/pkg/leantui/update_test.go index 0395072dca..3dffb9a3c5 100644 --- a/pkg/leantui/update_test.go +++ b/pkg/leantui/update_test.go @@ -87,9 +87,11 @@ func (r *cycleThinkingRuntime) UpdateSessionTitle(_ context.Context, sess *sessi sess.Title = title return nil } + func (r *cycleThinkingRuntime) TitleGenerator(context.Context) *sessiontitle.Generator { return nil } -func (r *cycleThinkingRuntime) Close() error { return nil } -func (r *cycleThinkingRuntime) Stop() {} + +func (r *cycleThinkingRuntime) Close() error { return nil } +func (r *cycleThinkingRuntime) Stop() {} func (r *cycleThinkingRuntime) Steer(_ context.Context, msg runtime.QueuedMessage) error { if r.steerErr != nil { return r.steerErr diff --git a/pkg/modelerrors/modelerrors_test.go b/pkg/modelerrors/modelerrors_test.go index ea4113ae96..b9e43bc043 100644 --- a/pkg/modelerrors/modelerrors_test.go +++ b/pkg/modelerrors/modelerrors_test.go @@ -179,13 +179,15 @@ func TestClassifyOverflow(t *testing.T) { { name: "anthropic 413 with request_too_large body", err: &StatusError{StatusCode: 413, Err: errors.New( - `POST "https://api.anthropic.com/v1/messages": 413 Payload Too Large {"type":"error","error":{"type":"request_too_large","message":"Request exceeds 32MB limit"}}`)}, + `POST "https://api.anthropic.com/v1/messages": 413 Payload Too Large {"type":"error","error":{"type":"request_too_large","message":"Request exceeds 32MB limit"}}`, + )}, want: OverflowKindWire, }, { name: "openai context_length_exceeded structured code", err: errors.New( - `POST "https://api.openai.com/v1/chat/completions": 400 Bad Request {"error":{"message":"maximum context length is 128000 tokens","type":"invalid_request_error","code":"context_length_exceeded"}}`), + `POST "https://api.openai.com/v1/chat/completions": 400 Bad Request {"error":{"message":"maximum context length is 128000 tokens","type":"invalid_request_error","code":"context_length_exceeded"}}`, + ), want: OverflowKindTokens, }, { @@ -196,7 +198,8 @@ func TestClassifyOverflow(t *testing.T) { { name: "vertex 413 with prompt-too-long body — wire wins via 413", err: &StatusError{StatusCode: 413, Err: errors.New( - `413 Payload Too Large {"error":{"message":"Prompt is too long"}}`)}, + `413 Payload Too Large {"error":{"message":"Prompt is too long"}}`, + )}, want: OverflowKindWire, }, @@ -204,13 +207,15 @@ func TestClassifyOverflow(t *testing.T) { { name: "anthropic 400 prompt too long", err: errors.New( - `POST "https://api.anthropic.com/v1/messages": 400 Bad Request {"type":"error","error":{"type":"invalid_request_error","message":"prompt is too long: 137500 tokens > 135000 maximum"}}`), + `POST "https://api.anthropic.com/v1/messages": 400 Bad Request {"type":"error","error":{"type":"invalid_request_error","message":"prompt is too long: 137500 tokens > 135000 maximum"}}`, + ), want: OverflowKindTokens, }, { name: "gemini input token count exceeds maximum", err: errors.New( - `googleapi: Error 400: input token count 200000 exceeds the maximum of 128000`), + `googleapi: Error 400: input token count 200000 exceeds the maximum of 128000`, + ), want: OverflowKindTokens, }, { @@ -260,7 +265,8 @@ func TestClassifyOverflow(t *testing.T) { { name: "anthropic image exceeds size", err: errors.New( - `400 Bad Request {"error":{"message":"image exceeds 5 MB maximum: 5316852 bytes > 5242880 bytes"}}`), + `400 Bad Request {"error":{"message":"image exceeds 5 MB maximum: 5316852 bytes > 5242880 bytes"}}`, + ), want: OverflowKindMedia, }, { @@ -351,7 +357,8 @@ func TestOverflowKindOf(t *testing.T) { t.Parallel() // Anthropic 413 with structured body → wire under := &StatusError{StatusCode: 413, Err: errors.New( - `413 Payload Too Large {"type":"error","error":{"type":"request_too_large","message":"too big"}}`)} + `413 Payload Too Large {"type":"error","error":{"type":"request_too_large","message":"too big"}}`, + )} wrapped := NewContextOverflowError(under) assert.Equal(t, OverflowKindWire, wrapped.Kind) diff --git a/pkg/rag/strategy/bm25_database.go b/pkg/rag/strategy/bm25_database.go index 230ef0c8ae..af1a1d1218 100644 --- a/pkg/rag/strategy/bm25_database.go +++ b/pkg/rag/strategy/bm25_database.go @@ -135,7 +135,8 @@ func (d *bm25DB) GetAllDocuments(ctx context.Context) ([]database.Document, erro ` SELECT id, source_path, chunk_index, content, file_hash, created_at FROM %s - `, d.docsTable) + `, d.docsTable, + ) rows, err := d.db.QueryContext(ctx, query) if err != nil { @@ -181,7 +182,8 @@ func (d *bm25DB) SetFileMetadata(ctx context.Context, metadata database.FileMeta file_hash = excluded.file_hash, last_indexed = CURRENT_TIMESTAMP, chunk_count = excluded.chunk_count - `, d.metadataTable) + `, d.metadataTable, + ) _, err := d.db.ExecContext(ctx, query, metadata.SourcePath, metadata.FileHash, metadata.ChunkCount) return err diff --git a/pkg/rag/strategy/chunked_embeddings_database.go b/pkg/rag/strategy/chunked_embeddings_database.go index 10d8684f24..be5604db47 100644 --- a/pkg/rag/strategy/chunked_embeddings_database.go +++ b/pkg/rag/strategy/chunked_embeddings_database.go @@ -78,7 +78,8 @@ func (d *chunkedVectorDB) createSchema(ctx context.Context) error { PRIMARY KEY (source_path, chunk_index), FOREIGN KEY (source_path) REFERENCES %s(source_path) ON DELETE CASCADE ); - `, d.filesTable, d.tablePrefix, d.filesTable, d.chunksTable, d.filesTable) + `, d.filesTable, d.tablePrefix, d.filesTable, d.chunksTable, d.filesTable, + ) _, err := d.db.ExecContext(ctx, schema) return err @@ -135,7 +136,8 @@ func (d *chunkedVectorDB) SearchSimilarVectors(ctx context.Context, queryEmbeddi SELECT c.source_path, c.chunk_index, c.content, c.embedding, f.file_hash, f.indexed_at FROM %s c JOIN %s f ON c.source_path = f.source_path - `, d.chunksTable, d.filesTable) + `, d.chunksTable, d.filesTable, + ) rows, err := d.db.QueryContext(ctx, query) if err != nil { diff --git a/pkg/rag/strategy/semantic_embeddings.go b/pkg/rag/strategy/semantic_embeddings.go index 6265671219..994519944c 100644 --- a/pkg/rag/strategy/semantic_embeddings.go +++ b/pkg/rag/strategy/semantic_embeddings.go @@ -179,7 +179,8 @@ func NewSemanticEmbeddingsFromConfig(ctx context.Context, cfg latest.RAGStrategy // Configure the embedding input builder to use the chat LLM store.SetEmbeddingInputBuilder(newLLMSemanticEmbeddingBuilder( - chatProvider, js.NewJsExpander(buildCtx.Env), semanticPrompt, usageTracker, useASTContext)) + chatProvider, js.NewJsExpander(buildCtx.Env), semanticPrompt, usageTracker, useASTContext, + )) return &Config{ Name: strategyName, diff --git a/pkg/rag/strategy/semantic_embeddings_database.go b/pkg/rag/strategy/semantic_embeddings_database.go index 4ef4ad53de..361614ef3f 100644 --- a/pkg/rag/strategy/semantic_embeddings_database.go +++ b/pkg/rag/strategy/semantic_embeddings_database.go @@ -80,7 +80,8 @@ func (d *semanticVectorDB) createSchema(ctx context.Context) error { PRIMARY KEY (source_path, chunk_index), FOREIGN KEY (source_path) REFERENCES %s(source_path) ON DELETE CASCADE ); - `, d.filesTable, d.tablePrefix, d.filesTable, d.chunksTable, d.filesTable) + `, d.filesTable, d.tablePrefix, d.filesTable, d.chunksTable, d.filesTable, + ) if _, err := d.db.ExecContext(ctx, schema); err != nil { return err @@ -142,7 +143,8 @@ func (d *semanticVectorDB) SearchSimilarVectors(ctx context.Context, queryEmbedd SELECT c.source_path, c.chunk_index, c.content, c.embedding, c.embedding_input, f.file_hash, f.indexed_at FROM %s c JOIN %s f ON c.source_path = f.source_path - `, d.chunksTable, d.filesTable) + `, d.chunksTable, d.filesTable, + ) rows, err := d.db.QueryContext(ctx, query) if err != nil { diff --git a/pkg/runtime/agent_delegation.go b/pkg/runtime/agent_delegation.go index 92dd017824..a435392610 100644 --- a/pkg/runtime/agent_delegation.go +++ b/pkg/runtime/agent_delegation.go @@ -814,5 +814,6 @@ func (r *LocalRuntime) applyForceHandoff(ctx context.Context, sess *session.Sess "off to agents that you see in the conversation history from previous agents, as those were " + "available to different agents with different capabilities. Look at the conversation history " + "for context, continue the work from where the previous agent stopped, and complete your " + - "part of the task.")) + "part of the task.", + )) } diff --git a/pkg/runtime/loop.go b/pkg/runtime/loop.go index 8b7850be0b..f6d917bc13 100644 --- a/pkg/runtime/loop.go +++ b/pkg/runtime/loop.go @@ -676,12 +676,14 @@ func emptyTurnWarning(res streamResult, prevTurnMadeToolCalls bool, modelID stri "Model %s produced only reasoning and no reply (stop reason: %s). "+ "Thinking-mode models can emit reasoning tokens without a final answer; "+ "the reasoning is not used as the response.", - modelID, reason) + modelID, reason, + ) default: return fmt.Sprintf( "Model %s returned an empty response (stop reason: %s). "+ "This usually means the provider rate-limited the request or the output token limit was reached.", - modelID, reason) + modelID, reason, + ) } } @@ -935,7 +937,8 @@ func (r *LocalRuntime) runTurn( errMsg := fmt.Sprintf( "Agent terminated: detected %d consecutive identical calls to %s. "+ "This indicates a degenerate loop where the model is not making progress.", - consecutive, toolName) + consecutive, toolName, + ) // Mark the session span as Error so loop-termination shows up // in trace status / error-rate dashboards instead of blending // in with normal completions. diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index 87a487b1e0..e28175d834 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -1766,9 +1766,10 @@ type recoveryAuthToolSet struct { func (r *recoveryAuthToolSet) Tools(context.Context) ([]tools.Tool, error) { return nil, nil } func (r *recoveryAuthToolSet) Start(context.Context) error { r.started = true; return nil } -func (r *recoveryAuthToolSet) Stop(context.Context) error { r.started = false; return nil } -func (r *recoveryAuthToolSet) IsStarted() bool { return r.started } -func (r *recoveryAuthToolSet) Restart(context.Context) error { return r.restartErr } + +func (r *recoveryAuthToolSet) Stop(context.Context) error { r.started = false; return nil } +func (r *recoveryAuthToolSet) IsStarted() bool { return r.started } +func (r *recoveryAuthToolSet) Restart(context.Context) error { return r.restartErr } // TestEmitStartupInfo_RecoveryAuthNoticeEmittedOnce is the regression test for // blocking issue 3: when a toolset was previously started and working but the diff --git a/pkg/runtime/structured_output.go b/pkg/runtime/structured_output.go index 639cb90333..148da32ca8 100644 --- a/pkg/runtime/structured_output.go +++ b/pkg/runtime/structured_output.go @@ -142,7 +142,8 @@ func (r *LocalRuntime) handleStructuredOutputCalls( rejectMsg := fmt.Sprintf( "Structured output rejected: %s must be the only tool call in the response. "+ "Finish any other tool use first, then call %s alone with the final answer.", - structuredoutput.ToolName, structuredoutput.ToolName) + structuredoutput.ToolName, structuredoutput.ToolName, + ) for _, tc := range outputCalls { r.rejectStructuredOutputCall(ctx, sess, a, tc, tool, rejectMsg, events) } @@ -319,7 +320,8 @@ func (r *LocalRuntime) structuredOutputStop( } errMsg := fmt.Sprintf( "Agent terminated: the model did not deliver structured output via the %s tool after %d reminders.", - structuredoutput.ToolName, maxStructuredOutputReminders) + structuredoutput.ToolName, maxStructuredOutputReminders, + ) slog.WarnContext(ctx, "Structured output failed: reminders exhausted", "agent", a.Name(), "session_id", sess.ID, "max", maxStructuredOutputReminders) events.Emit(ErrorWithCodeForSession(sess.ID, ErrorCodeStructuredOutputFailed, errMsg)) diff --git a/pkg/session/migrations.go b/pkg/session/migrations.go index 63c20d2b94..6396894a8f 100644 --- a/pkg/session/migrations.go +++ b/pkg/session/migrations.go @@ -104,7 +104,8 @@ func (m *MigrationManager) checkForUnknownMigrations(ctx context.Context) error "%w: you are running docker-agent %s which supports migrations up to %d, "+ "but the session database has migration %d from a newer version; "+ "please upgrade docker-agent to the latest version", - ErrNewerDatabase, version.Version, maxKnownID, maxAppliedID) + ErrNewerDatabase, version.Version, maxKnownID, maxAppliedID, + ) } return nil diff --git a/pkg/teamloader/registry_test.go b/pkg/teamloader/registry_test.go index 6cbc081f52..70f9ef23e3 100644 --- a/pkg/teamloader/registry_test.go +++ b/pkg/teamloader/registry_test.go @@ -117,6 +117,7 @@ func TestCreateMCPTool_BareCommandNotFound_CreatesToolsetAnyway(t *testing.T) { require.NotNil(t, tool) assert.Equal(t, "mcp(stdio cmd=some-nonexistent-mcp-binary)", tools.DescribeToolSet(tool)) } // TestCreateMCPTool_WorkingDir_ReachesSubprocess verifies that working_dir is + // wired all the way through createMCPTool to the underlying stdio command (N5). func TestCreateMCPTool_WorkingDir_ReachesSubprocess(t *testing.T) { t.Setenv("DOCKER_AGENT_TOOLS_DIR", t.TempDir()) diff --git a/pkg/tools/builtin/backgroundjobs/cmd_windows.go b/pkg/tools/builtin/backgroundjobs/cmd_windows.go index 2cbb43fa86..21d8e9a7b6 100644 --- a/pkg/tools/builtin/backgroundjobs/cmd_windows.go +++ b/pkg/tools/builtin/backgroundjobs/cmd_windows.go @@ -32,7 +32,8 @@ func createProcessGroup(proc *os.Process) (*processGroup, error) { job, windows.JobObjectExtendedLimitInformation, uintptr(unsafe.Pointer(&info)), //nolint:gosec // Windows API requires unsafe.Pointer - uint32(unsafe.Sizeof(info))); err != nil { + uint32(unsafe.Sizeof(info)), + ); err != nil { _ = windows.CloseHandle(job) return nil, err } diff --git a/pkg/tools/builtin/mcpcatalog/mcpcatalog.go b/pkg/tools/builtin/mcpcatalog/mcpcatalog.go index f48311f6ea..760e530e94 100644 --- a/pkg/tools/builtin/mcpcatalog/mcpcatalog.go +++ b/pkg/tools/builtin/mcpcatalog/mcpcatalog.go @@ -695,7 +695,8 @@ func (t *Toolset) handleEnable(ctx context.Context, args EnableArgs) (*tools.Too // Live entry — nothing to do. return tools.ResultSuccess(fmt.Sprintf( "server %q is already enabled and connected. Its tools (names starting with %q) are live; proceed with the user's original request using them.", - id, id+"_")), nil + id, id+"_", + )), nil } var notify func() @@ -765,7 +766,8 @@ func (t *Toolset) handleEnable(ctx context.Context, args EnableArgs) (*tools.Too return tools.ResultSuccess(fmt.Sprintf( "enabled %q (%s). Its tools (names starting with %q) are now active. Proceed with the user's original request using them right away; do not stop to ask for confirmation.", - id, server.Title, id+"_")), nil + id, server.Title, id+"_", + )), nil } // handleEnableStartError translates a failed Start() into a model-facing @@ -807,13 +809,15 @@ func (t *Toolset) handleEnableStartError(ctx context.Context, id string, server t.disableAfterDecline(ctx, id, wrapped) return tools.ResultError(fmt.Sprintf( "user declined the authorization dialog for %q (%s). No tools were activated — do NOT claim the server is connected and do NOT call any %q tools. Tell the user the request needs them to authorize the connection. If the user then says \"yes\", \"retry\", or re-asks for the same thing, call %s for %q again to surface a fresh authorization dialog.", - id, server.Title, id+"_", ToolNameEnable, id)) + id, server.Title, id+"_", ToolNameEnable, id, + )) case mcp.IsAuthorizationRequired(err): slog.DebugContext(ctx, "Remote MCP server enable deferred: authorization required, leaving in enabled set for next interactive Tools() / enable to retry", "id", id, "error", err) return tools.ResultSuccess(fmt.Sprintf( "enable requested for %q (%s); authorization is required and the host will surface the dialog. On your next turn, if tools whose names start with %q appear in your available tools, proceed with the user's original request using them. If NO such tools appear, the user dismissed the dialog — tell them the request needs them to authorize, and call %s for %q again if they want to retry.", - id, server.Title, id+"_", ToolNameEnable, id)) + id, server.Title, id+"_", ToolNameEnable, id, + )) case errors.Is(err, context.Canceled): // Roll back. The cancellation reaches us via the parent-ctx // stash that handleUnmanagedOAuthFlow observes (oauth.go's @@ -829,12 +833,14 @@ func (t *Toolset) handleEnableStartError(ctx context.Context, id string, server t.disableAfterDecline(ctx, id, wrapped) return tools.ResultError(fmt.Sprintf( "enable cancelled for %q before the connection completed — the user stopped the turn while authorization was pending. No tools were activated. Tell the user the request needs them to authorize the connection. Only call %s for %q again if the user asks to retry.", - id, ToolNameEnable, id)) + id, ToolNameEnable, id, + )) default: t.disableAfterDecline(ctx, id, wrapped) return tools.ResultError(fmt.Sprintf( "failed to connect to %q (%s): %v. No tools were activated — do NOT claim the server is connected. Report the failure to the user; they may need to fix their network or, if the server's credentials changed, call %s for %q before re-enabling.", - id, server.Title, err, ToolNameResetAuth, id)) + id, server.Title, err, ToolNameResetAuth, id, + )) } } diff --git a/pkg/tools/builtin/scheduler/schedule.go b/pkg/tools/builtin/scheduler/schedule.go index 6debf66354..e1decfc8c6 100644 --- a/pkg/tools/builtin/scheduler/schedule.go +++ b/pkg/tools/builtin/scheduler/schedule.go @@ -62,7 +62,8 @@ func parseWhen(when string, now time.Time) (next time.Time, interval time.Durati default: return time.Time{}, 0, fmt.Errorf( - "unrecognized schedule %q: use in:, at:, every:, or minutely/hourly/daily/weekly", when) + "unrecognized schedule %q: use in:, at:, every:, or minutely/hourly/daily/weekly", when, + ) } } diff --git a/pkg/tools/builtin/shell/cmd_windows.go b/pkg/tools/builtin/shell/cmd_windows.go index 85d4145786..460234e109 100644 --- a/pkg/tools/builtin/shell/cmd_windows.go +++ b/pkg/tools/builtin/shell/cmd_windows.go @@ -32,7 +32,8 @@ func createProcessGroup(proc *os.Process) (*processGroup, error) { job, windows.JobObjectExtendedLimitInformation, uintptr(unsafe.Pointer(&info)), //nolint:gosec // Windows API requires unsafe.Pointer - uint32(unsafe.Sizeof(info))); err != nil { + uint32(unsafe.Sizeof(info)), + ); err != nil { _ = windows.CloseHandle(job) return nil, err } diff --git a/pkg/tools/builtin/structuredoutput/structuredoutput.go b/pkg/tools/builtin/structuredoutput/structuredoutput.go index 866a5d03f9..690cf8ace5 100644 --- a/pkg/tools/builtin/structuredoutput/structuredoutput.go +++ b/pkg/tools/builtin/structuredoutput/structuredoutput.go @@ -104,7 +104,8 @@ func (t *OutputTool) Definition() tools.Tool { "Deliver the final answer of this conversation as structured output (%s). "+ "The arguments must be a JSON object matching the tool's parameter schema. "+ "Call this tool alone, with no other tool calls in the same response; a valid call ends the turn.", - t.cfg.Name) + t.cfg.Name, + ) if t.cfg.Description != "" { description += " Expected content: " + t.cfg.Description } diff --git a/pkg/tools/builtin/webhook/webhook.go b/pkg/tools/builtin/webhook/webhook.go index a8448a4427..8b627d1097 100644 --- a/pkg/tools/builtin/webhook/webhook.go +++ b/pkg/tools/builtin/webhook/webhook.go @@ -184,7 +184,8 @@ func (t *ToolSet) send(ctx context.Context, args SendArgs, rt tools.Runtime) (*t } if wait, limited := t.rateLimited(now); limited { return tools.ResultError(fmt.Sprintf( - "Error: rate limited; wait %s before sending another notification.", wait.Round(time.Millisecond))), nil + "Error: rate limited; wait %s before sending another notification.", wait.Round(time.Millisecond), + )), nil } t.markSent(args, now) @@ -200,7 +201,8 @@ func (t *ToolSet) send(ctx context.Context, args SendArgs, rt tools.Runtime) (*t }) return tools.ResultSuccess(fmt.Sprintf( "Queued delivery to the %s webhook. You will only be notified if it ultimately fails.", - normalizeProvider(t.cfg.Provider))), nil + normalizeProvider(t.cfg.Provider), + )), nil } cancel() } diff --git a/pkg/tools/mcp/oauth_test.go b/pkg/tools/mcp/oauth_test.go index 699829f786..5141e9d318 100644 --- a/pkg/tools/mcp/oauth_test.go +++ b/pkg/tools/mcp/oauth_test.go @@ -2888,7 +2888,8 @@ func TestResolveClientCredentials_NoDCR_PromptsUser(t *testing.T) { clientID, clientSecret, scopes, err := transport.resolveClientCredentials( t.Context(), &AuthorizationServerMetadata{}, "https://example.test/cb", - []string{"challenge-scope"}, []string{"prm-scope"}) + []string{"challenge-scope"}, []string{"prm-scope"}, + ) require.NoError(t, err) assert.Equal(t, "user-client-id", clientID) assert.Equal(t, "user-secret", clientSecret) @@ -3017,7 +3018,8 @@ func TestResolveClientCredentials_PromptDeclined(t *testing.T) { } clientID, clientSecret, _, err := transport.resolveClientCredentials( - t.Context(), &AuthorizationServerMetadata{}, "https://example.test/cb", nil, nil) + t.Context(), &AuthorizationServerMetadata{}, "https://example.test/cb", nil, nil, + ) require.Error(t, err) assert.Empty(t, clientID) assert.Empty(t, clientSecret) @@ -3052,7 +3054,8 @@ func TestResolveClientCredentials_PromptMissingClientID(t *testing.T) { } clientID, clientSecret, _, err := transport.resolveClientCredentials( - t.Context(), &AuthorizationServerMetadata{}, "https://example.test/cb", nil, nil) + t.Context(), &AuthorizationServerMetadata{}, "https://example.test/cb", nil, nil, + ) require.Error(t, err) assert.Empty(t, clientID) assert.Empty(t, clientSecret) @@ -3071,7 +3074,8 @@ func TestResolveClientCredentials_NoElicitationBridgeDefersAuth(t *testing.T) { transport := &oauthTransport{baseURL: "https://mcp.example.test/mcp"} clientID, clientSecret, _, err := transport.resolveClientCredentials( - t.Context(), &AuthorizationServerMetadata{}, "https://example.test/cb", nil, nil) + t.Context(), &AuthorizationServerMetadata{}, "https://example.test/cb", nil, nil, + ) require.Error(t, err) assert.Empty(t, clientID) assert.Empty(t, clientSecret) diff --git a/pkg/tui/components/sidebar/sidebar.go b/pkg/tui/components/sidebar/sidebar.go index 7c7d86b2ac..02698824f7 100644 --- a/pkg/tui/components/sidebar/sidebar.go +++ b/pkg/tui/components/sidebar/sidebar.go @@ -1962,15 +1962,18 @@ func (m *model) oneBudgetLine(s runtime.BudgetStatus, nameWidth int) string { var parts []string if s.MaxCost > 0 { parts = append(parts, budgetPartStyle(s.Cost, s.MaxCost).Render( - toolcommon.FormatCostPrecise(s.Cost)+"/"+toolcommon.FormatCostPrecise(s.MaxCost))) + toolcommon.FormatCostPrecise(s.Cost)+"/"+toolcommon.FormatCostPrecise(s.MaxCost), + )) } if s.MaxTokens > 0 { parts = append(parts, budgetPartStyle(float64(s.Tokens), float64(s.MaxTokens)).Render( - toolcommon.FormatTokenCount(s.Tokens)+"/"+toolcommon.FormatTokenCount(s.MaxTokens))) + toolcommon.FormatTokenCount(s.Tokens)+"/"+toolcommon.FormatTokenCount(s.MaxTokens), + )) } if s.MaxTimeSeconds > 0 { parts = append(parts, budgetPartStyle(s.ElapsedSeconds, s.MaxTimeSeconds).Render( - formatBudgetDuration(s.ElapsedSeconds)+"/"+formatBudgetDuration(s.MaxTimeSeconds))) + formatBudgetDuration(s.ElapsedSeconds)+"/"+formatBudgetDuration(s.MaxTimeSeconds), + )) } if len(parts) == 0 { return "" diff --git a/pkg/tui/dialog/multi_choice.go b/pkg/tui/dialog/multi_choice.go index 63de7dc962..524b9f550c 100644 --- a/pkg/tui/dialog/multi_choice.go +++ b/pkg/tui/dialog/multi_choice.go @@ -657,7 +657,8 @@ func (d *multiChoiceDialog) renderOption(num int, label string, isSelected bool, // Calculate available width for label (allow word wrap) labelWidth := max( // -1 for space between number box and label - contentWidth-numBoxWidth-1, multiChoiceMinLabelWidth) + contentWidth-numBoxWidth-1, multiChoiceMinLabelWidth, + ) // Apply width constraint for word wrapping var labelRendered string diff --git a/pkg/tui/dialog/plan_browser.go b/pkg/tui/dialog/plan_browser.go index af7b01ce60..4f7f80d6ce 100644 --- a/pkg/tui/dialog/plan_browser.go +++ b/pkg/tui/dialog/plan_browser.go @@ -454,7 +454,8 @@ func planMutationGuard(p plans.Plan, action string) tea.Cmd { return nil } return notification.InfoCmd(fmt.Sprintf( - "Session plans don't support %s: they belong to their session and carry no shared-plan metadata. Press e to edit the plan body, or use a shared plan.", action)) + "Session plans don't support %s: they belong to their session and carry no shared-plan metadata. Press e to edit the plan body, or use a shared plan.", action, + )) } if p.Version == nil { return notification.ErrorCmd(fmt.Sprintf("Cannot %s %q: no version is known; refresh (r) and retry.", action, p.Name)) diff --git a/pkg/tui/dialog/tour_offer.go b/pkg/tui/dialog/tour_offer.go index 519c2f71f2..5e1c63d0f9 100644 --- a/pkg/tui/dialog/tour_offer.go +++ b/pkg/tui/dialog/tour_offer.go @@ -99,13 +99,15 @@ func (d *tourOfferDialog) View() string { AddSeparator(). AddSpace(). AddContent(styles.BaseStyle.Width(contentWidth).Render( - "First time here? Learn docker agent by doing: a hands-on tour, right in this chat. Takes two minutes, Esc leaves anytime.")) + "First time here? Learn docker agent by doing: a hands-on tour, right in this chat. Takes two minutes, Esc leaves anytime.", + )) if d.showTelemetryNotice { content = content. AddSpace(). AddContent(styles.MutedStyle.Width(contentWidth).Render( - "Anonymous usage data helps improve docker agent. Opt out with TELEMETRY_ENABLED=false.")) + "Anonymous usage data helps improve docker agent. Opt out with TELEMETRY_ENABLED=false.", + )) } body := content. diff --git a/pkg/tui/handlers.go b/pkg/tui/handlers.go index 5c3c2acc25..eccde07987 100644 --- a/pkg/tui/handlers.go +++ b/pkg/tui/handlers.go @@ -244,7 +244,8 @@ func (m *appModel) handleCompactSession(msg messages.CompactSessionMsg) (tea.Mod } return m, notification.InfoCmd(fmt.Sprintf( "Compaction requested for %s; it runs at the session's next safe point.", - compactTargetLabel(msg))) + compactTargetLabel(msg), + )) } // compactTargetsCurrentSession reports whether msg addresses the current diff --git a/pkg/tui/plans.go b/pkg/tui/plans.go index 0e1b8f7c34..8b10dc6e9e 100644 --- a/pkg/tui/plans.go +++ b/pkg/tui/plans.go @@ -448,7 +448,8 @@ func (m *appModel) handlePlanExportResult(msg planExportResultMsg) (tea.Model, t switch { case msg.exists: return m, notification.ErrorCmd( - msg.path + " already exists — move it away, or export to a custom path with 'docker agent plans export'.") + msg.path + " already exists — move it away, or export to a custom path with 'docker agent plans export'.", + ) case msg.statErr != nil: return m, notification.ErrorCmd(fmt.Sprintf("Cannot export to %s: %v", msg.path, msg.statErr)) case msg.err != nil: @@ -654,7 +655,8 @@ func (m *appModel) handlePlanEditReady(msg planEditReadyMsg) (tea.Model, tea.Cmd if msg.currentVersion != msg.expectedVersion { cmds := []tea.Cmd{notification.WarningCmd(fmt.Sprintf( "Plan %q is at v%d now (you read v%d). Data refreshed — review and press e again.", - msg.ref.Name, msg.currentVersion, msg.expectedVersion))} + msg.ref.Name, msg.currentVersion, msg.expectedVersion, + ))} cmds = m.appendPlanRefreshCmd(cmds) return m, tea.Sequence(cmds...) } @@ -728,7 +730,8 @@ func (m *appModel) handlePlanEditorClosed(msg planEditorClosedMsg) (tea.Model, t // exited non-zero); the draft is kept so no edit is ever lost. return m, tea.Sequence( notification.ErrorCmd(fmt.Sprintf("Editor error: %v", msg.err)), - notification.InfoCmd("Your draft is kept at "+msg.path)) + notification.InfoCmd("Your draft is kept at "+msg.path), + ) } // Both the draft read and the persistence call run in a command: reading @@ -809,7 +812,8 @@ func (m *appModel) handlePlanWriteResult(msg planWriteResultMsg) (tea.Model, tea case msg.readErr != nil: return m, tea.Sequence( notification.ErrorCmd(fmt.Sprintf("Failed to read edited plan: %v", msg.readErr)), - notification.InfoCmd("Your draft is kept at "+msg.draftPath)) + notification.InfoCmd("Your draft is kept at "+msg.draftPath), + ) case msg.emptyDraft: switch { case msg.create: @@ -852,11 +856,13 @@ func (m *appModel) planEditorFailureCmd(err error, draftPath string) tea.Cmd { if errors.As(err, &conflict) { text := fmt.Sprintf( "Version conflict on %q: it is at v%d, you edited v%d. Your draft is kept at %s — refresh and retry from it.", - conflict.Name, conflict.Current, conflict.Expected, draftPath) + conflict.Name, conflict.Current, conflict.Expected, draftPath, + ) if conflict.Expected == 0 { text = fmt.Sprintf( "Plan %q already exists (v%d). Your draft is kept at %s — pick another name or edit the existing plan.", - conflict.Name, conflict.Current, draftPath) + conflict.Name, conflict.Current, draftPath, + ) } cmds := []tea.Cmd{notification.ErrorCmd(text)} cmds = m.appendPlanRefreshCmd(cmds) @@ -875,7 +881,8 @@ func (m *appModel) planWriteFailureCmd(err error) tea.Cmd { if errors.As(err, &conflict) { cmds := []tea.Cmd{notification.ErrorCmd(fmt.Sprintf( "Version conflict on %q: it changed to v%d since you read v%d. Data refreshed — review and retry.", - conflict.Name, conflict.Current, conflict.Expected))} + conflict.Name, conflict.Current, conflict.Expected, + ))} cmds = m.appendPlanRefreshCmd(cmds) return tea.Sequence(cmds...) } @@ -892,7 +899,8 @@ func (m *appModel) planTimeoutCmd(err error) tea.Cmd { } return notification.ErrorCmd(fmt.Sprintf( "Plan write timed out after %s — the plan store may be locked by another process. Retry shortly.", - m.planMutationTimeoutOrDefault())) + m.planMutationTimeoutOrDefault(), + )) } // planReadFailureCmd reports a failed plan read (list, get, export, or the @@ -905,7 +913,8 @@ func (m *appModel) planReadFailureCmd(err error) tea.Cmd { } return notification.ErrorCmd(fmt.Sprintf( "Plan read timed out after %s — plan storage may be unavailable. Retry shortly.", - m.planReadTimeoutOrDefault())) + m.planReadTimeoutOrDefault(), + )) } // planVersionOf reads a shared plan's version defensively; the service @@ -931,7 +940,8 @@ func planErrorCmd(err error) tea.Cmd { case errors.As(err, &conflict): return notification.ErrorCmd(fmt.Sprintf( "Version conflict on plan %q: expected v%d but it is at v%d. Refresh (r) and retry.", - conflict.Name, conflict.Expected, conflict.Current)) + conflict.Name, conflict.Expected, conflict.Current, + )) case errors.As(err, ¬Found): return notification.WarningCmd(fmt.Sprintf("No %s plan %q — it may have been deleted; refresh (r).", notFound.Scope, notFound.Name)) case errors.As(err, &validation): @@ -950,7 +960,8 @@ func planWarningsCmds(warnings []string) []tea.Cmd { return nil } return []tea.Cmd{notification.WarningCmd(fmt.Sprintf( - "%d plan(s) could not be read: %s", len(warnings), strings.Join(warnings, "; ")))} + "%d plan(s) could not be read: %s", len(warnings), strings.Join(warnings, "; "), + ))} } // handleSessionPlanUpdatedEvent forwards the event to the chat page like any