@kkelly-offical/kkcode 0.1.7 → 0.2.3-preview.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/LICENSE +674 -674
  2. package/README.md +474 -387
  3. package/package.json +50 -46
  4. package/src/agent/agent.mjs +228 -220
  5. package/src/agent/custom-agent-loader.mjs +6 -3
  6. package/src/agent/generator.mjs +2 -2
  7. package/src/agent/prompt/assistant.txt +12 -0
  8. package/src/agent/prompt/bug-hunter.txt +89 -89
  9. package/src/agent/prompt/frontend-designer.txt +58 -58
  10. package/src/agent/prompt/guide.txt +1 -1
  11. package/src/agent/prompt/longagent-blueprint-agent.txt +83 -83
  12. package/src/agent/prompt/longagent-coding-agent.txt +37 -37
  13. package/src/agent/prompt/longagent-debugging-agent.txt +46 -46
  14. package/src/agent/prompt/longagent-preview-agent.txt +63 -63
  15. package/src/command/custom-commands.mjs +2 -2
  16. package/src/commands/agent.mjs +1 -1
  17. package/src/commands/background.mjs +145 -4
  18. package/src/commands/chat.mjs +117 -76
  19. package/src/commands/config.mjs +148 -1
  20. package/src/commands/doctor.mjs +30 -6
  21. package/src/commands/init.mjs +32 -6
  22. package/src/commands/longagent.mjs +117 -0
  23. package/src/commands/mcp.mjs +275 -43
  24. package/src/commands/permission.mjs +1 -1
  25. package/src/commands/session.mjs +195 -140
  26. package/src/commands/skill.mjs +63 -0
  27. package/src/commands/theme.mjs +1 -1
  28. package/src/commands/update.mjs +32 -0
  29. package/src/config/defaults.mjs +289 -260
  30. package/src/config/import-config.mjs +1 -1
  31. package/src/config/load-config.mjs +61 -4
  32. package/src/config/schema.mjs +604 -574
  33. package/src/context.mjs +4 -1
  34. package/src/core/constants.mjs +97 -91
  35. package/src/core/types.mjs +1 -1
  36. package/src/github/api.mjs +78 -78
  37. package/src/github/auth.mjs +294 -286
  38. package/src/github/flow.mjs +298 -298
  39. package/src/github/workspace.mjs +225 -212
  40. package/src/index.mjs +87 -82
  41. package/src/knowledge/frontend-aesthetics.txt +38 -38
  42. package/src/mcp/client-http.mjs +139 -141
  43. package/src/mcp/client-sse.mjs +297 -288
  44. package/src/mcp/client-stdio.mjs +534 -533
  45. package/src/mcp/constants.mjs +4 -2
  46. package/src/mcp/registry.mjs +498 -479
  47. package/src/mcp/stdio-framing.mjs +135 -133
  48. package/src/mcp/tool-result.mjs +24 -24
  49. package/src/observability/edit-diagnostics.mjs +449 -0
  50. package/src/observability/index.mjs +42 -42
  51. package/src/observability/metrics.mjs +165 -137
  52. package/src/observability/tracer.mjs +137 -137
  53. package/src/onboarding.mjs +209 -0
  54. package/src/orchestration/background-manager.mjs +567 -372
  55. package/src/orchestration/background-worker.mjs +419 -305
  56. package/src/orchestration/interruption-reason.mjs +21 -0
  57. package/src/orchestration/longagent-manager.mjs +197 -171
  58. package/src/orchestration/stage-scheduler.mjs +733 -728
  59. package/src/orchestration/subagent-router.mjs +7 -1
  60. package/src/orchestration/task-scheduler.mjs +219 -7
  61. package/src/permission/engine.mjs +1 -1
  62. package/src/permission/exec-policy.mjs +370 -370
  63. package/src/permission/file-edit-policy.mjs +108 -0
  64. package/src/permission/prompt.mjs +1 -1
  65. package/src/permission/rules.mjs +116 -7
  66. package/src/plugin/builtin-hooks/post-edit-format.mjs +2 -1
  67. package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +104 -40
  68. package/src/plugin/hook-bus.mjs +19 -5
  69. package/src/plugin/manifest-loader.mjs +222 -0
  70. package/src/provider/anthropic.mjs +396 -390
  71. package/src/provider/ollama.mjs +7 -1
  72. package/src/provider/openai.mjs +382 -340
  73. package/src/provider/retry-policy.mjs +74 -68
  74. package/src/provider/router.mjs +242 -241
  75. package/src/provider/sse.mjs +104 -104
  76. package/src/provider/wizard.mjs +556 -0
  77. package/src/repl/capability-facade.mjs +30 -0
  78. package/src/repl/command-surface.mjs +23 -0
  79. package/src/repl/controller-entry.mjs +40 -0
  80. package/src/repl/core-shell.mjs +208 -0
  81. package/src/repl/dialog-router.mjs +87 -0
  82. package/src/repl/input-engine.mjs +76 -0
  83. package/src/repl/keymap.mjs +7 -0
  84. package/src/repl/operator-surface.mjs +15 -0
  85. package/src/repl/permission-flow.mjs +49 -0
  86. package/src/repl/runtime-facade.mjs +36 -0
  87. package/src/repl/slash-router.mjs +62 -0
  88. package/src/repl/state-store.mjs +29 -0
  89. package/src/repl/turn-controller.mjs +58 -0
  90. package/src/repl/verification.mjs +23 -0
  91. package/src/repl.mjs +3371 -2981
  92. package/src/rules/load-rules.mjs +3 -3
  93. package/src/runtime.mjs +1 -1
  94. package/src/session/agent-transaction.mjs +86 -0
  95. package/src/session/checkpoint.mjs +302 -302
  96. package/src/session/compaction.mjs +298 -298
  97. package/src/session/engine.mjs +417 -232
  98. package/src/session/longagent-4stage.mjs +467 -460
  99. package/src/session/longagent-hybrid.mjs +1344 -1097
  100. package/src/session/longagent-plan.mjs +376 -365
  101. package/src/session/longagent-project-memory.mjs +53 -53
  102. package/src/session/longagent-scaffold.mjs +291 -291
  103. package/src/session/longagent-task-bus.mjs +138 -54
  104. package/src/session/longagent-utils.mjs +828 -472
  105. package/src/session/longagent.mjs +911 -900
  106. package/src/session/loop.mjs +1005 -930
  107. package/src/session/prompt/agent.txt +25 -25
  108. package/src/session/prompt/anthropic.txt +150 -150
  109. package/src/session/prompt/beast.txt +1 -1
  110. package/src/session/prompt/plan.txt +31 -31
  111. package/src/session/prompt/qwen.txt +46 -46
  112. package/src/session/recovery.mjs +21 -0
  113. package/src/session/rollback.mjs +196 -195
  114. package/src/session/routing-observability.mjs +72 -0
  115. package/src/session/runtime-state.mjs +47 -0
  116. package/src/session/store.mjs +523 -519
  117. package/src/session/system-prompt.mjs +308 -273
  118. package/src/session/task-validator.mjs +267 -267
  119. package/src/session/usability-gates.mjs +2 -2
  120. package/src/skill/builtin/commit.mjs +64 -64
  121. package/src/skill/builtin/design.mjs +76 -76
  122. package/src/skill/generator.mjs +18 -2
  123. package/src/skill/registry.mjs +642 -390
  124. package/src/storage/audit-store.mjs +18 -11
  125. package/src/storage/event-log.mjs +7 -1
  126. package/src/storage/ghost-commit-store.mjs +243 -245
  127. package/src/storage/paths.mjs +17 -0
  128. package/src/theme/default-theme.mjs +1 -1
  129. package/src/theme/markdown.mjs +4 -0
  130. package/src/theme/schema.mjs +1 -1
  131. package/src/theme/status-bar.mjs +162 -158
  132. package/src/tool/audit-wrapper.mjs +18 -2
  133. package/src/tool/edit-transaction.mjs +23 -0
  134. package/src/tool/executor.mjs +26 -1
  135. package/src/tool/file-read-state.mjs +65 -0
  136. package/src/tool/git-auto.mjs +526 -526
  137. package/src/tool/git-full-auto.mjs +487 -478
  138. package/src/tool/mutation-guard.mjs +54 -0
  139. package/src/tool/prompt/edit.txt +3 -3
  140. package/src/tool/prompt/multiedit.txt +1 -0
  141. package/src/tool/prompt/notebookedit.txt +2 -1
  142. package/src/tool/prompt/patch.txt +25 -24
  143. package/src/tool/prompt/read.txt +3 -3
  144. package/src/tool/prompt/sysinfo.txt +29 -0
  145. package/src/tool/prompt/task.txt +66 -4
  146. package/src/tool/prompt/write.txt +2 -2
  147. package/src/tool/question-prompt.mjs +99 -93
  148. package/src/tool/registry.mjs +1701 -1343
  149. package/src/tool/task-tool.mjs +14 -6
  150. package/src/ui/activity-renderer.mjs +667 -664
  151. package/src/ui/repl-background-panel.mjs +7 -0
  152. package/src/ui/repl-capability-panel.mjs +9 -0
  153. package/src/ui/repl-dashboard.mjs +54 -4
  154. package/src/ui/repl-help.mjs +110 -0
  155. package/src/ui/repl-operator-panel.mjs +12 -0
  156. package/src/ui/repl-route-feedback.mjs +35 -0
  157. package/src/ui/repl-status-view.mjs +76 -0
  158. package/src/ui/repl-task-panel.mjs +5 -0
  159. package/src/ui/repl-transcript-panel.mjs +56 -0
  160. package/src/ui/repl-turn-summary.mjs +135 -0
  161. package/src/update/checker.mjs +184 -0
  162. package/src/usage/pricing.mjs +122 -121
  163. package/src/usage/usage-meter.mjs +1 -0
  164. package/src/util/git.mjs +562 -519
  165. package/src/util/template.mjs +6 -1
  166. package/src/version.mjs +3 -0
@@ -1,574 +1,604 @@
1
- import { VALID_MODES, VALID_PROVIDER_TYPES, VALID_REVIEW_SORT, getValidProviderTypes } from "./defaults.mjs"
2
-
3
- const HEX = /^#([A-Fa-f0-9]{6})$/
4
-
5
- function err(list, field, message) {
6
- list.push(`${field}: ${message}`)
7
- }
8
-
9
- function isObj(v) {
10
- return !!v && typeof v === "object" && !Array.isArray(v)
11
- }
12
-
13
- function checkInt(errors, field, value, min = 0) {
14
- if (!Number.isInteger(value) || value < min) err(errors, field, `must be integer >= ${min}`)
15
- }
16
-
17
- function checkColor(errors, field, value) {
18
- if (typeof value !== "string" || !HEX.test(value)) err(errors, field, "must be hex color like #112233")
19
- }
20
-
21
- function checkGateEnabledObject(errors, field, value) {
22
- if (!isObj(value)) {
23
- err(errors, field, "must be object")
24
- return
25
- }
26
- if (value.enabled !== undefined && typeof value.enabled !== "boolean") {
27
- err(errors, `${field}.enabled`, "must be boolean")
28
- }
29
- }
30
-
31
- export function validateConfig(config) {
32
- const errors = []
33
- if (!isObj(config)) {
34
- return { valid: false, errors: ["config must be object"] }
35
- }
36
-
37
- if (config.provider !== undefined) {
38
- if (!isObj(config.provider)) {
39
- err(errors, "provider", "must be object")
40
- } else {
41
- const providerTypes = getValidProviderTypes()
42
- const providerKeys = new Set([...providerTypes, ...Object.keys(config.provider).filter(k => k !== "default")])
43
- if (config.provider.default !== undefined && !providerKeys.has(config.provider.default)) {
44
- err(errors, "provider.default", `must be one of ${[...providerKeys].join(", ")}`)
45
- }
46
- for (const key of providerKeys) {
47
- const p = config.provider[key]
48
- if (p === undefined) continue
49
- if (!isObj(p)) {
50
- err(errors, `provider.${key}`, "must be object")
51
- continue
52
- }
53
- if (p.type !== undefined && typeof p.type !== "string") err(errors, `provider.${key}.type`, "must be string")
54
- if (p.base_url !== undefined && typeof p.base_url !== "string") err(errors, `provider.${key}.base_url`, "must be string")
55
- if (p.api_key_env !== undefined && typeof p.api_key_env !== "string") err(errors, `provider.${key}.api_key_env`, "must be string")
56
- if (p.default_model !== undefined && typeof p.default_model !== "string") err(errors, `provider.${key}.default_model`, "must be string")
57
- if (p.timeout_ms !== undefined) checkInt(errors, `provider.${key}.timeout_ms`, p.timeout_ms, 1000)
58
- if (p.retry_attempts !== undefined) checkInt(errors, `provider.${key}.retry_attempts`, p.retry_attempts, 0)
59
- if (p.retry_base_delay_ms !== undefined) checkInt(errors, `provider.${key}.retry_base_delay_ms`, p.retry_base_delay_ms, 100)
60
- if (p.stream !== undefined && typeof p.stream !== "boolean") err(errors, `provider.${key}.stream`, "must be boolean")
61
- if (p.context_limit !== undefined && p.context_limit !== null) {
62
- if (!Number.isInteger(p.context_limit) || p.context_limit < 1024) err(errors, `provider.${key}.context_limit`, "must be integer >= 1024 or null")
63
- }
64
- if (p.thinking !== undefined && p.thinking !== null) {
65
- if (!isObj(p.thinking)) err(errors, `provider.${key}.thinking`, "must be object or null")
66
- else {
67
- if (p.thinking.type !== undefined && typeof p.thinking.type !== "string") err(errors, `provider.${key}.thinking.type`, "must be string")
68
- if (p.thinking.budget_tokens !== undefined) {
69
- if (!Number.isInteger(p.thinking.budget_tokens) || p.thinking.budget_tokens < 0) err(errors, `provider.${key}.thinking.budget_tokens`, "must be integer >= 0")
70
- }
71
- }
72
- }
73
- }
74
- if (config.provider.strict_mode !== undefined && typeof config.provider.strict_mode !== "boolean") {
75
- err(errors, "provider.strict_mode", "must be boolean")
76
- }
77
- if (config.provider.model_context !== undefined) {
78
- if (!isObj(config.provider.model_context)) err(errors, "provider.model_context", "must be object")
79
- else {
80
- for (const [mk, mv] of Object.entries(config.provider.model_context)) {
81
- if (!Number.isInteger(mv) || mv < 1024) err(errors, `provider.model_context.${mk}`, "must be integer >= 1024")
82
- }
83
- }
84
- }
85
- }
86
- }
87
-
88
- if (config.agent !== undefined) {
89
- if (!isObj(config.agent)) err(errors, "agent", "must be object")
90
- else {
91
- if (config.agent.default_mode !== undefined && !VALID_MODES.includes(config.agent.default_mode)) {
92
- err(errors, "agent.default_mode", `must be one of ${VALID_MODES.join(", ")}`)
93
- }
94
- if (config.agent.max_steps !== undefined) checkInt(errors, "agent.max_steps", config.agent.max_steps, 1)
95
- if (config.agent.longagent !== undefined) {
96
- if (!isObj(config.agent.longagent)) err(errors, "agent.longagent", "must be object")
97
- else {
98
- if (config.agent.longagent.max_iterations !== undefined) {
99
- checkInt(errors, "agent.longagent.max_iterations", config.agent.longagent.max_iterations, 0)
100
- }
101
- if (config.agent.longagent.no_progress_warning !== undefined) {
102
- checkInt(errors, "agent.longagent.no_progress_warning", config.agent.longagent.no_progress_warning, 1)
103
- }
104
- if (config.agent.longagent.no_progress_limit !== undefined) {
105
- checkInt(errors, "agent.longagent.no_progress_limit", config.agent.longagent.no_progress_limit, 1)
106
- }
107
- if (config.agent.longagent.heartbeat_timeout_ms !== undefined) {
108
- checkInt(errors, "agent.longagent.heartbeat_timeout_ms", config.agent.longagent.heartbeat_timeout_ms, 1000)
109
- }
110
- if (config.agent.longagent.checkpoint_interval !== undefined) {
111
- checkInt(errors, "agent.longagent.checkpoint_interval", config.agent.longagent.checkpoint_interval, 0)
112
- }
113
- if (config.agent.longagent.parallel !== undefined) {
114
- if (!isObj(config.agent.longagent.parallel)) {
115
- err(errors, "agent.longagent.parallel", "must be object")
116
- } else {
117
- if (config.agent.longagent.parallel.enabled !== undefined && typeof config.agent.longagent.parallel.enabled !== "boolean") {
118
- err(errors, "agent.longagent.parallel.enabled", "must be boolean")
119
- }
120
- if (config.agent.longagent.parallel.max_concurrency !== undefined) {
121
- checkInt(errors, "agent.longagent.parallel.max_concurrency", config.agent.longagent.parallel.max_concurrency, 1)
122
- }
123
- if (config.agent.longagent.parallel.stage_pass_rule !== undefined && !["all_success", "majority", "any_success"].includes(config.agent.longagent.parallel.stage_pass_rule)) {
124
- err(errors, "agent.longagent.parallel.stage_pass_rule", "must be all_success|majority|any_success")
125
- }
126
- if (config.agent.longagent.parallel.poll_interval_ms !== undefined) {
127
- checkInt(errors, "agent.longagent.parallel.poll_interval_ms", config.agent.longagent.parallel.poll_interval_ms, 50)
128
- }
129
- if (config.agent.longagent.parallel.task_timeout_ms !== undefined) {
130
- checkInt(errors, "agent.longagent.parallel.task_timeout_ms", config.agent.longagent.parallel.task_timeout_ms, 1000)
131
- }
132
- if (config.agent.longagent.parallel.task_max_retries !== undefined) {
133
- checkInt(errors, "agent.longagent.parallel.task_max_retries", config.agent.longagent.parallel.task_max_retries, 0)
134
- }
135
- }
136
- }
137
- if (config.agent.longagent.planner !== undefined) {
138
- if (!isObj(config.agent.longagent.planner)) {
139
- err(errors, "agent.longagent.planner", "must be object")
140
- } else {
141
- if (config.agent.longagent.planner.intake_questions !== undefined) {
142
- if (!isObj(config.agent.longagent.planner.intake_questions)) {
143
- err(errors, "agent.longagent.planner.intake_questions", "must be object")
144
- } else {
145
- if (config.agent.longagent.planner.intake_questions.enabled !== undefined && typeof config.agent.longagent.planner.intake_questions.enabled !== "boolean") {
146
- err(errors, "agent.longagent.planner.intake_questions.enabled", "must be boolean")
147
- }
148
- if (config.agent.longagent.planner.intake_questions.max_rounds !== undefined) {
149
- checkInt(errors, "agent.longagent.planner.intake_questions.max_rounds", config.agent.longagent.planner.intake_questions.max_rounds, 1)
150
- }
151
- }
152
- }
153
- if (config.agent.longagent.planner.ask_user_after_plan_frozen !== undefined && typeof config.agent.longagent.planner.ask_user_after_plan_frozen !== "boolean") {
154
- err(errors, "agent.longagent.planner.ask_user_after_plan_frozen", "must be boolean")
155
- }
156
- }
157
- }
158
- if (config.agent.longagent.lock_timeout_ms !== undefined) {
159
- checkInt(errors, "agent.longagent.lock_timeout_ms", config.agent.longagent.lock_timeout_ms, 1000)
160
- }
161
- if (config.agent.longagent.four_stage !== undefined) {
162
- if (!isObj(config.agent.longagent.four_stage)) {
163
- err(errors, "agent.longagent.four_stage", "must be object")
164
- } else {
165
- const fs = config.agent.longagent.four_stage
166
- if (fs.enabled !== undefined && typeof fs.enabled !== "boolean") err(errors, "agent.longagent.four_stage.enabled", "must be boolean")
167
- if (fs.separate_models !== undefined) {
168
- if (!isObj(fs.separate_models)) err(errors, "agent.longagent.four_stage.separate_models", "must be object")
169
- else {
170
- if (fs.separate_models.enabled !== undefined && typeof fs.separate_models.enabled !== "boolean") err(errors, "agent.longagent.four_stage.separate_models.enabled", "must be boolean")
171
- for (const k of ["preview_model", "blueprint_model", "coding_model", "debugging_model"]) {
172
- if (fs.separate_models[k] !== undefined && fs.separate_models[k] !== null && typeof fs.separate_models[k] !== "string") {
173
- err(errors, `agent.longagent.four_stage.separate_models.${k}`, "must be string or null")
174
- }
175
- }
176
- }
177
- }
178
- }
179
- }
180
- if (config.agent.longagent.hybrid !== undefined) {
181
- if (!isObj(config.agent.longagent.hybrid)) {
182
- err(errors, "agent.longagent.hybrid", "must be object")
183
- } else {
184
- const hy = config.agent.longagent.hybrid
185
- if (hy.enabled !== undefined && typeof hy.enabled !== "boolean") err(errors, "agent.longagent.hybrid.enabled", "must be boolean")
186
- if (hy.separate_models !== undefined) {
187
- if (!isObj(hy.separate_models)) err(errors, "agent.longagent.hybrid.separate_models", "must be object")
188
- else {
189
- if (hy.separate_models.enabled !== undefined && typeof hy.separate_models.enabled !== "boolean") err(errors, "agent.longagent.hybrid.separate_models.enabled", "must be boolean")
190
- for (const k of ["preview_model", "blueprint_model", "debugging_model"]) {
191
- if (hy.separate_models[k] !== undefined && hy.separate_models[k] !== null && typeof hy.separate_models[k] !== "string") {
192
- err(errors, `agent.longagent.hybrid.separate_models.${k}`, "must be string or null")
193
- }
194
- }
195
- }
196
- }
197
- if (hy.adaptive_models !== undefined) {
198
- if (!isObj(hy.adaptive_models)) err(errors, "agent.longagent.hybrid.adaptive_models", "must be object")
199
- else {
200
- if (hy.adaptive_models.enabled !== undefined && typeof hy.adaptive_models.enabled !== "boolean") err(errors, "agent.longagent.hybrid.adaptive_models.enabled", "must be boolean")
201
- for (const k of ["low", "medium", "high"]) {
202
- if (hy.adaptive_models[k] !== undefined && hy.adaptive_models[k] !== null && typeof hy.adaptive_models[k] !== "string") {
203
- err(errors, `agent.longagent.hybrid.adaptive_models.${k}`, "must be string or null")
204
- }
205
- }
206
- }
207
- }
208
- }
209
- }
210
- if (config.agent.longagent.resume_incomplete_files !== undefined && typeof config.agent.longagent.resume_incomplete_files !== "boolean") {
211
- err(errors, "agent.longagent.resume_incomplete_files", "must be boolean")
212
- }
213
- if (config.agent.longagent.usability_gates !== undefined) {
214
- if (!isObj(config.agent.longagent.usability_gates)) {
215
- err(errors, "agent.longagent.usability_gates", "must be object")
216
- } else {
217
- checkGateEnabledObject(errors, "agent.longagent.usability_gates.build", config.agent.longagent.usability_gates.build || {})
218
- checkGateEnabledObject(errors, "agent.longagent.usability_gates.test", config.agent.longagent.usability_gates.test || {})
219
- checkGateEnabledObject(errors, "agent.longagent.usability_gates.review", config.agent.longagent.usability_gates.review || {})
220
- checkGateEnabledObject(errors, "agent.longagent.usability_gates.health", config.agent.longagent.usability_gates.health || {})
221
- checkGateEnabledObject(errors, "agent.longagent.usability_gates.budget", config.agent.longagent.usability_gates.budget || {})
222
- }
223
- }
224
- }
225
- }
226
- if (config.agent.subagents !== undefined && !isObj(config.agent.subagents)) {
227
- err(errors, "agent.subagents", "must be object")
228
- }
229
- if (config.agent.routing !== undefined && !isObj(config.agent.routing)) {
230
- err(errors, "agent.routing", "must be object")
231
- }
232
- }
233
- }
234
-
235
- if (config.mcp !== undefined) {
236
- if (!isObj(config.mcp)) err(errors, "mcp", "must be object")
237
- else {
238
- if (config.mcp.servers !== undefined && !isObj(config.mcp.servers)) err(errors, "mcp.servers", "must be object")
239
- if (config.mcp.timeout_ms !== undefined) checkInt(errors, "mcp.timeout_ms", config.mcp.timeout_ms, 1000)
240
- if (config.mcp.shutdown_timeout_ms !== undefined) checkInt(errors, "mcp.shutdown_timeout_ms", config.mcp.shutdown_timeout_ms, 100)
241
- if (config.mcp.max_sse_buffer_bytes !== undefined) checkInt(errors, "mcp.max_sse_buffer_bytes", config.mcp.max_sse_buffer_bytes, 1024)
242
- if (config.mcp.auto_discover !== undefined && typeof config.mcp.auto_discover !== "boolean") {
243
- err(errors, "mcp.auto_discover", "must be boolean")
244
- }
245
- if (isObj(config.mcp.servers)) {
246
- for (const [name, server] of Object.entries(config.mcp.servers)) {
247
- const prefix = `mcp.servers.${name}`
248
- if (!isObj(server)) {
249
- err(errors, prefix, "must be object")
250
- continue
251
- }
252
- if (server.enabled !== undefined && typeof server.enabled !== "boolean") {
253
- err(errors, `${prefix}.enabled`, "must be boolean")
254
- }
255
- if (server.type !== undefined && typeof server.type !== "string") {
256
- err(errors, `${prefix}.type`, "must be string")
257
- }
258
- if (server.transport !== undefined && !["stdio", "http", "sse", "streamable-http"].includes(server.transport)) {
259
- err(errors, `${prefix}.transport`, "must be stdio|http|sse|streamable-http")
260
- }
261
- if (server.url !== undefined && typeof server.url !== "string") {
262
- err(errors, `${prefix}.url`, "must be string")
263
- }
264
- if (server.command !== undefined && !Array.isArray(server.command) && typeof server.command !== "string") {
265
- err(errors, `${prefix}.command`, "must be string or array")
266
- }
267
- if (server.args !== undefined) {
268
- if (!Array.isArray(server.args)) err(errors, `${prefix}.args`, "must be array")
269
- else if (server.args.some((item) => typeof item !== "string")) err(errors, `${prefix}.args`, "all values must be string")
270
- }
271
- if (server.env !== undefined && !isObj(server.env)) {
272
- err(errors, `${prefix}.env`, "must be object")
273
- } else if (isObj(server.env)) {
274
- for (const [k, v] of Object.entries(server.env)) {
275
- if (typeof v !== "string") err(errors, `${prefix}.env.${k}`, "must be string")
276
- }
277
- }
278
- if (server.headers !== undefined && !isObj(server.headers)) {
279
- err(errors, `${prefix}.headers`, "must be object")
280
- } else if (isObj(server.headers)) {
281
- for (const [k, v] of Object.entries(server.headers)) {
282
- if (typeof v !== "string") err(errors, `${prefix}.headers.${k}`, "must be string")
283
- }
284
- }
285
- if (server.shell !== undefined && typeof server.shell !== "boolean") {
286
- err(errors, `${prefix}.shell`, "must be boolean")
287
- }
288
- if (server.framing !== undefined && !["auto", "content-length", "newline"].includes(server.framing)) {
289
- err(errors, `${prefix}.framing`, "must be auto|content-length|newline")
290
- }
291
- if (server.health_check_method !== undefined && !["auto", "ping", "tools_list"].includes(server.health_check_method)) {
292
- err(errors, `${prefix}.health_check_method`, "must be auto|ping|tools_list")
293
- }
294
- if (server.startup_timeout_ms !== undefined) checkInt(errors, `${prefix}.startup_timeout_ms`, server.startup_timeout_ms, 100)
295
- if (server.request_timeout_ms !== undefined) checkInt(errors, `${prefix}.request_timeout_ms`, server.request_timeout_ms, 100)
296
- if (server.timeout_ms !== undefined) checkInt(errors, `${prefix}.timeout_ms`, server.timeout_ms, 100)
297
- }
298
- }
299
- }
300
- }
301
-
302
- if (config.skills !== undefined) {
303
- if (!isObj(config.skills)) err(errors, "skills", "must be object")
304
- else {
305
- if (config.skills.enabled !== undefined && typeof config.skills.enabled !== "boolean") {
306
- err(errors, "skills.enabled", "must be boolean")
307
- }
308
- if (config.skills.dirs !== undefined && !Array.isArray(config.skills.dirs)) {
309
- err(errors, "skills.dirs", "must be array")
310
- }
311
- if (config.skills.allowed_commands !== undefined) {
312
- if (!Array.isArray(config.skills.allowed_commands)) err(errors, "skills.allowed_commands", "must be array")
313
- else if (config.skills.allowed_commands.some(c => typeof c !== "string")) err(errors, "skills.allowed_commands", "all values must be string")
314
- }
315
- }
316
- }
317
-
318
- if (config.permission !== undefined) {
319
- if (!isObj(config.permission)) err(errors, "permission", "must be object")
320
- else {
321
- if (config.permission.default_policy !== undefined && !["allow", "deny", "ask"].includes(config.permission.default_policy)) {
322
- err(errors, "permission.default_policy", "must be allow|deny|ask")
323
- }
324
- if (config.permission.non_tty_default !== undefined && !["allow_once", "deny"].includes(config.permission.non_tty_default)) {
325
- err(errors, "permission.non_tty_default", "must be allow_once|deny")
326
- }
327
- if (config.permission.rules !== undefined) {
328
- if (!Array.isArray(config.permission.rules)) err(errors, "permission.rules", "must be array")
329
- else {
330
- for (const [index, rule] of config.permission.rules.entries()) {
331
- if (!isObj(rule)) {
332
- err(errors, `permission.rules[${index}]`, "must be object")
333
- continue
334
- }
335
- if (typeof rule.tool !== "string") err(errors, `permission.rules[${index}].tool`, "must be string")
336
- if (!["allow", "deny", "ask"].includes(rule.action)) {
337
- err(errors, `permission.rules[${index}].action`, "must be allow|deny|ask")
338
- }
339
- if (rule.modes !== undefined) {
340
- if (!Array.isArray(rule.modes)) err(errors, `permission.rules[${index}].modes`, "must be array")
341
- else {
342
- for (const mode of rule.modes) {
343
- if (!VALID_MODES.includes(mode)) err(errors, `permission.rules[${index}].modes`, `invalid mode ${mode}`)
344
- }
345
- }
346
- }
347
- if (rule.file_patterns !== undefined) {
348
- if (!Array.isArray(rule.file_patterns) && typeof rule.file_patterns !== "string") {
349
- err(errors, `permission.rules[${index}].file_patterns`, "must be string or array of strings")
350
- } else if (Array.isArray(rule.file_patterns)) {
351
- for (const pat of rule.file_patterns) {
352
- if (typeof pat !== "string") err(errors, `permission.rules[${index}].file_patterns`, "each pattern must be string")
353
- }
354
- }
355
- }
356
- if (rule.command_prefix !== undefined) {
357
- if (!Array.isArray(rule.command_prefix) && typeof rule.command_prefix !== "string") {
358
- err(errors, `permission.rules[${index}].command_prefix`, "must be string or array of strings")
359
- } else if (Array.isArray(rule.command_prefix)) {
360
- for (const pfx of rule.command_prefix) {
361
- if (typeof pfx !== "string") err(errors, `permission.rules[${index}].command_prefix`, "each prefix must be string")
362
- }
363
- }
364
- }
365
- }
366
- }
367
- }
368
- }
369
- }
370
-
371
- if (config.storage !== undefined) {
372
- if (!isObj(config.storage)) err(errors, "storage", "must be object")
373
- else {
374
- if (config.storage.session_shard_enabled !== undefined && typeof config.storage.session_shard_enabled !== "boolean") {
375
- err(errors, "storage.session_shard_enabled", "must be boolean")
376
- }
377
- if (config.storage.flush_interval_ms !== undefined) checkInt(errors, "storage.flush_interval_ms", config.storage.flush_interval_ms, 0)
378
- if (config.storage.event_rotate_mb !== undefined) checkInt(errors, "storage.event_rotate_mb", config.storage.event_rotate_mb, 1)
379
- if (config.storage.event_retain_days !== undefined) checkInt(errors, "storage.event_retain_days", config.storage.event_retain_days, 1)
380
- }
381
- }
382
-
383
- if (config.background !== undefined) {
384
- if (!isObj(config.background)) err(errors, "background", "must be object")
385
- else {
386
- if (config.background.mode !== undefined && !["worker_process"].includes(config.background.mode)) {
387
- err(errors, "background.mode", "must be worker_process")
388
- }
389
- if (config.background.worker_timeout_ms !== undefined) checkInt(errors, "background.worker_timeout_ms", config.background.worker_timeout_ms, 1000)
390
- if (config.background.max_parallel !== undefined) checkInt(errors, "background.max_parallel", config.background.max_parallel, 1)
391
- if (config.background.max_log_lines !== undefined) checkInt(errors, "background.max_log_lines", config.background.max_log_lines, 1)
392
- }
393
- }
394
-
395
- if (config.runtime !== undefined) {
396
- if (!isObj(config.runtime)) err(errors, "runtime", "must be object")
397
- else {
398
- if (config.runtime.tool_registry_cache_ttl_ms !== undefined) {
399
- checkInt(errors, "runtime.tool_registry_cache_ttl_ms", config.runtime.tool_registry_cache_ttl_ms, 0)
400
- }
401
- if (config.runtime.mcp_refresh_ttl_ms !== undefined) {
402
- checkInt(errors, "runtime.mcp_refresh_ttl_ms", config.runtime.mcp_refresh_ttl_ms, 0)
403
- }
404
- }
405
- }
406
-
407
- if (config.tool !== undefined) {
408
- if (!isObj(config.tool)) err(errors, "tool", "must be object")
409
- else {
410
- if (config.tool.sources !== undefined && !isObj(config.tool.sources)) err(errors, "tool.sources", "must be object")
411
- if (config.tool.write_lock !== undefined) {
412
- if (!isObj(config.tool.write_lock)) err(errors, "tool.write_lock", "must be object")
413
- else {
414
- if (config.tool.write_lock.mode !== undefined && !["file_lock", "none"].includes(config.tool.write_lock.mode)) {
415
- err(errors, "tool.write_lock.mode", "must be file_lock|none")
416
- }
417
- if (config.tool.write_lock.wait_timeout_ms !== undefined) {
418
- checkInt(errors, "tool.write_lock.wait_timeout_ms", config.tool.write_lock.wait_timeout_ms, 0)
419
- }
420
- }
421
- }
422
- if (config.tool.local_dirs !== undefined && !Array.isArray(config.tool.local_dirs)) err(errors, "tool.local_dirs", "must be array")
423
- if (config.tool.plugin_dirs !== undefined && !Array.isArray(config.tool.plugin_dirs)) err(errors, "tool.plugin_dirs", "must be array")
424
- if (config.tool.bash_timeout_ms !== undefined) checkInt(errors, "tool.bash_timeout_ms", config.tool.bash_timeout_ms, 1000)
425
- }
426
- }
427
-
428
- if (config.session !== undefined) {
429
- if (!isObj(config.session)) err(errors, "session", "must be object")
430
- else {
431
- if (config.session.max_history !== undefined) checkInt(errors, "session.max_history", config.session.max_history, 1)
432
- if (config.session.recovery !== undefined && typeof config.session.recovery !== "boolean") {
433
- err(errors, "session.recovery", "must be boolean")
434
- }
435
- if (config.session.compaction_threshold_ratio !== undefined) {
436
- const ratio = Number(config.session.compaction_threshold_ratio)
437
- if (!Number.isFinite(ratio) || ratio <= 0 || ratio > 1) {
438
- err(errors, "session.compaction_threshold_ratio", "must be number in (0,1]")
439
- }
440
- }
441
- if (config.session.compaction_threshold_messages !== undefined) {
442
- checkInt(errors, "session.compaction_threshold_messages", config.session.compaction_threshold_messages, 1)
443
- }
444
- if (config.session.context_cache_points !== undefined && typeof config.session.context_cache_points !== "boolean") {
445
- err(errors, "session.context_cache_points", "must be boolean")
446
- }
447
- }
448
- }
449
-
450
- if (config.review !== undefined) {
451
- if (!isObj(config.review)) err(errors, "review", "must be object")
452
- else {
453
- if (config.review.sort !== undefined && !VALID_REVIEW_SORT.includes(config.review.sort)) {
454
- err(errors, "review.sort", `must be one of ${VALID_REVIEW_SORT.join(", ")}`)
455
- }
456
- if (config.review.default_lines !== undefined) checkInt(errors, "review.default_lines", config.review.default_lines, 1)
457
- if (config.review.max_expand_lines !== undefined) checkInt(errors, "review.max_expand_lines", config.review.max_expand_lines, 1)
458
- if (config.review.risk_weights !== undefined) {
459
- if (!isObj(config.review.risk_weights)) err(errors, "review.risk_weights", "must be object")
460
- else {
461
- for (const [key, val] of Object.entries(config.review.risk_weights)) {
462
- if (typeof val !== "number" || val < 0) err(errors, `review.risk_weights.${key}`, "must be non-negative number")
463
- }
464
- }
465
- }
466
- }
467
- }
468
-
469
- if (config.usage !== undefined) {
470
- if (!isObj(config.usage)) err(errors, "usage", "must be object")
471
- else {
472
- if (config.usage.pricing_file !== undefined && config.usage.pricing_file !== null && typeof config.usage.pricing_file !== "string") {
473
- err(errors, "usage.pricing_file", "must be string|null")
474
- }
475
- if (config.usage.aggregation !== undefined) {
476
- if (!Array.isArray(config.usage.aggregation)) err(errors, "usage.aggregation", "must be array")
477
- else {
478
- for (const scope of config.usage.aggregation) {
479
- if (!["turn", "session", "global"].includes(scope)) err(errors, "usage.aggregation", `invalid scope ${scope}`)
480
- }
481
- }
482
- }
483
- if (config.usage.budget !== undefined) {
484
- if (!isObj(config.usage.budget)) err(errors, "usage.budget", "must be object")
485
- else {
486
- if (config.usage.budget.warn_at_percent !== undefined) {
487
- const v = config.usage.budget.warn_at_percent
488
- if (typeof v !== "number" || v <= 0 || v > 100) err(errors, "usage.budget.warn_at_percent", "must be number in (0,100]")
489
- }
490
- if (config.usage.budget.strategy !== undefined && !["warn", "block"].includes(config.usage.budget.strategy)) {
491
- err(errors, "usage.budget.strategy", "must be warn|block")
492
- }
493
- }
494
- }
495
- }
496
- }
497
-
498
- if (config.ui !== undefined) {
499
- if (!isObj(config.ui)) err(errors, "ui", "must be object")
500
- else {
501
- if (config.ui.theme_file !== undefined && config.ui.theme_file !== null && typeof config.ui.theme_file !== "string") {
502
- err(errors, "ui.theme_file", "must be string|null")
503
- }
504
- if (config.ui.mode_colors !== undefined) {
505
- if (!isObj(config.ui.mode_colors)) err(errors, "ui.mode_colors", "must be object")
506
- else {
507
- for (const mode of VALID_MODES) {
508
- if (config.ui.mode_colors[mode] !== undefined) checkColor(errors, `ui.mode_colors.${mode}`, config.ui.mode_colors[mode])
509
- }
510
- }
511
- }
512
- if (config.ui.layout !== undefined && !["compact", "comfortable"].includes(config.ui.layout)) {
513
- err(errors, "ui.layout", "must be compact|comfortable")
514
- }
515
- if (config.ui.markdown_render !== undefined && typeof config.ui.markdown_render !== "boolean") {
516
- err(errors, "ui.markdown_render", "must be boolean")
517
- }
518
- if (config.ui.status !== undefined) {
519
- if (!isObj(config.ui.status)) err(errors, "ui.status", "must be object")
520
- else {
521
- if (config.ui.status.show_cost !== undefined && typeof config.ui.status.show_cost !== "boolean") {
522
- err(errors, "ui.status.show_cost", "must be boolean")
523
- }
524
- if (config.ui.status.show_token_meter !== undefined && typeof config.ui.status.show_token_meter !== "boolean") {
525
- err(errors, "ui.status.show_token_meter", "must be boolean")
526
- }
527
- }
528
- }
529
- }
530
- }
531
-
532
- if (config.git_auto !== undefined) {
533
- if (!isObj(config.git_auto)) {
534
- err(errors, "git_auto", "must be object")
535
- } else {
536
- if (config.git_auto.enabled !== undefined && typeof config.git_auto.enabled !== "boolean") {
537
- err(errors, "git_auto.enabled", "must be boolean")
538
- }
539
- if (config.git_auto.auto_snapshot !== undefined && typeof config.git_auto.auto_snapshot !== "boolean") {
540
- err(errors, "git_auto.auto_snapshot", "must be boolean")
541
- }
542
- if (config.git_auto.max_snapshots !== undefined) {
543
- checkInt(errors, "git_auto.max_snapshots", config.git_auto.max_snapshots, 1)
544
- }
545
- if (config.git_auto.ttl_days !== undefined) {
546
- checkInt(errors, "git_auto.ttl_days", config.git_auto.ttl_days, 1)
547
- }
548
- if (config.git_auto.forbid_commit !== undefined && typeof config.git_auto.forbid_commit !== "boolean") {
549
- err(errors, "git_auto.forbid_commit", "must be boolean")
550
- }
551
- if (config.git_auto.forbid_push !== undefined && typeof config.git_auto.forbid_push !== "boolean") {
552
- err(errors, "git_auto.forbid_push", "must be boolean")
553
- }
554
- // 全自动化模式配置
555
- if (config.git_auto.full_auto !== undefined && typeof config.git_auto.full_auto !== "boolean") {
556
- err(errors, "git_auto.full_auto", "must be boolean")
557
- }
558
- if (config.git_auto.auto_commit !== undefined && typeof config.git_auto.auto_commit !== "boolean") {
559
- err(errors, "git_auto.auto_commit", "must be boolean")
560
- }
561
- if (config.git_auto.auto_push !== undefined && typeof config.git_auto.auto_push !== "boolean") {
562
- err(errors, "git_auto.auto_push", "must be boolean")
563
- }
564
- if (config.git_auto.allow_dangerous_ops !== undefined && typeof config.git_auto.allow_dangerous_ops !== "boolean") {
565
- err(errors, "git_auto.allow_dangerous_ops", "must be boolean")
566
- }
567
- if (config.git_auto.auto_stage !== undefined && typeof config.git_auto.auto_stage !== "boolean") {
568
- err(errors, "git_auto.auto_stage", "must be boolean")
569
- }
570
- }
571
- }
572
-
573
- return { valid: errors.length === 0, errors }
574
- }
1
+ import { VALID_MODES, VALID_PROVIDER_TYPES, VALID_REVIEW_SORT, getValidProviderTypes } from "./defaults.mjs"
2
+
3
+ const HEX = /^#([A-Fa-f0-9]{6})$/
4
+
5
+ function err(list, field, message) {
6
+ list.push(`${field}: ${message}`)
7
+ }
8
+
9
+ function isObj(v) {
10
+ return !!v && typeof v === "object" && !Array.isArray(v)
11
+ }
12
+
13
+ function checkInt(errors, field, value, min = 0) {
14
+ if (!Number.isInteger(value) || value < min) err(errors, field, `must be integer >= ${min}`)
15
+ }
16
+
17
+ function checkColor(errors, field, value) {
18
+ if (typeof value !== "string" || !HEX.test(value)) err(errors, field, "must be hex color like #112233")
19
+ }
20
+
21
+ function checkGateEnabledObject(errors, field, value) {
22
+ if (!isObj(value)) {
23
+ err(errors, field, "must be object")
24
+ return
25
+ }
26
+ if (value.enabled !== undefined && typeof value.enabled !== "boolean") {
27
+ err(errors, `${field}.enabled`, "must be boolean")
28
+ }
29
+ }
30
+
31
+ export function validateConfig(config) {
32
+ const errors = []
33
+ if (!isObj(config)) {
34
+ return { valid: false, errors: ["config must be object"] }
35
+ }
36
+
37
+ if (config.provider !== undefined) {
38
+ if (!isObj(config.provider)) {
39
+ err(errors, "provider", "must be object")
40
+ } else {
41
+ const providerTypes = getValidProviderTypes()
42
+ const providerKeys = new Set([...providerTypes, ...Object.keys(config.provider).filter(k => k !== "default")])
43
+ if (config.provider.default !== undefined && !providerKeys.has(config.provider.default)) {
44
+ err(errors, "provider.default", `must be one of ${[...providerKeys].join(", ")}`)
45
+ }
46
+ for (const key of providerKeys) {
47
+ const p = config.provider[key]
48
+ if (p === undefined) continue
49
+ if (!isObj(p)) {
50
+ err(errors, `provider.${key}`, "must be object")
51
+ continue
52
+ }
53
+ if (p.type !== undefined && typeof p.type !== "string") err(errors, `provider.${key}.type`, "must be string")
54
+ if (p.base_url !== undefined && typeof p.base_url !== "string") err(errors, `provider.${key}.base_url`, "must be string")
55
+ if (p.api_key !== undefined && typeof p.api_key !== "string") err(errors, `provider.${key}.api_key`, "must be string")
56
+ if (p.api_key_env !== undefined && typeof p.api_key_env !== "string") err(errors, `provider.${key}.api_key_env`, "must be string")
57
+ if (p.default_model !== undefined && typeof p.default_model !== "string") err(errors, `provider.${key}.default_model`, "must be string")
58
+ if (p.timeout_ms !== undefined) checkInt(errors, `provider.${key}.timeout_ms`, p.timeout_ms, 1000)
59
+ if (p.retry_attempts !== undefined) checkInt(errors, `provider.${key}.retry_attempts`, p.retry_attempts, 0)
60
+ if (p.retry_base_delay_ms !== undefined) checkInt(errors, `provider.${key}.retry_base_delay_ms`, p.retry_base_delay_ms, 100)
61
+ if (p.stream !== undefined && typeof p.stream !== "boolean") err(errors, `provider.${key}.stream`, "must be boolean")
62
+ if (p.context_limit !== undefined && p.context_limit !== null) {
63
+ if (!Number.isInteger(p.context_limit) || p.context_limit < 1024) err(errors, `provider.${key}.context_limit`, "must be integer >= 1024 or null")
64
+ }
65
+ if (p.thinking !== undefined && p.thinking !== null) {
66
+ if (!isObj(p.thinking)) err(errors, `provider.${key}.thinking`, "must be object or null")
67
+ else {
68
+ if (p.thinking.type !== undefined && typeof p.thinking.type !== "string") err(errors, `provider.${key}.thinking.type`, "must be string")
69
+ if (p.thinking.budget_tokens !== undefined) {
70
+ if (!Number.isInteger(p.thinking.budget_tokens) || p.thinking.budget_tokens < 0) err(errors, `provider.${key}.thinking.budget_tokens`, "must be integer >= 0")
71
+ }
72
+ }
73
+ }
74
+ }
75
+ if (config.provider.strict_mode !== undefined && typeof config.provider.strict_mode !== "boolean") {
76
+ err(errors, "provider.strict_mode", "must be boolean")
77
+ }
78
+ if (config.provider.model_context !== undefined) {
79
+ if (!isObj(config.provider.model_context)) err(errors, "provider.model_context", "must be object")
80
+ else {
81
+ for (const [mk, mv] of Object.entries(config.provider.model_context)) {
82
+ if (!Number.isInteger(mv) || mv < 1024) err(errors, `provider.model_context.${mk}`, "must be integer >= 1024")
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ if (config.agent !== undefined) {
90
+ if (!isObj(config.agent)) err(errors, "agent", "must be object")
91
+ else {
92
+ if (config.agent.default_mode !== undefined && !VALID_MODES.includes(config.agent.default_mode)) {
93
+ err(errors, "agent.default_mode", `must be one of ${VALID_MODES.join(", ")}`)
94
+ }
95
+ if (config.agent.max_steps !== undefined) checkInt(errors, "agent.max_steps", config.agent.max_steps, 1)
96
+ if (config.agent.longagent !== undefined) {
97
+ if (!isObj(config.agent.longagent)) err(errors, "agent.longagent", "must be object")
98
+ else {
99
+ if (config.agent.longagent.max_iterations !== undefined) {
100
+ checkInt(errors, "agent.longagent.max_iterations", config.agent.longagent.max_iterations, 0)
101
+ }
102
+ if (config.agent.longagent.no_progress_warning !== undefined) {
103
+ checkInt(errors, "agent.longagent.no_progress_warning", config.agent.longagent.no_progress_warning, 1)
104
+ }
105
+ if (config.agent.longagent.no_progress_limit !== undefined) {
106
+ checkInt(errors, "agent.longagent.no_progress_limit", config.agent.longagent.no_progress_limit, 1)
107
+ }
108
+ if (config.agent.longagent.heartbeat_timeout_ms !== undefined) {
109
+ checkInt(errors, "agent.longagent.heartbeat_timeout_ms", config.agent.longagent.heartbeat_timeout_ms, 1000)
110
+ }
111
+ if (config.agent.longagent.checkpoint_interval !== undefined) {
112
+ checkInt(errors, "agent.longagent.checkpoint_interval", config.agent.longagent.checkpoint_interval, 0)
113
+ }
114
+ if (config.agent.longagent.parallel !== undefined) {
115
+ if (!isObj(config.agent.longagent.parallel)) {
116
+ err(errors, "agent.longagent.parallel", "must be object")
117
+ } else {
118
+ if (config.agent.longagent.parallel.enabled !== undefined && typeof config.agent.longagent.parallel.enabled !== "boolean") {
119
+ err(errors, "agent.longagent.parallel.enabled", "must be boolean")
120
+ }
121
+ if (config.agent.longagent.parallel.max_concurrency !== undefined) {
122
+ checkInt(errors, "agent.longagent.parallel.max_concurrency", config.agent.longagent.parallel.max_concurrency, 1)
123
+ }
124
+ if (config.agent.longagent.parallel.stage_pass_rule !== undefined && !["all_success", "majority", "any_success"].includes(config.agent.longagent.parallel.stage_pass_rule)) {
125
+ err(errors, "agent.longagent.parallel.stage_pass_rule", "must be all_success|majority|any_success")
126
+ }
127
+ if (config.agent.longagent.parallel.poll_interval_ms !== undefined) {
128
+ checkInt(errors, "agent.longagent.parallel.poll_interval_ms", config.agent.longagent.parallel.poll_interval_ms, 50)
129
+ }
130
+ if (config.agent.longagent.parallel.task_timeout_ms !== undefined) {
131
+ checkInt(errors, "agent.longagent.parallel.task_timeout_ms", config.agent.longagent.parallel.task_timeout_ms, 1000)
132
+ }
133
+ if (config.agent.longagent.parallel.task_max_retries !== undefined) {
134
+ checkInt(errors, "agent.longagent.parallel.task_max_retries", config.agent.longagent.parallel.task_max_retries, 0)
135
+ }
136
+ }
137
+ }
138
+ if (config.agent.longagent.planner !== undefined) {
139
+ if (!isObj(config.agent.longagent.planner)) {
140
+ err(errors, "agent.longagent.planner", "must be object")
141
+ } else {
142
+ if (config.agent.longagent.planner.intake_questions !== undefined) {
143
+ if (!isObj(config.agent.longagent.planner.intake_questions)) {
144
+ err(errors, "agent.longagent.planner.intake_questions", "must be object")
145
+ } else {
146
+ if (config.agent.longagent.planner.intake_questions.enabled !== undefined && typeof config.agent.longagent.planner.intake_questions.enabled !== "boolean") {
147
+ err(errors, "agent.longagent.planner.intake_questions.enabled", "must be boolean")
148
+ }
149
+ if (config.agent.longagent.planner.intake_questions.max_rounds !== undefined) {
150
+ checkInt(errors, "agent.longagent.planner.intake_questions.max_rounds", config.agent.longagent.planner.intake_questions.max_rounds, 1)
151
+ }
152
+ }
153
+ }
154
+ if (config.agent.longagent.planner.ask_user_after_plan_frozen !== undefined && typeof config.agent.longagent.planner.ask_user_after_plan_frozen !== "boolean") {
155
+ err(errors, "agent.longagent.planner.ask_user_after_plan_frozen", "must be boolean")
156
+ }
157
+ }
158
+ }
159
+ if (config.agent.longagent.lock_timeout_ms !== undefined) {
160
+ checkInt(errors, "agent.longagent.lock_timeout_ms", config.agent.longagent.lock_timeout_ms, 1000)
161
+ }
162
+ if (config.agent.longagent.four_stage !== undefined) {
163
+ if (!isObj(config.agent.longagent.four_stage)) {
164
+ err(errors, "agent.longagent.four_stage", "must be object")
165
+ } else {
166
+ const fs = config.agent.longagent.four_stage
167
+ if (fs.enabled !== undefined && typeof fs.enabled !== "boolean") err(errors, "agent.longagent.four_stage.enabled", "must be boolean")
168
+ if (fs.separate_models !== undefined) {
169
+ if (!isObj(fs.separate_models)) err(errors, "agent.longagent.four_stage.separate_models", "must be object")
170
+ else {
171
+ if (fs.separate_models.enabled !== undefined && typeof fs.separate_models.enabled !== "boolean") err(errors, "agent.longagent.four_stage.separate_models.enabled", "must be boolean")
172
+ for (const k of ["preview_model", "blueprint_model", "coding_model", "debugging_model"]) {
173
+ if (fs.separate_models[k] !== undefined && fs.separate_models[k] !== null && typeof fs.separate_models[k] !== "string") {
174
+ err(errors, `agent.longagent.four_stage.separate_models.${k}`, "must be string or null")
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+ if (config.agent.longagent.hybrid !== undefined) {
182
+ if (!isObj(config.agent.longagent.hybrid)) {
183
+ err(errors, "agent.longagent.hybrid", "must be object")
184
+ } else {
185
+ const hy = config.agent.longagent.hybrid
186
+ if (hy.enabled !== undefined && typeof hy.enabled !== "boolean") err(errors, "agent.longagent.hybrid.enabled", "must be boolean")
187
+ if (hy.separate_models !== undefined) {
188
+ if (!isObj(hy.separate_models)) err(errors, "agent.longagent.hybrid.separate_models", "must be object")
189
+ else {
190
+ if (hy.separate_models.enabled !== undefined && typeof hy.separate_models.enabled !== "boolean") err(errors, "agent.longagent.hybrid.separate_models.enabled", "must be boolean")
191
+ for (const k of ["preview_model", "blueprint_model", "debugging_model"]) {
192
+ if (hy.separate_models[k] !== undefined && hy.separate_models[k] !== null && typeof hy.separate_models[k] !== "string") {
193
+ err(errors, `agent.longagent.hybrid.separate_models.${k}`, "must be string or null")
194
+ }
195
+ }
196
+ }
197
+ }
198
+ if (hy.adaptive_models !== undefined) {
199
+ if (!isObj(hy.adaptive_models)) err(errors, "agent.longagent.hybrid.adaptive_models", "must be object")
200
+ else {
201
+ if (hy.adaptive_models.enabled !== undefined && typeof hy.adaptive_models.enabled !== "boolean") err(errors, "agent.longagent.hybrid.adaptive_models.enabled", "must be boolean")
202
+ for (const k of ["low", "medium", "high"]) {
203
+ if (hy.adaptive_models[k] !== undefined && hy.adaptive_models[k] !== null && typeof hy.adaptive_models[k] !== "string") {
204
+ err(errors, `agent.longagent.hybrid.adaptive_models.${k}`, "must be string or null")
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
211
+ if (config.agent.longagent.resume_incomplete_files !== undefined && typeof config.agent.longagent.resume_incomplete_files !== "boolean") {
212
+ err(errors, "agent.longagent.resume_incomplete_files", "must be boolean")
213
+ }
214
+ if (config.agent.longagent.usability_gates !== undefined) {
215
+ if (!isObj(config.agent.longagent.usability_gates)) {
216
+ err(errors, "agent.longagent.usability_gates", "must be object")
217
+ } else {
218
+ checkGateEnabledObject(errors, "agent.longagent.usability_gates.build", config.agent.longagent.usability_gates.build || {})
219
+ checkGateEnabledObject(errors, "agent.longagent.usability_gates.test", config.agent.longagent.usability_gates.test || {})
220
+ checkGateEnabledObject(errors, "agent.longagent.usability_gates.review", config.agent.longagent.usability_gates.review || {})
221
+ checkGateEnabledObject(errors, "agent.longagent.usability_gates.health", config.agent.longagent.usability_gates.health || {})
222
+ checkGateEnabledObject(errors, "agent.longagent.usability_gates.budget", config.agent.longagent.usability_gates.budget || {})
223
+ }
224
+ }
225
+ }
226
+ }
227
+ if (config.agent.subagents !== undefined && !isObj(config.agent.subagents)) {
228
+ err(errors, "agent.subagents", "must be object")
229
+ }
230
+ if (config.agent.routing !== undefined && !isObj(config.agent.routing)) {
231
+ err(errors, "agent.routing", "must be object")
232
+ }
233
+ }
234
+ }
235
+
236
+ if (config.mcp !== undefined) {
237
+ if (!isObj(config.mcp)) err(errors, "mcp", "must be object")
238
+ else {
239
+ if (config.mcp.servers !== undefined && !isObj(config.mcp.servers)) err(errors, "mcp.servers", "must be object")
240
+ if (config.mcp.timeout_ms !== undefined) checkInt(errors, "mcp.timeout_ms", config.mcp.timeout_ms, 1000)
241
+ if (config.mcp.shutdown_timeout_ms !== undefined) checkInt(errors, "mcp.shutdown_timeout_ms", config.mcp.shutdown_timeout_ms, 100)
242
+ if (config.mcp.max_sse_buffer_bytes !== undefined) checkInt(errors, "mcp.max_sse_buffer_bytes", config.mcp.max_sse_buffer_bytes, 1024)
243
+ if (config.mcp.auto_discover !== undefined && typeof config.mcp.auto_discover !== "boolean") {
244
+ err(errors, "mcp.auto_discover", "must be boolean")
245
+ }
246
+ if (isObj(config.mcp.servers)) {
247
+ for (const [name, server] of Object.entries(config.mcp.servers)) {
248
+ const prefix = `mcp.servers.${name}`
249
+ if (!isObj(server)) {
250
+ err(errors, prefix, "must be object")
251
+ continue
252
+ }
253
+ if (server.enabled !== undefined && typeof server.enabled !== "boolean") {
254
+ err(errors, `${prefix}.enabled`, "must be boolean")
255
+ }
256
+ if (server.type !== undefined && typeof server.type !== "string") {
257
+ err(errors, `${prefix}.type`, "must be string")
258
+ }
259
+ if (server.transport !== undefined && !["stdio", "http", "sse", "streamable-http"].includes(server.transport)) {
260
+ err(errors, `${prefix}.transport`, "must be stdio|http|sse|streamable-http")
261
+ }
262
+ if (server.url !== undefined && typeof server.url !== "string") {
263
+ err(errors, `${prefix}.url`, "must be string")
264
+ }
265
+ if (server.command !== undefined && !Array.isArray(server.command) && typeof server.command !== "string") {
266
+ err(errors, `${prefix}.command`, "must be string or array")
267
+ }
268
+ if (server.args !== undefined) {
269
+ if (!Array.isArray(server.args)) err(errors, `${prefix}.args`, "must be array")
270
+ else if (server.args.some((item) => typeof item !== "string")) err(errors, `${prefix}.args`, "all values must be string")
271
+ }
272
+ if (server.env !== undefined && !isObj(server.env)) {
273
+ err(errors, `${prefix}.env`, "must be object")
274
+ } else if (isObj(server.env)) {
275
+ for (const [k, v] of Object.entries(server.env)) {
276
+ if (typeof v !== "string") err(errors, `${prefix}.env.${k}`, "must be string")
277
+ }
278
+ }
279
+ if (server.headers !== undefined && !isObj(server.headers)) {
280
+ err(errors, `${prefix}.headers`, "must be object")
281
+ } else if (isObj(server.headers)) {
282
+ for (const [k, v] of Object.entries(server.headers)) {
283
+ if (typeof v !== "string") err(errors, `${prefix}.headers.${k}`, "must be string")
284
+ }
285
+ }
286
+ if (server.shell !== undefined && typeof server.shell !== "boolean") {
287
+ err(errors, `${prefix}.shell`, "must be boolean")
288
+ }
289
+ if (server.framing !== undefined && !["auto", "content-length", "newline"].includes(server.framing)) {
290
+ err(errors, `${prefix}.framing`, "must be auto|content-length|newline")
291
+ }
292
+ if (server.health_check_method !== undefined && !["auto", "ping", "tools_list"].includes(server.health_check_method)) {
293
+ err(errors, `${prefix}.health_check_method`, "must be auto|ping|tools_list")
294
+ }
295
+ if (server.startup_timeout_ms !== undefined) checkInt(errors, `${prefix}.startup_timeout_ms`, server.startup_timeout_ms, 100)
296
+ if (server.request_timeout_ms !== undefined) checkInt(errors, `${prefix}.request_timeout_ms`, server.request_timeout_ms, 100)
297
+ if (server.timeout_ms !== undefined) checkInt(errors, `${prefix}.timeout_ms`, server.timeout_ms, 100)
298
+ }
299
+ }
300
+ }
301
+ }
302
+
303
+ if (config.skills !== undefined) {
304
+ if (!isObj(config.skills)) err(errors, "skills", "must be object")
305
+ else {
306
+ if (config.skills.enabled !== undefined && typeof config.skills.enabled !== "boolean") {
307
+ err(errors, "skills.enabled", "must be boolean")
308
+ }
309
+ if (config.skills.auto_seed !== undefined && typeof config.skills.auto_seed !== "boolean") {
310
+ err(errors, "skills.auto_seed", "must be boolean")
311
+ }
312
+ if (config.skills.dirs !== undefined && !Array.isArray(config.skills.dirs)) {
313
+ err(errors, "skills.dirs", "must be array")
314
+ }
315
+ if (config.skills.allowed_commands !== undefined) {
316
+ if (!Array.isArray(config.skills.allowed_commands)) err(errors, "skills.allowed_commands", "must be array")
317
+ else if (config.skills.allowed_commands.some(c => typeof c !== "string")) err(errors, "skills.allowed_commands", "all values must be string")
318
+ }
319
+ }
320
+ }
321
+
322
+ if (config.permission !== undefined) {
323
+ if (!isObj(config.permission)) err(errors, "permission", "must be object")
324
+ else {
325
+ if (config.permission.mode !== undefined && !["auto", "manual", "yolo"].includes(config.permission.mode)) {
326
+ err(errors, "permission.mode", "must be auto|manual|yolo")
327
+ }
328
+ if (config.permission.default_policy !== undefined && !["allow", "deny", "ask"].includes(config.permission.default_policy)) {
329
+ err(errors, "permission.default_policy", "must be allow|deny|ask")
330
+ }
331
+ if (config.permission.non_tty_default !== undefined && !["allow_once", "deny"].includes(config.permission.non_tty_default)) {
332
+ err(errors, "permission.non_tty_default", "must be allow_once|deny")
333
+ }
334
+ if (config.permission.rules !== undefined) {
335
+ if (!Array.isArray(config.permission.rules)) err(errors, "permission.rules", "must be array")
336
+ else {
337
+ for (const [index, rule] of config.permission.rules.entries()) {
338
+ if (!isObj(rule)) {
339
+ err(errors, `permission.rules[${index}]`, "must be object")
340
+ continue
341
+ }
342
+ if (typeof rule.tool !== "string") err(errors, `permission.rules[${index}].tool`, "must be string")
343
+ if (!["allow", "deny", "ask"].includes(rule.action)) {
344
+ err(errors, `permission.rules[${index}].action`, "must be allow|deny|ask")
345
+ }
346
+ if (rule.modes !== undefined) {
347
+ if (!Array.isArray(rule.modes)) err(errors, `permission.rules[${index}].modes`, "must be array")
348
+ else {
349
+ for (const mode of rule.modes) {
350
+ if (!VALID_MODES.includes(mode)) err(errors, `permission.rules[${index}].modes`, `invalid mode ${mode}`)
351
+ }
352
+ }
353
+ }
354
+ if (rule.file_patterns !== undefined) {
355
+ if (!Array.isArray(rule.file_patterns) && typeof rule.file_patterns !== "string") {
356
+ err(errors, `permission.rules[${index}].file_patterns`, "must be string or array of strings")
357
+ } else if (Array.isArray(rule.file_patterns)) {
358
+ for (const pat of rule.file_patterns) {
359
+ if (typeof pat !== "string") err(errors, `permission.rules[${index}].file_patterns`, "each pattern must be string")
360
+ }
361
+ }
362
+ }
363
+ if (rule.command_prefix !== undefined) {
364
+ if (!Array.isArray(rule.command_prefix) && typeof rule.command_prefix !== "string") {
365
+ err(errors, `permission.rules[${index}].command_prefix`, "must be string or array of strings")
366
+ } else if (Array.isArray(rule.command_prefix)) {
367
+ for (const pfx of rule.command_prefix) {
368
+ if (typeof pfx !== "string") err(errors, `permission.rules[${index}].command_prefix`, "each prefix must be string")
369
+ }
370
+ }
371
+ }
372
+ }
373
+ }
374
+ }
375
+ }
376
+ }
377
+
378
+ if (config.storage !== undefined) {
379
+ if (!isObj(config.storage)) err(errors, "storage", "must be object")
380
+ else {
381
+ if (config.storage.session_shard_enabled !== undefined && typeof config.storage.session_shard_enabled !== "boolean") {
382
+ err(errors, "storage.session_shard_enabled", "must be boolean")
383
+ }
384
+ if (config.storage.flush_interval_ms !== undefined) checkInt(errors, "storage.flush_interval_ms", config.storage.flush_interval_ms, 0)
385
+ if (config.storage.event_rotate_mb !== undefined) checkInt(errors, "storage.event_rotate_mb", config.storage.event_rotate_mb, 1)
386
+ if (config.storage.event_retain_days !== undefined) checkInt(errors, "storage.event_retain_days", config.storage.event_retain_days, 1)
387
+ }
388
+ }
389
+
390
+ if (config.background !== undefined) {
391
+ if (!isObj(config.background)) err(errors, "background", "must be object")
392
+ else {
393
+ if (config.background.mode !== undefined && !["worker_process"].includes(config.background.mode)) {
394
+ err(errors, "background.mode", "must be worker_process")
395
+ }
396
+ if (config.background.worker_timeout_ms !== undefined) checkInt(errors, "background.worker_timeout_ms", config.background.worker_timeout_ms, 1000)
397
+ if (config.background.max_parallel !== undefined) checkInt(errors, "background.max_parallel", config.background.max_parallel, 1)
398
+ if (config.background.max_log_lines !== undefined) checkInt(errors, "background.max_log_lines", config.background.max_log_lines, 1)
399
+ }
400
+ }
401
+
402
+ if (config.runtime !== undefined) {
403
+ if (!isObj(config.runtime)) err(errors, "runtime", "must be object")
404
+ else {
405
+ if (config.runtime.tool_registry_cache_ttl_ms !== undefined) {
406
+ checkInt(errors, "runtime.tool_registry_cache_ttl_ms", config.runtime.tool_registry_cache_ttl_ms, 0)
407
+ }
408
+ if (config.runtime.mcp_refresh_ttl_ms !== undefined) {
409
+ checkInt(errors, "runtime.mcp_refresh_ttl_ms", config.runtime.mcp_refresh_ttl_ms, 0)
410
+ }
411
+ }
412
+ }
413
+
414
+ if (config.tool !== undefined) {
415
+ if (!isObj(config.tool)) err(errors, "tool", "must be object")
416
+ else {
417
+ if (config.tool.sources !== undefined && !isObj(config.tool.sources)) err(errors, "tool.sources", "must be object")
418
+ if (config.tool.write_lock !== undefined) {
419
+ if (!isObj(config.tool.write_lock)) err(errors, "tool.write_lock", "must be object")
420
+ else {
421
+ if (config.tool.write_lock.mode !== undefined && !["file_lock", "none"].includes(config.tool.write_lock.mode)) {
422
+ err(errors, "tool.write_lock.mode", "must be file_lock|none")
423
+ }
424
+ if (config.tool.write_lock.wait_timeout_ms !== undefined) {
425
+ checkInt(errors, "tool.write_lock.wait_timeout_ms", config.tool.write_lock.wait_timeout_ms, 0)
426
+ }
427
+ }
428
+ }
429
+ if (config.tool.local_dirs !== undefined && !Array.isArray(config.tool.local_dirs)) err(errors, "tool.local_dirs", "must be array")
430
+ if (config.tool.plugin_dirs !== undefined && !Array.isArray(config.tool.plugin_dirs)) err(errors, "tool.plugin_dirs", "must be array")
431
+ if (config.tool.sensitive_file_patterns !== undefined) {
432
+ if (!Array.isArray(config.tool.sensitive_file_patterns)) err(errors, "tool.sensitive_file_patterns", "must be array")
433
+ else if (config.tool.sensitive_file_patterns.some((pattern) => typeof pattern !== "string")) {
434
+ err(errors, "tool.sensitive_file_patterns", "all values must be string")
435
+ }
436
+ }
437
+ if (config.tool.bash_timeout_ms !== undefined) checkInt(errors, "tool.bash_timeout_ms", config.tool.bash_timeout_ms, 1000)
438
+ }
439
+ }
440
+
441
+ if (config.session !== undefined) {
442
+ if (!isObj(config.session)) err(errors, "session", "must be object")
443
+ else {
444
+ if (config.session.max_history !== undefined) checkInt(errors, "session.max_history", config.session.max_history, 1)
445
+ if (config.session.recovery !== undefined && typeof config.session.recovery !== "boolean") {
446
+ err(errors, "session.recovery", "must be boolean")
447
+ }
448
+ if (config.session.compaction_threshold_ratio !== undefined) {
449
+ const ratio = Number(config.session.compaction_threshold_ratio)
450
+ if (!Number.isFinite(ratio) || ratio <= 0 || ratio > 1) {
451
+ err(errors, "session.compaction_threshold_ratio", "must be number in (0,1]")
452
+ }
453
+ }
454
+ if (config.session.compaction_threshold_messages !== undefined) {
455
+ checkInt(errors, "session.compaction_threshold_messages", config.session.compaction_threshold_messages, 1)
456
+ }
457
+ if (config.session.context_cache_points !== undefined && typeof config.session.context_cache_points !== "boolean") {
458
+ err(errors, "session.context_cache_points", "must be boolean")
459
+ }
460
+ }
461
+ }
462
+
463
+ if (config.review !== undefined) {
464
+ if (!isObj(config.review)) err(errors, "review", "must be object")
465
+ else {
466
+ if (config.review.sort !== undefined && !VALID_REVIEW_SORT.includes(config.review.sort)) {
467
+ err(errors, "review.sort", `must be one of ${VALID_REVIEW_SORT.join(", ")}`)
468
+ }
469
+ if (config.review.default_lines !== undefined) checkInt(errors, "review.default_lines", config.review.default_lines, 1)
470
+ if (config.review.max_expand_lines !== undefined) checkInt(errors, "review.max_expand_lines", config.review.max_expand_lines, 1)
471
+ if (config.review.risk_weights !== undefined) {
472
+ if (!isObj(config.review.risk_weights)) err(errors, "review.risk_weights", "must be object")
473
+ else {
474
+ for (const [key, val] of Object.entries(config.review.risk_weights)) {
475
+ if (typeof val !== "number" || val < 0) err(errors, `review.risk_weights.${key}`, "must be non-negative number")
476
+ }
477
+ }
478
+ }
479
+ }
480
+ }
481
+
482
+ if (config.usage !== undefined) {
483
+ if (!isObj(config.usage)) err(errors, "usage", "must be object")
484
+ else {
485
+ if (config.usage.pricing_file !== undefined && config.usage.pricing_file !== null && typeof config.usage.pricing_file !== "string") {
486
+ err(errors, "usage.pricing_file", "must be string|null")
487
+ }
488
+ if (config.usage.aggregation !== undefined) {
489
+ if (!Array.isArray(config.usage.aggregation)) err(errors, "usage.aggregation", "must be array")
490
+ else {
491
+ for (const scope of config.usage.aggregation) {
492
+ if (!["turn", "session", "global"].includes(scope)) err(errors, "usage.aggregation", `invalid scope ${scope}`)
493
+ }
494
+ }
495
+ }
496
+ if (config.usage.budget !== undefined) {
497
+ if (!isObj(config.usage.budget)) err(errors, "usage.budget", "must be object")
498
+ else {
499
+ if (config.usage.budget.warn_at_percent !== undefined) {
500
+ const v = config.usage.budget.warn_at_percent
501
+ if (typeof v !== "number" || v <= 0 || v > 100) err(errors, "usage.budget.warn_at_percent", "must be number in (0,100]")
502
+ }
503
+ if (config.usage.budget.strategy !== undefined && !["warn", "block"].includes(config.usage.budget.strategy)) {
504
+ err(errors, "usage.budget.strategy", "must be warn|block")
505
+ }
506
+ if (config.usage.budget.budget_limit_usd !== undefined) {
507
+ const v = config.usage.budget.budget_limit_usd
508
+ if (typeof v !== "number" || !Number.isFinite(v) || v < 0) err(errors, "usage.budget.budget_limit_usd", "must be non-negative number")
509
+ }
510
+ }
511
+ }
512
+ }
513
+ }
514
+
515
+ if (config.update !== undefined) {
516
+ if (!isObj(config.update)) err(errors, "update", "must be object")
517
+ else {
518
+ if (config.update.enabled !== undefined && typeof config.update.enabled !== "boolean") err(errors, "update.enabled", "must be boolean")
519
+ if (config.update.notify_on_startup !== undefined && typeof config.update.notify_on_startup !== "boolean") err(errors, "update.notify_on_startup", "must be boolean")
520
+ if (config.update.auto_install !== undefined && typeof config.update.auto_install !== "boolean") err(errors, "update.auto_install", "must be boolean")
521
+ if (config.update.channel !== undefined && typeof config.update.channel !== "string") err(errors, "update.channel", "must be string")
522
+ if (config.update.registry !== undefined && typeof config.update.registry !== "string") err(errors, "update.registry", "must be string")
523
+ if (config.update.check_interval_hours !== undefined) checkInt(errors, "update.check_interval_hours", config.update.check_interval_hours, 0)
524
+ if (config.update.timeout_ms !== undefined) checkInt(errors, "update.timeout_ms", config.update.timeout_ms, 100)
525
+ }
526
+ }
527
+
528
+ if (config.ui !== undefined) {
529
+ if (!isObj(config.ui)) err(errors, "ui", "must be object")
530
+ else {
531
+ if (config.ui.theme_file !== undefined && config.ui.theme_file !== null && typeof config.ui.theme_file !== "string") {
532
+ err(errors, "ui.theme_file", "must be string|null")
533
+ }
534
+ if (config.ui.mode_colors !== undefined) {
535
+ if (!isObj(config.ui.mode_colors)) err(errors, "ui.mode_colors", "must be object")
536
+ else {
537
+ for (const mode of VALID_MODES) {
538
+ if (config.ui.mode_colors[mode] !== undefined) checkColor(errors, `ui.mode_colors.${mode}`, config.ui.mode_colors[mode])
539
+ }
540
+ }
541
+ }
542
+ if (config.ui.layout !== undefined && !["compact", "comfortable"].includes(config.ui.layout)) {
543
+ err(errors, "ui.layout", "must be compact|comfortable")
544
+ }
545
+ if (config.ui.markdown_render !== undefined && typeof config.ui.markdown_render !== "boolean") {
546
+ err(errors, "ui.markdown_render", "must be boolean")
547
+ }
548
+ if (config.ui.status !== undefined) {
549
+ if (!isObj(config.ui.status)) err(errors, "ui.status", "must be object")
550
+ else {
551
+ if (config.ui.status.show_cost !== undefined && typeof config.ui.status.show_cost !== "boolean") {
552
+ err(errors, "ui.status.show_cost", "must be boolean")
553
+ }
554
+ if (config.ui.status.show_token_meter !== undefined && typeof config.ui.status.show_token_meter !== "boolean") {
555
+ err(errors, "ui.status.show_token_meter", "must be boolean")
556
+ }
557
+ }
558
+ }
559
+ }
560
+ }
561
+
562
+ if (config.git_auto !== undefined) {
563
+ if (!isObj(config.git_auto)) {
564
+ err(errors, "git_auto", "must be object")
565
+ } else {
566
+ if (config.git_auto.enabled !== undefined && typeof config.git_auto.enabled !== "boolean") {
567
+ err(errors, "git_auto.enabled", "must be boolean")
568
+ }
569
+ if (config.git_auto.auto_snapshot !== undefined && typeof config.git_auto.auto_snapshot !== "boolean") {
570
+ err(errors, "git_auto.auto_snapshot", "must be boolean")
571
+ }
572
+ if (config.git_auto.max_snapshots !== undefined) {
573
+ checkInt(errors, "git_auto.max_snapshots", config.git_auto.max_snapshots, 1)
574
+ }
575
+ if (config.git_auto.ttl_days !== undefined) {
576
+ checkInt(errors, "git_auto.ttl_days", config.git_auto.ttl_days, 1)
577
+ }
578
+ if (config.git_auto.forbid_commit !== undefined && typeof config.git_auto.forbid_commit !== "boolean") {
579
+ err(errors, "git_auto.forbid_commit", "must be boolean")
580
+ }
581
+ if (config.git_auto.forbid_push !== undefined && typeof config.git_auto.forbid_push !== "boolean") {
582
+ err(errors, "git_auto.forbid_push", "must be boolean")
583
+ }
584
+ // 全自动化模式配置
585
+ if (config.git_auto.full_auto !== undefined && typeof config.git_auto.full_auto !== "boolean") {
586
+ err(errors, "git_auto.full_auto", "must be boolean")
587
+ }
588
+ if (config.git_auto.auto_commit !== undefined && typeof config.git_auto.auto_commit !== "boolean") {
589
+ err(errors, "git_auto.auto_commit", "must be boolean")
590
+ }
591
+ if (config.git_auto.auto_push !== undefined && typeof config.git_auto.auto_push !== "boolean") {
592
+ err(errors, "git_auto.auto_push", "must be boolean")
593
+ }
594
+ if (config.git_auto.allow_dangerous_ops !== undefined && typeof config.git_auto.allow_dangerous_ops !== "boolean") {
595
+ err(errors, "git_auto.allow_dangerous_ops", "must be boolean")
596
+ }
597
+ if (config.git_auto.auto_stage !== undefined && typeof config.git_auto.auto_stage !== "boolean") {
598
+ err(errors, "git_auto.auto_stage", "must be boolean")
599
+ }
600
+ }
601
+ }
602
+
603
+ return { valid: errors.length === 0, errors }
604
+ }