feat: opencode serve — постоянный сервер вместо разовых subprocess (вариант A)
Some checks failed
CI / test (push) Successful in 54s
CI / build-and-package (amd64, linux) (push) Failing after 1h58m55s
CI / build-and-package (amd64, windows) (push) Successful in 59s

Супервайзер Server spawn'ит opencode serve (или ходит на внешний URL),
Runner ходит к нему через 'opencode run --attach <url>'. По умолчанию
serve.enabled=false — историческая spawn-модель сохранена; наличие
serve.url переключает на внешний сервер.

- config: ServeCfg (enabled/hostname/port/url/password) + дефолты; лоадер
  научился int/bool (раньше только string/Duration/struct).
- opencode: Server (Start/Run/Close, /global/health, рестарт упавшего,
  reaper-горутина владеет Wait; .Exited() непригоден для SIGKILL).
- Runner.AttachURL: run --attach <url> при заданном URL, иначе как раньше.
- app: composition root — при включённом serve запускает супервайзер.
- тесты: TestRun_AttachMode, TestServer_{ExternalURL,OwnProcess,Restart}.
This commit is contained in:
Hermes
2026-08-18 08:35:09 +05:00
parent ed11879cbd
commit 2f26b6ae88
8 changed files with 618 additions and 1 deletions

View File

@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"gopkg.in/yaml.v3"
@@ -133,6 +134,18 @@ func applyDefaults(cfg *Config) {
if fv.Kind() == reflect.String {
fv.SetString(meta.defaultVal)
}
// int (значение по умолчанию, напр. port)
if fv.Kind() == reflect.Int {
if n, err := strconv.Atoi(meta.defaultVal); err == nil {
fv.SetInt(int64(n))
}
}
// bool (по умолчанию false/true)
if fv.Kind() == reflect.Bool {
if b, err := strconv.ParseBool(meta.defaultVal); err == nil {
fv.SetBool(b)
}
}
})
}
@@ -156,6 +169,16 @@ func applyEnvOverrides(cfg *Config) {
if fv.Kind() == reflect.String {
fv.SetString(envVal)
}
if fv.Kind() == reflect.Int {
if n, err := strconv.Atoi(envVal); err == nil {
fv.SetInt(int64(n))
}
}
if fv.Kind() == reflect.Bool {
if b, err := strconv.ParseBool(envVal); err == nil {
fv.SetBool(b)
}
}
})
}
@@ -173,6 +196,10 @@ func walk(v reflect.Value, fn func(reflect.Value, fieldMeta)) {
fn(fv, collectMeta(f))
} else if fv.Type() == durType {
fn(fv, collectMeta(f))
} else if fv.Kind() == reflect.Int {
fn(fv, collectMeta(f))
} else if fv.Kind() == reflect.Bool {
fn(fv, collectMeta(f))
} else if fv.Kind() == reflect.Struct {
walk(fv, fn)
}