@ricsam/r5d-worker 0.0.136 → 0.0.138
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 +4 -94
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/main.mjs +46 -7537
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/personal/action-scheduler.mjs +31 -0
- package/dist/mjs/personal/client.mjs +215 -0
- package/dist/mjs/personal/runtime.mjs +328 -0
- package/dist/mjs/personal/tcp.mjs +217 -0
- package/dist/mjs/runtime/adapter.mjs +42 -0
- package/dist/mjs/runtime/client.mjs +95 -0
- package/dist/mjs/runtime/daemon.mjs +131 -0
- package/dist/mjs/runtime/executor.mjs +436 -0
- package/dist/mjs/runtime/index.mjs +15 -0
- package/dist/mjs/runtime/protocol.mjs +139 -0
- package/dist/mjs/runtime/pty-host.mjs +156 -0
- package/dist/mjs/runtime/pty.mjs +127 -0
- package/dist/mjs/runtime/releases/artifacts.mjs +201 -0
- package/dist/mjs/runtime/releases/grants.mjs +93 -0
- package/dist/mjs/runtime/releases/main.mjs +101 -0
- package/dist/mjs/runtime/releases/manager.mjs +541 -0
- package/dist/mjs/runtime/releases/process.mjs +88 -0
- package/dist/mjs/runtime/releases/retirement.mjs +18 -0
- package/dist/mjs/runtime/releases/rpc-client.mjs +184 -0
- package/dist/mjs/runtime/releases/rpc-main.mjs +213 -0
- package/dist/mjs/runtime/releases/rpc-protocol.mjs +103 -0
- package/dist/mjs/runtime/storage.mjs +156 -0
- package/dist/mjs/runtime/workspace/artifacts.mjs +121 -0
- package/dist/mjs/runtime/workspace/authority.mjs +1355 -0
- package/dist/mjs/runtime/workspace/contracts.mjs +60 -0
- package/dist/mjs/runtime/workspace/file-write.mjs +23 -0
- package/dist/mjs/runtime/workspace/files.mjs +308 -0
- package/dist/mjs/runtime/workspace/http-body.mjs +45 -0
- package/dist/mjs/runtime/workspace/storage-client.mjs +59 -0
- package/dist/mjs/runtime/workspace/storage-wire.mjs +53 -0
- package/dist/types/main.d.ts +0 -1167
- package/dist/types/personal/action-scheduler.d.ts +23 -0
- package/dist/types/personal/client.d.ts +14 -0
- package/dist/types/personal/runtime.d.ts +121 -0
- package/dist/types/personal/tcp.d.ts +14 -0
- package/dist/types/runtime/client.d.ts +17 -0
- package/dist/types/runtime/daemon.d.ts +8 -0
- package/dist/types/runtime/executor.d.ts +35 -0
- package/dist/types/runtime/index.d.ts +5 -0
- package/dist/types/runtime/protocol.d.ts +544 -0
- package/dist/types/runtime/pty.d.ts +30 -0
- package/dist/types/runtime/releases/artifacts.d.ts +51 -0
- package/dist/types/runtime/releases/grants.d.ts +16 -0
- package/dist/types/runtime/releases/manager.d.ts +145 -0
- package/dist/types/runtime/releases/process.d.ts +14 -0
- package/dist/types/runtime/releases/retirement.d.ts +15 -0
- package/dist/types/runtime/releases/rpc-client.d.ts +52 -0
- package/dist/types/runtime/releases/rpc-main.d.ts +10 -0
- package/dist/types/runtime/releases/rpc-protocol.d.ts +1139 -0
- package/dist/types/runtime/storage.d.ts +21 -0
- package/dist/types/runtime/test-harness.d.ts +53 -0
- package/dist/types/runtime/workspace/artifacts.d.ts +30 -0
- package/dist/types/runtime/workspace/authority.d.ts +338 -0
- package/dist/types/runtime/workspace/contracts.d.ts +86 -0
- package/dist/types/runtime/workspace/file-write.d.ts +14 -0
- package/dist/types/runtime/workspace/files.d.ts +21 -0
- package/dist/types/runtime/workspace/http-body.d.ts +9 -0
- package/dist/types/runtime/workspace/storage-client.d.ts +23 -0
- package/dist/types/runtime/workspace/storage-wire.d.ts +138 -0
- package/package.json +8 -6
- package/dist/cjs/atomic-rename.cjs +0 -303
- package/dist/cjs/browser-upload.cjs +0 -73
- package/dist/cjs/cli-update.cjs +0 -108
- package/dist/cjs/command-launcher.cjs +0 -423
- package/dist/cjs/control-command-policy.cjs +0 -177
- package/dist/cjs/git-blob-hash.cjs +0 -41
- package/dist/cjs/git-identity.cjs +0 -39
- package/dist/cjs/git-process-environment.cjs +0 -133
- package/dist/cjs/inline-output.cjs +0 -62
- package/dist/cjs/main.cjs +0 -7511
- package/dist/cjs/managed-paths.cjs +0 -215
- package/dist/cjs/plan-lint.cjs +0 -165
- package/dist/cjs/plan-parser.cjs +0 -127
- package/dist/cjs/plan-paths.cjs +0 -53
- package/dist/cjs/port-forward-client.cjs +0 -206
- package/dist/cjs/process-tree.cjs +0 -117
- package/dist/cjs/project-checkout-garbage.cjs +0 -219
- package/dist/cjs/project-mirror-fetch-policy.cjs +0 -82
- package/dist/cjs/project-mirror-refs-token.cjs +0 -85
- package/dist/cjs/project-workspace-state.cjs +0 -972
- package/dist/cjs/project-worktrees.cjs +0 -1713
- package/dist/cjs/pty-output-coalescer.cjs +0 -87
- package/dist/cjs/recovery-journal-protocol.cjs +0 -56
- package/dist/cjs/recovery-journal-runtime.cjs +0 -133
- package/dist/cjs/recovery-journal-thread.cjs +0 -9
- package/dist/cjs/recovery-journal.cjs +0 -742
- package/dist/cjs/recovery-output-window.cjs +0 -77
- package/dist/cjs/recovery-protocol.cjs +0 -31
- package/dist/cjs/recovery-store.cjs +0 -252
- package/dist/cjs/registry-auth.cjs +0 -310
- package/dist/cjs/repository-transition-policy.cjs +0 -49
- package/dist/cjs/runtime-version.cjs +0 -88
- package/dist/cjs/session-file-mutations.cjs +0 -61
- package/dist/cjs/supervisor.cjs +0 -147
- package/dist/cjs/three-way-merge.cjs +0 -346
- package/dist/cjs/working-tree-mirror.cjs +0 -1872
- package/dist/cjs/workspace-automatic-sync-policy.cjs +0 -79
- package/dist/cjs/workspace-branch-incarnation-policy.cjs +0 -37
- package/dist/cjs/workspace-command-sync-policy.cjs +0 -112
- package/dist/cjs/workspace-command-targets.cjs +0 -63
- package/dist/cjs/workspace-filesystem-executor-thread.cjs +0 -36
- package/dist/cjs/workspace-filesystem-executor.cjs +0 -327
- package/dist/cjs/workspace-filesystem-job-types.cjs +0 -144
- package/dist/cjs/workspace-filesystem-jobs.cjs +0 -59
- package/dist/cjs/workspace-git-sync.cjs +0 -3450
- package/dist/cjs/workspace-hydration-ledger.cjs +0 -66
- package/dist/cjs/workspace-hydration-merge.cjs +0 -433
- package/dist/cjs/workspace-hydration-recovery-state.cjs +0 -53
- package/dist/cjs/workspace-incident-state.cjs +0 -37
- package/dist/cjs/workspace-merge-projection.cjs +0 -467
- package/dist/cjs/workspace-mount-boundary.cjs +0 -71
- package/dist/cjs/workspace-mount-hold-fence.cjs +0 -120
- package/dist/cjs/workspace-mutation-gate.cjs +0 -127
- package/dist/cjs/workspace-path-move.cjs +0 -195
- package/dist/cjs/workspace-preserve-only-policy.cjs +0 -36
- package/dist/cjs/workspace-project-config-policy.cjs +0 -53
- package/dist/cjs/workspace-projection-ledger.cjs +0 -81
- package/dist/cjs/workspace-publication-evidence.cjs +0 -46
- package/dist/cjs/workspace-sync-coalescer.cjs +0 -58
- package/dist/mjs/atomic-rename.mjs +0 -261
- package/dist/mjs/browser-upload.mjs +0 -42
- package/dist/mjs/command-launcher.mjs +0 -393
- package/dist/mjs/control-command-policy.mjs +0 -146
- package/dist/mjs/git-blob-hash.mjs +0 -16
- package/dist/mjs/git-identity.mjs +0 -15
- package/dist/mjs/git-process-environment.mjs +0 -105
- package/dist/mjs/inline-output.mjs +0 -37
- package/dist/mjs/managed-paths.mjs +0 -174
- package/dist/mjs/plan-lint.mjs +0 -129
- package/dist/mjs/plan-parser.mjs +0 -94
- package/dist/mjs/plan-paths.mjs +0 -25
- package/dist/mjs/port-forward-client.mjs +0 -172
- package/dist/mjs/project-checkout-garbage.mjs +0 -178
- package/dist/mjs/project-mirror-fetch-policy.mjs +0 -56
- package/dist/mjs/project-mirror-refs-token.mjs +0 -53
- package/dist/mjs/project-workspace-state.mjs +0 -937
- package/dist/mjs/project-worktrees.mjs +0 -1657
- package/dist/mjs/pty-output-coalescer.mjs +0 -61
- package/dist/mjs/recovery-journal-protocol.mjs +0 -30
- package/dist/mjs/recovery-journal-runtime.mjs +0 -112
- package/dist/mjs/recovery-journal-thread.mjs +0 -8
- package/dist/mjs/recovery-journal.mjs +0 -694
- package/dist/mjs/recovery-output-window.mjs +0 -53
- package/dist/mjs/recovery-protocol.mjs +0 -6
- package/dist/mjs/recovery-store.mjs +0 -217
- package/dist/mjs/registry-auth.mjs +0 -269
- package/dist/mjs/repository-transition-policy.mjs +0 -22
- package/dist/mjs/runtime-version.mjs +0 -54
- package/dist/mjs/session-file-mutations.mjs +0 -37
- package/dist/mjs/supervisor.mjs +0 -114
- package/dist/mjs/three-way-merge.mjs +0 -318
- package/dist/mjs/working-tree-mirror.mjs +0 -1817
- package/dist/mjs/workspace-automatic-sync-policy.mjs +0 -48
- package/dist/mjs/workspace-branch-incarnation-policy.mjs +0 -13
- package/dist/mjs/workspace-command-sync-policy.mjs +0 -84
- package/dist/mjs/workspace-command-targets.mjs +0 -37
- package/dist/mjs/workspace-filesystem-executor-thread.mjs +0 -38
- package/dist/mjs/workspace-filesystem-executor.mjs +0 -287
- package/dist/mjs/workspace-filesystem-job-types.mjs +0 -116
- package/dist/mjs/workspace-filesystem-jobs.mjs +0 -55
- package/dist/mjs/workspace-git-sync.mjs +0 -3407
- package/dist/mjs/workspace-hydration-ledger.mjs +0 -42
- package/dist/mjs/workspace-hydration-merge.mjs +0 -399
- package/dist/mjs/workspace-hydration-recovery-state.mjs +0 -29
- package/dist/mjs/workspace-incident-state.mjs +0 -12
- package/dist/mjs/workspace-merge-projection.mjs +0 -433
- package/dist/mjs/workspace-mount-boundary.mjs +0 -37
- package/dist/mjs/workspace-mount-hold-fence.mjs +0 -95
- package/dist/mjs/workspace-mutation-gate.mjs +0 -103
- package/dist/mjs/workspace-path-move.mjs +0 -160
- package/dist/mjs/workspace-preserve-only-policy.mjs +0 -11
- package/dist/mjs/workspace-project-config-policy.mjs +0 -27
- package/dist/mjs/workspace-projection-ledger.mjs +0 -57
- package/dist/mjs/workspace-publication-evidence.mjs +0 -21
- package/dist/mjs/workspace-sync-coalescer.mjs +0 -34
- package/dist/types/atomic-rename.d.ts +0 -78
- package/dist/types/browser-upload.d.ts +0 -7
- package/dist/types/command-launcher.d.ts +0 -104
- package/dist/types/control-command-policy.d.ts +0 -53
- package/dist/types/git-blob-hash.d.ts +0 -10
- package/dist/types/git-identity.d.ts +0 -9
- package/dist/types/git-process-environment.d.ts +0 -9
- package/dist/types/inline-output.d.ts +0 -3
- package/dist/types/managed-paths.d.ts +0 -19
- package/dist/types/plan-lint.d.ts +0 -16
- package/dist/types/plan-parser.d.ts +0 -38
- package/dist/types/plan-paths.d.ts +0 -36
- package/dist/types/port-forward-client.d.ts +0 -8
- package/dist/types/project-checkout-garbage.d.ts +0 -86
- package/dist/types/project-mirror-fetch-policy.d.ts +0 -84
- package/dist/types/project-mirror-refs-token.d.ts +0 -53
- package/dist/types/project-workspace-state.d.ts +0 -188
- package/dist/types/project-worktrees.d.ts +0 -397
- package/dist/types/pty-output-coalescer.d.ts +0 -20
- package/dist/types/recovery-journal-protocol.d.ts +0 -35
- package/dist/types/recovery-journal-runtime.d.ts +0 -12
- package/dist/types/recovery-journal-thread.d.ts +0 -1
- package/dist/types/recovery-journal.d.ts +0 -248
- package/dist/types/recovery-output-window.d.ts +0 -19
- package/dist/types/recovery-protocol.d.ts +0 -59
- package/dist/types/recovery-store.d.ts +0 -52
- package/dist/types/registry-auth.d.ts +0 -47
- package/dist/types/repository-transition-policy.d.ts +0 -26
- package/dist/types/runtime-version.d.ts +0 -1
- package/dist/types/session-file-mutations.d.ts +0 -22
- package/dist/types/supervisor-daemonized-fixture.d.ts +0 -1
- package/dist/types/supervisor-launcher-cleanup-fixture.d.ts +0 -1
- package/dist/types/supervisor-signal-fixture.d.ts +0 -1
- package/dist/types/supervisor.d.ts +0 -23
- package/dist/types/three-way-merge.d.ts +0 -77
- package/dist/types/working-tree-mirror.d.ts +0 -424
- package/dist/types/workspace-automatic-sync-policy.d.ts +0 -57
- package/dist/types/workspace-branch-incarnation-policy.d.ts +0 -14
- package/dist/types/workspace-command-sync-policy.d.ts +0 -57
- package/dist/types/workspace-command-targets.d.ts +0 -37
- package/dist/types/workspace-filesystem-executor-thread.d.ts +0 -1
- package/dist/types/workspace-filesystem-executor.d.ts +0 -123
- package/dist/types/workspace-filesystem-job-types.d.ts +0 -288
- package/dist/types/workspace-filesystem-jobs.d.ts +0 -7
- package/dist/types/workspace-git-sync.d.ts +0 -495
- package/dist/types/workspace-hydration-ledger.d.ts +0 -43
- package/dist/types/workspace-hydration-merge.d.ts +0 -95
- package/dist/types/workspace-hydration-recovery-state.d.ts +0 -10
- package/dist/types/workspace-incident-state.d.ts +0 -19
- package/dist/types/workspace-merge-projection.d.ts +0 -60
- package/dist/types/workspace-mount-boundary.d.ts +0 -10
- package/dist/types/workspace-mount-hold-fence.d.ts +0 -42
- package/dist/types/workspace-mutation-gate.d.ts +0 -42
- package/dist/types/workspace-path-move.d.ts +0 -21
- package/dist/types/workspace-preserve-only-policy.d.ts +0 -15
- package/dist/types/workspace-project-config-policy.d.ts +0 -48
- package/dist/types/workspace-projection-ledger.d.ts +0 -59
- package/dist/types/workspace-publication-evidence.d.ts +0 -17
- package/dist/types/workspace-sync-coalescer.d.ts +0 -28
- /package/dist/types/{credential-authority-lock-fixture.d.ts → runtime/adapter.d.ts} +0 -0
- /package/dist/types/{recovery-journal-stall-fixture.d.ts → runtime/releases/main.d.ts} +0 -0
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { randomUUID } from "node:crypto";
|
|
5
|
-
class AtomicRenameError extends Error {
|
|
6
|
-
constructor(syscall, code, errno, path2, dest) {
|
|
7
|
-
super(`${code}: ${syscall} failed, ${path2} -> ${dest}`);
|
|
8
|
-
this.syscall = syscall;
|
|
9
|
-
this.code = code;
|
|
10
|
-
this.errno = errno;
|
|
11
|
-
this.path = path2;
|
|
12
|
-
this.dest = dest;
|
|
13
|
-
this.name = "AtomicRenameError";
|
|
14
|
-
}
|
|
15
|
-
syscall;
|
|
16
|
-
code;
|
|
17
|
-
errno;
|
|
18
|
-
path;
|
|
19
|
-
dest;
|
|
20
|
-
}
|
|
21
|
-
const UNSUPPORTED_CODES = /* @__PURE__ */ new Set(["EINVAL", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EPERM"]);
|
|
22
|
-
function isAtomicRenameUnsupported(error) {
|
|
23
|
-
return error instanceof AtomicRenameError && UNSUPPORTED_CODES.has(error.code);
|
|
24
|
-
}
|
|
25
|
-
const LINUX_AT_FDCWD = -100;
|
|
26
|
-
const LINUX_RENAME_NOREPLACE = 1;
|
|
27
|
-
const LINUX_RENAME_EXCHANGE = 2;
|
|
28
|
-
const DARWIN_RENAME_SWAP = 2;
|
|
29
|
-
const DARWIN_RENAME_EXCL = 4;
|
|
30
|
-
const LINUX_SYS_RENAMEAT2 = { x64: 316, arm64: 276 };
|
|
31
|
-
const PROBE_PREFIX = ".r5d-atomic-rename-probe-";
|
|
32
|
-
let loaded = null;
|
|
33
|
-
let override = null;
|
|
34
|
-
const errnoNames = /* @__PURE__ */ new Map();
|
|
35
|
-
for (const [name, value] of Object.entries(os.constants.errno)) errnoNames.set(value, name);
|
|
36
|
-
function errnoCode(errno) {
|
|
37
|
-
return errnoNames.get(errno) ?? `E${errno}`;
|
|
38
|
-
}
|
|
39
|
-
function cString(value) {
|
|
40
|
-
return Buffer.from(`${value}\0`, "utf8");
|
|
41
|
-
}
|
|
42
|
-
function loadNative() {
|
|
43
|
-
if (loaded) return loaded;
|
|
44
|
-
loaded = { calls: null, detail: "not attempted" };
|
|
45
|
-
try {
|
|
46
|
-
const ffi = require("bun:ffi");
|
|
47
|
-
const { dlopen, FFIType, ptr, read } = ffi;
|
|
48
|
-
if (process.platform === "darwin") {
|
|
49
|
-
const lib = dlopen("libSystem.B.dylib", {
|
|
50
|
-
renamex_np: { args: [FFIType.ptr, FFIType.ptr, FFIType.u32], returns: FFIType.i32 },
|
|
51
|
-
__error: { args: [], returns: FFIType.ptr }
|
|
52
|
-
});
|
|
53
|
-
loaded = {
|
|
54
|
-
calls: {
|
|
55
|
-
rename2: (from, to, flags) => {
|
|
56
|
-
const fromBuffer = cString(from);
|
|
57
|
-
const toBuffer = cString(to);
|
|
58
|
-
return lib.symbols.renamex_np(ptr(fromBuffer), ptr(toBuffer), flags);
|
|
59
|
-
},
|
|
60
|
-
errno: () => read.i32(lib.symbols.__error(), 0),
|
|
61
|
-
exchangeFlag: DARWIN_RENAME_SWAP,
|
|
62
|
-
noReplaceFlag: DARWIN_RENAME_EXCL,
|
|
63
|
-
backend: "renamex_np"
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
return loaded;
|
|
67
|
-
}
|
|
68
|
-
if (process.platform === "linux") {
|
|
69
|
-
const errnoLib = dlopen("libc.so.6", { __errno_location: { args: [], returns: FFIType.ptr } });
|
|
70
|
-
const errno = () => read.i32(errnoLib.symbols.__errno_location(), 0);
|
|
71
|
-
try {
|
|
72
|
-
const lib = dlopen("libc.so.6", {
|
|
73
|
-
renameat2: { args: [FFIType.i32, FFIType.ptr, FFIType.i32, FFIType.ptr, FFIType.u32], returns: FFIType.i32 }
|
|
74
|
-
});
|
|
75
|
-
loaded = {
|
|
76
|
-
calls: {
|
|
77
|
-
rename2: (from, to, flags) => {
|
|
78
|
-
const fromBuffer = cString(from);
|
|
79
|
-
const toBuffer = cString(to);
|
|
80
|
-
return lib.symbols.renameat2(LINUX_AT_FDCWD, ptr(fromBuffer), LINUX_AT_FDCWD, ptr(toBuffer), flags);
|
|
81
|
-
},
|
|
82
|
-
errno,
|
|
83
|
-
exchangeFlag: LINUX_RENAME_EXCHANGE,
|
|
84
|
-
noReplaceFlag: LINUX_RENAME_NOREPLACE,
|
|
85
|
-
backend: "renameat2"
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
return loaded;
|
|
89
|
-
} catch (error) {
|
|
90
|
-
const number = LINUX_SYS_RENAMEAT2[process.arch];
|
|
91
|
-
if (number === void 0) {
|
|
92
|
-
loaded = { calls: null, detail: `renameat2 symbol unavailable and no syscall number for ${process.arch}: ${String(error)}` };
|
|
93
|
-
return loaded;
|
|
94
|
-
}
|
|
95
|
-
const lib = dlopen("libc.so.6", {
|
|
96
|
-
syscall: {
|
|
97
|
-
args: [FFIType.i64, FFIType.i64, FFIType.i64, FFIType.i64, FFIType.i64, FFIType.i64],
|
|
98
|
-
returns: FFIType.i64
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
loaded = {
|
|
102
|
-
calls: {
|
|
103
|
-
rename2: (from, to, flags) => {
|
|
104
|
-
const fromBuffer = cString(from);
|
|
105
|
-
const toBuffer = cString(to);
|
|
106
|
-
return Number(
|
|
107
|
-
lib.symbols.syscall(number, LINUX_AT_FDCWD, Number(ptr(fromBuffer)), LINUX_AT_FDCWD, Number(ptr(toBuffer)), flags)
|
|
108
|
-
);
|
|
109
|
-
},
|
|
110
|
-
errno,
|
|
111
|
-
exchangeFlag: LINUX_RENAME_EXCHANGE,
|
|
112
|
-
noReplaceFlag: LINUX_RENAME_NOREPLACE,
|
|
113
|
-
backend: "syscall"
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
return loaded;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
loaded = { calls: null, detail: `no atomic exchange primitive on ${process.platform}` };
|
|
120
|
-
} catch (error) {
|
|
121
|
-
loaded = { calls: null, detail: `bun:ffi unavailable: ${error instanceof Error ? error.message : String(error)}` };
|
|
122
|
-
}
|
|
123
|
-
return loaded;
|
|
124
|
-
}
|
|
125
|
-
function applyOverride(support) {
|
|
126
|
-
if (override?.exchange !== void 0) support.exchange = override.exchange;
|
|
127
|
-
if (override?.noReplace !== void 0) support.noReplace = override.noReplace;
|
|
128
|
-
return support;
|
|
129
|
-
}
|
|
130
|
-
function atomicRenameSupport() {
|
|
131
|
-
const native = loadNative();
|
|
132
|
-
return applyOverride(
|
|
133
|
-
native.calls ? { platform: process.platform, exchange: native.calls.backend, noReplace: native.calls.backend } : { platform: process.platform, exchange: "unavailable", noReplace: "link", detail: native.detail }
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
function overrideAtomicRenameSupportForTests(value) {
|
|
137
|
-
override = value;
|
|
138
|
-
}
|
|
139
|
-
function resetAtomicRenameProbesForTests() {
|
|
140
|
-
probes.clear();
|
|
141
|
-
}
|
|
142
|
-
function call(native, syscall, from, to, flags) {
|
|
143
|
-
if (native.rename2(from, to, flags) === 0) return;
|
|
144
|
-
const errno = native.errno();
|
|
145
|
-
throw new AtomicRenameError(syscall, errnoCode(errno), errno, from, to);
|
|
146
|
-
}
|
|
147
|
-
function exchangeWith(support, a, b) {
|
|
148
|
-
const native = loadNative().calls;
|
|
149
|
-
if (support.exchange === "unavailable" || !native) {
|
|
150
|
-
throw new AtomicRenameError("exchange", "ENOSYS", os.constants.errno.ENOSYS, a, b);
|
|
151
|
-
}
|
|
152
|
-
call(native, support.exchange, a, b, native.exchangeFlag);
|
|
153
|
-
}
|
|
154
|
-
function renameNoReplaceWith(support, from, to) {
|
|
155
|
-
const native = loadNative().calls;
|
|
156
|
-
if (support.noReplace !== "link" && native) {
|
|
157
|
-
call(native, support.noReplace, from, to, native.noReplaceFlag);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
const source = fs.lstatSync(from);
|
|
161
|
-
if (!source.isFile()) throw new AtomicRenameError("link", "ENOTSUP", os.constants.errno.ENOTSUP, from, to);
|
|
162
|
-
try {
|
|
163
|
-
fs.linkSync(from, to);
|
|
164
|
-
} catch (error) {
|
|
165
|
-
const code = error.code ?? "EIO";
|
|
166
|
-
throw new AtomicRenameError("link", code, error.errno ?? 0, from, to);
|
|
167
|
-
}
|
|
168
|
-
fs.unlinkSync(from);
|
|
169
|
-
}
|
|
170
|
-
function exchangePaths(a, b) {
|
|
171
|
-
exchangeWith(atomicRenameSupport(), a, b);
|
|
172
|
-
}
|
|
173
|
-
function renameNoReplace(from, to) {
|
|
174
|
-
renameNoReplaceWith(atomicRenameSupport(), from, to);
|
|
175
|
-
}
|
|
176
|
-
const probes = /* @__PURE__ */ new Map();
|
|
177
|
-
function withoutTemporaries(directory, run) {
|
|
178
|
-
const token = randomUUID();
|
|
179
|
-
const a = path.join(directory, `${PROBE_PREFIX}${token}-a`);
|
|
180
|
-
const b = path.join(directory, `${PROBE_PREFIX}${token}-b`);
|
|
181
|
-
try {
|
|
182
|
-
run(a, b);
|
|
183
|
-
} finally {
|
|
184
|
-
fs.rmSync(a, { force: true });
|
|
185
|
-
fs.rmSync(b, { force: true });
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
function createProbeFile(filePath, content) {
|
|
189
|
-
const descriptor = fs.openSync(filePath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, 384);
|
|
190
|
-
try {
|
|
191
|
-
fs.writeSync(descriptor, content);
|
|
192
|
-
return fs.fstatSync(descriptor);
|
|
193
|
-
} finally {
|
|
194
|
-
fs.closeSync(descriptor);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
function probeAtomicRenameSupport(directory) {
|
|
198
|
-
const native = atomicRenameSupport();
|
|
199
|
-
const dev = fs.lstatSync(directory).dev;
|
|
200
|
-
const cached = probes.get(dev);
|
|
201
|
-
if (cached) return applyOverride({ ...cached });
|
|
202
|
-
const probed = { ...native };
|
|
203
|
-
if (native.exchange !== "unavailable") {
|
|
204
|
-
withoutTemporaries(directory, (a, b) => {
|
|
205
|
-
const statA = createProbeFile(a, "a");
|
|
206
|
-
const statB = createProbeFile(b, "b");
|
|
207
|
-
let swapped = false;
|
|
208
|
-
let detail;
|
|
209
|
-
try {
|
|
210
|
-
exchangeWith(native, a, b);
|
|
211
|
-
const afterA = fs.lstatSync(a);
|
|
212
|
-
const afterB = fs.lstatSync(b);
|
|
213
|
-
swapped = afterA.ino === statB.ino && afterB.ino === statA.ino;
|
|
214
|
-
if (!swapped) detail = `exchange flag ignored by the filesystem holding ${directory}`;
|
|
215
|
-
} catch (error) {
|
|
216
|
-
detail = `exchange rejected by the filesystem holding ${directory}: ${error instanceof Error ? error.message : String(error)}`;
|
|
217
|
-
}
|
|
218
|
-
if (!swapped) {
|
|
219
|
-
probed.exchange = "unavailable";
|
|
220
|
-
probed.detail = detail;
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
if (native.noReplace !== "link") {
|
|
225
|
-
withoutTemporaries(directory, (a, b) => {
|
|
226
|
-
createProbeFile(a, "a");
|
|
227
|
-
createProbeFile(b, "b");
|
|
228
|
-
let refused = false;
|
|
229
|
-
try {
|
|
230
|
-
renameNoReplaceWith(native, a, b);
|
|
231
|
-
} catch (error) {
|
|
232
|
-
refused = error instanceof AtomicRenameError && error.code === "EEXIST";
|
|
233
|
-
}
|
|
234
|
-
if (!refused) {
|
|
235
|
-
probed.noReplace = "link";
|
|
236
|
-
probed.detail = `${probed.detail ? `${probed.detail}; ` : ""}no-replace flag ignored by the filesystem holding ${directory}`;
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
probes.set(dev, probed);
|
|
241
|
-
return applyOverride({ ...probed });
|
|
242
|
-
}
|
|
243
|
-
function atomicRenamePrimitivesFor(directory) {
|
|
244
|
-
const support = probeAtomicRenameSupport(directory);
|
|
245
|
-
return {
|
|
246
|
-
support,
|
|
247
|
-
exchange: (a, b) => exchangeWith(support, a, b),
|
|
248
|
-
renameNoReplace: (from, to) => renameNoReplaceWith(support, from, to)
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
export {
|
|
252
|
-
AtomicRenameError,
|
|
253
|
-
atomicRenamePrimitivesFor,
|
|
254
|
-
atomicRenameSupport,
|
|
255
|
-
exchangePaths,
|
|
256
|
-
isAtomicRenameUnsupported,
|
|
257
|
-
overrideAtomicRenameSupportForTests,
|
|
258
|
-
probeAtomicRenameSupport,
|
|
259
|
-
renameNoReplace,
|
|
260
|
-
resetAtomicRenameProbesForTests
|
|
261
|
-
};
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import { constants } from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
BROWSER_UPLOAD_CHUNK_BYTES,
|
|
7
|
-
validateUploadFile
|
|
8
|
-
} from "@ricsam/r5d-api/browser-upload";
|
|
9
|
-
async function readBrowserUploadChunk(input, context) {
|
|
10
|
-
if (typeof input.filePath !== "string" || !input.filePath.trim() || input.filePath.includes("\0"))
|
|
11
|
-
throw new Error("Upload requires a file path.");
|
|
12
|
-
if (!Number.isSafeInteger(input.offset) || input.offset < 0) throw new Error("Invalid upload read offset.");
|
|
13
|
-
if (input.offset > 0 && !input.version) throw new Error("Upload continuation requires a file version.");
|
|
14
|
-
const expanded = input.filePath.replace(/\$\{R5D_ROOT\}|\$R5D_ROOT\b/g, () => context.rootDir).replace(/\$\{R5D_SESSION_ID\}|\$R5D_SESSION_ID\b/g, () => context.sessionId).replace(/^~\//, () => `${os.homedir()}/`);
|
|
15
|
-
const filePath = path.resolve(context.cwd, expanded);
|
|
16
|
-
const handle = await fs.open(filePath, constants.O_RDONLY | constants.O_NONBLOCK);
|
|
17
|
-
try {
|
|
18
|
-
const stat = await handle.stat({ bigint: true });
|
|
19
|
-
if (!stat.isFile()) throw new Error(`Upload source is not a regular file: ${input.filePath}`);
|
|
20
|
-
const metadata = validateUploadFile({ filename: path.basename(filePath), size: Number(stat.size) });
|
|
21
|
-
const versionOf = (s) => `${s.dev}:${s.ino}:${s.size}:${s.mtimeNs}:${s.ctimeNs}`;
|
|
22
|
-
const version = versionOf(stat);
|
|
23
|
-
if (input.version !== void 0 && input.version !== version)
|
|
24
|
-
throw new Error(`Upload source changed while being transferred: ${input.filePath}`);
|
|
25
|
-
if (input.offset > metadata.size) throw new Error("Upload offset is past the end of the file.");
|
|
26
|
-
const bytes = Buffer.alloc(input.metadataOnly === true ? 0 : Math.min(BROWSER_UPLOAD_CHUNK_BYTES, metadata.size - input.offset));
|
|
27
|
-
let read = 0;
|
|
28
|
-
while (read < bytes.length) {
|
|
29
|
-
const { bytesRead } = await handle.read(bytes, read, bytes.length - read, input.offset + read);
|
|
30
|
-
if (!bytesRead) throw new Error(`Upload source was truncated: ${input.filePath}`);
|
|
31
|
-
read += bytesRead;
|
|
32
|
-
}
|
|
33
|
-
if (versionOf(await handle.stat({ bigint: true })) !== version)
|
|
34
|
-
throw new Error(`Upload source changed while being read: ${input.filePath}`);
|
|
35
|
-
return { type: "browser_upload_read", ...metadata, version, offset: input.offset, base64: bytes.toString("base64") };
|
|
36
|
-
} finally {
|
|
37
|
-
await handle.close();
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
export {
|
|
41
|
-
readBrowserUploadChunk
|
|
42
|
-
};
|
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
function commandLaunchLane(request) {
|
|
3
|
-
if (request.commandClass === "control") {
|
|
4
|
-
if (request.kind !== "exec_start") throw new Error("Control commands must be exec_start launches");
|
|
5
|
-
return "control";
|
|
6
|
-
}
|
|
7
|
-
return request.kind === "exec" && request.workspaceEffect === "none" ? "utility" : "general";
|
|
8
|
-
}
|
|
9
|
-
class CommandLauncherUnavailableError extends Error {
|
|
10
|
-
constructor(message, cause) {
|
|
11
|
-
super(message, { cause });
|
|
12
|
-
this.name = "CommandLauncherUnavailableError";
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
class CommandLaunchCancelledError extends Error {
|
|
16
|
-
constructor() {
|
|
17
|
-
super("Command cancelled before it started");
|
|
18
|
-
this.name = "CommandLaunchCancelledError";
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function parseCommandLauncher(configuration) {
|
|
22
|
-
if (configuration === void 0) return void 0;
|
|
23
|
-
let argv;
|
|
24
|
-
try {
|
|
25
|
-
argv = JSON.parse(configuration);
|
|
26
|
-
} catch {
|
|
27
|
-
throw new Error("R5D_WORKER_COMMAND_LAUNCHER must be a JSON argv array");
|
|
28
|
-
}
|
|
29
|
-
if (!Array.isArray(argv) || argv.length === 0 || argv.some((part) => typeof part !== "string" || part.includes("\0")) || !argv[0])
|
|
30
|
-
throw new Error("R5D_WORKER_COMMAND_LAUNCHER must be a nonempty JSON argv array without NUL characters");
|
|
31
|
-
return argv;
|
|
32
|
-
}
|
|
33
|
-
function commandLauncherEnvironment(source = process.env) {
|
|
34
|
-
const result = {};
|
|
35
|
-
for (const [key, value] of Object.entries(source)) {
|
|
36
|
-
if (/^(PATH|HOME|USER|LOGNAME|TMP|TMPDIR|TEMP|SHELL|SYSTEMROOT|COMSPEC|PATHEXT|LANG|LC_[A-Z_]+)$/i.test(key)) result[key] = value;
|
|
37
|
-
}
|
|
38
|
-
return result;
|
|
39
|
-
}
|
|
40
|
-
class WorkerCommandLauncher {
|
|
41
|
-
constructor(options = {}) {
|
|
42
|
-
this.options = options;
|
|
43
|
-
this.mode = options.argv ? "external" : "direct";
|
|
44
|
-
if (!options.argv) {
|
|
45
|
-
this.exited = Promise.resolve();
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
this.child = spawn(options.argv[0], options.argv.slice(1), {
|
|
49
|
-
env: options.env ?? commandLauncherEnvironment(),
|
|
50
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
51
|
-
});
|
|
52
|
-
this.exited = new Promise((resolve) => {
|
|
53
|
-
this.child.once("close", (code, signal) => {
|
|
54
|
-
if (!this.closing) this.fail(new Error(`Configured command launcher exited (${signal ?? code ?? "unknown"})`));
|
|
55
|
-
resolve();
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
this.child.once("error", (error) => this.fail(error));
|
|
59
|
-
this.child.stdin.on("error", (error) => {
|
|
60
|
-
if (!this.closing) this.fail(error);
|
|
61
|
-
});
|
|
62
|
-
let buffered = "";
|
|
63
|
-
this.child.stdout.setEncoding("utf8");
|
|
64
|
-
this.child.stdout.on("data", (data) => {
|
|
65
|
-
buffered += data;
|
|
66
|
-
if (Buffer.byteLength(buffered) > 1024 * 1024) {
|
|
67
|
-
this.fail(new Error("Command launcher response exceeded the protocol limit"));
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
while (buffered.includes("\n")) {
|
|
71
|
-
const newline = buffered.indexOf("\n");
|
|
72
|
-
const line = buffered.slice(0, newline);
|
|
73
|
-
buffered = buffered.slice(newline + 1);
|
|
74
|
-
try {
|
|
75
|
-
this.receive(JSON.parse(line));
|
|
76
|
-
} catch (error) {
|
|
77
|
-
this.fail(error instanceof Error ? error : new Error(String(error)));
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
this.child.stderr.setEncoding("utf8");
|
|
83
|
-
this.child.stderr.on("data", (data) => options.onStderr?.(data));
|
|
84
|
-
}
|
|
85
|
-
options;
|
|
86
|
-
mode;
|
|
87
|
-
child;
|
|
88
|
-
pending = /* @__PURE__ */ new Map();
|
|
89
|
-
launches = /* @__PURE__ */ new Map();
|
|
90
|
-
exited;
|
|
91
|
-
failure;
|
|
92
|
-
closing = false;
|
|
93
|
-
initialized = false;
|
|
94
|
-
closePromise;
|
|
95
|
-
sequence = 0;
|
|
96
|
-
capacity;
|
|
97
|
-
async initialize() {
|
|
98
|
-
this.assertHealthy();
|
|
99
|
-
if (this.initialized) return;
|
|
100
|
-
if (this.child) {
|
|
101
|
-
try {
|
|
102
|
-
const result = await this.rpc("initialize");
|
|
103
|
-
this.capacity = parseCapacity(result.capacity);
|
|
104
|
-
} catch (error) {
|
|
105
|
-
this.fail(error instanceof Error ? error : new Error(String(error)));
|
|
106
|
-
await this.close();
|
|
107
|
-
throw error;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
this.initialized = true;
|
|
111
|
-
}
|
|
112
|
-
get activeIds() {
|
|
113
|
-
return [...this.launches.keys()];
|
|
114
|
-
}
|
|
115
|
-
/** The lane totals the launcher declared at initialization; undefined for a direct worker or an older launcher. */
|
|
116
|
-
get declaredCapacity() {
|
|
117
|
-
return this.capacity;
|
|
118
|
-
}
|
|
119
|
-
get supportsControlLane() {
|
|
120
|
-
return this.initialized && (this.mode === "direct" || (this.capacity?.controlSlots ?? 0) > 0);
|
|
121
|
-
}
|
|
122
|
-
/** Current admissions per lane, for the worker's capacity report. */
|
|
123
|
-
capacityReport() {
|
|
124
|
-
const bounded = this.child !== void 0;
|
|
125
|
-
const lanes = {
|
|
126
|
-
general: { slots: bounded ? this.capacity?.generalSlots ?? null : null, active: [], queued: [] },
|
|
127
|
-
utility: { slots: bounded ? this.capacity?.utilitySlots ?? null : null, active: [], queued: [] },
|
|
128
|
-
control: { slots: bounded ? this.capacity?.controlSlots ?? null : null, active: [], queued: [] }
|
|
129
|
-
};
|
|
130
|
-
for (const record of this.launches.values()) {
|
|
131
|
-
if (record.released) continue;
|
|
132
|
-
const entry = {
|
|
133
|
-
id: record.request.id,
|
|
134
|
-
...record.request.sessionId ? { sessionId: record.request.sessionId } : {},
|
|
135
|
-
since: record.since
|
|
136
|
-
};
|
|
137
|
-
(record.phase === "active" ? lanes[record.lane].active : lanes[record.lane].queued).push(entry);
|
|
138
|
-
}
|
|
139
|
-
return { bounded, controlRuntimeMs: bounded ? this.capacity?.controlRuntimeMs ?? null : null, lanes };
|
|
140
|
-
}
|
|
141
|
-
notifyCapacityChange() {
|
|
142
|
-
try {
|
|
143
|
-
this.options.onCapacityChange?.();
|
|
144
|
-
} catch {
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
async acquire(request) {
|
|
148
|
-
this.assertHealthy();
|
|
149
|
-
if (!this.initialized) throw new Error("Command launcher is not initialized");
|
|
150
|
-
request.assertAdmission();
|
|
151
|
-
if (this.launches.has(request.id)) throw new Error(`Duplicate command launch identity: ${request.id}`);
|
|
152
|
-
const lane = commandLaunchLane(request);
|
|
153
|
-
if (lane === "control" && !this.supportsControlLane)
|
|
154
|
-
throw new Error("Update the worker command launcher before running control commands");
|
|
155
|
-
let rejectAdmission;
|
|
156
|
-
const cancellation = new Promise((_resolve, reject) => {
|
|
157
|
-
rejectAdmission = reject;
|
|
158
|
-
});
|
|
159
|
-
void cancellation.catch(() => {
|
|
160
|
-
});
|
|
161
|
-
const record = {
|
|
162
|
-
request,
|
|
163
|
-
lane,
|
|
164
|
-
phase: this.child ? "queued" : "active",
|
|
165
|
-
since: (/* @__PURE__ */ new Date()).toISOString(),
|
|
166
|
-
revoked: false,
|
|
167
|
-
released: false,
|
|
168
|
-
rejectAdmission
|
|
169
|
-
};
|
|
170
|
-
this.launches.set(request.id, record);
|
|
171
|
-
this.notifyCapacityChange();
|
|
172
|
-
let prefix = [];
|
|
173
|
-
let admissionTimer;
|
|
174
|
-
try {
|
|
175
|
-
if (this.child) {
|
|
176
|
-
const { assertAdmission: _assertAdmission, ...fields } = request;
|
|
177
|
-
const acquisition = this.rpc("acquire", fields, this.options.acquisitionTimeoutMs ?? 9e4).then(async (result) => {
|
|
178
|
-
if (record.revoked) {
|
|
179
|
-
await this.releaseRecord(record);
|
|
180
|
-
throw new CommandLaunchCancelledError();
|
|
181
|
-
}
|
|
182
|
-
if (!Array.isArray(result.argvPrefix) || result.argvPrefix.some((part) => typeof part !== "string" || part.includes("\0")))
|
|
183
|
-
throw this.fail(new Error("Command launcher acquire returned an invalid argvPrefix"));
|
|
184
|
-
return result.argvPrefix;
|
|
185
|
-
});
|
|
186
|
-
admissionTimer = setInterval(() => {
|
|
187
|
-
try {
|
|
188
|
-
request.assertAdmission();
|
|
189
|
-
} catch (error) {
|
|
190
|
-
record.revoked = true;
|
|
191
|
-
record.rejectAdmission(error);
|
|
192
|
-
void this.cancel(request.id).catch(() => {
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
}, 100);
|
|
196
|
-
prefix = await Promise.race([acquisition, cancellation]);
|
|
197
|
-
record.phase = "active";
|
|
198
|
-
record.since = (/* @__PURE__ */ new Date()).toISOString();
|
|
199
|
-
this.notifyCapacityChange();
|
|
200
|
-
}
|
|
201
|
-
request.assertAdmission();
|
|
202
|
-
this.assertHealthy();
|
|
203
|
-
if (record.revoked) throw new CommandLaunchCancelledError();
|
|
204
|
-
} catch (error) {
|
|
205
|
-
if (!this.failure && !this.closing) await this.releaseRecord(record);
|
|
206
|
-
throw error;
|
|
207
|
-
} finally {
|
|
208
|
-
if (admissionTimer) clearInterval(admissionTimer);
|
|
209
|
-
}
|
|
210
|
-
return {
|
|
211
|
-
wrap: (argv) => {
|
|
212
|
-
this.assertHealthy();
|
|
213
|
-
request.assertAdmission();
|
|
214
|
-
if (record.revoked) throw new CommandLaunchCancelledError();
|
|
215
|
-
if (record.released) throw new Error("Command launch lease is already released");
|
|
216
|
-
if (!argv.length) throw new Error("Command argv must not be empty");
|
|
217
|
-
return [...prefix, ...argv];
|
|
218
|
-
},
|
|
219
|
-
cancel: async () => {
|
|
220
|
-
await this.cancel(request.id);
|
|
221
|
-
},
|
|
222
|
-
diagnose: async () => {
|
|
223
|
-
this.assertHealthy();
|
|
224
|
-
if (!this.child) return void 0;
|
|
225
|
-
try {
|
|
226
|
-
const result = await this.rpc("diagnose", { id: request.id });
|
|
227
|
-
if (result.message !== void 0 && typeof result.message !== "string")
|
|
228
|
-
throw new Error("Command launcher returned an invalid diagnostic");
|
|
229
|
-
return result.message;
|
|
230
|
-
} catch (error) {
|
|
231
|
-
throw this.fail(error instanceof Error ? error : new Error(String(error)));
|
|
232
|
-
}
|
|
233
|
-
},
|
|
234
|
-
release: () => this.releaseRecord(record)
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
/** Revoke a launch. Returns whether this launcher still held it (queued or acquired, not yet released). */
|
|
238
|
-
async cancel(id) {
|
|
239
|
-
const record = this.launches.get(id);
|
|
240
|
-
if (!record || record.released) return false;
|
|
241
|
-
record.revoked = true;
|
|
242
|
-
record.rejectAdmission(new CommandLaunchCancelledError());
|
|
243
|
-
if (this.child) {
|
|
244
|
-
try {
|
|
245
|
-
await this.rpc("cancel", { id });
|
|
246
|
-
} catch (error) {
|
|
247
|
-
throw this.fail(error instanceof Error ? error : new Error(String(error)));
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return true;
|
|
251
|
-
}
|
|
252
|
-
async cancelSession(sessionId) {
|
|
253
|
-
await this.cancelMatching((_id, ownerSessionId) => ownerSessionId === sessionId);
|
|
254
|
-
}
|
|
255
|
-
async cancelMatching(matches) {
|
|
256
|
-
const results = await Promise.allSettled(
|
|
257
|
-
[...this.launches].filter(([id, record]) => matches(id, record.request.sessionId)).map(([id]) => this.cancel(id))
|
|
258
|
-
);
|
|
259
|
-
const errors = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
260
|
-
if (errors.length) throw new AggregateError(errors, "Command launcher cancellation was incomplete");
|
|
261
|
-
}
|
|
262
|
-
close() {
|
|
263
|
-
return this.closePromise ??= (async () => {
|
|
264
|
-
this.closing = true;
|
|
265
|
-
for (const record of this.launches.values()) {
|
|
266
|
-
record.revoked = true;
|
|
267
|
-
record.rejectAdmission(new CommandLaunchCancelledError());
|
|
268
|
-
}
|
|
269
|
-
this.rejectPending(new CommandLauncherUnavailableError("Command launcher is shutting down"));
|
|
270
|
-
this.child?.stdin.end();
|
|
271
|
-
const exited = await this.waitForExit(6e3);
|
|
272
|
-
if (!exited) {
|
|
273
|
-
this.child?.kill("SIGKILL");
|
|
274
|
-
await this.waitForExit(1e3);
|
|
275
|
-
}
|
|
276
|
-
})();
|
|
277
|
-
}
|
|
278
|
-
async waitForExit(timeoutMs) {
|
|
279
|
-
let timer;
|
|
280
|
-
try {
|
|
281
|
-
return await Promise.race([
|
|
282
|
-
this.exited.then(() => true),
|
|
283
|
-
new Promise((resolve) => {
|
|
284
|
-
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
285
|
-
})
|
|
286
|
-
]);
|
|
287
|
-
} finally {
|
|
288
|
-
if (timer) clearTimeout(timer);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
releaseRecord(record) {
|
|
292
|
-
return record.release ??= (async () => {
|
|
293
|
-
record.revoked = true;
|
|
294
|
-
if (this.child) {
|
|
295
|
-
try {
|
|
296
|
-
await this.rpc("release", { id: record.request.id });
|
|
297
|
-
} catch (error) {
|
|
298
|
-
throw this.fail(error instanceof Error ? error : new Error(String(error)));
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
record.released = true;
|
|
302
|
-
this.launches.delete(record.request.id);
|
|
303
|
-
this.notifyCapacityChange();
|
|
304
|
-
})();
|
|
305
|
-
}
|
|
306
|
-
assertHealthy() {
|
|
307
|
-
if (this.failure) throw this.failure;
|
|
308
|
-
if (this.closing) throw new CommandLauncherUnavailableError("Command launcher is shutting down");
|
|
309
|
-
}
|
|
310
|
-
rpc(method, fields = {}, timeoutMs = this.options.requestTimeoutMs ?? 1e4) {
|
|
311
|
-
try {
|
|
312
|
-
this.assertHealthy();
|
|
313
|
-
} catch (error) {
|
|
314
|
-
return Promise.reject(error);
|
|
315
|
-
}
|
|
316
|
-
const requestId = String(++this.sequence);
|
|
317
|
-
return new Promise((resolve, reject) => {
|
|
318
|
-
const timer = setTimeout(() => this.fail(new Error(`Command launcher ${method} request timed out`)), timeoutMs);
|
|
319
|
-
this.pending.set(requestId, { resolve, reject, timer });
|
|
320
|
-
this.child.stdin.write(`${JSON.stringify({ requestId, method, ...fields })}
|
|
321
|
-
`, (error) => {
|
|
322
|
-
if (error) this.fail(error);
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
receive(value) {
|
|
327
|
-
if (!value || typeof value !== "object") throw new Error("Invalid command launcher response");
|
|
328
|
-
const response = value;
|
|
329
|
-
if (typeof response.requestId !== "string") throw new Error("Command launcher response is missing requestId");
|
|
330
|
-
const request = this.pending.get(response.requestId);
|
|
331
|
-
if (!request) throw new Error("Command launcher returned an unknown requestId");
|
|
332
|
-
this.pending.delete(response.requestId);
|
|
333
|
-
clearTimeout(request.timer);
|
|
334
|
-
if (typeof response.error === "string") {
|
|
335
|
-
request.reject(new Error(response.error));
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
if (!response.result || typeof response.result !== "object" || Array.isArray(response.result)) {
|
|
339
|
-
const error = new Error("Command launcher response is missing result");
|
|
340
|
-
request.reject(error);
|
|
341
|
-
throw error;
|
|
342
|
-
}
|
|
343
|
-
request.resolve(response.result);
|
|
344
|
-
}
|
|
345
|
-
rejectPending(error) {
|
|
346
|
-
for (const request of this.pending.values()) {
|
|
347
|
-
clearTimeout(request.timer);
|
|
348
|
-
request.reject(error);
|
|
349
|
-
}
|
|
350
|
-
this.pending.clear();
|
|
351
|
-
}
|
|
352
|
-
fail(error) {
|
|
353
|
-
if (this.failure || this.closing) return this.failure ?? error;
|
|
354
|
-
this.failure = new CommandLauncherUnavailableError(`Command launcher unavailable: ${error.message}`, error);
|
|
355
|
-
for (const record of this.launches.values()) {
|
|
356
|
-
record.revoked = true;
|
|
357
|
-
record.rejectAdmission(this.failure);
|
|
358
|
-
}
|
|
359
|
-
this.rejectPending(this.failure);
|
|
360
|
-
this.child?.kill("SIGTERM");
|
|
361
|
-
this.options.onFailure?.(this.failure, this.activeIds);
|
|
362
|
-
return this.failure;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
function parseCapacity(value) {
|
|
366
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
367
|
-
const candidate = value;
|
|
368
|
-
const numbers = ["generalSlots", "utilitySlots", "controlSlots", "controlRuntimeMs", "queueTimeoutMs"];
|
|
369
|
-
if (!numbers.every((key) => typeof candidate[key] === "number" && Number.isSafeInteger(candidate[key]) && candidate[key] >= 0)) {
|
|
370
|
-
throw new Error("Command launcher initialize returned an invalid capacity");
|
|
371
|
-
}
|
|
372
|
-
return {
|
|
373
|
-
generalSlots: candidate.generalSlots,
|
|
374
|
-
utilitySlots: candidate.utilitySlots,
|
|
375
|
-
controlSlots: candidate.controlSlots,
|
|
376
|
-
controlRuntimeMs: candidate.controlRuntimeMs,
|
|
377
|
-
queueTimeoutMs: candidate.queueTimeoutMs
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
async function createWorkerCommandLauncher(options = {}) {
|
|
381
|
-
const launcher = new WorkerCommandLauncher({ ...options, argv: parseCommandLauncher(options.configuration) });
|
|
382
|
-
await launcher.initialize();
|
|
383
|
-
return launcher;
|
|
384
|
-
}
|
|
385
|
-
export {
|
|
386
|
-
CommandLaunchCancelledError,
|
|
387
|
-
CommandLauncherUnavailableError,
|
|
388
|
-
WorkerCommandLauncher,
|
|
389
|
-
commandLaunchLane,
|
|
390
|
-
commandLauncherEnvironment,
|
|
391
|
-
createWorkerCommandLauncher,
|
|
392
|
-
parseCommandLauncher
|
|
393
|
-
};
|