@kkelly-offical/kkcode 0.1.3 → 0.1.6

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 (58) hide show
  1. package/README.md +110 -172
  2. package/package.json +46 -46
  3. package/src/agent/agent.mjs +41 -0
  4. package/src/agent/prompt/frontend-designer.txt +58 -0
  5. package/src/agent/prompt/longagent-blueprint-agent.txt +83 -0
  6. package/src/agent/prompt/longagent-coding-agent.txt +37 -0
  7. package/src/agent/prompt/longagent-debugging-agent.txt +46 -0
  8. package/src/agent/prompt/longagent-preview-agent.txt +63 -0
  9. package/src/config/defaults.mjs +260 -195
  10. package/src/config/schema.mjs +71 -6
  11. package/src/core/constants.mjs +91 -46
  12. package/src/index.mjs +1 -1
  13. package/src/knowledge/frontend-aesthetics.txt +39 -0
  14. package/src/knowledge/loader.mjs +2 -1
  15. package/src/knowledge/tailwind.txt +12 -3
  16. package/src/mcp/client-http.mjs +141 -157
  17. package/src/mcp/client-sse.mjs +288 -286
  18. package/src/mcp/client-stdio.mjs +533 -451
  19. package/src/mcp/constants.mjs +2 -0
  20. package/src/mcp/registry.mjs +479 -394
  21. package/src/mcp/stdio-framing.mjs +133 -127
  22. package/src/mcp/tool-result.mjs +24 -0
  23. package/src/observability/index.mjs +42 -0
  24. package/src/observability/metrics.mjs +137 -0
  25. package/src/observability/tracer.mjs +137 -0
  26. package/src/orchestration/background-manager.mjs +372 -358
  27. package/src/orchestration/background-worker.mjs +305 -245
  28. package/src/orchestration/longagent-manager.mjs +171 -116
  29. package/src/orchestration/stage-scheduler.mjs +728 -489
  30. package/src/permission/exec-policy.mjs +9 -11
  31. package/src/provider/anthropic.mjs +1 -0
  32. package/src/provider/openai.mjs +340 -339
  33. package/src/provider/retry-policy.mjs +68 -68
  34. package/src/provider/router.mjs +241 -228
  35. package/src/provider/sse.mjs +104 -91
  36. package/src/repl.mjs +1 -1
  37. package/src/session/checkpoint.mjs +66 -3
  38. package/src/session/engine.mjs +227 -225
  39. package/src/session/longagent-4stage.mjs +460 -0
  40. package/src/session/longagent-hybrid.mjs +1081 -0
  41. package/src/session/longagent-plan.mjs +365 -329
  42. package/src/session/longagent-project-memory.mjs +53 -0
  43. package/src/session/longagent-scaffold.mjs +291 -100
  44. package/src/session/longagent-task-bus.mjs +54 -0
  45. package/src/session/longagent-utils.mjs +472 -0
  46. package/src/session/longagent.mjs +884 -1462
  47. package/src/session/project-context.mjs +30 -0
  48. package/src/session/store.mjs +510 -503
  49. package/src/session/task-validator.mjs +4 -3
  50. package/src/skill/builtin/design.mjs +76 -0
  51. package/src/skill/builtin/frontend.mjs +8 -0
  52. package/src/skill/registry.mjs +390 -336
  53. package/src/storage/ghost-commit-store.mjs +18 -8
  54. package/src/tool/executor.mjs +11 -0
  55. package/src/tool/git-auto.mjs +0 -19
  56. package/src/tool/registry.mjs +71 -37
  57. package/src/ui/activity-renderer.mjs +664 -410
  58. package/src/util/git.mjs +23 -0
@@ -1,195 +1,260 @@
1
- export const DEFAULT_CONFIG = {
2
- language: "en",
3
- provider: {
4
- default: "openai",
5
- openai: {
6
- base_url: "https://api.openai.com/v1",
7
- api_key_env: "OPENAI_API_KEY",
8
- default_model: "gpt-5.3-codex",
9
- models: ["gpt-5.3-codex", "gpt-5.2"],
10
- timeout_ms: 120000,
11
- stream_idle_timeout_ms: 120000,
12
- max_tokens: 32768,
13
- context_limit: null,
14
- retry_attempts: 3,
15
- retry_base_delay_ms: 800,
16
- stream: true,
17
- thinking: null
18
- },
19
- anthropic: {
20
- base_url: "https://api.anthropic.com/v1",
21
- api_key_env: "ANTHROPIC_API_KEY",
22
- default_model: "claude-opus-4-6",
23
- models: ["claude-sonnet-4-5", "claude-sonnet-4-6", "claude-haiku-4-5-20251001", "claude-opus-4-6"],
24
- timeout_ms: 120000,
25
- stream_idle_timeout_ms: 120000,
26
- max_tokens: 32768,
27
- context_limit: null,
28
- retry_attempts: 3,
29
- retry_base_delay_ms: 800,
30
- stream: true,
31
- thinking: null
32
- },
33
- ollama: {
34
- base_url: "http://localhost:11434",
35
- api_key_env: "",
36
- default_model: "llama3.1",
37
- timeout_ms: 300000,
38
- stream_idle_timeout_ms: 300000,
39
- max_tokens: 32768,
40
- context_limit: null,
41
- retry_attempts: 1,
42
- retry_base_delay_ms: 1000,
43
- stream: true,
44
- thinking: null
45
- },
46
- model_context: {}
47
- },
48
- agent: {
49
- default_mode: "agent",
50
- max_steps: 8,
51
- longagent: {
52
- max_iterations: 0,
53
- no_progress_warning: 3,
54
- no_progress_limit: 5,
55
- max_stage_recoveries: 3,
56
- heartbeat_timeout_ms: 120000,
57
- checkpoint_interval: 5,
58
- parallel: {
59
- enabled: true,
60
- max_concurrency: 3,
61
- stage_pass_rule: "all_success",
62
- task_timeout_ms: 600000,
63
- task_max_retries: 2
64
- },
65
- planner: {
66
- intake_questions: {
67
- enabled: true,
68
- max_rounds: 6
69
- },
70
- ask_user_after_plan_frozen: false
71
- },
72
- resume_incomplete_files: true,
73
- scaffold: {
74
- enabled: true
75
- },
76
- git: {
77
- enabled: "ask",
78
- auto_branch: true,
79
- auto_commit_stages: true,
80
- auto_merge: true,
81
- branch_prefix: "kkcode"
82
- },
83
- usability_gates: {
84
- prompt_user: "first_run",
85
- build: { enabled: true },
86
- test: { enabled: true },
87
- review: { enabled: true },
88
- health: { enabled: true },
89
- budget: { enabled: true }
90
- }
91
- },
92
- subagents: {},
93
- routing: {
94
- categories: {}
95
- }
96
- },
97
- mcp: {
98
- servers: {},
99
- auto_discover: true,
100
- timeout_ms: 30000
101
- },
102
- skills: {
103
- enabled: true,
104
- dirs: [".kkcode/skills"]
105
- },
106
- permission: {
107
- default_policy: "ask",
108
- non_tty_default: "deny",
109
- rules: []
110
- },
111
- storage: {
112
- session_shard_enabled: true,
113
- flush_interval_ms: 1000,
114
- event_rotate_mb: 32,
115
- event_retain_days: 14
116
- },
117
- background: {
118
- mode: "worker_process",
119
- worker_timeout_ms: 900000,
120
- max_parallel: 2
121
- },
122
- runtime: {
123
- tool_registry_cache_ttl_ms: 30000,
124
- mcp_refresh_ttl_ms: 60000
125
- },
126
- tool: {
127
- sources: {
128
- builtin: true,
129
- local: true,
130
- mcp: true,
131
- plugin: true
132
- },
133
- write_lock: {
134
- mode: "file_lock",
135
- wait_timeout_ms: 120000
136
- },
137
- local_dirs: [".kkcode/tools", ".kkcode/tool"],
138
- plugin_dirs: [".kkcode/plugins", ".kkcode/plugin"]
139
- },
140
- session: {
141
- max_history: 30,
142
- recovery: true,
143
- compaction_threshold_ratio: 0.7,
144
- compaction_threshold_messages: 50,
145
- context_cache_points: true
146
- },
147
- review: {
148
- sort: "risk_first",
149
- default_lines: 80,
150
- max_expand_lines: 1200,
151
- risk_weights: {
152
- sensitive_path: 4,
153
- large_change: 3,
154
- medium_change: 2,
155
- small_change: 1,
156
- executable_script: 2,
157
- command_pattern: 3
158
- }
159
- },
160
- usage: {
161
- pricing_file: null,
162
- aggregation: ["turn", "session", "global"],
163
- budget: {
164
- session_usd: null,
165
- global_usd: null,
166
- warn_at_percent: 80,
167
- strategy: "warn"
168
- }
169
- },
170
- ui: {
171
- theme_file: null,
172
- mode_colors: {
173
- ask: "#8da3b9",
174
- plan: "#00b7c2",
175
- agent: "#2ac26f",
176
- longagent: "#ff7a33"
177
- },
178
- layout: "compact",
179
- markdown_render: true,
180
- status: {
181
- show_cost: true,
182
- show_token_meter: true
183
- }
184
- }
185
- }
186
-
187
- export const VALID_PROVIDER_TYPES = ["openai", "anthropic", "ollama", "openai-compatible"]
188
-
189
- import { listProviders } from "../provider/router.mjs"
190
- export function getValidProviderTypes() {
191
- return listProviders()
192
- }
193
- export const VALID_MODES = ["ask", "plan", "agent", "longagent"]
194
- export const VALID_REVIEW_SORT = ["risk_first", "time_order", "file_order"]
195
- export const VALID_LANGUAGES = ["en", "zh"]
1
+ export const DEFAULT_CONFIG = {
2
+ language: "en",
3
+ provider: {
4
+ default: "openai",
5
+ strict_mode: false,
6
+ openai: {
7
+ base_url: "https://api.openai.com/v1",
8
+ api_key_env: "OPENAI_API_KEY",
9
+ default_model: "gpt-5.3-codex",
10
+ models: ["gpt-5.3-codex", "gpt-5.2"],
11
+ timeout_ms: 120000,
12
+ stream_idle_timeout_ms: 120000,
13
+ max_tokens: 32768,
14
+ context_limit: null,
15
+ retry_attempts: 3,
16
+ retry_base_delay_ms: 800,
17
+ stream: true,
18
+ thinking: null
19
+ },
20
+ anthropic: {
21
+ base_url: "https://api.anthropic.com/v1",
22
+ api_key_env: "ANTHROPIC_API_KEY",
23
+ default_model: "claude-opus-4-6",
24
+ models: ["claude-sonnet-4-5", "claude-sonnet-4-6", "claude-haiku-4-5-20251001", "claude-opus-4-6"],
25
+ timeout_ms: 120000,
26
+ stream_idle_timeout_ms: 120000,
27
+ max_tokens: 32768,
28
+ context_limit: null,
29
+ retry_attempts: 3,
30
+ retry_base_delay_ms: 800,
31
+ stream: true,
32
+ thinking: null
33
+ },
34
+ ollama: {
35
+ base_url: "http://localhost:11434",
36
+ api_key_env: "",
37
+ default_model: "llama3.1",
38
+ timeout_ms: 300000,
39
+ stream_idle_timeout_ms: 300000,
40
+ max_tokens: 32768,
41
+ context_limit: null,
42
+ retry_attempts: 1,
43
+ retry_base_delay_ms: 1000,
44
+ stream: true,
45
+ thinking: null
46
+ },
47
+ model_context: {}
48
+ },
49
+ agent: {
50
+ default_mode: "agent",
51
+ max_steps: 8,
52
+ longagent: {
53
+ max_iterations: 0,
54
+ no_progress_warning: 3,
55
+ no_progress_limit: 5,
56
+ max_stage_recoveries: 3,
57
+ heartbeat_timeout_ms: 120000,
58
+ checkpoint_interval: 5,
59
+ lock_timeout_ms: 5000,
60
+ parallel: {
61
+ enabled: true,
62
+ max_concurrency: 3,
63
+ stage_pass_rule: "all_success",
64
+ task_timeout_ms: 600000,
65
+ task_max_retries: 2,
66
+ poll_interval_ms: 300
67
+ },
68
+ planner: {
69
+ intake_questions: {
70
+ enabled: true,
71
+ max_rounds: 6
72
+ },
73
+ ask_user_after_plan_frozen: false
74
+ },
75
+ four_stage: {
76
+ enabled: false,
77
+ preview_max_iterations: 10,
78
+ blueprint_max_iterations: 10,
79
+ coding_max_iterations: 50,
80
+ debugging_max_iterations: 20,
81
+ separate_models: {
82
+ enabled: false,
83
+ preview_model: null,
84
+ blueprint_model: null,
85
+ coding_model: null,
86
+ debugging_model: null
87
+ }
88
+ },
89
+ hybrid: {
90
+ enabled: true,
91
+ intake: true,
92
+ completion_validation: true,
93
+ debugging_max_iterations: 20,
94
+ max_coding_rollbacks: 2,
95
+ parallel_preview: true,
96
+ blueprint_review: false,
97
+ blueprint_validation: true,
98
+ tdd_mode: false,
99
+ cross_review: true,
100
+ incremental_gates: true,
101
+ context_pressure_limit: 8000,
102
+ budget_awareness: true,
103
+ checkpoint_resume: true,
104
+ project_memory: true,
105
+ task_bus: true,
106
+ coding_phase_timeout_ms: 1800000,
107
+ debugging_phase_timeout_ms: 600000,
108
+ checkpoint_max_keep: 10,
109
+ checkpoint_cleanup: true,
110
+ degradation: {
111
+ enabled: true,
112
+ fallback_model: null,
113
+ skip_non_critical: true
114
+ },
115
+ separate_models: {
116
+ enabled: false,
117
+ preview_model: null,
118
+ blueprint_model: null,
119
+ debugging_model: null
120
+ },
121
+ adaptive_models: {
122
+ enabled: false,
123
+ low: null,
124
+ medium: null,
125
+ high: null
126
+ }
127
+ },
128
+ resume_incomplete_files: true,
129
+ scaffold: {
130
+ enabled: true
131
+ },
132
+ git: {
133
+ enabled: "ask",
134
+ auto_branch: true,
135
+ auto_commit_stages: true,
136
+ auto_merge: true,
137
+ branch_prefix: "kkcode"
138
+ },
139
+ usability_gates: {
140
+ prompt_user: "first_run",
141
+ build: { enabled: true },
142
+ test: { enabled: true },
143
+ review: { enabled: true },
144
+ health: { enabled: true },
145
+ budget: { enabled: true }
146
+ }
147
+ },
148
+ subagents: {},
149
+ routing: {
150
+ categories: {}
151
+ }
152
+ },
153
+ mcp: {
154
+ servers: {},
155
+ auto_discover: true,
156
+ timeout_ms: 30000,
157
+ max_reconnect_attempts: 5,
158
+ circuit_reset_ms: 60000,
159
+ health_check_interval_ms: 0,
160
+ max_buffer_bytes: 16777216,
161
+ shutdown_timeout_ms: 5000,
162
+ max_sse_buffer_bytes: 4194304
163
+ },
164
+ skills: {
165
+ enabled: true,
166
+ dirs: [".kkcode/skills"],
167
+ allowed_commands: []
168
+ },
169
+ permission: {
170
+ default_policy: "ask",
171
+ non_tty_default: "deny",
172
+ rules: []
173
+ },
174
+ storage: {
175
+ session_shard_enabled: true,
176
+ flush_interval_ms: 1000,
177
+ event_rotate_mb: 32,
178
+ event_retain_days: 14
179
+ },
180
+ background: {
181
+ mode: "worker_process",
182
+ worker_timeout_ms: 900000,
183
+ max_parallel: 2,
184
+ max_log_lines: 300
185
+ },
186
+ runtime: {
187
+ tool_registry_cache_ttl_ms: 30000,
188
+ mcp_refresh_ttl_ms: 60000
189
+ },
190
+ tool: {
191
+ sources: {
192
+ builtin: true,
193
+ local: true,
194
+ mcp: true,
195
+ plugin: true
196
+ },
197
+ bash_timeout_ms: 120000,
198
+ write_lock: {
199
+ mode: "file_lock",
200
+ wait_timeout_ms: 120000
201
+ },
202
+ local_dirs: [".kkcode/tools", ".kkcode/tool"],
203
+ plugin_dirs: [".kkcode/plugins", ".kkcode/plugin"]
204
+ },
205
+ session: {
206
+ max_history: 30,
207
+ recovery: true,
208
+ compaction_threshold_ratio: 0.7,
209
+ compaction_threshold_messages: 50,
210
+ context_cache_points: true
211
+ },
212
+ review: {
213
+ sort: "risk_first",
214
+ default_lines: 80,
215
+ max_expand_lines: 1200,
216
+ risk_weights: {
217
+ sensitive_path: 4,
218
+ large_change: 3,
219
+ medium_change: 2,
220
+ small_change: 1,
221
+ executable_script: 2,
222
+ command_pattern: 3
223
+ }
224
+ },
225
+ usage: {
226
+ pricing_file: null,
227
+ aggregation: ["turn", "session", "global"],
228
+ budget: {
229
+ session_usd: null,
230
+ global_usd: null,
231
+ warn_at_percent: 80,
232
+ strategy: "warn"
233
+ }
234
+ },
235
+ ui: {
236
+ theme_file: null,
237
+ mode_colors: {
238
+ ask: "#8da3b9",
239
+ plan: "#00b7c2",
240
+ agent: "#2ac26f",
241
+ longagent: "#ff7a33"
242
+ },
243
+ layout: "compact",
244
+ markdown_render: true,
245
+ status: {
246
+ show_cost: true,
247
+ show_token_meter: true
248
+ }
249
+ }
250
+ }
251
+
252
+ export const VALID_PROVIDER_TYPES = ["openai", "anthropic", "ollama", "openai-compatible"]
253
+
254
+ import { listProviders } from "../provider/router.mjs"
255
+ export function getValidProviderTypes() {
256
+ return listProviders()
257
+ }
258
+ export const VALID_MODES = ["ask", "plan", "agent", "longagent"]
259
+ export const VALID_REVIEW_SORT = ["risk_first", "time_order", "file_order"]
260
+ export const VALID_LANGUAGES = ["en", "zh"]
@@ -39,11 +39,10 @@ export function validateConfig(config) {
39
39
  err(errors, "provider", "must be object")
40
40
  } else {
41
41
  const providerTypes = getValidProviderTypes()
42
- const knownKeys = new Set([...providerTypes, ...Object.keys(config.provider).filter(k => k !== "default")])
43
- if (config.provider.default !== undefined && !knownKeys.has(config.provider.default)) {
44
- err(errors, "provider.default", `must be one of ${[...knownKeys].join(", ")}`)
45
- }
46
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
+ }
47
46
  for (const key of providerKeys) {
48
47
  const p = config.provider[key]
49
48
  if (p === undefined) continue
@@ -72,6 +71,9 @@ export function validateConfig(config) {
72
71
  }
73
72
  }
74
73
  }
74
+ if (config.provider.strict_mode !== undefined && typeof config.provider.strict_mode !== "boolean") {
75
+ err(errors, "provider.strict_mode", "must be boolean")
76
+ }
75
77
  if (config.provider.model_context !== undefined) {
76
78
  if (!isObj(config.provider.model_context)) err(errors, "provider.model_context", "must be object")
77
79
  else {
@@ -118,8 +120,11 @@ export function validateConfig(config) {
118
120
  if (config.agent.longagent.parallel.max_concurrency !== undefined) {
119
121
  checkInt(errors, "agent.longagent.parallel.max_concurrency", config.agent.longagent.parallel.max_concurrency, 1)
120
122
  }
121
- if (config.agent.longagent.parallel.stage_pass_rule !== undefined && config.agent.longagent.parallel.stage_pass_rule !== "all_success") {
122
- err(errors, "agent.longagent.parallel.stage_pass_rule", "must be all_success")
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)
123
128
  }
124
129
  if (config.agent.longagent.parallel.task_timeout_ms !== undefined) {
125
130
  checkInt(errors, "agent.longagent.parallel.task_timeout_ms", config.agent.longagent.parallel.task_timeout_ms, 1000)
@@ -150,6 +155,58 @@ export function validateConfig(config) {
150
155
  }
151
156
  }
152
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
+ }
153
210
  if (config.agent.longagent.resume_incomplete_files !== undefined && typeof config.agent.longagent.resume_incomplete_files !== "boolean") {
154
211
  err(errors, "agent.longagent.resume_incomplete_files", "must be boolean")
155
212
  }
@@ -180,6 +237,8 @@ export function validateConfig(config) {
180
237
  else {
181
238
  if (config.mcp.servers !== undefined && !isObj(config.mcp.servers)) err(errors, "mcp.servers", "must be object")
182
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)
183
242
  if (config.mcp.auto_discover !== undefined && typeof config.mcp.auto_discover !== "boolean") {
184
243
  err(errors, "mcp.auto_discover", "must be boolean")
185
244
  }
@@ -249,6 +308,10 @@ export function validateConfig(config) {
249
308
  if (config.skills.dirs !== undefined && !Array.isArray(config.skills.dirs)) {
250
309
  err(errors, "skills.dirs", "must be array")
251
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
+ }
252
315
  }
253
316
  }
254
317
 
@@ -325,6 +388,7 @@ export function validateConfig(config) {
325
388
  }
326
389
  if (config.background.worker_timeout_ms !== undefined) checkInt(errors, "background.worker_timeout_ms", config.background.worker_timeout_ms, 1000)
327
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)
328
392
  }
329
393
  }
330
394
 
@@ -357,6 +421,7 @@ export function validateConfig(config) {
357
421
  }
358
422
  if (config.tool.local_dirs !== undefined && !Array.isArray(config.tool.local_dirs)) err(errors, "tool.local_dirs", "must be array")
359
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)
360
425
  }
361
426
  }
362
427