@the-open-engine/zeroshot 6.31.3 → 6.32.1
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.
- package/README.md +66 -98
- package/cli/index.js +251 -252
- package/cli/lib/setup-provider-readiness.js +86 -0
- package/cli/lib/setup-scanner-worker.js +120 -0
- package/cli/lib/setup-scanner.js +185 -0
- package/cli/lib/setup-wizard-input.js +146 -0
- package/cli/lib/setup-wizard-model.js +205 -0
- package/cli/lib/setup-wizard-plan-view.js +157 -0
- package/cli/lib/setup-wizard-scan-view.js +144 -0
- package/cli/lib/setup-wizard-terminal.js +237 -0
- package/cli/lib/setup-wizard-view.js +180 -0
- package/cli/lib/setup-wizard.js +281 -0
- package/cli/message-formatters-normal.js +14 -18
- package/cli/message-formatters-watch.js +53 -141
- package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +8 -2
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/agent-cli-provider/provider-registry.d.ts +4 -2
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +13 -3
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +7 -4
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +2 -0
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/lib/completion.js +102 -153
- package/lib/settings.js +10 -2
- package/lib/setup-apply.js +62 -55
- package/lib/setup-plan.js +32 -52
- package/lib/start-cluster.js +65 -25
- package/npm-shrinkwrap.json +2 -2
- package/package.json +3 -3
- package/scripts/postinstall.js +54 -0
- package/src/agent/agent-lifecycle.js +11 -1
- package/src/agent/agent-task-executor.js +25 -7
- package/src/agent/structured-output-error.js +42 -0
- package/src/agent-cli-provider/adapters/codex.ts +8 -12
- package/src/agent-cli-provider/provider-registry.ts +15 -3
- package/src/agent-cli-provider/single-agent-runtime.ts +11 -11
- package/src/agent-cli-provider/types.ts +2 -0
- package/src/preflight.js +27 -1
- package/src/status-footer.js +19 -12
- package/task-lib/commands/list.js +90 -78
- package/task-lib/commands/status.js +97 -40
- package/task-lib/effective-status.js +52 -0
package/lib/setup-apply.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
const { resolveDecisionPath, domainFor, isConsumedPath, CONSUMED_PATHS } = require('./setup-plan');
|
|
15
15
|
const { validateMountConfig, validateEnvPassthrough } = require('./docker-config');
|
|
16
16
|
const { VALID_PROVIDERS } = require('./provider-names');
|
|
17
|
-
const
|
|
17
|
+
const VALID_LEVELS = Object.freeze(['level1', 'level2', 'level3']);
|
|
18
18
|
const {
|
|
19
19
|
loadJournal,
|
|
20
20
|
saveJournal,
|
|
@@ -38,10 +38,8 @@ function domainError(decisionId, value) {
|
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
// (defaultIsolation's domain is worktree|docker|none, but its target path is
|
|
44
|
-
// the boolean settings.defaultDocker — this is where that translation lives.)
|
|
41
|
+
// Resolve a submitted decision into the exact value stored at its canonical
|
|
42
|
+
// settings path, validating its decision domain before any write occurs.
|
|
45
43
|
function convertDecisionValue({ decisionId, value, globalSettings, deps }) {
|
|
46
44
|
switch (decisionId) {
|
|
47
45
|
case 'defaultProvider':
|
|
@@ -52,7 +50,7 @@ function convertDecisionValue({ decisionId, value, globalSettings, deps }) {
|
|
|
52
50
|
|
|
53
51
|
case 'defaultIsolation':
|
|
54
52
|
if (!['worktree', 'docker', 'none'].includes(value)) throw domainError(decisionId, value);
|
|
55
|
-
return value
|
|
53
|
+
return value;
|
|
56
54
|
|
|
57
55
|
case 'allowLocalNoIsolation':
|
|
58
56
|
if (typeof value !== 'boolean') throw domainError(decisionId, value);
|
|
@@ -88,24 +86,34 @@ function convertDecisionValue({ decisionId, value, globalSettings, deps }) {
|
|
|
88
86
|
if (!['off', 'notify', 'auto'].includes(value)) throw domainError(decisionId, value);
|
|
89
87
|
return value;
|
|
90
88
|
|
|
91
|
-
default:
|
|
92
|
-
if (decisionId.startsWith('providerLevel.')) {
|
|
93
|
-
|
|
94
|
-
throw domainError(decisionId, value);
|
|
95
|
-
}
|
|
96
|
-
for (const key of ['min', 'default', 'max']) {
|
|
97
|
-
if (!VALID_MODELS.includes(value[key])) throw domainError(decisionId, value);
|
|
98
|
-
}
|
|
99
|
-
const providerName = decisionId.slice('providerLevel.'.length);
|
|
100
|
-
const existing = getNestedValue(globalSettings, `providerSettings.${providerName}`) || {};
|
|
101
|
-
return {
|
|
102
|
-
...existing,
|
|
103
|
-
minLevel: mapLegacyModelToLevel(value.min),
|
|
104
|
-
defaultLevel: mapLegacyModelToLevel(value.default),
|
|
105
|
-
maxLevel: mapLegacyModelToLevel(value.max),
|
|
106
|
-
};
|
|
89
|
+
default: {
|
|
90
|
+
if (!decisionId.startsWith('providerLevel.')) {
|
|
91
|
+
throw new Error(`Unknown decision ID: ${decisionId}`);
|
|
107
92
|
}
|
|
108
|
-
|
|
93
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
94
|
+
throw domainError(decisionId, value);
|
|
95
|
+
}
|
|
96
|
+
const keys = Object.keys(value).sort();
|
|
97
|
+
const expectedKeys = ['defaultLevel', 'maxLevel', 'minLevel'];
|
|
98
|
+
if (
|
|
99
|
+
keys.length !== expectedKeys.length ||
|
|
100
|
+
keys.some((key, index) => key !== expectedKeys[index]) ||
|
|
101
|
+
expectedKeys.some((key) => !VALID_LEVELS.includes(value[key]))
|
|
102
|
+
) {
|
|
103
|
+
throw domainError(decisionId, value);
|
|
104
|
+
}
|
|
105
|
+
const providerName = decisionId.slice('providerLevel.'.length);
|
|
106
|
+
if (!deps.VALID_PROVIDERS.includes(providerName)) throw domainError(decisionId, value);
|
|
107
|
+
const rank = (level) => VALID_LEVELS.indexOf(level);
|
|
108
|
+
if (
|
|
109
|
+
rank(value.minLevel) > rank(value.defaultLevel) ||
|
|
110
|
+
rank(value.defaultLevel) > rank(value.maxLevel)
|
|
111
|
+
) {
|
|
112
|
+
throw domainError(decisionId, value);
|
|
113
|
+
}
|
|
114
|
+
const existing = getNestedValue(globalSettings, `providerSettings.${providerName}`) || {};
|
|
115
|
+
return { ...existing, ...value };
|
|
116
|
+
}
|
|
109
117
|
}
|
|
110
118
|
}
|
|
111
119
|
|
|
@@ -194,7 +202,6 @@ function applyWrite(write, { repoRoot, journal, deps }) {
|
|
|
194
202
|
return write.scope;
|
|
195
203
|
}
|
|
196
204
|
|
|
197
|
-
|
|
198
205
|
// Phase 2: resolve each global outcome against the locked, freshly read state.
|
|
199
206
|
// Repo-local settings remain intentionally outside the global settings lock.
|
|
200
207
|
function writeResolvedDecisions(
|
|
@@ -252,35 +259,20 @@ function writeResolvedDecisions(
|
|
|
252
259
|
return resolved.map((decision) => resultsById.get(decision.decisionId));
|
|
253
260
|
}
|
|
254
261
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
* @param {Object} params
|
|
259
|
-
* @param {string} params.decisionsPath - Path to the decisions JSON file.
|
|
260
|
-
* @param {string} params.cwd - Working directory (for repo-scope settings lookup).
|
|
261
|
-
* @param {boolean} [params.allowRiskyDefaults] - Required to store defaultDelivery='ship'.
|
|
262
|
-
* @param {Object} [params.deps] - Injected dependencies (for testing).
|
|
263
|
-
* @returns {Array<{decisionId: string, applied: boolean, from: *, to: *, skippedReason?: string}>}
|
|
264
|
-
*/
|
|
265
|
-
function applyDecisions({ decisionsPath, cwd, allowRiskyDefaults = false, deps = {} }) {
|
|
266
|
-
const resolvedDeps = { ...defaultApplyDeps(), ...deps };
|
|
267
|
-
|
|
268
|
-
let input;
|
|
269
|
-
try {
|
|
270
|
-
input = JSON.parse(resolvedDeps.readFile(decisionsPath));
|
|
271
|
-
} catch (err) {
|
|
272
|
-
throw new Error(`Failed to read decisions file "${decisionsPath}": ${err.message}`);
|
|
273
|
-
}
|
|
274
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
275
|
-
throw new Error('Decisions file must be a JSON object of { decisionId: value }');
|
|
262
|
+
function assertDecisionObject(decisions, label) {
|
|
263
|
+
if (!decisions || typeof decisions !== 'object' || Array.isArray(decisions)) {
|
|
264
|
+
throw new Error(`${label} must be a JSON object of { decisionId: value }`);
|
|
276
265
|
}
|
|
266
|
+
}
|
|
277
267
|
|
|
268
|
+
/** Apply an in-memory decision object without temporary files. */
|
|
269
|
+
function applyDecisionValues({ decisions, cwd, allowRiskyDefaults = false, deps = {} }) {
|
|
270
|
+
assertDecisionObject(decisions, 'Decisions');
|
|
271
|
+
const resolvedDeps = { ...defaultApplyDeps(), ...deps };
|
|
278
272
|
const globalSettings = resolvedDeps.loadSettings();
|
|
279
273
|
const { repoRoot, settings: repoSettingsRaw } = resolvedDeps.readRepoSettings(cwd);
|
|
280
274
|
const repoSettings = repoSettingsRaw || {};
|
|
281
|
-
|
|
282
|
-
const resolved = resolveAndValidateDecisions(input, globalSettings, resolvedDeps);
|
|
283
|
-
|
|
275
|
+
const resolved = resolveAndValidateDecisions(decisions, globalSettings, resolvedDeps);
|
|
284
276
|
const results = writeResolvedDecisions(resolved, {
|
|
285
277
|
repoSettings,
|
|
286
278
|
repoRoot,
|
|
@@ -289,22 +281,37 @@ function applyDecisions({ decisionsPath, cwd, allowRiskyDefaults = false, deps =
|
|
|
289
281
|
deps: resolvedDeps,
|
|
290
282
|
});
|
|
291
283
|
|
|
292
|
-
// Never store provider-auth secrets: print the login command instead.
|
|
293
284
|
const issueSourceApplied = results.find(
|
|
294
|
-
(
|
|
285
|
+
(result) => result.decisionId === 'defaultIssueSource' && result.applied
|
|
295
286
|
);
|
|
296
|
-
if (issueSourceApplied
|
|
287
|
+
if (issueSourceApplied?.to === 'github') {
|
|
297
288
|
const auth = resolvedDeps.checkGhAuth();
|
|
298
|
-
if (!auth
|
|
299
|
-
console.log('Run: gh auth login');
|
|
300
|
-
}
|
|
289
|
+
if (!auth?.authenticated) console.log('Run: gh auth login');
|
|
301
290
|
}
|
|
302
|
-
|
|
303
291
|
return results;
|
|
304
292
|
}
|
|
305
293
|
|
|
294
|
+
/** Apply a decisions JSON file through the shared object API. */
|
|
295
|
+
function applyDecisions({ decisionsPath, cwd, allowRiskyDefaults = false, deps = {} }) {
|
|
296
|
+
const resolvedDeps = { ...defaultApplyDeps(), ...deps };
|
|
297
|
+
let decisions;
|
|
298
|
+
try {
|
|
299
|
+
decisions = JSON.parse(resolvedDeps.readFile(decisionsPath));
|
|
300
|
+
} catch (error) {
|
|
301
|
+
throw new Error(`Failed to read decisions file "${decisionsPath}": ${error.message}`);
|
|
302
|
+
}
|
|
303
|
+
assertDecisionObject(decisions, 'Decisions file');
|
|
304
|
+
return applyDecisionValues({
|
|
305
|
+
decisions,
|
|
306
|
+
cwd,
|
|
307
|
+
allowRiskyDefaults,
|
|
308
|
+
deps: resolvedDeps,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
306
312
|
module.exports = {
|
|
307
313
|
applyDecisions,
|
|
314
|
+
applyDecisionValues,
|
|
308
315
|
resolveAndValidateDecisions,
|
|
309
316
|
writeResolvedDecisions,
|
|
310
317
|
assertSecretSafePath,
|
package/lib/setup-plan.js
CHANGED
|
@@ -11,15 +11,13 @@
|
|
|
11
11
|
|
|
12
12
|
const path = require('path');
|
|
13
13
|
|
|
14
|
-
const SCHEMA_VERSION =
|
|
14
|
+
const SCHEMA_VERSION = 2;
|
|
15
15
|
|
|
16
|
-
// Canonical settings
|
|
17
|
-
//
|
|
18
|
-
// already reads (today: settings.defaultDocker) — see the canonical-path
|
|
19
|
-
// rule in issue #605. Never introduce a second parallel key here.
|
|
16
|
+
// Canonical settings keys consumed by resolveEffectiveRunPlan. Never introduce
|
|
17
|
+
// a parallel setting or translate a decision into a differently named key.
|
|
20
18
|
const DECISION_PATHS = {
|
|
21
19
|
defaultProvider: { scope: 'global', path: 'defaultProvider' },
|
|
22
|
-
defaultIsolation: { scope: 'global', path: '
|
|
20
|
+
defaultIsolation: { scope: 'global', path: 'defaultIsolation' },
|
|
23
21
|
allowLocalNoIsolation: { scope: 'global', path: 'allowLocalNoIsolation' },
|
|
24
22
|
defaultDelivery: { scope: 'global', path: 'defaultDelivery' },
|
|
25
23
|
defaultIssueSource: { scope: 'global', path: 'defaultIssueSource' },
|
|
@@ -33,13 +31,11 @@ function providerLevelDecisionId(providerName) {
|
|
|
33
31
|
return `providerLevel.${providerName}`;
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
// Settings keys
|
|
37
|
-
//
|
|
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.
|
|
34
|
+
// Settings keys consumed by runtime resolvers. Shared by buildProposedWrites
|
|
35
|
+
// and setup apply so neither surface can advertise dead configuration.
|
|
40
36
|
const CONSUMED_PATHS = new Set([
|
|
41
37
|
'global:defaultProvider',
|
|
42
|
-
'global:
|
|
38
|
+
'global:defaultIsolation',
|
|
43
39
|
'global:defaultDelivery',
|
|
44
40
|
'global:defaultIssueSource',
|
|
45
41
|
'global:dockerMounts',
|
|
@@ -77,7 +73,7 @@ function defaultDeps() {
|
|
|
77
73
|
const { execSync } = require('../src/lib/safe-exec');
|
|
78
74
|
const { listProviders, getProvider } = require('../src/providers');
|
|
79
75
|
const { getProviderDefaults } = require('./provider-defaults');
|
|
80
|
-
const { getDefaultProviderId } = require('./provider-names');
|
|
76
|
+
const { getDefaultProviderId, getProviderMetadata } = require('./provider-names');
|
|
81
77
|
const packageJson = require('../package.json');
|
|
82
78
|
|
|
83
79
|
return {
|
|
@@ -90,6 +86,7 @@ function defaultDeps() {
|
|
|
90
86
|
getProvider,
|
|
91
87
|
getProviderDefaults,
|
|
92
88
|
getDefaultProviderId,
|
|
89
|
+
getProviderMetadata,
|
|
93
90
|
getNodeVersion: () => process.version,
|
|
94
91
|
getPackageVersion: () => packageJson.version,
|
|
95
92
|
};
|
|
@@ -98,14 +95,8 @@ function defaultDeps() {
|
|
|
98
95
|
function detectInstallSource(cwd, env) {
|
|
99
96
|
if (env.npm_config_global === 'true') return 'npm-global';
|
|
100
97
|
if (env.npm_execpath && /_npx|npx/.test(env.npm_execpath)) return 'npx';
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (cwd && (cwd === path.join(__dirname, '..') || cwd.startsWith(ownNodeModules))) {
|
|
104
|
-
return 'local';
|
|
105
|
-
}
|
|
106
|
-
} catch {
|
|
107
|
-
// fall through to unknown
|
|
108
|
-
}
|
|
98
|
+
const ownNodeModules = path.join(__dirname, '..', 'node_modules');
|
|
99
|
+
if (cwd && (cwd === path.join(__dirname, '..') || cwd.startsWith(ownNodeModules))) return 'local';
|
|
109
100
|
return 'unknown';
|
|
110
101
|
}
|
|
111
102
|
|
|
@@ -120,16 +111,20 @@ function buildNodeFacts({ cwd, env, deps }) {
|
|
|
120
111
|
function buildProviderFacts(deps) {
|
|
121
112
|
const providers = {};
|
|
122
113
|
for (const name of deps.listProviders()) {
|
|
123
|
-
|
|
114
|
+
const metadata = deps.getProviderMetadata(name);
|
|
115
|
+
const provider = deps.getProvider(name);
|
|
116
|
+
let available = false;
|
|
124
117
|
try {
|
|
125
|
-
|
|
118
|
+
available = provider.isAvailable() === true;
|
|
126
119
|
} catch {
|
|
127
|
-
|
|
120
|
+
available = false;
|
|
128
121
|
}
|
|
129
|
-
const
|
|
122
|
+
const cliCommand = provider.cliCommand || metadata.binary;
|
|
130
123
|
providers[name] = {
|
|
131
|
-
|
|
132
|
-
|
|
124
|
+
available,
|
|
125
|
+
displayName: metadata.displayName,
|
|
126
|
+
installInstructions: metadata.installInstructions,
|
|
127
|
+
path: available && deps.commandExists(cliCommand) ? deps.getCommandPath(cliCommand) : null,
|
|
133
128
|
};
|
|
134
129
|
}
|
|
135
130
|
return providers;
|
|
@@ -185,30 +180,19 @@ function inferPrBase(cwd, deps) {
|
|
|
185
180
|
|
|
186
181
|
function buildProviderLevelRecommendation(name, deps) {
|
|
187
182
|
const providerDefaults = deps.getProviderDefaults()[name] || {};
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
try {
|
|
194
|
-
const provider = deps.getProvider(name);
|
|
195
|
-
return {
|
|
196
|
-
min: provider.resolveModelSpec(minLevel, overrides).model,
|
|
197
|
-
default: provider.resolveModelSpec(defaultLevel, overrides).model,
|
|
198
|
-
max: provider.resolveModelSpec(maxLevel, overrides).model,
|
|
199
|
-
};
|
|
200
|
-
} catch {
|
|
201
|
-
return { min: null, default: null, max: null };
|
|
202
|
-
}
|
|
183
|
+
return {
|
|
184
|
+
minLevel: providerDefaults.minLevel || 'level1',
|
|
185
|
+
defaultLevel: providerDefaults.defaultLevel || 'level2',
|
|
186
|
+
maxLevel: providerDefaults.maxLevel || 'level3',
|
|
187
|
+
};
|
|
203
188
|
}
|
|
204
189
|
|
|
205
190
|
function buildRecommendedAndRisk({ cwd, facts, env, deps }) {
|
|
206
191
|
const recommended = {};
|
|
207
192
|
const risk = {};
|
|
208
|
-
|
|
209
193
|
const registryDefault = deps.getDefaultProviderId();
|
|
210
194
|
recommended.defaultProvider = registryDefault;
|
|
211
|
-
risk.defaultProvider = facts.providers[registryDefault]?.
|
|
195
|
+
risk.defaultProvider = facts.providers[registryDefault]?.available ? 'low' : 'medium';
|
|
212
196
|
|
|
213
197
|
for (const name of Object.keys(facts.providers)) {
|
|
214
198
|
recommended[providerLevelDecisionId(name)] = buildProviderLevelRecommendation(name, deps);
|
|
@@ -228,7 +212,6 @@ function buildRecommendedAndRisk({ cwd, facts, env, deps }) {
|
|
|
228
212
|
|
|
229
213
|
recommended.allowLocalNoIsolation = false;
|
|
230
214
|
risk.allowLocalNoIsolation = 'low';
|
|
231
|
-
|
|
232
215
|
recommended.defaultDelivery = 'none';
|
|
233
216
|
risk.defaultDelivery = 'low';
|
|
234
217
|
|
|
@@ -239,10 +222,8 @@ function buildRecommendedAndRisk({ cwd, facts, env, deps }) {
|
|
|
239
222
|
const inferredPrBase = inferPrBase(cwd, deps);
|
|
240
223
|
recommended.prBase = inferredPrBase || 'main';
|
|
241
224
|
risk.prBase = inferredPrBase ? 'low' : 'medium';
|
|
242
|
-
|
|
243
225
|
recommended.dockerMounts = ['gh', 'git', 'ssh'];
|
|
244
226
|
risk.dockerMounts = 'low';
|
|
245
|
-
|
|
246
227
|
recommended.dockerEnvPassthrough = [];
|
|
247
228
|
risk.dockerEnvPassthrough = 'low';
|
|
248
229
|
|
|
@@ -301,9 +282,11 @@ function buildDecisions({ facts, settings, repoSettings, inferredIssueSource, in
|
|
|
301
282
|
}
|
|
302
283
|
|
|
303
284
|
function domainFor(decisionId) {
|
|
304
|
-
if (decisionId.startsWith('providerLevel.'))
|
|
285
|
+
if (decisionId.startsWith('providerLevel.')) {
|
|
286
|
+
return '{ minLevel, defaultLevel, maxLevel } of level1|level2|level3';
|
|
287
|
+
}
|
|
305
288
|
const domains = {
|
|
306
|
-
defaultProvider: '
|
|
289
|
+
defaultProvider: 'registry provider id',
|
|
307
290
|
defaultIsolation: 'worktree | docker | none',
|
|
308
291
|
allowLocalNoIsolation: 'boolean',
|
|
309
292
|
defaultDelivery: 'none | pr | ship',
|
|
@@ -326,10 +309,7 @@ function buildProposedWrites({ decisions, recommended }) {
|
|
|
326
309
|
// no resolver reads would advertise a write that apply will always skip —
|
|
327
310
|
// dead config. Only propose writes apply will actually perform.
|
|
328
311
|
if (!isConsumedPath(target.scope, target.path)) continue;
|
|
329
|
-
|
|
330
|
-
if (decision.decisionId === 'defaultIsolation') {
|
|
331
|
-
to = to === 'docker';
|
|
332
|
-
}
|
|
312
|
+
const to = recommended[decision.decisionId];
|
|
333
313
|
if (to === decision.currentValue) continue;
|
|
334
314
|
writes.push({
|
|
335
315
|
scope: target.scope,
|
package/lib/start-cluster.js
CHANGED
|
@@ -198,24 +198,74 @@ function loadClusterConfig(orchestrator, configPath, settings = {}, providerOver
|
|
|
198
198
|
return prepareClusterConfig(orchestrator.loadConfig(configPath), settings, providerOverride);
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
const RUN_MODE_KEYS = ['docker', 'worktree', 'pr', 'ship', 'noIsolation', 'isolation'];
|
|
202
|
+
|
|
203
|
+
function hasExplicitRunMode(options) {
|
|
204
|
+
return RUN_MODE_KEYS.some(
|
|
205
|
+
(key) => options[key] === true || (key === 'isolation' && options[key] === false)
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
201
209
|
function mergeRunOptions(options) {
|
|
202
210
|
const envRunOptions = parseRunOptionsEnv();
|
|
203
|
-
|
|
211
|
+
if (!envRunOptions) return options;
|
|
212
|
+
if (!hasExplicitRunMode(options)) return { ...envRunOptions, ...options };
|
|
213
|
+
const withoutEnvMode = { ...envRunOptions };
|
|
214
|
+
for (const key of RUN_MODE_KEYS) delete withoutEnvMode[key];
|
|
215
|
+
return { ...withoutEnvMode, ...options };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function validateSavedRunModes(settings) {
|
|
219
|
+
const isolation = settings.defaultIsolation || 'none';
|
|
220
|
+
const delivery = settings.defaultDelivery || 'none';
|
|
221
|
+
if (!['none', 'worktree', 'docker'].includes(isolation)) {
|
|
222
|
+
throw new Error(`Invalid saved isolation mode: ${isolation}`);
|
|
223
|
+
}
|
|
224
|
+
if (!['none', 'pr', 'ship'].includes(delivery)) {
|
|
225
|
+
throw new Error(`Invalid saved delivery mode: ${delivery}`);
|
|
226
|
+
}
|
|
227
|
+
return { isolation, delivery };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function resolveEffectiveIsolation(options, savedIsolation) {
|
|
231
|
+
if (options.noIsolation === true || options.isolation === false) return 'none';
|
|
232
|
+
if (options.docker === true) return 'docker';
|
|
233
|
+
if (options.worktree === true || options.pr === true || options.ship === true) return 'worktree';
|
|
234
|
+
if (resolveEnvBool(process.env.ZEROSHOT_DOCKER) === true) return 'docker';
|
|
235
|
+
if (resolveEnvBool(process.env.ZEROSHOT_WORKTREE) === true) return 'worktree';
|
|
236
|
+
return savedIsolation;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function resolveEffectiveDelivery(options, savedDelivery) {
|
|
240
|
+
if (options.ship === true || options.autoMerge === true) return 'ship';
|
|
241
|
+
if (options.pr === true || resolveEnvBool(process.env.ZEROSHOT_PR) === true) return 'pr';
|
|
242
|
+
return savedDelivery;
|
|
204
243
|
}
|
|
205
244
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
245
|
+
function resolveEffectiveRunPlan(options = {}, settings = {}) {
|
|
246
|
+
const mergedOptions = mergeRunOptions(options);
|
|
247
|
+
const noIsolation = mergedOptions.noIsolation === true || mergedOptions.isolation === false;
|
|
248
|
+
const conflicts = ['docker', 'worktree', 'pr', 'ship'].filter(
|
|
249
|
+
(key) => mergedOptions[key] === true
|
|
250
|
+
);
|
|
251
|
+
if (noIsolation && conflicts.length > 0) {
|
|
252
|
+
throw new Error(`--no-isolation conflicts with --${conflicts.join(', --')}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const saved = validateSavedRunModes(settings);
|
|
256
|
+
let isolation = resolveEffectiveIsolation(mergedOptions, saved.isolation);
|
|
257
|
+
const delivery = resolveEffectiveDelivery(mergedOptions, saved.delivery);
|
|
258
|
+
if (delivery !== 'none' && isolation === 'none') {
|
|
259
|
+
if (noIsolation) {
|
|
260
|
+
throw new Error(`--no-isolation conflicts with saved delivery mode "${delivery}"`);
|
|
261
|
+
}
|
|
262
|
+
isolation = 'worktree';
|
|
263
|
+
}
|
|
211
264
|
return resolveRunPlan({
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
settings.defaultDocker
|
|
217
|
-
),
|
|
218
|
-
worktree: anyTruthy(mergedOptions.worktree, process.env.ZEROSHOT_WORKTREE === '1'),
|
|
265
|
+
docker: isolation === 'docker',
|
|
266
|
+
worktree: isolation === 'worktree',
|
|
267
|
+
pr: delivery === 'pr',
|
|
268
|
+
ship: delivery === 'ship',
|
|
219
269
|
});
|
|
220
270
|
}
|
|
221
271
|
|
|
@@ -255,7 +305,7 @@ function buildStartOptions({
|
|
|
255
305
|
forceProvider,
|
|
256
306
|
}) {
|
|
257
307
|
const mergedOptions = mergeRunOptions(options);
|
|
258
|
-
const plan = resolveEffectiveRunPlan(
|
|
308
|
+
const plan = resolveEffectiveRunPlan(options, settings);
|
|
259
309
|
return buildStartOptionsFromPlan({
|
|
260
310
|
clusterId,
|
|
261
311
|
plan,
|
|
@@ -368,16 +418,6 @@ function resolveConfigOrThrow({
|
|
|
368
418
|
return loadClusterConfig(orchestrator, resolvedPath, settings, providerOverride);
|
|
369
419
|
}
|
|
370
420
|
|
|
371
|
-
function applyDefaultDeliveryOptions(options, settings) {
|
|
372
|
-
if (settings.defaultDelivery === 'pr') {
|
|
373
|
-
return { ...options, pr: true };
|
|
374
|
-
}
|
|
375
|
-
if (settings.defaultDelivery === 'ship') {
|
|
376
|
-
return { ...options, ship: true };
|
|
377
|
-
}
|
|
378
|
-
return options;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
421
|
function startClusterWithInput(args, input) {
|
|
382
422
|
const { orchestrator, config, configPath, configName, settings, providerOverride } = args;
|
|
383
423
|
if (!orchestrator) {
|
|
@@ -393,7 +433,7 @@ function startClusterWithInput(args, input) {
|
|
|
393
433
|
});
|
|
394
434
|
const startOptions = buildStartOptions({
|
|
395
435
|
clusterId: args.clusterId,
|
|
396
|
-
options:
|
|
436
|
+
options: args.options || {},
|
|
397
437
|
settings,
|
|
398
438
|
providerOverride,
|
|
399
439
|
modelOverride: args.modelOverride,
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-open-engine/zeroshot",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.32.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@the-open-engine/zeroshot",
|
|
9
|
-
"version": "6.
|
|
9
|
+
"version": "6.32.1",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-open-engine/zeroshot",
|
|
3
|
-
"version": "6.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "6.32.1",
|
|
4
|
+
"description": "Independent executor–verifier orchestration for software changes.",
|
|
5
5
|
"main": "src/orchestrator.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"zeroshot": "./cli/index.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"test:all": "npm run test && npm run test:e2e && npm run test:slow",
|
|
39
39
|
"test:coverage": "c8 npm run test:unit",
|
|
40
40
|
"test:coverage:report": "c8 --reporter=html npm run test:unit && echo 'Coverage report generated at coverage/index.html'",
|
|
41
|
-
"postinstall": "node scripts/
|
|
41
|
+
"postinstall": "node scripts/postinstall.js",
|
|
42
42
|
"start": "node cli/index.js",
|
|
43
43
|
"typecheck": "tsc --noEmit && npm run typecheck:cluster && npm run typecheck:hosted-target && npm run typecheck:hosted-session && npm run typecheck:target",
|
|
44
44
|
"typecheck:agent-cli-provider": "tsc --project tsconfig.agent-cli-provider.json",
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
const LIFECYCLE_SCRIPTS = ['fix-node-pty-permissions.js', 'check-path.js'];
|
|
7
|
+
const SETUP_INVITATION = 'Run zeroshot to finish setup.\n';
|
|
8
|
+
|
|
9
|
+
function isTruthyEnvironmentFlag(value) {
|
|
10
|
+
if (typeof value !== 'string') return false;
|
|
11
|
+
return !['', '0', 'false'].includes(value.trim().toLowerCase());
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function shouldPrintSetupInvitation(env) {
|
|
15
|
+
const globalInstall = env.npm_config_global === 'true' || env.npm_config_location === 'global';
|
|
16
|
+
return globalInstall && !isTruthyEnvironmentFlag(env.CI);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function runLifecycleScript(scriptName) {
|
|
20
|
+
const result = spawnSync(process.execPath, [path.join(__dirname, scriptName)], {
|
|
21
|
+
stdio: 'inherit',
|
|
22
|
+
});
|
|
23
|
+
if (result.error) throw result.error;
|
|
24
|
+
return result.status ?? 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function runPostinstall({
|
|
28
|
+
env = process.env,
|
|
29
|
+
stdout = process.stdout,
|
|
30
|
+
runScript = runLifecycleScript,
|
|
31
|
+
} = {}) {
|
|
32
|
+
for (const scriptName of LIFECYCLE_SCRIPTS) {
|
|
33
|
+
const status = runScript(scriptName);
|
|
34
|
+
if (status !== 0) return status;
|
|
35
|
+
}
|
|
36
|
+
if (shouldPrintSetupInvitation(env)) stdout.write(SETUP_INVITATION);
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (require.main === module) {
|
|
41
|
+
try {
|
|
42
|
+
process.exitCode = runPostinstall();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.warn(`[postinstall] Warning: ${error.message}`);
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
LIFECYCLE_SCRIPTS,
|
|
51
|
+
SETUP_INVITATION,
|
|
52
|
+
runPostinstall,
|
|
53
|
+
shouldPrintSetupInvitation,
|
|
54
|
+
};
|
|
@@ -24,6 +24,10 @@ const { findPlatformMismatchReason } = require('./validation-platform');
|
|
|
24
24
|
const { calculateRateLimitDelay, isRateLimitError } = require('./rate-limit-backoff');
|
|
25
25
|
const { updateAgentProviderSession } = require('./provider-session');
|
|
26
26
|
const { rebuildProviderSessionAfterCommit } = require('./agent-task-executor');
|
|
27
|
+
const {
|
|
28
|
+
buildStructuredOutputClusterFailure,
|
|
29
|
+
isStructuredOutputInvalidError,
|
|
30
|
+
} = require('./structured-output-error');
|
|
27
31
|
const {
|
|
28
32
|
commitRecordedOwnership,
|
|
29
33
|
markCleanupRequired,
|
|
@@ -735,6 +739,7 @@ async function handleFinalFailure(agent, triggeringMessage, error, maxRetries) {
|
|
|
735
739
|
const failureAttempts = error?.terminationAttempts ?? maxRetries;
|
|
736
740
|
const unsupportedCapability =
|
|
737
741
|
error?.code === 'unsupported-capability' && error?.permanent === true;
|
|
742
|
+
const structuredOutputInvalid = isStructuredOutputInvalidError(error);
|
|
738
743
|
console.error(`
|
|
739
744
|
${'='.repeat(80)}`);
|
|
740
745
|
console.error(`🔴🔴🔴 MAX RETRIES EXHAUSTED - AGENT: ${agent.id} 🔴🔴🔴`);
|
|
@@ -810,6 +815,9 @@ ${'='.repeat(80)}`);
|
|
|
810
815
|
});
|
|
811
816
|
}
|
|
812
817
|
|
|
818
|
+
if (structuredOutputInvalid) {
|
|
819
|
+
agent._publish(buildStructuredOutputClusterFailure(agent, error));
|
|
820
|
+
}
|
|
813
821
|
if (unsupportedCapability) {
|
|
814
822
|
agent._publish({
|
|
815
823
|
topic: 'CLUSTER_FAILED',
|
|
@@ -880,6 +888,7 @@ ${'='.repeat(80)}`);
|
|
|
880
888
|
capability: error.capability,
|
|
881
889
|
}
|
|
882
890
|
: {}),
|
|
891
|
+
...(structuredOutputInvalid ? { code: error.code, details: error.details ?? null } : {}),
|
|
883
892
|
timestamp: Date.now(),
|
|
884
893
|
};
|
|
885
894
|
|
|
@@ -910,6 +919,7 @@ ${'='.repeat(80)}`);
|
|
|
910
919
|
capability: error.capability,
|
|
911
920
|
}
|
|
912
921
|
: {}),
|
|
922
|
+
...(structuredOutputInvalid ? { code: error.code, details: error.details ?? null } : {}),
|
|
913
923
|
hookFailureContext: error.message.includes('Hook uses result')
|
|
914
924
|
? {
|
|
915
925
|
taskId: agent.currentTaskId || 'UNKNOWN',
|
|
@@ -936,7 +946,7 @@ ${'='.repeat(80)}`);
|
|
|
936
946
|
orchestrator: agent.orchestrator,
|
|
937
947
|
});
|
|
938
948
|
|
|
939
|
-
if (!error?.terminationExhausted && !unsupportedCapability) {
|
|
949
|
+
if (!error?.terminationExhausted && !unsupportedCapability && !structuredOutputInvalid) {
|
|
940
950
|
agent.state = 'idle';
|
|
941
951
|
}
|
|
942
952
|
}
|