@fungi.computer/computer 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -10
- package/dist/computer-backends.d.ts +0 -1
- package/dist/computer-backends.js +0 -3
- package/dist/exec-output.d.ts +13 -0
- package/dist/exec-output.js +24 -0
- package/dist/execution-lifecycle.d.ts +35 -0
- package/dist/execution-lifecycle.js +78 -0
- package/dist/files-patch.d.ts +12 -4
- package/dist/files-patch.js +8 -14
- package/dist/files.d.ts +65 -15
- package/dist/files.js +152 -31
- package/dist/fleet/index.d.ts +1 -1
- package/dist/fleet/index.js +1 -1
- package/dist/fleet/inventory-wire.d.ts +6 -2
- package/dist/fleet/inventory-wire.js +7 -2
- package/dist/fleet/lifecycle.d.ts +7 -1
- package/dist/fleet/lifecycle.js +22 -0
- package/dist/fleet/wire.d.ts +12 -12
- package/dist/fleet/wire.js +1 -1
- package/dist/index.d.ts +33 -15
- package/dist/index.js +422 -105
- package/dist/internet-access.d.ts +4 -0
- package/dist/internet-access.js +14 -0
- package/dist/local-backend-replay.d.ts +10 -0
- package/dist/local-backend-replay.js +43 -0
- package/dist/local-backend.d.ts +2 -1
- package/dist/local-backend.js +4 -4
- package/dist/on-box-vm.d.ts +11 -0
- package/dist/on-box-vm.js +36 -13
- package/dist/operation-record.d.ts +1 -1
- package/dist/operation-record.js +1 -1
- package/dist/process-inventory.d.ts +3 -1
- package/dist/process-inventory.js +2 -2
- package/dist/team-candidate-builder.mjs +8375 -334
- package/package.json +17 -24
- package/dist/light-shell.d.ts +0 -44
- package/dist/light-shell.js +0 -376
- package/dist/operation-contract.d.ts +0 -456
- package/dist/operation-contract.js +0 -450
package/dist/index.js
CHANGED
|
@@ -50,6 +50,9 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
50
|
var e = new Error(message);
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
|
+
import { boundExecStream } from "./exec-output.js";
|
|
54
|
+
export { createLocalBackendReplayStore } from "./local-backend-replay.js";
|
|
55
|
+
import { computerInternetAccess } from "./internet-access.js";
|
|
53
56
|
import { getWorkspace, SQLiteWorkspaceProvider, Workspace, WorkspaceProxy, WorkspaceServiceProxy, } from "@fungi.computer/cloudflare-computer";
|
|
54
57
|
import { WorkerShellBackend, } from "@fungi.computer/cloudflare-computer/backends/worker-shell";
|
|
55
58
|
import jqCommands from "@fungi.computer/cloudflare-computer/shell/jq";
|
|
@@ -58,16 +61,19 @@ import xanCommands from "@fungi.computer/cloudflare-computer/shell/xan";
|
|
|
58
61
|
import fileCommands from "@fungi.computer/cloudflare-computer/shell/file";
|
|
59
62
|
import { withWorkspaceContainer, } from "@fungi.computer/cloudflare-computer/backends/container";
|
|
60
63
|
import { DurableObject } from "cloudflare:workers";
|
|
64
|
+
import * as Effect from "effect/Effect";
|
|
65
|
+
import * as Redacted from "effect/Redacted";
|
|
61
66
|
import { z } from "zod";
|
|
62
67
|
import { SystemTrustContainerBackend } from "./container-system-trust.js";
|
|
63
68
|
import { computerTransportBackend } from "./transport-backend.js";
|
|
64
69
|
import { PullPendingTracker } from "./pull-pending.js";
|
|
65
|
-
import { ComputerFileConflict, ComputerFileRangeError, ComputerFileTooLargeError, capabilityNameSchema, computerValueSchema, computerCapabilities, computerPortInputSchema, computerPortOutputSchema, computerPortRemovalOutputSchema, computerPortsOutputSchema, computerPortSchema, ComputerPortLimitError, ComputerPortReservedError, computerTerminalPort, isComputerReservedPort, maximumExposedPorts, maximumProcessRecords, execInputSchema, execOutputSchema, computerToolResult, computerExecutionRequestSchema, genericComputerExecutionBudget, mintComputerRequestId, listInputSchema, listOutputSchema, maximumFileBytes,
|
|
66
|
-
import { createWorkspaceFilesPort, readBoundedWorkspaceText } from "./files.js";
|
|
70
|
+
import { ComputerFileConflict, ComputerFileRangeError, ComputerFileTooLargeError, capabilityNameSchema, computerValueSchema, computerCapabilities, computerPortInputSchema, computerPortOutputSchema, computerPortRemovalOutputSchema, computerPortsOutputSchema, computerPortSchema, ComputerPortLimitError, ComputerPortReservedError, computerTerminalPort, isComputerReservedPort, maximumExposedPorts, maximumProcessRecords, execInputSchema, execOutputSchema, computerToolResult, computerExecutionRequestSchema, genericComputerExecutionBudget, mintComputerRequestId, listInputSchema, listOutputSchema, maximumFileBytes, mkdirInputSchema, mkdirOutputSchema, readInputSchema, readBytesInputSchema, readOutputSchema, replaceInputSchema, writeInputSchema, writeOutputSchema, processIdInputSchema, processKillOutputSchema, processListOutputSchema, processStartInputSchema, processStartToolInputSchema, ComputerProcessNotFoundError, } from "@fungi.computer/computer-contract";
|
|
71
|
+
import { createWorkspaceFilesPort, readBoundedWorkspaceText, listWorkspaceFiles, } from "./files.js";
|
|
67
72
|
import { computerExecutionResult } from "./execution-result.js";
|
|
68
73
|
import { ComputerOperationRecord } from "./operation-record.js";
|
|
74
|
+
import { createComputerExecutionLifecycle } from "./execution-lifecycle.js";
|
|
69
75
|
import { applyWorkspacePatch } from "./files-patch.js";
|
|
70
|
-
import { ComputerProcessInventory, drainProcessExit, observeProcess, } from "./process-inventory.js";
|
|
76
|
+
import { ComputerProcessInventory, drainProcessExit, failedObservation, observeProcess, } from "./process-inventory.js";
|
|
71
77
|
import { assertComputerContainersEnabled, computerContainerSleepAfterMs, ComputerContainersPausedError, configureComputerContainer, hostedComputerContainersPaused, } from "./container-policy.js";
|
|
72
78
|
import { OnBoxVmContainerHost, onBoxVmPlacement, } from "./on-box-vm.js";
|
|
73
79
|
import { attachedBackendId, describeAttachedBackend, routeComputerBackend, } from "./computer-backends.js";
|
|
@@ -96,23 +102,6 @@ const terminalQuerySchema = z.lazy(() => z.strictObject({
|
|
|
96
102
|
cols: z.coerce.number().int().min(2).max(500).catch(terminalDefaultCols),
|
|
97
103
|
rows: z.coerce.number().int().min(2).max(300).catch(terminalDefaultRows),
|
|
98
104
|
}));
|
|
99
|
-
function entryType(entry) {
|
|
100
|
-
if (entry.isFile)
|
|
101
|
-
return "file";
|
|
102
|
-
if (entry.isDirectory)
|
|
103
|
-
return "directory";
|
|
104
|
-
return "symlink";
|
|
105
|
-
}
|
|
106
|
-
function truncate(value) {
|
|
107
|
-
const bytes = utf8.encode(value);
|
|
108
|
-
if (bytes.byteLength <= maximumStreamBytes) {
|
|
109
|
-
return { value, truncated: false };
|
|
110
|
-
}
|
|
111
|
-
return {
|
|
112
|
-
value: new TextDecoder().decode(bytes.slice(0, maximumStreamBytes)),
|
|
113
|
-
truncated: true,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
105
|
async function read(workspace, input) {
|
|
117
106
|
const { path } = readInputSchema.parse(input);
|
|
118
107
|
const stat = await workspace.fs.stat(path);
|
|
@@ -127,21 +116,7 @@ async function read(workspace, input) {
|
|
|
127
116
|
});
|
|
128
117
|
}
|
|
129
118
|
async function list(workspace, input) {
|
|
130
|
-
|
|
131
|
-
const rows = await workspace.fs.readdir(path, {
|
|
132
|
-
limit: limit + 1,
|
|
133
|
-
offset,
|
|
134
|
-
});
|
|
135
|
-
return listOutputSchema.parse({
|
|
136
|
-
path,
|
|
137
|
-
entries: rows.slice(0, limit).map((entry) => ({
|
|
138
|
-
name: entry.name,
|
|
139
|
-
size: entry.size,
|
|
140
|
-
modifiedAt: entry.mtime,
|
|
141
|
-
type: entryType(entry),
|
|
142
|
-
})),
|
|
143
|
-
nextOffset: rows.length > limit ? offset + limit : null,
|
|
144
|
-
});
|
|
119
|
+
return listOutputSchema.parse(await listWorkspaceFiles(workspace, listInputSchema.parse(input)));
|
|
145
120
|
}
|
|
146
121
|
async function mkdir(workspace, input) {
|
|
147
122
|
const { path, recursive } = mkdirInputSchema.parse(input);
|
|
@@ -156,7 +131,7 @@ async function write(workspace, input) {
|
|
|
156
131
|
bytes: utf8.encode(content).byteLength,
|
|
157
132
|
});
|
|
158
133
|
}
|
|
159
|
-
async function exec(workspace, input, backend, signal) {
|
|
134
|
+
async function exec(workspace, input, backend, signal, execution) {
|
|
160
135
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
161
136
|
try {
|
|
162
137
|
// The host request has already been decoded under its admitted budget.
|
|
@@ -165,15 +140,23 @@ async function exec(workspace, input, backend, signal) {
|
|
|
165
140
|
const options = cwd === undefined
|
|
166
141
|
? { encoding: "utf8", timeoutMs, ...backendOption }
|
|
167
142
|
: { encoding: "utf8", timeoutMs, cwd, ...backendOption };
|
|
168
|
-
const
|
|
143
|
+
const nativeOptions = {
|
|
144
|
+
...options,
|
|
145
|
+
...execution?.carriage,
|
|
146
|
+
};
|
|
147
|
+
if (execution !== undefined)
|
|
148
|
+
nativeOptions.id = execution.invocation.id;
|
|
149
|
+
execution?.dispatch();
|
|
150
|
+
const run = __addDisposableResource(env_1, await workspace.runtime.exec(command, nativeOptions), false);
|
|
151
|
+
execution?.opened();
|
|
169
152
|
const result = await computerExecutionResult(run, signal);
|
|
170
153
|
const synchronizationFailure = result.sync.status === "pending"
|
|
171
154
|
? `Computer command filesystem synchronization failed: ${result.sync.error}`
|
|
172
155
|
: null;
|
|
173
|
-
const stdout =
|
|
174
|
-
const stderr =
|
|
156
|
+
const stdout = boundExecStream(result.stdout, "stdout");
|
|
157
|
+
const stderr = boundExecStream(synchronizationFailure === null
|
|
175
158
|
? result.stderr
|
|
176
|
-
: `${synchronizationFailure}${result.stderr.length === 0 ? "" : `\n${result.stderr}`}
|
|
159
|
+
: `${synchronizationFailure}${result.stderr.length === 0 ? "" : `\n${result.stderr}`}`, "stderr");
|
|
177
160
|
return execOutputSchema.parse({
|
|
178
161
|
status: synchronizationFailure === null ? result.status : "failed",
|
|
179
162
|
exitCode: synchronizationFailure !== null && result.exitCode === 0
|
|
@@ -181,7 +164,9 @@ async function exec(workspace, input, backend, signal) {
|
|
|
181
164
|
: result.exitCode,
|
|
182
165
|
stdout: stdout.value,
|
|
183
166
|
stderr: stderr.value,
|
|
184
|
-
truncated: stdout.
|
|
167
|
+
truncated: stdout.truncation.length + stderr.truncation.length === 0
|
|
168
|
+
? false
|
|
169
|
+
: [...stdout.truncation, ...stderr.truncation],
|
|
185
170
|
});
|
|
186
171
|
}
|
|
187
172
|
catch (e_1) {
|
|
@@ -295,7 +280,7 @@ class ContainerComputer extends ContainerComputerBase {
|
|
|
295
280
|
attachedBackendId(candidate) === backendId);
|
|
296
281
|
if (backend === undefined ||
|
|
297
282
|
computerName !== this.state.id.name ||
|
|
298
|
-
!(await verifyLocalBackendDial(request, computerName, backend)))
|
|
283
|
+
!(await verifyLocalBackendDial(request, computerName, backend, this.state.storage)))
|
|
299
284
|
return new Response(null, { status: 404 });
|
|
300
285
|
// A verified plain request lets the machine tell attached from detached.
|
|
301
286
|
if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket")
|
|
@@ -323,9 +308,10 @@ class ContainerComputer extends ContainerComputerBase {
|
|
|
323
308
|
* The hosted backend that terminals, processes and ports use: the first in
|
|
324
309
|
* the default order, or none when only local machines are attached.
|
|
325
310
|
*/
|
|
326
|
-
hostedRoute() {
|
|
311
|
+
hostedRoute(backend) {
|
|
327
312
|
const route = routeComputerBackend(this.attachedBackends(), () => false, {
|
|
328
313
|
need: "hosted",
|
|
314
|
+
backend,
|
|
329
315
|
});
|
|
330
316
|
if (route.kind !== "routed" || route.backend.kind === "local")
|
|
331
317
|
return undefined;
|
|
@@ -456,9 +442,8 @@ class ContainerComputer extends ContainerComputerBase {
|
|
|
456
442
|
}
|
|
457
443
|
/**
|
|
458
444
|
* The current Workspace once its mount index is ready and its guest mount
|
|
459
|
-
* root exists. Every backend mounts `/workspace`, so
|
|
460
|
-
*
|
|
461
|
-
* on a guest having booted first.
|
|
445
|
+
* root exists. Every backend mounts `/workspace`, so an ordinary host file
|
|
446
|
+
* write there must not depend on a guest having booted first.
|
|
462
447
|
*/
|
|
463
448
|
async readyWorkspace() {
|
|
464
449
|
const workspace = this.workspace();
|
|
@@ -496,9 +481,20 @@ class ContainerComputer extends ContainerComputerBase {
|
|
|
496
481
|
}
|
|
497
482
|
/** One upstream Cloudflare Workspace that advertises and runs its capabilities. */
|
|
498
483
|
export class ComputerDO extends ContainerComputer {
|
|
484
|
+
constructor(ctx, env) {
|
|
485
|
+
super(ctx, env);
|
|
486
|
+
// Reconstruction revokes unfinished exec authority before the first RPC.
|
|
487
|
+
void this.#executions;
|
|
488
|
+
}
|
|
489
|
+
/** Host-admitted settings write; egress reads the current durable value. */
|
|
490
|
+
internetAccess(enabled) {
|
|
491
|
+
return computerInternetAccess(this.state.storage.sql, enabled);
|
|
492
|
+
}
|
|
499
493
|
#binaryFileTail = Promise.resolve(undefined);
|
|
500
494
|
#operationRecord;
|
|
501
495
|
#processRecord;
|
|
496
|
+
#executionRecord;
|
|
497
|
+
#processStarts = new Map();
|
|
502
498
|
#terminalSync;
|
|
503
499
|
get #processes() {
|
|
504
500
|
this.#processRecord ??= new ComputerProcessInventory(this.state.storage, () => Date.now());
|
|
@@ -508,6 +504,128 @@ export class ComputerDO extends ContainerComputer {
|
|
|
508
504
|
this.#operationRecord ??= new ComputerOperationRecord(this.state.storage, () => Date.now());
|
|
509
505
|
return this.#operationRecord;
|
|
510
506
|
}
|
|
507
|
+
/** Ordinary Computers have no credential host; resource hosts override it. */
|
|
508
|
+
executionHooks() {
|
|
509
|
+
return undefined;
|
|
510
|
+
}
|
|
511
|
+
get #executions() {
|
|
512
|
+
if (this.#executionRecord === undefined) {
|
|
513
|
+
// Initialize the existing retention owners before their dependent table.
|
|
514
|
+
void this.#operations;
|
|
515
|
+
void this.#processes;
|
|
516
|
+
const executions = createComputerExecutionLifecycle(this.state.storage);
|
|
517
|
+
this.#executionRecord = executions;
|
|
518
|
+
for (const interrupted of executions.reconstructed) {
|
|
519
|
+
if (interrupted.invocation.kind === "process") {
|
|
520
|
+
// An opening process never crossed the synchronous SDK fence.
|
|
521
|
+
const { id } = processIdInputSchema.parse({
|
|
522
|
+
id: interrupted.invocation.id,
|
|
523
|
+
});
|
|
524
|
+
this.#processes.release(id);
|
|
525
|
+
}
|
|
526
|
+
this.state.waitUntil(Promise.resolve().then(() => this.#deliverExecutionClose(interrupted, "reconstructed")));
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return this.#executionRecord;
|
|
530
|
+
}
|
|
531
|
+
async #deliverExecutionClose(record, reason, hooks) {
|
|
532
|
+
try {
|
|
533
|
+
await (hooks ?? this.executionHooks())?.close(record.invocation, record.context, reason);
|
|
534
|
+
}
|
|
535
|
+
catch (cause) {
|
|
536
|
+
// A host error may contain credential-bearing transport data.
|
|
537
|
+
Effect.runSync(Effect.logError("Computer host execution close is unavailable", Redacted.make(cause)));
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async #closeExecution(id, reason, hooks) {
|
|
541
|
+
const record = this.#executions.close(id, reason);
|
|
542
|
+
if (record !== undefined)
|
|
543
|
+
await this.#deliverExecutionClose(record, reason, hooks);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Admission observes the actual native subscriber, never App completion,
|
|
547
|
+
* output silence, an accepted signal or this isolate's warm state.
|
|
548
|
+
*/
|
|
549
|
+
async executionIsLive(invocationId) {
|
|
550
|
+
const record = this.#executions.get(invocationId);
|
|
551
|
+
if (record === undefined ||
|
|
552
|
+
record.phase === "closed" ||
|
|
553
|
+
record.phase === "opening" ||
|
|
554
|
+
(record.phase === "open" && !record.nativeOpened) ||
|
|
555
|
+
(record.phase === "dispatching" &&
|
|
556
|
+
(record.invocation.kind !== "process" ||
|
|
557
|
+
this.#processStarts.has(invocationId))))
|
|
558
|
+
return false;
|
|
559
|
+
let route;
|
|
560
|
+
try {
|
|
561
|
+
const selected = this.hostedRoute(record.backend);
|
|
562
|
+
if (selected === undefined)
|
|
563
|
+
throw new ComputerBackendUnavailableError("no_backend");
|
|
564
|
+
route = selected;
|
|
565
|
+
}
|
|
566
|
+
catch {
|
|
567
|
+
await this.#closeExecution(invocationId, "unknown");
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
let workspace;
|
|
571
|
+
try {
|
|
572
|
+
if (!(await this.containerRunning(route.backing))) {
|
|
573
|
+
if (record.nativeOpened)
|
|
574
|
+
await this.#closeExecution(invocationId, "native_exit");
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
workspace = await this.readyWorkspace();
|
|
578
|
+
}
|
|
579
|
+
catch {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
const runtime = processRuntime(workspace, route.id);
|
|
583
|
+
let observed;
|
|
584
|
+
let archivedExit = false;
|
|
585
|
+
try {
|
|
586
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
587
|
+
try {
|
|
588
|
+
// Hold both handles unread. A live execution admits only one subscriber;
|
|
589
|
+
// two successful gets therefore prove archived native completion.
|
|
590
|
+
const first = __addDisposableResource(env_2, await runtime.getExec(invocationId, { resume: "tail" }), false);
|
|
591
|
+
void first;
|
|
592
|
+
const second = __addDisposableResource(env_2, await runtime.getExec(invocationId, { resume: "tail" }), false);
|
|
593
|
+
void second;
|
|
594
|
+
archivedExit = true;
|
|
595
|
+
observed = { state: "exited", exitCode: null, sync: "unknown" };
|
|
596
|
+
}
|
|
597
|
+
catch (e_2) {
|
|
598
|
+
env_2.error = e_2;
|
|
599
|
+
env_2.hasError = true;
|
|
600
|
+
}
|
|
601
|
+
finally {
|
|
602
|
+
__disposeResources(env_2);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
catch (cause) {
|
|
606
|
+
observed = failedObservation(cause instanceof Error ? cause : undefined);
|
|
607
|
+
}
|
|
608
|
+
if (observed.state === "running") {
|
|
609
|
+
this.#executions.opened(invocationId);
|
|
610
|
+
return this.#executions.get(invocationId)?.phase === "open";
|
|
611
|
+
}
|
|
612
|
+
if (observed.state === "exited" && (record.nativeOpened || archivedExit))
|
|
613
|
+
await this.#closeExecution(invocationId, "native_exit");
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
/** Caller-driven renewal only; a Git HTTP liveness check never renews. */
|
|
617
|
+
async #renewExecution(invocationId) {
|
|
618
|
+
const record = this.#executions.get(invocationId);
|
|
619
|
+
if (record === undefined || !(await this.executionIsLive(invocationId)))
|
|
620
|
+
return;
|
|
621
|
+
try {
|
|
622
|
+
await this.executionHooks()?.renew(record.invocation, record.context);
|
|
623
|
+
}
|
|
624
|
+
catch (cause) {
|
|
625
|
+
await this.#closeExecution(invocationId, "unknown");
|
|
626
|
+
Effect.runSync(Effect.logError("Computer host execution renewal is unavailable", Redacted.make(cause)));
|
|
627
|
+
}
|
|
628
|
+
}
|
|
511
629
|
#ensureExposedPorts() {
|
|
512
630
|
void this.#processes;
|
|
513
631
|
}
|
|
@@ -843,7 +961,7 @@ export class ComputerDO extends ContainerComputer {
|
|
|
843
961
|
return await this.callAfterAcceptance(capabilityId, input, () => undefined);
|
|
844
962
|
}
|
|
845
963
|
/** Resource hosts may accept authority once the Workspace is ready to dispatch. */
|
|
846
|
-
async callAfterAcceptance(capabilityId, input, accept) {
|
|
964
|
+
async callAfterAcceptance(capabilityId, input, accept, executionContext) {
|
|
847
965
|
const selected = capabilityNameSchema.parse(capabilityId);
|
|
848
966
|
if (selected === "bash" || selected === "exec") {
|
|
849
967
|
accept();
|
|
@@ -851,11 +969,11 @@ export class ComputerDO extends ContainerComputer {
|
|
|
851
969
|
return computerToolResult(await this.executeOperationAfterAcceptance({
|
|
852
970
|
...execInputSchema.parse(input),
|
|
853
971
|
requestId: mintComputerRequestId(),
|
|
854
|
-
}, () => undefined, selected));
|
|
972
|
+
}, () => undefined, selected, undefined, undefined, executionContext));
|
|
855
973
|
}
|
|
856
974
|
switch (selected) {
|
|
857
975
|
case "start_process":
|
|
858
|
-
return await this.startProcessAfterAcceptance(input, accept);
|
|
976
|
+
return await this.startProcessAfterAcceptance(input, accept, executionContext);
|
|
859
977
|
case "list_processes":
|
|
860
978
|
return await this.listProcessesAfterAcceptance(accept);
|
|
861
979
|
case "kill_process":
|
|
@@ -888,15 +1006,22 @@ export class ComputerDO extends ContainerComputer {
|
|
|
888
1006
|
* Execute a host request once, after the host accepts current authority.
|
|
889
1007
|
* `exec` runs on the Workspace's built-in Worker shell unless it names an
|
|
890
1008
|
* attached backend; `bash` always runs on an attached backend, the named one
|
|
891
|
-
* or the first available in the default order.
|
|
1009
|
+
* or the first available in the default order. Host-only `hosted` execution
|
|
1010
|
+
* excludes local backends, including an explicitly named local backend.
|
|
892
1011
|
*/
|
|
893
|
-
async executeOperationAfterAcceptance(input, accept, kind, budget = genericComputerExecutionBudget, signal) {
|
|
1012
|
+
async executeOperationAfterAcceptance(input, accept, kind, budget = genericComputerExecutionBudget, signal, executionContext) {
|
|
894
1013
|
const { requestId, ...command } = computerExecutionRequestSchema(budget).parse(input);
|
|
895
1014
|
const route = kind === "exec" && command.backend === undefined
|
|
896
1015
|
? undefined
|
|
897
|
-
: this.routeBackend(command.backend, "exec");
|
|
1016
|
+
: this.routeBackend(command.backend, kind === "hosted" ? "hosted" : "exec");
|
|
1017
|
+
if (executionContext !== undefined) {
|
|
1018
|
+
if (route === undefined)
|
|
1019
|
+
throw new Error("Host execution requires a hosted native backend");
|
|
1020
|
+
// Keep the selected backend; never redirect credential-bearing work from
|
|
1021
|
+
// a local backend to a different hosted one.
|
|
1022
|
+
this.routeBackend(route.id, "hosted");
|
|
1023
|
+
}
|
|
898
1024
|
const run = async () => {
|
|
899
|
-
const workspace = await this.readyWorkspace();
|
|
900
1025
|
const inputHash = await sha256(JSON.stringify({
|
|
901
1026
|
kind,
|
|
902
1027
|
command: command.command,
|
|
@@ -907,16 +1032,61 @@ export class ComputerDO extends ContainerComputer {
|
|
|
907
1032
|
const operations = this.#operations;
|
|
908
1033
|
signal?.throwIfAborted();
|
|
909
1034
|
const recorded = operations.admit(requestId, inputHash, accept);
|
|
910
|
-
if (recorded !== undefined)
|
|
1035
|
+
if (recorded !== undefined) {
|
|
1036
|
+
if (executionContext !== undefined)
|
|
1037
|
+
await this.#renewExecution(`exec:${requestId}`);
|
|
911
1038
|
return recorded;
|
|
1039
|
+
}
|
|
912
1040
|
operations.markRunning(requestId);
|
|
1041
|
+
let hooks;
|
|
1042
|
+
const invocation = {
|
|
1043
|
+
id: `exec:${requestId}`,
|
|
1044
|
+
kind: "exec",
|
|
1045
|
+
};
|
|
1046
|
+
let completed = false;
|
|
913
1047
|
try {
|
|
914
|
-
|
|
1048
|
+
const workspace = await this.readyWorkspace();
|
|
1049
|
+
hooks =
|
|
1050
|
+
executionContext === undefined ? undefined : this.executionHooks();
|
|
1051
|
+
let execution;
|
|
1052
|
+
if (executionContext !== undefined) {
|
|
1053
|
+
if (hooks === undefined || route === undefined)
|
|
1054
|
+
throw new Error("Computer host execution is unavailable");
|
|
1055
|
+
this.#executions.begin(invocation, route.id, executionContext);
|
|
1056
|
+
// The host's last awaited authority fence follows NEW admission.
|
|
1057
|
+
const carriage = await hooks.open(invocation, executionContext);
|
|
1058
|
+
execution = {
|
|
1059
|
+
invocation,
|
|
1060
|
+
carriage,
|
|
1061
|
+
dispatch: () => {
|
|
1062
|
+
signal?.throwIfAborted();
|
|
1063
|
+
this.#executions.dispatch(invocation.id);
|
|
1064
|
+
},
|
|
1065
|
+
opened: () => {
|
|
1066
|
+
this.#executions.opened(invocation.id);
|
|
1067
|
+
},
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
const output = await exec(workspace, command, route?.id, signal, execution);
|
|
1071
|
+
completed = true;
|
|
1072
|
+
operations.settle(requestId, output);
|
|
915
1073
|
}
|
|
916
1074
|
catch (cause) {
|
|
917
|
-
operations.settleUnknown(requestId,
|
|
918
|
-
?
|
|
919
|
-
:
|
|
1075
|
+
operations.settleUnknown(requestId, executionContext !== undefined
|
|
1076
|
+
? "Computer hosted execution outcome is unavailable"
|
|
1077
|
+
: cause instanceof Error
|
|
1078
|
+
? `${cause.name}: ${cause.message}`
|
|
1079
|
+
: "Computer execution rejected without an Error");
|
|
1080
|
+
}
|
|
1081
|
+
finally {
|
|
1082
|
+
if (hooks !== undefined && executionContext !== undefined) {
|
|
1083
|
+
const phase = this.#executions.get(invocation.id)?.phase;
|
|
1084
|
+
await this.#closeExecution(invocation.id, completed
|
|
1085
|
+
? "native_exit"
|
|
1086
|
+
: phase === "dispatching" || phase === "open"
|
|
1087
|
+
? "unknown"
|
|
1088
|
+
: "refused_start", hooks);
|
|
1089
|
+
}
|
|
920
1090
|
}
|
|
921
1091
|
return operations.status(requestId, () => undefined);
|
|
922
1092
|
};
|
|
@@ -937,22 +1107,33 @@ export class ComputerDO extends ContainerComputer {
|
|
|
937
1107
|
return await this.startProcessAfterAcceptance(input, () => undefined);
|
|
938
1108
|
}
|
|
939
1109
|
/** The host accepts authority when the running slot is reserved. */
|
|
940
|
-
async startProcessAfterAcceptance(input, accept) {
|
|
1110
|
+
async startProcessAfterAcceptance(input, accept, executionContext) {
|
|
941
1111
|
const start = processStartInputSchema.parse(input);
|
|
942
1112
|
const { command, cwd } = start;
|
|
943
1113
|
const inputHash = await sha256(JSON.stringify({ command, cwd: cwd ?? null, port: start.port ?? null }));
|
|
944
1114
|
const replay = this.#processes.replay(start, inputHash, accept);
|
|
945
|
-
if (replay !== undefined)
|
|
946
|
-
|
|
1115
|
+
if (replay !== undefined) {
|
|
1116
|
+
if (executionContext !== undefined)
|
|
1117
|
+
await this.#renewExecution(replay.id);
|
|
1118
|
+
return this.#processReceipt(replay, executionContext !== undefined);
|
|
1119
|
+
}
|
|
947
1120
|
if (start.port !== undefined && isComputerReservedPort(start.port))
|
|
948
1121
|
throw new ComputerPortReservedError(start.port);
|
|
949
1122
|
const backend = this.requireHostedRoute().id;
|
|
950
1123
|
const workspace = await this.readyWorkspace();
|
|
951
1124
|
const admission = this.#processes.admit(start, inputHash, accept);
|
|
952
|
-
if (!admission.created)
|
|
953
|
-
|
|
1125
|
+
if (!admission.created) {
|
|
1126
|
+
if (executionContext !== undefined)
|
|
1127
|
+
await this.#renewExecution(admission.process.id);
|
|
1128
|
+
return this.#processReceipt(admission.process, executionContext !== undefined);
|
|
1129
|
+
}
|
|
954
1130
|
const admitted = admission.process;
|
|
955
1131
|
const id = admitted.id;
|
|
1132
|
+
const invocation = { id, kind: "process" };
|
|
1133
|
+
const control = { cancelled: false };
|
|
1134
|
+
this.#processStarts.set(id, control);
|
|
1135
|
+
let hooks;
|
|
1136
|
+
let dispatched = false;
|
|
956
1137
|
let run;
|
|
957
1138
|
try {
|
|
958
1139
|
// timeoutMs 0 disables computerd's default 320 s command deadline.
|
|
@@ -964,9 +1145,38 @@ export class ComputerDO extends ContainerComputer {
|
|
|
964
1145
|
};
|
|
965
1146
|
if (cwd !== undefined)
|
|
966
1147
|
options.cwd = cwd;
|
|
967
|
-
|
|
1148
|
+
if (executionContext !== undefined) {
|
|
1149
|
+
hooks = this.executionHooks();
|
|
1150
|
+
if (hooks === undefined)
|
|
1151
|
+
throw new Error("Computer host execution is unavailable");
|
|
1152
|
+
this.#executions.begin(invocation, backend, executionContext);
|
|
1153
|
+
const carriage = await hooks.open(invocation, executionContext);
|
|
1154
|
+
if (carriage.env !== undefined)
|
|
1155
|
+
options.env = carriage.env;
|
|
1156
|
+
if (carriage.stdin !== undefined)
|
|
1157
|
+
options.stdin = carriage.stdin;
|
|
1158
|
+
}
|
|
1159
|
+
// No awaited work belongs between this fence and the SDK invocation.
|
|
1160
|
+
if (control.cancelled)
|
|
1161
|
+
throw new Error("Computer process start was cancelled");
|
|
1162
|
+
if (executionContext !== undefined)
|
|
1163
|
+
this.#executions.dispatch(id);
|
|
1164
|
+
dispatched = true;
|
|
1165
|
+
control.dispatch = workspace.runtime.exec(command, options);
|
|
1166
|
+
run = await control.dispatch;
|
|
1167
|
+
if (executionContext !== undefined)
|
|
1168
|
+
this.#executions.opened(id);
|
|
968
1169
|
}
|
|
969
1170
|
catch (error) {
|
|
1171
|
+
if (executionContext !== undefined) {
|
|
1172
|
+
await this.#closeExecution(id, dispatched ? "unknown" : "refused_start", hooks);
|
|
1173
|
+
if (!dispatched) {
|
|
1174
|
+
this.#processes.release(id);
|
|
1175
|
+
throw error;
|
|
1176
|
+
}
|
|
1177
|
+
// A failed dispatch reply is not proof that native spawn failed.
|
|
1178
|
+
return this.#settle(id, { state: "uncertain" });
|
|
1179
|
+
}
|
|
970
1180
|
// A transport failure after dispatch may still have started it.
|
|
971
1181
|
const observed = await observeProcess(processRuntime(workspace, backend), id);
|
|
972
1182
|
if (observed.state === "exited" && observed.exitCode === null) {
|
|
@@ -975,9 +1185,20 @@ export class ComputerDO extends ContainerComputer {
|
|
|
975
1185
|
}
|
|
976
1186
|
return this.#settle(id, observed);
|
|
977
1187
|
}
|
|
1188
|
+
finally {
|
|
1189
|
+
this.#processStarts.delete(id);
|
|
1190
|
+
}
|
|
978
1191
|
this.state.waitUntil(this.#watchProcess(id, run));
|
|
979
1192
|
return admitted;
|
|
980
1193
|
}
|
|
1194
|
+
#processReceipt(process, requiresContext = false) {
|
|
1195
|
+
const execution = this.#executions.get(process.id);
|
|
1196
|
+
return process.state === "running" &&
|
|
1197
|
+
(execution?.phase === "closed" ||
|
|
1198
|
+
(requiresContext && execution === undefined))
|
|
1199
|
+
? { ...process, state: "uncertain" }
|
|
1200
|
+
: process;
|
|
1201
|
+
}
|
|
981
1202
|
/** List this Computer's processes, reconciling live ones with computerd. */
|
|
982
1203
|
async listProcesses() {
|
|
983
1204
|
return await this.listProcessesAfterAcceptance(() => undefined);
|
|
@@ -990,28 +1211,55 @@ export class ComputerDO extends ContainerComputer {
|
|
|
990
1211
|
return processListOutputSchema.parse({
|
|
991
1212
|
processes: rows.slice(0, maximumProcessRecords),
|
|
992
1213
|
});
|
|
993
|
-
const
|
|
994
|
-
if (hosted === undefined ||
|
|
995
|
-
!(await this.containerRunning(hosted.backing))) {
|
|
996
|
-
// No container means none of its processes survived; do not wake it.
|
|
997
|
-
for (const row of running)
|
|
998
|
-
this.#processes.exited(row.id, null);
|
|
999
|
-
return processListOutputSchema.parse({
|
|
1000
|
-
processes: this.#processes.list().slice(0, maximumProcessRecords),
|
|
1001
|
-
});
|
|
1002
|
-
}
|
|
1003
|
-
const workspace = await this.readyWorkspace();
|
|
1004
|
-
const runtime = processRuntime(workspace, hosted.id);
|
|
1005
|
-
const observed = new Map(await Promise.all(running.map(async (row) => [
|
|
1006
|
-
row.id,
|
|
1007
|
-
this.#settle(row.id, await observeProcess(runtime, row.id)),
|
|
1008
|
-
])));
|
|
1214
|
+
const observed = new Map(await Promise.all(running.map(async (row) => [row.id, await this.#observeOwnedProcess(row)])));
|
|
1009
1215
|
return processListOutputSchema.parse({
|
|
1010
1216
|
processes: rows
|
|
1011
1217
|
.slice(0, maximumProcessRecords)
|
|
1012
1218
|
.map((row) => observed.get(row.id) ?? row),
|
|
1013
1219
|
});
|
|
1014
1220
|
}
|
|
1221
|
+
async #observeOwnedProcess(row) {
|
|
1222
|
+
// A reservation or unresolved SDK preparation is not a native exit.
|
|
1223
|
+
if (this.#processStarts.has(row.id))
|
|
1224
|
+
return { ...row, state: "uncertain" };
|
|
1225
|
+
const execution = this.#executions.get(row.id);
|
|
1226
|
+
let hosted = this.hostedRoute();
|
|
1227
|
+
if (execution !== undefined) {
|
|
1228
|
+
try {
|
|
1229
|
+
hosted = this.hostedRoute(execution.backend);
|
|
1230
|
+
if (hosted === undefined)
|
|
1231
|
+
throw new ComputerBackendUnavailableError("no_backend");
|
|
1232
|
+
}
|
|
1233
|
+
catch {
|
|
1234
|
+
await this.#closeExecution(row.id, "unknown");
|
|
1235
|
+
return { ...row, state: "uncertain" };
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
if (hosted === undefined ||
|
|
1239
|
+
!(await this.containerRunning(hosted.backing))) {
|
|
1240
|
+
// Never wake a stopped backend. Absence before a known native handle
|
|
1241
|
+
// cannot settle a dispatch whose reply was lost.
|
|
1242
|
+
if (execution !== undefined && !execution.nativeOpened)
|
|
1243
|
+
return { ...row, state: "uncertain" };
|
|
1244
|
+
return this.#settle(row.id, {
|
|
1245
|
+
state: "exited",
|
|
1246
|
+
exitCode: null,
|
|
1247
|
+
sync: "unknown",
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
const workspace = await this.readyWorkspace();
|
|
1251
|
+
const observed = await observeProcess(processRuntime(workspace, hosted.id), row.id);
|
|
1252
|
+
if (execution !== undefined &&
|
|
1253
|
+
!execution.nativeOpened &&
|
|
1254
|
+
observed.state === "exited" &&
|
|
1255
|
+
observed.exitCode === null &&
|
|
1256
|
+
observed.sync === "unknown")
|
|
1257
|
+
return { ...row, state: "uncertain" };
|
|
1258
|
+
const process = this.#settle(row.id, observed);
|
|
1259
|
+
if (observed.state === "running")
|
|
1260
|
+
await this.#renewExecution(row.id);
|
|
1261
|
+
return process;
|
|
1262
|
+
}
|
|
1015
1263
|
/** Send SIGTERM to one running process; its exit is recorded when observed. */
|
|
1016
1264
|
async killProcess(input) {
|
|
1017
1265
|
return await this.killProcessAfterAcceptance(input, () => undefined);
|
|
@@ -1024,10 +1272,61 @@ export class ComputerDO extends ContainerComputer {
|
|
|
1024
1272
|
throw new ComputerProcessNotFoundError(id);
|
|
1025
1273
|
if (row.state !== "running")
|
|
1026
1274
|
return processKillOutputSchema.parse({ process: row, signalled: false });
|
|
1027
|
-
const
|
|
1275
|
+
const pending = this.#processStarts.get(id);
|
|
1276
|
+
if (pending !== undefined) {
|
|
1277
|
+
pending.cancelled = true;
|
|
1278
|
+
const dispatch = pending.dispatch;
|
|
1279
|
+
if (dispatch === undefined) {
|
|
1280
|
+
await this.#closeExecution(id, "refused_start");
|
|
1281
|
+
this.#processes.release(id);
|
|
1282
|
+
return processKillOutputSchema.parse({
|
|
1283
|
+
process: this.#processes.get(id),
|
|
1284
|
+
signalled: false,
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
// Preparation can still be in flight. Await its actual native handle,
|
|
1288
|
+
// then signal that same invocation; never infer exit from acceptance.
|
|
1289
|
+
pending.signal ??= (async () => {
|
|
1290
|
+
try {
|
|
1291
|
+
const native = await dispatch;
|
|
1292
|
+
await native.kill("SIGTERM");
|
|
1293
|
+
return true;
|
|
1294
|
+
}
|
|
1295
|
+
catch {
|
|
1296
|
+
return false;
|
|
1297
|
+
}
|
|
1298
|
+
})();
|
|
1299
|
+
const signalled = await pending.signal;
|
|
1300
|
+
return processKillOutputSchema.parse({
|
|
1301
|
+
process: this.#processReceipt(this.#processes.get(id) ?? row),
|
|
1302
|
+
signalled,
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
const execution = this.#executions.get(id);
|
|
1306
|
+
let hosted = this.hostedRoute();
|
|
1307
|
+
if (execution !== undefined) {
|
|
1308
|
+
try {
|
|
1309
|
+
hosted = this.hostedRoute(execution.backend);
|
|
1310
|
+
if (hosted === undefined)
|
|
1311
|
+
throw new ComputerBackendUnavailableError("no_backend");
|
|
1312
|
+
}
|
|
1313
|
+
catch {
|
|
1314
|
+
await this.#closeExecution(id, "unknown");
|
|
1315
|
+
return processKillOutputSchema.parse({
|
|
1316
|
+
process: { ...row, state: "uncertain" },
|
|
1317
|
+
signalled: false,
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1028
1321
|
if (hosted === undefined || !(await this.containerRunning(hosted.backing)))
|
|
1029
1322
|
return processKillOutputSchema.parse({
|
|
1030
|
-
process:
|
|
1323
|
+
process: execution !== undefined && !execution.nativeOpened
|
|
1324
|
+
? { ...row, state: "uncertain" }
|
|
1325
|
+
: this.#settle(id, {
|
|
1326
|
+
state: "exited",
|
|
1327
|
+
exitCode: null,
|
|
1328
|
+
sync: "unknown",
|
|
1329
|
+
}),
|
|
1031
1330
|
signalled: false,
|
|
1032
1331
|
});
|
|
1033
1332
|
const workspace = await this.readyWorkspace();
|
|
@@ -1038,14 +1337,21 @@ export class ComputerDO extends ContainerComputer {
|
|
|
1038
1337
|
});
|
|
1039
1338
|
}
|
|
1040
1339
|
catch (cause) {
|
|
1041
|
-
|
|
1042
|
-
|
|
1340
|
+
if (execution === undefined)
|
|
1341
|
+
console.error("Computer runtime operation failed", cause);
|
|
1342
|
+
else
|
|
1343
|
+
console.error("Computer hosted process signal was not confirmed");
|
|
1043
1344
|
return processKillOutputSchema.parse({
|
|
1044
|
-
process:
|
|
1345
|
+
process: execution === undefined
|
|
1346
|
+
? this.#settle(id, await observeProcess(processRuntime(workspace, hosted.id), id))
|
|
1347
|
+
: await this.#observeOwnedProcess(row),
|
|
1045
1348
|
signalled: false,
|
|
1046
1349
|
});
|
|
1047
1350
|
}
|
|
1048
|
-
return processKillOutputSchema.parse({
|
|
1351
|
+
return processKillOutputSchema.parse({
|
|
1352
|
+
process: this.#processReceipt(this.#processes.get(id) ?? row),
|
|
1353
|
+
signalled: true,
|
|
1354
|
+
});
|
|
1049
1355
|
}
|
|
1050
1356
|
/**
|
|
1051
1357
|
* A "pending" verdict's background continuation keeps the host alive
|
|
@@ -1063,7 +1369,11 @@ export class ComputerDO extends ContainerComputer {
|
|
|
1063
1369
|
if (observed.state === "exited") {
|
|
1064
1370
|
if (observed.settleLater !== undefined)
|
|
1065
1371
|
this.#watchResync(id, observed.settleLater);
|
|
1066
|
-
|
|
1372
|
+
const process = this.#processes.exited(id, observed.exitCode, observed.sync);
|
|
1373
|
+
const execution = this.#executions.close(id, "native_exit");
|
|
1374
|
+
if (execution !== undefined)
|
|
1375
|
+
this.state.waitUntil(this.#deliverExecutionClose(execution, "native_exit"));
|
|
1376
|
+
return process;
|
|
1067
1377
|
}
|
|
1068
1378
|
const row = this.#processes.get(id);
|
|
1069
1379
|
if (row === undefined)
|
|
@@ -1082,13 +1392,21 @@ export class ComputerDO extends ContainerComputer {
|
|
|
1082
1392
|
try {
|
|
1083
1393
|
const settled = await drainProcessExit(run);
|
|
1084
1394
|
if (settled !== undefined) {
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1395
|
+
this.#settle(id, {
|
|
1396
|
+
state: "exited",
|
|
1397
|
+
...settled,
|
|
1398
|
+
});
|
|
1088
1399
|
}
|
|
1400
|
+
else
|
|
1401
|
+
await this.#closeExecution(id, "unknown");
|
|
1089
1402
|
}
|
|
1090
1403
|
catch (cause) {
|
|
1091
|
-
|
|
1404
|
+
if (this.#executions.get(id) === undefined)
|
|
1405
|
+
console.error("Computer runtime operation failed", cause);
|
|
1406
|
+
else {
|
|
1407
|
+
await this.#closeExecution(id, "unknown");
|
|
1408
|
+
console.error("Computer hosted process observation is unavailable");
|
|
1409
|
+
}
|
|
1092
1410
|
// The stream broke (eviction, transport); list reconciles later.
|
|
1093
1411
|
}
|
|
1094
1412
|
}
|
|
@@ -1116,10 +1434,10 @@ export class ComputerDO extends ContainerComputer {
|
|
|
1116
1434
|
if (hosted === undefined)
|
|
1117
1435
|
return Response.json({ error: "Computer container is unavailable" }, { status: 502 });
|
|
1118
1436
|
try {
|
|
1119
|
-
const
|
|
1437
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
1120
1438
|
try {
|
|
1121
1439
|
const workspace = await this.readyWorkspace();
|
|
1122
|
-
const readiness = __addDisposableResource(
|
|
1440
|
+
const readiness = __addDisposableResource(env_3, await workspace.runtime.exec("true", {
|
|
1123
1441
|
backend: hosted.id,
|
|
1124
1442
|
encoding: "utf8",
|
|
1125
1443
|
timeoutMs: 10_000,
|
|
@@ -1129,12 +1447,12 @@ export class ComputerDO extends ContainerComputer {
|
|
|
1129
1447
|
return Response.json({ error: "Computer container is unavailable" }, { status: 502 });
|
|
1130
1448
|
}
|
|
1131
1449
|
}
|
|
1132
|
-
catch (
|
|
1133
|
-
|
|
1134
|
-
|
|
1450
|
+
catch (e_3) {
|
|
1451
|
+
env_3.error = e_3;
|
|
1452
|
+
env_3.hasError = true;
|
|
1135
1453
|
}
|
|
1136
1454
|
finally {
|
|
1137
|
-
__disposeResources(
|
|
1455
|
+
__disposeResources(env_3);
|
|
1138
1456
|
}
|
|
1139
1457
|
}
|
|
1140
1458
|
catch (cause) {
|
|
@@ -1201,24 +1519,24 @@ export class ComputerDO extends ContainerComputer {
|
|
|
1201
1519
|
// Runtime backends reach the authoritative Workspace through these loopback
|
|
1202
1520
|
// entrypoints. The consuming Worker must re-export both from its main module.
|
|
1203
1521
|
export { WorkspaceProxy, WorkspaceServiceProxy };
|
|
1204
|
-
export { OnBoxVmContainerHost, onBoxVmPlacement, onBoxVmIngress, } from "./on-box-vm.js";
|
|
1522
|
+
export { OnBoxVmContainerHost, OnBoxVmTimeoutError, onBoxVmPlacement, onBoxVmIngress, } from "./on-box-vm.js";
|
|
1205
1523
|
export { computerContainerSleepAfterMs } from "./container-policy.js";
|
|
1206
1524
|
export * from "./computer-backends.js";
|
|
1207
1525
|
export { LocalBackendOfflineError, localBackendAudience, localBackendDialPath, verifyLocalBackendDial, } from "./local-backend.js";
|
|
1208
1526
|
/** Keep each native Workspace lease inside the operation that uses it. */
|
|
1209
1527
|
export function createComputerFilesPort(computer) {
|
|
1210
1528
|
async function withFiles(operation) {
|
|
1211
|
-
const
|
|
1529
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
1212
1530
|
try {
|
|
1213
|
-
const workspace = __addDisposableResource(
|
|
1531
|
+
const workspace = __addDisposableResource(env_4, await getWorkspace(computer), false);
|
|
1214
1532
|
return await operation(createWorkspaceFilesPort(workspace, computer));
|
|
1215
1533
|
}
|
|
1216
|
-
catch (
|
|
1217
|
-
|
|
1218
|
-
|
|
1534
|
+
catch (e_4) {
|
|
1535
|
+
env_4.error = e_4;
|
|
1536
|
+
env_4.hasError = true;
|
|
1219
1537
|
}
|
|
1220
1538
|
finally {
|
|
1221
|
-
__disposeResources(
|
|
1539
|
+
__disposeResources(env_4);
|
|
1222
1540
|
}
|
|
1223
1541
|
}
|
|
1224
1542
|
return {
|
|
@@ -1233,5 +1551,4 @@ export function createComputerFilesPort(computer) {
|
|
|
1233
1551
|
};
|
|
1234
1552
|
}
|
|
1235
1553
|
export { createWorkspaceFilesPort } from "./files.js";
|
|
1236
|
-
export * from "./operation-contract.js";
|
|
1237
1554
|
export { ComputerOperationConflictError, ComputerOperationLimitError, ComputerRequestInvalidError, } from "./operation-record.js";
|