@kkelly-offical/kkcode 0.1.2

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 (196) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +445 -0
  3. package/package.json +46 -0
  4. package/src/agent/agent.mjs +170 -0
  5. package/src/agent/custom-agent-loader.mjs +158 -0
  6. package/src/agent/generator.mjs +115 -0
  7. package/src/agent/prompt/architect.txt +36 -0
  8. package/src/agent/prompt/build-fixer.txt +71 -0
  9. package/src/agent/prompt/build.txt +101 -0
  10. package/src/agent/prompt/compaction.txt +12 -0
  11. package/src/agent/prompt/explore.txt +29 -0
  12. package/src/agent/prompt/guide.txt +40 -0
  13. package/src/agent/prompt/longagent.txt +178 -0
  14. package/src/agent/prompt/plan.txt +50 -0
  15. package/src/agent/prompt/researcher.txt +23 -0
  16. package/src/agent/prompt/reviewer.txt +44 -0
  17. package/src/agent/prompt/security-reviewer.txt +62 -0
  18. package/src/agent/prompt/tdd-guide.txt +84 -0
  19. package/src/agent/prompt/title.txt +8 -0
  20. package/src/command/custom-commands.mjs +57 -0
  21. package/src/commands/agent.mjs +71 -0
  22. package/src/commands/audit.mjs +77 -0
  23. package/src/commands/background.mjs +86 -0
  24. package/src/commands/chat.mjs +114 -0
  25. package/src/commands/command.mjs +41 -0
  26. package/src/commands/config.mjs +44 -0
  27. package/src/commands/doctor.mjs +148 -0
  28. package/src/commands/hook.mjs +29 -0
  29. package/src/commands/init.mjs +141 -0
  30. package/src/commands/longagent.mjs +100 -0
  31. package/src/commands/mcp.mjs +89 -0
  32. package/src/commands/permission.mjs +36 -0
  33. package/src/commands/prompt.mjs +42 -0
  34. package/src/commands/review.mjs +266 -0
  35. package/src/commands/rule.mjs +34 -0
  36. package/src/commands/session.mjs +235 -0
  37. package/src/commands/theme.mjs +98 -0
  38. package/src/commands/usage.mjs +91 -0
  39. package/src/config/defaults.mjs +195 -0
  40. package/src/config/import-config.mjs +76 -0
  41. package/src/config/load-config.mjs +76 -0
  42. package/src/config/schema.mjs +509 -0
  43. package/src/context.mjs +40 -0
  44. package/src/core/constants.mjs +46 -0
  45. package/src/core/errors.mjs +57 -0
  46. package/src/core/events.mjs +29 -0
  47. package/src/core/types.mjs +57 -0
  48. package/src/github/api.mjs +78 -0
  49. package/src/github/auth.mjs +286 -0
  50. package/src/github/flow.mjs +298 -0
  51. package/src/github/workspace.mjs +212 -0
  52. package/src/index.mjs +82 -0
  53. package/src/knowledge/api-design.txt +9 -0
  54. package/src/knowledge/cpp.txt +10 -0
  55. package/src/knowledge/docker.txt +10 -0
  56. package/src/knowledge/dotnet.txt +9 -0
  57. package/src/knowledge/electron.txt +10 -0
  58. package/src/knowledge/flutter.txt +10 -0
  59. package/src/knowledge/go.txt +9 -0
  60. package/src/knowledge/graphql.txt +10 -0
  61. package/src/knowledge/java.txt +9 -0
  62. package/src/knowledge/kotlin.txt +10 -0
  63. package/src/knowledge/loader.mjs +125 -0
  64. package/src/knowledge/next.txt +8 -0
  65. package/src/knowledge/node.txt +8 -0
  66. package/src/knowledge/nuxt.txt +9 -0
  67. package/src/knowledge/php.txt +10 -0
  68. package/src/knowledge/python.txt +10 -0
  69. package/src/knowledge/react-native.txt +10 -0
  70. package/src/knowledge/react.txt +9 -0
  71. package/src/knowledge/ruby.txt +11 -0
  72. package/src/knowledge/rust.txt +9 -0
  73. package/src/knowledge/svelte.txt +9 -0
  74. package/src/knowledge/swift.txt +10 -0
  75. package/src/knowledge/tailwind.txt +10 -0
  76. package/src/knowledge/testing.txt +8 -0
  77. package/src/knowledge/typescript.txt +8 -0
  78. package/src/knowledge/vue.txt +9 -0
  79. package/src/mcp/client-http.mjs +157 -0
  80. package/src/mcp/client-sse.mjs +286 -0
  81. package/src/mcp/client-stdio.mjs +451 -0
  82. package/src/mcp/registry.mjs +394 -0
  83. package/src/mcp/stdio-framing.mjs +127 -0
  84. package/src/orchestration/background-manager.mjs +358 -0
  85. package/src/orchestration/background-worker.mjs +245 -0
  86. package/src/orchestration/longagent-manager.mjs +116 -0
  87. package/src/orchestration/stage-scheduler.mjs +489 -0
  88. package/src/orchestration/subagent-router.mjs +62 -0
  89. package/src/orchestration/task-scheduler.mjs +74 -0
  90. package/src/permission/engine.mjs +92 -0
  91. package/src/permission/exec-policy.mjs +372 -0
  92. package/src/permission/prompt.mjs +39 -0
  93. package/src/permission/rules.mjs +120 -0
  94. package/src/permission/workspace-trust.mjs +44 -0
  95. package/src/plugin/builtin-hooks/console-warn.mjs +41 -0
  96. package/src/plugin/builtin-hooks/extract-patterns.mjs +75 -0
  97. package/src/plugin/builtin-hooks/post-edit-format.mjs +57 -0
  98. package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +61 -0
  99. package/src/plugin/builtin-hooks/strategic-compaction.mjs +38 -0
  100. package/src/plugin/hook-bus.mjs +154 -0
  101. package/src/provider/anthropic.mjs +389 -0
  102. package/src/provider/ollama.mjs +236 -0
  103. package/src/provider/openai-compatible.mjs +1 -0
  104. package/src/provider/openai.mjs +339 -0
  105. package/src/provider/retry-policy.mjs +68 -0
  106. package/src/provider/router.mjs +228 -0
  107. package/src/provider/sse.mjs +91 -0
  108. package/src/repl.mjs +2929 -0
  109. package/src/review/diff-parser.mjs +36 -0
  110. package/src/review/rejection-queue.mjs +62 -0
  111. package/src/review/review-store.mjs +21 -0
  112. package/src/review/risk-score.mjs +61 -0
  113. package/src/rules/load-rules.mjs +64 -0
  114. package/src/runtime.mjs +1 -0
  115. package/src/session/checkpoint.mjs +239 -0
  116. package/src/session/compaction.mjs +276 -0
  117. package/src/session/engine.mjs +225 -0
  118. package/src/session/instinct-manager.mjs +172 -0
  119. package/src/session/instruction-loader.mjs +25 -0
  120. package/src/session/longagent-plan.mjs +329 -0
  121. package/src/session/longagent-scaffold.mjs +100 -0
  122. package/src/session/longagent.mjs +1462 -0
  123. package/src/session/loop.mjs +905 -0
  124. package/src/session/memory-loader.mjs +75 -0
  125. package/src/session/project-context.mjs +367 -0
  126. package/src/session/prompt/anthropic.txt +151 -0
  127. package/src/session/prompt/beast.txt +37 -0
  128. package/src/session/prompt/max-steps.txt +6 -0
  129. package/src/session/prompt/plan.txt +9 -0
  130. package/src/session/prompt/qwen.txt +46 -0
  131. package/src/session/prompt-loader.mjs +18 -0
  132. package/src/session/recovery.mjs +52 -0
  133. package/src/session/store.mjs +503 -0
  134. package/src/session/system-prompt.mjs +260 -0
  135. package/src/session/task-validator.mjs +266 -0
  136. package/src/session/usability-gates.mjs +379 -0
  137. package/src/skill/builtin/backend-patterns.mjs +123 -0
  138. package/src/skill/builtin/commit.mjs +64 -0
  139. package/src/skill/builtin/debug.mjs +45 -0
  140. package/src/skill/builtin/frontend-patterns.mjs +120 -0
  141. package/src/skill/builtin/frontend.mjs +188 -0
  142. package/src/skill/builtin/init.mjs +220 -0
  143. package/src/skill/builtin/review.mjs +49 -0
  144. package/src/skill/builtin/security-checklist.mjs +80 -0
  145. package/src/skill/builtin/tdd.mjs +54 -0
  146. package/src/skill/generator.mjs +113 -0
  147. package/src/skill/registry.mjs +336 -0
  148. package/src/storage/audit-store.mjs +83 -0
  149. package/src/storage/event-log.mjs +82 -0
  150. package/src/storage/ghost-commit-store.mjs +235 -0
  151. package/src/storage/json-store.mjs +53 -0
  152. package/src/storage/paths.mjs +148 -0
  153. package/src/theme/color.mjs +64 -0
  154. package/src/theme/default-theme.mjs +29 -0
  155. package/src/theme/load-theme.mjs +71 -0
  156. package/src/theme/markdown.mjs +135 -0
  157. package/src/theme/schema.mjs +45 -0
  158. package/src/theme/status-bar.mjs +158 -0
  159. package/src/tool/audit-wrapper.mjs +38 -0
  160. package/src/tool/edit-transaction.mjs +126 -0
  161. package/src/tool/executor.mjs +109 -0
  162. package/src/tool/file-lock-manager.mjs +85 -0
  163. package/src/tool/git-auto.mjs +545 -0
  164. package/src/tool/git-full-auto.mjs +478 -0
  165. package/src/tool/image-util.mjs +276 -0
  166. package/src/tool/prompt/background_cancel.txt +1 -0
  167. package/src/tool/prompt/background_output.txt +1 -0
  168. package/src/tool/prompt/bash.txt +71 -0
  169. package/src/tool/prompt/codesearch.txt +18 -0
  170. package/src/tool/prompt/edit.txt +27 -0
  171. package/src/tool/prompt/enter_plan.txt +74 -0
  172. package/src/tool/prompt/exit_plan.txt +62 -0
  173. package/src/tool/prompt/glob.txt +33 -0
  174. package/src/tool/prompt/grep.txt +43 -0
  175. package/src/tool/prompt/list.txt +8 -0
  176. package/src/tool/prompt/multiedit.txt +20 -0
  177. package/src/tool/prompt/notebookedit.txt +21 -0
  178. package/src/tool/prompt/patch.txt +24 -0
  179. package/src/tool/prompt/question.txt +44 -0
  180. package/src/tool/prompt/read.txt +40 -0
  181. package/src/tool/prompt/task.txt +83 -0
  182. package/src/tool/prompt/todowrite.txt +117 -0
  183. package/src/tool/prompt/webfetch.txt +38 -0
  184. package/src/tool/prompt/websearch.txt +43 -0
  185. package/src/tool/prompt/write.txt +38 -0
  186. package/src/tool/prompt-loader.mjs +18 -0
  187. package/src/tool/question-prompt.mjs +86 -0
  188. package/src/tool/registry.mjs +1309 -0
  189. package/src/tool/task-tool.mjs +28 -0
  190. package/src/ui/activity-renderer.mjs +410 -0
  191. package/src/ui/repl-dashboard.mjs +357 -0
  192. package/src/usage/pricing.mjs +121 -0
  193. package/src/usage/usage-meter.mjs +113 -0
  194. package/src/util/git.mjs +496 -0
  195. package/src/util/template.mjs +10 -0
  196. package/src/util/yaml.mjs +100 -0
@@ -0,0 +1,76 @@
1
+ import path from "node:path"
2
+ import { access, readFile } from "node:fs/promises"
3
+ import YAML from "yaml"
4
+ import { DEFAULT_CONFIG } from "./defaults.mjs"
5
+ import { validateConfig } from "./schema.mjs"
6
+ import { projectConfigCandidates, userConfigCandidates } from "../storage/paths.mjs"
7
+
8
+ async function exists(file) {
9
+ try {
10
+ await access(file)
11
+ return true
12
+ } catch {
13
+ return false
14
+ }
15
+ }
16
+
17
+ function parseConfigFile(filePath, content) {
18
+ if (filePath.endsWith(".json")) return JSON.parse(content)
19
+ return YAML.parse(content)
20
+ }
21
+
22
+ function mergeObject(base, override) {
23
+ if (override === undefined || override === null) return base
24
+ if (Array.isArray(override)) return [...override]
25
+ if (!base || typeof base !== "object" || Array.isArray(base)) return override
26
+ if (typeof override !== "object") return override
27
+ const out = { ...base }
28
+ for (const key of Object.keys(override)) {
29
+ out[key] = mergeObject(base[key], override[key])
30
+ }
31
+ return out
32
+ }
33
+
34
+ async function firstExisting(candidates) {
35
+ for (const candidate of candidates) {
36
+ if (await exists(candidate)) return candidate
37
+ }
38
+ return null
39
+ }
40
+
41
+ async function loadOne(filePath) {
42
+ if (!filePath) return { config: {}, errors: [] }
43
+ try {
44
+ const raw = await readFile(filePath, "utf8")
45
+ const parsed = parseConfigFile(filePath, raw) ?? {}
46
+ const check = validateConfig(parsed)
47
+ if (check.valid) return { config: parsed, errors: [] }
48
+ return { config: {}, errors: check.errors.map((error) => `${filePath}: ${error}`) }
49
+ } catch (error) {
50
+ return { config: {}, errors: [`${filePath}: ${error.message}`] }
51
+ }
52
+ }
53
+
54
+ export async function loadConfig(cwd = process.cwd()) {
55
+ const userPath = await firstExisting(userConfigCandidates())
56
+ const projectPath = await firstExisting(projectConfigCandidates(cwd))
57
+
58
+ const userLoaded = await loadOne(userPath)
59
+ const projectLoaded = await loadOne(projectPath)
60
+ const merged = mergeObject(mergeObject(DEFAULT_CONFIG, userLoaded.config), projectLoaded.config)
61
+
62
+ const source = {
63
+ userPath,
64
+ userDir: userPath ? path.dirname(userPath) : null,
65
+ userRaw: userLoaded.config,
66
+ projectPath,
67
+ projectDir: projectPath ? path.dirname(projectPath) : null,
68
+ projectRaw: projectLoaded.config
69
+ }
70
+
71
+ return {
72
+ config: merged,
73
+ source,
74
+ errors: [...userLoaded.errors, ...projectLoaded.errors]
75
+ }
76
+ }
@@ -0,0 +1,509 @@
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 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
+ const providerKeys = new Set([...providerTypes, ...Object.keys(config.provider).filter(k => k !== "default")])
47
+ for (const key of providerKeys) {
48
+ const p = config.provider[key]
49
+ if (p === undefined) continue
50
+ if (!isObj(p)) {
51
+ err(errors, `provider.${key}`, "must be object")
52
+ continue
53
+ }
54
+ if (p.type !== undefined && typeof p.type !== "string") err(errors, `provider.${key}.type`, "must be string")
55
+ if (p.base_url !== undefined && typeof p.base_url !== "string") err(errors, `provider.${key}.base_url`, "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.model_context !== undefined) {
76
+ if (!isObj(config.provider.model_context)) err(errors, "provider.model_context", "must be object")
77
+ else {
78
+ for (const [mk, mv] of Object.entries(config.provider.model_context)) {
79
+ if (!Number.isInteger(mv) || mv < 1024) err(errors, `provider.model_context.${mk}`, "must be integer >= 1024")
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ if (config.agent !== undefined) {
87
+ if (!isObj(config.agent)) err(errors, "agent", "must be object")
88
+ else {
89
+ if (config.agent.default_mode !== undefined && !VALID_MODES.includes(config.agent.default_mode)) {
90
+ err(errors, "agent.default_mode", `must be one of ${VALID_MODES.join(", ")}`)
91
+ }
92
+ if (config.agent.max_steps !== undefined) checkInt(errors, "agent.max_steps", config.agent.max_steps, 1)
93
+ if (config.agent.longagent !== undefined) {
94
+ if (!isObj(config.agent.longagent)) err(errors, "agent.longagent", "must be object")
95
+ else {
96
+ if (config.agent.longagent.max_iterations !== undefined) {
97
+ checkInt(errors, "agent.longagent.max_iterations", config.agent.longagent.max_iterations, 0)
98
+ }
99
+ if (config.agent.longagent.no_progress_warning !== undefined) {
100
+ checkInt(errors, "agent.longagent.no_progress_warning", config.agent.longagent.no_progress_warning, 1)
101
+ }
102
+ if (config.agent.longagent.no_progress_limit !== undefined) {
103
+ checkInt(errors, "agent.longagent.no_progress_limit", config.agent.longagent.no_progress_limit, 1)
104
+ }
105
+ if (config.agent.longagent.heartbeat_timeout_ms !== undefined) {
106
+ checkInt(errors, "agent.longagent.heartbeat_timeout_ms", config.agent.longagent.heartbeat_timeout_ms, 1000)
107
+ }
108
+ if (config.agent.longagent.checkpoint_interval !== undefined) {
109
+ checkInt(errors, "agent.longagent.checkpoint_interval", config.agent.longagent.checkpoint_interval, 0)
110
+ }
111
+ if (config.agent.longagent.parallel !== undefined) {
112
+ if (!isObj(config.agent.longagent.parallel)) {
113
+ err(errors, "agent.longagent.parallel", "must be object")
114
+ } else {
115
+ if (config.agent.longagent.parallel.enabled !== undefined && typeof config.agent.longagent.parallel.enabled !== "boolean") {
116
+ err(errors, "agent.longagent.parallel.enabled", "must be boolean")
117
+ }
118
+ if (config.agent.longagent.parallel.max_concurrency !== undefined) {
119
+ checkInt(errors, "agent.longagent.parallel.max_concurrency", config.agent.longagent.parallel.max_concurrency, 1)
120
+ }
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
+ }
124
+ if (config.agent.longagent.parallel.task_timeout_ms !== undefined) {
125
+ checkInt(errors, "agent.longagent.parallel.task_timeout_ms", config.agent.longagent.parallel.task_timeout_ms, 1000)
126
+ }
127
+ if (config.agent.longagent.parallel.task_max_retries !== undefined) {
128
+ checkInt(errors, "agent.longagent.parallel.task_max_retries", config.agent.longagent.parallel.task_max_retries, 0)
129
+ }
130
+ }
131
+ }
132
+ if (config.agent.longagent.planner !== undefined) {
133
+ if (!isObj(config.agent.longagent.planner)) {
134
+ err(errors, "agent.longagent.planner", "must be object")
135
+ } else {
136
+ if (config.agent.longagent.planner.intake_questions !== undefined) {
137
+ if (!isObj(config.agent.longagent.planner.intake_questions)) {
138
+ err(errors, "agent.longagent.planner.intake_questions", "must be object")
139
+ } else {
140
+ if (config.agent.longagent.planner.intake_questions.enabled !== undefined && typeof config.agent.longagent.planner.intake_questions.enabled !== "boolean") {
141
+ err(errors, "agent.longagent.planner.intake_questions.enabled", "must be boolean")
142
+ }
143
+ if (config.agent.longagent.planner.intake_questions.max_rounds !== undefined) {
144
+ checkInt(errors, "agent.longagent.planner.intake_questions.max_rounds", config.agent.longagent.planner.intake_questions.max_rounds, 1)
145
+ }
146
+ }
147
+ }
148
+ if (config.agent.longagent.planner.ask_user_after_plan_frozen !== undefined && typeof config.agent.longagent.planner.ask_user_after_plan_frozen !== "boolean") {
149
+ err(errors, "agent.longagent.planner.ask_user_after_plan_frozen", "must be boolean")
150
+ }
151
+ }
152
+ }
153
+ if (config.agent.longagent.resume_incomplete_files !== undefined && typeof config.agent.longagent.resume_incomplete_files !== "boolean") {
154
+ err(errors, "agent.longagent.resume_incomplete_files", "must be boolean")
155
+ }
156
+ if (config.agent.longagent.usability_gates !== undefined) {
157
+ if (!isObj(config.agent.longagent.usability_gates)) {
158
+ err(errors, "agent.longagent.usability_gates", "must be object")
159
+ } else {
160
+ checkGateEnabledObject(errors, "agent.longagent.usability_gates.build", config.agent.longagent.usability_gates.build || {})
161
+ checkGateEnabledObject(errors, "agent.longagent.usability_gates.test", config.agent.longagent.usability_gates.test || {})
162
+ checkGateEnabledObject(errors, "agent.longagent.usability_gates.review", config.agent.longagent.usability_gates.review || {})
163
+ checkGateEnabledObject(errors, "agent.longagent.usability_gates.health", config.agent.longagent.usability_gates.health || {})
164
+ checkGateEnabledObject(errors, "agent.longagent.usability_gates.budget", config.agent.longagent.usability_gates.budget || {})
165
+ }
166
+ }
167
+ }
168
+ }
169
+ if (config.agent.subagents !== undefined && !isObj(config.agent.subagents)) {
170
+ err(errors, "agent.subagents", "must be object")
171
+ }
172
+ if (config.agent.routing !== undefined && !isObj(config.agent.routing)) {
173
+ err(errors, "agent.routing", "must be object")
174
+ }
175
+ }
176
+ }
177
+
178
+ if (config.mcp !== undefined) {
179
+ if (!isObj(config.mcp)) err(errors, "mcp", "must be object")
180
+ else {
181
+ if (config.mcp.servers !== undefined && !isObj(config.mcp.servers)) err(errors, "mcp.servers", "must be object")
182
+ if (config.mcp.timeout_ms !== undefined) checkInt(errors, "mcp.timeout_ms", config.mcp.timeout_ms, 1000)
183
+ if (config.mcp.auto_discover !== undefined && typeof config.mcp.auto_discover !== "boolean") {
184
+ err(errors, "mcp.auto_discover", "must be boolean")
185
+ }
186
+ if (isObj(config.mcp.servers)) {
187
+ for (const [name, server] of Object.entries(config.mcp.servers)) {
188
+ const prefix = `mcp.servers.${name}`
189
+ if (!isObj(server)) {
190
+ err(errors, prefix, "must be object")
191
+ continue
192
+ }
193
+ if (server.enabled !== undefined && typeof server.enabled !== "boolean") {
194
+ err(errors, `${prefix}.enabled`, "must be boolean")
195
+ }
196
+ if (server.type !== undefined && typeof server.type !== "string") {
197
+ err(errors, `${prefix}.type`, "must be string")
198
+ }
199
+ if (server.transport !== undefined && !["stdio", "http", "sse", "streamable-http"].includes(server.transport)) {
200
+ err(errors, `${prefix}.transport`, "must be stdio|http|sse|streamable-http")
201
+ }
202
+ if (server.url !== undefined && typeof server.url !== "string") {
203
+ err(errors, `${prefix}.url`, "must be string")
204
+ }
205
+ if (server.command !== undefined && !Array.isArray(server.command) && typeof server.command !== "string") {
206
+ err(errors, `${prefix}.command`, "must be string or array")
207
+ }
208
+ if (server.args !== undefined) {
209
+ if (!Array.isArray(server.args)) err(errors, `${prefix}.args`, "must be array")
210
+ else if (server.args.some((item) => typeof item !== "string")) err(errors, `${prefix}.args`, "all values must be string")
211
+ }
212
+ if (server.env !== undefined && !isObj(server.env)) {
213
+ err(errors, `${prefix}.env`, "must be object")
214
+ } else if (isObj(server.env)) {
215
+ for (const [k, v] of Object.entries(server.env)) {
216
+ if (typeof v !== "string") err(errors, `${prefix}.env.${k}`, "must be string")
217
+ }
218
+ }
219
+ if (server.headers !== undefined && !isObj(server.headers)) {
220
+ err(errors, `${prefix}.headers`, "must be object")
221
+ } else if (isObj(server.headers)) {
222
+ for (const [k, v] of Object.entries(server.headers)) {
223
+ if (typeof v !== "string") err(errors, `${prefix}.headers.${k}`, "must be string")
224
+ }
225
+ }
226
+ if (server.shell !== undefined && typeof server.shell !== "boolean") {
227
+ err(errors, `${prefix}.shell`, "must be boolean")
228
+ }
229
+ if (server.framing !== undefined && !["auto", "content-length", "newline"].includes(server.framing)) {
230
+ err(errors, `${prefix}.framing`, "must be auto|content-length|newline")
231
+ }
232
+ if (server.health_check_method !== undefined && !["auto", "ping", "tools_list"].includes(server.health_check_method)) {
233
+ err(errors, `${prefix}.health_check_method`, "must be auto|ping|tools_list")
234
+ }
235
+ if (server.startup_timeout_ms !== undefined) checkInt(errors, `${prefix}.startup_timeout_ms`, server.startup_timeout_ms, 100)
236
+ if (server.request_timeout_ms !== undefined) checkInt(errors, `${prefix}.request_timeout_ms`, server.request_timeout_ms, 100)
237
+ if (server.timeout_ms !== undefined) checkInt(errors, `${prefix}.timeout_ms`, server.timeout_ms, 100)
238
+ }
239
+ }
240
+ }
241
+ }
242
+
243
+ if (config.skills !== undefined) {
244
+ if (!isObj(config.skills)) err(errors, "skills", "must be object")
245
+ else {
246
+ if (config.skills.enabled !== undefined && typeof config.skills.enabled !== "boolean") {
247
+ err(errors, "skills.enabled", "must be boolean")
248
+ }
249
+ if (config.skills.dirs !== undefined && !Array.isArray(config.skills.dirs)) {
250
+ err(errors, "skills.dirs", "must be array")
251
+ }
252
+ }
253
+ }
254
+
255
+ if (config.permission !== undefined) {
256
+ if (!isObj(config.permission)) err(errors, "permission", "must be object")
257
+ else {
258
+ if (config.permission.default_policy !== undefined && !["allow", "deny", "ask"].includes(config.permission.default_policy)) {
259
+ err(errors, "permission.default_policy", "must be allow|deny|ask")
260
+ }
261
+ if (config.permission.non_tty_default !== undefined && !["allow_once", "deny"].includes(config.permission.non_tty_default)) {
262
+ err(errors, "permission.non_tty_default", "must be allow_once|deny")
263
+ }
264
+ if (config.permission.rules !== undefined) {
265
+ if (!Array.isArray(config.permission.rules)) err(errors, "permission.rules", "must be array")
266
+ else {
267
+ for (const [index, rule] of config.permission.rules.entries()) {
268
+ if (!isObj(rule)) {
269
+ err(errors, `permission.rules[${index}]`, "must be object")
270
+ continue
271
+ }
272
+ if (typeof rule.tool !== "string") err(errors, `permission.rules[${index}].tool`, "must be string")
273
+ if (!["allow", "deny", "ask"].includes(rule.action)) {
274
+ err(errors, `permission.rules[${index}].action`, "must be allow|deny|ask")
275
+ }
276
+ if (rule.modes !== undefined) {
277
+ if (!Array.isArray(rule.modes)) err(errors, `permission.rules[${index}].modes`, "must be array")
278
+ else {
279
+ for (const mode of rule.modes) {
280
+ if (!VALID_MODES.includes(mode)) err(errors, `permission.rules[${index}].modes`, `invalid mode ${mode}`)
281
+ }
282
+ }
283
+ }
284
+ if (rule.file_patterns !== undefined) {
285
+ if (!Array.isArray(rule.file_patterns) && typeof rule.file_patterns !== "string") {
286
+ err(errors, `permission.rules[${index}].file_patterns`, "must be string or array of strings")
287
+ } else if (Array.isArray(rule.file_patterns)) {
288
+ for (const pat of rule.file_patterns) {
289
+ if (typeof pat !== "string") err(errors, `permission.rules[${index}].file_patterns`, "each pattern must be string")
290
+ }
291
+ }
292
+ }
293
+ if (rule.command_prefix !== undefined) {
294
+ if (!Array.isArray(rule.command_prefix) && typeof rule.command_prefix !== "string") {
295
+ err(errors, `permission.rules[${index}].command_prefix`, "must be string or array of strings")
296
+ } else if (Array.isArray(rule.command_prefix)) {
297
+ for (const pfx of rule.command_prefix) {
298
+ if (typeof pfx !== "string") err(errors, `permission.rules[${index}].command_prefix`, "each prefix must be string")
299
+ }
300
+ }
301
+ }
302
+ }
303
+ }
304
+ }
305
+ }
306
+ }
307
+
308
+ if (config.storage !== undefined) {
309
+ if (!isObj(config.storage)) err(errors, "storage", "must be object")
310
+ else {
311
+ if (config.storage.session_shard_enabled !== undefined && typeof config.storage.session_shard_enabled !== "boolean") {
312
+ err(errors, "storage.session_shard_enabled", "must be boolean")
313
+ }
314
+ if (config.storage.flush_interval_ms !== undefined) checkInt(errors, "storage.flush_interval_ms", config.storage.flush_interval_ms, 0)
315
+ if (config.storage.event_rotate_mb !== undefined) checkInt(errors, "storage.event_rotate_mb", config.storage.event_rotate_mb, 1)
316
+ if (config.storage.event_retain_days !== undefined) checkInt(errors, "storage.event_retain_days", config.storage.event_retain_days, 1)
317
+ }
318
+ }
319
+
320
+ if (config.background !== undefined) {
321
+ if (!isObj(config.background)) err(errors, "background", "must be object")
322
+ else {
323
+ if (config.background.mode !== undefined && !["worker_process"].includes(config.background.mode)) {
324
+ err(errors, "background.mode", "must be worker_process")
325
+ }
326
+ if (config.background.worker_timeout_ms !== undefined) checkInt(errors, "background.worker_timeout_ms", config.background.worker_timeout_ms, 1000)
327
+ if (config.background.max_parallel !== undefined) checkInt(errors, "background.max_parallel", config.background.max_parallel, 1)
328
+ }
329
+ }
330
+
331
+ if (config.runtime !== undefined) {
332
+ if (!isObj(config.runtime)) err(errors, "runtime", "must be object")
333
+ else {
334
+ if (config.runtime.tool_registry_cache_ttl_ms !== undefined) {
335
+ checkInt(errors, "runtime.tool_registry_cache_ttl_ms", config.runtime.tool_registry_cache_ttl_ms, 0)
336
+ }
337
+ if (config.runtime.mcp_refresh_ttl_ms !== undefined) {
338
+ checkInt(errors, "runtime.mcp_refresh_ttl_ms", config.runtime.mcp_refresh_ttl_ms, 0)
339
+ }
340
+ }
341
+ }
342
+
343
+ if (config.tool !== undefined) {
344
+ if (!isObj(config.tool)) err(errors, "tool", "must be object")
345
+ else {
346
+ if (config.tool.sources !== undefined && !isObj(config.tool.sources)) err(errors, "tool.sources", "must be object")
347
+ if (config.tool.write_lock !== undefined) {
348
+ if (!isObj(config.tool.write_lock)) err(errors, "tool.write_lock", "must be object")
349
+ else {
350
+ if (config.tool.write_lock.mode !== undefined && !["file_lock", "none"].includes(config.tool.write_lock.mode)) {
351
+ err(errors, "tool.write_lock.mode", "must be file_lock|none")
352
+ }
353
+ if (config.tool.write_lock.wait_timeout_ms !== undefined) {
354
+ checkInt(errors, "tool.write_lock.wait_timeout_ms", config.tool.write_lock.wait_timeout_ms, 0)
355
+ }
356
+ }
357
+ }
358
+ if (config.tool.local_dirs !== undefined && !Array.isArray(config.tool.local_dirs)) err(errors, "tool.local_dirs", "must be array")
359
+ if (config.tool.plugin_dirs !== undefined && !Array.isArray(config.tool.plugin_dirs)) err(errors, "tool.plugin_dirs", "must be array")
360
+ }
361
+ }
362
+
363
+ if (config.session !== undefined) {
364
+ if (!isObj(config.session)) err(errors, "session", "must be object")
365
+ else {
366
+ if (config.session.max_history !== undefined) checkInt(errors, "session.max_history", config.session.max_history, 1)
367
+ if (config.session.recovery !== undefined && typeof config.session.recovery !== "boolean") {
368
+ err(errors, "session.recovery", "must be boolean")
369
+ }
370
+ if (config.session.compaction_threshold_ratio !== undefined) {
371
+ const ratio = Number(config.session.compaction_threshold_ratio)
372
+ if (!Number.isFinite(ratio) || ratio <= 0 || ratio > 1) {
373
+ err(errors, "session.compaction_threshold_ratio", "must be number in (0,1]")
374
+ }
375
+ }
376
+ if (config.session.compaction_threshold_messages !== undefined) {
377
+ checkInt(errors, "session.compaction_threshold_messages", config.session.compaction_threshold_messages, 1)
378
+ }
379
+ if (config.session.context_cache_points !== undefined && typeof config.session.context_cache_points !== "boolean") {
380
+ err(errors, "session.context_cache_points", "must be boolean")
381
+ }
382
+ }
383
+ }
384
+
385
+ if (config.review !== undefined) {
386
+ if (!isObj(config.review)) err(errors, "review", "must be object")
387
+ else {
388
+ if (config.review.sort !== undefined && !VALID_REVIEW_SORT.includes(config.review.sort)) {
389
+ err(errors, "review.sort", `must be one of ${VALID_REVIEW_SORT.join(", ")}`)
390
+ }
391
+ if (config.review.default_lines !== undefined) checkInt(errors, "review.default_lines", config.review.default_lines, 1)
392
+ if (config.review.max_expand_lines !== undefined) checkInt(errors, "review.max_expand_lines", config.review.max_expand_lines, 1)
393
+ if (config.review.risk_weights !== undefined) {
394
+ if (!isObj(config.review.risk_weights)) err(errors, "review.risk_weights", "must be object")
395
+ else {
396
+ for (const [key, val] of Object.entries(config.review.risk_weights)) {
397
+ if (typeof val !== "number" || val < 0) err(errors, `review.risk_weights.${key}`, "must be non-negative number")
398
+ }
399
+ }
400
+ }
401
+ }
402
+ }
403
+
404
+ if (config.usage !== undefined) {
405
+ if (!isObj(config.usage)) err(errors, "usage", "must be object")
406
+ else {
407
+ if (config.usage.pricing_file !== undefined && config.usage.pricing_file !== null && typeof config.usage.pricing_file !== "string") {
408
+ err(errors, "usage.pricing_file", "must be string|null")
409
+ }
410
+ if (config.usage.aggregation !== undefined) {
411
+ if (!Array.isArray(config.usage.aggregation)) err(errors, "usage.aggregation", "must be array")
412
+ else {
413
+ for (const scope of config.usage.aggregation) {
414
+ if (!["turn", "session", "global"].includes(scope)) err(errors, "usage.aggregation", `invalid scope ${scope}`)
415
+ }
416
+ }
417
+ }
418
+ if (config.usage.budget !== undefined) {
419
+ if (!isObj(config.usage.budget)) err(errors, "usage.budget", "must be object")
420
+ else {
421
+ if (config.usage.budget.warn_at_percent !== undefined) {
422
+ const v = config.usage.budget.warn_at_percent
423
+ if (typeof v !== "number" || v <= 0 || v > 100) err(errors, "usage.budget.warn_at_percent", "must be number in (0,100]")
424
+ }
425
+ if (config.usage.budget.strategy !== undefined && !["warn", "block"].includes(config.usage.budget.strategy)) {
426
+ err(errors, "usage.budget.strategy", "must be warn|block")
427
+ }
428
+ }
429
+ }
430
+ }
431
+ }
432
+
433
+ if (config.ui !== undefined) {
434
+ if (!isObj(config.ui)) err(errors, "ui", "must be object")
435
+ else {
436
+ if (config.ui.theme_file !== undefined && config.ui.theme_file !== null && typeof config.ui.theme_file !== "string") {
437
+ err(errors, "ui.theme_file", "must be string|null")
438
+ }
439
+ if (config.ui.mode_colors !== undefined) {
440
+ if (!isObj(config.ui.mode_colors)) err(errors, "ui.mode_colors", "must be object")
441
+ else {
442
+ for (const mode of VALID_MODES) {
443
+ if (config.ui.mode_colors[mode] !== undefined) checkColor(errors, `ui.mode_colors.${mode}`, config.ui.mode_colors[mode])
444
+ }
445
+ }
446
+ }
447
+ if (config.ui.layout !== undefined && !["compact", "comfortable"].includes(config.ui.layout)) {
448
+ err(errors, "ui.layout", "must be compact|comfortable")
449
+ }
450
+ if (config.ui.markdown_render !== undefined && typeof config.ui.markdown_render !== "boolean") {
451
+ err(errors, "ui.markdown_render", "must be boolean")
452
+ }
453
+ if (config.ui.status !== undefined) {
454
+ if (!isObj(config.ui.status)) err(errors, "ui.status", "must be object")
455
+ else {
456
+ if (config.ui.status.show_cost !== undefined && typeof config.ui.status.show_cost !== "boolean") {
457
+ err(errors, "ui.status.show_cost", "must be boolean")
458
+ }
459
+ if (config.ui.status.show_token_meter !== undefined && typeof config.ui.status.show_token_meter !== "boolean") {
460
+ err(errors, "ui.status.show_token_meter", "must be boolean")
461
+ }
462
+ }
463
+ }
464
+ }
465
+ }
466
+
467
+ if (config.git_auto !== undefined) {
468
+ if (!isObj(config.git_auto)) {
469
+ err(errors, "git_auto", "must be object")
470
+ } else {
471
+ if (config.git_auto.enabled !== undefined && typeof config.git_auto.enabled !== "boolean") {
472
+ err(errors, "git_auto.enabled", "must be boolean")
473
+ }
474
+ if (config.git_auto.auto_snapshot !== undefined && typeof config.git_auto.auto_snapshot !== "boolean") {
475
+ err(errors, "git_auto.auto_snapshot", "must be boolean")
476
+ }
477
+ if (config.git_auto.max_snapshots !== undefined) {
478
+ checkInt(errors, "git_auto.max_snapshots", config.git_auto.max_snapshots, 1)
479
+ }
480
+ if (config.git_auto.ttl_days !== undefined) {
481
+ checkInt(errors, "git_auto.ttl_days", config.git_auto.ttl_days, 1)
482
+ }
483
+ if (config.git_auto.forbid_commit !== undefined && typeof config.git_auto.forbid_commit !== "boolean") {
484
+ err(errors, "git_auto.forbid_commit", "must be boolean")
485
+ }
486
+ if (config.git_auto.forbid_push !== undefined && typeof config.git_auto.forbid_push !== "boolean") {
487
+ err(errors, "git_auto.forbid_push", "must be boolean")
488
+ }
489
+ // 全自动化模式配置
490
+ if (config.git_auto.full_auto !== undefined && typeof config.git_auto.full_auto !== "boolean") {
491
+ err(errors, "git_auto.full_auto", "must be boolean")
492
+ }
493
+ if (config.git_auto.auto_commit !== undefined && typeof config.git_auto.auto_commit !== "boolean") {
494
+ err(errors, "git_auto.auto_commit", "must be boolean")
495
+ }
496
+ if (config.git_auto.auto_push !== undefined && typeof config.git_auto.auto_push !== "boolean") {
497
+ err(errors, "git_auto.auto_push", "must be boolean")
498
+ }
499
+ if (config.git_auto.allow_dangerous_ops !== undefined && typeof config.git_auto.allow_dangerous_ops !== "boolean") {
500
+ err(errors, "git_auto.allow_dangerous_ops", "must be boolean")
501
+ }
502
+ if (config.git_auto.auto_stage !== undefined && typeof config.git_auto.auto_stage !== "boolean") {
503
+ err(errors, "git_auto.auto_stage", "must be boolean")
504
+ }
505
+ }
506
+ }
507
+
508
+ return { valid: errors.length === 0, errors }
509
+ }
@@ -0,0 +1,40 @@
1
+ import { loadConfig } from "./config/load-config.mjs"
2
+ import { loadTheme } from "./theme/load-theme.mjs"
3
+ import { configureSessionStore } from "./session/store.mjs"
4
+ import { configureEventLog } from "./storage/event-log.mjs"
5
+ import { configureAuditStore } from "./storage/audit-store.mjs"
6
+ import { checkWorkspaceTrust } from "./permission/workspace-trust.mjs"
7
+
8
+ export async function buildContext(options = {}) {
9
+ const configState = await loadConfig(options.cwd ?? process.cwd())
10
+
11
+ configureSessionStore({
12
+ sessionShardEnabled: Boolean(configState.config.storage?.session_shard_enabled ?? true),
13
+ flushIntervalMs: Number(configState.config.storage?.flush_interval_ms ?? 1000)
14
+ })
15
+ configureEventLog({
16
+ rotateMb: Number(configState.config.storage?.event_rotate_mb ?? 32),
17
+ retainDays: Number(configState.config.storage?.event_retain_days ?? 14)
18
+ })
19
+ configureAuditStore({
20
+ maxEntries: Number(configState.config.storage?.audit_max_entries ?? 5000)
21
+ })
22
+
23
+ const themeState = await loadTheme(configState, options.themeFile ?? null)
24
+ const cwd = options.cwd ?? process.cwd()
25
+ const trustState = options.trustState ?? await checkWorkspaceTrust({ cwd, cliTrust: Boolean(options.trust), isTTY: process.stdin.isTTY })
26
+ return {
27
+ configState,
28
+ themeState,
29
+ trustState
30
+ }
31
+ }
32
+
33
+ export function printContextWarnings(ctx) {
34
+ for (const error of ctx.configState.errors) {
35
+ console.error(`config warning: ${error}`)
36
+ }
37
+ for (const error of ctx.themeState.errors) {
38
+ console.error(`theme warning: ${error}`)
39
+ }
40
+ }
@@ -0,0 +1,46 @@
1
+ export const MODES = ["ask", "plan", "agent", "longagent"]
2
+
3
+ export const TOOL_STATUSES = ["running", "completed", "error", "cancelled"]
4
+
5
+ export const PERMISSION_DECISIONS = ["allow_once", "allow_session", "deny"]
6
+
7
+ export const DEFAULT_MAX_STEPS = 8
8
+ export const DEFAULT_REQUEST_TIMEOUT_MS = 120000
9
+ export const DEFAULT_RETRY_ATTEMPTS = 3
10
+ export const DEFAULT_LONGAGENT_RETRY_STORM_THRESHOLD = 3
11
+ export const DEFAULT_LONGAGENT_TOKEN_ALERT_THRESHOLD = 120000
12
+
13
+ export const EVENT_TYPES = {
14
+ TURN_START: "turn.start",
15
+ TURN_STEP_START: "turn.step.start",
16
+ TURN_STEP_FINISH: "turn.step.finish",
17
+ TURN_FINISH: "turn.finish",
18
+ TURN_ERROR: "turn.error",
19
+ TOOL_START: "tool.start",
20
+ TOOL_FINISH: "tool.finish",
21
+ TOOL_ERROR: "tool.error",
22
+ PERMISSION_ASKED: "permission.asked",
23
+ PERMISSION_DECIDED: "permission.decided",
24
+ REVIEW_DECISION: "review.decision",
25
+ MCP_HEALTH: "mcp.health",
26
+ MCP_REQUEST: "mcp.request",
27
+ LONGAGENT_HEARTBEAT: "longagent.heartbeat",
28
+ LONGAGENT_ALERT: "longagent.alert",
29
+ LONGAGENT_PHASE_CHANGED: "longagent.phase.changed",
30
+ LONGAGENT_GATE_CHECKED: "longagent.gate.checked",
31
+ LONGAGENT_RECOVERY_ENTERED: "longagent.recovery.entered",
32
+ LONGAGENT_INTAKE_STARTED: "longagent.intake.started",
33
+ LONGAGENT_PLAN_FROZEN: "longagent.plan.frozen",
34
+ LONGAGENT_STAGE_STARTED: "longagent.stage.started",
35
+ LONGAGENT_STAGE_TASK_DISPATCHED: "longagent.stage.task.dispatched",
36
+ LONGAGENT_STAGE_TASK_FINISHED: "longagent.stage.task.finished",
37
+ LONGAGENT_STAGE_FINISHED: "longagent.stage.finished",
38
+ LONGAGENT_SCAFFOLD_COMPLETE: "longagent.scaffold.complete",
39
+ LONGAGENT_GIT_BRANCH_CREATED: "longagent.git.branch.created",
40
+ LONGAGENT_GIT_STAGE_COMMITTED: "longagent.git.stage.committed",
41
+ LONGAGENT_GIT_MERGED: "longagent.git.merged",
42
+ SESSION_COMPACTED: "session.compacted",
43
+ TURN_USAGE_UPDATE: "turn.usage.update",
44
+ STREAM_TEXT_START: "stream.text.start",
45
+ STREAM_THINKING_START: "stream.thinking.start"
46
+ }