@the-open-engine/zeroshot 6.4.0 → 6.6.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 (146) 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 +31 -1
  107. package/scripts/assert-release-published.js +53 -0
  108. package/scripts/live-provider-smoke.js +221 -0
  109. package/scripts/release-preflight.js +236 -0
  110. package/src/agent/agent-lifecycle.js +5 -4
  111. package/src/agent/agent-quality-gate-schema.js +2 -2
  112. package/src/agent/agent-task-executor.js +41 -2
  113. package/src/agent-cli-provider/acp-stdio-runner.ts +314 -0
  114. package/src/agent-cli-provider/adapters/acp.ts +493 -0
  115. package/src/agent-cli-provider/adapters/claude.ts +2 -32
  116. package/src/agent-cli-provider/adapters/copilot-parser.ts +166 -0
  117. package/src/agent-cli-provider/adapters/copilot.ts +231 -0
  118. package/src/agent-cli-provider/adapters/gateway.ts +186 -0
  119. package/src/agent-cli-provider/adapters/gemini.ts +9 -2
  120. package/src/agent-cli-provider/adapters/index.ts +5 -36
  121. package/src/agent-cli-provider/adapters/opencode.ts +4 -1
  122. package/src/agent-cli-provider/adapters/pi.ts +410 -0
  123. package/src/agent-cli-provider/claude-command.ts +47 -0
  124. package/src/agent-cli-provider/contract-actions.ts +14 -17
  125. package/src/agent-cli-provider/contract-invoke.ts +12 -3
  126. package/src/agent-cli-provider/contract-options.ts +72 -3
  127. package/src/agent-cli-provider/contract-support.ts +30 -3
  128. package/src/agent-cli-provider/errors.ts +14 -4
  129. package/src/agent-cli-provider/gateway-client.ts +170 -0
  130. package/src/agent-cli-provider/gateway-runner.ts +353 -0
  131. package/src/agent-cli-provider/gateway-tools.ts +616 -0
  132. package/src/agent-cli-provider/index.ts +28 -0
  133. package/src/agent-cli-provider/invoke-evidence.ts +24 -3
  134. package/src/agent-cli-provider/provider-registry.ts +515 -0
  135. package/src/agent-cli-provider/single-agent-runtime.ts +192 -18
  136. package/src/agent-cli-provider/types.ts +145 -4
  137. package/src/config-validator.js +33 -15
  138. package/src/isolation-manager.js +124 -19
  139. package/src/orchestrator.js +23 -10
  140. package/src/preflight.js +83 -30
  141. package/src/providers/capabilities.js +15 -42
  142. package/src/providers/index.js +52 -72
  143. package/src/worktree-claude-config.js +17 -0
  144. package/task-lib/commands/run.js +1 -0
  145. package/task-lib/provider-helper-runtime.js +11 -0
  146. package/task-lib/runner.js +7 -0
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+
8
+ const helper = require('../lib/agent-cli-provider');
9
+
10
+ const SENTINEL = process.env.ZEROSHOT_LIVE_SENTINEL || 'ZEROSHOT_LIVE_SMOKE_OK';
11
+ const TIMEOUT_MS = Number(process.env.ZEROSHOT_LIVE_TIMEOUT_MS || 120000);
12
+ const DEFAULT_PROMPT = `Reply with exactly this text and nothing else: ${SENTINEL}`;
13
+
14
+ function fail(message) {
15
+ console.error(message);
16
+ process.exitCode = 1;
17
+ }
18
+
19
+ function usage() {
20
+ return [
21
+ 'Live provider smoke requires explicit provider selection.',
22
+ '',
23
+ 'Examples:',
24
+ ' ZEROSHOT_LIVE_PROVIDERS=all npm run test:providers:live',
25
+ ' ZEROSHOT_LIVE_PROVIDERS=claude,codex,gemini npm run test:providers:live',
26
+ ' ZEROSHOT_LIVE_PROVIDERS=pi npm run test:providers:live',
27
+ ' ZEROSHOT_LIVE_PROVIDERS=copilot npm run test:providers:live',
28
+ ' ZEROSHOT_LIVE_PROVIDERS=gateway \\',
29
+ ' ZEROSHOT_LIVE_GATEWAY_BASE_URL=https://openrouter.ai/api/v1 \\',
30
+ ' ZEROSHOT_LIVE_GATEWAY_API_KEY=... \\',
31
+ ' ZEROSHOT_LIVE_GATEWAY_MODEL=openai/gpt-5.4 \\',
32
+ ' npm run test:providers:live',
33
+ '',
34
+ 'This command invokes real provider CLIs or a real gateway endpoint. It is intentionally',
35
+ 'not part of the normal CI suite because it can require user auth and paid API calls.',
36
+ ].join('\n');
37
+ }
38
+
39
+ function parseProviders() {
40
+ const raw = process.env.ZEROSHOT_LIVE_PROVIDERS;
41
+ if (!raw) {
42
+ console.error(usage());
43
+ process.exit(2);
44
+ }
45
+
46
+ const providers = [];
47
+ const allProviders = helper.listProviderAdapters();
48
+ for (const item of raw.split(',')) {
49
+ const trimmed = item.trim();
50
+ if (trimmed.toLowerCase() === 'all') {
51
+ for (const provider of allProviders) {
52
+ if (!providers.includes(provider)) providers.push(provider);
53
+ }
54
+ continue;
55
+ }
56
+
57
+ const normalized = helper.normalizeProviderName(trimmed);
58
+ if (!normalized) continue;
59
+ if (!allProviders.includes(normalized)) {
60
+ throw new Error(
61
+ `Unknown provider "${item}". Valid providers: ${allProviders.join(', ')}, all`
62
+ );
63
+ }
64
+ if (!providers.includes(normalized)) providers.push(normalized);
65
+ }
66
+
67
+ if (providers.length === 0) {
68
+ throw new Error('ZEROSHOT_LIVE_PROVIDERS did not contain any provider names.');
69
+ }
70
+ return providers;
71
+ }
72
+
73
+ function readJsonEnv(key) {
74
+ const value = process.env[key];
75
+ if (!value) return undefined;
76
+ try {
77
+ return JSON.parse(value);
78
+ } catch (error) {
79
+ throw new Error(`${key} must be valid JSON: ${error.message}`);
80
+ }
81
+ }
82
+
83
+ function liveGatewayOptions(cwd) {
84
+ const baseUrl = process.env.ZEROSHOT_LIVE_GATEWAY_BASE_URL;
85
+ const apiKey = process.env.ZEROSHOT_LIVE_GATEWAY_API_KEY;
86
+ const model = process.env.ZEROSHOT_LIVE_GATEWAY_MODEL;
87
+ const headers = readJsonEnv('ZEROSHOT_LIVE_GATEWAY_HEADERS_JSON');
88
+
89
+ for (const [key, value] of [
90
+ ['ZEROSHOT_LIVE_GATEWAY_BASE_URL', baseUrl],
91
+ ['ZEROSHOT_LIVE_GATEWAY_API_KEY', apiKey],
92
+ ['ZEROSHOT_LIVE_GATEWAY_MODEL', model],
93
+ ]) {
94
+ if (!value) throw new Error(`gateway live smoke requires ${key}.`);
95
+ }
96
+
97
+ return {
98
+ cwd,
99
+ gateway: {
100
+ baseUrl,
101
+ apiKey,
102
+ model,
103
+ ...(headers === undefined ? {} : { headers }),
104
+ toolPolicy: {
105
+ roots: ['.'],
106
+ commands: [],
107
+ },
108
+ },
109
+ };
110
+ }
111
+
112
+ function providerOptions(provider, cwd) {
113
+ if (provider === 'gateway') return liveGatewayOptions(cwd);
114
+
115
+ return {
116
+ cwd,
117
+ outputFormat: 'stream-json',
118
+ };
119
+ }
120
+
121
+ function eventText(event) {
122
+ if (!event || typeof event !== 'object') return '';
123
+ if (event.type === 'text' || event.type === 'thinking') return event.text || '';
124
+ if (event.type === 'result') {
125
+ return [event.result, event.text, event.message].filter(Boolean).join('\n');
126
+ }
127
+ return '';
128
+ }
129
+
130
+ function summarizeResult(envelope) {
131
+ if (!envelope || typeof envelope !== 'object') return 'no envelope';
132
+ if (!envelope.ok) {
133
+ const error = envelope.error || {};
134
+ return `${error.code || 'error'}: ${error.message || 'unknown failure'}`;
135
+ }
136
+
137
+ const result = envelope.result || {};
138
+ const events = Array.isArray(result.events) ? result.events : [];
139
+ const text = events.map(eventText).join('\n').trim();
140
+ const last = events.at(-1);
141
+ return JSON.stringify(
142
+ {
143
+ provider: envelope.provider,
144
+ exitCode: result.exitCode,
145
+ signal: result.signal,
146
+ timedOut: result.timedOut,
147
+ classification: result.classification || null,
148
+ eventCount: events.length,
149
+ lastEventType: last && typeof last === 'object' ? last.type : null,
150
+ text: text.slice(0, 500),
151
+ },
152
+ null,
153
+ 2
154
+ );
155
+ }
156
+
157
+ function assertSmokePassed(provider, response) {
158
+ if (response.exitCode !== 0) {
159
+ throw new Error(
160
+ `${provider} contract exited ${response.exitCode}: ${summarizeResult(response.envelope)}`
161
+ );
162
+ }
163
+ if (!response.envelope.ok) {
164
+ throw new Error(`${provider} returned a contract error: ${summarizeResult(response.envelope)}`);
165
+ }
166
+
167
+ const result = response.envelope.result || {};
168
+ const events = Array.isArray(result.events) ? result.events : [];
169
+ const text = events.map(eventText).join('\n');
170
+ const finalResult = events.findLast((event) => event && event.type === 'result');
171
+
172
+ if (result.timedOut) {
173
+ throw new Error(`${provider} timed out after ${result.timeoutMs || TIMEOUT_MS}ms.`);
174
+ }
175
+ if (finalResult && finalResult.success === false) {
176
+ throw new Error(
177
+ `${provider} produced a failed result event: ${summarizeResult(response.envelope)}`
178
+ );
179
+ }
180
+ if (!text.includes(SENTINEL)) {
181
+ throw new Error(
182
+ `${provider} did not return sentinel ${SENTINEL}.\nResult:\n${summarizeResult(response.envelope)}`
183
+ );
184
+ }
185
+ }
186
+
187
+ async function smokeProvider(provider) {
188
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `zeroshot-live-${provider}-`));
189
+ try {
190
+ const prompt = process.env.ZEROSHOT_LIVE_PROMPT || DEFAULT_PROMPT;
191
+ const response = await helper.runProviderExecutable(
192
+ JSON.stringify({
193
+ schemaVersion: 1,
194
+ command: 'invoke',
195
+ provider,
196
+ context: prompt,
197
+ options: providerOptions(provider, tempDir),
198
+ timeoutMs: TIMEOUT_MS,
199
+ })
200
+ );
201
+
202
+ assertSmokePassed(provider, response);
203
+ console.log(`✓ ${provider} live smoke returned ${SENTINEL}`);
204
+ } finally {
205
+ fs.rmSync(tempDir, { recursive: true, force: true });
206
+ }
207
+ }
208
+
209
+ async function main() {
210
+ const providers = parseProviders();
211
+ console.log(`Running live provider smoke for: ${providers.join(', ')}`);
212
+ console.log(`Sentinel: ${SENTINEL}`);
213
+
214
+ for (const provider of providers) {
215
+ await smokeProvider(provider);
216
+ }
217
+ }
218
+
219
+ main().catch((error) => {
220
+ fail(error.stack || error.message || String(error));
221
+ });
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const https = require('https');
5
+ const { execFileSync } = require('child_process');
6
+
7
+ const RELEASE_ORDER = ['patch', 'minor', 'major'];
8
+ const REQUIRED_PLUGINS = [
9
+ '@semantic-release/commit-analyzer',
10
+ '@semantic-release/release-notes-generator',
11
+ '@semantic-release/npm',
12
+ '@semantic-release/github',
13
+ ];
14
+ const FORBIDDEN_EFFECTIVE_PLUGINS = new Set([
15
+ '@semantic-release/changelog',
16
+ '@semantic-release/git',
17
+ ]);
18
+
19
+ function normalizePlugin(plugin) {
20
+ return Array.isArray(plugin) ? plugin[0] : plugin;
21
+ }
22
+
23
+ function releaseRank(type) {
24
+ return RELEASE_ORDER.indexOf(type);
25
+ }
26
+
27
+ function maxReleaseType(current, candidate) {
28
+ if (!candidate) return current;
29
+ if (!current) return candidate;
30
+ return releaseRank(candidate) > releaseRank(current) ? candidate : current;
31
+ }
32
+
33
+ function analyzeMessage(message) {
34
+ const firstLine = String(message || '')
35
+ .split(/\r?\n/, 1)[0]
36
+ .trim();
37
+ if (!firstLine) return null;
38
+ if (/BREAKING CHANGE:|BREAKING-CHANGE:/m.test(message)) return 'major';
39
+
40
+ const separator = firstLine.indexOf(': ');
41
+ if (separator <= 0) return null;
42
+
43
+ const header = firstLine.slice(0, separator);
44
+ const breaking = header.endsWith('!');
45
+ const typeAndScope = breaking ? header.slice(0, -1) : header;
46
+ const scopeStart = typeAndScope.indexOf('(');
47
+ const type = scopeStart === -1 ? typeAndScope : typeAndScope.slice(0, scopeStart);
48
+
49
+ if (!/^[a-z][a-z0-9-]*$/i.test(type)) return null;
50
+ if (breaking) return 'major';
51
+
52
+ switch (type) {
53
+ case 'release':
54
+ case 'feat':
55
+ return 'minor';
56
+ case 'fix':
57
+ case 'perf':
58
+ return 'patch';
59
+ default:
60
+ return null;
61
+ }
62
+ }
63
+
64
+ function getPluginNames(releaseConfig) {
65
+ return (releaseConfig.plugins || []).map(normalizePlugin);
66
+ }
67
+
68
+ function validateReleaseConfig(packageJson) {
69
+ const releaseConfig = packageJson.release;
70
+ if (!releaseConfig || typeof releaseConfig !== 'object') {
71
+ throw new Error(
72
+ 'package.json#release is required so it takes precedence over stale .releaserc files'
73
+ );
74
+ }
75
+
76
+ const branches = Array.isArray(releaseConfig.branches)
77
+ ? releaseConfig.branches
78
+ : [releaseConfig.branches].filter(Boolean);
79
+ if (!branches.includes('main')) {
80
+ throw new Error('package.json#release.branches must include main');
81
+ }
82
+
83
+ const pluginNames = getPluginNames(releaseConfig);
84
+ for (const required of REQUIRED_PLUGINS) {
85
+ if (!pluginNames.includes(required)) {
86
+ throw new Error(`package.json#release.plugins is missing ${required}`);
87
+ }
88
+ }
89
+
90
+ for (const forbidden of FORBIDDEN_EFFECTIVE_PLUGINS) {
91
+ if (pluginNames.includes(forbidden)) {
92
+ throw new Error(
93
+ `${forbidden} must not be in the effective release config for protected main`
94
+ );
95
+ }
96
+ }
97
+
98
+ const npmPlugin = releaseConfig.plugins.find(
99
+ (plugin) => normalizePlugin(plugin) === '@semantic-release/npm'
100
+ );
101
+ const npmOptions = Array.isArray(npmPlugin) ? npmPlugin[1] || {} : {};
102
+ if (npmOptions.npmPublish !== true) {
103
+ throw new Error('@semantic-release/npm must publish to npm');
104
+ }
105
+
106
+ return pluginNames;
107
+ }
108
+
109
+ function readJson(path) {
110
+ return JSON.parse(fs.readFileSync(path, 'utf8'));
111
+ }
112
+
113
+ function git(args) {
114
+ return execFileSync('git', args, { encoding: 'utf8' }).trim();
115
+ }
116
+
117
+ function latestReleaseTag() {
118
+ return git(['describe', '--tags', '--abbrev=0', '--match', 'v[0-9]*']);
119
+ }
120
+
121
+ function commitMessagesSince(tag) {
122
+ const output = git(['log', '--format=%B%x1e', `${tag}..HEAD`]);
123
+ return output
124
+ .split('\x1e')
125
+ .map((message) => message.trim())
126
+ .filter(Boolean);
127
+ }
128
+
129
+ function prTitleFromEvent() {
130
+ const eventPath = process.env.GITHUB_EVENT_PATH;
131
+ if (!eventPath || !fs.existsSync(eventPath)) return null;
132
+ const event = readJson(eventPath);
133
+ return event.pull_request?.title || null;
134
+ }
135
+
136
+ function prNumberFromMergeQueueRef() {
137
+ const refName = process.env.GITHUB_REF_NAME || process.env.GITHUB_REF || '';
138
+ const match = refName.match(/(?:^|\/)pr-(\d+)-/);
139
+ return match ? match[1] : null;
140
+ }
141
+
142
+ function githubJson(path) {
143
+ const token = process.env.GITHUB_TOKEN;
144
+ const repository = process.env.GITHUB_REPOSITORY;
145
+ if (!token || !repository) return Promise.resolve(null);
146
+
147
+ return new Promise((resolve, reject) => {
148
+ const request = https.request(
149
+ {
150
+ hostname: 'api.github.com',
151
+ path: `/repos/${repository}${path}`,
152
+ headers: {
153
+ Accept: 'application/vnd.github+json',
154
+ Authorization: `Bearer ${token}`,
155
+ 'User-Agent': 'zeroshot-release-preflight',
156
+ 'X-GitHub-Api-Version': '2022-11-28',
157
+ },
158
+ },
159
+ (response) => {
160
+ let body = '';
161
+ response.setEncoding('utf8');
162
+ response.on('data', (chunk) => {
163
+ body += chunk;
164
+ });
165
+ response.on('end', () => {
166
+ if (response.statusCode < 200 || response.statusCode >= 300) {
167
+ reject(new Error(`GitHub API ${path} returned ${response.statusCode}: ${body}`));
168
+ return;
169
+ }
170
+ resolve(JSON.parse(body));
171
+ });
172
+ }
173
+ );
174
+ request.on('error', reject);
175
+ request.end();
176
+ });
177
+ }
178
+
179
+ async function prTitleFromMergeQueue() {
180
+ const number = prNumberFromMergeQueueRef();
181
+ if (!number) return null;
182
+ const pull = await githubJson(`/pulls/${number}`);
183
+ return pull?.title || null;
184
+ }
185
+
186
+ async function releaseSignal() {
187
+ const tag = latestReleaseTag();
188
+ const messages = commitMessagesSince(tag);
189
+ let releaseType = null;
190
+ for (const message of messages) {
191
+ releaseType = maxReleaseType(releaseType, analyzeMessage(message));
192
+ }
193
+
194
+ let title = prTitleFromEvent();
195
+ if (!title) title = await prTitleFromMergeQueue();
196
+ releaseType = maxReleaseType(releaseType, analyzeMessage(title));
197
+
198
+ return {
199
+ latestTag: tag,
200
+ commitCount: messages.length,
201
+ prTitle: title,
202
+ releaseType,
203
+ };
204
+ }
205
+
206
+ async function main() {
207
+ const packageJson = readJson('package.json');
208
+ const pluginNames = validateReleaseConfig(packageJson);
209
+ const signal = await releaseSignal();
210
+
211
+ console.log(`Effective release plugins: ${pluginNames.join(', ')}`);
212
+ console.log(`Latest release tag: ${signal.latestTag}`);
213
+ console.log(`Commits since tag: ${signal.commitCount}`);
214
+ if (signal.prTitle) console.log(`Release PR title: ${signal.prTitle}`);
215
+
216
+ if (!signal.releaseType) {
217
+ throw new Error(
218
+ 'Release promotion would publish nothing; use a release-worthy PR title/commit'
219
+ );
220
+ }
221
+
222
+ console.log(`Release preflight passed: ${signal.releaseType}`);
223
+ }
224
+
225
+ if (require.main === module) {
226
+ main().catch((error) => {
227
+ console.error(`Release preflight failed: ${error.message}`);
228
+ process.exit(1);
229
+ });
230
+ }
231
+
232
+ module.exports = {
233
+ analyzeMessage,
234
+ maxReleaseType,
235
+ validateReleaseConfig,
236
+ };
@@ -61,10 +61,6 @@ async function createValidatorIsolation(agent, isolationConfig) {
61
61
 
62
62
  const cluster = agent.cluster || {};
63
63
  const workDir = agent.config?.cwd || cluster.worktree?.path || cluster.cwd || process.cwd();
64
- const image = isolationConfig.image;
65
- await IsolationManager.ensureImage(image);
66
-
67
- const manager = new IsolationManager({ image });
68
64
  const providerName = normalizeProviderName(
69
65
  (agent._resolveProvider && agent._resolveProvider()) ||
70
66
  cluster.config?.forceProvider ||
@@ -72,6 +68,11 @@ async function createValidatorIsolation(agent, isolationConfig) {
72
68
  loadSettings().defaultProvider ||
73
69
  'claude'
74
70
  );
71
+ // Run validators on the provider's image variant (installs its CLI as a Docker-cached layer).
72
+ const image = IsolationManager.imageForProvider(providerName, isolationConfig.image);
73
+ await IsolationManager.ensureImage(image, true, IsolationManager.providerBuildArgs(providerName));
74
+
75
+ const manager = new IsolationManager({ image });
75
76
 
76
77
  const isolationClusterId = `${cluster.id}-validators`;
77
78
  const containerId = await manager.createContainer(isolationClusterId, {
@@ -41,11 +41,11 @@ function buildQualityGateSchema() {
41
41
  required: ['command', 'exitCode', 'output'],
42
42
  },
43
43
  completedAt: {
44
- type: ['string', 'number'],
44
+ anyOf: [{ type: 'string' }, { type: 'number' }],
45
45
  description: 'When the gate completed, as an ISO string or numeric timestamp.',
46
46
  },
47
47
  timestamp: {
48
- type: ['string', 'number'],
48
+ anyOf: [{ type: 'string' }, { type: 'number' }],
49
49
  description: 'Alternate completion timestamp, as an ISO string or numeric timestamp.',
50
50
  },
51
51
  stale: {
@@ -14,12 +14,15 @@ const { spawn, spawnSync } = require('child_process');
14
14
  const path = require('path');
15
15
  const fs = require('fs');
16
16
  const os = require('os');
17
- const { parseProviderChunk } = require('../providers');
17
+ const { parseProviderChunk, getProvider } = require('../providers');
18
18
  const { getTask } = require('../../task-lib/store.js');
19
19
  const { loadSettings } = require('../../lib/settings.js');
20
20
  const { resolveClaudeAuth } = require('../../lib/settings/claude-auth.js');
21
21
  const { prependWorktreeToolBinToEnv } = require('../worktree-tooling-env.js');
22
- const { prepareClaudeConfigDir } = require('../worktree-claude-config.js');
22
+ const {
23
+ prepareClaudeConfigDir,
24
+ resolveRepoMcpConfigPath,
25
+ } = require('../worktree-claude-config.js');
23
26
  const { buildRawLogOnlyMetadata } = require('./context-replay-policy');
24
27
 
25
28
  function runCommandWithTimeout(command, args, options = {}, callback = null) {
@@ -733,9 +736,45 @@ function buildTaskRunArgs({ agent, providerName, modelSpec, runOutputFormat }) {
733
736
  args.push('--json-schema', schema);
734
737
  }
735
738
 
739
+ // MCP servers: providers whose CLI accepts an MCP config flag (e.g. Copilot's
740
+ // --additional-mcp-config) cannot use the Claude config-dir overlay, so forward the repo's
741
+ // `.mcp.json` (the same MCP source Claude consumes) inline via `--mcp-config`.
742
+ for (const mcpArg of resolveMcpConfigArgs(agent, providerName)) {
743
+ args.push(mcpArg);
744
+ }
745
+
736
746
  return args;
737
747
  }
738
748
 
749
+ /**
750
+ * Build the `--mcp-config` args for a task-run invocation, or [] when they don't apply.
751
+ *
752
+ * Only providers whose adapter models an MCP config CLI flag receive it — Claude consumes MCP via
753
+ * the config-dir `.mcp.json` overlay (see prepareClaudeConfigDir) and needs no flag. The repo
754
+ * `.mcp.json` content is inlined (not passed as an @<path> reference) so the identical value works
755
+ * under local, worktree, and Docker isolation without host/container path translation.
756
+ */
757
+ function resolveMcpConfigArgs(agent, providerName) {
758
+ if (!providerModelsMcpConfigFlag(providerName)) return [];
759
+
760
+ const mcpPath = resolveRepoMcpConfigPath({
761
+ cwd: agent.config?.cwd || process.cwd(),
762
+ worktreePath: agent.worktree?.path || null,
763
+ });
764
+ if (!mcpPath) return [];
765
+
766
+ const content = fs.readFileSync(mcpPath, 'utf8').trim();
767
+ if (content.length === 0) return [];
768
+
769
+ return ['--mcp-config', content];
770
+ }
771
+
772
+ /** True when the provider's adapter models an MCP config CLI flag (currently only Copilot). */
773
+ function providerModelsMcpConfigFlag(providerName) {
774
+ const adapter = getProvider(providerName).adapter;
775
+ return 'supportsMcpConfig' in adapter.detectCliFeatures('');
776
+ }
777
+
739
778
  function maybeLogStreamJsonNotice(agent, runOutputFormat) {
740
779
  if (agent.config.jsonSchema && runOutputFormat !== 'json' && !agent.quiet) {
741
780
  agent._log(