@the-open-engine/zeroshot 6.39.1 → 6.40.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/README.md +45 -1
- package/cli/agent-provider-boundary.js +19 -0
- package/cli/agent-provider-boundary.ts +79 -0
- package/cli/export-stream.js +94 -0
- package/cli/export-stream.ts +118 -0
- package/cli/index.js +132 -61
- package/cli/json-export.js +2 -38
- package/cli/json-export.ts +2 -56
- package/cli/semantic-canonical.js +60 -0
- package/cli/semantic-canonical.ts +59 -0
- package/cli/semantic-contract.js +97 -0
- package/cli/semantic-contract.ts +153 -0
- package/cli/semantic-events.js +53 -0
- package/cli/semantic-events.ts +65 -0
- package/cli/semantic-evidence.js +63 -0
- package/cli/semantic-evidence.ts +65 -0
- package/cli/semantic-export.js +186 -0
- package/cli/semantic-export.ts +245 -0
- package/cli/semantic-json.js +69 -0
- package/cli/semantic-json.ts +69 -0
- package/cli/semantic-line-scanner.js +56 -0
- package/cli/semantic-line-scanner.ts +54 -0
- package/cli/semantic-parser.js +78 -0
- package/cli/semantic-parser.ts +95 -0
- package/cli/semantic-provider-line.js +91 -0
- package/cli/semantic-provider-line.ts +106 -0
- package/cli/trace-evidence.js +86 -0
- package/cli/trace-evidence.ts +115 -0
- package/cli/trace-export.js +120 -0
- package/cli/trace-export.ts +164 -0
- package/cli/trace-output-record.js +15 -0
- package/cli/trace-output-record.ts +18 -0
- package/cli/trace-output.js +98 -0
- package/cli/trace-output.ts +119 -0
- package/cluster-templates/conductor-bootstrap.json +1 -1
- package/lib/agent-cli-provider/adapters/claude.js +1 -1
- package/lib/agent-cli-provider/adapters/claude.js.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +26 -1
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/agent-cli-provider/adapters/omp.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/omp.js +1 -1
- package/lib/agent-cli-provider/adapters/omp.js.map +1 -1
- package/lib/agent-cli-provider/log-prefix.d.ts.map +1 -1
- package/lib/agent-cli-provider/log-prefix.js +8 -6
- package/lib/agent-cli-provider/log-prefix.js.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-environment-policy.d.ts +4 -0
- package/lib/agent-cli-provider/omp/sdk-environment-policy.d.ts.map +1 -0
- package/lib/agent-cli-provider/omp/sdk-environment-policy.js +37 -0
- package/lib/agent-cli-provider/omp/sdk-environment-policy.js.map +1 -0
- package/lib/agent-cli-provider/omp/sdk-process-private-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-process-private-runtime.js +4 -15
- package/lib/agent-cli-provider/omp/sdk-process-private-runtime.js.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-process-result.d.ts +2 -0
- package/lib/agent-cli-provider/omp/sdk-process-result.d.ts.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-process-result.js +2 -2
- package/lib/agent-cli-provider/omp/sdk-process-result.js.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-process-runner.d.ts.map +1 -1
- package/lib/agent-cli-provider/omp/sdk-process-runner.js +6 -1
- package/lib/agent-cli-provider/omp/sdk-process-runner.js.map +1 -1
- package/lib/agent-cli-provider/provider-registry.d.ts +3 -1
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +9 -2
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts +4 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +18 -15
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +3 -0
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/lib/cluster-worker/engine-adapter-common.js +18 -0
- package/lib/cluster-worker/engine-adapter.js +2 -1
- package/lib/git-remote-utils.js +22 -5
- package/lib/stream-json-parser.js +6 -5
- package/npm-shrinkwrap.json +2 -2
- package/package.json +7 -4
- package/scripts/omp/runtime-identities.js +31 -18
- package/scripts/omp/runtime.js +2 -0
- package/src/agent/agent-lifecycle.js +17 -1
- package/src/agent/agent-task-executor.js +36 -40
- package/src/agent/output-extraction-failures.js +73 -0
- package/src/agent/output-extraction-failures.ts +100 -0
- package/src/agent/output-extraction-json.js +5 -6
- package/src/agent/output-extraction-json.ts +4 -7
- package/src/agent/output-extraction-types.ts +5 -0
- package/src/agent/provider-terminal-failure.js +2 -2
- package/src/agent/provider-terminal-failure.ts +4 -2
- package/src/agent-cli-provider/adapters/claude.ts +1 -1
- package/src/agent-cli-provider/adapters/codex.ts +36 -1
- package/src/agent-cli-provider/adapters/omp.ts +2 -1
- package/src/agent-cli-provider/log-prefix.ts +12 -7
- package/src/agent-cli-provider/omp/sdk-environment-policy.ts +38 -0
- package/src/agent-cli-provider/omp/sdk-process-private-runtime.ts +4 -16
- package/src/agent-cli-provider/omp/sdk-process-result.ts +4 -2
- package/src/agent-cli-provider/omp/sdk-process-runner.ts +7 -1
- package/src/agent-cli-provider/provider-registry.ts +10 -2
- package/src/agent-cli-provider/single-agent-runtime.ts +24 -15
- package/src/agent-cli-provider/types.ts +3 -0
- package/src/agents/git-pusher-template.js +61 -71
- package/src/attach/socket-paths.js +1 -1
- package/src/attach/socket-paths.ts +1 -1
- package/src/claude-task-runner.js +9 -9
- package/src/foreground-benchmark-run.js +24 -12
- package/src/legacy-lib/git-remote-utils.ts +23 -5
- package/src/legacy-lib/stream-json-parser.ts +10 -5
- package/src/omp-session-verifier.js +8 -5
- package/src/orchestrator.js +42 -0
- package/src/preflight.js +31 -6
- package/src/providers/index.js +4 -22
- package/src/task-execution-context.js +22 -0
- package/src/task-log-line.d.ts +20 -0
- package/src/task-log-line.js +80 -0
- package/src/watcher-prompt-channel.js +1 -1
- package/src/worktree-claude-config.js +25 -0
- package/src/worktree-claude-config.ts +28 -0
- package/task-lib/commands/logs.js +5 -8
- package/task-lib/runner.js +57 -22
- package/task-lib/sdk-watcher-output.js +31 -0
- package/task-lib/sdk-watcher.js +4 -11
- package/task-lib/watcher-output-runtime.js +30 -16
|
@@ -11,6 +11,7 @@ const TaskRunner = require('./task-runner');
|
|
|
11
11
|
const { loadSettings } = require('../lib/settings');
|
|
12
12
|
const { normalizeProviderName, getDefaultProviderId } = require('../lib/provider-names');
|
|
13
13
|
const { getProvider } = require('./providers');
|
|
14
|
+
const { decodeTaskLogLine } = require('./task-log-line');
|
|
14
15
|
const { prependWorktreeToolBinToEnv } = require('./worktree-tooling-env');
|
|
15
16
|
const { applyDarwinKeychainBoundaryToEnv } = require('./darwin-keychain-boundary');
|
|
16
17
|
const { getTask, getTaskBySpawnOwnershipToken } = require('../task-lib/store.js');
|
|
@@ -556,18 +557,17 @@ class ClaudeTaskRunner extends TaskRunner {
|
|
|
556
557
|
const broadcastLine = (line) => {
|
|
557
558
|
if (!line.trim()) return;
|
|
558
559
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
content = timestampMatch[2];
|
|
563
|
-
}
|
|
560
|
+
const decoded = decodeTaskLogLine(line);
|
|
561
|
+
if (!decoded.providerOutput) return;
|
|
562
|
+
const content = decoded.content;
|
|
564
563
|
|
|
565
564
|
// Skip non-JSON patterns
|
|
566
565
|
if (
|
|
567
|
-
|
|
568
|
-
content.startsWith('
|
|
569
|
-
|
|
570
|
-
|
|
566
|
+
decoded.channel !== 'provider_stdout' &&
|
|
567
|
+
(content.startsWith('===') ||
|
|
568
|
+
content.startsWith('Finished:') ||
|
|
569
|
+
content.startsWith('Exit code:') ||
|
|
570
|
+
(content.includes('"type":"system"') && content.includes('"subtype":"init"')))
|
|
571
571
|
) {
|
|
572
572
|
return;
|
|
573
573
|
}
|
|
@@ -4,6 +4,7 @@ const { writeBenchmarkResultBundle } = require('./foreground-benchmark-files');
|
|
|
4
4
|
const TERMINAL_TOPICS = ['CLUSTER_COMPLETE', 'CLUSTER_FAILED'];
|
|
5
5
|
const VERIFIER_ELIGIBLE = new Set(['completed', 'task_failure']);
|
|
6
6
|
const EXIT_CODES = Object.freeze({
|
|
7
|
+
task_failure: 23,
|
|
7
8
|
provider_failure: 20,
|
|
8
9
|
engine_failure: 21,
|
|
9
10
|
cancelled: 22,
|
|
@@ -40,20 +41,24 @@ function requireSettledStatus(orchestrator, clusterId) {
|
|
|
40
41
|
return status;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
function
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
44
|
+
function buildForegroundResult({ orchestrator, cluster, clusterId, cancelled }) {
|
|
45
|
+
const finalRun = orchestrator.getFinalRun?.(clusterId);
|
|
46
|
+
const status = finalRun?.status || requireSettledStatus(orchestrator, clusterId);
|
|
47
|
+
const terminals = finalRun?.terminalMessages || terminalMessages(cluster, clusterId);
|
|
47
48
|
if (cancelled && terminals.length === 0) {
|
|
48
|
-
|
|
49
|
-
} else {
|
|
50
|
-
result = buildBenchmarkResult({
|
|
51
|
-
runId: clusterId,
|
|
52
|
-
terminalMessages: terminals,
|
|
53
|
-
agents: status.agents,
|
|
54
|
-
});
|
|
49
|
+
return buildCancelledResult({ runId: clusterId, agents: status.agents });
|
|
55
50
|
}
|
|
56
|
-
|
|
51
|
+
return buildBenchmarkResult({
|
|
52
|
+
runId: clusterId,
|
|
53
|
+
terminalMessages: terminals,
|
|
54
|
+
agents: status.agents,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function writeForegroundResult({ orchestrator, cluster, clusterId, resultPath, cancelled }) {
|
|
59
|
+
const result = buildForegroundResult({ orchestrator, cluster, clusterId, cancelled });
|
|
60
|
+
const snapshot =
|
|
61
|
+
orchestrator.getFinalRun?.(clusterId)?.snapshot || cluster.messageBus.readSnapshot(clusterId);
|
|
57
62
|
return writeBenchmarkResultBundle(resultPath, result, snapshot);
|
|
58
63
|
}
|
|
59
64
|
|
|
@@ -64,8 +69,15 @@ function exitCodeForResult(result) {
|
|
|
64
69
|
return exitCode;
|
|
65
70
|
}
|
|
66
71
|
|
|
72
|
+
function exitCodeForForegroundResult(result) {
|
|
73
|
+
if (result.outcome === 'completed') return 0;
|
|
74
|
+
return EXIT_CODES[result.outcome] ?? exitCodeForResult(result);
|
|
75
|
+
}
|
|
76
|
+
|
|
67
77
|
module.exports = {
|
|
78
|
+
buildForegroundResult,
|
|
68
79
|
exitCodeForResult,
|
|
80
|
+
exitCodeForForegroundResult,
|
|
69
81
|
isForegroundStatusSettled,
|
|
70
82
|
terminalMessages,
|
|
71
83
|
writeForegroundResult,
|
|
@@ -181,6 +181,25 @@ function parseGitRemoteUrl(remoteUrl: unknown): GitContext | null {
|
|
|
181
181
|
return null;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
function parseFetchRemoteLine(line: string): readonly [string, string] | null {
|
|
185
|
+
// Partial clones append their filter after the fetch marker, for example
|
|
186
|
+
// `origin https://github.com/org/repo.git (fetch) [blob:none]`.
|
|
187
|
+
const fetchMarker = ' (fetch)';
|
|
188
|
+
const fetchMarkerIndex = line.lastIndexOf(fetchMarker);
|
|
189
|
+
if (fetchMarkerIndex < 1) return null;
|
|
190
|
+
const suffix = line.slice(fetchMarkerIndex + fetchMarker.length);
|
|
191
|
+
if (suffix.length > 0 && !(suffix.startsWith(' [') && suffix.endsWith(']'))) return null;
|
|
192
|
+
|
|
193
|
+
const remoteLine = line.slice(0, fetchMarkerIndex);
|
|
194
|
+
const separatorIndex = remoteLine.search(/\s/);
|
|
195
|
+
if (separatorIndex < 1) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const remoteUrl = remoteLine.slice(separatorIndex).trim();
|
|
200
|
+
return remoteUrl.length > 0 ? [remoteLine.slice(0, separatorIndex), remoteUrl] : null;
|
|
201
|
+
}
|
|
202
|
+
|
|
184
203
|
/**
|
|
185
204
|
* Detect git repository context from current working directory.
|
|
186
205
|
* Returns provider context extracted from git remote URL.
|
|
@@ -205,15 +224,14 @@ function detectGitContext(cwd = process.cwd()): GitContextWithRemote | null {
|
|
|
205
224
|
|
|
206
225
|
const supportedRemotes = new Map<string, GitContextWithRemote>();
|
|
207
226
|
for (const line of remoteOutput.split(/\r?\n/)) {
|
|
208
|
-
const
|
|
209
|
-
if (!
|
|
227
|
+
const remoteParts = parseFetchRemoteLine(line);
|
|
228
|
+
if (!remoteParts) {
|
|
210
229
|
continue;
|
|
211
230
|
}
|
|
212
231
|
|
|
213
|
-
const remoteCandidate =
|
|
214
|
-
const remoteUrl = match[2];
|
|
232
|
+
const [remoteCandidate, remoteUrl] = remoteParts;
|
|
215
233
|
const remote = normalizeGitRemoteName(remoteCandidate);
|
|
216
|
-
if (!remote
|
|
234
|
+
if (!remote) {
|
|
217
235
|
continue;
|
|
218
236
|
}
|
|
219
237
|
const context = parseGitRemoteUrl(remoteUrl);
|
|
@@ -16,9 +16,16 @@ interface ProviderFacade {
|
|
|
16
16
|
listProviders(): string[];
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
interface TaskLogDecoder {
|
|
20
|
+
decodeTaskLogLine(line: string): { content: string; providerOutput: boolean };
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
// The runtime facade is maintained JavaScript; keep its original emitted require path.
|
|
20
24
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
21
25
|
const { getProvider, listProviders }: ProviderFacade = require('../src/providers');
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
27
|
+
const taskLogDecoder: TaskLogDecoder = require('../src/task-log-line');
|
|
28
|
+
const { decodeTaskLogLine } = taskLogDecoder;
|
|
22
29
|
|
|
23
30
|
function createProviderParsers(): ProviderParser[] {
|
|
24
31
|
return listProviders().map((name) => getProvider(name));
|
|
@@ -26,11 +33,9 @@ function createProviderParsers(): ProviderParser[] {
|
|
|
26
33
|
|
|
27
34
|
function stripTimestampPrefix(line: unknown): string {
|
|
28
35
|
if (!line || typeof line !== 'string') return '';
|
|
29
|
-
|
|
30
|
-
if (!
|
|
31
|
-
|
|
32
|
-
const tsMatch = /^\[(\d{13})\](.*)$/.exec(trimmed);
|
|
33
|
-
if (tsMatch) trimmed = (tsMatch[2] || '').trimStart();
|
|
36
|
+
const decoded = decodeTaskLogLine(line.trim());
|
|
37
|
+
if (!decoded.providerOutput) return '';
|
|
38
|
+
const trimmed = decoded.content;
|
|
34
39
|
|
|
35
40
|
if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {
|
|
36
41
|
const pipeMatch = /^[^|]{1,40}\|\s*(.*)$/.exec(trimmed);
|
|
@@ -399,7 +399,10 @@ function streamSessionJsonl(fd, describePath, limits) {
|
|
|
399
399
|
`${describePath} record ${records} is not valid JSON: ${error.message}`
|
|
400
400
|
);
|
|
401
401
|
}
|
|
402
|
-
|
|
402
|
+
// OMP 17.2.1 may reserve its first JSONL record for mutable title metadata before writing the
|
|
403
|
+
// authoritative session record. Accept exactly that one known preamble position; every other
|
|
404
|
+
// ordering still fails closed in parseSessionHeader below.
|
|
405
|
+
if (header === null && !(records === 1 && parsed?.type === 'title')) header = parsed;
|
|
403
406
|
collectCanonicalBlobRefs(parsed, blobRefs, limits);
|
|
404
407
|
}
|
|
405
408
|
|
|
@@ -430,9 +433,9 @@ function streamSessionJsonl(fd, describePath, limits) {
|
|
|
430
433
|
};
|
|
431
434
|
}
|
|
432
435
|
|
|
433
|
-
/** The session
|
|
434
|
-
*
|
|
435
|
-
* to disk and its `cwd` is the workspace the session belongs to. */
|
|
436
|
+
/** The session header (`{type:"session", version, id, cwd, ...}`) is either the first record or
|
|
437
|
+
* follows OMP 17.2.1's single leading title-metadata record. Its `id` is the authoritative session
|
|
438
|
+
* identity written to disk and its `cwd` is the workspace the session belongs to. */
|
|
436
439
|
function parseSessionHeader(header, describePath) {
|
|
437
440
|
if (!header || typeof header !== 'object' || Array.isArray(header)) {
|
|
438
441
|
fail('session-header-missing', `${describePath} has no session header record.`);
|
|
@@ -440,7 +443,7 @@ function parseSessionHeader(header, describePath) {
|
|
|
440
443
|
if (header.type !== 'session') {
|
|
441
444
|
fail(
|
|
442
445
|
'session-header-invalid',
|
|
443
|
-
`${describePath} first record is type ${JSON.stringify(header.type)}, not "session".`
|
|
446
|
+
`${describePath} first non-title-preamble record is type ${JSON.stringify(header.type)}, not "session".`
|
|
444
447
|
);
|
|
445
448
|
}
|
|
446
449
|
if (typeof header.id !== 'string' || header.id.length === 0) {
|
package/src/orchestrator.js
CHANGED
|
@@ -307,6 +307,9 @@ class Orchestrator {
|
|
|
307
307
|
this.closed = false;
|
|
308
308
|
this._conductorWatchdogs = new Set();
|
|
309
309
|
this._clusterRunBoundaries = new Map();
|
|
310
|
+
// Successful auto-cleanup closes and removes the live cluster before a foreground caller can
|
|
311
|
+
// build its authoritative result. Preserve only the bounded terminal handoff for this process.
|
|
312
|
+
this._finalRuns = new Map();
|
|
310
313
|
|
|
311
314
|
// Track if clusters are loaded (for lazy loading pattern)
|
|
312
315
|
this._clustersLoaded = options.skipLoad === true;
|
|
@@ -2430,6 +2433,7 @@ class Orchestrator {
|
|
|
2430
2433
|
cluster.pid = null; // Clear PID - cluster is no longer running
|
|
2431
2434
|
|
|
2432
2435
|
if (shouldAutoCleanWorktree) {
|
|
2436
|
+
this._captureFinalRun(clusterId, cluster);
|
|
2433
2437
|
// Close message bus and ledger (same as kill() path)
|
|
2434
2438
|
cluster.messageBus.close();
|
|
2435
2439
|
}
|
|
@@ -2562,6 +2566,35 @@ class Orchestrator {
|
|
|
2562
2566
|
cluster.messageBus.close();
|
|
2563
2567
|
}
|
|
2564
2568
|
}
|
|
2569
|
+
this._finalRuns.clear();
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
_captureFinalRun(clusterId, cluster) {
|
|
2573
|
+
const snapshot = cluster.messageBus.readSnapshot(clusterId);
|
|
2574
|
+
const terminalMessages = ['CLUSTER_COMPLETE', 'CLUSTER_FAILED']
|
|
2575
|
+
.flatMap((topic) => cluster.messageBus.query({ cluster_id: clusterId, topic }))
|
|
2576
|
+
.sort((left, right) => {
|
|
2577
|
+
const a = BigInt(left.sequence);
|
|
2578
|
+
const b = BigInt(right.sequence);
|
|
2579
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
2580
|
+
});
|
|
2581
|
+
this._finalRuns.set(clusterId, {
|
|
2582
|
+
snapshot,
|
|
2583
|
+
terminalMessages,
|
|
2584
|
+
status: {
|
|
2585
|
+
id: clusterId,
|
|
2586
|
+
state: cluster.state,
|
|
2587
|
+
isZombie: false,
|
|
2588
|
+
pid: null,
|
|
2589
|
+
createdAt: cluster.createdAt,
|
|
2590
|
+
agents: cluster.agents.map((agent) => agent.getState()),
|
|
2591
|
+
messageCount: snapshot.messageCount,
|
|
2592
|
+
setupLogPath: cluster.setupLogPath || null,
|
|
2593
|
+
setupStage: cluster.setupStage || null,
|
|
2594
|
+
failureInfo: cluster.failureInfo || null,
|
|
2595
|
+
runMode: cluster.runMode || null,
|
|
2596
|
+
},
|
|
2597
|
+
});
|
|
2565
2598
|
}
|
|
2566
2599
|
|
|
2567
2600
|
/**
|
|
@@ -4300,11 +4333,16 @@ Continue from where you left off. Review your previous output to understand what
|
|
|
4300
4333
|
* @returns {Object} Cluster status
|
|
4301
4334
|
*/
|
|
4302
4335
|
getStatus(clusterId) {
|
|
4336
|
+
const finalRun = this._finalRuns.get(clusterId);
|
|
4337
|
+
if (finalRun) return finalRun.status;
|
|
4303
4338
|
const cluster = this.clusters.get(clusterId);
|
|
4304
4339
|
if (!cluster) {
|
|
4305
4340
|
throw new Error(`Cluster ${clusterId} not found`);
|
|
4306
4341
|
}
|
|
4342
|
+
return this._getLiveStatus(clusterId, cluster);
|
|
4343
|
+
}
|
|
4307
4344
|
|
|
4345
|
+
_getLiveStatus(clusterId, cluster) {
|
|
4308
4346
|
// Detect zombie clusters: state=running but no backing process
|
|
4309
4347
|
let state = cluster.state;
|
|
4310
4348
|
let isZombie = false;
|
|
@@ -4388,6 +4426,10 @@ Continue from where you left off. Review your previous output to understand what
|
|
|
4388
4426
|
return this.clusters.get(clusterId);
|
|
4389
4427
|
}
|
|
4390
4428
|
|
|
4429
|
+
getFinalRun(clusterId) {
|
|
4430
|
+
return this._finalRuns.get(clusterId) || null;
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4391
4433
|
/**
|
|
4392
4434
|
* Export cluster conversation
|
|
4393
4435
|
* @param {String} clusterId - Cluster ID
|
package/src/preflight.js
CHANGED
|
@@ -29,6 +29,10 @@ const {
|
|
|
29
29
|
} = require('../lib/provider-names');
|
|
30
30
|
const { detectGitContext } = require('../lib/git-remote-utils');
|
|
31
31
|
const { readKeychainCredentials } = require('./claude-credentials');
|
|
32
|
+
const {
|
|
33
|
+
TASK_EXECUTION_CONTEXT_ENV,
|
|
34
|
+
resolveTaskExecutionContext,
|
|
35
|
+
} = require('./task-execution-context');
|
|
32
36
|
|
|
33
37
|
/**
|
|
34
38
|
* Validation result
|
|
@@ -164,6 +168,13 @@ function checkClaudeAuth() {
|
|
|
164
168
|
...(method && { method }),
|
|
165
169
|
});
|
|
166
170
|
|
|
171
|
+
// Claude Code supports gateway bearer authentication independently of Anthropic API keys.
|
|
172
|
+
// This is the documented path for OpenRouter and enterprise LLM gateways, where
|
|
173
|
+
// ANTHROPIC_API_KEY is intentionally present but empty to avoid credential conflicts.
|
|
174
|
+
if (process.env.ANTHROPIC_AUTH_TOKEN) {
|
|
175
|
+
return authResult(true, null, 'auth_token');
|
|
176
|
+
}
|
|
177
|
+
|
|
167
178
|
// Check for Bedrock bearer token (highest priority)
|
|
168
179
|
if (process.env.AWS_BEARER_TOKEN_BEDROCK) {
|
|
169
180
|
if (!process.env.AWS_REGION) {
|
|
@@ -481,7 +492,7 @@ function validateProviderExecutionSettings(providerName, settings, options) {
|
|
|
481
492
|
return [];
|
|
482
493
|
}
|
|
483
494
|
|
|
484
|
-
const executionContext = options
|
|
495
|
+
const executionContext = resolvePreflightExecutionContext(options);
|
|
485
496
|
const error = metadata.settingsValidator(providerSettings, { executionContext });
|
|
486
497
|
if (!error) return [];
|
|
487
498
|
|
|
@@ -519,11 +530,20 @@ function validateProvider(providerName, options) {
|
|
|
519
530
|
return { errors: [...isolationErrors, ...result.errors], warnings: result.warnings };
|
|
520
531
|
}
|
|
521
532
|
|
|
522
|
-
const result = validateRegistryCliProvider(
|
|
533
|
+
const result = validateRegistryCliProvider(
|
|
534
|
+
providerName,
|
|
535
|
+
resolvePreflightExecutionContext(options)
|
|
536
|
+
);
|
|
523
537
|
return { errors: [...isolationErrors, ...result.errors], warnings: result.warnings };
|
|
524
538
|
}
|
|
525
539
|
|
|
526
|
-
function
|
|
540
|
+
function resolvePreflightExecutionContext(options) {
|
|
541
|
+
const executionContext =
|
|
542
|
+
options.executionContext ?? (options.requireDocker ? 'docker' : 'detached');
|
|
543
|
+
return resolveTaskExecutionContext({ [TASK_EXECUTION_CONTEXT_ENV]: executionContext });
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function validateRegistryCliProvider(providerName, executionContext) {
|
|
527
547
|
const metadata = getProviderMetadata(providerName);
|
|
528
548
|
const { command } = resolveProviderCommand(providerName);
|
|
529
549
|
const installSteps = metadata.installInstructions.split('\n').filter(Boolean);
|
|
@@ -537,7 +557,7 @@ function validateRegistryCliProvider(providerName) {
|
|
|
537
557
|
}
|
|
538
558
|
|
|
539
559
|
const { getProvider } = require('./providers');
|
|
540
|
-
if (getProvider(providerName).isAvailable()) {
|
|
560
|
+
if (getProvider(providerName).isAvailable({ executionContext })) {
|
|
541
561
|
return { errors: [], warnings: [] };
|
|
542
562
|
}
|
|
543
563
|
|
|
@@ -642,6 +662,7 @@ function validateGitRequirement() {
|
|
|
642
662
|
* @param {boolean} options.quiet - Suppress success messages
|
|
643
663
|
* @param {string} options.claudeCommand - Custom Claude command (from settings)
|
|
644
664
|
* @param {string} options.provider - Provider override
|
|
665
|
+
* @param {'host'|'detached'|'docker'|'benchmark'} options.executionContext - Provider execution boundary
|
|
645
666
|
* @returns {ValidationResult}
|
|
646
667
|
*/
|
|
647
668
|
async function runPreflight(options = {}) {
|
|
@@ -670,10 +691,14 @@ async function runPreflight(options = {}) {
|
|
|
670
691
|
const providerName = normalizeProviderName(
|
|
671
692
|
options.provider || settings.defaultProvider || getDefaultProviderId()
|
|
672
693
|
);
|
|
694
|
+
const providerOptions = {
|
|
695
|
+
...options,
|
|
696
|
+
executionContext: resolvePreflightExecutionContext(options),
|
|
697
|
+
};
|
|
673
698
|
|
|
674
|
-
const providerResult = validateProvider(providerName,
|
|
699
|
+
const providerResult = validateProvider(providerName, providerOptions);
|
|
675
700
|
errors.push(...providerResult.errors);
|
|
676
|
-
errors.push(...validateProviderExecutionSettings(providerName, settings,
|
|
701
|
+
errors.push(...validateProviderExecutionSettings(providerName, settings, providerOptions));
|
|
677
702
|
warnings.push(...providerResult.warnings);
|
|
678
703
|
|
|
679
704
|
// 4. Check issue provider CLI (if required)
|
package/src/providers/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
} = require('../../lib/provider-names');
|
|
7
7
|
const { commandExists, getCommandPath } = require('../../lib/provider-detection');
|
|
8
8
|
const helper = require('../../lib/agent-cli-provider');
|
|
9
|
+
const { stripTimestampPrefix } = require('../../lib/agent-cli-provider/log-prefix');
|
|
9
10
|
|
|
10
11
|
const warned = new Set();
|
|
11
12
|
|
|
@@ -32,13 +33,13 @@ class RuntimeProvider extends BaseProvider {
|
|
|
32
33
|
return this._adapter;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
isAvailable() {
|
|
36
|
+
isAvailable(context = {}) {
|
|
36
37
|
if (this._usesBundledRunner()) {
|
|
37
|
-
return helper.probeRuntimeProviderCli(this.name).available;
|
|
38
|
+
return helper.probeRuntimeProviderCli(this.name, undefined, undefined, context).available;
|
|
38
39
|
}
|
|
39
40
|
const { command } = resolveProviderCommand(this.name);
|
|
40
41
|
if (!commandExists(command)) return false;
|
|
41
|
-
return helper.probeRuntimeProviderCli(this.name).available;
|
|
42
|
+
return helper.probeRuntimeProviderCli(this.name, undefined, undefined, context).available;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
getCliPath() {
|
|
@@ -218,25 +219,6 @@ function listProviders() {
|
|
|
218
219
|
return helper.listProviderAdapters();
|
|
219
220
|
}
|
|
220
221
|
|
|
221
|
-
function stripTimestampPrefix(line) {
|
|
222
|
-
if (!line || typeof line !== 'string') return '';
|
|
223
|
-
let trimmed = line.trim().replace(/\r$/, '');
|
|
224
|
-
if (!trimmed) return '';
|
|
225
|
-
|
|
226
|
-
const tsMatch = trimmed.match(/^\[(\d{13})\](.*)$/);
|
|
227
|
-
if (tsMatch) trimmed = (tsMatch[2] || '').trimStart();
|
|
228
|
-
|
|
229
|
-
if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {
|
|
230
|
-
const pipeMatch = trimmed.match(/^[^|]{1,40}\|\s*(.*)$/);
|
|
231
|
-
if (pipeMatch) {
|
|
232
|
-
const afterPipe = (pipeMatch[1] || '').trimStart();
|
|
233
|
-
if (afterPipe.startsWith('{') || afterPipe.startsWith('[')) return afterPipe;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return trimmed;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
222
|
function collectEvents(events, event) {
|
|
241
223
|
if (!event) return;
|
|
242
224
|
if (Array.isArray(event)) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const TASK_EXECUTION_CONTEXT_ENV = 'ZEROSHOT_TASK_EXECUTION_CONTEXT';
|
|
4
|
+
const TASK_EXECUTION_CONTEXTS = Object.freeze(['host', 'detached', 'docker', 'benchmark']);
|
|
5
|
+
const taskExecutionContexts = new Set(TASK_EXECUTION_CONTEXTS);
|
|
6
|
+
|
|
7
|
+
function resolveTaskExecutionContext(environment = process.env) {
|
|
8
|
+
const context = environment[TASK_EXECUTION_CONTEXT_ENV];
|
|
9
|
+
if (context === undefined) return 'detached';
|
|
10
|
+
if (!taskExecutionContexts.has(context)) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
`${TASK_EXECUTION_CONTEXT_ENV} must be one of: ${TASK_EXECUTION_CONTEXTS.join(', ')}.`
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
return context;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
TASK_EXECUTION_CONTEXT_ENV,
|
|
20
|
+
TASK_EXECUTION_CONTEXTS,
|
|
21
|
+
resolveTaskExecutionContext,
|
|
22
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const TASK_LOG_V2_MARKER: '[ZEROSHOT][LOG_FORMAT] channel-framed-v2';
|
|
2
|
+
export const TASK_LOG_STDOUT_PREFIX: '[ZEROSHOT][PROVIDER_STDOUT] ';
|
|
3
|
+
export const TASK_LOG_STDERR_PREFIX: '[ZEROSHOT][PROVIDER_STDERR] ';
|
|
4
|
+
|
|
5
|
+
export type TaskLogChannel = 'provider_stdout' | 'provider_stderr' | 'control' | 'legacy';
|
|
6
|
+
export type TaskLogFormat = 'stderr-tagged-v1' | 'channel-framed-v2' | null;
|
|
7
|
+
|
|
8
|
+
export interface DecodedTaskLogLine {
|
|
9
|
+
readonly channel: TaskLogChannel;
|
|
10
|
+
readonly content: string;
|
|
11
|
+
readonly format: TaskLogFormat;
|
|
12
|
+
readonly providerOutput: boolean;
|
|
13
|
+
readonly timestamp: number | null;
|
|
14
|
+
readonly timestamped: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function decodeTaskLogLine(line: string): DecodedTaskLogLine;
|
|
18
|
+
export function formatTaskLogMarker(timestamp: number): string;
|
|
19
|
+
export function formatTaskLogStderr(timestamp: number, content: string): string;
|
|
20
|
+
export function formatTaskLogStdout(timestamp: number, content: string): string;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const TASK_LOG_V2_MARKER = '[ZEROSHOT][LOG_FORMAT] channel-framed-v2';
|
|
4
|
+
const TASK_LOG_STDOUT_PREFIX = '[ZEROSHOT][PROVIDER_STDOUT] ';
|
|
5
|
+
const TASK_LOG_STDERR_PREFIX = '[ZEROSHOT][PROVIDER_STDERR] ';
|
|
6
|
+
const TASK_LOG_V1_MARKER = '[ZEROSHOT][LOG_FORMAT] stderr-tagged-v1';
|
|
7
|
+
const CONTROL_LINE =
|
|
8
|
+
/^\[(?:ATTACH|CLEANUP|CRASH|DETACH|ERROR|OMP-OWNERSHIP|SDK-DIAGNOSTIC|SYSTEM)\](?:\s|$)/;
|
|
9
|
+
|
|
10
|
+
function timestampParts(line) {
|
|
11
|
+
const normalized = line.replace(/\r$/, '');
|
|
12
|
+
const match = /^\[(\d{13}|\d{4}-\d{2}-\d{2}T[^\]]+)\](.*)$/.exec(normalized);
|
|
13
|
+
if (!match) return { content: normalized, timestamp: null };
|
|
14
|
+
const rawTimestamp = match[1];
|
|
15
|
+
const epochTimestamp = /^\d{13}$/.test(rawTimestamp);
|
|
16
|
+
const timestamp = epochTimestamp
|
|
17
|
+
? Number.parseInt(rawTimestamp, 10)
|
|
18
|
+
: new Date(rawTimestamp).getTime();
|
|
19
|
+
return {
|
|
20
|
+
content: match[2].trimStart(),
|
|
21
|
+
timestamp: Number.isFinite(timestamp) ? timestamp : null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function decodeTaskLogLine(line) {
|
|
26
|
+
const { content, timestamp } = timestampParts(line);
|
|
27
|
+
const common = { timestamp, timestamped: timestamp !== null };
|
|
28
|
+
if (content.startsWith(TASK_LOG_STDOUT_PREFIX)) {
|
|
29
|
+
return {
|
|
30
|
+
...common,
|
|
31
|
+
channel: 'provider_stdout',
|
|
32
|
+
content: content.slice(TASK_LOG_STDOUT_PREFIX.length),
|
|
33
|
+
format: null,
|
|
34
|
+
providerOutput: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (content.startsWith(TASK_LOG_STDERR_PREFIX)) {
|
|
38
|
+
return {
|
|
39
|
+
...common,
|
|
40
|
+
channel: 'provider_stderr',
|
|
41
|
+
content: content.slice(TASK_LOG_STDERR_PREFIX.length),
|
|
42
|
+
format: null,
|
|
43
|
+
providerOutput: false,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
let format = null;
|
|
47
|
+
if (content === TASK_LOG_V2_MARKER) format = 'channel-framed-v2';
|
|
48
|
+
else if (content === TASK_LOG_V1_MARKER) format = 'stderr-tagged-v1';
|
|
49
|
+
const control =
|
|
50
|
+
format !== null || content.startsWith('[ZEROSHOT][FATAL] ') || CONTROL_LINE.test(content);
|
|
51
|
+
return {
|
|
52
|
+
...common,
|
|
53
|
+
channel: control ? 'control' : 'legacy',
|
|
54
|
+
content,
|
|
55
|
+
format,
|
|
56
|
+
providerOutput: !control,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function formatTaskLogMarker(timestamp) {
|
|
61
|
+
return `[${timestamp}]${TASK_LOG_V2_MARKER}\n`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function formatTaskLogStdout(timestamp, content) {
|
|
65
|
+
return `[${timestamp}]${TASK_LOG_STDOUT_PREFIX}${content}\n`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function formatTaskLogStderr(timestamp, content) {
|
|
69
|
+
return `[${timestamp}]${TASK_LOG_STDERR_PREFIX}${content}\n`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
TASK_LOG_STDERR_PREFIX,
|
|
74
|
+
TASK_LOG_STDOUT_PREFIX,
|
|
75
|
+
TASK_LOG_V2_MARKER,
|
|
76
|
+
decodeTaskLogLine,
|
|
77
|
+
formatTaskLogMarker,
|
|
78
|
+
formatTaskLogStderr,
|
|
79
|
+
formatTaskLogStdout,
|
|
80
|
+
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The OMP prompt is task content (repository text, instructions, whatever a caller pasted in), so
|
|
5
5
|
* it must never travel in the watcher's argv: `ps` and /proc/<pid>/cmdline expose argv to every
|
|
6
6
|
* local user for the entire lifetime of a long-lived watcher. task-lib/runner.js therefore hands
|
|
7
|
-
* the prompt to task-lib/rpc-watcher.js over
|
|
7
|
+
* the prompt to task-lib/rpc-watcher.js over its anonymous stdin pipe. The
|
|
8
8
|
* pipe has exactly two ends and no name, so nothing is written to the filesystem, the task log, or
|
|
9
9
|
* the ledger while the prompt is in transit.
|
|
10
10
|
*
|
|
@@ -12,6 +12,11 @@ const CLAUDE_SETTINGS_ENV = 'ZEROSHOT_CLAUDE_SETTINGS_FILE';
|
|
|
12
12
|
const CLAUDE_MCP_CONFIG_ENV = 'ZEROSHOT_CLAUDE_MCP_CONFIG_FILE';
|
|
13
13
|
const ASK_USER_HOOK = 'block-ask-user-question.py';
|
|
14
14
|
const DANGEROUS_GIT_HOOK = 'block-dangerous-git.py';
|
|
15
|
+
const ALTERNATE_BACKEND_ENV = [
|
|
16
|
+
'CLAUDE_CODE_USE_BEDROCK',
|
|
17
|
+
'CLAUDE_CODE_USE_VERTEX',
|
|
18
|
+
'CLAUDE_CODE_USE_FOUNDRY',
|
|
19
|
+
];
|
|
15
20
|
function isMutableRecord(value) {
|
|
16
21
|
return typeof value === 'object' && value !== null;
|
|
17
22
|
}
|
|
@@ -109,6 +114,25 @@ function ensureDangerousGitHook(targetClaudeDir) {
|
|
|
109
114
|
writeSettings(settingsPath, settings);
|
|
110
115
|
}
|
|
111
116
|
}
|
|
117
|
+
function ensureExplicitGatewayBackend(targetClaudeDir, environment) {
|
|
118
|
+
if (!environment.ANTHROPIC_AUTH_TOKEN?.trim() || !environment.ANTHROPIC_BASE_URL?.trim())
|
|
119
|
+
return;
|
|
120
|
+
const overlayDir = requireTargetClaudeDir(targetClaudeDir);
|
|
121
|
+
const settingsPath = path.join(overlayDir, SETTINGS_BASENAME);
|
|
122
|
+
const settings = readSettings(settingsPath);
|
|
123
|
+
const existingEnv = settings.env;
|
|
124
|
+
let env = {};
|
|
125
|
+
if (existingEnv !== undefined) {
|
|
126
|
+
if (!isMutableRecord(existingEnv)) {
|
|
127
|
+
throw new TypeError('Claude settings env must be an object.');
|
|
128
|
+
}
|
|
129
|
+
env = { ...existingEnv };
|
|
130
|
+
}
|
|
131
|
+
for (const name of ALTERNATE_BACKEND_ENV)
|
|
132
|
+
env[name] = '0';
|
|
133
|
+
settings.env = env;
|
|
134
|
+
writeSettings(settingsPath, settings);
|
|
135
|
+
}
|
|
112
136
|
function prepareClaudeSettingsOverlay(options = {}) {
|
|
113
137
|
const overlayDir = fs.mkdtempSync(path.join(os.tmpdir(), OVERLAY_PREFIX));
|
|
114
138
|
fs.chmodSync(overlayDir, 0o700);
|
|
@@ -116,6 +140,7 @@ function prepareClaudeSettingsOverlay(options = {}) {
|
|
|
116
140
|
ensureAskUserQuestionHook(overlayDir);
|
|
117
141
|
if (options.includeDangerousGit)
|
|
118
142
|
ensureDangerousGitHook(overlayDir);
|
|
143
|
+
ensureExplicitGatewayBackend(overlayDir, options.environment || process.env);
|
|
119
144
|
return path.join(overlayDir, SETTINGS_BASENAME);
|
|
120
145
|
}
|
|
121
146
|
catch (error) {
|
|
@@ -19,6 +19,7 @@ interface HookEntry {
|
|
|
19
19
|
|
|
20
20
|
interface OverlayOptions {
|
|
21
21
|
includeDangerousGit?: boolean;
|
|
22
|
+
environment?: NodeJS.ProcessEnv;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
interface WorktreeOptions {
|
|
@@ -34,6 +35,11 @@ const CLAUDE_SETTINGS_ENV = 'ZEROSHOT_CLAUDE_SETTINGS_FILE';
|
|
|
34
35
|
const CLAUDE_MCP_CONFIG_ENV = 'ZEROSHOT_CLAUDE_MCP_CONFIG_FILE';
|
|
35
36
|
const ASK_USER_HOOK = 'block-ask-user-question.py';
|
|
36
37
|
const DANGEROUS_GIT_HOOK = 'block-dangerous-git.py';
|
|
38
|
+
const ALTERNATE_BACKEND_ENV = [
|
|
39
|
+
'CLAUDE_CODE_USE_BEDROCK',
|
|
40
|
+
'CLAUDE_CODE_USE_VERTEX',
|
|
41
|
+
'CLAUDE_CODE_USE_FOUNDRY',
|
|
42
|
+
] as const;
|
|
37
43
|
|
|
38
44
|
function isMutableRecord(value: unknown): value is MutableRecord {
|
|
39
45
|
return typeof value === 'object' && value !== null;
|
|
@@ -155,12 +161,34 @@ function ensureDangerousGitHook(targetClaudeDir: unknown): void {
|
|
|
155
161
|
}
|
|
156
162
|
}
|
|
157
163
|
|
|
164
|
+
function ensureExplicitGatewayBackend(
|
|
165
|
+
targetClaudeDir: unknown,
|
|
166
|
+
environment: NodeJS.ProcessEnv
|
|
167
|
+
): void {
|
|
168
|
+
if (!environment.ANTHROPIC_AUTH_TOKEN?.trim() || !environment.ANTHROPIC_BASE_URL?.trim()) return;
|
|
169
|
+
const overlayDir = requireTargetClaudeDir(targetClaudeDir);
|
|
170
|
+
const settingsPath = path.join(overlayDir, SETTINGS_BASENAME);
|
|
171
|
+
const settings = readSettings(settingsPath);
|
|
172
|
+
const existingEnv = settings.env;
|
|
173
|
+
let env: MutableRecord = {};
|
|
174
|
+
if (existingEnv !== undefined) {
|
|
175
|
+
if (!isMutableRecord(existingEnv)) {
|
|
176
|
+
throw new TypeError('Claude settings env must be an object.');
|
|
177
|
+
}
|
|
178
|
+
env = { ...existingEnv };
|
|
179
|
+
}
|
|
180
|
+
for (const name of ALTERNATE_BACKEND_ENV) env[name] = '0';
|
|
181
|
+
settings.env = env;
|
|
182
|
+
writeSettings(settingsPath, settings);
|
|
183
|
+
}
|
|
184
|
+
|
|
158
185
|
function prepareClaudeSettingsOverlay(options: OverlayOptions = {}): string {
|
|
159
186
|
const overlayDir = fs.mkdtempSync(path.join(os.tmpdir(), OVERLAY_PREFIX));
|
|
160
187
|
fs.chmodSync(overlayDir, 0o700);
|
|
161
188
|
try {
|
|
162
189
|
ensureAskUserQuestionHook(overlayDir);
|
|
163
190
|
if (options.includeDangerousGit) ensureDangerousGitHook(overlayDir);
|
|
191
|
+
ensureExplicitGatewayBackend(overlayDir, options.environment || process.env);
|
|
164
192
|
return path.join(overlayDir, SETTINGS_BASENAME);
|
|
165
193
|
} catch (error) {
|
|
166
194
|
fs.rmSync(overlayDir, { recursive: true, force: true });
|