update docs according to newest config version 2 (#2186)

This commit is contained in:
Cytown
2026-03-30 17:59:56 +08:00
committed by GitHub
parent 803b8bc02f
commit 010d807e61
23 changed files with 323 additions and 265 deletions
+80 -81
View File
@@ -11,24 +11,35 @@ PicoClaw uses a schema versioning system for `config.json` to ensure smooth upgr
- **Changes**: Added `version` field to Config struct
- **Migration**: No structural changes needed for existing configs
### Version 2
- **Introduction**: Model enable/disable support and channel config unification
- **Changes**:
- Added `enabled` field to `ModelConfig` — allows disabling individual model entries without removing them
- During V1→V2 migration, `enabled` is auto-inferred: models with API keys or the reserved `local-model` name are enabled; others default to disabled
- Migrated legacy channel fields: Discord `mention_only``group_trigger.mention_only`, OneBot `group_trigger_prefix``group_trigger.prefixes`
- V0 configs now migrate directly to CurrentVersion (V2) instead of going through V1
- `makeBackup()` now uses date-only suffix (e.g., `config.json.20260330.bak`) and also backs up `.security.yml`
## How It Works
### Automatic Migration
When you load a config file:
1. The system first reads the `version` field from the JSON
2. Based on the detected version, it loads the appropriate config struct (`ConfigV0`, `ConfigV1`, etc.)
2. Based on the detected version, it loads the appropriate config struct (`configV0`, `configV1`, etc.)
3. If the loaded version is less than the latest, migrations are applied incrementally
4. The version number is updated automatically
5. The migrated config is automatically saved back to disk
4. Before saving, the system automatically creates a date-stamped backup of `config.json` and `.security.yml`
5. The version number is updated automatically
6. The migrated config is automatically saved back to disk
### Version Field
The `version` field in `config.json` indicates the schema version:
- `0` or missing: Legacy config (no version field)
- `1`: Current version with versioning support
- `1`: Previous version (will be auto-migrated to V2 on load)
- `2`: Current version
```json
{
"version": 1,
"version": 2,
"agents": {...},
...
}
@@ -54,25 +65,25 @@ type ConfigV2 struct {
### Step 2: Update Current Config Version
```go
const CurrentConfigVersion = 2 // Increment this
const CurrentVersion = 2 // Increment this
```
### Step 3: Add a Loader Function
```go
// loadConfigV2 loads a version 2 config
func loadConfigV2(data []byte) (*Config, error) {
// loadConfigV3 loads a version 3 config
func loadConfigV3(data []byte) (*Config, error) {
cfg := DefaultConfig()
// Parse to ConfigV2 struct
var v2 ConfigV2
if err := json.Unmarshal(data, &v2); err != nil {
// Parse to ConfigV3 struct
var v3 ConfigV3
if err := json.Unmarshal(data, &v3); err != nil {
return nil, err
}
// Convert to current Config
cfg.Version = v2.Version
cfg.Agents = v2.Agents
cfg.Version = v3.Version
cfg.Agents = v3.Agents
// ... map other fields
return cfg, nil
@@ -82,29 +93,12 @@ func loadConfigV2(data []byte) (*Config, error) {
### Step 4: Add Migration Logic
```go
// applyMigration applies a single migration step from fromVersion to toVersion
func applyMigration(cfg *Config, fromVersion, toVersion int) (*Config, error) {
switch toVersion {
case 1:
// Migration from version 0 to 1
return &Config{
Version: 1,
Agents: cfg.Agents,
// ... copy all fields
}, nil
case 2:
// Migration from version 1 to 2
// Example: Move or rename fields
migrated := *cfg
migrated.Version = 2
// Apply structural changes
if cfg.SomeOldField != "" {
migrated.SomeNewField = cfg.SomeOldField
}
return &migrated, nil
default:
return nil, fmt.Errorf("unsupported migration target version: %d", toVersion)
}
func (c *configV2) Migrate() (*Config, error) {
// Apply V2→V3 structural changes here
migrated := &c.Config
migrated.Version = 3
// Apply structural changes
return migrated, nil
}
```
@@ -120,7 +114,9 @@ func LoadConfig(path string) (*Config, error) {
case 1:
cfg, err = loadConfigV1(data)
case 2:
cfg, err = loadConfigV2(data)
cfg, err = loadConfig(data)
case 3:
cfg, err = loadConfigV3(data)
default:
return nil, fmt.Errorf("unsupported config version: %d", versionInfo.Version)
}
@@ -134,22 +130,22 @@ func LoadConfig(path string) (*Config, error) {
Create a test in `config_migration_test.go`:
```go
func TestMigrateV1ToV2(t *testing.T) {
// Create a version 1 config
v1Config := Config{
Version: 1,
func TestMigrateV2ToV3(t *testing.T) {
// Create a version 2 config
v2Config := Config{
Version: 2,
// ... set up test data
}
// Apply migration
migrated, err := applyMigration(&v1Config, 1, 2)
migrated, err := v2Config.Migrate()
if err != nil {
t.Fatalf("Migration failed: %v", err)
}
// Verify version is updated
if migrated.Version != 2 {
t.Errorf("Expected version 2, got %d", migrated.Version)
if migrated.Version != 3 {
t.Errorf("Expected version 3, got %d", migrated.Version)
}
// Verify data is preserved/transformed correctly
@@ -164,58 +160,60 @@ func TestMigrateV1ToV2(t *testing.T) {
3. **No Data Loss**: Migrations should preserve all user settings
4. **Idempotent**: Running the same migration multiple times should be safe
5. **Auto-Save**: Migrated configs are automatically saved to update the user's file
6. **Test Thoroughly**: Test with real user config files
7. **Update Defaults**: Keep `defaults.go` in sync with the latest schema
6. **Auto-Backup**: Before saving, the system creates a date-stamped backup of `config.json` and `.security.yml`
7. **Test Thoroughly**: Test with real user config files
8. **Update Defaults**: Keep `defaults.go` in sync with the latest schema
## Example Migration
### Scenario: Adding a new field with default value
Old config (version 1):
```json
{
"version": 1,
"agents": {
"defaults": {
"max_tokens": 32768
}
}
}
```
Migration to version 2:
```go
case 2:
migrated := *cfg
migrated.Version = 2
// Add new field with default value if not set
if migrated.Agents.Defaults.NewFeatureEnabled == false {
// Use default value
}
return &migrated, nil
```
New config (version 2):
Old config (version 2):
```json
{
"version": 2,
"agents": {
"defaults": {
"max_tokens": 32768,
"new_feature_enabled": false
"model_list": [
{
"model_name": "gpt-5.4",
"model": "openai/gpt-5.4"
}
}
]
}
```
Migration to version 3:
```go
func (c *configV2) Migrate() (*Config, error) {
migrated := &c.Config
migrated.Version = 3
// Add new field with default value if not set
// ...
return migrated, nil
}
```
New config (version 3):
```json
{
"version": 3,
"model_list": [
{
"model_name": "gpt-5.4",
"model": "openai/gpt-5.4",
"new_option": true
}
]
}
```
## Troubleshooting
### Config Not Upgrading
- Check that `CurrentConfigVersion` is incremented
- Verify migration logic in `applyMigration()` handles the target version
- Ensure `migrateConfig()` is called in `LoadConfig()`
- Check that `CurrentVersion` is incremented
- Verify migration logic handles the target version
- Ensure `Migrate()` is called in `LoadConfig()`
### Migration Errors
- Check error messages for specific migration failures
@@ -227,4 +225,5 @@ New config (version 2):
- Ensure all fields are copied during migration
- Check that the migration doesn't overwrite values with defaults unnecessarily
- Review the conversion logic in the loader functions
- Check the auto-backup files (e.g., `config.json.20260330.bak`) to recover original data