@the-open-engine/zeroshot 6.3.0 → 6.5.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 (149) hide show
  1. package/cli/commands/providers.js +11 -5
  2. package/cli/index.js +83 -17
  3. package/cli/lib/first-run.js +14 -3
  4. package/docker/zeroshot-cluster/Dockerfile +11 -1
  5. package/lib/agent-cli-provider/acp-stdio-runner.d.ts +4 -0
  6. package/lib/agent-cli-provider/acp-stdio-runner.d.ts.map +1 -0
  7. package/lib/agent-cli-provider/acp-stdio-runner.js +259 -0
  8. package/lib/agent-cli-provider/acp-stdio-runner.js.map +1 -0
  9. package/lib/agent-cli-provider/adapters/acp.d.ts +23 -0
  10. package/lib/agent-cli-provider/adapters/acp.d.ts.map +1 -0
  11. package/lib/agent-cli-provider/adapters/acp.js +373 -0
  12. package/lib/agent-cli-provider/adapters/acp.js.map +1 -0
  13. package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -1
  14. package/lib/agent-cli-provider/adapters/claude.js +2 -29
  15. package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
  16. package/lib/agent-cli-provider/adapters/copilot-parser.d.ts +3 -0
  17. package/lib/agent-cli-provider/adapters/copilot-parser.d.ts.map +1 -0
  18. package/lib/agent-cli-provider/adapters/copilot-parser.js +154 -0
  19. package/lib/agent-cli-provider/adapters/copilot-parser.js.map +1 -0
  20. package/lib/agent-cli-provider/adapters/copilot.d.ts +3 -0
  21. package/lib/agent-cli-provider/adapters/copilot.d.ts.map +1 -0
  22. package/lib/agent-cli-provider/adapters/copilot.js +176 -0
  23. package/lib/agent-cli-provider/adapters/copilot.js.map +1 -0
  24. package/lib/agent-cli-provider/adapters/gateway.d.ts +6 -0
  25. package/lib/agent-cli-provider/adapters/gateway.d.ts.map +1 -0
  26. package/lib/agent-cli-provider/adapters/gateway.js +161 -0
  27. package/lib/agent-cli-provider/adapters/gateway.js.map +1 -0
  28. package/lib/agent-cli-provider/adapters/gemini.d.ts.map +1 -1
  29. package/lib/agent-cli-provider/adapters/gemini.js +9 -2
  30. package/lib/agent-cli-provider/adapters/gemini.js.map +1 -1
  31. package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -1
  32. package/lib/agent-cli-provider/adapters/index.js +6 -34
  33. package/lib/agent-cli-provider/adapters/index.js.map +1 -1
  34. package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -1
  35. package/lib/agent-cli-provider/adapters/opencode.js +5 -1
  36. package/lib/agent-cli-provider/adapters/opencode.js.map +1 -1
  37. package/lib/agent-cli-provider/adapters/pi.d.ts +3 -0
  38. package/lib/agent-cli-provider/adapters/pi.d.ts.map +1 -0
  39. package/lib/agent-cli-provider/adapters/pi.js +331 -0
  40. package/lib/agent-cli-provider/adapters/pi.js.map +1 -0
  41. package/lib/agent-cli-provider/claude-command.d.ts +6 -0
  42. package/lib/agent-cli-provider/claude-command.d.ts.map +1 -0
  43. package/lib/agent-cli-provider/claude-command.js +43 -0
  44. package/lib/agent-cli-provider/claude-command.js.map +1 -0
  45. package/lib/agent-cli-provider/contract-actions.d.ts.map +1 -1
  46. package/lib/agent-cli-provider/contract-actions.js +13 -17
  47. package/lib/agent-cli-provider/contract-actions.js.map +1 -1
  48. package/lib/agent-cli-provider/contract-invoke.d.ts.map +1 -1
  49. package/lib/agent-cli-provider/contract-invoke.js +10 -3
  50. package/lib/agent-cli-provider/contract-invoke.js.map +1 -1
  51. package/lib/agent-cli-provider/contract-options.d.ts.map +1 -1
  52. package/lib/agent-cli-provider/contract-options.js +57 -2
  53. package/lib/agent-cli-provider/contract-options.js.map +1 -1
  54. package/lib/agent-cli-provider/contract-support.d.ts +1 -0
  55. package/lib/agent-cli-provider/contract-support.d.ts.map +1 -1
  56. package/lib/agent-cli-provider/contract-support.js +25 -3
  57. package/lib/agent-cli-provider/contract-support.js.map +1 -1
  58. package/lib/agent-cli-provider/errors.d.ts +1 -0
  59. package/lib/agent-cli-provider/errors.d.ts.map +1 -1
  60. package/lib/agent-cli-provider/errors.js +14 -5
  61. package/lib/agent-cli-provider/errors.js.map +1 -1
  62. package/lib/agent-cli-provider/gateway-client.d.ts +32 -0
  63. package/lib/agent-cli-provider/gateway-client.d.ts.map +1 -0
  64. package/lib/agent-cli-provider/gateway-client.js +135 -0
  65. package/lib/agent-cli-provider/gateway-client.js.map +1 -0
  66. package/lib/agent-cli-provider/gateway-runner.d.ts +9 -0
  67. package/lib/agent-cli-provider/gateway-runner.d.ts.map +1 -0
  68. package/lib/agent-cli-provider/gateway-runner.js +294 -0
  69. package/lib/agent-cli-provider/gateway-runner.js.map +1 -0
  70. package/lib/agent-cli-provider/gateway-tools.d.ts +14 -0
  71. package/lib/agent-cli-provider/gateway-tools.d.ts.map +1 -0
  72. package/lib/agent-cli-provider/gateway-tools.js +495 -0
  73. package/lib/agent-cli-provider/gateway-tools.js.map +1 -0
  74. package/lib/agent-cli-provider/index.d.ts +3 -2
  75. package/lib/agent-cli-provider/index.d.ts.map +1 -1
  76. package/lib/agent-cli-provider/index.js +14 -1
  77. package/lib/agent-cli-provider/index.js.map +1 -1
  78. package/lib/agent-cli-provider/invoke-evidence.d.ts +2 -2
  79. package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -1
  80. package/lib/agent-cli-provider/invoke-evidence.js +17 -3
  81. package/lib/agent-cli-provider/invoke-evidence.js.map +1 -1
  82. package/lib/agent-cli-provider/provider-registry.d.ts +437 -0
  83. package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -0
  84. package/lib/agent-cli-provider/provider-registry.js +411 -0
  85. package/lib/agent-cli-provider/provider-registry.js.map +1 -0
  86. package/lib/agent-cli-provider/single-agent-runtime.d.ts +7 -0
  87. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
  88. package/lib/agent-cli-provider/single-agent-runtime.js +139 -19
  89. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
  90. package/lib/agent-cli-provider/types.d.ts +106 -4
  91. package/lib/agent-cli-provider/types.d.ts.map +1 -1
  92. package/lib/agent-cli-provider/types.js.map +1 -1
  93. package/lib/detached-startup.js +4 -2
  94. package/lib/docker-config.js +24 -13
  95. package/lib/provider-defaults.js +15 -4
  96. package/lib/provider-detection.js +2 -0
  97. package/lib/provider-names.js +32 -12
  98. package/lib/repo-settings.js +15 -1
  99. package/lib/run-mode.js +18 -6
  100. package/lib/run-plan.js +32 -0
  101. package/lib/settings/claude-auth.js +3 -6
  102. package/lib/settings.js +17 -2
  103. package/lib/setup-apply.js +300 -0
  104. package/lib/setup-journal.js +109 -0
  105. package/lib/setup-plan.js +406 -0
  106. package/lib/setup-undo.js +88 -0
  107. package/lib/start-cluster.js +34 -9
  108. package/lib/stream-json-parser.js +7 -5
  109. package/package.json +33 -3
  110. package/scripts/live-provider-smoke.js +221 -0
  111. package/src/agent/agent-lifecycle.js +7 -4
  112. package/src/agent/agent-quality-gate-schema.js +2 -2
  113. package/src/agent/agent-task-executor.js +41 -2
  114. package/src/agent-cli-provider/acp-stdio-runner.ts +314 -0
  115. package/src/agent-cli-provider/adapters/acp.ts +493 -0
  116. package/src/agent-cli-provider/adapters/claude.ts +2 -32
  117. package/src/agent-cli-provider/adapters/copilot-parser.ts +166 -0
  118. package/src/agent-cli-provider/adapters/copilot.ts +231 -0
  119. package/src/agent-cli-provider/adapters/gateway.ts +186 -0
  120. package/src/agent-cli-provider/adapters/gemini.ts +9 -2
  121. package/src/agent-cli-provider/adapters/index.ts +5 -36
  122. package/src/agent-cli-provider/adapters/opencode.ts +4 -1
  123. package/src/agent-cli-provider/adapters/pi.ts +410 -0
  124. package/src/agent-cli-provider/claude-command.ts +47 -0
  125. package/src/agent-cli-provider/contract-actions.ts +14 -17
  126. package/src/agent-cli-provider/contract-invoke.ts +12 -3
  127. package/src/agent-cli-provider/contract-options.ts +72 -3
  128. package/src/agent-cli-provider/contract-support.ts +30 -3
  129. package/src/agent-cli-provider/errors.ts +14 -4
  130. package/src/agent-cli-provider/gateway-client.ts +170 -0
  131. package/src/agent-cli-provider/gateway-runner.ts +353 -0
  132. package/src/agent-cli-provider/gateway-tools.ts +616 -0
  133. package/src/agent-cli-provider/index.ts +28 -0
  134. package/src/agent-cli-provider/invoke-evidence.ts +24 -3
  135. package/src/agent-cli-provider/provider-registry.ts +515 -0
  136. package/src/agent-cli-provider/single-agent-runtime.ts +192 -18
  137. package/src/agent-cli-provider/types.ts +145 -4
  138. package/src/config-validator.js +33 -15
  139. package/src/isolation-manager.js +124 -19
  140. package/src/ledger.js +3 -0
  141. package/src/message-bus.js +12 -2
  142. package/src/orchestrator.js +37 -17
  143. package/src/preflight.js +83 -30
  144. package/src/providers/capabilities.js +15 -42
  145. package/src/providers/index.js +52 -72
  146. package/src/worktree-claude-config.js +17 -0
  147. package/task-lib/commands/run.js +1 -0
  148. package/task-lib/provider-helper-runtime.js +11 -0
  149. package/task-lib/runner.js +7 -0
@@ -0,0 +1,406 @@
1
+ /**
2
+ * Setup plan — read-only facts collector + the pinned, versioned setup contract.
3
+ *
4
+ * buildSetupPlan() is pure over its injected inputs: no direct process.env/fs
5
+ * reads for config data, no writes, no prompts. Everything downstream (apply,
6
+ * undo, the TTY wizard, agents) consumes the object this returns, so its shape
7
+ * (schemaVersion, facts, decisions, recommended, risk, proposedWrites) and the
8
+ * decisionId registry below are a stable contract — do not rename IDs or add a
9
+ * parallel run-mode field without updating every consumer.
10
+ */
11
+
12
+ const path = require('path');
13
+
14
+ const SCHEMA_VERSION = 1;
15
+
16
+ // Canonical settings key each decision maps to. `defaultIsolation` and
17
+ // `defaultDelivery` MUST stay pinned to the keys resolveEffectiveRunPlan
18
+ // already reads (today: settings.defaultDocker) — see the canonical-path
19
+ // rule in issue #605. Never introduce a second parallel key here.
20
+ const DECISION_PATHS = {
21
+ defaultProvider: { scope: 'global', path: 'defaultProvider' },
22
+ defaultIsolation: { scope: 'global', path: 'defaultDocker' },
23
+ allowLocalNoIsolation: { scope: 'global', path: 'allowLocalNoIsolation' },
24
+ defaultDelivery: { scope: 'global', path: 'defaultDelivery' },
25
+ defaultIssueSource: { scope: 'global', path: 'defaultIssueSource' },
26
+ prBase: { scope: 'repo', path: 'prBase' },
27
+ dockerMounts: { scope: 'global', path: 'dockerMounts' },
28
+ dockerEnvPassthrough: { scope: 'global', path: 'dockerEnvPassthrough' },
29
+ updatePolicy: { scope: 'global', path: 'updatePolicy' },
30
+ };
31
+
32
+ function providerLevelDecisionId(providerName) {
33
+ return `providerLevel.${providerName}`;
34
+ }
35
+
36
+ // Settings keys a run-mode resolver actually reads today. Shared by
37
+ // buildProposedWrites below (never propose a write nobody will read) and by
38
+ // lib/setup-apply.js (never perform a write nobody will read) — one
39
+ // canonical answer to "is this key consumed?" so the two can't drift.
40
+ const CONSUMED_PATHS = new Set([
41
+ 'global:defaultProvider',
42
+ 'global:defaultDocker',
43
+ 'global:defaultDelivery',
44
+ 'global:defaultIssueSource',
45
+ 'global:dockerMounts',
46
+ 'global:dockerEnvPassthrough',
47
+ // No resolver consumes this yet; consumption is deferred to a future
48
+ // update-policy issue, but issue #606 explicitly sanctions writing it now.
49
+ 'global:updatePolicy',
50
+ ]);
51
+
52
+ function isConsumedPath(scope, targetPath) {
53
+ if (scope === 'global' && targetPath.startsWith('providerSettings.')) return true;
54
+ return CONSUMED_PATHS.has(`${scope}:${targetPath}`);
55
+ }
56
+
57
+ // providerLevel.<provider> decisionIds map to a per-provider settings key
58
+ // (providerSettings.<provider>) not present in DECISION_PATHS above, since
59
+ // the provider name is only known at runtime.
60
+ function resolveDecisionPath(decisionId) {
61
+ if (decisionId.startsWith('providerLevel.')) {
62
+ const providerName = decisionId.slice('providerLevel.'.length);
63
+ return { scope: 'global', path: `providerSettings.${providerName}` };
64
+ }
65
+ return DECISION_PATHS[decisionId] || null;
66
+ }
67
+
68
+ function getNestedValue(source, pathStr) {
69
+ return pathStr
70
+ .split('.')
71
+ .reduce((acc, key) => (acc === null || acc === undefined ? undefined : acc[key]), source);
72
+ }
73
+
74
+ function defaultDeps() {
75
+ const { commandExists, getCommandPath } = require('./provider-detection');
76
+ const { checkDocker, checkGhAuth } = require('../src/preflight');
77
+ const { execSync } = require('../src/lib/safe-exec');
78
+ const { listProviders, getProvider } = require('../src/providers');
79
+ const { getProviderDefaults } = require('./provider-defaults');
80
+ const packageJson = require('../package.json');
81
+
82
+ return {
83
+ commandExists,
84
+ getCommandPath,
85
+ checkDocker,
86
+ checkGhAuth,
87
+ execSync,
88
+ listProviders,
89
+ getProvider,
90
+ getProviderDefaults,
91
+ getNodeVersion: () => process.version,
92
+ getPackageVersion: () => packageJson.version,
93
+ };
94
+ }
95
+
96
+ function detectInstallSource(cwd, env) {
97
+ if (env.npm_config_global === 'true') return 'npm-global';
98
+ if (env.npm_execpath && /_npx|npx/.test(env.npm_execpath)) return 'npx';
99
+ try {
100
+ const ownNodeModules = path.join(__dirname, '..', 'node_modules');
101
+ if (cwd && (cwd === path.join(__dirname, '..') || cwd.startsWith(ownNodeModules))) {
102
+ return 'local';
103
+ }
104
+ } catch {
105
+ // fall through to unknown
106
+ }
107
+ return 'unknown';
108
+ }
109
+
110
+ function buildNodeFacts({ cwd, env, deps }) {
111
+ return {
112
+ version: deps.getNodeVersion(),
113
+ packageVersion: deps.getPackageVersion(),
114
+ installSource: detectInstallSource(cwd, env),
115
+ };
116
+ }
117
+
118
+ function buildProviderFacts(deps) {
119
+ const providers = {};
120
+ for (const name of deps.listProviders()) {
121
+ let cliCommand = name;
122
+ try {
123
+ cliCommand = deps.getProvider(name).cliCommand || name;
124
+ } catch {
125
+ // Provider metadata unavailable — fall back to the provider name as the CLI command.
126
+ }
127
+ const cliAvailable = deps.commandExists(cliCommand);
128
+ providers[name] = {
129
+ cliAvailable,
130
+ path: cliAvailable ? deps.getCommandPath(cliCommand) : null,
131
+ };
132
+ }
133
+ return providers;
134
+ }
135
+
136
+ function safeExecTrim(deps, command, cwd) {
137
+ try {
138
+ return deps.execSync(command, { cwd, stdio: 'pipe', encoding: 'utf8' }).trim();
139
+ } catch {
140
+ return null;
141
+ }
142
+ }
143
+
144
+ function buildGitFacts({ cwd, deps }) {
145
+ const isRepo = safeExecTrim(deps, 'git rev-parse --is-inside-work-tree', cwd) === 'true';
146
+ const ghAvailable = deps.commandExists('gh');
147
+
148
+ if (!isRepo) {
149
+ return { isRepo: false, branch: null, remote: null, ghAvailable, ghAuthed: null };
150
+ }
151
+
152
+ const branch = safeExecTrim(deps, 'git rev-parse --abbrev-ref HEAD', cwd);
153
+ const remote = safeExecTrim(deps, 'git remote get-url origin', cwd);
154
+ const ghAuthed = ghAvailable ? !!deps.checkGhAuth().authenticated : null;
155
+
156
+ return { isRepo: true, branch, remote, ghAvailable, ghAuthed };
157
+ }
158
+
159
+ function buildFacts({ cwd, settings, repoSettings, env, deps }) {
160
+ return {
161
+ node: buildNodeFacts({ cwd, env, deps }),
162
+ providers: buildProviderFacts(deps),
163
+ git: buildGitFacts({ cwd, deps }),
164
+ docker: { available: !!deps.checkDocker().available },
165
+ settings: {
166
+ hasGlobal: settings?.__meta ? !!settings.__meta.fileExists : true,
167
+ hasRepo: repoSettings !== null && repoSettings !== undefined,
168
+ },
169
+ };
170
+ }
171
+
172
+ function inferIssueSource(facts) {
173
+ const { parseGitRemoteUrl } = require('./git-remote-utils');
174
+ const parsed = parseGitRemoteUrl(facts.git.remote);
175
+ return parsed?.provider || null;
176
+ }
177
+
178
+ function inferPrBase(cwd, deps) {
179
+ const branch = safeExecTrim(deps, 'git rev-parse --abbrev-ref origin/HEAD', cwd);
180
+ if (!branch) return null;
181
+ return branch.replace(/^origin\//, '');
182
+ }
183
+
184
+ function buildProviderLevelRecommendation(name, deps) {
185
+ const providerDefaults = deps.getProviderDefaults()[name] || {};
186
+ const minLevel = providerDefaults.minLevel;
187
+ const defaultLevel = providerDefaults.defaultLevel;
188
+ const maxLevel = providerDefaults.maxLevel;
189
+ const overrides = providerDefaults.levelOverrides || {};
190
+
191
+ try {
192
+ const provider = deps.getProvider(name);
193
+ return {
194
+ min: provider.resolveModelSpec(minLevel, overrides).model,
195
+ default: provider.resolveModelSpec(defaultLevel, overrides).model,
196
+ max: provider.resolveModelSpec(maxLevel, overrides).model,
197
+ };
198
+ } catch {
199
+ return { min: null, default: null, max: null };
200
+ }
201
+ }
202
+
203
+ function buildRecommendedAndRisk({ cwd, facts, env, deps }) {
204
+ const recommended = {};
205
+ const risk = {};
206
+
207
+ const firstAvailableProvider = Object.entries(facts.providers).find(
208
+ ([, info]) => info.cliAvailable
209
+ );
210
+ recommended.defaultProvider = firstAvailableProvider ? firstAvailableProvider[0] : 'claude';
211
+ risk.defaultProvider = firstAvailableProvider ? 'low' : 'medium';
212
+
213
+ for (const name of Object.keys(facts.providers)) {
214
+ recommended[providerLevelDecisionId(name)] = buildProviderLevelRecommendation(name, deps);
215
+ risk[providerLevelDecisionId(name)] = 'low';
216
+ }
217
+
218
+ if (facts.git.isRepo) {
219
+ recommended.defaultIsolation = 'worktree';
220
+ risk.defaultIsolation = 'low';
221
+ } else if (facts.docker.available) {
222
+ recommended.defaultIsolation = 'docker';
223
+ risk.defaultIsolation = 'low';
224
+ } else {
225
+ recommended.defaultIsolation = 'none';
226
+ risk.defaultIsolation = 'high';
227
+ }
228
+
229
+ recommended.allowLocalNoIsolation = false;
230
+ risk.allowLocalNoIsolation = 'low';
231
+
232
+ recommended.defaultDelivery = 'none';
233
+ risk.defaultDelivery = 'low';
234
+
235
+ const inferredIssueSource = inferIssueSource(facts);
236
+ recommended.defaultIssueSource = inferredIssueSource || 'github';
237
+ risk.defaultIssueSource = inferredIssueSource ? 'low' : 'medium';
238
+
239
+ const inferredPrBase = inferPrBase(cwd, deps);
240
+ recommended.prBase = inferredPrBase || 'main';
241
+ risk.prBase = inferredPrBase ? 'low' : 'medium';
242
+
243
+ recommended.dockerMounts = ['gh', 'git', 'ssh'];
244
+ risk.dockerMounts = 'low';
245
+
246
+ recommended.dockerEnvPassthrough = [];
247
+ risk.dockerEnvPassthrough = 'low';
248
+
249
+ const nonInteractive = env.CI === 'true' || env.__isTTY === false;
250
+ recommended.updatePolicy = nonInteractive ? 'off' : 'notify';
251
+ risk.updatePolicy = 'low';
252
+
253
+ return { recommended, risk, inferredIssueSource, inferredPrBase };
254
+ }
255
+
256
+ function currentValueFor(decisionId, settings, repoSettings) {
257
+ const target = resolveDecisionPath(decisionId);
258
+ if (!target) return null;
259
+ const source = target.scope === 'repo' ? repoSettings || {} : settings || {};
260
+ const value = getNestedValue(source, target.path);
261
+ return value === undefined ? null : value;
262
+ }
263
+
264
+ function buildDecisions({ facts, settings, repoSettings, inferredIssueSource, inferredPrBase }) {
265
+ const decisions = [];
266
+ const hasGlobal = facts.settings.hasGlobal;
267
+ const hasRepo = facts.settings.hasRepo;
268
+
269
+ const globalDecisionIds = [
270
+ 'defaultProvider',
271
+ ...Object.keys(facts.providers).map(providerLevelDecisionId),
272
+ 'defaultIsolation',
273
+ 'allowLocalNoIsolation',
274
+ 'defaultDelivery',
275
+ 'defaultIssueSource',
276
+ 'dockerMounts',
277
+ 'dockerEnvPassthrough',
278
+ 'updatePolicy',
279
+ ];
280
+
281
+ for (const decisionId of globalDecisionIds) {
282
+ const shouldInclude =
283
+ !hasGlobal || (decisionId === 'defaultIssueSource' && !inferredIssueSource);
284
+ if (!shouldInclude) continue;
285
+ decisions.push({
286
+ decisionId,
287
+ domain: domainFor(decisionId),
288
+ currentValue: currentValueFor(decisionId, settings, repoSettings),
289
+ });
290
+ }
291
+
292
+ if (!hasRepo || !inferredPrBase) {
293
+ decisions.push({
294
+ decisionId: 'prBase',
295
+ domain: domainFor('prBase'),
296
+ currentValue: currentValueFor('prBase', settings, repoSettings),
297
+ });
298
+ }
299
+
300
+ return decisions;
301
+ }
302
+
303
+ function domainFor(decisionId) {
304
+ if (decisionId.startsWith('providerLevel.')) return '{ min, default, max } of haiku|sonnet|opus';
305
+ const domains = {
306
+ defaultProvider: 'claude | codex | gemini | opencode',
307
+ defaultIsolation: 'worktree | docker | none',
308
+ allowLocalNoIsolation: 'boolean',
309
+ defaultDelivery: 'none | pr | ship',
310
+ defaultIssueSource: 'github | gitlab | jira | azure-devops | linear',
311
+ prBase: 'string (branch)',
312
+ dockerMounts: 'array of presets/objects',
313
+ dockerEnvPassthrough: 'string[]',
314
+ updatePolicy: 'off | notify | auto',
315
+ };
316
+ return domains[decisionId] || 'unknown';
317
+ }
318
+
319
+ function buildProposedWrites({ decisions, recommended }) {
320
+ const writes = [];
321
+ for (const decision of decisions) {
322
+ const target = resolveDecisionPath(decision.decisionId);
323
+ if (!target) continue;
324
+ // A decision can still be surfaced for the user to make (e.g. a future
325
+ // wizard asking about prBase), but proposing a *write* for a settings key
326
+ // no resolver reads would advertise a write that apply will always skip —
327
+ // dead config. Only propose writes apply will actually perform.
328
+ if (!isConsumedPath(target.scope, target.path)) continue;
329
+ let to = recommended[decision.decisionId];
330
+ if (decision.decisionId === 'defaultIsolation') {
331
+ to = to === 'docker';
332
+ }
333
+ if (to === decision.currentValue) continue;
334
+ writes.push({
335
+ scope: target.scope,
336
+ path: target.path,
337
+ from: decision.currentValue ?? null,
338
+ to,
339
+ decisionId: decision.decisionId,
340
+ });
341
+ }
342
+ return writes;
343
+ }
344
+
345
+ /**
346
+ * Build the pinned, versioned setup contract. Pure over injected inputs —
347
+ * performs only cheap, read-only detection (no writes, no prompts).
348
+ *
349
+ * @param {Object} params
350
+ * @param {string} params.cwd
351
+ * @param {Object} params.settings - loaded global settings (optionally with __meta.fileExists)
352
+ * @param {Object|null} params.repoSettings - repo-local settings object, or null
353
+ * @param {Object} params.env - caller-provided env (e.g. process.env), plus optional __isTTY
354
+ * @param {Object} [params.deps] - injected dependencies (for testing)
355
+ * @returns {Object} plan
356
+ */
357
+ function buildSetupPlan({ cwd, settings, repoSettings, env, deps } = {}) {
358
+ const resolvedDeps = { ...defaultDeps(), ...(deps || {}) };
359
+ const resolvedEnv = env || {};
360
+ const resolvedCwd = cwd || '.';
361
+ const resolvedSettings = settings || {};
362
+
363
+ const facts = buildFacts({
364
+ cwd: resolvedCwd,
365
+ settings: resolvedSettings,
366
+ repoSettings,
367
+ env: resolvedEnv,
368
+ deps: resolvedDeps,
369
+ });
370
+
371
+ const { recommended, risk, inferredIssueSource, inferredPrBase } = buildRecommendedAndRisk({
372
+ cwd: resolvedCwd,
373
+ facts,
374
+ env: resolvedEnv,
375
+ deps: resolvedDeps,
376
+ });
377
+
378
+ const decisions = buildDecisions({
379
+ facts,
380
+ settings: resolvedSettings,
381
+ repoSettings,
382
+ inferredIssueSource,
383
+ inferredPrBase,
384
+ });
385
+
386
+ const proposedWrites = buildProposedWrites({ decisions, recommended });
387
+
388
+ return {
389
+ schemaVersion: SCHEMA_VERSION,
390
+ facts,
391
+ decisions,
392
+ recommended,
393
+ risk,
394
+ proposedWrites,
395
+ };
396
+ }
397
+
398
+ module.exports = {
399
+ buildSetupPlan,
400
+ resolveDecisionPath,
401
+ domainFor,
402
+ DECISION_PATHS,
403
+ getNestedValue,
404
+ isConsumedPath,
405
+ CONSUMED_PATHS,
406
+ };
@@ -0,0 +1,88 @@
1
+ /**
2
+ * `zeroshot setup undo` — reverts writes made by `zeroshot setup apply` using
3
+ * the journal recorded at apply time (lib/setup-journal.js).
4
+ *
5
+ * Three-way conflict rule per journaled write, comparing `current` against
6
+ * `appliedValue`:
7
+ * - current === appliedValue -> restore priorValue (delete if null)
8
+ * - current === priorValue -> already-restored (no-op)
9
+ * - otherwise (changed elsewhere) -> skipped-modified, never clobbered
10
+ */
11
+
12
+ const {
13
+ loadJournal,
14
+ getNestedValue,
15
+ setNestedValue,
16
+ deleteNestedKey,
17
+ deepEqual,
18
+ } = require('./setup-journal');
19
+
20
+ function defaultUndoDeps() {
21
+ const { loadSettings, saveSettings } = require('./settings');
22
+ const { readRepoSettings, writeRepoSettings } = require('./repo-settings');
23
+
24
+ return { loadSettings, saveSettings, readRepoSettings, writeRepoSettings };
25
+ }
26
+
27
+ /**
28
+ * Undo every journaled write, per the three-way conflict rule.
29
+ *
30
+ * @param {Object} [params]
31
+ * @param {Object} [params.deps] - Injected dependencies (for testing).
32
+ * @returns {Array<{scope: string, path: string, repoRoot: string|null, status: string, current?: *, wouldRestore?: *}>}
33
+ */
34
+ function undo({ deps = {} } = {}) {
35
+ const resolvedDeps = { ...defaultUndoDeps(), ...deps };
36
+ const journal = loadJournal();
37
+
38
+ const globalSettings = resolvedDeps.loadSettings();
39
+ const repoSettingsCache = new Map();
40
+ let globalDirty = false;
41
+ const dirtyRepoRoots = new Set();
42
+
43
+ function repoSettingsFor(repoRoot) {
44
+ if (!repoSettingsCache.has(repoRoot)) {
45
+ const { settings } = resolvedDeps.readRepoSettings(repoRoot);
46
+ repoSettingsCache.set(repoRoot, settings || {});
47
+ }
48
+ return repoSettingsCache.get(repoRoot);
49
+ }
50
+
51
+ const results = journal.entries.map((entry) => {
52
+ const settingsObj = entry.scope === 'repo' ? repoSettingsFor(entry.repoRoot) : globalSettings;
53
+ const current = getNestedValue(settingsObj, entry.path) ?? null;
54
+
55
+ if (deepEqual(current, entry.priorValue)) {
56
+ return { ...entry, status: 'already-restored' };
57
+ }
58
+
59
+ if (!deepEqual(current, entry.appliedValue)) {
60
+ return { ...entry, status: 'skipped-modified', current, wouldRestore: entry.priorValue };
61
+ }
62
+
63
+ if (entry.priorValue === null) {
64
+ deleteNestedKey(settingsObj, entry.path);
65
+ } else {
66
+ setNestedValue(settingsObj, entry.path, entry.priorValue);
67
+ }
68
+
69
+ if (entry.scope === 'repo') {
70
+ dirtyRepoRoots.add(entry.repoRoot);
71
+ } else {
72
+ globalDirty = true;
73
+ }
74
+
75
+ return { ...entry, status: entry.priorValue === null ? 'deleted' : 'restored' };
76
+ });
77
+
78
+ if (globalDirty) resolvedDeps.saveSettings(globalSettings);
79
+ for (const repoRoot of dirtyRepoRoots) {
80
+ resolvedDeps.writeRepoSettings(repoRoot, repoSettingsFor(repoRoot));
81
+ }
82
+
83
+ // Journal entries are left in place (not cleared) so a re-run reports
84
+ // 'already-restored' instead of finding nothing to undo.
85
+ return results;
86
+ }
87
+
88
+ module.exports = { undo };
@@ -5,7 +5,8 @@ const { normalizeProviderName } = require('./provider-names');
5
5
  const { getProvider } = require('../src/providers');
6
6
  const { detectProvider } = require('../src/issue-providers');
7
7
  const TemplateResolver = require('../src/template-resolver');
8
- const { resolveRunMode } = require('./run-mode');
8
+ const { runModeFromPlan } = require('./run-mode');
9
+ const { resolveRunPlan } = require('./run-plan');
9
10
 
10
11
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
11
12
 
@@ -198,8 +199,20 @@ function mergeRunOptions(options) {
198
199
  return envRunOptions ? { ...envRunOptions, ...options } : options;
199
200
  }
200
201
 
201
- function resolveIsolation(mergedOptions, settings) {
202
- return mergedOptions.docker || process.env.ZEROSHOT_DOCKER === '1' || settings.defaultDocker;
202
+ // The single producer of the run plan for a cluster start: fold env + settings
203
+ // into the flags, then resolve the canonical isolation/delivery/autoMerge plan.
204
+ // buildStartOptions reads EVERY mode field off this one plan — no field
205
+ // (isolation, worktree, autoPr, autoMerge, runMode) is derived independently.
206
+ function resolveEffectiveRunPlan(mergedOptions, settings) {
207
+ return resolveRunPlan({
208
+ ...mergedOptions,
209
+ docker: anyTruthy(
210
+ mergedOptions.docker,
211
+ process.env.ZEROSHOT_DOCKER === '1',
212
+ settings.defaultDocker
213
+ ),
214
+ worktree: anyTruthy(mergedOptions.worktree, process.env.ZEROSHOT_WORKTREE === '1'),
215
+ });
203
216
  }
204
217
 
205
218
  function resolveMergeQueue(mergedOptions) {
@@ -238,14 +251,15 @@ function buildStartOptions({
238
251
  forceProvider,
239
252
  }) {
240
253
  const mergedOptions = mergeRunOptions(options);
254
+ const plan = resolveEffectiveRunPlan(mergedOptions, settings);
241
255
  return {
242
256
  clusterId,
243
257
  cwd: resolveTargetCwd(),
244
- isolation: resolveIsolation(mergedOptions, settings),
258
+ isolation: plan.isolation === 'docker',
245
259
  isolationImage: firstTruthy(mergedOptions.dockerImage, process.env.ZEROSHOT_DOCKER_IMAGE),
246
- worktree: anyTruthy(mergedOptions.worktree, process.env.ZEROSHOT_WORKTREE === '1'),
247
- autoPr: anyTruthy(mergedOptions.pr, process.env.ZEROSHOT_PR === '1'),
248
- autoMerge: Boolean(mergedOptions.ship),
260
+ worktree: plan.isolation === 'worktree',
261
+ autoPr: plan.delivery !== 'none',
262
+ autoMerge: plan.autoMerge,
249
263
  autoPush: process.env.ZEROSHOT_PUSH === '1',
250
264
  modelOverride: optionalValue(modelOverride),
251
265
  providerOverride: optionalValue(providerOverride),
@@ -257,7 +271,7 @@ function buildStartOptions({
257
271
  mergeQueue: resolveMergeQueue(mergedOptions),
258
272
  closeIssue: resolveCloseIssue(mergedOptions),
259
273
  ship: mergedOptions.ship,
260
- runMode: resolveRunMode(mergedOptions) || null,
274
+ runMode: runModeFromPlan(plan),
261
275
  requiredQualityGates: mergedOptions.requiredQualityGates,
262
276
  settings,
263
277
  };
@@ -281,6 +295,16 @@ function resolveConfigOrThrow({
281
295
  return loadClusterConfig(orchestrator, resolvedPath, settings, providerOverride);
282
296
  }
283
297
 
298
+ function applyDefaultDeliveryOptions(options, settings) {
299
+ if (settings.defaultDelivery === 'pr') {
300
+ return { ...options, pr: true };
301
+ }
302
+ if (settings.defaultDelivery === 'ship') {
303
+ return { ...options, ship: true };
304
+ }
305
+ return options;
306
+ }
307
+
284
308
  function startClusterWithInput(args, input) {
285
309
  const { orchestrator, config, configPath, configName, settings, providerOverride } = args;
286
310
  if (!orchestrator) {
@@ -296,7 +320,7 @@ function startClusterWithInput(args, input) {
296
320
  });
297
321
  const startOptions = buildStartOptions({
298
322
  clusterId: args.clusterId,
299
- options: args.options || {},
323
+ options: applyDefaultDeliveryOptions(args.options || {}, settings),
300
324
  settings,
301
325
  providerOverride,
302
326
  modelOverride: args.modelOverride,
@@ -330,6 +354,7 @@ module.exports = {
330
354
  resolveConfigPath,
331
355
  loadClusterConfig,
332
356
  buildStartOptions,
357
+ resolveEffectiveRunPlan,
333
358
  startClusterFromText,
334
359
  startClusterFromIssue,
335
360
  startClusterFromFile,
@@ -4,10 +4,11 @@
4
4
  * Provider parsing is delegated to the helper-backed runtime provider facade.
5
5
  */
6
6
 
7
- const { getProvider } = require('../src/providers');
7
+ const { getProvider, listProviders } = require('../src/providers');
8
8
 
9
- const providerOrder = ['claude', 'codex', 'opencode', 'gemini'];
10
- const providerParsers = providerOrder.map((name) => getProvider(name));
9
+ function createProviderParsers() {
10
+ return listProviders().map((name) => getProvider(name));
11
+ }
11
12
 
12
13
  function stripTimestampPrefix(line) {
13
14
  if (!line || typeof line !== 'string') return '';
@@ -28,7 +29,7 @@ function stripTimestampPrefix(line) {
28
29
  return trimmed;
29
30
  }
30
31
 
31
- function parseEvent(line) {
32
+ function parseEvent(line, providerParsers = createProviderParsers()) {
32
33
  const content = stripTimestampPrefix(line);
33
34
  if (!content) return null;
34
35
 
@@ -52,10 +53,11 @@ function collectEvent(events, event) {
52
53
  function parseChunk(chunk) {
53
54
  const events = [];
54
55
  const lines = String(chunk || '').split('\n');
56
+ const providerParsers = createProviderParsers();
55
57
 
56
58
  for (const line of lines) {
57
59
  if (!line.trim()) continue;
58
- collectEvent(events, parseEvent(line));
60
+ collectEvent(events, parseEvent(line, providerParsers));
59
61
  }
60
62
 
61
63
  return events;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@the-open-engine/zeroshot",
3
- "version": "6.3.0",
3
+ "version": "6.5.0",
4
4
  "description": "Multi-agent orchestration engine for Claude, Codex, and Gemini",
5
5
  "main": "src/orchestrator.js",
6
6
  "bin": {
@@ -11,12 +11,40 @@
11
11
  "example": "examples",
12
12
  "test": "tests"
13
13
  },
14
+ "release": {
15
+ "branches": [
16
+ "main"
17
+ ],
18
+ "plugins": [
19
+ [
20
+ "@semantic-release/commit-analyzer",
21
+ {
22
+ "releaseRules": [
23
+ {
24
+ "type": "release",
25
+ "release": "minor"
26
+ }
27
+ ]
28
+ }
29
+ ],
30
+ "@semantic-release/release-notes-generator",
31
+ [
32
+ "@semantic-release/npm",
33
+ {
34
+ "npmPublish": true
35
+ }
36
+ ],
37
+ "@semantic-release/github"
38
+ ]
39
+ },
14
40
  "scripts": {
15
41
  "pretest": "npm run build:agent-cli-provider",
16
42
  "test": "node tests/run-tests.js",
17
43
  "test:unit": "node tests/run-tests.js",
18
- "test:slow": "mocha 'tests/integration/**/*.test.js' --timeout 180000",
19
- "test:all": "npm run test && npm run test:slow",
44
+ "test:e2e": "mocha 'tests/e2e/**/*.test.js' --timeout 120000",
45
+ "test:e2e:docker": "bash tests/integration/e2e-isolation-and-auto.test.sh",
46
+ "test:slow": "mocha 'tests/integration/**/*.test.js' 'tests/providers/**/*.test.js' --timeout 180000",
47
+ "test:all": "npm run test && npm run test:e2e && npm run test:slow",
20
48
  "test:coverage": "c8 npm run test:unit",
21
49
  "test:coverage:report": "c8 --reporter=html npm run test:unit && echo 'Coverage report generated at coverage/index.html'",
22
50
  "postinstall": "node scripts/fix-node-pty-permissions.js && node scripts/check-path.js",
@@ -26,6 +54,8 @@
26
54
  "lint:agent-cli-provider": "eslint \"src/agent-cli-provider/**/*.ts\" \"tests/agent-cli-provider/**/*.ts\"",
27
55
  "build:agent-cli-provider": "tsc --project tsconfig.agent-cli-provider.build.json",
28
56
  "test:agent-cli-provider": "node --test tests/agent-cli-provider/*.test.js",
57
+ "test:providers:live": "npm run build:agent-cli-provider && node scripts/live-provider-smoke.js",
58
+ "check:agent-cli-provider": "npm run check:agent-cli-provider:ci",
29
59
  "check:agent-cli-provider:ci": "npm run typecheck:agent-cli-provider && npm run lint:agent-cli-provider && npm run build:agent-cli-provider && npm run test:agent-cli-provider",
30
60
  "dev:link": "npm link",
31
61
  "lint": "eslint .",