| | @@ -2,10 +2,11 @@ |
| 2 | 2 | package config |
| 3 | 3 | |
| 4 | 4 | import ( |
| 5 | 5 | "fmt" |
| 6 | 6 | "os" |
| 7 | + "time" |
| 7 | 8 | |
| 8 | 9 | "gopkg.in/yaml.v3" |
| 9 | 10 | ) |
| 10 | 11 | |
| 11 | 12 | // Config is the top-level scuttlebot configuration. |
| | @@ -13,10 +14,11 @@ |
| 13 | 14 | Ergo ErgoConfig `yaml:"ergo"` |
| 14 | 15 | Datastore DatastoreConfig `yaml:"datastore"` |
| 15 | 16 | Bridge BridgeConfig `yaml:"bridge"` |
| 16 | 17 | TLS TLSConfig `yaml:"tls"` |
| 17 | 18 | LLM LLMConfig `yaml:"llm"` |
| 19 | + Topology TopologyConfig `yaml:"topology"` |
| 18 | 20 | |
| 19 | 21 | // APIAddr is the address for scuttlebot's own HTTP management API. |
| 20 | 22 | // Ignored when TLS.Domain is set (HTTPS runs on :443, HTTP on :80). |
| 21 | 23 | // Default: ":8080" |
| 22 | 24 | APIAddr string `yaml:"api_addr"` |
| | @@ -189,10 +191,85 @@ |
| 189 | 191 | // DSN is the data source name. |
| 190 | 192 | // For sqlite: path to the .db file. |
| 191 | 193 | // For postgres: connection string. |
| 192 | 194 | DSN string `yaml:"dsn"` |
| 193 | 195 | } |
| 196 | + |
| 197 | +// TopologyConfig is the top-level channel topology declaration. |
| 198 | +// It defines static channels provisioned at startup and dynamic channel type |
| 199 | +// rules applied when agents create channels at runtime. |
| 200 | +type TopologyConfig struct { |
| 201 | + // Channels are static channels provisioned at daemon startup. |
| 202 | + Channels []StaticChannelConfig `yaml:"channels"` |
| 203 | + |
| 204 | + // Types are prefix-based rules applied to dynamically created channels. |
| 205 | + // The first matching prefix wins. |
| 206 | + Types []ChannelTypeConfig `yaml:"types"` |
| 207 | +} |
| 208 | + |
| 209 | +// StaticChannelConfig describes a channel that is provisioned at startup. |
| 210 | +type StaticChannelConfig struct { |
| 211 | + // Name is the full channel name including the # prefix (e.g. "#general"). |
| 212 | + Name string `yaml:"name"` |
| 213 | + |
| 214 | + // Topic is the initial channel topic. |
| 215 | + Topic string `yaml:"topic"` |
| 216 | + |
| 217 | + // Ops is a list of nicks to grant channel operator (+o) access. |
| 218 | + Ops []string `yaml:"ops"` |
| 219 | + |
| 220 | + // Voice is a list of nicks to grant voice (+v) access. |
| 221 | + Voice []string `yaml:"voice"` |
| 222 | + |
| 223 | + // Autojoin is a list of bot nicks to invite when the channel is provisioned. |
| 224 | + Autojoin []string `yaml:"autojoin"` |
| 225 | +} |
| 226 | + |
| 227 | +// ChannelTypeConfig defines policy rules for a class of dynamically created channels. |
| 228 | +// Matched by prefix against channel names (e.g. prefix "task." matches "#task.gh-42"). |
| 229 | +type ChannelTypeConfig struct { |
| 230 | + // Name is a human-readable type identifier (e.g. "task", "sprint", "incident"). |
| 231 | + Name string `yaml:"name"` |
| 232 | + |
| 233 | + // Prefix is matched against channel names after stripping the leading #. |
| 234 | + // The first matching type wins. (e.g. "task." matches "#task.gh-42") |
| 235 | + Prefix string `yaml:"prefix"` |
| 236 | + |
| 237 | + // Autojoin is a list of bot nicks to invite when a channel of this type is created. |
| 238 | + Autojoin []string `yaml:"autojoin"` |
| 239 | + |
| 240 | + // Supervision is the coordination channel where summaries should surface. |
| 241 | + // Agents receive this when they create a channel so they know where to also post. |
| 242 | + // May be a static channel name (e.g. "#general") or a type prefix pattern |
| 243 | + // (e.g. "sprint." — resolved to the most recently created matching channel). |
| 244 | + Supervision string `yaml:"supervision"` |
| 245 | + |
| 246 | + // Ephemeral marks channels of this type for automatic cleanup. |
| 247 | + Ephemeral bool `yaml:"ephemeral"` |
| 248 | + |
| 249 | + // TTL is the maximum lifetime of an ephemeral channel with no non-bot members. |
| 250 | + // Zero means no TTL; cleanup only occurs when the channel is empty. |
| 251 | + TTL Duration `yaml:"ttl"` |
| 252 | +} |
| 253 | + |
| 254 | +// Duration wraps time.Duration for YAML unmarshalling ("72h", "30m", etc.). |
| 255 | +type Duration struct { |
| 256 | + time.Duration |
| 257 | +} |
| 258 | + |
| 259 | +func (d *Duration) UnmarshalYAML(value *yaml.Node) error { |
| 260 | + var s string |
| 261 | + if err := value.Decode(&s); err != nil { |
| 262 | + return err |
| 263 | + } |
| 264 | + dur, err := time.ParseDuration(s) |
| 265 | + if err != nil { |
| 266 | + return fmt.Errorf("config: invalid duration %q: %w", s, err) |
| 267 | + } |
| 268 | + d.Duration = dur |
| 269 | + return nil |
| 270 | +} |
| 194 | 271 | |
| 195 | 272 | // Defaults fills in zero values with sensible defaults. |
| 196 | 273 | func (c *Config) Defaults() { |
| 197 | 274 | if c.Ergo.BinaryPath == "" { |
| 198 | 275 | c.Ergo.BinaryPath = "ergo" |
| 199 | 276 | |
| 200 | 277 | ADDED internal/config/config_test.go |