@the-open-engine/zeroshot 6.3.0 → 6.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/index.js +4 -1
- package/lib/detached-startup.js +4 -2
- package/lib/run-mode.js +18 -6
- package/lib/run-plan.js +32 -0
- package/lib/start-cluster.js +22 -8
- package/package.json +5 -3
- package/src/agent/agent-lifecycle.js +2 -0
- package/src/ledger.js +3 -0
- package/src/message-bus.js +12 -2
- package/src/orchestrator.js +14 -7
package/cli/index.js
CHANGED
|
@@ -189,7 +189,9 @@ function normalizeRunOptions(options) {
|
|
|
189
189
|
if (options.docker) {
|
|
190
190
|
options.worktree = false;
|
|
191
191
|
}
|
|
192
|
-
|
|
192
|
+
// autoMerge is NOT stored here — it is derived from the run plan (delivery ===
|
|
193
|
+
// 'ship') at every consumer. Writing it here unconditionally would clobber an
|
|
194
|
+
// explicit autoMerge intent (e.g. a future `--auto-merge` flag) back to false.
|
|
193
195
|
}
|
|
194
196
|
|
|
195
197
|
async function runClusterPreflight({ input, options, providerOverride, settings, forceProvider }) {
|
|
@@ -2610,6 +2612,7 @@ Force provider flags: -G (GitHub), -L (GitLab), -J (Jira), -D (DevOps), -N (Line
|
|
|
2610
2612
|
|
|
2611
2613
|
if (!process.env.ZEROSHOT_DAEMON) {
|
|
2612
2614
|
await streamClusterInForeground(cluster, orchestrator, clusterId, options);
|
|
2615
|
+
orchestrator.close();
|
|
2613
2616
|
}
|
|
2614
2617
|
|
|
2615
2618
|
setupDaemonCleanup(orchestrator, clusterId);
|
package/lib/detached-startup.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const os = require('os');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const lockfile = require('proper-lockfile');
|
|
5
|
+
const { resolveRunPlan } = require('./run-plan');
|
|
5
6
|
|
|
6
7
|
const DEFAULT_WAIT_TIMEOUT_SECONDS = 180;
|
|
7
8
|
const DEFAULT_WAIT_POLL_MS = 1000;
|
|
@@ -116,6 +117,7 @@ async function registerDetachedSetupCluster({
|
|
|
116
117
|
runOptions = {},
|
|
117
118
|
cwd,
|
|
118
119
|
}) {
|
|
120
|
+
const plan = resolveRunPlan(runOptions);
|
|
119
121
|
await updateClustersFile(storageDir, (clusters) => {
|
|
120
122
|
clusters[clusterId] = {
|
|
121
123
|
id: clusterId,
|
|
@@ -125,13 +127,13 @@ async function registerDetachedSetupCluster({
|
|
|
125
127
|
setupLogPath: logPath || null,
|
|
126
128
|
setupStartedAt: Date.now(),
|
|
127
129
|
setupStage: 'starting',
|
|
128
|
-
autoPr:
|
|
130
|
+
autoPr: plan.delivery !== 'none',
|
|
129
131
|
prOptions: runOptions.prBase
|
|
130
132
|
? {
|
|
131
133
|
prBase: runOptions.prBase,
|
|
132
134
|
mergeQueue: runOptions.mergeQueue || false,
|
|
133
135
|
closeIssue: runOptions.closeIssue || null,
|
|
134
|
-
autoMerge:
|
|
136
|
+
autoMerge: plan.autoMerge,
|
|
135
137
|
cwd: cwd || null,
|
|
136
138
|
}
|
|
137
139
|
: null,
|
package/lib/run-mode.js
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
const { resolveRunPlan } = require('./run-plan');
|
|
2
|
+
|
|
3
|
+
// The run-mode label is a VIEW of the canonical plan, never an independent
|
|
4
|
+
// cascade. Deriving it from the plan is what keeps the user-facing label and the
|
|
5
|
+
// actual isolation/delivery/autoMerge behavior from drifting apart. Callers that
|
|
6
|
+
// already hold a plan (e.g. the effective plan with env/settings folded in) use
|
|
7
|
+
// runModeFromPlan directly so the label reflects the SAME plan as behavior.
|
|
8
|
+
function runModeFromPlan({ isolation, delivery }) {
|
|
9
|
+
const dockerSuffix = isolation === 'docker' ? '+docker' : '';
|
|
10
|
+
if (delivery === 'ship') return `ship${dockerSuffix}`;
|
|
11
|
+
if (delivery === 'pr') return `pr${dockerSuffix}`;
|
|
12
|
+
if (isolation === 'docker') return 'docker';
|
|
13
|
+
if (isolation === 'worktree') return 'worktree';
|
|
6
14
|
return null;
|
|
7
15
|
}
|
|
8
16
|
|
|
17
|
+
function resolveRunMode(options) {
|
|
18
|
+
return runModeFromPlan(resolveRunPlan(options));
|
|
19
|
+
}
|
|
20
|
+
|
|
9
21
|
const RUN_MODE_LABELS = {
|
|
10
22
|
ship: 'ship (worktree + PR + auto-merge)',
|
|
11
23
|
'ship+docker': 'ship (docker + PR + auto-merge)',
|
|
@@ -19,4 +31,4 @@ function describeRunMode(mode) {
|
|
|
19
31
|
return RUN_MODE_LABELS[mode] || 'local (no isolation)';
|
|
20
32
|
}
|
|
21
33
|
|
|
22
|
-
module.exports = { resolveRunMode, describeRunMode };
|
|
34
|
+
module.exports = { resolveRunMode, runModeFromPlan, describeRunMode };
|
package/lib/run-plan.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single canonical run-mode resolver.
|
|
3
|
+
*
|
|
4
|
+
* Every consumer (orchestrator, daemon startup, CLI label) derives isolation,
|
|
5
|
+
* delivery, and autoMerge from THIS function and nowhere else. The raw
|
|
6
|
+
* worktree/docker/pr/ship/autoMerge booleans are inputs; the frozen plan is the
|
|
7
|
+
* one truth. Deriving autoMerge separately (the old `Boolean(ship)` scatter) is
|
|
8
|
+
* what let `--pr` merge and let the run-mode label drift from behavior.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
function resolveIsolation(options) {
|
|
12
|
+
if (options.docker) return 'docker';
|
|
13
|
+
if (options.worktree || options.pr || options.ship) return 'worktree';
|
|
14
|
+
return 'none';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function resolveDelivery(options) {
|
|
18
|
+
// Explicit autoMerge (e.g. a future `--auto-merge` flag) is ship-equivalent:
|
|
19
|
+
// "merge it" implies the ship delivery. This is the ONLY place that intent is
|
|
20
|
+
// interpreted, so it can never be silently overwritten downstream.
|
|
21
|
+
if (options.ship || options.autoMerge === true) return 'ship';
|
|
22
|
+
if (options.pr) return 'pr';
|
|
23
|
+
return 'none';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveRunPlan(options = {}) {
|
|
27
|
+
const isolation = resolveIsolation(options);
|
|
28
|
+
const delivery = resolveDelivery(options);
|
|
29
|
+
return Object.freeze({ isolation, delivery, autoMerge: delivery === 'ship' });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { resolveRunPlan };
|
package/lib/start-cluster.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
202
|
-
|
|
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:
|
|
258
|
+
isolation: plan.isolation === 'docker',
|
|
245
259
|
isolationImage: firstTruthy(mergedOptions.dockerImage, process.env.ZEROSHOT_DOCKER_IMAGE),
|
|
246
|
-
worktree:
|
|
247
|
-
autoPr:
|
|
248
|
-
autoMerge:
|
|
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:
|
|
274
|
+
runMode: runModeFromPlan(plan),
|
|
261
275
|
requiredQualityGates: mergedOptions.requiredQualityGates,
|
|
262
276
|
settings,
|
|
263
277
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-open-engine/zeroshot",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.0",
|
|
4
4
|
"description": "Multi-agent orchestration engine for Claude, Codex, and Gemini",
|
|
5
5
|
"main": "src/orchestrator.js",
|
|
6
6
|
"bin": {
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
"pretest": "npm run build:agent-cli-provider",
|
|
16
16
|
"test": "node tests/run-tests.js",
|
|
17
17
|
"test:unit": "node tests/run-tests.js",
|
|
18
|
-
"test:
|
|
19
|
-
"test:
|
|
18
|
+
"test:e2e": "mocha 'tests/e2e/**/*.test.js' --timeout 120000",
|
|
19
|
+
"test:e2e:docker": "bash tests/integration/e2e-isolation-and-auto.test.sh",
|
|
20
|
+
"test:slow": "mocha 'tests/integration/**/*.test.js' 'tests/providers/**/*.test.js' --timeout 180000",
|
|
21
|
+
"test:all": "npm run test && npm run test:e2e && npm run test:slow",
|
|
20
22
|
"test:coverage": "c8 npm run test:unit",
|
|
21
23
|
"test:coverage:report": "c8 --reporter=html npm run test:unit && echo 'Coverage report generated at coverage/index.html'",
|
|
22
24
|
"postinstall": "node scripts/fix-node-pty-permissions.js && node scripts/check-path.js",
|
package/src/ledger.js
CHANGED
package/src/message-bus.js
CHANGED
|
@@ -24,12 +24,14 @@ class MessageBus extends EventEmitter {
|
|
|
24
24
|
this.setMaxListeners(MAX_LISTENERS);
|
|
25
25
|
this.ledger = ledger || new Ledger();
|
|
26
26
|
this.wsClients = new Set();
|
|
27
|
+
this._closed = false;
|
|
27
28
|
|
|
28
29
|
// Forward ledger events
|
|
29
|
-
this.
|
|
30
|
+
this._forwardLedgerMessage = (message) => {
|
|
30
31
|
this.emit('message', message);
|
|
31
32
|
this._broadcastToWebSocket(message);
|
|
32
|
-
}
|
|
33
|
+
};
|
|
34
|
+
this.ledger.on('message', this._forwardLedgerMessage);
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
/**
|
|
@@ -241,6 +243,14 @@ class MessageBus extends EventEmitter {
|
|
|
241
243
|
* Close the message bus
|
|
242
244
|
*/
|
|
243
245
|
close() {
|
|
246
|
+
if (this._closed) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
this._closed = true;
|
|
250
|
+
|
|
251
|
+
this.ledger.off('message', this._forwardLedgerMessage);
|
|
252
|
+
this.removeAllListeners();
|
|
253
|
+
|
|
244
254
|
// Close all WebSocket connections
|
|
245
255
|
for (const ws of this.wsClients) {
|
|
246
256
|
try {
|
package/src/orchestrator.js
CHANGED
|
@@ -47,6 +47,7 @@ const configValidator = require('./config-validator');
|
|
|
47
47
|
const TemplateResolver = require('./template-resolver');
|
|
48
48
|
const { loadSettings } = require('../lib/settings');
|
|
49
49
|
const { normalizeProviderName } = require('../lib/provider-names');
|
|
50
|
+
const { resolveRunPlan } = require('../lib/run-plan');
|
|
50
51
|
const { getProvider } = require('./providers');
|
|
51
52
|
const StateSnapshotter = require('./state-snapshotter');
|
|
52
53
|
const { resolveClusterRequiredQualityGates } = require('./quality-gates');
|
|
@@ -212,14 +213,11 @@ function applyPushBlockedRepairTriggers(config) {
|
|
|
212
213
|
}
|
|
213
214
|
}
|
|
214
215
|
|
|
215
|
-
function resolveAutoMerge(options) {
|
|
216
|
-
return Boolean(options.ship) || Boolean(options.autoMerge);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
216
|
function buildPrOptions(options, requiredQualityGates) {
|
|
220
217
|
// autoMerge must always be persisted (even when no other PR fields are set) so that
|
|
221
218
|
// `zeroshot run --pr` (autoMerge=false) vs `--ship` (autoMerge=true) survives resume.
|
|
222
|
-
|
|
219
|
+
// Derived from the canonical run plan — never recomputed from ship/pr here.
|
|
220
|
+
const autoMerge = resolveRunPlan(options).autoMerge;
|
|
223
221
|
|
|
224
222
|
return {
|
|
225
223
|
prBase: options.prBase || null,
|
|
@@ -577,6 +575,12 @@ class Orchestrator {
|
|
|
577
575
|
// missing - a ledger write. Read-only orchestrators must never write, so they
|
|
578
576
|
// skip it; they only need to read existing snapshots, not produce new ones.
|
|
579
577
|
if (!this.readonly) {
|
|
578
|
+
this._registerClusterSubscriptions({
|
|
579
|
+
messageBus,
|
|
580
|
+
clusterId,
|
|
581
|
+
isolationManager,
|
|
582
|
+
containerId: isolation?.containerId || null,
|
|
583
|
+
});
|
|
580
584
|
this._startSnapshotter(clusterContext);
|
|
581
585
|
}
|
|
582
586
|
this._log(`[Orchestrator] Loaded cluster: ${clusterId} with ${agents.length} agents`);
|
|
@@ -1825,7 +1829,7 @@ class Orchestrator {
|
|
|
1825
1829
|
mergeQueue: options.mergeQueue,
|
|
1826
1830
|
closeIssue: options.closeIssue,
|
|
1827
1831
|
requiredQualityGates: options.requiredQualityGates,
|
|
1828
|
-
autoMerge:
|
|
1832
|
+
autoMerge: resolveRunPlan(options).autoMerge,
|
|
1829
1833
|
cwd: options.cwd,
|
|
1830
1834
|
});
|
|
1831
1835
|
|
|
@@ -2316,6 +2320,9 @@ class Orchestrator {
|
|
|
2316
2320
|
* Call before deleting storageDir to prevent ENOENT race conditions during cleanup
|
|
2317
2321
|
*/
|
|
2318
2322
|
close() {
|
|
2323
|
+
if (this.closed) {
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2319
2326
|
this.closed = true;
|
|
2320
2327
|
}
|
|
2321
2328
|
|
|
@@ -4103,7 +4110,7 @@ Continue from where you left off. Review your previous output to understand what
|
|
|
4103
4110
|
|
|
4104
4111
|
// Exported for testing (PR options persistence, e.g. autoMerge for --pr vs --ship).
|
|
4105
4112
|
Orchestrator.buildPrOptions = buildPrOptions;
|
|
4106
|
-
Orchestrator.
|
|
4113
|
+
Orchestrator.resolveRunPlan = resolveRunPlan;
|
|
4107
4114
|
|
|
4108
4115
|
module.exports = Orchestrator;
|
|
4109
4116
|
module.exports.DuplicateClusterError = DuplicateClusterError;
|