diff --git a/forge-cli/cmd/root.go b/forge-cli/cmd/root.go index 6609e09..568082f 100644 --- a/forge-cli/cmd/root.go +++ b/forge-cli/cmd/root.go @@ -15,6 +15,7 @@ var ( themeOverride string appVersion = "dev" + appCommit = "none" ) var rootCmd = &cobra.Command{ @@ -52,6 +53,7 @@ func init() { // SetVersionInfo sets the version and commit for display. func SetVersionInfo(version, commit string) { appVersion = version + appCommit = commit rootCmd.Version = version rootCmd.SetVersionTemplate(fmt.Sprintf("forge %s (commit: %s)\n", version, commit)) } diff --git a/forge-cli/cmd/run.go b/forge-cli/cmd/run.go index b04bb2d..a06130a 100644 --- a/forge-cli/cmd/run.go +++ b/forge-cli/cmd/run.go @@ -231,6 +231,7 @@ func runRun(cmd *cobra.Command, args []string) error { RateLimitOverride: rateLimitOverride, TracingFlags: tracingFlags, RuntimeVersion: appVersion, + RuntimeCommit: appCommit, }) if err != nil { return fmt.Errorf("creating runner: %w", err) diff --git a/forge-cli/runtime/banner_version_test.go b/forge-cli/runtime/banner_version_test.go new file mode 100644 index 0000000..423e943 --- /dev/null +++ b/forge-cli/runtime/banner_version_test.go @@ -0,0 +1,30 @@ +package runtime + +import "testing" + +// forgeVersionString is what the startup banner shows on the "Forge:" line — +// the forge binary/runtime version, distinct from the agent's forge.yaml +// version. It must degrade cleanly for dev builds (#335). +func TestForgeVersionString(t *testing.T) { + tests := []struct { + name string + version string + commit string + want string + }{ + {"release with commit", "v0.17.0", "51df9a4", "v0.17.0 (commit: 51df9a4)"}, + {"release, no commit baked", "v0.17.0", "none", "v0.17.0"}, + {"release, empty commit", "v0.17.0", "", "v0.17.0"}, + {"dev build (defaults)", "dev", "none", "dev"}, + {"empty version defaults to dev", "", "none", "dev"}, + {"empty version, real commit", "", "abc1234", "dev (commit: abc1234)"}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + r := &Runner{cfg: RunnerConfig{RuntimeVersion: tc.version, RuntimeCommit: tc.commit}} + if got := r.forgeVersionString(); got != tc.want { + t.Errorf("forgeVersionString(%q, %q) = %q, want %q", tc.version, tc.commit, got, tc.want) + } + }) + } +} diff --git a/forge-cli/runtime/runner.go b/forge-cli/runtime/runner.go index 41fc6b9..49f502b 100644 --- a/forge-cli/runtime/runner.go +++ b/forge-cli/runtime/runner.go @@ -106,6 +106,12 @@ type RunnerConfig struct { // the `forge.runtime.version` OTel resource attribute so backends // can compare agent runs across Forge upgrade waves. Empty = "dev". RuntimeVersion string + + // RuntimeCommit is the Forge cli's own build commit (short SHA), + // injected via `-X main.commit`. Shown on the startup banner next to + // RuntimeVersion so a running agent's exact binary is identifiable. + // "none"/"" = unset (a dev build) → the banner shows just the version. + RuntimeCommit string } // ScheduleNotifier is called after a scheduled task completes to deliver the @@ -2848,6 +2854,19 @@ func (r *Runner) createProviderClient(provider string, cfg llm.ClientConfig) (ll return providers.NewClient(provider, cfg) } +// forgeVersionString formats the Forge binary/runtime version for the banner: +// "v0.17.0 (commit: 51df9a4)" when a real commit is baked in, else just the +// version. An empty version reads "dev"; the "none" sentinel (no -X commit) +// suppresses the commit suffix. This is the FORGE binary version, distinct +// from the agent's own forge.yaml version shown on the "Agent:" line. +func (r *Runner) forgeVersionString() string { + v := defaultStr(r.cfg.RuntimeVersion, "dev") + if c := r.cfg.RuntimeCommit; c != "" && c != "none" { + return fmt.Sprintf("%s (commit: %s)", v, c) + } + return v +} + func (r *Runner) printBanner(proxyURL string) { title := "Forge Dev Server" if r.cfg.Host != "" { @@ -2859,6 +2878,7 @@ func (r *Runner) printBanner(proxyURL string) { fmt.Fprintf(os.Stderr, " %s\n", title) fmt.Fprintf(os.Stderr, " ────────────────────────────────────────\n") fmt.Fprintf(os.Stderr, " Agent: %s (v%s)\n", r.cfg.Config.AgentID, r.cfg.Config.Version) + fmt.Fprintf(os.Stderr, " Forge: %s\n", r.forgeVersionString()) fmt.Fprintf(os.Stderr, " Framework: %s\n", r.cfg.Config.Framework) fmt.Fprintf(os.Stderr, " Listen: %s:%d\n", host, r.cfg.Port) if r.cfg.MockTools {