@stim-cli/core 1.20.2 → 1.22.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 +2 -0
- package/dist/{core-BkgfKqJ8.mjs → core-BTsCO1nW.mjs} +444 -60
- package/dist/{index-VDWBflH3.d.mts → index-DKq1j8dw.d.mts} +340 -91
- package/dist/index.d.mts +101 -5
- package/dist/index.mjs +3 -3
- package/dist/keyed-delta-BBoOeR2J.mjs +289 -0
- package/dist/noise-DYIbymAJ.d.mts +100 -0
- package/dist/noise-session.d.mts +164 -0
- package/dist/noise-session.mjs +402 -0
- package/dist/noise.d.mts +2 -0
- package/dist/noise.mjs +393 -0
- package/dist/oversight.mjs +16 -6
- package/dist/{ownership-claim-O31Y5iIb.mjs → ownership-claim-DHUs_Dir.mjs} +1 -1
- package/dist/{ownership-claim-Xxxr9MPp.d.mts → ownership-claim-RY3fbm1D.d.mts} +3 -2
- package/dist/ownership-claim.d.mts +2 -2
- package/dist/ownership-claim.mjs +2 -2
- package/dist/phone-protocol.d.mts +85 -16
- package/dist/phone-protocol.mjs +2 -2
- package/dist/{process-identity-BTyww2Xj.d.mts → process-identity-DVNGpF5c.d.mts} +1 -1
- package/dist/process-identity.d.mts +1 -1
- package/dist/process-identity.mjs +3 -1
- package/dist/{protocol-pH632kA3.d.mts → protocol-BjJt4ghB.d.mts} +632 -65
- package/dist/protocol.d.mts +2 -2
- package/dist/protocol.mjs +81 -10
- package/dist/receive-protocol.d.mts +1 -1
- package/dist/receive-protocol.mjs +20163 -14469
- package/dist/state.d.mts +2 -2
- package/dist/state.mjs +1261 -208
- package/dist/toolchain.d.mts +64 -0
- package/dist/toolchain.mjs +326 -0
- package/dist/workspace-link.d.mts +16 -0
- package/dist/workspace-link.mjs +50 -0
- package/package.json +21 -1
- package/dist/status-derived-C13g6eQt.mjs +0 -151
package/dist/noise.mjs
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, createHash, createHmac, createPrivateKey, createPublicKey, diffieHellman, generateKeyPairSync } from "node:crypto";
|
|
2
|
+
//#region noise.ts
|
|
3
|
+
const NOISE_XX = "Noise_XX_25519_ChaChaPoly_SHA256";
|
|
4
|
+
const NOISE_XX_PSK2 = "Noise_XXpsk2_25519_ChaChaPoly_SHA256";
|
|
5
|
+
/** Largest handshake message either side writes or reads, payload included. */
|
|
6
|
+
const MAX_HANDSHAKE_MESSAGE_BYTES = 1024;
|
|
7
|
+
const KEY_BYTES = 32;
|
|
8
|
+
const HASH_BYTES = 32;
|
|
9
|
+
const TAG_BYTES = 16;
|
|
10
|
+
const MAX_NONCE = 2n ** 64n - 1n;
|
|
11
|
+
const EMPTY = Buffer.alloc(0);
|
|
12
|
+
const TEXT = 1;
|
|
13
|
+
const BINARY = 2;
|
|
14
|
+
const PKCS8_X25519 = Buffer.from("302e020100300506032b656e04220420", "hex");
|
|
15
|
+
const SPKI_X25519 = Buffer.from("302a300506032b656e032100", "hex");
|
|
16
|
+
/** Thrown for any handshake or transport failure; the handshake or transport is unusable afterwards. */
|
|
17
|
+
var NoiseError = class extends Error {
|
|
18
|
+
name = "NoiseError";
|
|
19
|
+
};
|
|
20
|
+
function generateNoiseKeyPair() {
|
|
21
|
+
const { privateKey } = generateKeyPairSync("x25519");
|
|
22
|
+
return noiseKeyPairFromPrivateKey(privateKey.export({
|
|
23
|
+
format: "der",
|
|
24
|
+
type: "pkcs8"
|
|
25
|
+
}).subarray(-32));
|
|
26
|
+
}
|
|
27
|
+
function noiseKeyPairFromPrivateKey(privateKey) {
|
|
28
|
+
if (privateKey.length !== KEY_BYTES) throw new RangeError("An X25519 private key is 32 bytes");
|
|
29
|
+
const key = createPrivateKey({
|
|
30
|
+
key: Buffer.concat([PKCS8_X25519, privateKey]),
|
|
31
|
+
format: "der",
|
|
32
|
+
type: "pkcs8"
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
publicKey: createPublicKey(key).export({
|
|
36
|
+
format: "der",
|
|
37
|
+
type: "spki"
|
|
38
|
+
}).subarray(-32),
|
|
39
|
+
privateKey: Buffer.from(privateKey)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const nodeDh = (privateKey, publicKey) => diffieHellman({
|
|
43
|
+
privateKey: createPrivateKey({
|
|
44
|
+
key: Buffer.concat([PKCS8_X25519, privateKey]),
|
|
45
|
+
format: "der",
|
|
46
|
+
type: "pkcs8"
|
|
47
|
+
}),
|
|
48
|
+
publicKey: createPublicKey({
|
|
49
|
+
key: Buffer.concat([SPKI_X25519, publicKey]),
|
|
50
|
+
format: "der",
|
|
51
|
+
type: "spki"
|
|
52
|
+
})
|
|
53
|
+
});
|
|
54
|
+
function nonceBytes(n) {
|
|
55
|
+
const nonce = Buffer.alloc(12);
|
|
56
|
+
nonce.writeBigUInt64LE(n, 4);
|
|
57
|
+
return nonce;
|
|
58
|
+
}
|
|
59
|
+
function aeadEncrypt(k, n, ad, plaintext) {
|
|
60
|
+
const cipher = createCipheriv("chacha20-poly1305", k, nonceBytes(n), { authTagLength: TAG_BYTES });
|
|
61
|
+
cipher.setAAD(ad, { plaintextLength: plaintext.length });
|
|
62
|
+
return Buffer.concat([
|
|
63
|
+
cipher.update(plaintext),
|
|
64
|
+
cipher.final(),
|
|
65
|
+
cipher.getAuthTag()
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
function aeadDecrypt(k, n, ad, ciphertext) {
|
|
69
|
+
if (ciphertext.length < TAG_BYTES) throw new NoiseError("Message is shorter than its authentication tag");
|
|
70
|
+
const body = ciphertext.subarray(0, ciphertext.length - TAG_BYTES);
|
|
71
|
+
const decipher = createDecipheriv("chacha20-poly1305", k, nonceBytes(n), { authTagLength: TAG_BYTES });
|
|
72
|
+
decipher.setAAD(ad, { plaintextLength: body.length });
|
|
73
|
+
decipher.setAuthTag(ciphertext.subarray(ciphertext.length - TAG_BYTES));
|
|
74
|
+
try {
|
|
75
|
+
return Buffer.concat([decipher.update(body), decipher.final()]);
|
|
76
|
+
} catch {
|
|
77
|
+
throw new NoiseError("Message failed authentication");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function sha256(...parts) {
|
|
81
|
+
const hash = createHash("sha256");
|
|
82
|
+
for (const part of parts) hash.update(part);
|
|
83
|
+
return hash.digest();
|
|
84
|
+
}
|
|
85
|
+
function hmac(key, ...parts) {
|
|
86
|
+
const mac = createHmac("sha256", key);
|
|
87
|
+
for (const part of parts) mac.update(part);
|
|
88
|
+
return mac.digest();
|
|
89
|
+
}
|
|
90
|
+
function hkdf(chainingKey, input) {
|
|
91
|
+
const temp = hmac(chainingKey, input);
|
|
92
|
+
const first = hmac(temp, Uint8Array.of(1));
|
|
93
|
+
const second = hmac(temp, first, Uint8Array.of(2));
|
|
94
|
+
return [
|
|
95
|
+
first,
|
|
96
|
+
second,
|
|
97
|
+
hmac(temp, second, Uint8Array.of(3))
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
/** Noise's CipherState: a key and a 64-bit counter used as the nonce. */
|
|
101
|
+
var CipherState = class {
|
|
102
|
+
#k;
|
|
103
|
+
#n = 0n;
|
|
104
|
+
constructor(key) {
|
|
105
|
+
if (key && key.length !== KEY_BYTES) throw new RangeError("A cipher key is 32 bytes");
|
|
106
|
+
this.#k = key && Buffer.from(key);
|
|
107
|
+
}
|
|
108
|
+
hasKey() {
|
|
109
|
+
return this.#k !== void 0;
|
|
110
|
+
}
|
|
111
|
+
setNonce(n) {
|
|
112
|
+
this.#n = n;
|
|
113
|
+
}
|
|
114
|
+
encryptWithAd(ad, plaintext) {
|
|
115
|
+
if (!this.#k) return Buffer.from(plaintext);
|
|
116
|
+
if (this.#n >= MAX_NONCE) throw new NoiseError("Nonce exhausted");
|
|
117
|
+
const ciphertext = aeadEncrypt(this.#k, this.#n, ad, plaintext);
|
|
118
|
+
this.#n++;
|
|
119
|
+
return ciphertext;
|
|
120
|
+
}
|
|
121
|
+
decryptWithAd(ad, ciphertext) {
|
|
122
|
+
if (!this.#k) return Buffer.from(ciphertext);
|
|
123
|
+
if (this.#n >= MAX_NONCE) throw new NoiseError("Nonce exhausted");
|
|
124
|
+
const plaintext = aeadDecrypt(this.#k, this.#n, ad, ciphertext);
|
|
125
|
+
this.#n++;
|
|
126
|
+
return plaintext;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var SymmetricState = class {
|
|
130
|
+
#ck;
|
|
131
|
+
#h;
|
|
132
|
+
cipher = new CipherState();
|
|
133
|
+
constructor(protocol) {
|
|
134
|
+
const name = Buffer.from(protocol, "ascii");
|
|
135
|
+
this.#h = name.length <= HASH_BYTES ? Buffer.concat([name, Buffer.alloc(HASH_BYTES - name.length)]) : sha256(name);
|
|
136
|
+
this.#ck = this.#h;
|
|
137
|
+
}
|
|
138
|
+
get handshakeHash() {
|
|
139
|
+
return Buffer.from(this.#h);
|
|
140
|
+
}
|
|
141
|
+
mixKey(input) {
|
|
142
|
+
const [ck, k] = hkdf(this.#ck, input);
|
|
143
|
+
this.#ck = ck;
|
|
144
|
+
this.cipher = new CipherState(k);
|
|
145
|
+
}
|
|
146
|
+
mixHash(data) {
|
|
147
|
+
this.#h = sha256(this.#h, data);
|
|
148
|
+
}
|
|
149
|
+
mixKeyAndHash(input) {
|
|
150
|
+
const [ck, h, k] = hkdf(this.#ck, input);
|
|
151
|
+
this.#ck = ck;
|
|
152
|
+
this.mixHash(h);
|
|
153
|
+
this.cipher = new CipherState(k);
|
|
154
|
+
}
|
|
155
|
+
encryptAndHash(plaintext) {
|
|
156
|
+
const ciphertext = this.cipher.encryptWithAd(this.#h, plaintext);
|
|
157
|
+
this.mixHash(ciphertext);
|
|
158
|
+
return ciphertext;
|
|
159
|
+
}
|
|
160
|
+
decryptAndHash(ciphertext) {
|
|
161
|
+
const plaintext = this.cipher.decryptWithAd(this.#h, ciphertext);
|
|
162
|
+
this.mixHash(ciphertext);
|
|
163
|
+
return plaintext;
|
|
164
|
+
}
|
|
165
|
+
split() {
|
|
166
|
+
const [first, second] = hkdf(this.#ck, EMPTY);
|
|
167
|
+
return [new CipherState(first), new CipherState(second)];
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const PATTERNS = {
|
|
171
|
+
[NOISE_XX]: [
|
|
172
|
+
["e"],
|
|
173
|
+
[
|
|
174
|
+
"e",
|
|
175
|
+
"ee",
|
|
176
|
+
"s",
|
|
177
|
+
"es"
|
|
178
|
+
],
|
|
179
|
+
["s", "se"]
|
|
180
|
+
],
|
|
181
|
+
[NOISE_XX_PSK2]: [
|
|
182
|
+
["e"],
|
|
183
|
+
[
|
|
184
|
+
"e",
|
|
185
|
+
"ee",
|
|
186
|
+
"s",
|
|
187
|
+
"es",
|
|
188
|
+
"psk"
|
|
189
|
+
],
|
|
190
|
+
["s", "se"]
|
|
191
|
+
]
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Noise's HandshakeState for {@link NOISE_XX} and {@link NOISE_XX_PSK2}. The initiator writes messages 1 and 3. Any
|
|
195
|
+
* error from {@link writeMessage} or {@link readMessage}, including an out-of-turn call, a call after the last
|
|
196
|
+
* message, or an invalid or oversized message, ends the handshake: every later call throws {@link NoiseError}.
|
|
197
|
+
*/
|
|
198
|
+
var HandshakeState = class {
|
|
199
|
+
#symmetric;
|
|
200
|
+
#messages;
|
|
201
|
+
#initiator;
|
|
202
|
+
#s;
|
|
203
|
+
#psk;
|
|
204
|
+
#dh;
|
|
205
|
+
#e;
|
|
206
|
+
#re;
|
|
207
|
+
#rs;
|
|
208
|
+
#index = 0;
|
|
209
|
+
#failed = false;
|
|
210
|
+
#split = false;
|
|
211
|
+
constructor(options) {
|
|
212
|
+
if (options.protocol === "Noise_XXpsk2_25519_ChaChaPoly_SHA256" !== (options.psk !== void 0)) throw new TypeError(`${options.protocol} psk mismatch`);
|
|
213
|
+
if (options.psk && options.psk.length !== KEY_BYTES) throw new RangeError("A PSK is 32 bytes");
|
|
214
|
+
this.#symmetric = new SymmetricState(options.protocol);
|
|
215
|
+
this.#symmetric.mixHash(options.prologue);
|
|
216
|
+
this.#messages = PATTERNS[options.protocol];
|
|
217
|
+
this.#initiator = options.initiator;
|
|
218
|
+
this.#s = options.staticKeyPair;
|
|
219
|
+
this.#psk = options.psk && Buffer.from(options.psk);
|
|
220
|
+
this.#e = options.ephemeralKeyPair;
|
|
221
|
+
this.#dh = options.dh ?? nodeDh;
|
|
222
|
+
}
|
|
223
|
+
get isComplete() {
|
|
224
|
+
return this.#index === this.#messages.length;
|
|
225
|
+
}
|
|
226
|
+
/** The peer's static public key, once the message that carries it has been read and authenticated. */
|
|
227
|
+
get remoteStaticPublicKey() {
|
|
228
|
+
return this.#failed || !this.#rs ? void 0 : Buffer.from(this.#rs);
|
|
229
|
+
}
|
|
230
|
+
get handshakeHash() {
|
|
231
|
+
if (!this.isComplete) throw new NoiseError("The handshake is not complete");
|
|
232
|
+
return this.#symmetric.handshakeHash;
|
|
233
|
+
}
|
|
234
|
+
writeMessage(payload = EMPTY) {
|
|
235
|
+
return this.#step(true, (tokens) => {
|
|
236
|
+
const parts = [];
|
|
237
|
+
for (const token of tokens) if (token === "e") {
|
|
238
|
+
this.#e ??= generateNoiseKeyPair();
|
|
239
|
+
parts.push(this.#e.publicKey);
|
|
240
|
+
this.#mixEphemeral(this.#e.publicKey);
|
|
241
|
+
} else if (token === "s") parts.push(this.#symmetric.encryptAndHash(this.#s.publicKey));
|
|
242
|
+
else this.#mixToken(token);
|
|
243
|
+
parts.push(this.#symmetric.encryptAndHash(payload));
|
|
244
|
+
const message = Buffer.concat(parts);
|
|
245
|
+
if (message.length > 1024) throw new NoiseError("Handshake message is too large");
|
|
246
|
+
return message;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
readMessage(message) {
|
|
250
|
+
return this.#step(false, (tokens) => {
|
|
251
|
+
if (message.length > 1024) throw new NoiseError("Handshake message is too large");
|
|
252
|
+
let offset = 0;
|
|
253
|
+
const take = (length) => {
|
|
254
|
+
if (message.length - offset < length) throw new NoiseError("Handshake message is truncated");
|
|
255
|
+
const part = Buffer.from(message.subarray(offset, offset + length));
|
|
256
|
+
offset += length;
|
|
257
|
+
return part;
|
|
258
|
+
};
|
|
259
|
+
for (const token of tokens) if (token === "e") {
|
|
260
|
+
this.#re = take(KEY_BYTES);
|
|
261
|
+
this.#mixEphemeral(this.#re);
|
|
262
|
+
} else if (token === "s") {
|
|
263
|
+
const length = KEY_BYTES + (this.#symmetric.cipher.hasKey() ? TAG_BYTES : 0);
|
|
264
|
+
this.#rs = this.#symmetric.decryptAndHash(take(length));
|
|
265
|
+
} else this.#mixToken(token);
|
|
266
|
+
return this.#symmetric.decryptAndHash(message.subarray(offset));
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/** Noise's Split(), returning this side's sending and receiving cipher states. It can be called once. */
|
|
270
|
+
split() {
|
|
271
|
+
if (this.#failed || !this.isComplete) throw new NoiseError("The handshake is not complete");
|
|
272
|
+
if (this.#split) throw new NoiseError("The handshake has already been split");
|
|
273
|
+
this.#split = true;
|
|
274
|
+
const [initiatorToResponder, responderToInitiator] = this.#symmetric.split();
|
|
275
|
+
return this.#initiator ? {
|
|
276
|
+
send: initiatorToResponder,
|
|
277
|
+
receive: responderToInitiator
|
|
278
|
+
} : {
|
|
279
|
+
send: responderToInitiator,
|
|
280
|
+
receive: initiatorToResponder
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
#step(writing, run) {
|
|
284
|
+
if (this.#failed) throw new NoiseError("The handshake has failed");
|
|
285
|
+
try {
|
|
286
|
+
const tokens = this.#messages[this.#index];
|
|
287
|
+
if (!tokens) throw new NoiseError("The handshake is complete");
|
|
288
|
+
if (writing !== (this.#initiator === (this.#index % 2 === 0))) throw new NoiseError(`It is not this side's turn to ${writing ? "write" : "read"}`);
|
|
289
|
+
const result = run(tokens);
|
|
290
|
+
this.#index++;
|
|
291
|
+
return result;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
this.#failed = true;
|
|
294
|
+
throw error instanceof NoiseError ? error : new NoiseError("Handshake failed", { cause: error });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
#mixEphemeral(publicKey) {
|
|
298
|
+
this.#symmetric.mixHash(publicKey);
|
|
299
|
+
if (this.#psk) this.#symmetric.mixKey(publicKey);
|
|
300
|
+
}
|
|
301
|
+
#x25519(keyPair, publicKey) {
|
|
302
|
+
try {
|
|
303
|
+
return Buffer.from(this.#dh(keyPair.privateKey, publicKey));
|
|
304
|
+
} catch {
|
|
305
|
+
throw new NoiseError("X25519 failed; the peer sent an invalid public key");
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
#mixToken(token) {
|
|
309
|
+
if (token === "psk") {
|
|
310
|
+
if (!this.#psk) throw new NoiseError("Pattern needs a PSK");
|
|
311
|
+
this.#symmetric.mixKeyAndHash(this.#psk);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const remote = (key) => {
|
|
315
|
+
if (!key) throw new NoiseError(`Token ${token} needs a remote key`);
|
|
316
|
+
return key;
|
|
317
|
+
};
|
|
318
|
+
const e = this.#e;
|
|
319
|
+
if (!e) throw new NoiseError(`Token ${token} needs an ephemeral key`);
|
|
320
|
+
if (token === "ee") this.#symmetric.mixKey(this.#x25519(e, remote(this.#re)));
|
|
321
|
+
else if (token === "es") this.#symmetric.mixKey(this.#initiator ? this.#x25519(e, remote(this.#rs)) : this.#x25519(this.#s, remote(this.#re)));
|
|
322
|
+
else this.#symmetric.mixKey(this.#initiator ? this.#x25519(this.#s, remote(this.#re)) : this.#x25519(e, remote(this.#rs)));
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
const utf8 = new TextDecoder("utf-8", {
|
|
326
|
+
fatal: true,
|
|
327
|
+
ignoreBOM: true
|
|
328
|
+
});
|
|
329
|
+
/**
|
|
330
|
+
* Stim's transport framing after the handshake: each WebSocket message is one ChaCha20-Poly1305 message with empty
|
|
331
|
+
* associated data under the next counter value. The first plaintext byte is the message type, 0x01 text or 0x02
|
|
332
|
+
* binary. Counters are not sent, so a reordered, replayed, dropped or altered message fails to decrypt. Any decryption
|
|
333
|
+
* failure closes the transport: every later call throws {@link NoiseError}. Nonce 2^64-1 is reserved and never used:
|
|
334
|
+
* after a direction's last usable message, at counter 2^64-2, the next call in that direction closes the transport.
|
|
335
|
+
*/
|
|
336
|
+
var NoiseTransport = class {
|
|
337
|
+
#send;
|
|
338
|
+
#receive;
|
|
339
|
+
#maxSend;
|
|
340
|
+
#maxReceive;
|
|
341
|
+
#closed = false;
|
|
342
|
+
constructor(options) {
|
|
343
|
+
if (!options.send.hasKey() || !options.receive.hasKey()) throw new TypeError("Transport cipher states need keys");
|
|
344
|
+
this.#send = options.send;
|
|
345
|
+
this.#receive = options.receive;
|
|
346
|
+
this.#maxSend = options.maxSendBytes;
|
|
347
|
+
this.#maxReceive = options.maxReceiveBytes;
|
|
348
|
+
}
|
|
349
|
+
get closed() {
|
|
350
|
+
return this.#closed;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Encrypts a string as a text message or bytes as a binary message. Data over `maxSendBytes` (UTF-8 bytes for text)
|
|
354
|
+
* throws a `RangeError`, consumes no counter, and leaves the transport open: it is the caller's own error and nothing
|
|
355
|
+
* was sent.
|
|
356
|
+
*/
|
|
357
|
+
encrypt(data) {
|
|
358
|
+
this.#assertOpen();
|
|
359
|
+
const body = typeof data === "string" ? Buffer.from(data, "utf8") : data;
|
|
360
|
+
if (body.length > this.#maxSend) throw new RangeError(`Message exceeds ${this.#maxSend} bytes`);
|
|
361
|
+
try {
|
|
362
|
+
return this.#send.encryptWithAd(EMPTY, Buffer.concat([Uint8Array.of(typeof data === "string" ? TEXT : BINARY), body]));
|
|
363
|
+
} catch (error) {
|
|
364
|
+
this.#closed = true;
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
decrypt(message) {
|
|
369
|
+
this.#assertOpen();
|
|
370
|
+
try {
|
|
371
|
+
if (message.length > this.#maxReceive + 1 + TAG_BYTES) throw new NoiseError("Message is too large");
|
|
372
|
+
const plaintext = this.#receive.decryptWithAd(EMPTY, message);
|
|
373
|
+
const body = plaintext.subarray(1);
|
|
374
|
+
if (plaintext[0] === TEXT) return {
|
|
375
|
+
type: "text",
|
|
376
|
+
data: utf8.decode(body)
|
|
377
|
+
};
|
|
378
|
+
if (plaintext[0] === BINARY) return {
|
|
379
|
+
type: "binary",
|
|
380
|
+
data: body
|
|
381
|
+
};
|
|
382
|
+
throw new NoiseError("Unknown message type");
|
|
383
|
+
} catch (error) {
|
|
384
|
+
this.#closed = true;
|
|
385
|
+
throw error instanceof NoiseError ? error : new NoiseError("Message is not valid", { cause: error });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
#assertOpen() {
|
|
389
|
+
if (this.#closed) throw new NoiseError("The transport is closed");
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
//#endregion
|
|
393
|
+
export { CipherState, HandshakeState, MAX_HANDSHAKE_MESSAGE_BYTES, NOISE_XX, NOISE_XX_PSK2, NoiseError, NoiseTransport, generateNoiseKeyPair, noiseKeyPairFromPrivateKey };
|
package/dist/oversight.mjs
CHANGED
|
@@ -389,12 +389,13 @@ function overseeMerge(run, { env, entry, notify }, prev) {
|
|
|
389
389
|
* handles is left out. The allowance returns when the workspace stops being idle or has no item left.
|
|
390
390
|
*/
|
|
391
391
|
function overseeAttention(run, { env, entry, notify }, prev) {
|
|
392
|
-
const items =
|
|
392
|
+
const { items, wakeAt } = workspaceAttention(env, {
|
|
393
393
|
now: run.now,
|
|
394
394
|
stuckMinutes: run.prefs.stuckMinutes,
|
|
395
395
|
easSessionMinutes: EAS_SESSION_MINUTES,
|
|
396
396
|
ownLeases: run.input.ownLeases
|
|
397
397
|
});
|
|
398
|
+
if (wakeAt !== null) wake(run, wakeAt);
|
|
398
399
|
const pending = [];
|
|
399
400
|
for (const item of items) {
|
|
400
401
|
if (item.category !== "attention") continue;
|
|
@@ -558,9 +559,10 @@ const SIGNING_CODES = [
|
|
|
558
559
|
/** A failed run older than this on an idle workspace is history, not a problem. */
|
|
559
560
|
const STALE_MS = 864e5;
|
|
560
561
|
const recent = (env, at, now) => env.live || now - time(at) < 864e5;
|
|
561
|
-
function
|
|
562
|
+
function workspaceAttention(env, input, format = attentionBody) {
|
|
562
563
|
const { now } = input;
|
|
563
564
|
const items = [];
|
|
565
|
+
let wakeAt = null;
|
|
564
566
|
for (const issue of env.issues ?? []) {
|
|
565
567
|
if (issue.severity === "info" || !PERSON_ISSUES.has(issue.code)) continue;
|
|
566
568
|
items.push({
|
|
@@ -610,7 +612,9 @@ function workspaceItems(env, input, format = attentionBody) {
|
|
|
610
612
|
});
|
|
611
613
|
}
|
|
612
614
|
for (const device of env.physicalDevices ?? []) {
|
|
613
|
-
|
|
615
|
+
const expiresAt = time(device.lease.expiresAt);
|
|
616
|
+
if (expiresAt > now) wakeAt = Math.min(wakeAt ?? expiresAt, expiresAt);
|
|
617
|
+
if (!(expiresAt <= now)) continue;
|
|
614
618
|
const slot = device.slot === "default" ? "" : ` --slot ${device.slot}`;
|
|
615
619
|
items.push({
|
|
616
620
|
id: `lease-${device.platform}-${device.slot}:${env.path}`,
|
|
@@ -628,7 +632,10 @@ function workspaceItems(env, input, format = attentionBody) {
|
|
|
628
632
|
const driven = devices.find((d) => d.running && agentDriven(d, input.ownLeases ?? []));
|
|
629
633
|
for (const session of env.remoteDevices ?? []) {
|
|
630
634
|
const started = time(session.startedAt);
|
|
631
|
-
|
|
635
|
+
const due = started + input.easSessionMinutes * 6e4;
|
|
636
|
+
if (driven) continue;
|
|
637
|
+
if (due > now) wakeAt = Math.min(wakeAt ?? due, due);
|
|
638
|
+
if (!(due <= now)) continue;
|
|
632
639
|
items.push({
|
|
633
640
|
id: `eas-${session.sessionId}:${env.path}`,
|
|
634
641
|
category: "attention",
|
|
@@ -659,7 +666,10 @@ function workspaceItems(env, input, format = attentionBody) {
|
|
|
659
666
|
remedy: null
|
|
660
667
|
});
|
|
661
668
|
}
|
|
662
|
-
return
|
|
669
|
+
return {
|
|
670
|
+
items,
|
|
671
|
+
wakeAt
|
|
672
|
+
};
|
|
663
673
|
}
|
|
664
674
|
const severityRank = (item) => item.severity === "error" ? 0 : 1;
|
|
665
675
|
/**
|
|
@@ -683,7 +693,7 @@ function needsAttention(input, format = attentionBody) {
|
|
|
683
693
|
remedy: null
|
|
684
694
|
}
|
|
685
695
|
});
|
|
686
|
-
for (const env of input.environments) for (const item of
|
|
696
|
+
for (const env of input.environments) for (const item of workspaceAttention(env, input, format).items) ranked.push({
|
|
687
697
|
item,
|
|
688
698
|
scope: env.live ? 1 : 2
|
|
689
699
|
});
|
|
@@ -659,4 +659,4 @@ function writeChild(handle, body) {
|
|
|
659
659
|
}
|
|
660
660
|
}
|
|
661
661
|
//#endregion
|
|
662
|
-
export { quotedPath as S, releaseClaim as _, claimFailure as a, sharedClaimDir as b, clearClaimChild as c, inspectClaimSet as d, isClaimRefusal as f, readClaimSet as g, processGroupAlive as h, ClaimUnavailableError as i, clearFreeClaimSet as l, markClaimChildPending as m, CLAIM_UNAVAILABLE as n, claimLiveness as o, isClaimUnavailable as p, ClaimRefusedError as r, claimRemoveCommand as s, CLAIM_REFUSED as t, exclusiveClaimDir as u, setClaimChild as v,
|
|
662
|
+
export { quotedPath as C, tryAcquireClaim as S, releaseClaim as _, claimFailure as a, sharedClaimDir as b, clearClaimChild as c, inspectClaimSet as d, isClaimRefusal as f, readClaimSet as g, processGroupAlive as h, ClaimUnavailableError as i, clearFreeClaimSet as l, markClaimChildPending as m, CLAIM_UNAVAILABLE as n, claimLiveness as o, isClaimUnavailable as p, ClaimRefusedError as r, claimRemoveCommand as s, CLAIM_REFUSED as t, exclusiveClaimDir as u, setClaimChild as v, sleepSync as x, settleClaim as y };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as ProcessRecord } from "./process-identity-
|
|
1
|
+
import { r as ProcessRecord } from "./process-identity-DVNGpF5c.mjs";
|
|
2
2
|
//#region ownership-claim.d.ts
|
|
3
3
|
type ClaimMode = "exclusive" | "shared";
|
|
4
4
|
interface ClaimDetails {
|
|
@@ -96,6 +96,7 @@ interface ClaimFailure {
|
|
|
96
96
|
declare function claimFailure(err: unknown, retryCommand: string | null): ClaimFailure | null;
|
|
97
97
|
declare function exclusiveClaimDir(root: string): string;
|
|
98
98
|
declare function sharedClaimDir(root: string): string;
|
|
99
|
+
declare function sleepSync(ms: number): void;
|
|
99
100
|
/**
|
|
100
101
|
* Whether any member of the process group `pid` leads is alive. A recorded child is resolved through
|
|
101
102
|
* its group rather than through the one process, so a package manager's postinstall descendant keeps
|
|
@@ -149,4 +150,4 @@ declare function markClaimChildPending(handle: ClaimHandle): void;
|
|
|
149
150
|
declare function setClaimChild(handle: ClaimHandle, record: ProcessRecord): void;
|
|
150
151
|
declare function clearClaimChild(handle: ClaimHandle): void;
|
|
151
152
|
//#endregion
|
|
152
|
-
export { setClaimChild as A, inspectClaimSet as C, processGroupAlive as D, markClaimChildPending as E, sharedClaimDir as M,
|
|
153
|
+
export { setClaimChild as A, inspectClaimSet as C, processGroupAlive as D, markClaimChildPending as E, sharedClaimDir as M, sleepSync as N, readClaimSet as O, tryAcquireClaim as P, exclusiveClaimDir as S, isClaimUnavailable as T, claimFailure as _, ClaimFailure as a, clearClaimChild as b, ClaimMode as c, ClaimProblem as d, ClaimRefusedError as f, ClaimUnavailableError as g, ClaimSurvey as h, ClaimDetails as i, settleClaim as j, releaseClaim as k, ClaimOptions as l, ClaimSetState as m, CLAIM_UNAVAILABLE as n, ClaimHandle as o, ClaimSetClearance as p, ClaimAttempt as r, ClaimHolder as s, CLAIM_REFUSED as t, ClaimOwner as u, claimLiveness as v, isClaimRefusal as w, clearFreeClaimSet as x, claimRemoveCommand as y };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as setClaimChild, C as inspectClaimSet, D as processGroupAlive, E as markClaimChildPending, M as sharedClaimDir, N as
|
|
2
|
-
export { CLAIM_REFUSED, CLAIM_UNAVAILABLE, ClaimAttempt, ClaimDetails, ClaimFailure, ClaimHandle, ClaimHolder, ClaimMode, ClaimOptions, ClaimOwner, ClaimProblem, ClaimRefusedError, ClaimSetClearance, ClaimSetState, ClaimSurvey, ClaimUnavailableError, claimFailure, claimLiveness, claimRemoveCommand, clearClaimChild, clearFreeClaimSet, exclusiveClaimDir, inspectClaimSet, isClaimRefusal, isClaimUnavailable, markClaimChildPending, processGroupAlive, readClaimSet, releaseClaim, setClaimChild, settleClaim, sharedClaimDir, tryAcquireClaim };
|
|
1
|
+
import { A as setClaimChild, C as inspectClaimSet, D as processGroupAlive, E as markClaimChildPending, M as sharedClaimDir, N as sleepSync, O as readClaimSet, P as tryAcquireClaim, S as exclusiveClaimDir, T as isClaimUnavailable, _ as claimFailure, a as ClaimFailure, b as clearClaimChild, c as ClaimMode, d as ClaimProblem, f as ClaimRefusedError, g as ClaimUnavailableError, h as ClaimSurvey, i as ClaimDetails, j as settleClaim, k as releaseClaim, l as ClaimOptions, m as ClaimSetState, n as CLAIM_UNAVAILABLE, o as ClaimHandle, p as ClaimSetClearance, r as ClaimAttempt, s as ClaimHolder, t as CLAIM_REFUSED, u as ClaimOwner, v as claimLiveness, w as isClaimRefusal, x as clearFreeClaimSet, y as claimRemoveCommand } from "./ownership-claim-RY3fbm1D.mjs";
|
|
2
|
+
export { CLAIM_REFUSED, CLAIM_UNAVAILABLE, ClaimAttempt, ClaimDetails, ClaimFailure, ClaimHandle, ClaimHolder, ClaimMode, ClaimOptions, ClaimOwner, ClaimProblem, ClaimRefusedError, ClaimSetClearance, ClaimSetState, ClaimSurvey, ClaimUnavailableError, claimFailure, claimLiveness, claimRemoveCommand, clearClaimChild, clearFreeClaimSet, exclusiveClaimDir, inspectClaimSet, isClaimRefusal, isClaimUnavailable, markClaimChildPending, processGroupAlive, readClaimSet, releaseClaim, setClaimChild, settleClaim, sharedClaimDir, sleepSync, tryAcquireClaim };
|
package/dist/ownership-claim.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as releaseClaim, a as claimFailure, b as sharedClaimDir, c as clearClaimChild, d as inspectClaimSet, f as isClaimRefusal, g as readClaimSet, h as processGroupAlive, i as ClaimUnavailableError, l as clearFreeClaimSet, m as markClaimChildPending, n as CLAIM_UNAVAILABLE, o as claimLiveness, p as isClaimUnavailable, r as ClaimRefusedError, s as claimRemoveCommand, t as CLAIM_REFUSED, u as exclusiveClaimDir, v as setClaimChild, x as
|
|
2
|
-
export { CLAIM_REFUSED, CLAIM_UNAVAILABLE, ClaimRefusedError, ClaimUnavailableError, claimFailure, claimLiveness, claimRemoveCommand, clearClaimChild, clearFreeClaimSet, exclusiveClaimDir, inspectClaimSet, isClaimRefusal, isClaimUnavailable, markClaimChildPending, processGroupAlive, readClaimSet, releaseClaim, setClaimChild, settleClaim, sharedClaimDir, tryAcquireClaim };
|
|
1
|
+
import { S as tryAcquireClaim, _ as releaseClaim, a as claimFailure, b as sharedClaimDir, c as clearClaimChild, d as inspectClaimSet, f as isClaimRefusal, g as readClaimSet, h as processGroupAlive, i as ClaimUnavailableError, l as clearFreeClaimSet, m as markClaimChildPending, n as CLAIM_UNAVAILABLE, o as claimLiveness, p as isClaimUnavailable, r as ClaimRefusedError, s as claimRemoveCommand, t as CLAIM_REFUSED, u as exclusiveClaimDir, v as setClaimChild, x as sleepSync, y as settleClaim } from "./ownership-claim-DHUs_Dir.mjs";
|
|
2
|
+
export { CLAIM_REFUSED, CLAIM_UNAVAILABLE, ClaimRefusedError, ClaimUnavailableError, claimFailure, claimLiveness, claimRemoveCommand, clearClaimChild, clearFreeClaimSet, exclusiveClaimDir, inspectClaimSet, isClaimRefusal, isClaimUnavailable, markClaimChildPending, processGroupAlive, readClaimSet, releaseClaim, setClaimChild, settleClaim, sharedClaimDir, sleepSync, tryAcquireClaim };
|