@the-open-engine/zeroshot 6.4.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 (144) hide show
  1. package/cli/commands/providers.js +11 -5
  2. package/cli/index.js +79 -16
  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/docker-config.js +24 -13
  94. package/lib/provider-defaults.js +15 -4
  95. package/lib/provider-detection.js +2 -0
  96. package/lib/provider-names.js +32 -12
  97. package/lib/repo-settings.js +15 -1
  98. package/lib/settings/claude-auth.js +3 -6
  99. package/lib/settings.js +17 -2
  100. package/lib/setup-apply.js +300 -0
  101. package/lib/setup-journal.js +109 -0
  102. package/lib/setup-plan.js +406 -0
  103. package/lib/setup-undo.js +88 -0
  104. package/lib/start-cluster.js +12 -1
  105. package/lib/stream-json-parser.js +7 -5
  106. package/package.json +29 -1
  107. package/scripts/live-provider-smoke.js +221 -0
  108. package/src/agent/agent-lifecycle.js +5 -4
  109. package/src/agent/agent-quality-gate-schema.js +2 -2
  110. package/src/agent/agent-task-executor.js +41 -2
  111. package/src/agent-cli-provider/acp-stdio-runner.ts +314 -0
  112. package/src/agent-cli-provider/adapters/acp.ts +493 -0
  113. package/src/agent-cli-provider/adapters/claude.ts +2 -32
  114. package/src/agent-cli-provider/adapters/copilot-parser.ts +166 -0
  115. package/src/agent-cli-provider/adapters/copilot.ts +231 -0
  116. package/src/agent-cli-provider/adapters/gateway.ts +186 -0
  117. package/src/agent-cli-provider/adapters/gemini.ts +9 -2
  118. package/src/agent-cli-provider/adapters/index.ts +5 -36
  119. package/src/agent-cli-provider/adapters/opencode.ts +4 -1
  120. package/src/agent-cli-provider/adapters/pi.ts +410 -0
  121. package/src/agent-cli-provider/claude-command.ts +47 -0
  122. package/src/agent-cli-provider/contract-actions.ts +14 -17
  123. package/src/agent-cli-provider/contract-invoke.ts +12 -3
  124. package/src/agent-cli-provider/contract-options.ts +72 -3
  125. package/src/agent-cli-provider/contract-support.ts +30 -3
  126. package/src/agent-cli-provider/errors.ts +14 -4
  127. package/src/agent-cli-provider/gateway-client.ts +170 -0
  128. package/src/agent-cli-provider/gateway-runner.ts +353 -0
  129. package/src/agent-cli-provider/gateway-tools.ts +616 -0
  130. package/src/agent-cli-provider/index.ts +28 -0
  131. package/src/agent-cli-provider/invoke-evidence.ts +24 -3
  132. package/src/agent-cli-provider/provider-registry.ts +515 -0
  133. package/src/agent-cli-provider/single-agent-runtime.ts +192 -18
  134. package/src/agent-cli-provider/types.ts +145 -4
  135. package/src/config-validator.js +33 -15
  136. package/src/isolation-manager.js +124 -19
  137. package/src/orchestrator.js +23 -10
  138. package/src/preflight.js +83 -30
  139. package/src/providers/capabilities.js +15 -42
  140. package/src/providers/index.js +52 -72
  141. package/src/worktree-claude-config.js +17 -0
  142. package/task-lib/commands/run.js +1 -0
  143. package/task-lib/provider-helper-runtime.js +11 -0
  144. 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 };
@@ -295,6 +295,16 @@ function resolveConfigOrThrow({
295
295
  return loadClusterConfig(orchestrator, resolvedPath, settings, providerOverride);
296
296
  }
297
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
+
298
308
  function startClusterWithInput(args, input) {
299
309
  const { orchestrator, config, configPath, configName, settings, providerOverride } = args;
300
310
  if (!orchestrator) {
@@ -310,7 +320,7 @@ function startClusterWithInput(args, input) {
310
320
  });
311
321
  const startOptions = buildStartOptions({
312
322
  clusterId: args.clusterId,
313
- options: args.options || {},
323
+ options: applyDefaultDeliveryOptions(args.options || {}, settings),
314
324
  settings,
315
325
  providerOverride,
316
326
  modelOverride: args.modelOverride,
@@ -344,6 +354,7 @@ module.exports = {
344
354
  resolveConfigPath,
345
355
  loadClusterConfig,
346
356
  buildStartOptions,
357
+ resolveEffectiveRunPlan,
347
358
  startClusterFromText,
348
359
  startClusterFromIssue,
349
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.4.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,6 +11,32 @@
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",
@@ -28,6 +54,8 @@
28
54
  "lint:agent-cli-provider": "eslint \"src/agent-cli-provider/**/*.ts\" \"tests/agent-cli-provider/**/*.ts\"",
29
55
  "build:agent-cli-provider": "tsc --project tsconfig.agent-cli-provider.build.json",
30
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",
31
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",
32
60
  "dev:link": "npm link",
33
61
  "lint": "eslint .",