@the-open-engine/zeroshot 5.4.0

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 (295) hide show
  1. package/CHANGELOG.md +539 -0
  2. package/LICENSE +21 -0
  3. package/README.md +508 -0
  4. package/cli/commands/inspect-render.js +135 -0
  5. package/cli/commands/inspect.js +294 -0
  6. package/cli/commands/providers.js +149 -0
  7. package/cli/index.js +5431 -0
  8. package/cli/lib/first-run.js +211 -0
  9. package/cli/lib/update-checker.js +281 -0
  10. package/cli/message-formatter-utils.js +75 -0
  11. package/cli/message-formatters-normal.js +275 -0
  12. package/cli/message-formatters-watch.js +185 -0
  13. package/cluster-templates/base-templates/debug-workflow.json +422 -0
  14. package/cluster-templates/base-templates/full-workflow.json +727 -0
  15. package/cluster-templates/base-templates/heavy-validation.json +272 -0
  16. package/cluster-templates/base-templates/quick-validation.json +285 -0
  17. package/cluster-templates/base-templates/single-worker.json +71 -0
  18. package/cluster-templates/base-templates/worker-validator.json +230 -0
  19. package/cluster-templates/conductor-bootstrap.json +130 -0
  20. package/docker/zeroshot-cluster/Dockerfile +153 -0
  21. package/docker/zeroshot-cluster/pre-baked-deps.json +28 -0
  22. package/lib/agent-cli-provider/adapters/claude-parser.d.ts +3 -0
  23. package/lib/agent-cli-provider/adapters/claude-parser.d.ts.map +1 -0
  24. package/lib/agent-cli-provider/adapters/claude-parser.js +122 -0
  25. package/lib/agent-cli-provider/adapters/claude-parser.js.map +1 -0
  26. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts +15 -0
  27. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts.map +1 -0
  28. package/lib/agent-cli-provider/adapters/claude-recovery.js +165 -0
  29. package/lib/agent-cli-provider/adapters/claude-recovery.js.map +1 -0
  30. package/lib/agent-cli-provider/adapters/claude.d.ts +3 -0
  31. package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -0
  32. package/lib/agent-cli-provider/adapters/claude.js +181 -0
  33. package/lib/agent-cli-provider/adapters/claude.js.map +1 -0
  34. package/lib/agent-cli-provider/adapters/codex-parser.d.ts +3 -0
  35. package/lib/agent-cli-provider/adapters/codex-parser.d.ts.map +1 -0
  36. package/lib/agent-cli-provider/adapters/codex-parser.js +192 -0
  37. package/lib/agent-cli-provider/adapters/codex-parser.js.map +1 -0
  38. package/lib/agent-cli-provider/adapters/codex.d.ts +3 -0
  39. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -0
  40. package/lib/agent-cli-provider/adapters/codex.js +151 -0
  41. package/lib/agent-cli-provider/adapters/codex.js.map +1 -0
  42. package/lib/agent-cli-provider/adapters/common.d.ts +28 -0
  43. package/lib/agent-cli-provider/adapters/common.d.ts.map +1 -0
  44. package/lib/agent-cli-provider/adapters/common.js +71 -0
  45. package/lib/agent-cli-provider/adapters/common.js.map +1 -0
  46. package/lib/agent-cli-provider/adapters/gemini.d.ts +3 -0
  47. package/lib/agent-cli-provider/adapters/gemini.d.ts.map +1 -0
  48. package/lib/agent-cli-provider/adapters/gemini.js +176 -0
  49. package/lib/agent-cli-provider/adapters/gemini.js.map +1 -0
  50. package/lib/agent-cli-provider/adapters/index.d.ts +9 -0
  51. package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -0
  52. package/lib/agent-cli-provider/adapters/index.js +95 -0
  53. package/lib/agent-cli-provider/adapters/index.js.map +1 -0
  54. package/lib/agent-cli-provider/adapters/opencode.d.ts +3 -0
  55. package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -0
  56. package/lib/agent-cli-provider/adapters/opencode.js +228 -0
  57. package/lib/agent-cli-provider/adapters/opencode.js.map +1 -0
  58. package/lib/agent-cli-provider/contract-actions.d.ts +5 -0
  59. package/lib/agent-cli-provider/contract-actions.d.ts.map +1 -0
  60. package/lib/agent-cli-provider/contract-actions.js +140 -0
  61. package/lib/agent-cli-provider/contract-actions.js.map +1 -0
  62. package/lib/agent-cli-provider/contract-env.d.ts +11 -0
  63. package/lib/agent-cli-provider/contract-env.d.ts.map +1 -0
  64. package/lib/agent-cli-provider/contract-env.js +113 -0
  65. package/lib/agent-cli-provider/contract-env.js.map +1 -0
  66. package/lib/agent-cli-provider/contract-envelope.d.ts +49 -0
  67. package/lib/agent-cli-provider/contract-envelope.d.ts.map +1 -0
  68. package/lib/agent-cli-provider/contract-envelope.js +52 -0
  69. package/lib/agent-cli-provider/contract-envelope.js.map +1 -0
  70. package/lib/agent-cli-provider/contract-errors.d.ts +22 -0
  71. package/lib/agent-cli-provider/contract-errors.d.ts.map +1 -0
  72. package/lib/agent-cli-provider/contract-errors.js +71 -0
  73. package/lib/agent-cli-provider/contract-errors.js.map +1 -0
  74. package/lib/agent-cli-provider/contract-fallback.d.ts +7 -0
  75. package/lib/agent-cli-provider/contract-fallback.d.ts.map +1 -0
  76. package/lib/agent-cli-provider/contract-fallback.js +102 -0
  77. package/lib/agent-cli-provider/contract-fallback.js.map +1 -0
  78. package/lib/agent-cli-provider/contract-invoke.d.ts +5 -0
  79. package/lib/agent-cli-provider/contract-invoke.d.ts.map +1 -0
  80. package/lib/agent-cli-provider/contract-invoke.js +89 -0
  81. package/lib/agent-cli-provider/contract-invoke.js.map +1 -0
  82. package/lib/agent-cli-provider/contract-options.d.ts +3 -0
  83. package/lib/agent-cli-provider/contract-options.d.ts.map +1 -0
  84. package/lib/agent-cli-provider/contract-options.js +140 -0
  85. package/lib/agent-cli-provider/contract-options.js.map +1 -0
  86. package/lib/agent-cli-provider/contract-parse.d.ts +15 -0
  87. package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -0
  88. package/lib/agent-cli-provider/contract-parse.js +81 -0
  89. package/lib/agent-cli-provider/contract-parse.js.map +1 -0
  90. package/lib/agent-cli-provider/contract-support.d.ts +18 -0
  91. package/lib/agent-cli-provider/contract-support.d.ts.map +1 -0
  92. package/lib/agent-cli-provider/contract-support.js +154 -0
  93. package/lib/agent-cli-provider/contract-support.js.map +1 -0
  94. package/lib/agent-cli-provider/contract.d.ts +13 -0
  95. package/lib/agent-cli-provider/contract.d.ts.map +1 -0
  96. package/lib/agent-cli-provider/contract.js +30 -0
  97. package/lib/agent-cli-provider/contract.js.map +1 -0
  98. package/lib/agent-cli-provider/env-safety.d.ts +11 -0
  99. package/lib/agent-cli-provider/env-safety.d.ts.map +1 -0
  100. package/lib/agent-cli-provider/env-safety.js +83 -0
  101. package/lib/agent-cli-provider/env-safety.js.map +1 -0
  102. package/lib/agent-cli-provider/errors.d.ts +5 -0
  103. package/lib/agent-cli-provider/errors.d.ts.map +1 -0
  104. package/lib/agent-cli-provider/errors.js +115 -0
  105. package/lib/agent-cli-provider/errors.js.map +1 -0
  106. package/lib/agent-cli-provider/executable.d.ts +3 -0
  107. package/lib/agent-cli-provider/executable.d.ts.map +1 -0
  108. package/lib/agent-cli-provider/executable.js +24 -0
  109. package/lib/agent-cli-provider/executable.js.map +1 -0
  110. package/lib/agent-cli-provider/index.d.ts +8 -0
  111. package/lib/agent-cli-provider/index.d.ts.map +1 -0
  112. package/lib/agent-cli-provider/index.js +31 -0
  113. package/lib/agent-cli-provider/index.js.map +1 -0
  114. package/lib/agent-cli-provider/invoke-evidence.d.ts +4 -0
  115. package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -0
  116. package/lib/agent-cli-provider/invoke-evidence.js +17 -0
  117. package/lib/agent-cli-provider/invoke-evidence.js.map +1 -0
  118. package/lib/agent-cli-provider/json.d.ts +16 -0
  119. package/lib/agent-cli-provider/json.d.ts.map +1 -0
  120. package/lib/agent-cli-provider/json.js +110 -0
  121. package/lib/agent-cli-provider/json.js.map +1 -0
  122. package/lib/agent-cli-provider/log-prefix.d.ts +2 -0
  123. package/lib/agent-cli-provider/log-prefix.d.ts.map +1 -0
  124. package/lib/agent-cli-provider/log-prefix.js +22 -0
  125. package/lib/agent-cli-provider/log-prefix.js.map +1 -0
  126. package/lib/agent-cli-provider/process-runner.d.ts +17 -0
  127. package/lib/agent-cli-provider/process-runner.d.ts.map +1 -0
  128. package/lib/agent-cli-provider/process-runner.js +89 -0
  129. package/lib/agent-cli-provider/process-runner.js.map +1 -0
  130. package/lib/agent-cli-provider/redaction.d.ts +9 -0
  131. package/lib/agent-cli-provider/redaction.d.ts.map +1 -0
  132. package/lib/agent-cli-provider/redaction.js +227 -0
  133. package/lib/agent-cli-provider/redaction.js.map +1 -0
  134. package/lib/agent-cli-provider/schema.d.ts +5 -0
  135. package/lib/agent-cli-provider/schema.d.ts.map +1 -0
  136. package/lib/agent-cli-provider/schema.js +136 -0
  137. package/lib/agent-cli-provider/schema.js.map +1 -0
  138. package/lib/agent-cli-provider/single-agent-runtime.d.ts +15 -0
  139. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -0
  140. package/lib/agent-cli-provider/single-agent-runtime.js +228 -0
  141. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -0
  142. package/lib/agent-cli-provider/types.d.ts +194 -0
  143. package/lib/agent-cli-provider/types.d.ts.map +1 -0
  144. package/lib/agent-cli-provider/types.js +12 -0
  145. package/lib/agent-cli-provider/types.js.map +1 -0
  146. package/lib/completion.js +174 -0
  147. package/lib/detached-startup.js +220 -0
  148. package/lib/docker-config.js +220 -0
  149. package/lib/git-remote-utils.js +165 -0
  150. package/lib/id-detector.js +55 -0
  151. package/lib/provider-defaults.js +62 -0
  152. package/lib/provider-detection.js +59 -0
  153. package/lib/provider-names.js +57 -0
  154. package/lib/repo-settings.js +69 -0
  155. package/lib/settings/claude-auth.js +78 -0
  156. package/lib/settings.js +542 -0
  157. package/lib/start-cluster.js +321 -0
  158. package/lib/stream-json-parser.js +67 -0
  159. package/package.json +162 -0
  160. package/scripts/fix-node-pty-permissions.js +75 -0
  161. package/scripts/record-demo.sh +279 -0
  162. package/scripts/setup-merge-queue.sh +170 -0
  163. package/scripts/test-install.sh +40 -0
  164. package/scripts/validate-templates.js +107 -0
  165. package/src/agent/agent-config.js +266 -0
  166. package/src/agent/agent-context-builder.js +189 -0
  167. package/src/agent/agent-context-sections.js +338 -0
  168. package/src/agent/agent-context-sources.js +147 -0
  169. package/src/agent/agent-hook-executor.js +721 -0
  170. package/src/agent/agent-input-injector.js +141 -0
  171. package/src/agent/agent-lifecycle.js +982 -0
  172. package/src/agent/agent-quality-gate-schema.js +93 -0
  173. package/src/agent/agent-quality-gates-context.js +51 -0
  174. package/src/agent/agent-stuck-detector.js +256 -0
  175. package/src/agent/agent-task-executor.js +2028 -0
  176. package/src/agent/agent-trigger-evaluator.js +67 -0
  177. package/src/agent/context-metrics.js +160 -0
  178. package/src/agent/context-pack-builder.js +367 -0
  179. package/src/agent/context-replay-policy.js +51 -0
  180. package/src/agent/guidance-queue.js +77 -0
  181. package/src/agent/output-extraction.js +367 -0
  182. package/src/agent/output-reformatter.js +175 -0
  183. package/src/agent/pr-verification.js +653 -0
  184. package/src/agent/rate-limit-backoff.js +82 -0
  185. package/src/agent/schema-utils.js +146 -0
  186. package/src/agent/validation-platform.js +35 -0
  187. package/src/agent-cli-provider/adapters/claude-parser.ts +133 -0
  188. package/src/agent-cli-provider/adapters/claude-recovery.ts +203 -0
  189. package/src/agent-cli-provider/adapters/claude.ts +247 -0
  190. package/src/agent-cli-provider/adapters/codex-parser.ts +211 -0
  191. package/src/agent-cli-provider/adapters/codex.ts +217 -0
  192. package/src/agent-cli-provider/adapters/common.ts +124 -0
  193. package/src/agent-cli-provider/adapters/gemini.ts +243 -0
  194. package/src/agent-cli-provider/adapters/index.ts +126 -0
  195. package/src/agent-cli-provider/adapters/opencode.ts +286 -0
  196. package/src/agent-cli-provider/contract-actions.ts +150 -0
  197. package/src/agent-cli-provider/contract-env.ts +111 -0
  198. package/src/agent-cli-provider/contract-envelope.ts +110 -0
  199. package/src/agent-cli-provider/contract-errors.ts +66 -0
  200. package/src/agent-cli-provider/contract-fallback.ts +121 -0
  201. package/src/agent-cli-provider/contract-invoke.ts +104 -0
  202. package/src/agent-cli-provider/contract-options.ts +173 -0
  203. package/src/agent-cli-provider/contract-parse.ts +94 -0
  204. package/src/agent-cli-provider/contract-support.ts +167 -0
  205. package/src/agent-cli-provider/contract.ts +56 -0
  206. package/src/agent-cli-provider/env-safety.ts +82 -0
  207. package/src/agent-cli-provider/errors.ts +122 -0
  208. package/src/agent-cli-provider/executable.ts +24 -0
  209. package/src/agent-cli-provider/index.ts +83 -0
  210. package/src/agent-cli-provider/invoke-evidence.ts +18 -0
  211. package/src/agent-cli-provider/json.ts +114 -0
  212. package/src/agent-cli-provider/log-prefix.ts +20 -0
  213. package/src/agent-cli-provider/process-runner.ts +145 -0
  214. package/src/agent-cli-provider/redaction.ts +282 -0
  215. package/src/agent-cli-provider/schema.ts +115 -0
  216. package/src/agent-cli-provider/single-agent-runtime.ts +311 -0
  217. package/src/agent-cli-provider/types.ts +237 -0
  218. package/src/agent-wrapper.js +615 -0
  219. package/src/agents/git-pusher-template.js +705 -0
  220. package/src/attach/attach-client.js +438 -0
  221. package/src/attach/attach-server.js +543 -0
  222. package/src/attach/index.js +37 -0
  223. package/src/attach/protocol.js +220 -0
  224. package/src/attach/ring-buffer.js +121 -0
  225. package/src/attach/send-input.js +88 -0
  226. package/src/attach/socket-discovery.js +267 -0
  227. package/src/claude-task-runner.js +661 -0
  228. package/src/config-router.js +89 -0
  229. package/src/config-validator.js +2202 -0
  230. package/src/copy-worker.js +43 -0
  231. package/src/guidance-topics.js +10 -0
  232. package/src/input-helpers.js +65 -0
  233. package/src/isolation-manager.js +1734 -0
  234. package/src/issue-providers/README.md +305 -0
  235. package/src/issue-providers/azure-devops-provider.js +307 -0
  236. package/src/issue-providers/base-provider.js +232 -0
  237. package/src/issue-providers/github-provider.js +210 -0
  238. package/src/issue-providers/gitlab-provider.js +262 -0
  239. package/src/issue-providers/index.js +196 -0
  240. package/src/issue-providers/jira-provider.js +260 -0
  241. package/src/ledger.js +692 -0
  242. package/src/lib/gc.js +232 -0
  243. package/src/lib/safe-exec.js +88 -0
  244. package/src/logic-engine.js +201 -0
  245. package/src/message-buffer.js +81 -0
  246. package/src/message-bus-bridge.js +144 -0
  247. package/src/message-bus.js +256 -0
  248. package/src/name-generator.js +232 -0
  249. package/src/orchestrator.js +3924 -0
  250. package/src/preflight.js +712 -0
  251. package/src/process-metrics.js +608 -0
  252. package/src/providers/anthropic/index.js +3 -0
  253. package/src/providers/base-provider.js +355 -0
  254. package/src/providers/capabilities.js +60 -0
  255. package/src/providers/google/index.js +3 -0
  256. package/src/providers/index.js +293 -0
  257. package/src/providers/openai/index.js +3 -0
  258. package/src/providers/opencode/index.js +3 -0
  259. package/src/quality-gates.js +143 -0
  260. package/src/schemas/sub-cluster.js +208 -0
  261. package/src/state-snapshot.js +398 -0
  262. package/src/state-snapshotter.js +142 -0
  263. package/src/status-footer.js +1026 -0
  264. package/src/sub-cluster-wrapper.js +693 -0
  265. package/src/task-runner.js +30 -0
  266. package/src/template-resolver.js +425 -0
  267. package/src/template-validation/index.js +338 -0
  268. package/src/template-validation/simulate-consensus-gates.js +324 -0
  269. package/src/template-validation/simulate-random-topology.js +541 -0
  270. package/src/template-validation/simulate-two-stage-validation.js +270 -0
  271. package/src/worktree-claude-config.js +135 -0
  272. package/src/worktree-tooling-env.js +150 -0
  273. package/task-lib/attachable-watcher.js +381 -0
  274. package/task-lib/commands/clean.js +50 -0
  275. package/task-lib/commands/episodes.js +105 -0
  276. package/task-lib/commands/get-log-path.js +23 -0
  277. package/task-lib/commands/kill.js +32 -0
  278. package/task-lib/commands/list.js +105 -0
  279. package/task-lib/commands/logs.js +439 -0
  280. package/task-lib/commands/resume.js +42 -0
  281. package/task-lib/commands/run.js +57 -0
  282. package/task-lib/commands/schedule.js +105 -0
  283. package/task-lib/commands/scheduler-cmd.js +96 -0
  284. package/task-lib/commands/schedules.js +148 -0
  285. package/task-lib/commands/status.js +44 -0
  286. package/task-lib/commands/unschedule.js +16 -0
  287. package/task-lib/completion.js +9 -0
  288. package/task-lib/config.js +11 -0
  289. package/task-lib/name-generator.js +230 -0
  290. package/task-lib/package.json +3 -0
  291. package/task-lib/provider-helper-runtime.js +29 -0
  292. package/task-lib/runner.js +190 -0
  293. package/task-lib/scheduler.js +252 -0
  294. package/task-lib/store.js +529 -0
  295. package/task-lib/watcher.js +305 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Claude authentication settings and helpers
3
+ * Extracted from settings.js for provider-specific isolation
4
+ */
5
+
6
+ /**
7
+ * Anthropic API key prefix for validation
8
+ */
9
+ const ANTHROPIC_KEY_PREFIX = 'sk-ant-';
10
+
11
+ /**
12
+ * Environment variables used for Claude authentication
13
+ */
14
+ const CLAUDE_AUTH_ENV_VARS = [
15
+ 'ANTHROPIC_API_KEY',
16
+ 'AWS_BEARER_TOKEN_BEDROCK',
17
+ 'AWS_REGION',
18
+ 'CLAUDE_CODE_USE_BEDROCK',
19
+ ];
20
+
21
+ /**
22
+ * Validate an Anthropic API key format
23
+ * @param {string|null|undefined} key - API key to validate
24
+ * @returns {boolean} True if key is falsy OR starts with valid prefix
25
+ */
26
+ function isValidAnthropicKey(key) {
27
+ return !key || key.startsWith(ANTHROPIC_KEY_PREFIX);
28
+ }
29
+
30
+ /**
31
+ * Check if Bedrock mode is active based on env and overrides
32
+ * @param {Object} [envOverrides={}] - Environment variable overrides to check
33
+ * @returns {boolean} True if Bedrock mode is active
34
+ */
35
+ function isBedrockMode(envOverrides = {}) {
36
+ return (
37
+ envOverrides.CLAUDE_CODE_USE_BEDROCK === '1' || process.env.CLAUDE_CODE_USE_BEDROCK === '1'
38
+ );
39
+ }
40
+
41
+ /**
42
+ * Resolve Claude authentication environment variables from settings
43
+ * Bedrock takes priority over direct Anthropic API key AND OAuth session
44
+ * @param {Object} settings - Settings object from loadSettings()
45
+ * @returns {Object} Environment variables to set for Claude auth
46
+ */
47
+ function resolveClaudeAuth(settings) {
48
+ const claudeSettings = settings.providerSettings?.claude || {};
49
+ const env = {};
50
+
51
+ // Bedrock takes priority over everything (including OAuth)
52
+ if (!process.env.AWS_BEARER_TOKEN_BEDROCK && claudeSettings.bedrockApiKey) {
53
+ env.AWS_BEARER_TOKEN_BEDROCK = claudeSettings.bedrockApiKey;
54
+ env.CLAUDE_CODE_USE_BEDROCK = '1';
55
+ if (claudeSettings.bedrockRegion && !process.env.AWS_REGION) {
56
+ env.AWS_REGION = claudeSettings.bedrockRegion;
57
+ }
58
+ } else if (process.env.AWS_BEARER_TOKEN_BEDROCK && !process.env.CLAUDE_CODE_USE_BEDROCK) {
59
+ // Auto-set CLAUDE_CODE_USE_BEDROCK if token present in env
60
+ env.CLAUDE_CODE_USE_BEDROCK = '1';
61
+ }
62
+
63
+ // Anthropic API key only if no Bedrock
64
+ const hasBedrock = env.AWS_BEARER_TOKEN_BEDROCK || process.env.AWS_BEARER_TOKEN_BEDROCK;
65
+ if (!process.env.ANTHROPIC_API_KEY && !hasBedrock && claudeSettings.anthropicApiKey) {
66
+ env.ANTHROPIC_API_KEY = claudeSettings.anthropicApiKey;
67
+ }
68
+
69
+ return env;
70
+ }
71
+
72
+ module.exports = {
73
+ ANTHROPIC_KEY_PREFIX,
74
+ CLAUDE_AUTH_ENV_VARS,
75
+ isValidAnthropicKey,
76
+ isBedrockMode,
77
+ resolveClaudeAuth,
78
+ };
@@ -0,0 +1,542 @@
1
+ /**
2
+ * Settings management for zeroshot
3
+ * Persistent user preferences stored in ~/.zeroshot/settings.json
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const os = require('os');
9
+ const { validateMountConfig, validateEnvPassthrough } = require('./docker-config');
10
+ const {
11
+ VALID_PROVIDERS,
12
+ normalizeProviderName,
13
+ normalizeProviderSettings,
14
+ } = require('./provider-names');
15
+
16
+ // Lazy-loaded to avoid circular dependency (issue-providers → settings → issue-providers)
17
+ let _issueProviderFns = null;
18
+ function getIssueProviderFns() {
19
+ if (!_issueProviderFns) {
20
+ _issueProviderFns = require('../src/issue-providers');
21
+ }
22
+ return _issueProviderFns;
23
+ }
24
+
25
+ /**
26
+ * Get settings file path (dynamically reads env var for testing)
27
+ * Using a getter ensures tests can override the path at runtime
28
+ * @returns {string}
29
+ */
30
+ function getSettingsFile() {
31
+ return (
32
+ process.env.ZEROSHOT_SETTINGS_FILE || path.join(os.homedir(), '.zeroshot', 'settings.json')
33
+ );
34
+ }
35
+
36
+ // Import provider defaults from separate module to avoid circular dependency
37
+ const { getProviderDefaults, clearProviderDefaultsCache } = require('./provider-defaults');
38
+
39
+ /**
40
+ * Model hierarchy for cost ceiling validation
41
+ * Higher number = more expensive/capable model
42
+ */
43
+ const MODEL_HIERARCHY = {
44
+ opus: 3,
45
+ sonnet: 2,
46
+ haiku: 1,
47
+ };
48
+
49
+ const VALID_MODELS = Object.keys(MODEL_HIERARCHY);
50
+ const LEVEL_RANKS = { level1: 1, level2: 2, level3: 3 };
51
+
52
+ /**
53
+ * Validate a requested model against the maxModel ceiling and minModel floor
54
+ * @param {string} requestedModel - Model the agent wants to use
55
+ * @param {string} maxModel - Maximum allowed model (cost ceiling)
56
+ * @param {string|null} minModel - Minimum required model (cost floor)
57
+ * @returns {string} The validated model
58
+ * @throws {Error} If requested model exceeds ceiling or falls below floor
59
+ */
60
+ function validateModelAgainstMax(requestedModel, maxModel, minModel = null) {
61
+ if (!requestedModel) return maxModel; // Default to ceiling if unspecified
62
+
63
+ if (!VALID_MODELS.includes(requestedModel)) {
64
+ throw new Error(`Invalid model "${requestedModel}". Valid: ${VALID_MODELS.join(', ')}`);
65
+ }
66
+ if (!VALID_MODELS.includes(maxModel)) {
67
+ throw new Error(`Invalid maxModel "${maxModel}". Valid: ${VALID_MODELS.join(', ')}`);
68
+ }
69
+
70
+ if (MODEL_HIERARCHY[requestedModel] > MODEL_HIERARCHY[maxModel]) {
71
+ throw new Error(
72
+ `Agent requests "${requestedModel}" but maxModel is "${maxModel}". ` +
73
+ `Either lower agent's model or raise maxModel.`
74
+ );
75
+ }
76
+
77
+ if (minModel) {
78
+ if (!VALID_MODELS.includes(minModel)) {
79
+ throw new Error(`Invalid minModel "${minModel}". Valid: ${VALID_MODELS.join(', ')}`);
80
+ }
81
+ if (MODEL_HIERARCHY[minModel] > MODEL_HIERARCHY[maxModel]) {
82
+ throw new Error(`minModel "${minModel}" cannot be higher than maxModel "${maxModel}".`);
83
+ }
84
+ if (MODEL_HIERARCHY[requestedModel] < MODEL_HIERARCHY[minModel]) {
85
+ throw new Error(
86
+ `Agent requests "${requestedModel}" but minModel is "${minModel}". ` +
87
+ `Either raise agent's model or lower minModel.`
88
+ );
89
+ }
90
+ }
91
+
92
+ return requestedModel;
93
+ }
94
+
95
+ // Default settings (base - without issue provider settings which are added dynamically)
96
+ const DEFAULT_SETTINGS_BASE = {
97
+ maxModel: 'opus', // Cost ceiling - agents cannot use models above this
98
+ minModel: null, // Cost floor - agents cannot use models below this (null = no minimum)
99
+ defaultProvider: 'claude',
100
+ get providerSettings() {
101
+ // Dynamically build from providers on first access
102
+ return getProviderDefaults();
103
+ },
104
+ defaultConfig: 'conductor-bootstrap',
105
+ defaultDocker: false,
106
+ strictSchema: true, // true = reliable json output (default), false = live streaming (may crash - see bold-meadow-11)
107
+ logLevel: 'normal',
108
+ // Auto-update settings
109
+ autoCheckUpdates: true, // Check npm registry for newer versions
110
+ lastUpdateCheckAt: null, // Unix timestamp of last check (null = never checked)
111
+ lastSeenVersion: null, // Don't re-prompt for same version
112
+ // Claude command - customize how to invoke Claude CLI (default: 'claude')
113
+ // Example: 'ccr code' for claude-code-router integration
114
+ claudeCommand: 'claude',
115
+ // Docker isolation mounts - preset names or {host, container, readonly?} objects
116
+ // Valid presets: gh, git, ssh, aws, azure, kube, terraform, gcloud, claude, codex, gemini, opencode
117
+ dockerMounts: ['gh', 'git', 'ssh'],
118
+ // Extra env vars to pass to Docker container (in addition to preset-implied ones)
119
+ // Supports: VAR (if set), VAR_* (pattern), VAR=value (forced), VAR= (empty)
120
+ dockerEnvPassthrough: [],
121
+ // Container home directory - where $HOME resolves in container paths
122
+ // Default: /home/node (matches zeroshot-cluster-base image)
123
+ dockerContainerHome: '/home/node',
124
+ // Retry/restart robustness defaults
125
+ maxRetries: 3, // Agent task retries (per execution) for retryable errors
126
+ maxRestartAttempts: 3, // Agent restarts since last TASK_COMPLETED
127
+ maxTotalRestarts: 10, // Safety valve (never resets)
128
+ staleWarningsBeforeKill: 2, // Consecutive stale warnings before restart
129
+ backoffBaseMs: 2000, // Initial retry backoff
130
+ backoffMaxMs: 30000, // Max retry backoff
131
+ jitterFactor: 0.2, // Random jitter ±20%
132
+ // Issue provider settings - defaultIssueSource is here, others come from providers
133
+ defaultIssueSource: 'github', // 'github' | 'gitlab' | 'jira' | 'azure-devops'
134
+ };
135
+
136
+ // Cache for merged defaults (base + issue provider settings)
137
+ let _defaultSettingsCache = null;
138
+
139
+ /**
140
+ * Get DEFAULT_SETTINGS with issue provider settings merged in
141
+ * Lazy-loaded to avoid circular dependency at module load time
142
+ */
143
+ function getDefaultSettings() {
144
+ if (!_defaultSettingsCache) {
145
+ const issueProviderDefaults = getIssueProviderFns().getIssueProviderSettingsDefaults();
146
+ _defaultSettingsCache = {
147
+ ...DEFAULT_SETTINGS_BASE,
148
+ ...issueProviderDefaults,
149
+ };
150
+ }
151
+ return _defaultSettingsCache;
152
+ }
153
+
154
+ // For backward compatibility, export DEFAULT_SETTINGS as a getter
155
+ const DEFAULT_SETTINGS = new Proxy(DEFAULT_SETTINGS_BASE, {
156
+ get(target, prop) {
157
+ // For known base properties, return from base
158
+ if (prop in target) {
159
+ return target[prop];
160
+ }
161
+ // For issue provider settings, get from merged defaults
162
+ const merged = getDefaultSettings();
163
+ return merged[prop];
164
+ },
165
+ has(target, prop) {
166
+ if (prop in target) return true;
167
+ const merged = getDefaultSettings();
168
+ return prop in merged;
169
+ },
170
+ ownKeys() {
171
+ const merged = getDefaultSettings();
172
+ return Object.keys(merged);
173
+ },
174
+ getOwnPropertyDescriptor(target, prop) {
175
+ const merged = getDefaultSettings();
176
+ if (prop in merged) {
177
+ return { enumerable: true, configurable: true, value: merged[prop] };
178
+ }
179
+ return undefined;
180
+ },
181
+ });
182
+
183
+ function mapLegacyModelToLevel(model) {
184
+ switch (model) {
185
+ case 'haiku':
186
+ return 'level1';
187
+ case 'sonnet':
188
+ return 'level2';
189
+ case 'opus':
190
+ return 'level3';
191
+ default:
192
+ return null;
193
+ }
194
+ }
195
+
196
+ function mergeProviderSettings(current, overrides) {
197
+ // Ensure current has all providers with their defaults
198
+ const providerDefaults = getProviderDefaults();
199
+ const merged = {};
200
+
201
+ for (const provider of VALID_PROVIDERS) {
202
+ merged[provider] = {
203
+ ...(providerDefaults[provider] || {}),
204
+ ...(current[provider] || {}),
205
+ ...(overrides?.[provider] || {}),
206
+ };
207
+ if (!merged[provider].levelOverrides) {
208
+ merged[provider].levelOverrides = {};
209
+ }
210
+ }
211
+ return merged;
212
+ }
213
+
214
+ function applyLegacyModelBounds(settings) {
215
+ if (!settings.providerSettings) return settings;
216
+ const claude = settings.providerSettings.claude || {};
217
+ const legacyMaxLevel = mapLegacyModelToLevel(settings.maxModel);
218
+ const legacyMinLevel = mapLegacyModelToLevel(settings.minModel);
219
+
220
+ if (legacyMaxLevel) {
221
+ claude.maxLevel = legacyMaxLevel;
222
+ }
223
+
224
+ if (legacyMinLevel) {
225
+ claude.minLevel = legacyMinLevel;
226
+ }
227
+
228
+ const minRank = LEVEL_RANKS[claude.minLevel] || LEVEL_RANKS.level1;
229
+ const maxRank = LEVEL_RANKS[claude.maxLevel] || LEVEL_RANKS.level3;
230
+ const defaultRank = LEVEL_RANKS[claude.defaultLevel] || LEVEL_RANKS.level2;
231
+
232
+ if (minRank > maxRank) {
233
+ claude.minLevel = 'level1';
234
+ claude.maxLevel = 'level3';
235
+ } else if (defaultRank < minRank) {
236
+ claude.defaultLevel = claude.minLevel;
237
+ } else if (defaultRank > maxRank) {
238
+ claude.defaultLevel = claude.maxLevel;
239
+ }
240
+
241
+ settings.providerSettings.claude = claude;
242
+ return settings;
243
+ }
244
+
245
+ function normalizeLoadedSettings(parsed) {
246
+ const normalized = { ...parsed };
247
+ if (parsed.defaultProvider) {
248
+ normalized.defaultProvider = normalizeProviderName(parsed.defaultProvider);
249
+ }
250
+ if (parsed.providerSettings) {
251
+ normalized.providerSettings = normalizeProviderSettings(parsed.providerSettings);
252
+ }
253
+ return normalized;
254
+ }
255
+
256
+ /**
257
+ * Load settings from disk, merging with defaults
258
+ */
259
+ function loadSettings() {
260
+ const settingsFile = getSettingsFile();
261
+ if (!fs.existsSync(settingsFile)) {
262
+ // Return a copy with resolved providerSettings
263
+ return {
264
+ ...DEFAULT_SETTINGS,
265
+ providerSettings: getProviderDefaults(),
266
+ };
267
+ }
268
+ try {
269
+ const data = fs.readFileSync(settingsFile, 'utf8');
270
+ const parsed = normalizeLoadedSettings(JSON.parse(data));
271
+ const merged = { ...DEFAULT_SETTINGS, ...parsed };
272
+ merged.defaultProvider =
273
+ normalizeProviderName(merged.defaultProvider) || DEFAULT_SETTINGS.defaultProvider;
274
+ merged.providerSettings = mergeProviderSettings(getProviderDefaults(), parsed.providerSettings);
275
+ return applyLegacyModelBounds(merged);
276
+ } catch {
277
+ console.error('Warning: Could not load settings, using defaults');
278
+ return {
279
+ ...DEFAULT_SETTINGS,
280
+ providerSettings: getProviderDefaults(),
281
+ };
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Save settings to disk
287
+ */
288
+ function saveSettings(settings) {
289
+ const settingsFile = getSettingsFile();
290
+ const dir = path.dirname(settingsFile);
291
+ if (!fs.existsSync(dir)) {
292
+ fs.mkdirSync(dir, { recursive: true });
293
+ }
294
+ fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2), 'utf8');
295
+ }
296
+
297
+ /**
298
+ * Validate claudeCommand setting
299
+ * @returns {string|null} Error message if invalid, null if valid
300
+ */
301
+ function validateClaudeCommand(value) {
302
+ if (typeof value !== 'string') {
303
+ return 'claudeCommand must be a string';
304
+ }
305
+ if (value.trim().length === 0) {
306
+ return 'claudeCommand cannot be empty';
307
+ }
308
+ return null;
309
+ }
310
+
311
+ /**
312
+ * Validate providerSettings structure by delegating to provider implementations
313
+ * @returns {string|null} Error message if invalid, null if valid
314
+ */
315
+ function validateProviderSettings(value) {
316
+ const normalizedSettings = normalizeProviderSettings(value);
317
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
318
+ return 'providerSettings must be an object';
319
+ }
320
+
321
+ // Lazy require to avoid circular dependency
322
+ const { getProvider } = require('../src/providers');
323
+
324
+ for (const [providerName, settings] of Object.entries(normalizedSettings || {})) {
325
+ if (!VALID_PROVIDERS.includes(providerName)) {
326
+ return `Unknown provider in providerSettings: ${providerName}`;
327
+ }
328
+
329
+ // Delegate validation to the provider
330
+ try {
331
+ const provider = getProvider(providerName);
332
+ const error = provider.validateSettings(settings);
333
+ if (error) return error;
334
+ } catch (err) {
335
+ return `Failed to validate ${providerName} settings: ${err.message}`;
336
+ }
337
+ }
338
+
339
+ return null;
340
+ }
341
+
342
+ /**
343
+ * Validate a setting value
344
+ * @returns {string|null} Error message if invalid, null if valid
345
+ */
346
+ function validateSetting(key, value) {
347
+ if (!(key in DEFAULT_SETTINGS)) {
348
+ return `Unknown setting: ${key}`;
349
+ }
350
+
351
+ if (
352
+ [
353
+ 'maxRetries',
354
+ 'maxRestartAttempts',
355
+ 'maxTotalRestarts',
356
+ 'staleWarningsBeforeKill',
357
+ 'backoffBaseMs',
358
+ 'backoffMaxMs',
359
+ ].includes(key)
360
+ ) {
361
+ if (!Number.isFinite(value)) {
362
+ return `${key} must be a number`;
363
+ }
364
+ if (!Number.isInteger(value)) {
365
+ return `${key} must be an integer`;
366
+ }
367
+ const min = ['backoffBaseMs', 'backoffMaxMs'].includes(key) ? 0 : 1;
368
+ if (value < min) {
369
+ return `${key} must be >= ${min}`;
370
+ }
371
+ }
372
+
373
+ if (key === 'jitterFactor') {
374
+ if (!Number.isFinite(value)) {
375
+ return 'jitterFactor must be a number';
376
+ }
377
+ if (value < 0 || value > 1) {
378
+ return 'jitterFactor must be between 0 and 1';
379
+ }
380
+ }
381
+
382
+ if (key === 'maxModel' && !VALID_MODELS.includes(value)) {
383
+ return `Invalid model: ${value}. Valid models: ${VALID_MODELS.join(', ')}`;
384
+ }
385
+
386
+ if (key === 'minModel' && value !== null && !VALID_MODELS.includes(value)) {
387
+ return `Invalid model: ${value}. Valid models: ${VALID_MODELS.join(', ')}, null`;
388
+ }
389
+
390
+ if (key === 'logLevel' && !['quiet', 'normal', 'verbose'].includes(value)) {
391
+ return `Invalid log level: ${value}. Valid levels: quiet, normal, verbose`;
392
+ }
393
+
394
+ if (key === 'claudeCommand') {
395
+ return validateClaudeCommand(value);
396
+ }
397
+
398
+ if (key === 'defaultProvider') {
399
+ const normalized = normalizeProviderName(value);
400
+ if (!VALID_PROVIDERS.includes(normalized)) {
401
+ return `Invalid provider: ${value}. Valid providers: ${VALID_PROVIDERS.join(', ')}`;
402
+ }
403
+ }
404
+
405
+ if (key === 'providerSettings') {
406
+ return validateProviderSettings(value);
407
+ }
408
+
409
+ if (key === 'dockerMounts') {
410
+ return validateMountConfig(value);
411
+ }
412
+
413
+ if (key === 'dockerEnvPassthrough') {
414
+ return validateEnvPassthrough(value);
415
+ }
416
+
417
+ // Issue source settings validation (grouped for maintainability)
418
+ const issueSourceError = validateIssueSourceSetting(key, value);
419
+ if (issueSourceError !== undefined) {
420
+ return issueSourceError;
421
+ }
422
+
423
+ return null;
424
+ }
425
+
426
+ /**
427
+ * Validate issue source related settings
428
+ * Delegates to issue providers for provider-specific settings
429
+ * @param {string} key - Setting key
430
+ * @param {any} value - Setting value
431
+ * @returns {string|null|undefined} Error message, null if valid, undefined if not an issue source setting
432
+ */
433
+ function validateIssueSourceSetting(key, value) {
434
+ // defaultIssueSource is handled here (not provider-specific)
435
+ if (key === 'defaultIssueSource') {
436
+ const { listProviders } = getIssueProviderFns();
437
+ const validSources = listProviders();
438
+ if (!validSources.includes(value)) {
439
+ return `Invalid issue source: ${value}. Valid: ${validSources.join(', ')}`;
440
+ }
441
+ return null;
442
+ }
443
+
444
+ // Delegate to issue providers for provider-specific settings
445
+ const { validateIssueProviderSetting } = getIssueProviderFns();
446
+ return validateIssueProviderSetting(key, value);
447
+ }
448
+
449
+ /**
450
+ * Coerce value to correct type based on default value type
451
+ */
452
+ function coerceValue(key, value) {
453
+ const defaultValue = DEFAULT_SETTINGS[key];
454
+
455
+ // Handle null values for minModel
456
+ if (key === 'minModel' && (value === 'null' || value === null)) {
457
+ return null;
458
+ }
459
+
460
+ if (typeof defaultValue === 'boolean') {
461
+ return value === 'true' || value === '1' || value === 'yes' || value === true;
462
+ }
463
+
464
+ if (typeof defaultValue === 'number') {
465
+ const parsed = parseFloat(value);
466
+ if (isNaN(parsed)) {
467
+ throw new Error(`Invalid number: ${value}`);
468
+ }
469
+ return parsed;
470
+ }
471
+
472
+ // Handle array settings (dockerMounts, dockerEnvPassthrough)
473
+ if (Array.isArray(defaultValue)) {
474
+ if (typeof value === 'string') {
475
+ try {
476
+ const parsed = JSON.parse(value);
477
+ if (!Array.isArray(parsed)) {
478
+ throw new Error(`${key} must be an array`);
479
+ }
480
+ return parsed;
481
+ } catch (e) {
482
+ if (e instanceof SyntaxError) {
483
+ throw new Error(`Invalid JSON for ${key}: ${value}`);
484
+ }
485
+ throw e;
486
+ }
487
+ }
488
+ return value;
489
+ }
490
+
491
+ if (key === 'providerSettings') {
492
+ if (typeof value === 'string') {
493
+ try {
494
+ return normalizeProviderSettings(JSON.parse(value));
495
+ } catch {
496
+ throw new Error(`Invalid JSON for providerSettings: ${value}`);
497
+ }
498
+ }
499
+ return normalizeProviderSettings(value);
500
+ }
501
+
502
+ if (key === 'defaultProvider') {
503
+ return normalizeProviderName(value);
504
+ }
505
+
506
+ return value;
507
+ }
508
+
509
+ /**
510
+ * Get parsed Claude command from settings/env
511
+ * Supports space-separated commands like 'ccr code'
512
+ * @returns {{ command: string, args: string[] }}
513
+ */
514
+ function getClaudeCommand() {
515
+ const settings = loadSettings();
516
+ const raw = process.env.ZEROSHOT_CLAUDE_COMMAND || settings.claudeCommand || 'claude';
517
+ const parts = raw.trim().split(/\s+/);
518
+ return {
519
+ command: parts[0],
520
+ args: parts.slice(1),
521
+ };
522
+ }
523
+
524
+ module.exports = {
525
+ loadSettings,
526
+ saveSettings,
527
+ validateSetting,
528
+ coerceValue,
529
+ DEFAULT_SETTINGS,
530
+ getSettingsFile,
531
+ getClaudeCommand,
532
+ // Model validation exports
533
+ MODEL_HIERARCHY,
534
+ VALID_MODELS,
535
+ validateModelAgainstMax,
536
+ // Provider defaults exports
537
+ clearProviderDefaultsCache,
538
+ // Backward compatibility: SETTINGS_FILE as getter (reads env var dynamically)
539
+ get SETTINGS_FILE() {
540
+ return getSettingsFile();
541
+ },
542
+ };