@rivet-dev/agentos-core 0.0.0-abc-fixes-1.d8c4d95 → 0.0.0-agent-acp-codex-session-benchmark.62b1f46
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 +48 -48
- package/dist/agent-os.d.ts +215 -161
- package/dist/agent-os.js +1300 -2576
- package/dist/agentos-package.d.ts +27 -0
- package/dist/agentos-package.js +21 -0
- package/dist/base-filesystem.d.ts +1 -16
- package/dist/base-filesystem.js +1 -173
- package/dist/cron/cron-manager.d.ts +39 -14
- package/dist/cron/cron-manager.js +289 -128
- package/dist/cron/errors.d.ts +8 -0
- package/dist/cron/errors.js +16 -0
- package/dist/cron/index.d.ts +1 -3
- package/dist/cron/index.js +1 -2
- package/dist/cron/types.d.ts +2 -2
- package/dist/default-software.d.ts +8 -0
- package/dist/default-software.js +10 -0
- package/dist/filesystem-snapshot.d.ts +1 -1
- package/dist/filesystem-snapshot.js +1 -1
- package/dist/generated/AcpLimitsConfig.d.ts +4 -0
- package/dist/generated/AcpLimitsConfig.js +2 -0
- package/dist/generated/CreateVmConfig.d.ts +29 -0
- package/dist/generated/FsPermissionRule.d.ts +6 -0
- package/dist/generated/FsPermissionRule.js +1 -0
- package/dist/generated/FsPermissionRuleSet.d.ts +6 -0
- package/dist/generated/FsPermissionRuleSet.js +1 -0
- package/dist/generated/FsPermissionScope.d.ts +3 -0
- package/dist/generated/FsPermissionScope.js +1 -0
- package/dist/generated/HttpLimitsConfig.d.ts +3 -0
- package/dist/generated/HttpLimitsConfig.js +2 -0
- package/dist/generated/JsModuleResolution.d.ts +1 -0
- package/dist/generated/JsModuleResolution.js +2 -0
- package/dist/generated/JsRuntimeConfig.d.ts +31 -0
- package/dist/generated/JsRuntimeConfig.js +1 -0
- package/dist/generated/JsRuntimeLimitsConfig.d.ts +8 -0
- package/dist/generated/JsRuntimeLimitsConfig.js +2 -0
- package/dist/generated/JsRuntimePlatform.d.ts +1 -0
- package/dist/generated/JsRuntimePlatform.js +2 -0
- package/dist/generated/MountPluginDescriptor.d.ts +4 -0
- package/dist/generated/MountPluginDescriptor.js +2 -0
- package/dist/generated/NativeRootFilesystemConfig.d.ts +5 -0
- package/dist/generated/NativeRootFilesystemConfig.js +1 -0
- package/dist/generated/PatternPermissionRule.d.ts +6 -0
- package/dist/generated/PatternPermissionRule.js +1 -0
- package/dist/generated/PatternPermissionRuleSet.d.ts +6 -0
- package/dist/generated/PatternPermissionRuleSet.js +1 -0
- package/dist/generated/PatternPermissionScope.d.ts +3 -0
- package/dist/generated/PatternPermissionScope.js +1 -0
- package/dist/generated/PermissionMode.d.ts +1 -0
- package/dist/generated/PermissionMode.js +2 -0
- package/dist/generated/PermissionsPolicy.d.ts +10 -0
- package/dist/generated/PermissionsPolicy.js +1 -0
- package/dist/generated/PluginLimitsConfig.d.ts +4 -0
- package/dist/generated/PluginLimitsConfig.js +2 -0
- package/dist/generated/PythonLimitsConfig.d.ts +6 -0
- package/dist/generated/PythonLimitsConfig.js +2 -0
- package/dist/generated/ResourceLimitsConfig.d.ts +25 -0
- package/dist/generated/ResourceLimitsConfig.js +2 -0
- package/dist/generated/RootFilesystemConfig.d.ts +9 -0
- package/dist/generated/RootFilesystemConfig.js +1 -0
- package/dist/generated/RootFilesystemEntry.d.ts +13 -0
- package/dist/generated/RootFilesystemEntry.js +1 -0
- package/dist/generated/RootFilesystemEntryEncoding.d.ts +1 -0
- package/dist/generated/RootFilesystemEntryEncoding.js +2 -0
- package/dist/generated/RootFilesystemEntryKind.d.ts +1 -0
- package/dist/generated/RootFilesystemEntryKind.js +2 -0
- package/dist/generated/RootFilesystemLowerDescriptor.d.ts +7 -0
- package/dist/generated/RootFilesystemLowerDescriptor.js +1 -0
- package/dist/generated/RootFilesystemMode.d.ts +1 -0
- package/dist/generated/RootFilesystemMode.js +2 -0
- package/dist/generated/ToolLimitsConfig.d.ts +10 -0
- package/dist/generated/ToolLimitsConfig.js +2 -0
- package/dist/generated/VmDnsConfig.d.ts +6 -0
- package/dist/generated/VmDnsConfig.js +2 -0
- package/dist/generated/VmLimitsConfig.d.ts +18 -0
- package/dist/generated/VmLimitsConfig.js +1 -0
- package/dist/generated/VmListenPolicyConfig.d.ts +5 -0
- package/dist/generated/VmListenPolicyConfig.js +2 -0
- package/dist/generated/WasmLimitsConfig.d.ts +7 -0
- package/dist/generated/WasmLimitsConfig.js +2 -0
- package/dist/host-dir-mount.d.ts +5 -5
- package/dist/host-dir-mount.js +4 -4
- package/dist/host-tools-zod.js +27 -7
- package/dist/host-tools.d.ts +2 -9
- package/dist/host-tools.js +0 -18
- package/dist/index.d.ts +7 -4
- package/dist/index.js +6 -4
- package/dist/layers.d.ts +3 -3
- package/dist/layers.js +19 -10
- package/dist/memory-filesystem.d.ts +39 -0
- package/dist/memory-filesystem.js +375 -0
- package/dist/options-schema.d.ts +452 -0
- package/dist/options-schema.js +269 -0
- package/dist/overlay-filesystem.d.ts +1 -1
- package/dist/overlay-filesystem.js +3 -3
- package/dist/packages.d.ts +10 -123
- package/dist/packages.js +3 -329
- package/dist/runtime.d.ts +41 -184
- package/dist/sidecar/agentos-protocol.d.ts +232 -29
- package/dist/sidecar/agentos-protocol.js +456 -120
- package/dist/sidecar/binary.d.ts +2 -2
- package/dist/sidecar/binary.js +2 -2
- package/dist/sidecar/native-process-client.d.ts +5 -3
- package/dist/sidecar/native-process-client.js +5 -3
- package/dist/sidecar/permissions.d.ts +4 -3
- package/dist/sidecar/permissions.js +6 -23
- package/dist/sidecar/rpc-client.d.ts +74 -86
- package/dist/sidecar/rpc-client.js +422 -1418
- package/dist/test/file-system.d.ts +1 -1
- package/dist/test/file-system.js +3 -1
- package/dist/test/runtime.d.ts +2 -3
- package/dist/test/runtime.js +1 -1
- package/dist/test/terminal-harness.d.ts +5 -4
- package/dist/test/terminal-harness.js +12 -7
- package/dist/types.d.ts +12 -5
- package/package.json +37 -41
- package/dist/agents.d.ts +0 -63
- package/dist/agents.js +0 -42
- package/dist/cron/parse-schedule.d.ts +0 -25
- package/dist/cron/parse-schedule.js +0 -66
- package/dist/cron/schedule-driver.d.ts +0 -19
- package/dist/cron/timer-driver.d.ts +0 -17
- package/dist/cron/timer-driver.js +0 -61
- package/dist/runtime-compat.d.ts +0 -475
- package/dist/runtime-compat.js +0 -2010
- package/dist/sidecar/cargo.d.ts +0 -2
- package/dist/sidecar/cargo.js +0 -142
- package/fixtures/AGENTOS_SYSTEM_PROMPT.md +0 -21
- /package/dist/{cron/schedule-driver.js → generated/CreateVmConfig.js} +0 -0
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { constants as osConstants } from "node:os";
|
|
3
3
|
import { posix as posixPath } from "node:path";
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
4
|
+
function shouldLogStructuredSidecarEvent(name) {
|
|
5
|
+
const normalized = name.toLowerCase();
|
|
6
|
+
return (normalized === "limit_warning" ||
|
|
7
|
+
normalized.startsWith("security.") ||
|
|
8
|
+
normalized.includes("warning") ||
|
|
9
|
+
normalized.includes("failed") ||
|
|
10
|
+
normalized.includes("error"));
|
|
11
|
+
}
|
|
12
|
+
function decodeUtf8(data) {
|
|
13
|
+
return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8");
|
|
14
|
+
}
|
|
15
|
+
function formatStructuredSidecarDetail(detail) {
|
|
16
|
+
const entries = Object.entries(detail);
|
|
17
|
+
if (entries.length === 0) {
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
return entries
|
|
21
|
+
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
|
|
22
|
+
.join(" ");
|
|
23
|
+
}
|
|
24
|
+
function logStructuredSidecarEvent(name, detail) {
|
|
25
|
+
if (!shouldLogStructuredSidecarEvent(name)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const formatted = formatStructuredSidecarDetail(detail);
|
|
29
|
+
console.warn(formatted
|
|
30
|
+
? `[agent-os] sidecar ${name}: ${formatted}`
|
|
31
|
+
: `[agent-os] sidecar ${name}`);
|
|
18
32
|
}
|
|
19
33
|
const PREFERRED_SIGNAL_NAMES = [
|
|
20
34
|
"SIGHUP",
|
|
@@ -58,98 +72,6 @@ const NON_CANONICAL_SIGNAL_NAMES = new Set([
|
|
|
58
72
|
"SIGUNUSED",
|
|
59
73
|
]);
|
|
60
74
|
const SIGNAL_NAME_BY_NUMBER = buildSignalNameByNumber();
|
|
61
|
-
const DOUBLE_QUOTE_ESCAPABLE_CHARACTERS = new Set(['"', "\\", "$", "`"]);
|
|
62
|
-
function appendDoubleQuotedEscape(current, character) {
|
|
63
|
-
if (DOUBLE_QUOTE_ESCAPABLE_CHARACTERS.has(character)) {
|
|
64
|
-
return current + character;
|
|
65
|
-
}
|
|
66
|
-
if (character === "\n") {
|
|
67
|
-
return current;
|
|
68
|
-
}
|
|
69
|
-
return `${current}\\${character}`;
|
|
70
|
-
}
|
|
71
|
-
function parseSimpleExecCommand(command) {
|
|
72
|
-
const tokens = [];
|
|
73
|
-
let current = "";
|
|
74
|
-
let quote = null;
|
|
75
|
-
let escaped = false;
|
|
76
|
-
for (const character of command) {
|
|
77
|
-
if (quote === null) {
|
|
78
|
-
if (escaped) {
|
|
79
|
-
current += character;
|
|
80
|
-
escaped = false;
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
if (character === "\\") {
|
|
84
|
-
escaped = true;
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (character === "'" || character === '"') {
|
|
88
|
-
quote = character;
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
if (/\s/.test(character)) {
|
|
92
|
-
if (current) {
|
|
93
|
-
tokens.push(current);
|
|
94
|
-
current = "";
|
|
95
|
-
}
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
if ("|&;<>()$`*?[]{}~!".includes(character)) {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
current += character;
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
if (quote === "'") {
|
|
105
|
-
if (character === "'") {
|
|
106
|
-
quote = null;
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
current += character;
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
if (escaped) {
|
|
113
|
-
current = appendDoubleQuotedEscape(current, character);
|
|
114
|
-
escaped = false;
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
if (character === "\\") {
|
|
118
|
-
escaped = true;
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
if (character === '"') {
|
|
122
|
-
quote = null;
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
if (character === "$" || character === "`") {
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
current += character;
|
|
129
|
-
}
|
|
130
|
-
if (quote !== null || escaped) {
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
if (current) {
|
|
134
|
-
tokens.push(current);
|
|
135
|
-
}
|
|
136
|
-
if (tokens.length === 0) {
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
if (tokens.some((token) => token.length === 0)) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
return tokens;
|
|
143
|
-
}
|
|
144
|
-
function canUseDirectExec(driver, commandName) {
|
|
145
|
-
return driver === "wasmvm" || (driver === "node" && commandName === "node");
|
|
146
|
-
}
|
|
147
|
-
function shellSingleQuote(value) {
|
|
148
|
-
if (value.length === 0) {
|
|
149
|
-
return "''";
|
|
150
|
-
}
|
|
151
|
-
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
152
|
-
}
|
|
153
75
|
function buildSignalNameByNumber() {
|
|
154
76
|
const signals = osConstants.signals;
|
|
155
77
|
const names = new Map();
|
|
@@ -171,79 +93,110 @@ function buildSignalNameByNumber() {
|
|
|
171
93
|
export function toSidecarSignalName(signal) {
|
|
172
94
|
return SIGNAL_NAME_BY_NUMBER.get(signal) ?? String(signal);
|
|
173
95
|
}
|
|
96
|
+
function toSidecarTimeoutMs(timeout) {
|
|
97
|
+
if (timeout === undefined) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
if (!Number.isFinite(timeout) || timeout < 0) {
|
|
101
|
+
throw new RangeError("process timeout must be a finite non-negative number");
|
|
102
|
+
}
|
|
103
|
+
return Math.trunc(timeout);
|
|
104
|
+
}
|
|
105
|
+
const processCompletion = Symbol("agentos.processCompletion");
|
|
174
106
|
export class NativeSidecarKernelProxy {
|
|
175
107
|
env;
|
|
176
108
|
cwd;
|
|
177
|
-
commands;
|
|
178
109
|
vfs;
|
|
179
110
|
processes = new Map();
|
|
180
|
-
defaultExecCwd;
|
|
181
111
|
client;
|
|
182
112
|
session;
|
|
183
113
|
vm;
|
|
114
|
+
ownsClient;
|
|
184
115
|
localMounts;
|
|
185
|
-
|
|
186
|
-
onWasmCommandResolved;
|
|
116
|
+
baseSidecarMounts;
|
|
187
117
|
onDispose;
|
|
188
118
|
trackedProcesses = new Map();
|
|
189
119
|
trackedProcessesById = new Map();
|
|
190
|
-
listenerLookups = new Map();
|
|
191
|
-
boundUdpLookups = new Map();
|
|
192
|
-
signalStates = new Map();
|
|
193
|
-
signalRefreshes = new Map();
|
|
194
|
-
sidecarProcessSnapshot = [];
|
|
195
120
|
processSnapshotRefresh = null;
|
|
196
|
-
observedProcessStartTimes = new Map();
|
|
197
121
|
rootView;
|
|
198
|
-
zombieTimerCountValue = 0;
|
|
199
|
-
zombieTimerCountRefresh = null;
|
|
200
122
|
disposed = false;
|
|
123
|
+
disposePromise = null;
|
|
201
124
|
pumpError = null;
|
|
202
|
-
|
|
125
|
+
mountReconfigurePromise = null;
|
|
203
126
|
eventPumpAbortController = new AbortController();
|
|
204
127
|
eventPump;
|
|
205
128
|
constructor(options) {
|
|
206
129
|
this.client = options.client;
|
|
207
130
|
this.session = options.session;
|
|
208
131
|
this.vm = options.vm;
|
|
132
|
+
this.ownsClient = options.ownsClient ?? true;
|
|
209
133
|
this.env = { ...options.env };
|
|
210
134
|
this.cwd = options.cwd;
|
|
211
|
-
this.defaultExecCwd = options.defaultExecCwd;
|
|
212
135
|
this.localMounts = [...options.localMounts].sort((left, right) => right.path.length - left.path.length);
|
|
213
|
-
|
|
214
|
-
this.
|
|
136
|
+
const localMountPaths = new Set(this.localMounts.map((mount) => mount.path));
|
|
137
|
+
this.baseSidecarMounts = options.sidecarMounts.filter((mount) => mount.plugin.id !== "js_bridge" ||
|
|
138
|
+
!localMountPaths.has(posixPath.normalize(mount.guestPath)));
|
|
215
139
|
this.onDispose = options.onDispose;
|
|
216
|
-
this.
|
|
217
|
-
this.
|
|
218
|
-
this.rootView = this.createFilesystemView(false);
|
|
140
|
+
this.vfs = this.createFilesystemView();
|
|
141
|
+
this.rootView = this.vfs;
|
|
219
142
|
this.eventPump = this.runEventPump();
|
|
220
|
-
void this.eventPump
|
|
143
|
+
void this.eventPump;
|
|
221
144
|
}
|
|
222
145
|
createRootView() {
|
|
223
146
|
return this.rootView;
|
|
224
147
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
return this.zombieTimerCountValue;
|
|
230
|
-
}
|
|
231
|
-
registerCommandGuestPaths(commandGuestPaths) {
|
|
232
|
-
for (const name of commandGuestPaths.keys()) {
|
|
233
|
-
this.commandDrivers.set(name, "wasmvm");
|
|
234
|
-
}
|
|
148
|
+
/** Resolve the host-only backing store for an exact js_bridge mount id. */
|
|
149
|
+
hostFilesystemForMount(mountId) {
|
|
150
|
+
const normalized = posixPath.normalize(mountId);
|
|
151
|
+
return this.localMounts.find((mount) => mount.path === normalized)?.fs;
|
|
235
152
|
}
|
|
236
153
|
async dispose() {
|
|
237
154
|
if (this.disposed) {
|
|
238
155
|
return;
|
|
239
156
|
}
|
|
157
|
+
if (this.disposePromise) {
|
|
158
|
+
return this.disposePromise;
|
|
159
|
+
}
|
|
160
|
+
const attempt = this.disposeOnce();
|
|
161
|
+
this.disposePromise = attempt;
|
|
162
|
+
try {
|
|
163
|
+
await attempt;
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
if (this.disposePromise === attempt) {
|
|
167
|
+
this.disposePromise = null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async disposeOnce() {
|
|
172
|
+
const errors = [];
|
|
173
|
+
try {
|
|
174
|
+
await this.mountReconfigurePromise;
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
errors.push(toError(error));
|
|
178
|
+
}
|
|
179
|
+
const liveProcesses = [...this.trackedProcessesById.values()].filter((entry) => !entry.settled);
|
|
180
|
+
const signalResults = await Promise.allSettled(liveProcesses.map((entry) => this.signalProcess(entry, 15)));
|
|
181
|
+
for (const result of signalResults) {
|
|
182
|
+
if (result.status === "rejected") {
|
|
183
|
+
errors.push(toError(result.reason));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
await this.client.disposeVm(this.session, this.vm);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
errors.push(toError(error));
|
|
191
|
+
throw new AggregateError(errors, "failed to dispose sidecar VM");
|
|
192
|
+
}
|
|
193
|
+
// Remote disposal is authoritative. Only now make local teardown
|
|
194
|
+
// irreversible; a rejected/failed request leaves the route and lease live so
|
|
195
|
+
// the caller can retry without manufacturing local success.
|
|
240
196
|
this.disposed = true;
|
|
241
197
|
this.eventPumpAbortController.abort();
|
|
242
|
-
const liveProcesses = [...this.trackedProcesses.values()].filter((entry) => entry.exitCode === null);
|
|
243
|
-
await Promise.allSettled(liveProcesses.map((entry) => this.signalProcess(entry, 15)));
|
|
244
|
-
await this.client.disposeVm(this.session, this.vm).catch(() => { });
|
|
245
198
|
for (const entry of liveProcesses) {
|
|
246
|
-
if (entry.
|
|
199
|
+
if (!entry.settled) {
|
|
247
200
|
// The sidecar dispose path already performs TERM/KILL escalation for any
|
|
248
201
|
// guest executions that are still live. Resolve local waiters eagerly so
|
|
249
202
|
// VM teardown does not hang on killed ACP adapter processes that never
|
|
@@ -251,191 +204,107 @@ export class NativeSidecarKernelProxy {
|
|
|
251
204
|
this.finishProcess(entry, 143);
|
|
252
205
|
}
|
|
253
206
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
throw new Error(`native sidecar exec requires guest shell command 'sh': ${command}`);
|
|
261
|
-
}
|
|
262
|
-
const stdoutChunks = [];
|
|
263
|
-
const stderrChunks = [];
|
|
264
|
-
const effectiveCwd = options?.cwd ?? this.defaultExecCwd ?? this.cwd;
|
|
265
|
-
const parsedCommand = parseSimpleExecCommand(command);
|
|
266
|
-
const resolveExecPath = (targetPath) => targetPath.startsWith("/")
|
|
267
|
-
? posixPath.normalize(targetPath)
|
|
268
|
-
: posixPath.normalize(posixPath.join(effectiveCwd, targetPath));
|
|
269
|
-
const runAndCapture = async (proc, stdinOverride, readExitCode) => {
|
|
270
|
-
if (stdinOverride !== undefined) {
|
|
271
|
-
proc.writeStdin(stdinOverride);
|
|
272
|
-
}
|
|
273
|
-
else if (options?.stdin !== undefined) {
|
|
274
|
-
proc.writeStdin(options.stdin);
|
|
207
|
+
// Only tear down the shared sidecar process when this proxy owns it. VMs
|
|
208
|
+
// leased from an `AgentOsSidecar` handle share one process, which is
|
|
209
|
+
// disposed when the handle is disposed.
|
|
210
|
+
if (this.ownsClient) {
|
|
211
|
+
try {
|
|
212
|
+
await this.client.dispose();
|
|
275
213
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
// should observe EOF after any provided input so commands like
|
|
279
|
-
// `node -e ...` do not linger behind an inherited open stdin pipe.
|
|
280
|
-
proc.closeStdin();
|
|
281
|
-
const waitPromise = proc.wait();
|
|
282
|
-
const shellExitCode = typeof options?.timeout === "number"
|
|
283
|
-
? await new Promise((resolve) => {
|
|
284
|
-
const timer = setTimeout(() => {
|
|
285
|
-
proc.kill(9);
|
|
286
|
-
void proc.wait().then(resolve);
|
|
287
|
-
}, options.timeout);
|
|
288
|
-
void waitPromise.then((code) => {
|
|
289
|
-
clearTimeout(timer);
|
|
290
|
-
resolve(code);
|
|
291
|
-
});
|
|
292
|
-
})
|
|
293
|
-
: await waitPromise;
|
|
294
|
-
const exitCode = readExitCode
|
|
295
|
-
? await readExitCode().catch(() => shellExitCode)
|
|
296
|
-
: shellExitCode;
|
|
297
|
-
await drainTrailingProcessOutputTurn();
|
|
298
|
-
return {
|
|
299
|
-
exitCode,
|
|
300
|
-
stdout: Buffer.concat(stdoutChunks.map((chunk) => Buffer.from(chunk))).toString("utf8"),
|
|
301
|
-
stderr: Buffer.concat(stderrChunks.map((chunk) => Buffer.from(chunk))).toString("utf8"),
|
|
302
|
-
};
|
|
303
|
-
};
|
|
304
|
-
if (parsedCommand &&
|
|
305
|
-
(parsedCommand[0] === "sh" || parsedCommand[0] === "/bin/sh") &&
|
|
306
|
-
parsedCommand[1] === "-c" &&
|
|
307
|
-
parsedCommand.length === 3) {
|
|
308
|
-
const shellScript = parsedCommand[2].trim();
|
|
309
|
-
const exitMatch = shellScript.match(/^exit(?:\s+(-?\d+))?$/);
|
|
310
|
-
if (exitMatch) {
|
|
311
|
-
return {
|
|
312
|
-
exitCode: Number.parseInt(exitMatch[1] ?? "0", 10),
|
|
313
|
-
stdout: "",
|
|
314
|
-
stderr: "",
|
|
315
|
-
};
|
|
214
|
+
catch (error) {
|
|
215
|
+
errors.push(toError(error));
|
|
316
216
|
}
|
|
317
|
-
return this.exec(parsedCommand[2], options);
|
|
318
217
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
parsedCommand.length >= 3 &&
|
|
322
|
-
/^[0-7]{3,4}$/.test(parsedCommand[1] ?? "")) {
|
|
323
|
-
const mode = Number.parseInt(parsedCommand[1], 8);
|
|
324
|
-
for (const target of parsedCommand.slice(2)) {
|
|
325
|
-
await this.client.chmod(this.session, this.vm, resolveExecPath(target), mode);
|
|
326
|
-
}
|
|
327
|
-
return { exitCode: 0, stdout: "", stderr: "" };
|
|
218
|
+
try {
|
|
219
|
+
await this.eventPump;
|
|
328
220
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
parsedCommand.length === 4 &&
|
|
332
|
-
parsedCommand[1] === "-c" &&
|
|
333
|
-
parsedCommand[2] === "%a") {
|
|
334
|
-
const stat = await this.stat(resolveExecPath(parsedCommand[3]));
|
|
335
|
-
return {
|
|
336
|
-
exitCode: 0,
|
|
337
|
-
stdout: `${(stat.mode & 0o777).toString(8)}\n`,
|
|
338
|
-
stderr: "",
|
|
339
|
-
};
|
|
221
|
+
catch (error) {
|
|
222
|
+
errors.push(toError(error));
|
|
340
223
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
options?.onStdout?.(chunk);
|
|
358
|
-
},
|
|
359
|
-
onStderr: (chunk) => {
|
|
360
|
-
stderrChunks.push(chunk);
|
|
361
|
-
options?.onStderr?.(chunk);
|
|
362
|
-
},
|
|
363
|
-
}));
|
|
224
|
+
try {
|
|
225
|
+
await this.onDispose?.();
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
errors.push(toError(error));
|
|
229
|
+
}
|
|
230
|
+
// Drop all per-VM tracking state so a disposed proxy retains nothing.
|
|
231
|
+
for (const entry of this.trackedProcesses.values()) {
|
|
232
|
+
entry.onStdout.clear();
|
|
233
|
+
entry.onStderr.clear();
|
|
234
|
+
}
|
|
235
|
+
this.trackedProcesses.clear();
|
|
236
|
+
this.trackedProcessesById.clear();
|
|
237
|
+
this.localMounts.length = 0;
|
|
238
|
+
if (errors.length > 0) {
|
|
239
|
+
throw new AggregateError(errors, "failed to dispose sidecar VM");
|
|
364
240
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
241
|
+
}
|
|
242
|
+
/** Test-only snapshot of the per-VM tracking collection sizes. */
|
|
243
|
+
__trackingSizesForTest() {
|
|
244
|
+
return {
|
|
245
|
+
trackedProcesses: this.trackedProcesses.size,
|
|
246
|
+
trackedProcessesById: this.trackedProcessesById.size,
|
|
247
|
+
localMounts: this.localMounts.length,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/** Test-only handle to a tracked entry so its listener Sets can be inspected. */
|
|
251
|
+
__trackedEntryForTest(pid) {
|
|
252
|
+
return this.trackedProcesses.get(pid);
|
|
253
|
+
}
|
|
254
|
+
async exec(command, options) {
|
|
255
|
+
const captureOutput = options?.captureStdio !== false;
|
|
256
|
+
const proc = (await this.spawnTracked("sh", [], {
|
|
257
|
+
env: options?.env,
|
|
258
|
+
cwd: options?.cwd,
|
|
259
|
+
timeout: options?.timeout,
|
|
260
|
+
onStdout: options?.onStdout,
|
|
261
|
+
onStderr: options?.onStderr,
|
|
262
|
+
shellCommand: command,
|
|
263
|
+
captureOutput,
|
|
264
|
+
}));
|
|
265
|
+
if (options?.stdin !== undefined) {
|
|
266
|
+
await proc.writeStdin(options.stdin);
|
|
267
|
+
}
|
|
268
|
+
await proc.closeStdin();
|
|
269
|
+
const completion = await proc[processCompletion];
|
|
270
|
+
return {
|
|
271
|
+
exitCode: completion.exitCode,
|
|
272
|
+
stdout: decodeUtf8(completion.stdout),
|
|
273
|
+
stderr: decodeUtf8(completion.stderr),
|
|
274
|
+
};
|
|
378
275
|
}
|
|
379
276
|
async execArgv(command, args = [], options) {
|
|
380
|
-
const
|
|
381
|
-
const
|
|
382
|
-
const effectiveCwd = options?.cwd ?? this.defaultExecCwd ?? this.cwd;
|
|
277
|
+
const captureOutput = options?.captureStdio !== false;
|
|
278
|
+
const requestedCwd = options?.cwd;
|
|
383
279
|
const runAndCapture = async (proc) => {
|
|
384
280
|
if (options?.stdin !== undefined) {
|
|
385
|
-
proc.writeStdin(options.stdin);
|
|
281
|
+
await proc.writeStdin(options.stdin);
|
|
386
282
|
}
|
|
387
|
-
proc.closeStdin();
|
|
388
|
-
const
|
|
389
|
-
const exitCode = typeof options?.timeout === "number"
|
|
390
|
-
? await new Promise((resolve) => {
|
|
391
|
-
const timer = setTimeout(() => {
|
|
392
|
-
proc.kill(9);
|
|
393
|
-
void proc.wait().then(resolve);
|
|
394
|
-
}, options.timeout);
|
|
395
|
-
void waitPromise.then((code) => {
|
|
396
|
-
clearTimeout(timer);
|
|
397
|
-
resolve(code);
|
|
398
|
-
});
|
|
399
|
-
})
|
|
400
|
-
: await waitPromise;
|
|
401
|
-
await drainTrailingProcessOutputTurn();
|
|
283
|
+
await proc.closeStdin();
|
|
284
|
+
const completion = await proc[processCompletion];
|
|
402
285
|
return {
|
|
403
|
-
exitCode,
|
|
404
|
-
stdout:
|
|
405
|
-
stderr:
|
|
286
|
+
exitCode: completion.exitCode,
|
|
287
|
+
stdout: decodeUtf8(completion.stdout),
|
|
288
|
+
stderr: decodeUtf8(completion.stderr),
|
|
406
289
|
};
|
|
407
290
|
};
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
stdoutChunks.push(chunk);
|
|
416
|
-
options?.onStdout?.(chunk);
|
|
417
|
-
},
|
|
418
|
-
onStderr: (chunk) => {
|
|
419
|
-
stderrChunks.push(chunk);
|
|
420
|
-
options?.onStderr?.(chunk);
|
|
421
|
-
},
|
|
291
|
+
const proc = (await this.spawnTracked(command, [...args], {
|
|
292
|
+
env: options?.env,
|
|
293
|
+
cwd: requestedCwd,
|
|
294
|
+
timeout: options?.timeout,
|
|
295
|
+
onStdout: options?.onStdout,
|
|
296
|
+
onStderr: options?.onStderr,
|
|
297
|
+
captureOutput,
|
|
422
298
|
}));
|
|
299
|
+
return runAndCapture(proc);
|
|
423
300
|
}
|
|
424
|
-
spawn(command, args, options) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if (!this.commands.has("sh")) {
|
|
432
|
-
throw new Error(`native sidecar shell-mode spawn requires guest shell command 'sh': ${command}`);
|
|
433
|
-
}
|
|
434
|
-
spawnCommand = "sh";
|
|
435
|
-
spawnArgs = ["-c", [command, ...args].join(" ")];
|
|
436
|
-
}
|
|
437
|
-
const pid = this.nextSyntheticPid++;
|
|
438
|
-
const processId = `proc-${pid}`;
|
|
301
|
+
async spawn(command, args, options) {
|
|
302
|
+
return this.spawnTracked(command, args, options);
|
|
303
|
+
}
|
|
304
|
+
async spawnTracked(command, args, options) {
|
|
305
|
+
const spawnCommand = command;
|
|
306
|
+
const spawnArgs = [...args];
|
|
307
|
+
const shellCommand = options?.shellCommand;
|
|
439
308
|
let resolveWait;
|
|
440
309
|
let rejectWait;
|
|
441
310
|
const waitPromise = new Promise((resolve, reject) => {
|
|
@@ -443,437 +312,85 @@ export class NativeSidecarKernelProxy {
|
|
|
443
312
|
rejectWait = reject;
|
|
444
313
|
});
|
|
445
314
|
const entry = {
|
|
446
|
-
pid,
|
|
447
|
-
processId,
|
|
448
|
-
command: spawnCommand,
|
|
315
|
+
pid: null,
|
|
316
|
+
processId: null,
|
|
317
|
+
command: shellCommand === undefined ? spawnCommand : undefined,
|
|
318
|
+
shellCommand,
|
|
449
319
|
args: spawnArgs,
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
env: {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
startTime: Date.now(),
|
|
457
|
-
exitTime: null,
|
|
458
|
-
hostPid: null,
|
|
320
|
+
requestedCwd: options?.cwd,
|
|
321
|
+
pty: options?.pty,
|
|
322
|
+
env: options?.env ? { ...options.env } : undefined,
|
|
323
|
+
keepStdinOpen: options?.streamStdin,
|
|
324
|
+
timeoutMs: toSidecarTimeoutMs(options?.timeout),
|
|
325
|
+
captureOutput: options?.captureOutput,
|
|
459
326
|
exitCode: null,
|
|
460
|
-
started: false,
|
|
461
|
-
startPromise: Promise.resolve(),
|
|
462
327
|
waitPromise,
|
|
463
328
|
resolveWait,
|
|
464
329
|
rejectWait,
|
|
330
|
+
settled: false,
|
|
465
331
|
onStdout: new Set(options?.onStdout ? [options.onStdout] : []),
|
|
466
332
|
onStderr: new Set(options?.onStderr ? [options.onStderr] : []),
|
|
467
|
-
pendingStdin: [],
|
|
468
|
-
stdinFlushPromise: null,
|
|
469
|
-
pendingCloseStdin: false,
|
|
470
|
-
pendingKillSignal: null,
|
|
471
|
-
waitWithFallbackPromise: null,
|
|
472
|
-
hostExitObservedAt: null,
|
|
473
|
-
outputGeneration: 0,
|
|
474
333
|
};
|
|
475
|
-
this.
|
|
476
|
-
|
|
477
|
-
|
|
334
|
+
await this.startTrackedProcess(entry);
|
|
335
|
+
if (entry.pid === null) {
|
|
336
|
+
throw new Error("sidecar did not return a kernel pid for the process");
|
|
337
|
+
}
|
|
338
|
+
const pid = entry.pid;
|
|
478
339
|
const proc = {
|
|
340
|
+
[processCompletion]: waitPromise,
|
|
479
341
|
pid,
|
|
480
|
-
writeStdin: (data) => {
|
|
342
|
+
writeStdin: async (data) => {
|
|
481
343
|
if (entry.exitCode !== null) {
|
|
482
344
|
return;
|
|
483
345
|
}
|
|
484
|
-
|
|
485
|
-
void this.flushPendingStdin(entry).catch((error) => {
|
|
486
|
-
this.handleBackgroundProcessError(entry, error);
|
|
487
|
-
});
|
|
346
|
+
await this.client.writeStdin(this.session, this.vm, this.processIdFor(entry), data);
|
|
488
347
|
},
|
|
489
|
-
closeStdin: () => {
|
|
490
|
-
entry.pendingCloseStdin = true;
|
|
491
|
-
void this.closeTrackedStdin(entry).catch((error) => {
|
|
492
|
-
this.handleBackgroundProcessError(entry, error);
|
|
493
|
-
});
|
|
494
|
-
},
|
|
495
|
-
kill: (signal = 15) => {
|
|
348
|
+
closeStdin: async () => {
|
|
496
349
|
if (entry.exitCode !== null) {
|
|
497
350
|
return;
|
|
498
351
|
}
|
|
499
|
-
|
|
500
|
-
void entry.startPromise.then(async () => {
|
|
501
|
-
if (entry.exitCode !== null || entry.pendingKillSignal === null) {
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
const pendingSignal = entry.pendingKillSignal;
|
|
505
|
-
entry.pendingKillSignal = null;
|
|
506
|
-
await this.signalProcess(entry, pendingSignal);
|
|
507
|
-
});
|
|
508
|
-
},
|
|
509
|
-
wait: async () => {
|
|
510
|
-
const exitCode = await this.waitForTrackedProcess(entry);
|
|
511
|
-
await this.drainTrailingProcessOutput(entry);
|
|
512
|
-
return exitCode;
|
|
352
|
+
await this.client.closeStdin(this.session, this.vm, this.processIdFor(entry));
|
|
513
353
|
},
|
|
354
|
+
kill: (signal = 15) => this.signalProcess(entry, signal),
|
|
355
|
+
wait: () => this.waitForTrackedProcess(entry),
|
|
514
356
|
get exitCode() {
|
|
515
357
|
return entry.exitCode;
|
|
516
358
|
},
|
|
517
359
|
};
|
|
518
|
-
entry.startPromise = this.startTrackedProcess(entry).catch((error) => {
|
|
519
|
-
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
520
|
-
const stderr = new TextEncoder().encode(`${normalized.message}\n`);
|
|
521
|
-
for (const handler of entry.onStderr) {
|
|
522
|
-
handler(stderr);
|
|
523
|
-
}
|
|
524
|
-
this.finishProcess(entry, 1);
|
|
525
|
-
});
|
|
526
360
|
return proc;
|
|
527
361
|
}
|
|
528
|
-
openShell(options) {
|
|
362
|
+
async openShell(options) {
|
|
529
363
|
const stdoutHandlers = new Set();
|
|
530
364
|
const stderrHandlers = new Set();
|
|
531
|
-
const command = options?.command ?? "sh";
|
|
532
|
-
const args = options?.args ??
|
|
533
|
-
(command === "sh" || command === "/bin/sh" ? ["-i"] : []);
|
|
534
|
-
const synthesizePrompt = !options?.command && !options?.args;
|
|
535
|
-
const autoCloseExplicitCommandStdin = Boolean(options?.command) && !["sh", "/bin/sh", "bash"].includes(command);
|
|
536
|
-
const promptText = "sh-0.4$ ";
|
|
537
|
-
const textEncoder = new TextEncoder();
|
|
538
|
-
const textDecoder = new TextDecoder();
|
|
539
|
-
const execCommand = this.exec.bind(this);
|
|
540
|
-
const spawnCommand = this.spawn.bind(this);
|
|
541
|
-
const sanitizeSyntheticShellText = (value) => value
|
|
542
|
-
.replace(/\u001b\[[0-9;]*m/g, "")
|
|
543
|
-
.replace(/^.*WARN could not retrieve pid for child process\n?/gm, "")
|
|
544
|
-
.replace(/^ProcessExitError:.*\n(?:\s+at .*\n)*/gm, "");
|
|
545
|
-
let bufferedInput = "";
|
|
546
|
-
let bufferedCommand = "";
|
|
547
|
-
let activeForegroundProcess = null;
|
|
548
|
-
let shellEnv = { ...(options?.env ?? {}) };
|
|
549
|
-
let shellCwd = options?.cwd ?? this.cwd;
|
|
550
|
-
let syntheticCommandQueue = Promise.resolve();
|
|
551
|
-
let promptTimer = null;
|
|
552
|
-
let closeStdinTimer = null;
|
|
553
|
-
let commandInFlight = false;
|
|
554
|
-
let syntheticCursorAtLineStart = true;
|
|
555
|
-
const syntheticPid = this.nextSyntheticPid++;
|
|
556
|
-
let syntheticExitCode = null;
|
|
557
|
-
let resolveSyntheticWait;
|
|
558
|
-
const syntheticWaitPromise = new Promise((resolve) => {
|
|
559
|
-
resolveSyntheticWait = resolve;
|
|
560
|
-
});
|
|
561
|
-
const clearPromptTimer = () => {
|
|
562
|
-
if (promptTimer !== null) {
|
|
563
|
-
clearTimeout(promptTimer);
|
|
564
|
-
promptTimer = null;
|
|
565
|
-
}
|
|
566
|
-
};
|
|
567
|
-
const clearCloseStdinTimer = () => {
|
|
568
|
-
if (closeStdinTimer !== null) {
|
|
569
|
-
clearTimeout(closeStdinTimer);
|
|
570
|
-
closeStdinTimer = null;
|
|
571
|
-
}
|
|
572
|
-
};
|
|
573
|
-
const normalizeSyntheticTerminalText = (text) => text.replace(/\r?\n/g, "\r\n");
|
|
574
|
-
const updateSyntheticCursor = (text) => {
|
|
575
|
-
if (!text) {
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
syntheticCursorAtLineStart = /(?:\r\n)$/.test(text);
|
|
579
|
-
};
|
|
580
|
-
const emitSyntheticStdout = (text) => {
|
|
581
|
-
if (!text) {
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
const normalized = normalizeSyntheticTerminalText(text);
|
|
585
|
-
updateSyntheticCursor(normalized);
|
|
586
|
-
const chunk = textEncoder.encode(normalized);
|
|
587
|
-
for (const handler of stdoutHandlers) {
|
|
588
|
-
handler(chunk);
|
|
589
|
-
}
|
|
590
|
-
};
|
|
591
|
-
const emitSyntheticTerminal = (text) => {
|
|
592
|
-
if (!text) {
|
|
593
|
-
return;
|
|
594
|
-
}
|
|
595
|
-
const normalized = normalizeSyntheticTerminalText(text);
|
|
596
|
-
updateSyntheticCursor(normalized);
|
|
597
|
-
const chunk = textEncoder.encode(normalized);
|
|
598
|
-
for (const handler of stdoutHandlers) {
|
|
599
|
-
handler(chunk);
|
|
600
|
-
}
|
|
601
|
-
};
|
|
602
|
-
const finishSyntheticShell = (exitCode) => {
|
|
603
|
-
if (syntheticExitCode !== null) {
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
syntheticExitCode = exitCode;
|
|
607
|
-
clearPromptTimer();
|
|
608
|
-
resolveSyntheticWait(exitCode);
|
|
609
|
-
};
|
|
610
|
-
const commandNeedsContinuation = (source) => {
|
|
611
|
-
let singleQuoted = false;
|
|
612
|
-
let doubleQuoted = false;
|
|
613
|
-
let escaped = false;
|
|
614
|
-
for (const character of source) {
|
|
615
|
-
if (escaped) {
|
|
616
|
-
escaped = false;
|
|
617
|
-
continue;
|
|
618
|
-
}
|
|
619
|
-
if (character === "\\") {
|
|
620
|
-
escaped = true;
|
|
621
|
-
continue;
|
|
622
|
-
}
|
|
623
|
-
if (!doubleQuoted && character === "'") {
|
|
624
|
-
singleQuoted = !singleQuoted;
|
|
625
|
-
continue;
|
|
626
|
-
}
|
|
627
|
-
if (!singleQuoted && character === '"') {
|
|
628
|
-
doubleQuoted = !doubleQuoted;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
return singleQuoted || doubleQuoted || escaped;
|
|
632
|
-
};
|
|
633
|
-
const emitPrompt = () => {
|
|
634
|
-
if (!synthesizePrompt) {
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
if (syntheticExitCode !== null) {
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
commandInFlight = false;
|
|
641
|
-
const promptPrefix = syntheticCursorAtLineStart ? "" : "\r\n";
|
|
642
|
-
const promptChunk = textEncoder.encode(`${promptPrefix}${promptText}`);
|
|
643
|
-
for (const handler of stdoutHandlers) {
|
|
644
|
-
handler(promptChunk);
|
|
645
|
-
}
|
|
646
|
-
syntheticCursorAtLineStart = false;
|
|
647
|
-
};
|
|
648
|
-
const schedulePrompt = (delayMs) => {
|
|
649
|
-
if (!synthesizePrompt) {
|
|
650
|
-
return;
|
|
651
|
-
}
|
|
652
|
-
clearPromptTimer();
|
|
653
|
-
promptTimer = setTimeout(() => {
|
|
654
|
-
promptTimer = null;
|
|
655
|
-
emitPrompt();
|
|
656
|
-
}, delayMs);
|
|
657
|
-
};
|
|
658
|
-
const parseForegroundCommand = (source) => {
|
|
659
|
-
const parsed = parseSimpleExecCommand(source);
|
|
660
|
-
const driver = parsed ? this.commands.get(parsed[0]) : undefined;
|
|
661
|
-
if (!parsed ||
|
|
662
|
-
!canUseDirectExec(driver, parsed[0]) ||
|
|
663
|
-
(driver === "wasmvm" && parsed[0] === "pwd")) {
|
|
664
|
-
return null;
|
|
665
|
-
}
|
|
666
|
-
return parsed;
|
|
667
|
-
};
|
|
668
|
-
const writeForegroundInput = (proc, data) => {
|
|
669
|
-
if (typeof data === "string") {
|
|
670
|
-
for (const character of data) {
|
|
671
|
-
proc.writeStdin(character);
|
|
672
|
-
}
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
for (const byte of data) {
|
|
676
|
-
proc.writeStdin(new Uint8Array([byte]));
|
|
677
|
-
}
|
|
678
|
-
};
|
|
679
365
|
let onData = null;
|
|
680
366
|
stdoutHandlers.add((data) => onData?.(data));
|
|
681
367
|
if (options?.onStderr) {
|
|
682
368
|
stderrHandlers.add(options.onStderr);
|
|
683
369
|
}
|
|
684
|
-
|
|
685
|
-
schedulePrompt(0);
|
|
686
|
-
return {
|
|
687
|
-
pid: syntheticPid,
|
|
688
|
-
write(data) {
|
|
689
|
-
if (syntheticExitCode !== null) {
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
692
|
-
if (activeForegroundProcess) {
|
|
693
|
-
const rawText = typeof data === "string"
|
|
694
|
-
? data
|
|
695
|
-
: Buffer.from(data).toString("utf8");
|
|
696
|
-
if (rawText.includes("\u0003")) {
|
|
697
|
-
const [beforeInterrupt] = rawText.split("\u0003");
|
|
698
|
-
if (beforeInterrupt) {
|
|
699
|
-
writeForegroundInput(activeForegroundProcess, beforeInterrupt);
|
|
700
|
-
}
|
|
701
|
-
emitSyntheticTerminal("^C\n");
|
|
702
|
-
activeForegroundProcess.kill(2);
|
|
703
|
-
return;
|
|
704
|
-
}
|
|
705
|
-
writeForegroundInput(activeForegroundProcess, data);
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
708
|
-
const rawText = typeof data === "string"
|
|
709
|
-
? data
|
|
710
|
-
: Buffer.from(data).toString("utf8");
|
|
711
|
-
let text = rawText;
|
|
712
|
-
if (rawText.includes("\u0003")) {
|
|
713
|
-
const segments = rawText.split("\u0003");
|
|
714
|
-
bufferedInput = "";
|
|
715
|
-
bufferedCommand = "";
|
|
716
|
-
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
717
|
-
emitSyntheticTerminal("^C\n");
|
|
718
|
-
emitPrompt();
|
|
719
|
-
}
|
|
720
|
-
text = segments[segments.length - 1] ?? "";
|
|
721
|
-
}
|
|
722
|
-
if (text.includes("\u0004") &&
|
|
723
|
-
bufferedInput.length === 0 &&
|
|
724
|
-
bufferedCommand.length === 0) {
|
|
725
|
-
finishSyntheticShell(0);
|
|
726
|
-
return;
|
|
727
|
-
}
|
|
728
|
-
bufferedInput += text.replace(/\u0004/g, "");
|
|
729
|
-
while (true) {
|
|
730
|
-
const newlineIndex = bufferedInput.indexOf("\n");
|
|
731
|
-
if (newlineIndex < 0) {
|
|
732
|
-
break;
|
|
733
|
-
}
|
|
734
|
-
const line = bufferedInput
|
|
735
|
-
.slice(0, newlineIndex)
|
|
736
|
-
.replace(/\r$/, "");
|
|
737
|
-
bufferedInput = bufferedInput.slice(newlineIndex + 1);
|
|
738
|
-
emitSyntheticStdout(`${line}\n`);
|
|
739
|
-
const nextCommand = bufferedCommand
|
|
740
|
-
? `${bufferedCommand}\n${line}`
|
|
741
|
-
: line;
|
|
742
|
-
if (commandNeedsContinuation(nextCommand)) {
|
|
743
|
-
bufferedCommand = nextCommand;
|
|
744
|
-
continue;
|
|
745
|
-
}
|
|
746
|
-
bufferedCommand = "";
|
|
747
|
-
syntheticCommandQueue = syntheticCommandQueue
|
|
748
|
-
.then(async () => {
|
|
749
|
-
const trimmed = nextCommand.trim();
|
|
750
|
-
if (!trimmed) {
|
|
751
|
-
emitPrompt();
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
const exitMatch = trimmed.match(/^exit(?:\s+(-?\d+))?$/);
|
|
755
|
-
if (exitMatch) {
|
|
756
|
-
finishSyntheticShell(Number.parseInt(exitMatch[1] ?? "0", 10));
|
|
757
|
-
return;
|
|
758
|
-
}
|
|
759
|
-
const exportMatch = trimmed.match(/^export\s+([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
760
|
-
if (exportMatch) {
|
|
761
|
-
shellEnv = {
|
|
762
|
-
...shellEnv,
|
|
763
|
-
[exportMatch[1]]: exportMatch[2],
|
|
764
|
-
};
|
|
765
|
-
emitPrompt();
|
|
766
|
-
return;
|
|
767
|
-
}
|
|
768
|
-
const cdMatch = trimmed.match(/^cd(?:\s+(.*))?$/);
|
|
769
|
-
if (cdMatch) {
|
|
770
|
-
const target = cdMatch[1]?.trim() || "/";
|
|
771
|
-
shellCwd = target.startsWith("/")
|
|
772
|
-
? posixPath.normalize(target)
|
|
773
|
-
: posixPath.normalize(posixPath.join(shellCwd, target));
|
|
774
|
-
emitPrompt();
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
777
|
-
const foregroundCommand = parseForegroundCommand(trimmed);
|
|
778
|
-
if (foregroundCommand) {
|
|
779
|
-
const proc = spawnCommand(foregroundCommand[0], foregroundCommand.slice(1), {
|
|
780
|
-
env: shellEnv,
|
|
781
|
-
cwd: shellCwd,
|
|
782
|
-
streamStdin: true,
|
|
783
|
-
onStdout: (chunk) => emitSyntheticTerminal(textDecoder.decode(chunk)),
|
|
784
|
-
onStderr: (chunk) => emitSyntheticTerminal(textDecoder.decode(chunk)),
|
|
785
|
-
});
|
|
786
|
-
activeForegroundProcess = proc;
|
|
787
|
-
try {
|
|
788
|
-
await proc.wait();
|
|
789
|
-
}
|
|
790
|
-
finally {
|
|
791
|
-
if (activeForegroundProcess === proc) {
|
|
792
|
-
activeForegroundProcess = null;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
emitPrompt();
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
|
-
const result = await execCommand(nextCommand, {
|
|
799
|
-
env: shellEnv,
|
|
800
|
-
cwd: shellCwd,
|
|
801
|
-
});
|
|
802
|
-
const sanitizedStdout = sanitizeSyntheticShellText(result.stdout);
|
|
803
|
-
if (sanitizedStdout) {
|
|
804
|
-
emitSyntheticStdout(sanitizedStdout);
|
|
805
|
-
}
|
|
806
|
-
const sanitizedStderr = sanitizeSyntheticShellText(result.stderr).replace(/^error: failed to execute command '([^']+)': .*$/gm, "error: command not found: $1");
|
|
807
|
-
if (sanitizedStderr) {
|
|
808
|
-
emitSyntheticTerminal(sanitizedStderr);
|
|
809
|
-
}
|
|
810
|
-
emitPrompt();
|
|
811
|
-
})
|
|
812
|
-
.catch((error) => {
|
|
813
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
814
|
-
emitSyntheticTerminal(`${message}\n`);
|
|
815
|
-
emitPrompt();
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
},
|
|
819
|
-
get onData() {
|
|
820
|
-
return onData;
|
|
821
|
-
},
|
|
822
|
-
set onData(handler) {
|
|
823
|
-
onData = handler;
|
|
824
|
-
},
|
|
825
|
-
resize() {
|
|
826
|
-
// Synthetic shells are terminal-less.
|
|
827
|
-
},
|
|
828
|
-
kill(signal = 15) {
|
|
829
|
-
finishSyntheticShell(128 + signal);
|
|
830
|
-
},
|
|
831
|
-
wait() {
|
|
832
|
-
return syntheticWaitPromise;
|
|
833
|
-
},
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
const proc = this.spawn(command, args, {
|
|
370
|
+
const proc = await this.spawnTracked(options?.command, options?.args ?? [], {
|
|
837
371
|
env: options?.env,
|
|
838
372
|
cwd: options?.cwd,
|
|
839
|
-
|
|
373
|
+
pty: { cols: options?.cols, rows: options?.rows },
|
|
840
374
|
onStdout: (chunk) => {
|
|
841
375
|
for (const handler of stdoutHandlers) {
|
|
842
376
|
handler(chunk);
|
|
843
377
|
}
|
|
844
|
-
if (commandInFlight) {
|
|
845
|
-
schedulePrompt(120);
|
|
846
|
-
}
|
|
847
378
|
},
|
|
848
379
|
onStderr: (chunk) => {
|
|
849
380
|
for (const handler of stderrHandlers) {
|
|
850
381
|
handler(chunk);
|
|
851
382
|
}
|
|
852
|
-
if (commandInFlight) {
|
|
853
|
-
schedulePrompt(120);
|
|
854
|
-
}
|
|
855
383
|
},
|
|
856
384
|
});
|
|
385
|
+
const entry = this.trackedProcesses.get(proc.pid);
|
|
386
|
+
if (!entry) {
|
|
387
|
+
throw new Error(`sidecar shell process ${proc.pid} is not tracked`);
|
|
388
|
+
}
|
|
857
389
|
return {
|
|
858
390
|
pid: proc.pid,
|
|
391
|
+
processId: this.processIdFor(entry),
|
|
859
392
|
write(data) {
|
|
860
|
-
|
|
861
|
-
return;
|
|
862
|
-
}
|
|
863
|
-
proc.writeStdin(data);
|
|
864
|
-
if (autoCloseExplicitCommandStdin) {
|
|
865
|
-
clearCloseStdinTimer();
|
|
866
|
-
closeStdinTimer = setTimeout(() => {
|
|
867
|
-
closeStdinTimer = null;
|
|
868
|
-
proc.closeStdin();
|
|
869
|
-
}, 100);
|
|
870
|
-
}
|
|
871
|
-
if (synthesizePrompt &&
|
|
872
|
-
typeof data === "string" &&
|
|
873
|
-
(data.includes("\n") || data.includes("\r"))) {
|
|
874
|
-
commandInFlight = true;
|
|
875
|
-
schedulePrompt(120);
|
|
876
|
-
}
|
|
393
|
+
return proc.writeStdin(data);
|
|
877
394
|
},
|
|
878
395
|
get onData() {
|
|
879
396
|
return onData;
|
|
@@ -881,298 +398,177 @@ export class NativeSidecarKernelProxy {
|
|
|
881
398
|
set onData(handler) {
|
|
882
399
|
onData = handler;
|
|
883
400
|
},
|
|
884
|
-
resize() {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
proc.kill(signal);
|
|
401
|
+
resize: (cols, rows) => {
|
|
402
|
+
const entry = this.trackedProcesses.get(proc.pid);
|
|
403
|
+
if (!entry || entry.exitCode !== null) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
return this.client.resizePty(this.session, this.vm, this.processIdFor(entry), Math.trunc(cols), Math.trunc(rows));
|
|
891
407
|
},
|
|
408
|
+
kill: (signal) => proc.kill(signal),
|
|
892
409
|
wait() {
|
|
893
|
-
clearPromptTimer();
|
|
894
410
|
return proc.wait();
|
|
895
411
|
},
|
|
896
412
|
};
|
|
897
413
|
}
|
|
898
|
-
async connectTerminal(options) {
|
|
899
|
-
const stdin = process.stdin;
|
|
900
|
-
const stdout = process.stdout;
|
|
901
|
-
const { onData, ...shellOptions } = options ?? {};
|
|
902
|
-
const shell = this.openShell({
|
|
903
|
-
...shellOptions,
|
|
904
|
-
onStderr: shellOptions.onStderr ??
|
|
905
|
-
((data) => {
|
|
906
|
-
process.stderr.write(data);
|
|
907
|
-
}),
|
|
908
|
-
});
|
|
909
|
-
const outputHandler = onData ??
|
|
910
|
-
((data) => {
|
|
911
|
-
stdout.write(data);
|
|
912
|
-
});
|
|
913
|
-
const restoreRawMode = stdin.isTTY && typeof stdin.setRawMode === "function";
|
|
914
|
-
const onStdinData = (data) => {
|
|
915
|
-
shell.write(data);
|
|
916
|
-
};
|
|
917
|
-
const onResize = () => {
|
|
918
|
-
shell.resize(stdout.columns, stdout.rows);
|
|
919
|
-
};
|
|
920
|
-
let cleanedUp = false;
|
|
921
|
-
const cleanup = () => {
|
|
922
|
-
if (cleanedUp) {
|
|
923
|
-
return;
|
|
924
|
-
}
|
|
925
|
-
cleanedUp = true;
|
|
926
|
-
stdin.removeListener("data", onStdinData);
|
|
927
|
-
stdin.pause();
|
|
928
|
-
if (restoreRawMode) {
|
|
929
|
-
stdin.setRawMode(false);
|
|
930
|
-
}
|
|
931
|
-
if (stdout.isTTY) {
|
|
932
|
-
stdout.removeListener("resize", onResize);
|
|
933
|
-
}
|
|
934
|
-
};
|
|
935
|
-
try {
|
|
936
|
-
if (restoreRawMode) {
|
|
937
|
-
stdin.setRawMode(true);
|
|
938
|
-
}
|
|
939
|
-
stdin.on("data", onStdinData);
|
|
940
|
-
stdin.resume();
|
|
941
|
-
shell.onData = outputHandler;
|
|
942
|
-
if (stdout.isTTY) {
|
|
943
|
-
stdout.on("resize", onResize);
|
|
944
|
-
shell.resize(stdout.columns, stdout.rows);
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
catch (error) {
|
|
948
|
-
cleanup();
|
|
949
|
-
shell.kill();
|
|
950
|
-
throw error;
|
|
951
|
-
}
|
|
952
|
-
void shell.wait().finally(() => {
|
|
953
|
-
cleanup();
|
|
954
|
-
});
|
|
955
|
-
return shell.pid;
|
|
956
|
-
}
|
|
957
414
|
readFile(path) {
|
|
958
|
-
return this.
|
|
415
|
+
return this.dispatchNativeRead(path);
|
|
959
416
|
}
|
|
960
|
-
writeFile(path, content) {
|
|
961
|
-
|
|
417
|
+
async writeFile(path, content) {
|
|
418
|
+
await this.waitForMountReconfigure();
|
|
419
|
+
await this.client.writeFile(this.session, this.vm, path, content);
|
|
962
420
|
}
|
|
963
|
-
async mkdir(path,
|
|
964
|
-
|
|
421
|
+
async mkdir(path, options) {
|
|
422
|
+
await this.waitForMountReconfigure();
|
|
423
|
+
await this.client.mkdir(this.session, this.vm, path, options);
|
|
965
424
|
}
|
|
966
425
|
async exists(path) {
|
|
967
|
-
const local = this.resolveLocalMount(path);
|
|
968
|
-
if (local) {
|
|
969
|
-
return local.mount.fs.exists(local.relativePath);
|
|
970
|
-
}
|
|
971
426
|
return this.client.exists(this.session, this.vm, path);
|
|
972
427
|
}
|
|
973
428
|
async stat(path) {
|
|
974
|
-
const local = this.resolveLocalMount(path);
|
|
975
|
-
if (local) {
|
|
976
|
-
return local.mount.fs.stat(local.relativePath);
|
|
977
|
-
}
|
|
978
429
|
return toVirtualStat(await this.client.stat(this.session, this.vm, path));
|
|
979
430
|
}
|
|
980
431
|
async readdir(path) {
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
}
|
|
985
|
-
const entries = await this.client.readdir(this.session, this.vm, path);
|
|
986
|
-
return [...new Set([...entries, ...this.mountedChildNames(path)])].sort((a, b) => a.localeCompare(b));
|
|
432
|
+
return this.client.readdir(this.session, this.vm, path);
|
|
433
|
+
}
|
|
434
|
+
async readdirRecursive(path, options) {
|
|
435
|
+
return (await this.client.readdirRecursive(this.session, this.vm, path, options)).map((entry) => ({ ...entry, size: Number(entry.size) }));
|
|
987
436
|
}
|
|
988
437
|
async removeFile(path) {
|
|
989
|
-
|
|
438
|
+
await this.client.removeFile(this.session, this.vm, path);
|
|
990
439
|
}
|
|
991
440
|
async removeDir(path) {
|
|
992
|
-
|
|
441
|
+
await this.client.removeDir(this.session, this.vm, path);
|
|
442
|
+
}
|
|
443
|
+
async removePath(path, options) {
|
|
444
|
+
await this.client.removePath(this.session, this.vm, path, options);
|
|
445
|
+
}
|
|
446
|
+
async copyPath(fromPath, toPath, options) {
|
|
447
|
+
return this.client.copyPath(this.session, this.vm, fromPath, toPath, options);
|
|
993
448
|
}
|
|
994
449
|
async rename(oldPath, newPath) {
|
|
995
|
-
const from = this.resolveLocalMount(oldPath);
|
|
996
|
-
const to = this.resolveLocalMount(newPath);
|
|
997
|
-
if (!!from !== !!to) {
|
|
998
|
-
throw errnoError("EXDEV", "cross-device link not permitted");
|
|
999
|
-
}
|
|
1000
|
-
if (from && to) {
|
|
1001
|
-
if (from.mount.path !== to.mount.path) {
|
|
1002
|
-
throw errnoError("EXDEV", "cross-device link not permitted");
|
|
1003
|
-
}
|
|
1004
|
-
this.assertLocalWritable(from.mount);
|
|
1005
|
-
return from.mount.fs.rename(from.relativePath, to.relativePath);
|
|
1006
|
-
}
|
|
1007
450
|
return this.client.rename(this.session, this.vm, oldPath, newPath);
|
|
1008
451
|
}
|
|
452
|
+
async movePath(oldPath, newPath) {
|
|
453
|
+
return this.client.movePath(this.session, this.vm, oldPath, newPath);
|
|
454
|
+
}
|
|
1009
455
|
mountFs(path, driver, options) {
|
|
1010
456
|
this.localMounts.unshift({
|
|
1011
457
|
path: posixPath.normalize(path),
|
|
1012
458
|
fs: driver,
|
|
1013
|
-
readOnly: options?.readOnly
|
|
459
|
+
readOnly: options?.readOnly,
|
|
460
|
+
sidecarMount: options?.sidecarMount,
|
|
1014
461
|
});
|
|
1015
462
|
this.localMounts.sort((left, right) => right.path.length - left.path.length);
|
|
463
|
+
return this.reconfigureSidecarMounts();
|
|
1016
464
|
}
|
|
1017
465
|
unmountFs(path) {
|
|
1018
466
|
const normalized = posixPath.normalize(path);
|
|
1019
467
|
const index = this.localMounts.findIndex((mount) => mount.path === normalized);
|
|
1020
|
-
if (index
|
|
1021
|
-
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
468
|
+
if (index < 0) {
|
|
469
|
+
return Promise.resolve();
|
|
470
|
+
}
|
|
471
|
+
this.localMounts.splice(index, 1);
|
|
472
|
+
return this.reconfigureSidecarMounts();
|
|
473
|
+
}
|
|
474
|
+
desiredSidecarMounts() {
|
|
475
|
+
return [
|
|
476
|
+
...this.baseSidecarMounts,
|
|
477
|
+
...this.localMounts.map((mount) => mount.sidecarMount ?? {
|
|
478
|
+
guestPath: mount.path,
|
|
479
|
+
readOnly: mount.readOnly,
|
|
480
|
+
plugin: {
|
|
481
|
+
id: "js_bridge",
|
|
482
|
+
},
|
|
483
|
+
}),
|
|
484
|
+
];
|
|
1026
485
|
}
|
|
1027
|
-
|
|
1028
|
-
const
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
}
|
|
1036
|
-
return this.listenerLookups.get(key)?.value ?? null;
|
|
1037
|
-
}
|
|
1038
|
-
findBoundUdp(request) {
|
|
1039
|
-
const key = socketLookupKey("udp", request);
|
|
1040
|
-
const cached = this.boundUdpLookups.get(key);
|
|
1041
|
-
if (!cached?.pending) {
|
|
1042
|
-
this.boundUdpLookups.set(key, {
|
|
1043
|
-
value: cached?.value ?? null,
|
|
1044
|
-
pending: this.refreshSocketLookup(this.boundUdpLookups, key, () => this.client.findBoundUdp(this.session, this.vm, request)),
|
|
486
|
+
reconfigureSidecarMounts() {
|
|
487
|
+
const run = async () => {
|
|
488
|
+
if (this.disposed) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
// Package projections are sidecar-owned and survive mount reconfiguration.
|
|
492
|
+
await this.client.configureVm(this.session, this.vm, {
|
|
493
|
+
mounts: this.desiredSidecarMounts(),
|
|
1045
494
|
});
|
|
1046
|
-
}
|
|
1047
|
-
|
|
495
|
+
};
|
|
496
|
+
const previous = this.mountReconfigurePromise ?? Promise.resolve();
|
|
497
|
+
const next = previous.then(run, run);
|
|
498
|
+
const tracked = next.finally(() => {
|
|
499
|
+
if (this.mountReconfigurePromise === tracked) {
|
|
500
|
+
this.mountReconfigurePromise = null;
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
this.mountReconfigurePromise = tracked;
|
|
504
|
+
return tracked;
|
|
1048
505
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
this.signalRefreshes.set(pid, this.refreshSignalState(entry));
|
|
506
|
+
async waitForMountReconfigure() {
|
|
507
|
+
if (this.mountReconfigurePromise) {
|
|
508
|
+
await this.mountReconfigurePromise;
|
|
1053
509
|
}
|
|
1054
|
-
return this.signalStates.get(pid) ?? { handlers: new Map() };
|
|
1055
510
|
}
|
|
1056
|
-
async
|
|
1057
|
-
|
|
1058
|
-
const socket = await lookup();
|
|
1059
|
-
cache.set(key, {
|
|
1060
|
-
value: socket ? toKernelSocketSnapshot(socket) : null,
|
|
1061
|
-
pending: null,
|
|
1062
|
-
});
|
|
1063
|
-
}
|
|
1064
|
-
catch {
|
|
1065
|
-
cache.set(key, {
|
|
1066
|
-
value: cache.get(key)?.value ?? null,
|
|
1067
|
-
pending: null,
|
|
1068
|
-
});
|
|
1069
|
-
}
|
|
511
|
+
async snapshotProcesses() {
|
|
512
|
+
return this.buildProcessSnapshot(await this.refreshProcessSnapshot());
|
|
1070
513
|
}
|
|
1071
|
-
async
|
|
1072
|
-
|
|
1073
|
-
const signalState = await this.client.getSignalState(this.session, this.vm, entry.processId);
|
|
1074
|
-
this.signalStates.set(entry.pid, toKernelSignalState(signalState.handlers));
|
|
1075
|
-
}
|
|
1076
|
-
catch {
|
|
1077
|
-
this.signalStates.set(entry.pid, this.signalStates.get(entry.pid) ?? { handlers: new Map() });
|
|
1078
|
-
}
|
|
1079
|
-
finally {
|
|
1080
|
-
this.signalRefreshes.delete(entry.pid);
|
|
1081
|
-
}
|
|
514
|
+
async processSnapshotById(processId) {
|
|
515
|
+
return (await this.refreshProcessSnapshot()).find((process) => process.processId === processId);
|
|
1082
516
|
}
|
|
1083
517
|
async refreshProcessSnapshot() {
|
|
1084
518
|
if (this.processSnapshotRefresh) {
|
|
1085
|
-
|
|
1086
|
-
return;
|
|
519
|
+
return this.processSnapshotRefresh;
|
|
1087
520
|
}
|
|
1088
521
|
this.processSnapshotRefresh = (async () => {
|
|
1089
522
|
try {
|
|
1090
|
-
|
|
523
|
+
return await this.client.getProcessSnapshot(this.session, this.vm);
|
|
1091
524
|
}
|
|
1092
525
|
finally {
|
|
1093
526
|
this.processSnapshotRefresh = null;
|
|
1094
527
|
}
|
|
1095
528
|
})();
|
|
1096
|
-
|
|
1097
|
-
}
|
|
1098
|
-
async refreshZombieTimerCount() {
|
|
1099
|
-
try {
|
|
1100
|
-
const snapshot = await this.client.getZombieTimerCount(this.session, this.vm);
|
|
1101
|
-
this.zombieTimerCountValue = snapshot.count;
|
|
1102
|
-
}
|
|
1103
|
-
catch {
|
|
1104
|
-
// Keep the last known value if the sidecar query fails.
|
|
1105
|
-
}
|
|
1106
|
-
finally {
|
|
1107
|
-
this.zombieTimerCountRefresh = null;
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
async drainTrailingProcessOutput(entry) {
|
|
1111
|
-
if (entry.onStdout.size === 0 && entry.onStderr.size === 0) {
|
|
1112
|
-
return;
|
|
1113
|
-
}
|
|
1114
|
-
let observedGeneration = entry.outputGeneration;
|
|
1115
|
-
let quietTurns = 0;
|
|
1116
|
-
let delayMs = 0;
|
|
1117
|
-
const deadline = Date.now() + TRAILING_OUTPUT_DRAIN_MAX_MS;
|
|
1118
|
-
while (quietTurns < TRAILING_OUTPUT_DRAIN_QUIET_TURNS) {
|
|
1119
|
-
const remainingMs = deadline - Date.now();
|
|
1120
|
-
if (remainingMs <= 0) {
|
|
1121
|
-
return;
|
|
1122
|
-
}
|
|
1123
|
-
await drainTrailingProcessOutputTurn(Math.min(delayMs, remainingMs));
|
|
1124
|
-
if (entry.outputGeneration === observedGeneration) {
|
|
1125
|
-
quietTurns += 1;
|
|
1126
|
-
}
|
|
1127
|
-
else {
|
|
1128
|
-
observedGeneration = entry.outputGeneration;
|
|
1129
|
-
quietTurns = 0;
|
|
1130
|
-
}
|
|
1131
|
-
delayMs = TRAILING_OUTPUT_DRAIN_INTERVAL_MS;
|
|
1132
|
-
}
|
|
529
|
+
return this.processSnapshotRefresh;
|
|
1133
530
|
}
|
|
1134
531
|
async startTrackedProcess(entry) {
|
|
532
|
+
await this.waitForMountReconfigure();
|
|
1135
533
|
const started = await this.client.execute(this.session, this.vm, {
|
|
1136
|
-
|
|
1137
|
-
|
|
534
|
+
...(entry.shellCommand !== undefined
|
|
535
|
+
? { shellCommand: entry.shellCommand }
|
|
536
|
+
: entry.command !== undefined
|
|
537
|
+
? { command: entry.command }
|
|
538
|
+
: {}),
|
|
1138
539
|
args: entry.args,
|
|
1139
|
-
env: entry.env,
|
|
1140
|
-
cwd: entry.
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
});
|
|
1150
|
-
void this.closeTrackedStdin(entry).catch((error) => {
|
|
1151
|
-
this.handleBackgroundProcessError(entry, error);
|
|
540
|
+
...(entry.env !== undefined ? { env: entry.env } : {}),
|
|
541
|
+
...(entry.requestedCwd !== undefined ? { cwd: entry.requestedCwd } : {}),
|
|
542
|
+
...(entry.pty ? { pty: entry.pty } : {}),
|
|
543
|
+
...(entry.keepStdinOpen !== undefined
|
|
544
|
+
? { keepStdinOpen: entry.keepStdinOpen }
|
|
545
|
+
: {}),
|
|
546
|
+
...(entry.timeoutMs !== undefined ? { timeoutMs: entry.timeoutMs } : {}),
|
|
547
|
+
...(entry.captureOutput !== undefined
|
|
548
|
+
? { captureOutput: entry.captureOutput }
|
|
549
|
+
: {}),
|
|
1152
550
|
});
|
|
1153
|
-
if (
|
|
1154
|
-
|
|
1155
|
-
entry.pendingKillSignal = null;
|
|
1156
|
-
await this.signalProcess(entry, signal);
|
|
551
|
+
if (started.pid === null) {
|
|
552
|
+
throw new Error("sidecar did not return a kernel pid for the process");
|
|
1157
553
|
}
|
|
554
|
+
entry.processId = started.processId;
|
|
555
|
+
entry.pid = started.pid;
|
|
556
|
+
this.trackedProcessesById.set(entry.processId, entry);
|
|
557
|
+
this.trackedProcesses.set(entry.pid, entry);
|
|
1158
558
|
}
|
|
1159
559
|
async runEventPump() {
|
|
560
|
+
// Scope the pump to THIS VM's ownership so multiple proxies can share one
|
|
561
|
+
// sidecar process: events for other VMs stay buffered for their own pumps
|
|
562
|
+
// rather than being consumed (and dropped) here.
|
|
563
|
+
const vmId = this.vm.vmId;
|
|
1160
564
|
while (!this.disposed) {
|
|
1161
565
|
try {
|
|
1162
|
-
const event = await this.client.waitForEvent(
|
|
1163
|
-
signal: this.eventPumpAbortController.signal,
|
|
1164
|
-
});
|
|
566
|
+
const event = await this.client.waitForEvent((frame) => frame.ownership.scope === "vm" && frame.ownership.vm_id === vmId, undefined, { signal: this.eventPumpAbortController.signal });
|
|
1165
567
|
if (event.payload.type === "process_output") {
|
|
1166
568
|
const entry = this.trackedProcessesById.get(event.payload.process_id);
|
|
1167
569
|
if (!entry) {
|
|
1168
570
|
continue;
|
|
1169
571
|
}
|
|
1170
|
-
entry.outputGeneration += 1;
|
|
1171
|
-
void this.refreshProcessSnapshot().catch(() => { });
|
|
1172
|
-
if (!this.signalRefreshes.has(entry.pid)) {
|
|
1173
|
-
this.signalRefreshes.set(entry.pid, this.refreshSignalState(entry));
|
|
1174
|
-
await this.signalRefreshes.get(entry.pid);
|
|
1175
|
-
}
|
|
1176
572
|
const chunk = event.payload.chunk;
|
|
1177
573
|
const listeners = event.payload.channel === "stdout"
|
|
1178
574
|
? entry.onStdout
|
|
@@ -1187,9 +583,17 @@ export class NativeSidecarKernelProxy {
|
|
|
1187
583
|
if (!entry) {
|
|
1188
584
|
continue;
|
|
1189
585
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
586
|
+
if (event.payload.error_code !== undefined) {
|
|
587
|
+
const error = new Error(event.payload.error_message ?? event.payload.error_code);
|
|
588
|
+
error.code = event.payload.error_code;
|
|
589
|
+
this.failProcess(entry, error);
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
this.finishProcess(entry, event.payload.exit_code, event.payload.stdout, event.payload.stderr);
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (event.payload.type === "structured") {
|
|
596
|
+
logStructuredSidecarEvent(event.payload.name, event.payload.detail);
|
|
1193
597
|
}
|
|
1194
598
|
}
|
|
1195
599
|
catch (error) {
|
|
@@ -1206,362 +610,113 @@ export class NativeSidecarKernelProxy {
|
|
|
1206
610
|
for (const listener of entry.onStderr) {
|
|
1207
611
|
listener(stderr);
|
|
1208
612
|
}
|
|
1209
|
-
this.
|
|
613
|
+
this.failProcess(entry, this.pumpError);
|
|
1210
614
|
}
|
|
1211
615
|
return;
|
|
1212
616
|
}
|
|
1213
617
|
}
|
|
1214
618
|
}
|
|
1215
|
-
finishProcess(entry, exitCode) {
|
|
1216
|
-
if (entry.
|
|
619
|
+
finishProcess(entry, exitCode, stdout = new Uint8Array(), stderr = new Uint8Array()) {
|
|
620
|
+
if (entry.settled) {
|
|
1217
621
|
return;
|
|
1218
622
|
}
|
|
623
|
+
entry.settled = true;
|
|
1219
624
|
entry.exitCode = exitCode;
|
|
1220
|
-
entry.
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
return entry.waitWithFallbackPromise;
|
|
625
|
+
entry.resolveWait({ exitCode, stdout, stderr });
|
|
626
|
+
// The sidecar guarantees that all process_output events precede the terminal
|
|
627
|
+
// process_exited event. Release client routing immediately after exit; the
|
|
628
|
+
// exited record lives on in `processes` for listing.
|
|
629
|
+
this.releaseProcessTracking(entry);
|
|
630
|
+
}
|
|
631
|
+
failProcess(entry, error) {
|
|
632
|
+
if (entry.settled) {
|
|
633
|
+
return;
|
|
1230
634
|
}
|
|
1231
|
-
entry.
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
const maybeExit = await Promise.race([
|
|
1235
|
-
entry.waitPromise.then((exitCode) => exitCode),
|
|
1236
|
-
new Promise((resolve) => setTimeout(() => resolve(null), 50)),
|
|
1237
|
-
]);
|
|
1238
|
-
if (maybeExit !== null) {
|
|
1239
|
-
return maybeExit;
|
|
1240
|
-
}
|
|
1241
|
-
try {
|
|
1242
|
-
await this.refreshProcessSnapshot();
|
|
1243
|
-
const snapshot = this.sidecarProcessSnapshot.find((candidate) => candidate.processId === entry.processId);
|
|
1244
|
-
if (snapshot?.status === "exited") {
|
|
1245
|
-
this.finishProcess(entry, snapshot.exitCode ?? 0);
|
|
1246
|
-
break;
|
|
1247
|
-
}
|
|
1248
|
-
if (snapshot) {
|
|
1249
|
-
entry.hostExitObservedAt = null;
|
|
1250
|
-
continue;
|
|
1251
|
-
}
|
|
1252
|
-
// Fast guest processes can exit before the sidecar emits a
|
|
1253
|
-
// `process_exited` event. Once a started process disappears from the
|
|
1254
|
-
// authoritative VM snapshot for a full grace window, treat it as
|
|
1255
|
-
// reaped even if the `pid` returned at launch was only a kernel/shared
|
|
1256
|
-
// runtime identifier rather than a probeable host PID.
|
|
1257
|
-
if (!snapshot) {
|
|
1258
|
-
const now = Date.now();
|
|
1259
|
-
if (entry.hostExitObservedAt === null) {
|
|
1260
|
-
entry.hostExitObservedAt = now;
|
|
1261
|
-
continue;
|
|
1262
|
-
}
|
|
1263
|
-
if (now - entry.hostExitObservedAt >= MISSING_EXIT_EVENT_GRACE_MS) {
|
|
1264
|
-
this.finishProcess(entry, 0);
|
|
1265
|
-
break;
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
catch {
|
|
1270
|
-
// Fall back to the next wait interval if the sidecar snapshot query fails.
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
return entry.waitPromise;
|
|
1274
|
-
})().finally(() => {
|
|
1275
|
-
entry.waitWithFallbackPromise = null;
|
|
1276
|
-
});
|
|
1277
|
-
return entry.waitWithFallbackPromise;
|
|
635
|
+
entry.settled = true;
|
|
636
|
+
entry.rejectWait(error);
|
|
637
|
+
this.releaseProcessTracking(entry);
|
|
1278
638
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
639
|
+
releaseProcessTracking(entry) {
|
|
640
|
+
if (entry.pid !== null) {
|
|
641
|
+
this.trackedProcesses.delete(entry.pid);
|
|
1282
642
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
return;
|
|
1286
|
-
}
|
|
1287
|
-
throw error;
|
|
643
|
+
if (entry.processId !== null) {
|
|
644
|
+
this.trackedProcessesById.delete(entry.processId);
|
|
1288
645
|
}
|
|
646
|
+
entry.onStdout.clear();
|
|
647
|
+
entry.onStderr.clear();
|
|
1289
648
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
return entry.stdinFlushPromise;
|
|
1293
|
-
}
|
|
1294
|
-
entry.stdinFlushPromise = entry.startPromise
|
|
1295
|
-
.then(async () => {
|
|
1296
|
-
if (entry.exitCode !== null) {
|
|
1297
|
-
return;
|
|
1298
|
-
}
|
|
1299
|
-
while (entry.pendingStdin.length > 0) {
|
|
1300
|
-
const chunk = entry.pendingStdin.shift();
|
|
1301
|
-
if (chunk === undefined) {
|
|
1302
|
-
break;
|
|
1303
|
-
}
|
|
1304
|
-
await this.client.writeStdin(this.session, this.vm, entry.processId, chunk);
|
|
1305
|
-
}
|
|
1306
|
-
})
|
|
1307
|
-
.catch((error) => {
|
|
1308
|
-
if (isNoSuchProcessError(error) || isUnknownVmError(error)) {
|
|
1309
|
-
return;
|
|
1310
|
-
}
|
|
1311
|
-
throw error;
|
|
1312
|
-
})
|
|
1313
|
-
.finally(() => {
|
|
1314
|
-
entry.stdinFlushPromise = null;
|
|
1315
|
-
if (entry.pendingStdin.length > 0 && entry.exitCode === null) {
|
|
1316
|
-
void this.flushPendingStdin(entry).catch((error) => {
|
|
1317
|
-
this.handleBackgroundProcessError(entry, error);
|
|
1318
|
-
});
|
|
1319
|
-
}
|
|
1320
|
-
});
|
|
1321
|
-
return entry.stdinFlushPromise;
|
|
649
|
+
waitForTrackedProcess(entry) {
|
|
650
|
+
return entry.waitPromise.then((completion) => completion.exitCode);
|
|
1322
651
|
}
|
|
1323
|
-
async
|
|
1324
|
-
await entry
|
|
1325
|
-
await this.flushPendingStdin(entry);
|
|
1326
|
-
if (entry.exitCode !== null || !entry.pendingCloseStdin) {
|
|
1327
|
-
return;
|
|
1328
|
-
}
|
|
1329
|
-
entry.pendingCloseStdin = false;
|
|
1330
|
-
try {
|
|
1331
|
-
await this.client.closeStdin(this.session, this.vm, entry.processId);
|
|
1332
|
-
}
|
|
1333
|
-
catch (error) {
|
|
1334
|
-
if (isNoSuchProcessError(error) || isUnknownVmError(error)) {
|
|
1335
|
-
return;
|
|
1336
|
-
}
|
|
1337
|
-
throw error;
|
|
1338
|
-
}
|
|
652
|
+
async signalProcess(entry, signal) {
|
|
653
|
+
await this.client.killProcess(this.session, this.vm, this.processIdFor(entry), toSidecarSignalName(signal));
|
|
1339
654
|
}
|
|
1340
|
-
|
|
1341
|
-
if (
|
|
1342
|
-
|
|
1343
|
-
isUnknownVmError(error)) {
|
|
1344
|
-
return;
|
|
1345
|
-
}
|
|
1346
|
-
if (entry.exitCode !== null) {
|
|
1347
|
-
this.recordCompletedProcessError(entry, error);
|
|
1348
|
-
return;
|
|
1349
|
-
}
|
|
1350
|
-
this.emitBackgroundProcessError(entry, error);
|
|
1351
|
-
this.finishProcess(entry, 1);
|
|
1352
|
-
}
|
|
1353
|
-
recordCompletedProcessError(entry, error) {
|
|
1354
|
-
if (this.disposed ||
|
|
1355
|
-
isNoSuchProcessError(error) ||
|
|
1356
|
-
isUnknownVmError(error)) {
|
|
1357
|
-
return entry.exitCode ?? 1;
|
|
1358
|
-
}
|
|
1359
|
-
this.emitBackgroundProcessError(entry, error);
|
|
1360
|
-
entry.exitCode =
|
|
1361
|
-
entry.exitCode === null || entry.exitCode === 0 ? 1 : entry.exitCode;
|
|
1362
|
-
entry.exitTime ??= Date.now();
|
|
1363
|
-
this.updateTrackedProcessSnapshot(entry);
|
|
1364
|
-
return entry.exitCode;
|
|
1365
|
-
}
|
|
1366
|
-
emitBackgroundProcessError(entry, error) {
|
|
1367
|
-
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
1368
|
-
const stderr = new TextEncoder().encode(`${normalized.message}\n`);
|
|
1369
|
-
for (const handler of entry.onStderr) {
|
|
1370
|
-
handler(stderr);
|
|
655
|
+
processIdFor(entry) {
|
|
656
|
+
if (entry.processId === null) {
|
|
657
|
+
throw new Error("sidecar process has not started");
|
|
1371
658
|
}
|
|
659
|
+
return entry.processId;
|
|
1372
660
|
}
|
|
1373
|
-
createFilesystemView(
|
|
661
|
+
createFilesystemView() {
|
|
1374
662
|
return {
|
|
1375
|
-
readFile: (path) => this.
|
|
1376
|
-
readTextFile: async (path) => new TextDecoder().decode(await this.
|
|
1377
|
-
readDir:
|
|
1378
|
-
const local = includeLocalMounts ? this.resolveLocalMount(path) : null;
|
|
1379
|
-
if (local) {
|
|
1380
|
-
return local.mount.fs.readDir(local.relativePath);
|
|
1381
|
-
}
|
|
1382
|
-
const entries = await this.client.readdir(this.session, this.vm, path);
|
|
1383
|
-
return includeLocalMounts
|
|
1384
|
-
? [...new Set([...entries, ...this.mountedChildNames(path)])].sort((a, b) => a.localeCompare(b))
|
|
1385
|
-
: entries;
|
|
1386
|
-
},
|
|
663
|
+
readFile: (path) => this.readFile(path),
|
|
664
|
+
readTextFile: async (path) => new TextDecoder().decode(await this.readFile(path)),
|
|
665
|
+
readDir: (path) => this.readdir(path),
|
|
1387
666
|
readDirWithTypes: async (path) => {
|
|
1388
|
-
const entries = await this.
|
|
667
|
+
const entries = await this.readdir(path);
|
|
1389
668
|
return Promise.all(entries.map(async (name) => {
|
|
1390
|
-
const stat = await this.
|
|
669
|
+
const stat = await this.client.lstat(this.session, this.vm, posixPath.join(path, name));
|
|
1391
670
|
return {
|
|
1392
671
|
name,
|
|
1393
|
-
isDirectory: stat.
|
|
1394
|
-
isSymbolicLink: stat.
|
|
672
|
+
isDirectory: stat.is_directory,
|
|
673
|
+
isSymbolicLink: stat.is_symbolic_link,
|
|
1395
674
|
};
|
|
1396
675
|
}));
|
|
1397
676
|
},
|
|
1398
|
-
writeFile: (path, content) => this.
|
|
1399
|
-
createDir:
|
|
677
|
+
writeFile: (path, content) => this.writeFile(path, content),
|
|
678
|
+
createDir: async (path) => {
|
|
1400
679
|
try {
|
|
1401
|
-
await this.client.mkdir(this.session, this.vm, path
|
|
1402
|
-
recursive: false,
|
|
1403
|
-
});
|
|
680
|
+
await this.client.mkdir(this.session, this.vm, path);
|
|
1404
681
|
}
|
|
1405
682
|
catch (error) {
|
|
1406
683
|
if (!isAlreadyExistsError(error)) {
|
|
1407
684
|
throw error;
|
|
1408
685
|
}
|
|
1409
686
|
}
|
|
1410
|
-
}, includeLocalMounts),
|
|
1411
|
-
mkdir: (path, options) => this.dispatchWrite(path, (mount, relativePath) => mount.fs.mkdir(relativePath, {
|
|
1412
|
-
recursive: options?.recursive ?? true,
|
|
1413
|
-
}), () => this.client.mkdir(this.session, this.vm, path, {
|
|
1414
|
-
recursive: options?.recursive ?? true,
|
|
1415
|
-
}), includeLocalMounts),
|
|
1416
|
-
exists: async (path) => {
|
|
1417
|
-
const local = includeLocalMounts ? this.resolveLocalMount(path) : null;
|
|
1418
|
-
if (local) {
|
|
1419
|
-
return local.mount.fs.exists(local.relativePath);
|
|
1420
|
-
}
|
|
1421
|
-
return this.client.exists(this.session, this.vm, path);
|
|
1422
|
-
},
|
|
1423
|
-
stat: async (path) => {
|
|
1424
|
-
const local = includeLocalMounts ? this.resolveLocalMount(path) : null;
|
|
1425
|
-
if (local) {
|
|
1426
|
-
return local.mount.fs.stat(local.relativePath);
|
|
1427
|
-
}
|
|
1428
|
-
return toVirtualStat(await this.client.stat(this.session, this.vm, path));
|
|
1429
|
-
},
|
|
1430
|
-
removeFile: (path) => this.dispatchWrite(path, (mount, relativePath) => mount.fs.removeFile(relativePath), () => this.client.removeFile(this.session, this.vm, path), includeLocalMounts),
|
|
1431
|
-
removeDir: (path) => this.dispatchWrite(path, (mount, relativePath) => mount.fs.removeDir(relativePath), () => this.client.removeDir(this.session, this.vm, path), includeLocalMounts),
|
|
1432
|
-
rename: async (oldPath, newPath) => {
|
|
1433
|
-
const from = includeLocalMounts
|
|
1434
|
-
? this.resolveLocalMount(oldPath)
|
|
1435
|
-
: null;
|
|
1436
|
-
const to = includeLocalMounts ? this.resolveLocalMount(newPath) : null;
|
|
1437
|
-
if (!!from !== !!to) {
|
|
1438
|
-
throw errnoError("EXDEV", "cross-device link not permitted");
|
|
1439
|
-
}
|
|
1440
|
-
if (from && to) {
|
|
1441
|
-
if (from.mount.path !== to.mount.path) {
|
|
1442
|
-
throw errnoError("EXDEV", "cross-device link not permitted");
|
|
1443
|
-
}
|
|
1444
|
-
this.assertLocalWritable(from.mount);
|
|
1445
|
-
return from.mount.fs.rename(from.relativePath, to.relativePath);
|
|
1446
|
-
}
|
|
1447
|
-
return this.client.rename(this.session, this.vm, oldPath, newPath);
|
|
1448
|
-
},
|
|
1449
|
-
realpath: async (path) => {
|
|
1450
|
-
const local = includeLocalMounts ? this.resolveLocalMount(path) : null;
|
|
1451
|
-
if (local) {
|
|
1452
|
-
return local.mount.fs.realpath(local.relativePath);
|
|
1453
|
-
}
|
|
1454
|
-
return this.client.realpath(this.session, this.vm, path);
|
|
1455
|
-
},
|
|
1456
|
-
symlink: (target, linkPath) => this.dispatchWrite(linkPath, (mount, relativePath) => mount.fs.symlink(target, relativePath), () => this.client.symlink(this.session, this.vm, target, linkPath), includeLocalMounts),
|
|
1457
|
-
readlink: async (path) => {
|
|
1458
|
-
const local = includeLocalMounts ? this.resolveLocalMount(path) : null;
|
|
1459
|
-
if (local) {
|
|
1460
|
-
return local.mount.fs.readlink(local.relativePath);
|
|
1461
|
-
}
|
|
1462
|
-
return this.client.readLink(this.session, this.vm, path);
|
|
1463
|
-
},
|
|
1464
|
-
lstat: async (path) => {
|
|
1465
|
-
const local = includeLocalMounts ? this.resolveLocalMount(path) : null;
|
|
1466
|
-
if (local) {
|
|
1467
|
-
return local.mount.fs.lstat(local.relativePath);
|
|
1468
|
-
}
|
|
1469
|
-
return toVirtualStat(await this.client.lstat(this.session, this.vm, path));
|
|
1470
|
-
},
|
|
1471
|
-
link: async (oldPath, newPath) => {
|
|
1472
|
-
const from = includeLocalMounts
|
|
1473
|
-
? this.resolveLocalMount(oldPath)
|
|
1474
|
-
: null;
|
|
1475
|
-
const to = includeLocalMounts ? this.resolveLocalMount(newPath) : null;
|
|
1476
|
-
if (!!from !== !!to) {
|
|
1477
|
-
throw errnoError("EXDEV", "cross-device link not permitted");
|
|
1478
|
-
}
|
|
1479
|
-
if (from && to) {
|
|
1480
|
-
if (from.mount.path !== to.mount.path) {
|
|
1481
|
-
throw errnoError("EXDEV", "cross-device link not permitted");
|
|
1482
|
-
}
|
|
1483
|
-
this.assertLocalWritable(from.mount);
|
|
1484
|
-
return from.mount.fs.link(from.relativePath, to.relativePath);
|
|
1485
|
-
}
|
|
1486
|
-
return this.client.link(this.session, this.vm, oldPath, newPath);
|
|
1487
|
-
},
|
|
1488
|
-
chmod: (path, mode) => this.dispatchWrite(path, (mount, relativePath) => mount.fs.chmod(relativePath, mode), () => this.client.chmod(this.session, this.vm, path, mode), includeLocalMounts),
|
|
1489
|
-
chown: (path, uid, gid) => this.dispatchWrite(path, (mount, relativePath) => mount.fs.chown(relativePath, uid, gid), () => this.client.chown(this.session, this.vm, path, uid, gid), includeLocalMounts),
|
|
1490
|
-
utimes: (path, atimeMs, mtimeMs) => this.dispatchWrite(path, (mount, relativePath) => mount.fs.utimes(relativePath, atimeMs, mtimeMs), () => this.client.utimes(this.session, this.vm, path, atimeMs, mtimeMs), includeLocalMounts),
|
|
1491
|
-
truncate: (path, length) => this.dispatchWrite(path, (mount, relativePath) => mount.fs.truncate(relativePath, length), () => this.client.truncate(this.session, this.vm, path, length), includeLocalMounts),
|
|
1492
|
-
pread: async (path, offset, length) => {
|
|
1493
|
-
const local = includeLocalMounts ? this.resolveLocalMount(path) : null;
|
|
1494
|
-
if (local) {
|
|
1495
|
-
return local.mount.fs.pread(local.relativePath, offset, length);
|
|
1496
|
-
}
|
|
1497
|
-
return this.client.pread(this.session, this.vm, path, offset, length);
|
|
1498
|
-
},
|
|
1499
|
-
pwrite: async (path, offset, data) => {
|
|
1500
|
-
const bytes = await this.createFilesystemView(includeLocalMounts).readFile(path);
|
|
1501
|
-
const nextSize = Math.max(bytes.length, offset + data.length);
|
|
1502
|
-
const updated = new Uint8Array(nextSize);
|
|
1503
|
-
updated.set(bytes);
|
|
1504
|
-
updated.set(data, offset);
|
|
1505
|
-
await this.createFilesystemView(includeLocalMounts).writeFile(path, updated);
|
|
1506
687
|
},
|
|
688
|
+
mkdir: (path, options) => this.mkdir(path, options),
|
|
689
|
+
exists: (path) => this.exists(path),
|
|
690
|
+
stat: (path) => this.stat(path),
|
|
691
|
+
removeFile: (path) => this.removeFile(path),
|
|
692
|
+
removeDir: (path) => this.removeDir(path),
|
|
693
|
+
rename: (oldPath, newPath) => this.client.rename(this.session, this.vm, oldPath, newPath),
|
|
694
|
+
realpath: (path) => this.client.realpath(this.session, this.vm, path),
|
|
695
|
+
symlink: (target, linkPath) => this.client.symlink(this.session, this.vm, target, linkPath),
|
|
696
|
+
readlink: (path) => this.client.readLink(this.session, this.vm, path),
|
|
697
|
+
lstat: async (path) => toVirtualStat(await this.client.lstat(this.session, this.vm, path)),
|
|
698
|
+
link: (oldPath, newPath) => this.client.link(this.session, this.vm, oldPath, newPath),
|
|
699
|
+
chmod: (path, mode) => this.client.chmod(this.session, this.vm, path, mode),
|
|
700
|
+
chown: (path, uid, gid) => this.client.chown(this.session, this.vm, path, uid, gid),
|
|
701
|
+
utimes: (path, atimeMs, mtimeMs) => this.client.utimes(this.session, this.vm, path, atimeMs, mtimeMs),
|
|
702
|
+
truncate: (path, length) => this.client.truncate(this.session, this.vm, path, length),
|
|
703
|
+
pread: (path, offset, length) => this.client.pread(this.session, this.vm, path, offset, length),
|
|
704
|
+
pwrite: (path, offset, data) => this.client.pwrite(this.session, this.vm, path, offset, data),
|
|
1507
705
|
};
|
|
1508
706
|
}
|
|
1509
|
-
buildProcessSnapshot() {
|
|
1510
|
-
void this.refreshProcessSnapshot().catch(() => { });
|
|
707
|
+
buildProcessSnapshot(snapshot) {
|
|
1511
708
|
const processMap = new Map();
|
|
1512
|
-
const
|
|
1513
|
-
for (const entry of this.sidecarProcessSnapshot) {
|
|
1514
|
-
const tracked = this.trackedProcessesById.get(entry.processId);
|
|
1515
|
-
if (tracked) {
|
|
1516
|
-
displayPidByKernelPid.set(entry.pid, tracked.pid);
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
for (const entry of this.sidecarProcessSnapshot) {
|
|
1520
|
-
const tracked = this.trackedProcessesById.get(entry.processId);
|
|
1521
|
-
const displayPid = displayPidByKernelPid.get(entry.pid) ?? entry.pid;
|
|
1522
|
-
const displayPpid = displayPidByKernelPid.get(entry.ppid) ?? entry.ppid;
|
|
1523
|
-
const displayPgid = displayPidByKernelPid.get(entry.pgid) ?? entry.pgid;
|
|
1524
|
-
const displaySid = displayPidByKernelPid.get(entry.sid) ?? entry.sid;
|
|
1525
|
-
const processKey = `${entry.processId}:${entry.pid}`;
|
|
1526
|
-
const startTime = tracked?.startTime ??
|
|
1527
|
-
this.observedProcessStartTimes.get(processKey) ??
|
|
1528
|
-
Date.now();
|
|
1529
|
-
this.observedProcessStartTimes.set(processKey, startTime);
|
|
1530
|
-
processMap.set(displayPid, {
|
|
1531
|
-
pid: displayPid,
|
|
1532
|
-
ppid: displayPpid,
|
|
1533
|
-
pgid: displayPgid,
|
|
1534
|
-
sid: displaySid,
|
|
1535
|
-
driver: tracked?.driver ?? entry.driver,
|
|
1536
|
-
command: tracked?.command ?? entry.command,
|
|
1537
|
-
args: tracked?.args ?? entry.args,
|
|
1538
|
-
cwd: tracked?.cwd ?? entry.cwd,
|
|
1539
|
-
status: tracked?.exitCode !== null
|
|
1540
|
-
? "exited"
|
|
1541
|
-
: tracked
|
|
1542
|
-
? "running"
|
|
1543
|
-
: entry.status === "exited"
|
|
1544
|
-
? "exited"
|
|
1545
|
-
: "running",
|
|
1546
|
-
exitCode: tracked?.exitCode ?? entry.exitCode,
|
|
1547
|
-
startTime,
|
|
1548
|
-
exitTime: tracked?.exitTime ?? null,
|
|
1549
|
-
});
|
|
1550
|
-
}
|
|
1551
|
-
for (const entry of this.trackedProcesses.values()) {
|
|
1552
|
-
if (processMap.has(entry.pid)) {
|
|
1553
|
-
continue;
|
|
1554
|
-
}
|
|
709
|
+
for (const entry of snapshot) {
|
|
1555
710
|
processMap.set(entry.pid, {
|
|
1556
711
|
pid: entry.pid,
|
|
1557
|
-
ppid:
|
|
1558
|
-
pgid: entry.
|
|
1559
|
-
sid: entry.
|
|
712
|
+
ppid: entry.ppid,
|
|
713
|
+
pgid: entry.pgid,
|
|
714
|
+
sid: entry.sid,
|
|
1560
715
|
driver: entry.driver,
|
|
1561
716
|
command: entry.command,
|
|
1562
717
|
args: entry.args,
|
|
1563
718
|
cwd: entry.cwd,
|
|
1564
|
-
status: entry.
|
|
719
|
+
status: entry.status,
|
|
1565
720
|
exitCode: entry.exitCode,
|
|
1566
721
|
startTime: entry.startTime,
|
|
1567
722
|
exitTime: entry.exitTime,
|
|
@@ -1573,119 +728,10 @@ export class NativeSidecarKernelProxy {
|
|
|
1573
728
|
}
|
|
1574
729
|
return [...processMap.values()].sort((left, right) => left.pid - right.pid);
|
|
1575
730
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
if (local) {
|
|
1579
|
-
return handler(local.mount, local.relativePath);
|
|
1580
|
-
}
|
|
1581
|
-
return this.dispatchNativeRead(path);
|
|
1582
|
-
}
|
|
1583
|
-
dispatchNativeRead(path) {
|
|
731
|
+
async dispatchNativeRead(path) {
|
|
732
|
+
await this.waitForMountReconfigure();
|
|
1584
733
|
return this.client.readFile(this.session, this.vm, path);
|
|
1585
734
|
}
|
|
1586
|
-
async dispatchWrite(path, handler, nativeHandler, includeLocalMounts = true) {
|
|
1587
|
-
this.assertGuestPathWritable(path);
|
|
1588
|
-
const local = includeLocalMounts ? this.resolveLocalMount(path) : null;
|
|
1589
|
-
if (local) {
|
|
1590
|
-
this.assertLocalWritable(local.mount);
|
|
1591
|
-
await handler(local.mount, local.relativePath);
|
|
1592
|
-
return;
|
|
1593
|
-
}
|
|
1594
|
-
await nativeHandler();
|
|
1595
|
-
}
|
|
1596
|
-
resolveLocalMount(path) {
|
|
1597
|
-
const normalizedPath = posixPath.normalize(path);
|
|
1598
|
-
for (const mount of this.localMounts) {
|
|
1599
|
-
if (normalizedPath !== mount.path &&
|
|
1600
|
-
!normalizedPath.startsWith(`${mount.path}/`)) {
|
|
1601
|
-
continue;
|
|
1602
|
-
}
|
|
1603
|
-
const relativePath = normalizedPath === mount.path
|
|
1604
|
-
? "/"
|
|
1605
|
-
: `/${normalizedPath.slice(mount.path.length + 1)}`;
|
|
1606
|
-
return {
|
|
1607
|
-
mount,
|
|
1608
|
-
relativePath,
|
|
1609
|
-
};
|
|
1610
|
-
}
|
|
1611
|
-
return null;
|
|
1612
|
-
}
|
|
1613
|
-
assertGuestPathWritable(path) {
|
|
1614
|
-
const normalizedPath = posixPath.normalize(path);
|
|
1615
|
-
for (const root of PROTECTED_READ_ONLY_GUEST_ROOTS) {
|
|
1616
|
-
if (normalizedPath === root || normalizedPath.startsWith(`${root}/`)) {
|
|
1617
|
-
throw errnoError("EROFS", "read-only file system");
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
mountedChildNames(path) {
|
|
1622
|
-
const normalizedPath = posixPath.normalize(path);
|
|
1623
|
-
const names = new Set();
|
|
1624
|
-
for (const mount of this.localMounts) {
|
|
1625
|
-
if (mount.path === normalizedPath) {
|
|
1626
|
-
continue;
|
|
1627
|
-
}
|
|
1628
|
-
if (!mount.path.startsWith(`${normalizedPath}/`) &&
|
|
1629
|
-
normalizedPath !== "/") {
|
|
1630
|
-
continue;
|
|
1631
|
-
}
|
|
1632
|
-
const relative = normalizedPath === "/"
|
|
1633
|
-
? mount.path.slice(1)
|
|
1634
|
-
: mount.path.slice(normalizedPath.length + 1);
|
|
1635
|
-
const name = relative.split("/").find(Boolean);
|
|
1636
|
-
if (name) {
|
|
1637
|
-
names.add(name);
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
return [...names];
|
|
1641
|
-
}
|
|
1642
|
-
assertLocalWritable(mount) {
|
|
1643
|
-
if (mount.readOnly) {
|
|
1644
|
-
throw errnoError("EROFS", "read-only file system");
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
updateTrackedProcessSnapshot(entry) {
|
|
1648
|
-
this.processes.set(entry.pid, {
|
|
1649
|
-
pid: entry.pid,
|
|
1650
|
-
ppid: 0,
|
|
1651
|
-
pgid: entry.pid,
|
|
1652
|
-
sid: entry.pid,
|
|
1653
|
-
driver: entry.driver,
|
|
1654
|
-
command: entry.command,
|
|
1655
|
-
args: entry.args,
|
|
1656
|
-
cwd: entry.cwd,
|
|
1657
|
-
status: entry.exitCode === null ? "running" : "exited",
|
|
1658
|
-
exitCode: entry.exitCode,
|
|
1659
|
-
startTime: entry.startTime,
|
|
1660
|
-
exitTime: entry.exitTime,
|
|
1661
|
-
});
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
function buildCommandMap(commandGuestPaths) {
|
|
1665
|
-
const commands = new Map([
|
|
1666
|
-
["node", "node"],
|
|
1667
|
-
["npm", "node"],
|
|
1668
|
-
["npx", "node"],
|
|
1669
|
-
]);
|
|
1670
|
-
for (const name of commandGuestPaths.keys()) {
|
|
1671
|
-
commands.set(name, "wasmvm");
|
|
1672
|
-
}
|
|
1673
|
-
return commands;
|
|
1674
|
-
}
|
|
1675
|
-
function isNoSuchProcessError(error) {
|
|
1676
|
-
if (!(error instanceof Error)) {
|
|
1677
|
-
return false;
|
|
1678
|
-
}
|
|
1679
|
-
const message = error.message.toLowerCase();
|
|
1680
|
-
return (error.message.includes("ESRCH") ||
|
|
1681
|
-
message.includes("no such process") ||
|
|
1682
|
-
message.includes("has no active process"));
|
|
1683
|
-
}
|
|
1684
|
-
function isUnknownVmError(error) {
|
|
1685
|
-
if (!(error instanceof Error)) {
|
|
1686
|
-
return false;
|
|
1687
|
-
}
|
|
1688
|
-
return error.message.toLowerCase().includes("unknown sidecar vm");
|
|
1689
735
|
}
|
|
1690
736
|
function isAlreadyExistsError(error) {
|
|
1691
737
|
if (!(error instanceof Error)) {
|
|
@@ -1694,63 +740,32 @@ function isAlreadyExistsError(error) {
|
|
|
1694
740
|
const message = error.message.toLowerCase();
|
|
1695
741
|
return error.message.includes("EEXIST") || message.includes("file exists");
|
|
1696
742
|
}
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
"code" in error &&
|
|
1701
|
-
error.code === "ESRCH");
|
|
1702
|
-
}
|
|
1703
|
-
function errnoError(code, message) {
|
|
1704
|
-
return Object.assign(new Error(`${code}: ${message}`), { code });
|
|
1705
|
-
}
|
|
743
|
+
// VirtualStat is a numeric, Node-default-shaped view: u64 fields above
|
|
744
|
+
// Number.MAX_SAFE_INTEGER lose precision here, same as Node's non-bigint
|
|
745
|
+
// fs.stat on the host.
|
|
1706
746
|
function toVirtualStat(stat) {
|
|
1707
747
|
return {
|
|
1708
748
|
mode: stat.mode,
|
|
1709
|
-
size: stat.size,
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
749
|
+
size: Number(stat.size),
|
|
750
|
+
sizeExact: stat.size,
|
|
751
|
+
blocks: Number(stat.blocks),
|
|
752
|
+
dev: Number(stat.dev),
|
|
753
|
+
rdev: Number(stat.rdev),
|
|
1713
754
|
isDirectory: stat.is_directory,
|
|
1714
755
|
isSymbolicLink: stat.is_symbolic_link,
|
|
1715
756
|
atimeMs: stat.atime_ms,
|
|
1716
757
|
mtimeMs: stat.mtime_ms,
|
|
1717
758
|
ctimeMs: stat.ctime_ms,
|
|
1718
759
|
birthtimeMs: stat.birthtime_ms,
|
|
1719
|
-
ino: stat.ino,
|
|
1720
|
-
|
|
760
|
+
ino: Number(stat.ino),
|
|
761
|
+
inoExact: stat.ino,
|
|
762
|
+
nlink: Number(stat.nlink),
|
|
763
|
+
nlinkExact: stat.nlink,
|
|
1721
764
|
uid: stat.uid,
|
|
1722
765
|
gid: stat.gid,
|
|
1723
766
|
};
|
|
1724
767
|
}
|
|
1725
|
-
|
|
1726
|
-
return {
|
|
1727
|
-
processId: socket.processId,
|
|
1728
|
-
...(socket.host !== undefined ? { host: socket.host } : {}),
|
|
1729
|
-
...(socket.port !== undefined ? { port: socket.port } : {}),
|
|
1730
|
-
...(socket.path !== undefined ? { path: socket.path } : {}),
|
|
1731
|
-
};
|
|
1732
|
-
}
|
|
1733
|
-
function toKernelSignalState(handlers) {
|
|
1734
|
-
return {
|
|
1735
|
-
handlers: new Map([...handlers.entries()].map(([signal, registration]) => [
|
|
1736
|
-
signal,
|
|
1737
|
-
{
|
|
1738
|
-
action: registration.action,
|
|
1739
|
-
mask: new Set(registration.mask),
|
|
1740
|
-
flags: registration.flags,
|
|
1741
|
-
},
|
|
1742
|
-
])),
|
|
1743
|
-
};
|
|
1744
|
-
}
|
|
1745
|
-
function socketLookupKey(kind, request) {
|
|
1746
|
-
return JSON.stringify({
|
|
1747
|
-
kind,
|
|
1748
|
-
host: request.host ?? null,
|
|
1749
|
-
port: request.port ?? null,
|
|
1750
|
-
path: request.path ?? null,
|
|
1751
|
-
});
|
|
1752
|
-
}
|
|
1753
|
-
export { NATIVE_SIDECAR_FRAME_TIMEOUT_MS, SidecarProcess, SidecarEventBufferOverflow, SidecarProcessError, SidecarProcessExited, } from "./native-process-client.js";
|
|
768
|
+
export { NativeSidecarProcessClient, SidecarEventBufferOverflow, SidecarProcess, SidecarProcessError, SidecarProcessExited, } from "./native-process-client.js";
|
|
1754
769
|
export class AgentOsSidecarVmHandle {
|
|
1755
770
|
client;
|
|
1756
771
|
sessionId;
|
|
@@ -1780,8 +795,8 @@ export class AgentOsSidecarSessionHandle {
|
|
|
1780
795
|
listVms() {
|
|
1781
796
|
return this.client.listVms(this.sessionId);
|
|
1782
797
|
}
|
|
1783
|
-
async createVm(
|
|
1784
|
-
return this.client.createVm(this.sessionId
|
|
798
|
+
async createVm() {
|
|
799
|
+
return this.client.createVm(this.sessionId);
|
|
1785
800
|
}
|
|
1786
801
|
async dispose() {
|
|
1787
802
|
await this.client.disposeSession(this.sessionId);
|
|
@@ -1802,13 +817,11 @@ export class AgentOsSidecarClient {
|
|
|
1802
817
|
this.assertActive();
|
|
1803
818
|
const sessionId = this.createId();
|
|
1804
819
|
const placement = clonePlacement(options.placement);
|
|
1805
|
-
const metadata = cloneMetadata(options.metadata);
|
|
1806
820
|
const lifecycle = {
|
|
1807
821
|
sessionId,
|
|
1808
822
|
placement,
|
|
1809
823
|
state: "connecting",
|
|
1810
824
|
createdAt: this.now(),
|
|
1811
|
-
metadata,
|
|
1812
825
|
vmIds: [],
|
|
1813
826
|
};
|
|
1814
827
|
const entry = {
|
|
@@ -1820,7 +833,6 @@ export class AgentOsSidecarClient {
|
|
|
1820
833
|
entry.transport = await this.createSessionTransport({
|
|
1821
834
|
sessionId,
|
|
1822
835
|
placement: clonePlacement(placement),
|
|
1823
|
-
metadata: cloneMetadata(metadata),
|
|
1824
836
|
signal: options.signal,
|
|
1825
837
|
});
|
|
1826
838
|
entry.lifecycle.state = "ready";
|
|
@@ -1848,21 +860,19 @@ export class AgentOsSidecarClient {
|
|
|
1848
860
|
const vmEntry = this.getVmEntry(sessionId, vmId);
|
|
1849
861
|
return cloneVmLifecycle(vmEntry.lifecycle);
|
|
1850
862
|
}
|
|
1851
|
-
async createVm(sessionId
|
|
863
|
+
async createVm(sessionId) {
|
|
1852
864
|
this.assertActive();
|
|
1853
865
|
const entry = this.getSessionEntry(sessionId);
|
|
1854
866
|
if (entry.lifecycle.state !== "ready" || !entry.transport) {
|
|
1855
867
|
throw new Error(`Cannot create VM for sidecar session ${sessionId} while it is ${entry.lifecycle.state}`);
|
|
1856
868
|
}
|
|
1857
869
|
const vmId = this.createId();
|
|
1858
|
-
const metadata = cloneMetadata(options.metadata);
|
|
1859
870
|
const vmEntry = {
|
|
1860
871
|
lifecycle: {
|
|
1861
872
|
vmId,
|
|
1862
873
|
sessionId,
|
|
1863
874
|
state: "creating",
|
|
1864
875
|
createdAt: this.now(),
|
|
1865
|
-
metadata,
|
|
1866
876
|
},
|
|
1867
877
|
};
|
|
1868
878
|
entry.vms.set(vmId, vmEntry);
|
|
@@ -1871,7 +881,6 @@ export class AgentOsSidecarClient {
|
|
|
1871
881
|
await entry.transport.createVm?.({
|
|
1872
882
|
vmId,
|
|
1873
883
|
sessionId,
|
|
1874
|
-
metadata: cloneMetadata(metadata),
|
|
1875
884
|
});
|
|
1876
885
|
vmEntry.lifecycle.state = "ready";
|
|
1877
886
|
vmEntry.lifecycle.readyAt = this.now();
|
|
@@ -1933,10 +942,10 @@ export class AgentOsSidecarClient {
|
|
|
1933
942
|
errors.push(toError(error));
|
|
1934
943
|
}
|
|
1935
944
|
}
|
|
1936
|
-
this.disposed = true;
|
|
1937
945
|
if (errors.length > 0) {
|
|
1938
946
|
throw new Error(errors.map((error) => error.message).join("; "));
|
|
1939
947
|
}
|
|
948
|
+
this.disposed = true;
|
|
1940
949
|
}
|
|
1941
950
|
async disposeVmEntry(sessionEntry, vmEntry) {
|
|
1942
951
|
if (vmEntry.lifecycle.state === "disposed" ||
|
|
@@ -1983,32 +992,32 @@ export function serializeMountConfigForSidecar(mount) {
|
|
|
1983
992
|
if ("driver" in mount) {
|
|
1984
993
|
return {
|
|
1985
994
|
guestPath: mount.path,
|
|
1986
|
-
readOnly: mount.readOnly
|
|
995
|
+
...(mount.readOnly === undefined ? {} : { readOnly: mount.readOnly }),
|
|
1987
996
|
plugin: {
|
|
1988
997
|
id: "js_bridge",
|
|
1989
|
-
config: {},
|
|
1990
998
|
},
|
|
1991
999
|
};
|
|
1992
1000
|
}
|
|
1993
1001
|
return {
|
|
1994
1002
|
guestPath: mount.path,
|
|
1995
|
-
readOnly: mount.readOnly
|
|
1003
|
+
...(mount.readOnly === undefined ? {} : { readOnly: mount.readOnly }),
|
|
1996
1004
|
plugin: {
|
|
1997
1005
|
id: mount.plugin.id,
|
|
1998
|
-
|
|
1006
|
+
...(mount.plugin.config === undefined
|
|
1007
|
+
? {}
|
|
1008
|
+
: { config: mount.plugin.config }),
|
|
1999
1009
|
},
|
|
2000
1010
|
};
|
|
2001
1011
|
}
|
|
2002
|
-
export function serializeRootFilesystemForSidecar(config
|
|
2003
|
-
const lowerInputs = [
|
|
2004
|
-
...(config?.lowers ?? []),
|
|
2005
|
-
...(bootstrapLower ? [bootstrapLower] : []),
|
|
2006
|
-
];
|
|
1012
|
+
export function serializeRootFilesystemForSidecar(config) {
|
|
2007
1013
|
return {
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
1014
|
+
...(config?.mode !== undefined ? { mode: config.mode } : {}),
|
|
1015
|
+
...(config?.disableDefaultBaseLayer !== undefined
|
|
1016
|
+
? { disableDefaultBaseLayer: config.disableDefaultBaseLayer }
|
|
1017
|
+
: {}),
|
|
1018
|
+
...(config?.lowers !== undefined
|
|
1019
|
+
? { lowers: config.lowers.map(serializeRootLowerForSidecar) }
|
|
1020
|
+
: {}),
|
|
2012
1021
|
};
|
|
2013
1022
|
}
|
|
2014
1023
|
function clonePlacement(placement) {
|
|
@@ -2023,21 +1032,16 @@ function clonePlacement(placement) {
|
|
|
2023
1032
|
sidecarId: placement.sidecarId,
|
|
2024
1033
|
};
|
|
2025
1034
|
}
|
|
2026
|
-
function cloneMetadata(metadata) {
|
|
2027
|
-
return { ...(metadata ?? {}) };
|
|
2028
|
-
}
|
|
2029
1035
|
function cloneSessionLifecycle(lifecycle) {
|
|
2030
1036
|
return {
|
|
2031
1037
|
...lifecycle,
|
|
2032
1038
|
placement: clonePlacement(lifecycle.placement),
|
|
2033
|
-
metadata: cloneMetadata(lifecycle.metadata),
|
|
2034
1039
|
vmIds: [...lifecycle.vmIds],
|
|
2035
1040
|
};
|
|
2036
1041
|
}
|
|
2037
1042
|
function cloneVmLifecycle(lifecycle) {
|
|
2038
1043
|
return {
|
|
2039
1044
|
...lifecycle,
|
|
2040
|
-
metadata: cloneMetadata(lifecycle.metadata),
|
|
2041
1045
|
};
|
|
2042
1046
|
}
|
|
2043
1047
|
function serializeRootLowerForSidecar(lower) {
|