Skip to content
Merged
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
3 changes: 2 additions & 1 deletion cli/serve/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,14 +677,15 @@ listen_addr = %q
advertise_base_url = %q
access_token = %q
no_auth = %t
show_upgrade = %t

[bootstrap]
default_manager_template = %q
default_worker_template = %q

[sandbox]
provider = %q
`, cfg.Server.ListenAddr, cfg.Server.AdvertiseBaseURL, partiallyMaskSecret(cfg.Server.AccessToken), cfg.Server.NoAuth, cfg.Bootstrap.ResolvedDefaultManagerTemplate(), cfg.Bootstrap.ResolvedDefaultWorkerTemplate(), cfg.Sandbox.Resolved().Provider)
`, cfg.Server.ListenAddr, cfg.Server.AdvertiseBaseURL, partiallyMaskSecret(cfg.Server.AccessToken), cfg.Server.NoAuth, cfg.Server.ShowUpgrade, cfg.Bootstrap.ResolvedDefaultManagerTemplate(), cfg.Bootstrap.ResolvedDefaultWorkerTemplate(), cfg.Sandbox.Resolved().Provider)
if len(cfg.Sandbox.Resolved().DebianRegistriesOverride) > 0 {
content += fmt.Sprintf("debian_registries_override = %s\n", formatModelList(cfg.Sandbox.Resolved().DebianRegistriesOverride))
} else {
Expand Down
6 changes: 6 additions & 0 deletions cli/serve/serve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@ func TestServeForegroundPassesContextToServer(t *testing.T) {
AdvertiseBaseURL: "http://example.test",
AccessToken: "pc-secret",
NoAuth: true,
ShowUpgrade: true,
},
Model: config.ModelConfig{
Provider: "llm-api",
Expand Down Expand Up @@ -619,6 +620,7 @@ func TestServeForegroundPassesContextToServer(t *testing.T) {
`api_key = "sk*****et"`,
`access_token = "pc*****et"`,
`no_auth = true`,
`show_upgrade = true`,
`[sandbox]`,
fmt.Sprintf(`provider = %q`, config.DockerProvider),
`debian_registries_override = []`,
Expand Down Expand Up @@ -1015,6 +1017,7 @@ func TestFormatEffectiveConfigFormatsSectionsWithoutExtraWhitespace(t *testing.T
AdvertiseBaseURL: "http://192.168.2.52:18080",
AccessToken: "your_access_token",
NoAuth: true,
ShowUpgrade: true,
},
Models: config.SingleProfileLLM(config.ModelConfig{
BaseURL: "http://127.0.0.1:4000",
Expand Down Expand Up @@ -1054,6 +1057,7 @@ listen_addr = "0.0.0.0:18080"
advertise_base_url = "http://192.168.2.52:18080"
access_token = "yo*************en"
no_auth = true
show_upgrade = true

[bootstrap]
default_manager_template = "builtin/picoclaw-manager"
Expand Down Expand Up @@ -1111,6 +1115,7 @@ func TestFormatEffectiveConfigIncludesDefaultHubRegistriesWhenOmitted(t *testing
ListenAddr: "127.0.0.1:18080",
AdvertiseBaseURL: "http://127.0.0.1:18080",
AccessToken: "your_access_token",
ShowUpgrade: true,
},
Bootstrap: config.BootstrapConfig{
DefaultManagerTemplate: "builtin/picoclaw-manager",
Expand Down Expand Up @@ -1217,6 +1222,7 @@ func csgHubLiteServeConfig(baseURL string) config.Config {
Server: config.ServerConfig{
ListenAddr: "127.0.0.1:18080",
AccessToken: "pc-secret",
ShowUpgrade: true,
},
Models: config.LLMConfig{
Default: "csghub-lite.Qwen/Qwen3-0.6B-GGUF",
Expand Down
6 changes: 6 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Use `advertise_base_url` when the automatically inferred address is not reachabl

`no_auth` controls whether CSGClaw skips the bearer-token check. The default is `false`. Set it to `true` only for trusted local or development environments.

`show_upgrade` controls whether the Web UI shows upgrade actions. The default is `true`; set it to `false` only when the deployment cannot self-upgrade, such as managed Kubernetes environments.

String values in `config.toml` can reference environment variables with `${NAME}` or `$NAME`. CSGClaw expands them when loading the config and keeps the placeholder form when it later rewrites the same value. If an environment variable is not set, it expands to an empty string.

```toml
Expand All @@ -24,6 +26,7 @@ listen_addr = "0.0.0.0:${PORT}"
advertise_base_url = "http://${IP}:${PORT}"
access_token = "${ACCESS_TOKEN}"
no_auth = false
show_upgrade = true
```

## Model Provider Examples
Expand All @@ -36,6 +39,7 @@ listen_addr = "0.0.0.0:18080"
advertise_base_url = "http://127.0.0.1:18080"
access_token = "your_access_token"
no_auth = false
show_upgrade = true

[models]
default = "csghub-lite.Qwen/Qwen3-0.6B-GGUF"
Expand All @@ -61,6 +65,7 @@ listen_addr = "0.0.0.0:18080"
advertise_base_url = "http://127.0.0.1:18080"
access_token = "your_access_token"
no_auth = false
show_upgrade = true

[models]
default = "remote.gpt-5.4"
Expand All @@ -86,6 +91,7 @@ listen_addr = "0.0.0.0:18080"
advertise_base_url = "http://127.0.0.1:18080"
access_token = "your_access_token"
no_auth = false
show_upgrade = true

[bootstrap]
manager_image_override = ""
Expand Down
6 changes: 6 additions & 0 deletions docs/config.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

`no_auth` 控制 CSGClaw 是否跳过 bearer token 检查,默认值是 `false`。仅建议在可信的本地或开发环境中设置为 `true`。

`show_upgrade` 控制 Web UI 是否展示升级操作。默认值是 `true`;仅在当前部署不能自升级时设置为 `false`,例如托管的 Kubernetes 环境。

`config.toml` 中的字符串值可以通过 `${NAME}` 或 `$NAME` 引用环境变量。CSGClaw 读取配置时会展开这些变量;后续重写同一个值时,会尽量保留占位符形式。如果环境变量未设置,会展开为空字符串。

```toml
Expand All @@ -24,6 +26,7 @@ listen_addr = "0.0.0.0:${PORT}"
advertise_base_url = "http://${IP}:${PORT}"
access_token = "${ACCESS_TOKEN}"
no_auth = false
show_upgrade = true
```

## Model Provider 配置示例
Expand All @@ -36,6 +39,7 @@ listen_addr = "0.0.0.0:18080"
advertise_base_url = "http://127.0.0.1:18080"
access_token = "your_access_token"
no_auth = false
show_upgrade = true

[models]
default = "csghub-lite.Qwen/Qwen3-0.6B-GGUF"
Expand All @@ -61,6 +65,7 @@ listen_addr = "0.0.0.0:18080"
advertise_base_url = "http://127.0.0.1:18080"
access_token = "your_access_token"
no_auth = false
show_upgrade = true

[models]
default = "remote.gpt-5.4"
Expand All @@ -86,6 +91,7 @@ listen_addr = "0.0.0.0:18080"
advertise_base_url = "http://127.0.0.1:18080"
access_token = "your_access_token"
no_auth = false
show_upgrade = true

[bootstrap]
manager_image_override = ""
Expand Down
2 changes: 2 additions & 0 deletions docs/sandbox/csghub.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ populate, at minimum:
- `CSGHUB_API_BASE_URL`, `CSGHUB_USER_TOKEN`
- `CSGCLAW_RESOURCE_ID`, `CSGCLAW_CLUSTER_ID` *(optional but recommended)*
- a `config.toml` whose `[sandbox].provider` is `csghub`, whose
`[server].show_upgrade` is `false` for managed deployments that cannot
self-upgrade, whose
`[bootstrap].manager_image_override` points at the
`csgclaw-agent-sandbox` image when you need to override the built-in
default, and whose `[server]` / `[models]` sections are valid for the
Expand Down
3 changes: 3 additions & 0 deletions internal/api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type bootstrapConfigResponse struct {
DefaultManagerTemplate string `json:"default_manager_template"`
DefaultWorkerTemplate string `json:"default_worker_template"`
RuntimeKind string `json:"runtime_kind"`
ShowUpgrade bool `json:"show_upgrade"`
EffectiveManagerImage string `json:"effective_manager_image"`
AdvertiseBaseURL string `json:"advertise_base_url,omitempty"`
SupportedRuntimeKinds []string `json:"supported_runtime_kinds"`
Expand Down Expand Up @@ -184,6 +185,7 @@ func (h *Handler) loadBootstrapConfig() (config.Config, string, error) {
ListenAddr: config.DefaultListenAddr(),
AccessToken: config.DefaultAccessToken,
NoAuth: false,
ShowUpgrade: true,
},
Bootstrap: config.BootstrapConfig{},
Sandbox: config.SandboxConfig{
Expand All @@ -203,6 +205,7 @@ func bootstrapConfigView(ctx context.Context, cfg config.Config, hubSvc *hub.Ser
resp := bootstrapConfigResponse{
DefaultManagerTemplate: cfg.Bootstrap.ResolvedDefaultManagerTemplate(),
DefaultWorkerTemplate: cfg.Bootstrap.ResolvedDefaultWorkerTemplate(),
ShowUpgrade: cfg.Server.ShowUpgrade,
AdvertiseBaseURL: config.ResolveAdvertiseBaseURL(cfg.Server),
SupportedRuntimeKinds: []string{
agent.RuntimeKindPicoClawSandbox,
Expand Down
23 changes: 23 additions & 0 deletions internal/api/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,29 @@ func TestHandleVersionMethodNotAllowed(t *testing.T) {
}
}

func TestBootstrapConfigViewUsesServerUpgradeVisibility(t *testing.T) {
tests := []struct {
name string
configValue bool
showUpgrade bool
}{
{name: "shown", configValue: true, showUpgrade: true},
{name: "hidden", configValue: false, showUpgrade: false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := bootstrapConfigView(context.Background(), config.Config{
Server: config.ServerConfig{ShowUpgrade: tt.configValue},
}, nil)

if got.ShowUpgrade != tt.showUpgrade {
t.Fatalf("ShowUpgrade = %t, want %t", got.ShowUpgrade, tt.showUpgrade)
}
})
}
}

func TestHandleFeishuRoomsMembers(t *testing.T) {
feishuSvc := feishu.NewServiceWithCreateChatAndAddMembers(
map[string]feishu.AppConfig{
Expand Down
13 changes: 12 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type ServerConfig struct {
AdvertiseBaseURL string
AccessToken string
NoAuth bool
ShowUpgrade bool
}

type ModelConfig struct {
Expand Down Expand Up @@ -415,6 +416,9 @@ func Load(path string) (Config, error) {

modelsCfg := newLLMConfig()
cfg := Config{
Server: ServerConfig{
ShowUpgrade: true,
},
Models: modelsCfg,
LLM: newLLMConfig(),
raw: rawConfigValues{
Expand Down Expand Up @@ -477,6 +481,12 @@ func Load(path string) (Config, error) {
return Config{}, fmt.Errorf("parse server.no_auth: %w", err)
}
cfg.Server.NoAuth = noAuth
case "show_upgrade":
showUpgrade, err := parseBoolValue(rawValue)
if err != nil {
return Config{}, fmt.Errorf("parse server.show_upgrade: %w", err)
}
cfg.Server.ShowUpgrade = showUpgrade
}
case section == "models":
switch key {
Expand Down Expand Up @@ -667,11 +677,12 @@ listen_addr = %q
advertise_base_url = %q
access_token = %q
no_auth = %t
show_upgrade = %t

[bootstrap]
default_manager_template = %q
default_worker_template = %q
`, cfg.rawOrResolvedString(cfg.raw.server.ListenAddr, loadedRaw.server.ListenAddr, cfg.Server.ListenAddr), cfg.rawOrResolvedString(cfg.raw.server.AdvertiseBaseURL, loadedRaw.server.AdvertiseBaseURL, cfg.Server.AdvertiseBaseURL), cfg.rawOrResolvedString(cfg.raw.server.AccessToken, loadedRaw.server.AccessToken, cfg.Server.AccessToken), cfg.Server.NoAuth, cfg.rawOrResolvedString(cfg.raw.bootstrap.DefaultManagerTemplate, loadedRaw.bootstrap.DefaultManagerTemplate, cfg.Bootstrap.ResolvedDefaultManagerTemplate()), cfg.rawOrResolvedString(cfg.raw.bootstrap.DefaultWorkerTemplate, loadedRaw.bootstrap.DefaultWorkerTemplate, cfg.Bootstrap.ResolvedDefaultWorkerTemplate()))
`, cfg.rawOrResolvedString(cfg.raw.server.ListenAddr, loadedRaw.server.ListenAddr, cfg.Server.ListenAddr), cfg.rawOrResolvedString(cfg.raw.server.AdvertiseBaseURL, loadedRaw.server.AdvertiseBaseURL, cfg.Server.AdvertiseBaseURL), cfg.rawOrResolvedString(cfg.raw.server.AccessToken, loadedRaw.server.AccessToken, cfg.Server.AccessToken), cfg.Server.NoAuth, cfg.Server.ShowUpgrade, cfg.rawOrResolvedString(cfg.raw.bootstrap.DefaultManagerTemplate, loadedRaw.bootstrap.DefaultManagerTemplate, cfg.Bootstrap.ResolvedDefaultManagerTemplate()), cfg.rawOrResolvedString(cfg.raw.bootstrap.DefaultWorkerTemplate, loadedRaw.bootstrap.DefaultWorkerTemplate, cfg.Bootstrap.ResolvedDefaultWorkerTemplate()))
sandboxSection := fmt.Sprintf(`
[sandbox]
provider = %q
Expand Down
40 changes: 40 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ models = ["minimax-m2.7"]
if cfg.Server.NoAuth {
t.Fatal("cfg.Server.NoAuth = true, want false")
}
if !cfg.Server.ShowUpgrade {
t.Fatal("cfg.Server.ShowUpgrade = false, want true")
}
if got, want := cfg.Sandbox.Provider, DockerProvider; got != want {
t.Fatalf("cfg.Sandbox.Provider = %q, want %q", got, want)
}
Expand All @@ -98,6 +101,34 @@ models = ["minimax-m2.7"]
}
}

func TestLoadReadsServerShowUpgrade(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "config.toml")
content := `[server]
listen_addr = "127.0.0.1:18080"
show_upgrade = false

[models]
default = "default.minimax-m2.7"

[models.providers.default]
base_url = "http://127.0.0.1:4000"
api_key = "sk"
models = ["minimax-m2.7"]
`
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}

cfg, err := Load(path)
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if cfg.Server.ShowUpgrade {
t.Fatal("cfg.Server.ShowUpgrade = true, want false")
}
}

func TestBootstrapValidateUsesDefaultTemplatesWhenUnset(t *testing.T) {
cfg := BootstrapConfig{
DefaultManagerTemplate: "",
Expand Down Expand Up @@ -735,6 +766,7 @@ func TestSaveWritesModelsSection(t *testing.T) {
ListenAddr: "127.0.0.1:18080",
AdvertiseBaseURL: "http://127.0.0.1:18080",
AccessToken: "shared-token",
ShowUpgrade: true,
},
Models: models,
LLM: models,
Expand Down Expand Up @@ -764,6 +796,9 @@ func TestSaveWritesModelsSection(t *testing.T) {
if !strings.Contains(content, "no_auth = false") {
t.Fatalf("saved config missing server no_auth:\n%s", content)
}
if !strings.Contains(content, "show_upgrade = true") {
t.Fatalf("saved config missing server show_upgrade:\n%s", content)
}
if !strings.Contains(content, "[models]") || !strings.Contains(content, "[models.providers.default]") {
t.Fatalf("saved config missing models sections:\n%s", content)
}
Expand Down Expand Up @@ -820,6 +855,7 @@ func TestSaveWritesCSGHubLiteProvider(t *testing.T) {
Server: ServerConfig{
ListenAddr: "127.0.0.1:18080",
AccessToken: "shared-token",
ShowUpgrade: true,
},
Models: models,
LLM: models,
Expand Down Expand Up @@ -866,6 +902,7 @@ func TestSaveFormatsTopLevelSectionsWithoutExtraWhitespace(t *testing.T) {
AdvertiseBaseURL: "http://192.168.2.52:18080",
AccessToken: "your_access_token",
NoAuth: true,
ShowUpgrade: true,
},
Models: models,
LLM: models,
Expand Down Expand Up @@ -894,6 +931,7 @@ listen_addr = "0.0.0.0:18080"
advertise_base_url = "http://192.168.2.52:18080"
access_token = "your_access_token"
no_auth = true
show_upgrade = true

[bootstrap]
default_manager_template = "builtin/picoclaw-manager"
Expand Down Expand Up @@ -945,6 +983,7 @@ func TestSaveWritesHubConfig(t *testing.T) {
ListenAddr: "127.0.0.1:18080",
AdvertiseBaseURL: "http://127.0.0.1:18080",
AccessToken: "shared-token",
ShowUpgrade: true,
},
Hub: HubConfig{
DefaultRegistry: "builtin",
Expand Down Expand Up @@ -1002,6 +1041,7 @@ func TestSaveWritesEmptySandboxDebianRegistriesOverride(t *testing.T) {
ListenAddr: "127.0.0.1:18080",
AdvertiseBaseURL: "http://127.0.0.1:18080",
AccessToken: "shared-token",
ShowUpgrade: true,
},
Sandbox: SandboxConfig{
Provider: BoxLiteProvider,
Expand Down
1 change: 1 addition & 0 deletions internal/onboard/onboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ func defaultConfig() config.Config {
ListenAddr: config.DefaultListenAddr(),
AccessToken: config.DefaultAccessToken,
NoAuth: false,
ShowUpgrade: true,
},
Bootstrap: config.BootstrapConfig{},
Sandbox: config.SandboxConfig{},
Expand Down
2 changes: 2 additions & 0 deletions internal/onboard/onboard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func TestEnsureStateCreatesConfigAndBootstrapsManagerState(t *testing.T) {
}
for _, want := range []string{
`[server]`,
`show_upgrade = true`,
`[bootstrap]`,
`[sandbox]`,
`provider = ""`,
Expand Down Expand Up @@ -340,6 +341,7 @@ access_token = "your_access_token"
content := string(data)
for _, want := range []string{
`no_auth = false`,
`show_upgrade = true`,
`[bootstrap]`,
`default_manager_template = "builtin/picoclaw-manager"`,
`default_worker_template = "builtin/picoclaw-worker"`,
Expand Down
1 change: 1 addition & 0 deletions web/app/src/hooks/workspace/useWorkspaceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ export function useWorkspaceController() {
currentUserID: displayData.current_user_id,
usersById: conversation.usersById,
collapsedWorkspaceGroups,
showUpgradeControls: bootstrapConfig?.show_upgrade !== false,
onToggleWorkspaceGroup: shell.toggleWorkspaceGroup,
onCreateRoom: () => conversation.openCreateRoomModal(),
onCreateAgent: agent.openCreateAgentModal,
Expand Down
1 change: 1 addition & 0 deletions web/app/src/models/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export type RuntimeBootstrapConfig = {
effective_manager_image?: string | null;
runtime_default_images?: unknown;
runtime_kind?: string | null;
show_upgrade?: boolean | null;
supported_runtime_kinds?: unknown;
};

Expand Down
Loading
Loading