@evermore.work/adapter-utils 2026.509.0-canary.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/dist/billing.d.ts +2 -0
- package/dist/billing.d.ts.map +1 -0
- package/dist/billing.js +16 -0
- package/dist/billing.js.map +1 -0
- package/dist/billing.test.d.ts +2 -0
- package/dist/billing.test.d.ts.map +1 -0
- package/dist/billing.test.js +14 -0
- package/dist/billing.test.js.map +1 -0
- package/dist/command-managed-runtime.d.ts +45 -0
- package/dist/command-managed-runtime.d.ts.map +1 -0
- package/dist/command-managed-runtime.js +164 -0
- package/dist/command-managed-runtime.js.map +1 -0
- package/dist/command-managed-runtime.test.d.ts +2 -0
- package/dist/command-managed-runtime.test.d.ts.map +1 -0
- package/dist/command-managed-runtime.test.js +102 -0
- package/dist/command-managed-runtime.test.js.map +1 -0
- package/dist/command-redaction.d.ts +3 -0
- package/dist/command-redaction.d.ts.map +1 -0
- package/dist/command-redaction.js +17 -0
- package/dist/command-redaction.js.map +1 -0
- package/dist/execution-target-sandbox.test.d.ts +2 -0
- package/dist/execution-target-sandbox.test.d.ts.map +1 -0
- package/dist/execution-target-sandbox.test.js +392 -0
- package/dist/execution-target-sandbox.test.js.map +1 -0
- package/dist/execution-target.d.ts +150 -0
- package/dist/execution-target.d.ts.map +1 -0
- package/dist/execution-target.js +791 -0
- package/dist/execution-target.js.map +1 -0
- package/dist/execution-target.test.d.ts +2 -0
- package/dist/execution-target.test.d.ts.map +1 -0
- package/dist/execution-target.test.js +314 -0
- package/dist/execution-target.test.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/log-redaction.d.ts +9 -0
- package/dist/log-redaction.d.ts.map +1 -0
- package/dist/log-redaction.js +88 -0
- package/dist/log-redaction.js.map +1 -0
- package/dist/remote-execution-env.d.ts +2 -0
- package/dist/remote-execution-env.d.ts.map +1 -0
- package/dist/remote-execution-env.js +46 -0
- package/dist/remote-execution-env.js.map +1 -0
- package/dist/remote-managed-runtime.d.ts +31 -0
- package/dist/remote-managed-runtime.d.ts.map +1 -0
- package/dist/remote-managed-runtime.js +81 -0
- package/dist/remote-managed-runtime.js.map +1 -0
- package/dist/sandbox-callback-bridge.d.ts +132 -0
- package/dist/sandbox-callback-bridge.d.ts.map +1 -0
- package/dist/sandbox-callback-bridge.js +925 -0
- package/dist/sandbox-callback-bridge.js.map +1 -0
- package/dist/sandbox-callback-bridge.test.d.ts +2 -0
- package/dist/sandbox-callback-bridge.test.d.ts.map +1 -0
- package/dist/sandbox-callback-bridge.test.js +719 -0
- package/dist/sandbox-callback-bridge.test.js.map +1 -0
- package/dist/sandbox-managed-runtime.d.ts +54 -0
- package/dist/sandbox-managed-runtime.d.ts.map +1 -0
- package/dist/sandbox-managed-runtime.js +234 -0
- package/dist/sandbox-managed-runtime.js.map +1 -0
- package/dist/sandbox-managed-runtime.test.d.ts +2 -0
- package/dist/sandbox-managed-runtime.test.d.ts.map +1 -0
- package/dist/sandbox-managed-runtime.test.js +118 -0
- package/dist/sandbox-managed-runtime.test.js.map +1 -0
- package/dist/sandbox-shell.d.ts +2 -0
- package/dist/sandbox-shell.d.ts.map +1 -0
- package/dist/sandbox-shell.js +4 -0
- package/dist/sandbox-shell.js.map +1 -0
- package/dist/server-utils.d.ts +253 -0
- package/dist/server-utils.d.ts.map +1 -0
- package/dist/server-utils.js +1522 -0
- package/dist/server-utils.js.map +1 -0
- package/dist/server-utils.test.d.ts +2 -0
- package/dist/server-utils.test.d.ts.map +1 -0
- package/dist/server-utils.test.js +685 -0
- package/dist/server-utils.test.js.map +1 -0
- package/dist/session-compaction.d.ts +25 -0
- package/dist/session-compaction.d.ts.map +1 -0
- package/dist/session-compaction.js +154 -0
- package/dist/session-compaction.js.map +1 -0
- package/dist/ssh-fixture.test.d.ts +2 -0
- package/dist/ssh-fixture.test.d.ts.map +1 -0
- package/dist/ssh-fixture.test.js +214 -0
- package/dist/ssh-fixture.test.js.map +1 -0
- package/dist/ssh.d.ts +111 -0
- package/dist/ssh.d.ts.map +1 -0
- package/dist/ssh.js +1098 -0
- package/dist/ssh.js.map +1 -0
- package/dist/types.d.ts +465 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { prepareCommandManagedRuntime, } from "./command-managed-runtime.js";
|
|
3
|
+
import { buildRemoteExecutionSessionIdentity, prepareRemoteManagedRuntime, remoteExecutionSessionMatches, } from "./remote-managed-runtime.js";
|
|
4
|
+
import { createCommandManagedSandboxCallbackBridgeQueueClient, createSandboxCallbackBridgeAsset, createSandboxCallbackBridgeToken, DEFAULT_SANDBOX_CALLBACK_BRIDGE_MAX_BODY_BYTES, startSandboxCallbackBridgeServer, startSandboxCallbackBridgeWorker, } from "./sandbox-callback-bridge.js";
|
|
5
|
+
import { createSshCommandManagedRuntimeRunner, parseSshRemoteExecutionSpec, runSshCommand, shellQuote } from "./ssh.js";
|
|
6
|
+
import { ensureCommandResolvable, resolveCommandForLogs, runChildProcess, } from "./server-utils.js";
|
|
7
|
+
import { sanitizeRemoteExecutionEnv } from "./remote-execution-env.js";
|
|
8
|
+
import { preferredShellForSandbox } from "./sandbox-shell.js";
|
|
9
|
+
export { sanitizeRemoteExecutionEnv } from "./remote-execution-env.js";
|
|
10
|
+
function parseObject(value) {
|
|
11
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
12
|
+
? value
|
|
13
|
+
: {};
|
|
14
|
+
}
|
|
15
|
+
function readString(value) {
|
|
16
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
17
|
+
}
|
|
18
|
+
function readStringMeta(parsed, key) {
|
|
19
|
+
return readString(parsed[key]);
|
|
20
|
+
}
|
|
21
|
+
function resolveHostForUrl(rawHost) {
|
|
22
|
+
const host = rawHost.trim();
|
|
23
|
+
if (!host || host === "0.0.0.0" || host === "::")
|
|
24
|
+
return "localhost";
|
|
25
|
+
if (host.includes(":") && !host.startsWith("[") && !host.endsWith("]"))
|
|
26
|
+
return `[${host}]`;
|
|
27
|
+
return host;
|
|
28
|
+
}
|
|
29
|
+
function resolveDefaultEvermoreApiUrl() {
|
|
30
|
+
const runtimeHost = resolveHostForUrl(process.env.EVERMORE_LISTEN_HOST ?? process.env.HOST ?? "localhost");
|
|
31
|
+
// 3100 matches the default Evermore dev server port when the runtime does not provide one.
|
|
32
|
+
const runtimePort = process.env.EVERMORE_LISTEN_PORT ?? process.env.PORT ?? "3100";
|
|
33
|
+
return `http://${runtimeHost}:${runtimePort}`;
|
|
34
|
+
}
|
|
35
|
+
function isBridgeDebugEnabled(env) {
|
|
36
|
+
const value = env.EVERMORE_BRIDGE_DEBUG?.trim().toLowerCase();
|
|
37
|
+
return value === "1" || value === "true" || value === "yes";
|
|
38
|
+
}
|
|
39
|
+
function isAdapterExecutionTargetInstance(value) {
|
|
40
|
+
const parsed = parseObject(value);
|
|
41
|
+
if (parsed.kind === "local")
|
|
42
|
+
return true;
|
|
43
|
+
if (parsed.kind !== "remote")
|
|
44
|
+
return false;
|
|
45
|
+
if (parsed.transport === "ssh")
|
|
46
|
+
return parseSshRemoteExecutionSpec(parseObject(parsed.spec)) !== null;
|
|
47
|
+
if (parsed.transport !== "sandbox")
|
|
48
|
+
return false;
|
|
49
|
+
return readStringMeta(parsed, "remoteCwd") !== null;
|
|
50
|
+
}
|
|
51
|
+
export function adapterExecutionTargetToRemoteSpec(target) {
|
|
52
|
+
return target?.kind === "remote" && target.transport === "ssh" ? target.spec : null;
|
|
53
|
+
}
|
|
54
|
+
export function adapterExecutionTargetIsRemote(target) {
|
|
55
|
+
return target?.kind === "remote";
|
|
56
|
+
}
|
|
57
|
+
export function adapterExecutionTargetUsesManagedHome(target) {
|
|
58
|
+
return target?.kind === "remote" && target.transport === "sandbox";
|
|
59
|
+
}
|
|
60
|
+
export function adapterExecutionTargetRemoteCwd(target, localCwd) {
|
|
61
|
+
return target?.kind === "remote" ? target.remoteCwd : localCwd;
|
|
62
|
+
}
|
|
63
|
+
export function resolveAdapterExecutionTargetCwd(target, configuredCwd, localFallbackCwd) {
|
|
64
|
+
if (typeof configuredCwd === "string" && configuredCwd.trim().length > 0) {
|
|
65
|
+
return configuredCwd;
|
|
66
|
+
}
|
|
67
|
+
return adapterExecutionTargetRemoteCwd(target, localFallbackCwd);
|
|
68
|
+
}
|
|
69
|
+
export function adapterExecutionTargetUsesEvermoreBridge(target) {
|
|
70
|
+
return target?.kind === "remote";
|
|
71
|
+
}
|
|
72
|
+
export function describeAdapterExecutionTarget(target) {
|
|
73
|
+
if (!target || target.kind === "local")
|
|
74
|
+
return "local environment";
|
|
75
|
+
if (target.transport === "ssh") {
|
|
76
|
+
return `SSH environment ${target.spec.username}@${target.spec.host}:${target.spec.port}`;
|
|
77
|
+
}
|
|
78
|
+
return `sandbox environment${target.providerKey ? ` (${target.providerKey})` : ""}`;
|
|
79
|
+
}
|
|
80
|
+
function requireSandboxRunner(target) {
|
|
81
|
+
if (target.runner)
|
|
82
|
+
return target.runner;
|
|
83
|
+
throw new Error("Sandbox execution target is missing its provider runtime runner. Sandbox commands must execute through the environment runtime.");
|
|
84
|
+
}
|
|
85
|
+
function preferredSandboxShell(target) {
|
|
86
|
+
return preferredShellForSandbox(target.shellCommand);
|
|
87
|
+
}
|
|
88
|
+
function adapterExecutionTargetCommandRunner(target) {
|
|
89
|
+
if (target.transport === "ssh") {
|
|
90
|
+
return createSshCommandManagedRuntimeRunner({
|
|
91
|
+
spec: target.spec,
|
|
92
|
+
defaultCwd: target.remoteCwd,
|
|
93
|
+
maxBufferBytes: DEFAULT_SANDBOX_CALLBACK_BRIDGE_MAX_BODY_BYTES * 4,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return requireSandboxRunner(target);
|
|
97
|
+
}
|
|
98
|
+
function adapterExecutionTargetShellCommand(target) {
|
|
99
|
+
return target.transport === "ssh" ? "sh" : preferredSandboxShell(target);
|
|
100
|
+
}
|
|
101
|
+
function adapterExecutionTargetTimeoutMs(target) {
|
|
102
|
+
return target.transport === "sandbox" ? target.timeoutMs : undefined;
|
|
103
|
+
}
|
|
104
|
+
export async function ensureAdapterExecutionTargetCommandResolvable(command, target, cwd, env, options = {}) {
|
|
105
|
+
if (target?.kind === "remote" && target.transport === "sandbox") {
|
|
106
|
+
await ensureSandboxCommandResolvable(command, target, options.installCommand?.trim() || null);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
await ensureCommandResolvable(command, cwd, env, {
|
|
110
|
+
remoteExecution: adapterExecutionTargetToRemoteSpec(target),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async function probeSandboxCommandResolvable(command, target) {
|
|
114
|
+
const runner = requireSandboxRunner(target);
|
|
115
|
+
const probeScript = `command -v ${shellQuote(command)}`;
|
|
116
|
+
const result = await runner.execute({
|
|
117
|
+
command: "sh",
|
|
118
|
+
args: ["-c", probeScript],
|
|
119
|
+
cwd: target.remoteCwd,
|
|
120
|
+
timeoutMs: target.timeoutMs ?? 15_000,
|
|
121
|
+
});
|
|
122
|
+
return {
|
|
123
|
+
resolved: !result.timedOut && (result.exitCode ?? 1) === 0,
|
|
124
|
+
timedOut: result.timedOut,
|
|
125
|
+
stderr: result.stderr.trim(),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async function ensureSandboxCommandResolvable(command, target, installCommand) {
|
|
129
|
+
// Probe whether the binary is resolvable inside the sandbox. We previously
|
|
130
|
+
// short-circuited this for sandbox targets, which let the caller report a
|
|
131
|
+
// success message even when the CLI was missing from the image. Now we run
|
|
132
|
+
// a real `command -v` through the same runner the hello probe will use, so
|
|
133
|
+
// the first step honestly reflects whether the binary is on PATH. The
|
|
134
|
+
// sandbox provider is responsible for sourcing login profiles (e2b mirrors
|
|
135
|
+
// SSH's buildSshSpawnTarget) so this and the hello probe agree on PATH.
|
|
136
|
+
let probe = await probeSandboxCommandResolvable(command, target);
|
|
137
|
+
if (probe.resolved)
|
|
138
|
+
return;
|
|
139
|
+
if (probe.timedOut) {
|
|
140
|
+
throw new Error(`Timed out checking command "${command}" on sandbox target.`);
|
|
141
|
+
}
|
|
142
|
+
// If the caller supplied an install command, attempt the install once via
|
|
143
|
+
// the sandbox runner (which the sandbox provider wraps in a login shell)
|
|
144
|
+
// and re-probe before reporting failure. This lets fresh sandbox leases
|
|
145
|
+
// bring up the CLI before the resolvability gate, mirroring the test path.
|
|
146
|
+
let installFailureDetail = null;
|
|
147
|
+
if (installCommand) {
|
|
148
|
+
const runner = requireSandboxRunner(target);
|
|
149
|
+
try {
|
|
150
|
+
const installResult = await runner.execute({
|
|
151
|
+
command: "sh",
|
|
152
|
+
args: ["-lc", installCommand],
|
|
153
|
+
cwd: target.remoteCwd,
|
|
154
|
+
timeoutMs: target.timeoutMs ?? 300_000,
|
|
155
|
+
});
|
|
156
|
+
if (installResult.timedOut) {
|
|
157
|
+
installFailureDetail = `install command timed out: ${installCommand}`;
|
|
158
|
+
}
|
|
159
|
+
else if ((installResult.exitCode ?? 0) !== 0) {
|
|
160
|
+
const tail = (text) => text.split(/\r?\n/).filter((line) => line.trim().length > 0).slice(-2).join(" | ").slice(0, 240);
|
|
161
|
+
const reason = tail(installResult.stderr || installResult.stdout) || `exit ${installResult.exitCode ?? "?"}`;
|
|
162
|
+
installFailureDetail = `install command exited ${installResult.exitCode ?? "?"}: ${reason}`;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
installFailureDetail = `install command threw: ${err instanceof Error ? err.message : String(err)}`;
|
|
167
|
+
}
|
|
168
|
+
probe = await probeSandboxCommandResolvable(command, target);
|
|
169
|
+
if (probe.resolved)
|
|
170
|
+
return;
|
|
171
|
+
if (probe.timedOut) {
|
|
172
|
+
throw new Error(`Timed out checking command "${command}" on sandbox target.`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const probeStderr = probe.stderr.length > 0 ? ` probe stderr: ${probe.stderr}` : "";
|
|
176
|
+
const installDetail = installFailureDetail ? `; ${installFailureDetail}` : "";
|
|
177
|
+
throw new Error(`Command "${command}" is not installed or not on PATH in the sandbox environment${installDetail}.${probeStderr}`);
|
|
178
|
+
}
|
|
179
|
+
export async function resolveAdapterExecutionTargetCommandForLogs(command, target, cwd, env) {
|
|
180
|
+
if (target?.kind === "remote" && target.transport === "sandbox") {
|
|
181
|
+
return `sandbox://${target.providerKey ?? "provider"}/${target.leaseId ?? "lease"}/${target.remoteCwd} :: ${command}`;
|
|
182
|
+
}
|
|
183
|
+
return await resolveCommandForLogs(command, cwd, env, {
|
|
184
|
+
remoteExecution: adapterExecutionTargetToRemoteSpec(target),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
export async function runAdapterExecutionTargetProcess(runId, target, command, args, options) {
|
|
188
|
+
if (target?.kind === "remote" && target.transport === "sandbox") {
|
|
189
|
+
const runner = requireSandboxRunner(target);
|
|
190
|
+
const env = sanitizeRemoteExecutionEnv(options.env);
|
|
191
|
+
return await runner.execute({
|
|
192
|
+
command,
|
|
193
|
+
args,
|
|
194
|
+
cwd: target.remoteCwd,
|
|
195
|
+
env,
|
|
196
|
+
stdin: options.stdin,
|
|
197
|
+
timeoutMs: options.timeoutSec > 0 ? options.timeoutSec * 1000 : target.timeoutMs ?? undefined,
|
|
198
|
+
onLog: options.onLog,
|
|
199
|
+
onSpawn: options.onSpawn
|
|
200
|
+
? async (meta) => options.onSpawn?.({ ...meta, processGroupId: null })
|
|
201
|
+
: undefined,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const env = target?.kind === "remote" && target.transport === "ssh"
|
|
205
|
+
? sanitizeRemoteExecutionEnv(options.env)
|
|
206
|
+
: options.env;
|
|
207
|
+
return await runChildProcess(runId, command, args, {
|
|
208
|
+
cwd: options.cwd,
|
|
209
|
+
env,
|
|
210
|
+
stdin: options.stdin,
|
|
211
|
+
timeoutSec: options.timeoutSec,
|
|
212
|
+
graceSec: options.graceSec,
|
|
213
|
+
onLog: options.onLog,
|
|
214
|
+
onSpawn: options.onSpawn,
|
|
215
|
+
terminalResultCleanup: options.terminalResultCleanup,
|
|
216
|
+
remoteExecution: adapterExecutionTargetToRemoteSpec(target),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
export async function runAdapterExecutionTargetShellCommand(runId, target, command, options) {
|
|
220
|
+
const onLog = options.onLog ?? (async () => { });
|
|
221
|
+
if (target?.kind === "remote") {
|
|
222
|
+
const startedAt = new Date().toISOString();
|
|
223
|
+
const env = sanitizeRemoteExecutionEnv(options.env);
|
|
224
|
+
if (target.transport === "ssh") {
|
|
225
|
+
try {
|
|
226
|
+
// Pass the raw command — `runSshCommand` owns profile sourcing and
|
|
227
|
+
// the outer `sh -lc` wrapper. Wrapping again here would nest a second
|
|
228
|
+
// `sh -lc` after the explicit `env KEY=VAL` overrides, re-sourcing
|
|
229
|
+
// login profiles AFTER the override and silently undoing any
|
|
230
|
+
// identity var (NVM_DIR / PATH / etc.) that a profile re-exports.
|
|
231
|
+
const result = await runSshCommand(target.spec, command, {
|
|
232
|
+
env,
|
|
233
|
+
timeoutMs: (options.timeoutSec ?? 15) * 1000,
|
|
234
|
+
});
|
|
235
|
+
if (result.stdout)
|
|
236
|
+
await onLog("stdout", result.stdout);
|
|
237
|
+
if (result.stderr)
|
|
238
|
+
await onLog("stderr", result.stderr);
|
|
239
|
+
return {
|
|
240
|
+
exitCode: 0,
|
|
241
|
+
signal: null,
|
|
242
|
+
timedOut: false,
|
|
243
|
+
stdout: result.stdout,
|
|
244
|
+
stderr: result.stderr,
|
|
245
|
+
pid: null,
|
|
246
|
+
startedAt,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
const timedOutError = error;
|
|
251
|
+
const stdout = timedOutError.stdout ?? "";
|
|
252
|
+
const stderr = timedOutError.stderr ?? "";
|
|
253
|
+
if (typeof timedOutError.code === "number") {
|
|
254
|
+
if (stdout)
|
|
255
|
+
await onLog("stdout", stdout);
|
|
256
|
+
if (stderr)
|
|
257
|
+
await onLog("stderr", stderr);
|
|
258
|
+
return {
|
|
259
|
+
exitCode: timedOutError.code,
|
|
260
|
+
signal: timedOutError.signal ?? null,
|
|
261
|
+
timedOut: false,
|
|
262
|
+
stdout,
|
|
263
|
+
stderr,
|
|
264
|
+
pid: null,
|
|
265
|
+
startedAt,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (timedOutError.code !== "ETIMEDOUT") {
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
if (stdout)
|
|
272
|
+
await onLog("stdout", stdout);
|
|
273
|
+
if (stderr)
|
|
274
|
+
await onLog("stderr", stderr);
|
|
275
|
+
return {
|
|
276
|
+
exitCode: null,
|
|
277
|
+
signal: timedOutError.signal ?? null,
|
|
278
|
+
timedOut: true,
|
|
279
|
+
stdout,
|
|
280
|
+
stderr,
|
|
281
|
+
pid: null,
|
|
282
|
+
startedAt,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const shellCommand = preferredSandboxShell(target);
|
|
287
|
+
return await requireSandboxRunner(target).execute({
|
|
288
|
+
command: shellCommand,
|
|
289
|
+
args: ["-lc", command],
|
|
290
|
+
cwd: target.remoteCwd,
|
|
291
|
+
env,
|
|
292
|
+
timeoutMs: (options.timeoutSec ?? 15) * 1000,
|
|
293
|
+
onLog,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return await runAdapterExecutionTargetProcess(runId, target, "sh", ["-lc", command], {
|
|
297
|
+
cwd: options.cwd,
|
|
298
|
+
env: options.env,
|
|
299
|
+
timeoutSec: options.timeoutSec ?? 15,
|
|
300
|
+
graceSec: options.graceSec ?? 5,
|
|
301
|
+
onLog,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
// Best-effort run of an adapter-supplied install command on a sandbox target
|
|
305
|
+
// before the resolvability + hello probe. Returns null for non-sandbox
|
|
306
|
+
// targets so callers can no-op. Returns a structured check otherwise — never
|
|
307
|
+
// throws — so the rest of the test still runs and reports the post-install
|
|
308
|
+
// state honestly. Caller pushes the check into its result array; the test
|
|
309
|
+
// report shows whether install was attempted and what came back.
|
|
310
|
+
export async function maybeRunSandboxInstallCommand(input) {
|
|
311
|
+
const { target, adapterKey, installCommand } = input;
|
|
312
|
+
if (!target || target.kind !== "remote" || target.transport !== "sandbox") {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
const trimmed = installCommand.trim();
|
|
316
|
+
if (trimmed.length === 0)
|
|
317
|
+
return null;
|
|
318
|
+
const code = `${adapterKey}_install_command_run`;
|
|
319
|
+
// Skip install when the binary is already on PATH. Avoids running
|
|
320
|
+
// network-dependent installers (e.g. `curl ... | bash`) on every test
|
|
321
|
+
// probe when the CLI is preinstalled on the lease/template.
|
|
322
|
+
const detectCommand = input.detectCommand?.trim();
|
|
323
|
+
if (detectCommand) {
|
|
324
|
+
try {
|
|
325
|
+
const probe = await runAdapterExecutionTargetShellCommand(input.runId, target, `command -v ${shellQuote(detectCommand)} >/dev/null 2>&1`, {
|
|
326
|
+
cwd: target.remoteCwd,
|
|
327
|
+
env: input.env ?? {},
|
|
328
|
+
timeoutSec: 30,
|
|
329
|
+
graceSec: 5,
|
|
330
|
+
});
|
|
331
|
+
if (!probe.timedOut && probe.exitCode === 0) {
|
|
332
|
+
return {
|
|
333
|
+
code,
|
|
334
|
+
level: "info",
|
|
335
|
+
message: `${detectCommand} already on PATH; skipped install.`,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// Fall through to actually running the install — failure to probe
|
|
341
|
+
// is not a reason to skip the install gate.
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
let result;
|
|
345
|
+
try {
|
|
346
|
+
result = await runAdapterExecutionTargetShellCommand(input.runId, target, trimmed, {
|
|
347
|
+
cwd: target.remoteCwd,
|
|
348
|
+
env: input.env ?? {},
|
|
349
|
+
timeoutSec: input.timeoutSec ?? 240,
|
|
350
|
+
graceSec: 10,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
return {
|
|
355
|
+
code,
|
|
356
|
+
level: "warn",
|
|
357
|
+
message: "Install command threw before completion.",
|
|
358
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
const tail = (text) => text.split(/\r?\n/).filter((line) => line.trim().length > 0).slice(-3).join(" | ").slice(0, 480);
|
|
362
|
+
if (result.timedOut) {
|
|
363
|
+
return {
|
|
364
|
+
code,
|
|
365
|
+
level: "warn",
|
|
366
|
+
message: `Install command timed out: ${trimmed}`,
|
|
367
|
+
detail: tail(result.stderr || result.stdout),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
if ((result.exitCode ?? 1) === 0) {
|
|
371
|
+
return {
|
|
372
|
+
code,
|
|
373
|
+
level: "info",
|
|
374
|
+
message: `Install command ran: ${trimmed}`,
|
|
375
|
+
...(tail(result.stdout) ? { detail: tail(result.stdout) } : {}),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
code,
|
|
380
|
+
level: "warn",
|
|
381
|
+
message: `Install command exited ${result.exitCode}: ${trimmed}`,
|
|
382
|
+
detail: tail(result.stderr || result.stdout),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
export async function readAdapterExecutionTargetHomeDir(runId, target, options) {
|
|
386
|
+
const result = await runAdapterExecutionTargetShellCommand(runId, target, 'printf %s "$HOME"', options);
|
|
387
|
+
const homeDir = result.stdout.trim();
|
|
388
|
+
return homeDir.length > 0 ? homeDir : null;
|
|
389
|
+
}
|
|
390
|
+
export async function ensureAdapterExecutionTargetRuntimeCommandInstalled(input) {
|
|
391
|
+
const installCommand = input.installCommand?.trim();
|
|
392
|
+
if (!installCommand || input.target?.kind !== "remote" || input.target.transport !== "sandbox") {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const detectCommand = input.detectCommand?.trim();
|
|
396
|
+
if (detectCommand) {
|
|
397
|
+
const probe = await runAdapterExecutionTargetShellCommand(input.runId, input.target, `command -v ${shellQuote(detectCommand)} >/dev/null 2>&1`, {
|
|
398
|
+
cwd: input.cwd,
|
|
399
|
+
env: input.env,
|
|
400
|
+
timeoutSec: input.timeoutSec,
|
|
401
|
+
graceSec: input.graceSec,
|
|
402
|
+
});
|
|
403
|
+
if (!probe.timedOut && probe.exitCode === 0) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const result = await runAdapterExecutionTargetShellCommand(input.runId, input.target, installCommand, {
|
|
408
|
+
cwd: input.cwd,
|
|
409
|
+
env: input.env,
|
|
410
|
+
timeoutSec: input.timeoutSec,
|
|
411
|
+
graceSec: input.graceSec,
|
|
412
|
+
onLog: input.onLog,
|
|
413
|
+
});
|
|
414
|
+
// A failed or timed-out install is not necessarily fatal: the CLI may already
|
|
415
|
+
// be on PATH from a previous lease's install, the template image, or another
|
|
416
|
+
// path entry. Re-run the detect probe (when one is configured) so a transient
|
|
417
|
+
// install failure does not abort the agent run when the binary is reachable.
|
|
418
|
+
const installFailed = result.timedOut || (result.exitCode ?? 0) !== 0;
|
|
419
|
+
if (!installFailed) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (detectCommand) {
|
|
423
|
+
const recheck = await runAdapterExecutionTargetShellCommand(input.runId, input.target, `command -v ${shellQuote(detectCommand)} >/dev/null 2>&1`, {
|
|
424
|
+
cwd: input.cwd,
|
|
425
|
+
env: input.env,
|
|
426
|
+
timeoutSec: input.timeoutSec,
|
|
427
|
+
graceSec: input.graceSec,
|
|
428
|
+
});
|
|
429
|
+
if (!recheck.timedOut && recheck.exitCode === 0) {
|
|
430
|
+
if (input.onLog) {
|
|
431
|
+
const reason = result.timedOut ? "timed out" : `exited ${result.exitCode ?? "?"}`;
|
|
432
|
+
await input.onLog("stderr", `[evermore] Install command ${reason} (${installCommand}) but ${detectCommand} is on PATH; continuing.\n`);
|
|
433
|
+
}
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (result.timedOut) {
|
|
438
|
+
throw new Error(`Timed out while installing the adapter runtime command via: ${installCommand}`);
|
|
439
|
+
}
|
|
440
|
+
throw new Error(`Failed to install the adapter runtime command via: ${installCommand}`);
|
|
441
|
+
}
|
|
442
|
+
export async function ensureAdapterExecutionTargetFile(runId, target, filePath, options) {
|
|
443
|
+
await runAdapterExecutionTargetShellCommand(runId, target, `mkdir -p ${shellQuote(path.posix.dirname(filePath))} && : > ${shellQuote(filePath)}`, options);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Ensure a working directory exists (and is a directory) on the execution target.
|
|
447
|
+
*
|
|
448
|
+
* For local targets this delegates to the local `ensureAbsoluteDirectory` helper
|
|
449
|
+
* (Node fs). For remote (SSH/sandbox) targets it shells out and runs
|
|
450
|
+
* `mkdir -p` (when allowed) followed by a `[ -d ]` check so the result reflects
|
|
451
|
+
* the directory state inside the environment, not on the Evermore host.
|
|
452
|
+
*
|
|
453
|
+
* Throws an Error with a human-readable message on failure.
|
|
454
|
+
*/
|
|
455
|
+
export async function ensureAdapterExecutionTargetDirectory(runId, target, cwd, options) {
|
|
456
|
+
const createIfMissing = options.createIfMissing ?? false;
|
|
457
|
+
if (!target || target.kind === "local") {
|
|
458
|
+
const { ensureAbsoluteDirectory } = await import("./server-utils.js");
|
|
459
|
+
await ensureAbsoluteDirectory(cwd, { createIfMissing });
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
// Remote (SSH or sandbox): both expect POSIX absolute paths inside the env.
|
|
463
|
+
if (!cwd.startsWith("/")) {
|
|
464
|
+
throw new Error(`Working directory must be an absolute POSIX path on the remote target: "${cwd}"`);
|
|
465
|
+
}
|
|
466
|
+
const quoted = shellQuote(cwd);
|
|
467
|
+
const script = createIfMissing
|
|
468
|
+
? `mkdir -p ${quoted} && [ -d ${quoted} ]`
|
|
469
|
+
: `[ -d ${quoted} ]`;
|
|
470
|
+
const result = await runAdapterExecutionTargetShellCommand(runId, target, script, {
|
|
471
|
+
cwd: target.kind === "remote" ? target.remoteCwd : cwd,
|
|
472
|
+
env: options.env,
|
|
473
|
+
timeoutSec: options.timeoutSec ?? 15,
|
|
474
|
+
graceSec: options.graceSec ?? 5,
|
|
475
|
+
onLog: options.onLog,
|
|
476
|
+
});
|
|
477
|
+
if (result.timedOut) {
|
|
478
|
+
throw new Error(`Timed out checking working directory on remote target: "${cwd}"`);
|
|
479
|
+
}
|
|
480
|
+
if ((result.exitCode ?? 1) !== 0) {
|
|
481
|
+
const detail = (result.stderr || result.stdout || "").trim();
|
|
482
|
+
if (createIfMissing) {
|
|
483
|
+
throw new Error(`Could not create working directory "${cwd}" on remote target${detail ? `: ${detail}` : "."}`);
|
|
484
|
+
}
|
|
485
|
+
throw new Error(`Working directory does not exist on remote target: "${cwd}"${detail ? ` (${detail})` : ""}`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
export function adapterExecutionTargetSessionIdentity(target) {
|
|
489
|
+
if (!target || target.kind === "local")
|
|
490
|
+
return null;
|
|
491
|
+
if (target.transport === "ssh")
|
|
492
|
+
return buildRemoteExecutionSessionIdentity(target.spec);
|
|
493
|
+
return {
|
|
494
|
+
transport: "sandbox",
|
|
495
|
+
providerKey: target.providerKey ?? null,
|
|
496
|
+
environmentId: target.environmentId ?? null,
|
|
497
|
+
leaseId: target.leaseId ?? null,
|
|
498
|
+
remoteCwd: target.remoteCwd,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
export function adapterExecutionTargetSessionMatches(saved, target) {
|
|
502
|
+
if (!target || target.kind === "local") {
|
|
503
|
+
return Object.keys(parseObject(saved)).length === 0;
|
|
504
|
+
}
|
|
505
|
+
if (target.transport === "ssh")
|
|
506
|
+
return remoteExecutionSessionMatches(saved, target.spec);
|
|
507
|
+
const current = adapterExecutionTargetSessionIdentity(target);
|
|
508
|
+
const parsedSaved = parseObject(saved);
|
|
509
|
+
return (readStringMeta(parsedSaved, "transport") === current?.transport &&
|
|
510
|
+
readStringMeta(parsedSaved, "providerKey") === current?.providerKey &&
|
|
511
|
+
readStringMeta(parsedSaved, "environmentId") === current?.environmentId &&
|
|
512
|
+
readStringMeta(parsedSaved, "leaseId") === current?.leaseId &&
|
|
513
|
+
readStringMeta(parsedSaved, "remoteCwd") === current?.remoteCwd);
|
|
514
|
+
}
|
|
515
|
+
export function parseAdapterExecutionTarget(value) {
|
|
516
|
+
const parsed = parseObject(value);
|
|
517
|
+
const kind = readStringMeta(parsed, "kind");
|
|
518
|
+
if (kind === "local") {
|
|
519
|
+
return {
|
|
520
|
+
kind: "local",
|
|
521
|
+
environmentId: readStringMeta(parsed, "environmentId"),
|
|
522
|
+
leaseId: readStringMeta(parsed, "leaseId"),
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
if (kind === "remote" && readStringMeta(parsed, "transport") === "ssh") {
|
|
526
|
+
const spec = parseSshRemoteExecutionSpec(parseObject(parsed.spec));
|
|
527
|
+
if (!spec)
|
|
528
|
+
return null;
|
|
529
|
+
return {
|
|
530
|
+
kind: "remote",
|
|
531
|
+
transport: "ssh",
|
|
532
|
+
environmentId: readStringMeta(parsed, "environmentId"),
|
|
533
|
+
leaseId: readStringMeta(parsed, "leaseId"),
|
|
534
|
+
remoteCwd: spec.remoteCwd,
|
|
535
|
+
spec,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
if (kind === "remote" && readStringMeta(parsed, "transport") === "sandbox") {
|
|
539
|
+
const remoteCwd = readStringMeta(parsed, "remoteCwd");
|
|
540
|
+
if (!remoteCwd)
|
|
541
|
+
return null;
|
|
542
|
+
return {
|
|
543
|
+
kind: "remote",
|
|
544
|
+
transport: "sandbox",
|
|
545
|
+
providerKey: readStringMeta(parsed, "providerKey"),
|
|
546
|
+
environmentId: readStringMeta(parsed, "environmentId"),
|
|
547
|
+
leaseId: readStringMeta(parsed, "leaseId"),
|
|
548
|
+
remoteCwd,
|
|
549
|
+
timeoutMs: typeof parsed.timeoutMs === "number" ? parsed.timeoutMs : null,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
export function adapterExecutionTargetFromRemoteExecution(remoteExecution, metadata = {}) {
|
|
555
|
+
const parsed = parseObject(remoteExecution);
|
|
556
|
+
const ssh = parseSshRemoteExecutionSpec(parsed);
|
|
557
|
+
if (ssh) {
|
|
558
|
+
return {
|
|
559
|
+
kind: "remote",
|
|
560
|
+
transport: "ssh",
|
|
561
|
+
environmentId: metadata.environmentId ?? null,
|
|
562
|
+
leaseId: metadata.leaseId ?? null,
|
|
563
|
+
remoteCwd: ssh.remoteCwd,
|
|
564
|
+
spec: ssh,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
export function readAdapterExecutionTarget(input) {
|
|
570
|
+
if (isAdapterExecutionTargetInstance(input.executionTarget)) {
|
|
571
|
+
return input.executionTarget;
|
|
572
|
+
}
|
|
573
|
+
return (parseAdapterExecutionTarget(input.executionTarget) ??
|
|
574
|
+
adapterExecutionTargetFromRemoteExecution(input.legacyRemoteExecution));
|
|
575
|
+
}
|
|
576
|
+
export async function prepareAdapterExecutionTargetRuntime(input) {
|
|
577
|
+
const target = input.target ?? { kind: "local" };
|
|
578
|
+
if (target.kind === "local") {
|
|
579
|
+
return {
|
|
580
|
+
target,
|
|
581
|
+
runtimeRootDir: null,
|
|
582
|
+
assetDirs: {},
|
|
583
|
+
restoreWorkspace: async () => { },
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
if (target.transport === "ssh") {
|
|
587
|
+
const prepared = await prepareRemoteManagedRuntime({
|
|
588
|
+
spec: target.spec,
|
|
589
|
+
adapterKey: input.adapterKey,
|
|
590
|
+
workspaceLocalDir: input.workspaceLocalDir,
|
|
591
|
+
assets: input.assets,
|
|
592
|
+
});
|
|
593
|
+
return {
|
|
594
|
+
target,
|
|
595
|
+
runtimeRootDir: prepared.runtimeRootDir,
|
|
596
|
+
assetDirs: prepared.assetDirs,
|
|
597
|
+
restoreWorkspace: prepared.restoreWorkspace,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
const prepared = await prepareCommandManagedRuntime({
|
|
601
|
+
runner: requireSandboxRunner(target),
|
|
602
|
+
spec: {
|
|
603
|
+
providerKey: target.providerKey,
|
|
604
|
+
shellCommand: target.shellCommand,
|
|
605
|
+
leaseId: target.leaseId,
|
|
606
|
+
remoteCwd: target.remoteCwd,
|
|
607
|
+
timeoutMs: target.timeoutMs,
|
|
608
|
+
},
|
|
609
|
+
adapterKey: input.adapterKey,
|
|
610
|
+
workspaceLocalDir: input.workspaceLocalDir,
|
|
611
|
+
workspaceExclude: input.workspaceExclude,
|
|
612
|
+
preserveAbsentOnRestore: input.preserveAbsentOnRestore,
|
|
613
|
+
assets: input.assets,
|
|
614
|
+
installCommand: input.installCommand,
|
|
615
|
+
detectCommand: input.detectCommand,
|
|
616
|
+
});
|
|
617
|
+
return {
|
|
618
|
+
target,
|
|
619
|
+
runtimeRootDir: prepared.runtimeRootDir,
|
|
620
|
+
assetDirs: prepared.assetDirs,
|
|
621
|
+
restoreWorkspace: prepared.restoreWorkspace,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
export function runtimeAssetDir(prepared, key, fallbackRemoteCwd) {
|
|
625
|
+
return prepared.assetDirs[key] ?? path.posix.join(fallbackRemoteCwd, ".evermore-runtime", key);
|
|
626
|
+
}
|
|
627
|
+
function buildBridgeResponseHeaders(response) {
|
|
628
|
+
const out = {};
|
|
629
|
+
for (const key of ["content-type", "etag", "last-modified"]) {
|
|
630
|
+
const value = response.headers.get(key);
|
|
631
|
+
if (value && value.trim().length > 0)
|
|
632
|
+
out[key] = value.trim();
|
|
633
|
+
}
|
|
634
|
+
return out;
|
|
635
|
+
}
|
|
636
|
+
function buildBridgeForwardUrl(baseUrl, request) {
|
|
637
|
+
const url = new URL(request.path, baseUrl);
|
|
638
|
+
const query = request.query.trim();
|
|
639
|
+
url.search = query.startsWith("?") ? query.slice(1) : query;
|
|
640
|
+
return url;
|
|
641
|
+
}
|
|
642
|
+
function bridgeResponseBodyLimitError(maxBodyBytes) {
|
|
643
|
+
return new Error(`Bridge response body exceeded the configured size limit of ${maxBodyBytes} bytes.`);
|
|
644
|
+
}
|
|
645
|
+
async function readBridgeForwardResponseBody(response, maxBodyBytes) {
|
|
646
|
+
const rawContentLength = response.headers.get("content-length");
|
|
647
|
+
if (rawContentLength) {
|
|
648
|
+
const contentLength = Number.parseInt(rawContentLength, 10);
|
|
649
|
+
if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) {
|
|
650
|
+
throw bridgeResponseBodyLimitError(maxBodyBytes);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (!response.body) {
|
|
654
|
+
return "";
|
|
655
|
+
}
|
|
656
|
+
const reader = response.body.getReader();
|
|
657
|
+
const chunks = [];
|
|
658
|
+
let totalBytes = 0;
|
|
659
|
+
while (true) {
|
|
660
|
+
const { done, value } = await reader.read();
|
|
661
|
+
if (done)
|
|
662
|
+
break;
|
|
663
|
+
if (!value)
|
|
664
|
+
continue;
|
|
665
|
+
totalBytes += value.byteLength;
|
|
666
|
+
if (totalBytes > maxBodyBytes) {
|
|
667
|
+
await reader.cancel().catch(() => undefined);
|
|
668
|
+
throw bridgeResponseBodyLimitError(maxBodyBytes);
|
|
669
|
+
}
|
|
670
|
+
chunks.push(Buffer.from(value));
|
|
671
|
+
}
|
|
672
|
+
return Buffer.concat(chunks, totalBytes).toString("utf8");
|
|
673
|
+
}
|
|
674
|
+
export async function startAdapterExecutionTargetEvermoreBridge(input) {
|
|
675
|
+
if (!adapterExecutionTargetUsesEvermoreBridge(input.target)) {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
if (!input.target || input.target.kind !== "remote") {
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
const target = input.target;
|
|
682
|
+
const onLog = input.onLog ?? (async () => { });
|
|
683
|
+
const hostApiToken = input.hostApiToken?.trim() ?? "";
|
|
684
|
+
if (hostApiToken.length === 0) {
|
|
685
|
+
throw new Error("Sandbox bridge mode requires a host-side Evermore API token.");
|
|
686
|
+
}
|
|
687
|
+
const runtimeRootDir = input.runtimeRootDir?.trim().length
|
|
688
|
+
? input.runtimeRootDir.trim()
|
|
689
|
+
: path.posix.join(target.remoteCwd, ".evermore-runtime", input.adapterKey);
|
|
690
|
+
const bridgeRuntimeDir = path.posix.join(runtimeRootDir, "evermore-bridge");
|
|
691
|
+
const queueDir = path.posix.join(bridgeRuntimeDir, "queue");
|
|
692
|
+
const assetRemoteDir = path.posix.join(bridgeRuntimeDir, "server");
|
|
693
|
+
const bridgeToken = createSandboxCallbackBridgeToken();
|
|
694
|
+
const maxBodyBytes = typeof input.maxBodyBytes === "number" && Number.isFinite(input.maxBodyBytes) && input.maxBodyBytes > 0
|
|
695
|
+
? Math.trunc(input.maxBodyBytes)
|
|
696
|
+
: DEFAULT_SANDBOX_CALLBACK_BRIDGE_MAX_BODY_BYTES;
|
|
697
|
+
const hostApiUrl = input.hostApiUrl?.trim() ||
|
|
698
|
+
process.env.EVERMORE_RUNTIME_API_URL?.trim() ||
|
|
699
|
+
process.env.EVERMORE_API_URL?.trim() ||
|
|
700
|
+
resolveDefaultEvermoreApiUrl();
|
|
701
|
+
const shellCommand = adapterExecutionTargetShellCommand(target);
|
|
702
|
+
const runner = adapterExecutionTargetCommandRunner(target);
|
|
703
|
+
await onLog("stdout", `[evermore] Starting sandbox callback bridge for ${input.adapterKey} in ${bridgeRuntimeDir}.\n`);
|
|
704
|
+
const bridgeAsset = await createSandboxCallbackBridgeAsset();
|
|
705
|
+
let server = null;
|
|
706
|
+
let worker = null;
|
|
707
|
+
try {
|
|
708
|
+
const client = createCommandManagedSandboxCallbackBridgeQueueClient({
|
|
709
|
+
runner,
|
|
710
|
+
remoteCwd: target.remoteCwd,
|
|
711
|
+
timeoutMs: adapterExecutionTargetTimeoutMs(target),
|
|
712
|
+
shellCommand,
|
|
713
|
+
});
|
|
714
|
+
// EVERMORE_BRIDGE_DEBUG opts into verbose stdout logs of every bridge
|
|
715
|
+
// proxy request/response. The query string is logged verbatim, so callers
|
|
716
|
+
// who pass auth tokens or other sensitive values as query parameters
|
|
717
|
+
// should be aware those values appear in the host process's stdout when
|
|
718
|
+
// this flag is enabled. Only intended for active debugging in trusted
|
|
719
|
+
// environments.
|
|
720
|
+
const bridgeDebugEnabled = isBridgeDebugEnabled(process.env);
|
|
721
|
+
worker = await startSandboxCallbackBridgeWorker({
|
|
722
|
+
client,
|
|
723
|
+
queueDir,
|
|
724
|
+
maxBodyBytes,
|
|
725
|
+
handleRequest: async (request) => {
|
|
726
|
+
const method = request.method.trim().toUpperCase() || "GET";
|
|
727
|
+
if (bridgeDebugEnabled) {
|
|
728
|
+
await onLog("stdout", `[evermore] Bridge proxy ${method} ${request.path}${request.query ? `?${request.query}` : ""}\n`);
|
|
729
|
+
}
|
|
730
|
+
const headers = new Headers();
|
|
731
|
+
for (const [key, value] of Object.entries(request.headers)) {
|
|
732
|
+
if (value.trim().length === 0)
|
|
733
|
+
continue;
|
|
734
|
+
headers.set(key, value);
|
|
735
|
+
}
|
|
736
|
+
headers.set("authorization", `Bearer ${hostApiToken}`);
|
|
737
|
+
headers.set("x-evermore-run-id", input.runId);
|
|
738
|
+
const response = await fetch(buildBridgeForwardUrl(hostApiUrl, request), {
|
|
739
|
+
method,
|
|
740
|
+
headers,
|
|
741
|
+
...(method === "GET" || method === "HEAD" ? {} : { body: request.body }),
|
|
742
|
+
signal: AbortSignal.timeout(30_000),
|
|
743
|
+
});
|
|
744
|
+
if (bridgeDebugEnabled) {
|
|
745
|
+
await onLog("stdout", `[evermore] Bridge proxy response ${response.status} for ${method} ${request.path}${request.query ? `?${request.query}` : ""}\n`);
|
|
746
|
+
}
|
|
747
|
+
return {
|
|
748
|
+
status: response.status,
|
|
749
|
+
headers: buildBridgeResponseHeaders(response),
|
|
750
|
+
body: await readBridgeForwardResponseBody(response, maxBodyBytes),
|
|
751
|
+
};
|
|
752
|
+
},
|
|
753
|
+
});
|
|
754
|
+
server = await startSandboxCallbackBridgeServer({
|
|
755
|
+
runner,
|
|
756
|
+
remoteCwd: target.remoteCwd,
|
|
757
|
+
assetRemoteDir,
|
|
758
|
+
queueDir,
|
|
759
|
+
bridgeToken,
|
|
760
|
+
bridgeAsset,
|
|
761
|
+
timeoutMs: adapterExecutionTargetTimeoutMs(target),
|
|
762
|
+
maxBodyBytes,
|
|
763
|
+
shellCommand,
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
catch (error) {
|
|
767
|
+
await Promise.allSettled([
|
|
768
|
+
server?.stop(),
|
|
769
|
+
worker?.stop(),
|
|
770
|
+
bridgeAsset.cleanup(),
|
|
771
|
+
]);
|
|
772
|
+
throw error;
|
|
773
|
+
}
|
|
774
|
+
return {
|
|
775
|
+
env: {
|
|
776
|
+
EVERMORE_API_URL: server.baseUrl,
|
|
777
|
+
EVERMORE_API_KEY: bridgeToken,
|
|
778
|
+
EVERMORE_API_BRIDGE_MODE: "queue_v1",
|
|
779
|
+
},
|
|
780
|
+
stop: async () => {
|
|
781
|
+
await Promise.allSettled([
|
|
782
|
+
server?.stop(),
|
|
783
|
+
]);
|
|
784
|
+
await Promise.allSettled([
|
|
785
|
+
worker?.stop(),
|
|
786
|
+
bridgeAsset.cleanup(),
|
|
787
|
+
]);
|
|
788
|
+
},
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
//# sourceMappingURL=execution-target.js.map
|