@zixt/host 0.0.73 → 0.0.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1153 -1099
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,27 +11,27 @@ var __export = (target, all) => {
|
|
|
11
11
|
import { fstatSync as fstatSync2 } from "node:fs";
|
|
12
12
|
|
|
13
13
|
// src/supervisor.ts
|
|
14
|
-
import { spawn as
|
|
14
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
15
15
|
import { fstatSync } from "node:fs";
|
|
16
16
|
import {
|
|
17
17
|
access as access2,
|
|
18
|
-
lstat as
|
|
19
|
-
mkdir as
|
|
20
|
-
open as
|
|
21
|
-
readFile as
|
|
18
|
+
lstat as lstat5,
|
|
19
|
+
mkdir as mkdir4,
|
|
20
|
+
open as open4,
|
|
21
|
+
readFile as readFile6,
|
|
22
22
|
readlink,
|
|
23
|
-
readdir as
|
|
24
|
-
rename as
|
|
25
|
-
rm as
|
|
23
|
+
readdir as readdir3,
|
|
24
|
+
rename as rename3,
|
|
25
|
+
rm as rm4,
|
|
26
26
|
symlink
|
|
27
27
|
} from "node:fs/promises";
|
|
28
|
-
import { basename as basename3, dirname as
|
|
29
|
-
import { homedir } from "node:os";
|
|
28
|
+
import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute8, join as join7, relative as relative4, resolve as resolve5, sep as sep4 } from "node:path";
|
|
29
|
+
import { homedir as homedir2 } from "node:os";
|
|
30
30
|
|
|
31
31
|
// package.json
|
|
32
32
|
var package_default = {
|
|
33
33
|
name: "@zixt/host",
|
|
34
|
-
version: "0.0.
|
|
34
|
+
version: "0.0.75",
|
|
35
35
|
type: "module",
|
|
36
36
|
exports: {
|
|
37
37
|
".": "./src/client.ts",
|
|
@@ -14665,6 +14665,12 @@ var ID_PREFIXES = {
|
|
|
14665
14665
|
managerUsage: "mgu",
|
|
14666
14666
|
/** One ordered Manager reply awaiting Slack delivery. */
|
|
14667
14667
|
managerSlackOutbox: "mso",
|
|
14668
|
+
/** One organization-owned virtual Manager email inbox. */
|
|
14669
|
+
managerEmailInbox: "mei",
|
|
14670
|
+
/** One admitted or quarantined inbound Manager email. */
|
|
14671
|
+
managerEmailEvent: "mee",
|
|
14672
|
+
/** One idempotent outbound Manager email. */
|
|
14673
|
+
managerEmailDelivery: "med",
|
|
14668
14674
|
/** One organization phone line that reaches its Manager. */
|
|
14669
14675
|
voiceLine: "vln",
|
|
14670
14676
|
/** One member-owned phone number admitted to Manager calls. */
|
|
@@ -14706,6 +14712,18 @@ var ManagerIntegrationActionId = idSchema(
|
|
|
14706
14712
|
ID_PREFIXES.managerIntegrationAction,
|
|
14707
14713
|
"Manager integration action id"
|
|
14708
14714
|
);
|
|
14715
|
+
var ManagerEmailInboxId = idSchema(
|
|
14716
|
+
ID_PREFIXES.managerEmailInbox,
|
|
14717
|
+
"Manager email inbox id"
|
|
14718
|
+
);
|
|
14719
|
+
var ManagerEmailEventId = idSchema(
|
|
14720
|
+
ID_PREFIXES.managerEmailEvent,
|
|
14721
|
+
"Manager email event id"
|
|
14722
|
+
);
|
|
14723
|
+
var ManagerEmailDeliveryId = idSchema(
|
|
14724
|
+
ID_PREFIXES.managerEmailDelivery,
|
|
14725
|
+
"Manager email delivery id"
|
|
14726
|
+
);
|
|
14709
14727
|
var VoiceLineId = idSchema(ID_PREFIXES.voiceLine, "voice line id");
|
|
14710
14728
|
var VoiceCallerId = idSchema(ID_PREFIXES.voiceCaller, "voice caller id");
|
|
14711
14729
|
var VoiceCallId = idSchema(ID_PREFIXES.voiceCall, "voice call id");
|
|
@@ -18232,7 +18250,33 @@ var ListManagerMemoriesResponse = external_exports.object({
|
|
|
18232
18250
|
memories: external_exports.array(ManagerMemoryProjection).max(MANAGER_MEMORY_MAX_ITEMS),
|
|
18233
18251
|
limit: external_exports.number().int().min(1)
|
|
18234
18252
|
}).strict();
|
|
18235
|
-
var ConversationChannel = external_exports.enum(["web", "slack", "whatsapp", "phone"]);
|
|
18253
|
+
var ConversationChannel = external_exports.enum(["web", "slack", "whatsapp", "phone", "email"]);
|
|
18254
|
+
var MANAGER_EMAIL_ALLOWLIST_MAX = 200;
|
|
18255
|
+
var MANAGER_EMAIL_QUARANTINE_LIMIT = 25;
|
|
18256
|
+
var ManagerEmailAddress = external_exports.string().email().max(320);
|
|
18257
|
+
var ManagerEmailDomain = external_exports.string().min(3).max(253).regex(/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i);
|
|
18258
|
+
var ManagerEmailQuarantineProjection = external_exports.object({
|
|
18259
|
+
id: external_exports.string().min(1),
|
|
18260
|
+
sender: ManagerEmailAddress,
|
|
18261
|
+
subject: external_exports.string().max(300),
|
|
18262
|
+
reason: external_exports.enum(["authentication_failed", "not_approved", "automatic_message"]),
|
|
18263
|
+
receivedAt: external_exports.string()
|
|
18264
|
+
}).strict();
|
|
18265
|
+
var ManagerEmailSettingsResponse = external_exports.object({
|
|
18266
|
+
address: ManagerEmailAddress,
|
|
18267
|
+
status: external_exports.enum(["ready", "not_configured"]),
|
|
18268
|
+
memberAddressesApprovedAutomatically: external_exports.literal(true),
|
|
18269
|
+
allowedAddresses: external_exports.array(ManagerEmailAddress).max(MANAGER_EMAIL_ALLOWLIST_MAX),
|
|
18270
|
+
allowedDomains: external_exports.array(ManagerEmailDomain).max(MANAGER_EMAIL_ALLOWLIST_MAX),
|
|
18271
|
+
quarantine: external_exports.array(ManagerEmailQuarantineProjection).max(MANAGER_EMAIL_QUARANTINE_LIMIT),
|
|
18272
|
+
revision: external_exports.number().int().min(0)
|
|
18273
|
+
}).strict();
|
|
18274
|
+
var UpdateManagerEmailSettingsRequest = external_exports.object({
|
|
18275
|
+
allowedAddresses: external_exports.array(ManagerEmailAddress).max(MANAGER_EMAIL_ALLOWLIST_MAX),
|
|
18276
|
+
allowedDomains: external_exports.array(ManagerEmailDomain).max(MANAGER_EMAIL_ALLOWLIST_MAX),
|
|
18277
|
+
expectedRevision: external_exports.number().int().min(0)
|
|
18278
|
+
}).strict();
|
|
18279
|
+
var DeleteManagerEmailQuarantineResponse = external_exports.object({ discarded: external_exports.literal(true) }).strict();
|
|
18236
18280
|
var ConversationStatus = external_exports.enum(["idle", "thinking"]);
|
|
18237
18281
|
var ConversationProjection = external_exports.object({
|
|
18238
18282
|
id: ConversationId,
|
|
@@ -21619,7 +21663,6 @@ var HostClient = class _HostClient {
|
|
|
21619
21663
|
this.runnerRuntimeFeatureActive = false;
|
|
21620
21664
|
this.hostConsoleFeatureActive = false;
|
|
21621
21665
|
this.opts.browser?.attach(null);
|
|
21622
|
-
void this.opts.browser?.closeAll("machine_offline");
|
|
21623
21666
|
if (code === CLOSE_CODES.revoked) {
|
|
21624
21667
|
this.opts.onStatus?.("revoked");
|
|
21625
21668
|
this.stopped = true;
|
|
@@ -23994,6 +24037,23 @@ function createReleaseStateStore(root) {
|
|
|
23994
24037
|
};
|
|
23995
24038
|
}
|
|
23996
24039
|
|
|
24040
|
+
// src/runners/run-artifacts.ts
|
|
24041
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
24042
|
+
import {
|
|
24043
|
+
chmod as chmod2,
|
|
24044
|
+
lstat as lstat4,
|
|
24045
|
+
mkdir as mkdir3,
|
|
24046
|
+
open as open3,
|
|
24047
|
+
readdir as readdir2,
|
|
24048
|
+
readFile as readFile5,
|
|
24049
|
+
realpath as realpath3,
|
|
24050
|
+
rename as rename2,
|
|
24051
|
+
rm as rm3,
|
|
24052
|
+
writeFile
|
|
24053
|
+
} from "node:fs/promises";
|
|
24054
|
+
import { homedir } from "node:os";
|
|
24055
|
+
import { dirname as dirname3, isAbsolute as isAbsolute7, join as join6, relative as relative3, resolve as resolve4, sep as sep3, win32 as win322 } from "node:path";
|
|
24056
|
+
|
|
23997
24057
|
// src/windows-job.ts
|
|
23998
24058
|
import { spawn as spawn3 } from "node:child_process";
|
|
23999
24059
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -24514,257 +24574,908 @@ async function awaitWindowsContainmentGate(env = process.env, input = process.st
|
|
|
24514
24574
|
});
|
|
24515
24575
|
}
|
|
24516
24576
|
|
|
24517
|
-
// src/
|
|
24518
|
-
var
|
|
24519
|
-
var
|
|
24520
|
-
var
|
|
24521
|
-
var WORKER_PROXY_ROLE = "worker_proxy";
|
|
24522
|
-
var SUPERVISOR_ROLE = "supervisor";
|
|
24523
|
-
var WORKER_PROXY_ENTRY_ENV = "ZIXT_HOST_WORKER_PROXY_ENTRY";
|
|
24524
|
-
var SUPERVISOR_PROTOCOL_ENV = "ZIXT_HOST_SUPERVISOR_PROTOCOL";
|
|
24525
|
-
var SUPERVISOR_VERSION_ENV = "ZIXT_HOST_SUPERVISOR_VERSION";
|
|
24526
|
-
var SUPERVISOR_PROTOCOL = "2";
|
|
24527
|
-
var SUPERVISOR_RELOAD_EXIT_CODE = 76;
|
|
24528
|
-
var DO_NOT_RESTART_EXIT_CODE = 64;
|
|
24529
|
-
var RESTART_BACKOFF_MS = 2e3;
|
|
24530
|
-
var MAX_RESTART_BACKOFF_MS = 6e4;
|
|
24531
|
-
var CRASH_STREAK_RESET_MS = 6e4;
|
|
24532
|
-
var RELEASE_PROBATION_MS = 5 * 6e4;
|
|
24533
|
-
var FAST_UPDATE_EXIT_MS = 1e4;
|
|
24534
|
-
var STOP_GRACE_MS = 3e4;
|
|
24535
|
-
var INSTALL_TIMEOUT_MS = 5 * 6e4;
|
|
24536
|
-
var WORKER_WATCHDOG_CHECK_MS = 1e3;
|
|
24537
|
-
var WORKER_WATCHDOG_TIMEOUT_MS = 15e3;
|
|
24538
|
-
var WORKER_WATCHDOG_STARTUP_MS = 6e4;
|
|
24539
|
-
var WORKER_READINESS_TIMEOUT_MS = 9e4;
|
|
24577
|
+
// src/runners/run-artifacts.ts
|
|
24578
|
+
var SAFE_SEGMENT = /^[A-Za-z0-9_-]{1,200}$/;
|
|
24579
|
+
var DIRECTORY_MODE = 448;
|
|
24580
|
+
var FILE_MODE = 384;
|
|
24540
24581
|
var CLEANUP_ATTEMPTS = 3;
|
|
24541
|
-
var
|
|
24542
|
-
var
|
|
24543
|
-
var
|
|
24544
|
-
|
|
24545
|
-
|
|
24546
|
-
|
|
24547
|
-
|
|
24582
|
+
var CLEANUP_RETRY_DELAY_MS = 50;
|
|
24583
|
+
var WINDOWS_ACL_TIMEOUT_MS = 5e3;
|
|
24584
|
+
var RUNNER_GUARDIAN = String.raw`
|
|
24585
|
+
const { spawn } = require('node:child_process');
|
|
24586
|
+
|
|
24587
|
+
const quote = (value) => '"' + String(value).replace(/\\/g, '/').replaceAll('"', '""') + '"';
|
|
24588
|
+
const launchMarker = '__ZIXT_RELEASE_RUNNER__';
|
|
24589
|
+
const exitMarker = process.argv[2];
|
|
24590
|
+
const guardianNonce = process.argv[3];
|
|
24591
|
+
if (!/^[A-Za-z0-9_-]{16,200}$/.test(guardianNonce || '')) process.exit(1);
|
|
24592
|
+
const containmentNonce = process.env[${JSON.stringify(WINDOWS_CONTAINMENT_GATE_ENV)}];
|
|
24593
|
+
const containmentMarker = ${JSON.stringify(WINDOWS_CONTAINMENT_GATE_PREFIX)};
|
|
24594
|
+
let containmentReady = !containmentNonce;
|
|
24595
|
+
const maxReleaseBytes = 32 * 1024 * 1024;
|
|
24596
|
+
let target = null;
|
|
24597
|
+
let pending = Buffer.alloc(0);
|
|
24598
|
+
let stdinEnded = false;
|
|
24599
|
+
let expectedBytes = null;
|
|
24600
|
+
let config = null;
|
|
24601
|
+
let launchPending = false;
|
|
24602
|
+
let stderrTail = '';
|
|
24603
|
+
let postReleaseInput = Buffer.alloc(0);
|
|
24604
|
+
let reported = false;
|
|
24605
|
+
const report = (result) => {
|
|
24606
|
+
if (reported) return;
|
|
24607
|
+
reported = true;
|
|
24608
|
+
process.stdout.write(
|
|
24609
|
+
'\n' + exitMarker + JSON.stringify({ ...result, stderrTail }) + '\n'
|
|
24610
|
+
);
|
|
24611
|
+
setInterval(() => {}, 2_147_483_647);
|
|
24612
|
+
};
|
|
24613
|
+
|
|
24614
|
+
// Job helper setup + assignment each have a bounded Windows phase. This gate
|
|
24615
|
+
// must outlive both, including slow first-run Add-Type under endpoint protection.
|
|
24616
|
+
const prelaunchTimeout = setTimeout(() => process.exit(1), 130_000);
|
|
24617
|
+
prelaunchTimeout.unref();
|
|
24618
|
+
|
|
24619
|
+
const launchTarget = (release) => {
|
|
24548
24620
|
try {
|
|
24549
|
-
|
|
24550
|
-
|
|
24621
|
+
const command = config.comspec || config.command;
|
|
24622
|
+
const args = config.comspec
|
|
24623
|
+
? ['/d', '/s', '/c', '"' + [config.command, ...config.args].map(quote).join(' ') + '"']
|
|
24624
|
+
: config.args;
|
|
24625
|
+
target = spawn(command, args, {
|
|
24626
|
+
cwd: config.cwd,
|
|
24627
|
+
env: config.env,
|
|
24628
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
24629
|
+
windowsHide: true,
|
|
24630
|
+
windowsVerbatimArguments: Boolean(config.comspec),
|
|
24631
|
+
});
|
|
24632
|
+
launchPending = false;
|
|
24551
24633
|
} catch {
|
|
24634
|
+
process.exit(1);
|
|
24635
|
+
return;
|
|
24552
24636
|
}
|
|
24553
|
-
|
|
24554
|
-
|
|
24555
|
-
|
|
24556
|
-
|
|
24557
|
-
|
|
24558
|
-
|
|
24559
|
-
}
|
|
24560
|
-
|
|
24561
|
-
|
|
24562
|
-
|
|
24563
|
-
|
|
24564
|
-
|
|
24565
|
-
|
|
24566
|
-
|
|
24567
|
-
|
|
24568
|
-
}
|
|
24569
|
-
|
|
24570
|
-
|
|
24571
|
-
|
|
24572
|
-
|
|
24637
|
+
|
|
24638
|
+
target.stdin.on('error', () => {});
|
|
24639
|
+
target.stdout.pipe(process.stdout, { end: false });
|
|
24640
|
+
target.stderr.on('data', (chunk) => {
|
|
24641
|
+
process.stderr.write(chunk);
|
|
24642
|
+
stderrTail = (stderrTail + String(chunk)).slice(-32_768);
|
|
24643
|
+
});
|
|
24644
|
+
let targetResult;
|
|
24645
|
+
let stdoutEnded = false;
|
|
24646
|
+
const maybeReport = () => {
|
|
24647
|
+
if (targetResult && stdoutEnded) report(targetResult);
|
|
24648
|
+
};
|
|
24649
|
+
target.stdout.once('end', () => {
|
|
24650
|
+
stdoutEnded = true;
|
|
24651
|
+
maybeReport();
|
|
24652
|
+
});
|
|
24653
|
+
target.stdout.once('close', () => {
|
|
24654
|
+
stdoutEnded = true;
|
|
24655
|
+
maybeReport();
|
|
24656
|
+
});
|
|
24657
|
+
target.once('error', () => {
|
|
24658
|
+
targetResult = { kind: 'spawn_error' };
|
|
24659
|
+
stdoutEnded = true;
|
|
24660
|
+
maybeReport();
|
|
24661
|
+
});
|
|
24662
|
+
target.once('close', (code, signal) => {
|
|
24663
|
+
targetResult = { kind: 'close', code, signal };
|
|
24664
|
+
maybeReport();
|
|
24665
|
+
});
|
|
24666
|
+
if (config.prompt) target.stdin.write(config.prompt);
|
|
24667
|
+
if (postReleaseInput.length) {
|
|
24668
|
+
target.stdin.write(postReleaseInput);
|
|
24669
|
+
postReleaseInput = Buffer.alloc(0);
|
|
24670
|
+
}
|
|
24671
|
+
if (stdinEnded) target.stdin.end();
|
|
24672
|
+
};
|
|
24673
|
+
|
|
24674
|
+
const launch = (release) => {
|
|
24675
|
+
clearTimeout(prelaunchTimeout);
|
|
24676
|
+
config = release;
|
|
24677
|
+
launchPending = true;
|
|
24678
|
+
if (process.platform === 'win32') {
|
|
24679
|
+
launchTarget(release);
|
|
24680
|
+
return;
|
|
24681
|
+
}
|
|
24682
|
+
|
|
24683
|
+
// A detached POSIX group's numeric id outlives its leader, but without a
|
|
24684
|
+
// live identity witness a stale registry entry cannot distinguish that
|
|
24685
|
+
// inherited group from the same PGID being recycled later. This
|
|
24686
|
+
// credential-free sentinel inherits the guardian's exact group and carries
|
|
24687
|
+
// only its high-entropy nonce in argv. It deliberately survives SIGTERM so
|
|
24688
|
+
// checked recovery can keep the PGID reserved through the graceful pass and
|
|
24689
|
+
// validate the nonce again immediately before a group-wide SIGKILL.
|
|
24690
|
+
const identitySentinel = spawn(
|
|
24691
|
+
process.execPath,
|
|
24692
|
+
[
|
|
24693
|
+
'-e',
|
|
24694
|
+
"process.on('SIGTERM', () => {}); setInterval(() => {}, 2147483647);",
|
|
24695
|
+
guardianNonce,
|
|
24696
|
+
],
|
|
24697
|
+
{ env: process.env, stdio: 'ignore' },
|
|
24698
|
+
);
|
|
24699
|
+
identitySentinel.once('error', () => process.exit(1));
|
|
24700
|
+
identitySentinel.once('spawn', () => launchTarget(release));
|
|
24701
|
+
identitySentinel.once('exit', () => {
|
|
24702
|
+
if (!reported) process.exit(1);
|
|
24703
|
+
});
|
|
24704
|
+
};
|
|
24705
|
+
|
|
24706
|
+
process.stdin.on('data', (chunk) => {
|
|
24707
|
+
if (target) {
|
|
24708
|
+
target.stdin.write(chunk);
|
|
24709
|
+
return;
|
|
24710
|
+
}
|
|
24711
|
+
if (config) {
|
|
24712
|
+
postReleaseInput = Buffer.concat([postReleaseInput, chunk]);
|
|
24713
|
+
if (postReleaseInput.length > maxReleaseBytes) process.exit(1);
|
|
24714
|
+
return;
|
|
24715
|
+
}
|
|
24716
|
+
pending = Buffer.concat([pending, chunk]);
|
|
24717
|
+
if (pending.length > maxReleaseBytes + 256) process.exit(1);
|
|
24718
|
+
if (!containmentReady) {
|
|
24719
|
+
const gateNewline = pending.indexOf(10);
|
|
24720
|
+
if (gateNewline < 0) return;
|
|
24721
|
+
const gate = pending.subarray(0, gateNewline).toString().replace(/\r$/, '');
|
|
24722
|
+
if (gate !== containmentMarker + ' ' + containmentNonce) process.exit(1);
|
|
24723
|
+
containmentReady = true;
|
|
24724
|
+
pending = pending.subarray(gateNewline + 1);
|
|
24725
|
+
}
|
|
24726
|
+
if (expectedBytes === null) {
|
|
24727
|
+
const newline = pending.indexOf(10);
|
|
24728
|
+
if (newline < 0) return;
|
|
24729
|
+
const header = pending.subarray(0, newline).toString();
|
|
24730
|
+
const match = new RegExp('^' + launchMarker + ' ([1-9][0-9]{0,8})$').exec(header);
|
|
24731
|
+
if (!match) process.exit(1);
|
|
24732
|
+
expectedBytes = Number(match[1]);
|
|
24733
|
+
if (expectedBytes > maxReleaseBytes) process.exit(1);
|
|
24734
|
+
pending = pending.subarray(newline + 1);
|
|
24735
|
+
}
|
|
24736
|
+
if (pending.length < expectedBytes) return;
|
|
24573
24737
|
try {
|
|
24574
|
-
const
|
|
24575
|
-
|
|
24576
|
-
|
|
24577
|
-
|
|
24578
|
-
if (!entry.isFile()) return false;
|
|
24579
|
-
const manifest = JSON.parse(manifestText);
|
|
24580
|
-
return manifest.name === PACKAGE_NAME && manifest.version === version2;
|
|
24738
|
+
const release = JSON.parse(pending.subarray(0, expectedBytes).toString('utf8'));
|
|
24739
|
+
postReleaseInput = pending.subarray(expectedBytes);
|
|
24740
|
+
pending = Buffer.alloc(0);
|
|
24741
|
+
launch(release);
|
|
24581
24742
|
} catch {
|
|
24582
|
-
|
|
24743
|
+
process.exit(1);
|
|
24583
24744
|
}
|
|
24584
|
-
}
|
|
24585
|
-
|
|
24586
|
-
|
|
24587
|
-
|
|
24588
|
-
|
|
24589
|
-
|
|
24590
|
-
|
|
24591
|
-
|
|
24745
|
+
});
|
|
24746
|
+
process.stdin.on('end', () => {
|
|
24747
|
+
stdinEnded = true;
|
|
24748
|
+
if (!containmentReady) process.exit(1);
|
|
24749
|
+
if (target) target.stdin.end();
|
|
24750
|
+
else if (!launchPending) process.exit(1);
|
|
24751
|
+
});
|
|
24752
|
+
process.stdin.on('error', () => process.exit(1));
|
|
24753
|
+
`;
|
|
24754
|
+
var WINDOWS_PRIVATE_DACL_SCRIPT = String.raw`
|
|
24755
|
+
$ErrorActionPreference = 'Stop'
|
|
24756
|
+
$paths = ConvertFrom-Json $env:ZIXT_RUN_ARTIFACT_PATHS
|
|
24757
|
+
$current = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
24758
|
+
$system = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-18')
|
|
24759
|
+
$admins = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-32-544')
|
|
24760
|
+
$allowed = @($current.Value, $system.Value, $admins.Value)
|
|
24761
|
+
foreach ($path in $paths) {
|
|
24762
|
+
$acl = [System.Security.AccessControl.DirectorySecurity]::new()
|
|
24763
|
+
$acl.SetAccessRuleProtection($true, $false)
|
|
24764
|
+
foreach ($sid in @($current, $system, $admins)) {
|
|
24765
|
+
$rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
|
|
24766
|
+
$sid,
|
|
24767
|
+
[System.Security.AccessControl.FileSystemRights]::FullControl,
|
|
24768
|
+
[System.Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit',
|
|
24769
|
+
[System.Security.AccessControl.PropagationFlags]::None,
|
|
24770
|
+
[System.Security.AccessControl.AccessControlType]::Allow
|
|
24771
|
+
)
|
|
24772
|
+
[void]$acl.AddAccessRule($rule)
|
|
24773
|
+
}
|
|
24774
|
+
$entry = [System.IO.DirectoryInfo]::new($path)
|
|
24775
|
+
$entry.SetAccessControl($acl)
|
|
24776
|
+
$check = $entry.GetAccessControl()
|
|
24777
|
+
if (-not $check.AreAccessRulesProtected) { throw 'DACL inheritance remained enabled' }
|
|
24778
|
+
$seen = @{}
|
|
24779
|
+
foreach ($rule in $check.Access) {
|
|
24780
|
+
$sid = $rule.IdentityReference.Translate(
|
|
24781
|
+
[System.Security.Principal.SecurityIdentifier]
|
|
24782
|
+
).Value
|
|
24783
|
+
if ($rule.AccessControlType -ne 'Allow' -or $allowed -notcontains $sid) {
|
|
24784
|
+
throw 'Unexpected DACL entry'
|
|
24785
|
+
}
|
|
24786
|
+
$seen[$sid] = $true
|
|
24787
|
+
}
|
|
24788
|
+
foreach ($sid in $allowed) {
|
|
24789
|
+
if (-not $seen.ContainsKey($sid)) { throw 'Required DACL entry is missing' }
|
|
24592
24790
|
}
|
|
24593
24791
|
}
|
|
24594
|
-
|
|
24595
|
-
|
|
24596
|
-
|
|
24597
|
-
|
|
24598
|
-
|
|
24792
|
+
`;
|
|
24793
|
+
function defaultRunArtifactRoot() {
|
|
24794
|
+
return join6(homedir(), ".zixt", "run-artifacts");
|
|
24795
|
+
}
|
|
24796
|
+
function requireSafeSegment(value, field) {
|
|
24797
|
+
if (!SAFE_SEGMENT.test(value)) {
|
|
24798
|
+
throw new Error(`${field} must be a safe path segment`);
|
|
24599
24799
|
}
|
|
24600
|
-
const version2 = relativeEntry.split(sep3)[0];
|
|
24601
|
-
if (!version2 || !VERSION_DIR.test(version2)) return null;
|
|
24602
|
-
return resolve4(entry) === resolve4(installedReleaseEntry(version2, root)) ? version2 : null;
|
|
24603
24800
|
}
|
|
24604
|
-
function
|
|
24605
|
-
return
|
|
24801
|
+
function isMissing(error52) {
|
|
24802
|
+
return error52.code === "ENOENT";
|
|
24606
24803
|
}
|
|
24607
|
-
function
|
|
24608
|
-
|
|
24804
|
+
function assertBelow(parent, child) {
|
|
24805
|
+
const path = relative3(parent, child);
|
|
24806
|
+
const escapes = path === "" || path === ".." || path.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute7(path);
|
|
24807
|
+
if (escapes) throw new Error("run artifact path escapes its private root");
|
|
24609
24808
|
}
|
|
24610
|
-
|
|
24611
|
-
|
|
24612
|
-
|
|
24613
|
-
|
|
24614
|
-
|
|
24615
|
-
const pointer = JSON.parse(readFileSync(join(dirname(__filename), '${WINDOWS_RELEASE_POINTER}'), 'utf8'));
|
|
24616
|
-
if (pointer.schema !== 1 || typeof pointer.entry !== 'string' || !isAbsolute(pointer.entry)) process.exit(64);
|
|
24617
|
-
const child = spawn(process.execPath, [pointer.entry, ...process.argv.slice(2)], {
|
|
24618
|
-
// This wrapper owns the only write end of the installed launcher's stdin.
|
|
24619
|
-
// If Task Scheduler/the wrapper dies, EOF reaches launchHostSupervisor and
|
|
24620
|
-
// triggers checked supervisor/worker cleanup before a scheduled restart.
|
|
24621
|
-
stdio: ['pipe', 'inherit', 'inherit'],
|
|
24622
|
-
windowsHide: true,
|
|
24623
|
-
});
|
|
24624
|
-
child.stdin.on('error', () => {});
|
|
24625
|
-
let stopping = false;
|
|
24626
|
-
const closeChildInput = () => {
|
|
24627
|
-
if (stopping) return;
|
|
24628
|
-
stopping = true;
|
|
24629
|
-
try { child.stdin.end(); } catch {}
|
|
24630
|
-
};
|
|
24631
|
-
process.stdin.on('data', (chunk) => {
|
|
24632
|
-
if (!stopping && child.stdin.writable) child.stdin.write(chunk);
|
|
24633
|
-
});
|
|
24634
|
-
process.stdin.on('end', closeChildInput);
|
|
24635
|
-
process.stdin.on('error', closeChildInput);
|
|
24636
|
-
process.stdin.resume();
|
|
24637
|
-
process.on('SIGINT', closeChildInput);
|
|
24638
|
-
process.on('SIGTERM', closeChildInput);
|
|
24639
|
-
child.once('error', () => process.exit(1));
|
|
24640
|
-
child.once('exit', (code) => process.exit(code == null ? 1 : code));
|
|
24641
|
-
`;
|
|
24642
|
-
async function replaceDurableFile(path, contents, sync = syncDirectory3) {
|
|
24643
|
-
const parent = dirname3(path);
|
|
24644
|
-
await mkdir3(parent, { recursive: true, mode: 448 });
|
|
24645
|
-
const temporary = join6(parent, `.${CURRENT_RELEASE_ENTRY}.${process.pid}.${crypto.randomUUID()}`);
|
|
24646
|
-
const handle = await open3(temporary, "wx", 384);
|
|
24647
|
-
try {
|
|
24648
|
-
await handle.writeFile(contents, "utf8");
|
|
24649
|
-
await handle.sync();
|
|
24650
|
-
await handle.close();
|
|
24651
|
-
await rename2(temporary, path);
|
|
24652
|
-
await sync(parent);
|
|
24653
|
-
} catch (error52) {
|
|
24654
|
-
await handle.close().catch(() => void 0);
|
|
24655
|
-
await rm3(temporary, { force: true }).catch(() => void 0);
|
|
24656
|
-
throw error52;
|
|
24657
|
-
}
|
|
24809
|
+
async function requireRealDirectory(path, label) {
|
|
24810
|
+
const entry = await lstat4(path);
|
|
24811
|
+
if (entry.isSymbolicLink()) throw new Error(`${label} must not be a symbolic link`);
|
|
24812
|
+
if (!entry.isDirectory()) throw new Error(`${label} must be a directory`);
|
|
24813
|
+
return realpath3(path);
|
|
24658
24814
|
}
|
|
24659
|
-
|
|
24660
|
-
|
|
24661
|
-
|
|
24815
|
+
function assertWindowsProfileBoundary(profile, target) {
|
|
24816
|
+
const path = win322.relative(win322.resolve(profile), win322.resolve(target));
|
|
24817
|
+
if (path === "" || path === ".." || path.startsWith("..\\") || win322.isAbsolute(path)) {
|
|
24818
|
+
throw new Error("run artifact root must be inside the current Windows user profile");
|
|
24662
24819
|
}
|
|
24663
|
-
|
|
24664
|
-
|
|
24665
|
-
|
|
24666
|
-
|
|
24667
|
-
|
|
24668
|
-
|
|
24669
|
-
|
|
24670
|
-
|
|
24671
|
-
|
|
24672
|
-
|
|
24673
|
-
throw new Error("the Zixt Host Windows launcher is not a regular file");
|
|
24674
|
-
}
|
|
24675
|
-
const contents = await readFile5(launcher, "utf8");
|
|
24676
|
-
if (!contents.startsWith(WINDOWS_LAUNCHER_OWNED_MARKER)) {
|
|
24677
|
-
throw new Error("the Zixt Host Windows launcher is not owned by Zixt");
|
|
24678
|
-
}
|
|
24679
|
-
if (contents !== WINDOWS_STABLE_LAUNCHER) {
|
|
24680
|
-
await replaceDurableFile(launcher, WINDOWS_STABLE_LAUNCHER, sync);
|
|
24820
|
+
}
|
|
24821
|
+
async function rejectWindowsSymlinkAncestors(profile, target) {
|
|
24822
|
+
const path = win322.relative(win322.resolve(profile), win322.resolve(target));
|
|
24823
|
+
let current = win322.resolve(profile);
|
|
24824
|
+
for (const segment of path.split("\\").filter(Boolean)) {
|
|
24825
|
+
current = win322.join(current, segment);
|
|
24826
|
+
try {
|
|
24827
|
+
const entry = await lstat4(current);
|
|
24828
|
+
if (entry.isSymbolicLink()) {
|
|
24829
|
+
throw new Error("run artifact path must not contain symbolic links or junctions");
|
|
24681
24830
|
}
|
|
24682
|
-
}
|
|
24683
|
-
|
|
24684
|
-
}
|
|
24685
|
-
const pointerPath = windowsReleasePointer(root);
|
|
24686
|
-
const existingPointer = await lstat4(pointerPath).catch((error52) => {
|
|
24687
|
-
if (error52.code === "ENOENT") return null;
|
|
24831
|
+
} catch (error52) {
|
|
24832
|
+
if (isMissing(error52)) return;
|
|
24688
24833
|
throw error52;
|
|
24689
|
-
});
|
|
24690
|
-
if (existingPointer) {
|
|
24691
|
-
if (!existingPointer.isFile() || existingPointer.size > 4 * 1024) {
|
|
24692
|
-
throw new Error("the Zixt Host Windows release pointer is invalid");
|
|
24693
|
-
}
|
|
24694
|
-
let prior;
|
|
24695
|
-
try {
|
|
24696
|
-
prior = JSON.parse(await readFile5(pointerPath, "utf8"));
|
|
24697
|
-
} catch {
|
|
24698
|
-
throw new Error("the Zixt Host Windows release pointer is invalid");
|
|
24699
|
-
}
|
|
24700
|
-
const candidate = prior;
|
|
24701
|
-
if (!prior || typeof prior !== "object" || candidate.schema !== 1 || typeof candidate.entry !== "string" || installedReleaseVersion(candidate.entry, root) === null) {
|
|
24702
|
-
throw new Error("the Zixt Host Windows release pointer is invalid");
|
|
24703
|
-
}
|
|
24704
24834
|
}
|
|
24705
|
-
await replaceDurableFile(
|
|
24706
|
-
pointerPath,
|
|
24707
|
-
`${JSON.stringify({ schema: 1, entry: resolve4(entry) })}
|
|
24708
|
-
`,
|
|
24709
|
-
sync
|
|
24710
|
-
);
|
|
24711
|
-
return launcher;
|
|
24712
24835
|
}
|
|
24713
|
-
|
|
24714
|
-
|
|
24715
|
-
const
|
|
24716
|
-
|
|
24717
|
-
|
|
24718
|
-
|
|
24719
|
-
|
|
24720
|
-
|
|
24721
|
-
|
|
24836
|
+
}
|
|
24837
|
+
async function prepareRoot(root) {
|
|
24838
|
+
const absolute = resolve4(root);
|
|
24839
|
+
let realProfile;
|
|
24840
|
+
if (process.platform === "win32") {
|
|
24841
|
+
const profile = resolve4(homedir());
|
|
24842
|
+
assertWindowsProfileBoundary(profile, absolute);
|
|
24843
|
+
await rejectWindowsSymlinkAncestors(profile, absolute);
|
|
24844
|
+
realProfile = await realpath3(profile);
|
|
24722
24845
|
}
|
|
24723
|
-
const temporary = join6(root, `.${CURRENT_RELEASE_ENTRY}.${process.pid}.${crypto.randomUUID()}`);
|
|
24724
24846
|
try {
|
|
24725
|
-
await
|
|
24726
|
-
await rename2(temporary, current);
|
|
24727
|
-
await sync(root);
|
|
24847
|
+
await lstat4(absolute);
|
|
24728
24848
|
} catch (error52) {
|
|
24729
|
-
|
|
24730
|
-
|
|
24849
|
+
if (!isMissing(error52)) throw error52;
|
|
24850
|
+
await mkdir3(absolute, { recursive: true, mode: DIRECTORY_MODE });
|
|
24731
24851
|
}
|
|
24732
|
-
|
|
24852
|
+
const real = await requireRealDirectory(absolute, "run artifact root");
|
|
24853
|
+
if (realProfile) assertWindowsProfileBoundary(realProfile, real);
|
|
24854
|
+
await chmod2(real, DIRECTORY_MODE);
|
|
24855
|
+
return real;
|
|
24733
24856
|
}
|
|
24734
|
-
async function
|
|
24735
|
-
|
|
24857
|
+
async function prepareAgentRoot(root, agentId) {
|
|
24858
|
+
const path = join6(root, agentId);
|
|
24859
|
+
assertBelow(root, path);
|
|
24860
|
+
try {
|
|
24861
|
+
await lstat4(path);
|
|
24862
|
+
} catch (error52) {
|
|
24863
|
+
if (!isMissing(error52)) throw error52;
|
|
24736
24864
|
try {
|
|
24737
|
-
|
|
24738
|
-
|
|
24739
|
-
if (
|
|
24740
|
-
const value = JSON.parse(await readFile5(pointerPath, "utf8"));
|
|
24741
|
-
return value.schema === 1 && typeof value.entry === "string" ? installedReleaseVersion(value.entry, root) : null;
|
|
24742
|
-
} catch {
|
|
24743
|
-
return null;
|
|
24865
|
+
await mkdir3(path, { mode: DIRECTORY_MODE });
|
|
24866
|
+
} catch (mkdirError) {
|
|
24867
|
+
if (mkdirError.code !== "EEXIST") throw mkdirError;
|
|
24744
24868
|
}
|
|
24745
24869
|
}
|
|
24746
|
-
|
|
24747
|
-
|
|
24748
|
-
|
|
24749
|
-
|
|
24750
|
-
|
|
24751
|
-
|
|
24752
|
-
|
|
24870
|
+
const real = await requireRealDirectory(path, "run artifact Agent directory");
|
|
24871
|
+
assertBelow(root, real);
|
|
24872
|
+
await chmod2(real, DIRECTORY_MODE);
|
|
24873
|
+
return real;
|
|
24874
|
+
}
|
|
24875
|
+
async function lockDownWindowsDirectories(paths) {
|
|
24876
|
+
if (process.platform !== "win32") return;
|
|
24877
|
+
const windowsRoot = process.env.SystemRoot ?? process.env.WINDIR;
|
|
24878
|
+
if (!windowsRoot || !win322.isAbsolute(windowsRoot)) {
|
|
24879
|
+
throw new Error("private Windows run-artifact ACL authority is unavailable");
|
|
24753
24880
|
}
|
|
24881
|
+
const powershell = join6(windowsRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
24882
|
+
const encoded = Buffer.from(WINDOWS_PRIVATE_DACL_SCRIPT, "utf16le").toString("base64");
|
|
24883
|
+
await new Promise((resolvePromise, reject3) => {
|
|
24884
|
+
const helper = spawn4(
|
|
24885
|
+
powershell,
|
|
24886
|
+
["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded],
|
|
24887
|
+
{
|
|
24888
|
+
env: {
|
|
24889
|
+
SystemRoot: windowsRoot,
|
|
24890
|
+
WINDIR: windowsRoot,
|
|
24891
|
+
...process.env.TEMP ? { TEMP: process.env.TEMP } : {},
|
|
24892
|
+
...process.env.TMP ? { TMP: process.env.TMP } : {},
|
|
24893
|
+
ZIXT_RUN_ARTIFACT_PATHS: JSON.stringify(paths)
|
|
24894
|
+
},
|
|
24895
|
+
stdio: "ignore",
|
|
24896
|
+
windowsHide: true
|
|
24897
|
+
}
|
|
24898
|
+
);
|
|
24899
|
+
let finished = false;
|
|
24900
|
+
const timeout = setTimeout(() => {
|
|
24901
|
+
helper.kill("SIGKILL");
|
|
24902
|
+
finish(new Error("private Windows run-artifact ACL update timed out"));
|
|
24903
|
+
}, WINDOWS_ACL_TIMEOUT_MS);
|
|
24904
|
+
timeout.unref?.();
|
|
24905
|
+
const finish = (error52) => {
|
|
24906
|
+
if (finished) return;
|
|
24907
|
+
finished = true;
|
|
24908
|
+
clearTimeout(timeout);
|
|
24909
|
+
if (error52) reject3(error52);
|
|
24910
|
+
else resolvePromise();
|
|
24911
|
+
};
|
|
24912
|
+
helper.once(
|
|
24913
|
+
"error",
|
|
24914
|
+
() => finish(new Error("private Windows run-artifact ACL update could not start"))
|
|
24915
|
+
);
|
|
24916
|
+
helper.once(
|
|
24917
|
+
"close",
|
|
24918
|
+
(code) => finish(code === 0 ? void 0 : new Error("private Windows run-artifact ACL update failed"))
|
|
24919
|
+
);
|
|
24920
|
+
});
|
|
24754
24921
|
}
|
|
24755
|
-
function
|
|
24756
|
-
const
|
|
24757
|
-
|
|
24758
|
-
|
|
24759
|
-
|
|
24760
|
-
|
|
24761
|
-
|
|
24762
|
-
|
|
24763
|
-
|
|
24764
|
-
|
|
24765
|
-
|
|
24766
|
-
|
|
24767
|
-
|
|
24922
|
+
async function createPrivateDirectory(parent, name) {
|
|
24923
|
+
const path = join6(parent, name);
|
|
24924
|
+
assertBelow(parent, path);
|
|
24925
|
+
await mkdir3(path, { mode: DIRECTORY_MODE });
|
|
24926
|
+
await chmod2(path, DIRECTORY_MODE);
|
|
24927
|
+
const real = await realpath3(path);
|
|
24928
|
+
assertBelow(parent, real);
|
|
24929
|
+
return real;
|
|
24930
|
+
}
|
|
24931
|
+
async function writePrivateFile(path, content) {
|
|
24932
|
+
await writeFile(path, content, { flag: "wx", mode: FILE_MODE });
|
|
24933
|
+
await chmod2(path, FILE_MODE);
|
|
24934
|
+
}
|
|
24935
|
+
function quotePosix(value) {
|
|
24936
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
24937
|
+
}
|
|
24938
|
+
function quoteWindows(value) {
|
|
24939
|
+
if (value.includes('"') || /[\r\n]/.test(value)) {
|
|
24940
|
+
throw new Error("Windows command paths must not contain quotes or newlines");
|
|
24941
|
+
}
|
|
24942
|
+
return `"${value}"`;
|
|
24943
|
+
}
|
|
24944
|
+
function buildDenySshCommand(executablePath, scriptPath, platform = process.platform) {
|
|
24945
|
+
const quote2 = platform === "win32" ? quoteWindows : quotePosix;
|
|
24946
|
+
return `${quote2(executablePath)} ${quote2(scriptPath)}`;
|
|
24947
|
+
}
|
|
24948
|
+
async function createRunArtifacts(input) {
|
|
24949
|
+
requireSafeSegment(input.agentId, "agentId");
|
|
24950
|
+
requireSafeSegment(input.runToken, "runToken");
|
|
24951
|
+
const root = await prepareRoot(input.root);
|
|
24952
|
+
const removeTree = input.removeTree ?? ((path) => rm3(path, { recursive: true, force: true }));
|
|
24953
|
+
const cleanupRetryDelayMs = input.cleanupRetryDelayMs ?? CLEANUP_RETRY_DELAY_MS;
|
|
24954
|
+
const agentRoot = await prepareAgentRoot(root, input.agentId);
|
|
24955
|
+
await lockDownWindowsDirectories([root, agentRoot]);
|
|
24956
|
+
const runRoot = join6(agentRoot, input.runToken);
|
|
24957
|
+
assertBelow(agentRoot, runRoot);
|
|
24958
|
+
try {
|
|
24959
|
+
await mkdir3(runRoot, { mode: DIRECTORY_MODE });
|
|
24960
|
+
await chmod2(runRoot, DIRECTORY_MODE);
|
|
24961
|
+
const realRunRoot = await realpath3(runRoot);
|
|
24962
|
+
assertBelow(agentRoot, realRunRoot);
|
|
24963
|
+
const emptyGithubConfigDirectory = await createPrivateDirectory(realRunRoot, "gh-config");
|
|
24964
|
+
const emptyGitHooksDirectory = await createPrivateDirectory(realRunRoot, "git-hooks");
|
|
24965
|
+
const gitBridgesDirectory = await createPrivateDirectory(realRunRoot, "git-bridges");
|
|
24966
|
+
const denySshScript = join6(realRunRoot, "deny-ssh.cjs");
|
|
24967
|
+
const runnerWrapperScript = join6(realRunRoot, "runner-wrapper.cjs");
|
|
24968
|
+
const systemPromptPath = join6(realRunRoot, "system-prompt.txt");
|
|
24969
|
+
const mcpConfigPath = join6(realRunRoot, "mcp.json");
|
|
24970
|
+
await writePrivateFile(denySshScript, "process.exitCode = 127;");
|
|
24971
|
+
await writePrivateFile(runnerWrapperScript, RUNNER_GUARDIAN);
|
|
24972
|
+
return {
|
|
24973
|
+
runRoot: realRunRoot,
|
|
24974
|
+
emptyGithubConfigDirectory,
|
|
24975
|
+
emptyGitHooksDirectory,
|
|
24976
|
+
gitBridgesDirectory,
|
|
24977
|
+
denySshScript,
|
|
24978
|
+
denySshCommand: buildDenySshCommand(process.execPath, denySshScript),
|
|
24979
|
+
runnerWrapperScript,
|
|
24980
|
+
nullConfigPath: process.platform === "win32" ? "NUL" : "/dev/null",
|
|
24981
|
+
async writeSystemPrompt(content) {
|
|
24982
|
+
await writePrivateFile(systemPromptPath, content);
|
|
24983
|
+
return systemPromptPath;
|
|
24984
|
+
},
|
|
24985
|
+
async writeMcpConfig(content) {
|
|
24986
|
+
await writePrivateFile(mcpConfigPath, content);
|
|
24987
|
+
return mcpConfigPath;
|
|
24988
|
+
},
|
|
24989
|
+
async cleanup() {
|
|
24990
|
+
await removePrivateTreeWithRetries(realRunRoot, removeTree, cleanupRetryDelayMs);
|
|
24991
|
+
}
|
|
24992
|
+
};
|
|
24993
|
+
} catch (error52) {
|
|
24994
|
+
await removePrivateTreeWithRetries(runRoot, removeTree, cleanupRetryDelayMs).catch(() => {
|
|
24995
|
+
});
|
|
24996
|
+
throw error52;
|
|
24997
|
+
}
|
|
24998
|
+
}
|
|
24999
|
+
async function removePrivateTreeWithRetries(path, removeTree, retryDelayMs) {
|
|
25000
|
+
for (let attempt = 1; attempt <= CLEANUP_ATTEMPTS; attempt++) {
|
|
25001
|
+
try {
|
|
25002
|
+
await removeTree(path);
|
|
25003
|
+
return;
|
|
25004
|
+
} catch (error52) {
|
|
25005
|
+
if (attempt === CLEANUP_ATTEMPTS) throw error52;
|
|
25006
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, retryDelayMs));
|
|
25007
|
+
}
|
|
25008
|
+
}
|
|
25009
|
+
}
|
|
25010
|
+
async function sweepOrphanedRunArtifacts(root) {
|
|
25011
|
+
const absolute = resolve4(root);
|
|
25012
|
+
let realProfile;
|
|
25013
|
+
if (process.platform === "win32") {
|
|
25014
|
+
const profile = resolve4(homedir());
|
|
25015
|
+
assertWindowsProfileBoundary(profile, absolute);
|
|
25016
|
+
await rejectWindowsSymlinkAncestors(profile, absolute);
|
|
25017
|
+
realProfile = await realpath3(profile);
|
|
25018
|
+
}
|
|
25019
|
+
let realRoot;
|
|
25020
|
+
try {
|
|
25021
|
+
realRoot = await requireRealDirectory(absolute, "run artifact root");
|
|
25022
|
+
} catch (error52) {
|
|
25023
|
+
if (isMissing(error52)) return 0;
|
|
25024
|
+
throw error52;
|
|
25025
|
+
}
|
|
25026
|
+
if (realProfile) assertWindowsProfileBoundary(realProfile, realRoot);
|
|
25027
|
+
await chmod2(realRoot, DIRECTORY_MODE);
|
|
25028
|
+
await lockDownWindowsDirectories([realRoot]);
|
|
25029
|
+
const agents = await readdir2(realRoot, { withFileTypes: true });
|
|
25030
|
+
let removed = 0;
|
|
25031
|
+
for (const agent of agents) {
|
|
25032
|
+
if (!SAFE_SEGMENT.test(agent.name) || !agent.isDirectory() || agent.isSymbolicLink()) continue;
|
|
25033
|
+
const agentPath = join6(realRoot, agent.name);
|
|
25034
|
+
const runs = await readdir2(agentPath, { withFileTypes: true });
|
|
25035
|
+
for (const run3 of runs) {
|
|
25036
|
+
if (!SAFE_SEGMENT.test(run3.name) || !run3.isDirectory() || run3.isSymbolicLink()) continue;
|
|
25037
|
+
const runPath = join6(agentPath, run3.name);
|
|
25038
|
+
assertBelow(agentPath, runPath);
|
|
25039
|
+
await rm3(runPath, { recursive: true, force: true });
|
|
25040
|
+
removed++;
|
|
25041
|
+
}
|
|
25042
|
+
}
|
|
25043
|
+
return removed;
|
|
25044
|
+
}
|
|
25045
|
+
function defaultRunRegistryRoot() {
|
|
25046
|
+
return join6(homedir(), ".zixt", "run-registry");
|
|
25047
|
+
}
|
|
25048
|
+
async function terminateRecordedRunProcesses(registryRoot = defaultRunRegistryRoot(), terminate = terminateRecordedProcessTree) {
|
|
25049
|
+
const entries = await readRecordedRunAssignmentEntriesStrict(registryRoot);
|
|
25050
|
+
const terminated = /* @__PURE__ */ new Set();
|
|
25051
|
+
const orderedEntries = [...entries].sort(
|
|
25052
|
+
(left, right) => Number(Boolean(right.record.identity)) - Number(Boolean(left.record.identity))
|
|
25053
|
+
);
|
|
25054
|
+
for (const { record: record2 } of orderedEntries) {
|
|
25055
|
+
const identityKey = record2.identity ? `${record2.identity.kind}:${record2.identity.nonce}` : "legacy";
|
|
25056
|
+
const witnessKey = `${record2.pid}:${identityKey}`;
|
|
25057
|
+
if (terminated.has(witnessKey)) continue;
|
|
25058
|
+
await terminate(record2.pid, record2.identity);
|
|
25059
|
+
terminated.add(witnessKey);
|
|
25060
|
+
}
|
|
25061
|
+
return entries.map(({ record: record2 }) => record2);
|
|
25062
|
+
}
|
|
25063
|
+
async function syncRunRegistryDirectory(path) {
|
|
25064
|
+
const handle = await open3(path, "r");
|
|
25065
|
+
try {
|
|
25066
|
+
await handle.sync();
|
|
25067
|
+
} finally {
|
|
25068
|
+
await handle.close();
|
|
25069
|
+
}
|
|
25070
|
+
}
|
|
25071
|
+
async function ensureDurableRunRegistryRoot(registryRoot, syncDirectory7) {
|
|
25072
|
+
const firstCreated = await mkdir3(registryRoot, { recursive: true, mode: DIRECTORY_MODE });
|
|
25073
|
+
if (firstCreated && process.platform !== "win32") {
|
|
25074
|
+
const first = resolve4(firstCreated);
|
|
25075
|
+
const target = resolve4(registryRoot);
|
|
25076
|
+
await syncDirectory7(dirname3(first));
|
|
25077
|
+
let current = first;
|
|
25078
|
+
for (const part of relative3(first, target).split(sep3).filter(Boolean)) {
|
|
25079
|
+
await syncDirectory7(current);
|
|
25080
|
+
current = join6(current, part);
|
|
25081
|
+
}
|
|
25082
|
+
}
|
|
25083
|
+
await chmod2(registryRoot, DIRECTORY_MODE);
|
|
25084
|
+
}
|
|
25085
|
+
async function recordRunAssignment(runToken, record2, registryRoot = defaultRunRegistryRoot(), options = {}) {
|
|
25086
|
+
if (!SAFE_SEGMENT.test(runToken)) return false;
|
|
25087
|
+
const destination = join6(registryRoot, `${runToken}.json`);
|
|
25088
|
+
const temporary = join6(registryRoot, `.${runToken}.${process.pid}.${Date.now()}.tmp`);
|
|
25089
|
+
let handle;
|
|
25090
|
+
try {
|
|
25091
|
+
const syncDirectory7 = options.syncDirectory ?? syncRunRegistryDirectory;
|
|
25092
|
+
await ensureDurableRunRegistryRoot(registryRoot, syncDirectory7);
|
|
25093
|
+
handle = await open3(temporary, "wx", FILE_MODE);
|
|
25094
|
+
await handle.writeFile(JSON.stringify(record2), "utf8");
|
|
25095
|
+
await handle.sync();
|
|
25096
|
+
await handle.close();
|
|
25097
|
+
handle = void 0;
|
|
25098
|
+
await rename2(temporary, destination);
|
|
25099
|
+
if (process.platform !== "win32") {
|
|
25100
|
+
await syncDirectory7(registryRoot);
|
|
25101
|
+
}
|
|
25102
|
+
return true;
|
|
25103
|
+
} catch {
|
|
25104
|
+
return false;
|
|
25105
|
+
} finally {
|
|
25106
|
+
await handle?.close().catch(() => {
|
|
25107
|
+
});
|
|
25108
|
+
await rm3(temporary, { force: true }).catch(() => {
|
|
25109
|
+
});
|
|
25110
|
+
}
|
|
25111
|
+
}
|
|
25112
|
+
var retainingAssignments = false;
|
|
25113
|
+
function retainRunAssignments() {
|
|
25114
|
+
retainingAssignments = true;
|
|
25115
|
+
}
|
|
25116
|
+
async function forgetRunAssignment(runToken, registryRoot = defaultRunRegistryRoot()) {
|
|
25117
|
+
if (retainingAssignments) return;
|
|
25118
|
+
if (!SAFE_SEGMENT.test(runToken)) return;
|
|
25119
|
+
try {
|
|
25120
|
+
await rm3(join6(registryRoot, `${runToken}.json`), { force: true });
|
|
25121
|
+
} catch {
|
|
25122
|
+
}
|
|
25123
|
+
}
|
|
25124
|
+
function parseAssignment(text) {
|
|
25125
|
+
let parsed;
|
|
25126
|
+
try {
|
|
25127
|
+
parsed = JSON.parse(text);
|
|
25128
|
+
} catch {
|
|
25129
|
+
return null;
|
|
25130
|
+
}
|
|
25131
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
25132
|
+
const value = parsed;
|
|
25133
|
+
if (typeof value.taskId !== "string" || value.taskId === "") return null;
|
|
25134
|
+
if (typeof value.epoch !== "number" || !Number.isInteger(value.epoch) || value.epoch < 1) {
|
|
25135
|
+
return null;
|
|
25136
|
+
}
|
|
25137
|
+
const pid = value.pid;
|
|
25138
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 1) return null;
|
|
25139
|
+
let identity;
|
|
25140
|
+
if (value.identity !== void 0) {
|
|
25141
|
+
if (typeof value.identity !== "object" || value.identity === null || value.identity.kind !== "guardian_nonce" || typeof value.identity.nonce !== "string" || !/^[A-Za-z0-9_-]{16,200}$/.test(value.identity.nonce)) {
|
|
25142
|
+
return null;
|
|
25143
|
+
}
|
|
25144
|
+
identity = {
|
|
25145
|
+
kind: "guardian_nonce",
|
|
25146
|
+
nonce: value.identity.nonce
|
|
25147
|
+
};
|
|
25148
|
+
}
|
|
25149
|
+
return { taskId: value.taskId, epoch: value.epoch, pid, ...identity ? { identity } : {} };
|
|
25150
|
+
}
|
|
25151
|
+
async function readRecordedRunAssignmentEntries(registryRoot = defaultRunRegistryRoot()) {
|
|
25152
|
+
let entries;
|
|
25153
|
+
try {
|
|
25154
|
+
entries = await readdir2(registryRoot, { withFileTypes: true });
|
|
25155
|
+
} catch {
|
|
25156
|
+
return [];
|
|
25157
|
+
}
|
|
25158
|
+
const found = [];
|
|
25159
|
+
for (const entry of entries) {
|
|
25160
|
+
if (!entry.isFile() || entry.isSymbolicLink() || !entry.name.endsWith(".json")) continue;
|
|
25161
|
+
const runToken = entry.name.slice(0, -".json".length);
|
|
25162
|
+
if (!SAFE_SEGMENT.test(runToken)) continue;
|
|
25163
|
+
let text;
|
|
25164
|
+
try {
|
|
25165
|
+
text = await readFile5(join6(registryRoot, entry.name), "utf8");
|
|
25166
|
+
} catch {
|
|
25167
|
+
continue;
|
|
25168
|
+
}
|
|
25169
|
+
const record2 = parseAssignment(text);
|
|
25170
|
+
if (record2) found.push({ runToken, record: record2 });
|
|
25171
|
+
}
|
|
25172
|
+
return found;
|
|
25173
|
+
}
|
|
25174
|
+
async function readRecordedRunAssignmentEntriesStrict(registryRoot = defaultRunRegistryRoot()) {
|
|
25175
|
+
let rootStat;
|
|
25176
|
+
try {
|
|
25177
|
+
rootStat = await lstat4(registryRoot);
|
|
25178
|
+
} catch (error52) {
|
|
25179
|
+
if (isMissing(error52)) return [];
|
|
25180
|
+
throw new Error("run registry state could not be observed");
|
|
25181
|
+
}
|
|
25182
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
25183
|
+
throw new Error("run registry root is not a trusted directory");
|
|
25184
|
+
}
|
|
25185
|
+
let entries;
|
|
25186
|
+
try {
|
|
25187
|
+
entries = await readdir2(registryRoot, { withFileTypes: true });
|
|
25188
|
+
} catch {
|
|
25189
|
+
throw new Error("run registry state could not be observed");
|
|
25190
|
+
}
|
|
25191
|
+
const found = [];
|
|
25192
|
+
for (const entry of entries) {
|
|
25193
|
+
if (!entry.name.endsWith(".json")) continue;
|
|
25194
|
+
const runToken = entry.name.slice(0, -".json".length);
|
|
25195
|
+
if (!SAFE_SEGMENT.test(runToken)) continue;
|
|
25196
|
+
if (!entry.isFile() || entry.isSymbolicLink()) {
|
|
25197
|
+
throw new Error("committed run registry witness is not a regular file");
|
|
25198
|
+
}
|
|
25199
|
+
let text;
|
|
25200
|
+
try {
|
|
25201
|
+
text = await readFile5(join6(registryRoot, entry.name), "utf8");
|
|
25202
|
+
} catch {
|
|
25203
|
+
throw new Error("committed run registry witness could not be read");
|
|
25204
|
+
}
|
|
25205
|
+
const record2 = parseAssignment(text);
|
|
25206
|
+
if (!record2) throw new Error("committed run registry witness is malformed");
|
|
25207
|
+
found.push({ runToken, record: record2 });
|
|
25208
|
+
}
|
|
25209
|
+
return found;
|
|
25210
|
+
}
|
|
25211
|
+
async function forgetAcknowledgedRunAssignments(assignments, registryRoot = defaultRunRegistryRoot()) {
|
|
25212
|
+
if (assignments.length === 0) return;
|
|
25213
|
+
const acknowledged = new Set(
|
|
25214
|
+
assignments.map((assignment) => `${assignment.taskId}:${assignment.epoch}`)
|
|
25215
|
+
);
|
|
25216
|
+
const entries = await readRecordedRunAssignmentEntries(registryRoot);
|
|
25217
|
+
await Promise.all(
|
|
25218
|
+
entries.filter(({ record: record2 }) => acknowledged.has(`${record2.taskId}:${record2.epoch}`)).map(({ runToken }) => rm3(join6(registryRoot, `${runToken}.json`), { force: true }))
|
|
25219
|
+
);
|
|
25220
|
+
}
|
|
25221
|
+
async function forgetSupersededRunAssignments(taskId, epoch, registryRoot = defaultRunRegistryRoot()) {
|
|
25222
|
+
const entries = await readRecordedRunAssignmentEntries(registryRoot);
|
|
25223
|
+
await Promise.all(
|
|
25224
|
+
entries.filter(({ record: record2 }) => record2.taskId === taskId && record2.epoch < epoch).map(({ runToken }) => rm3(join6(registryRoot, `${runToken}.json`), { force: true }))
|
|
25225
|
+
);
|
|
25226
|
+
}
|
|
25227
|
+
|
|
25228
|
+
// src/supervisor.ts
|
|
25229
|
+
var PACKAGE_NAME = "@zixt/host";
|
|
25230
|
+
var WORKER_ROLE_ENV = "ZIXT_HOST_ROLE";
|
|
25231
|
+
var WORKER_ROLE = "worker";
|
|
25232
|
+
var WORKER_PROXY_ROLE = "worker_proxy";
|
|
25233
|
+
var SUPERVISOR_ROLE = "supervisor";
|
|
25234
|
+
var WORKER_PROXY_ENTRY_ENV = "ZIXT_HOST_WORKER_PROXY_ENTRY";
|
|
25235
|
+
var SUPERVISOR_PROTOCOL_ENV = "ZIXT_HOST_SUPERVISOR_PROTOCOL";
|
|
25236
|
+
var SUPERVISOR_VERSION_ENV = "ZIXT_HOST_SUPERVISOR_VERSION";
|
|
25237
|
+
var SUPERVISOR_PROTOCOL = "2";
|
|
25238
|
+
var SUPERVISOR_RELOAD_EXIT_CODE = 76;
|
|
25239
|
+
var DO_NOT_RESTART_EXIT_CODE = 64;
|
|
25240
|
+
var RESTART_BACKOFF_MS = 2e3;
|
|
25241
|
+
var MAX_RESTART_BACKOFF_MS = 6e4;
|
|
25242
|
+
var CRASH_STREAK_RESET_MS = 6e4;
|
|
25243
|
+
var RELEASE_PROBATION_MS = 5 * 6e4;
|
|
25244
|
+
var FAST_UPDATE_EXIT_MS = 1e4;
|
|
25245
|
+
var STOP_GRACE_MS = 3e4;
|
|
25246
|
+
var INSTALL_TIMEOUT_MS = 5 * 6e4;
|
|
25247
|
+
var WORKER_WATCHDOG_CHECK_MS = 1e3;
|
|
25248
|
+
var WORKER_WATCHDOG_TIMEOUT_MS = 15e3;
|
|
25249
|
+
var WORKER_WATCHDOG_STARTUP_MS = 6e4;
|
|
25250
|
+
var WORKER_READINESS_TIMEOUT_MS = 9e4;
|
|
25251
|
+
var CLEANUP_ATTEMPTS2 = 3;
|
|
25252
|
+
var VERSION_DIR = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
25253
|
+
var CURRENT_RELEASE_ENTRY = "current.js";
|
|
25254
|
+
var WINDOWS_RELEASE_POINTER = "current-release.json";
|
|
25255
|
+
var WINDOWS_LAUNCHER_OWNED_MARKER = "// Zixt Host stable Windows launcher v";
|
|
25256
|
+
var WINDOWS_LAUNCHER_MARKER = `${WINDOWS_LAUNCHER_OWNED_MARKER}2`;
|
|
25257
|
+
function signalWorkerGroup(child, signal) {
|
|
25258
|
+
if (!child.pid) return;
|
|
25259
|
+
try {
|
|
25260
|
+
if (process.platform === "win32") child.kill(signal);
|
|
25261
|
+
else process.kill(-child.pid, signal);
|
|
25262
|
+
} catch {
|
|
25263
|
+
}
|
|
25264
|
+
}
|
|
25265
|
+
function versionsRoot() {
|
|
25266
|
+
return process.env.ZIXT_HOST_VERSIONS_DIR ?? join7(homedir2(), ".zixt", "host-versions");
|
|
25267
|
+
}
|
|
25268
|
+
function npmCommand(platform = process.platform) {
|
|
25269
|
+
return platform === "win32" ? "npm.cmd" : "npm";
|
|
25270
|
+
}
|
|
25271
|
+
function windowsInstallerCommandLine(command, args) {
|
|
25272
|
+
return [command, ...args].map(quoteForCmd).join(" ");
|
|
25273
|
+
}
|
|
25274
|
+
function installedReleaseEntry(version2, root = versionsRoot()) {
|
|
25275
|
+
return join7(root, version2, "node_modules", PACKAGE_NAME, "dist", "index.js");
|
|
25276
|
+
}
|
|
25277
|
+
function releaseEntryAtPrefix(prefix) {
|
|
25278
|
+
return join7(prefix, "node_modules", PACKAGE_NAME, "dist", "index.js");
|
|
25279
|
+
}
|
|
25280
|
+
function releaseManifestAtPrefix(prefix) {
|
|
25281
|
+
return join7(prefix, "node_modules", PACKAGE_NAME, "package.json");
|
|
25282
|
+
}
|
|
25283
|
+
async function validReleaseAtPrefix(prefix, version2) {
|
|
25284
|
+
try {
|
|
25285
|
+
const [entry, manifestText] = await Promise.all([
|
|
25286
|
+
lstat5(releaseEntryAtPrefix(prefix)),
|
|
25287
|
+
readFile6(releaseManifestAtPrefix(prefix), "utf8")
|
|
25288
|
+
]);
|
|
25289
|
+
if (!entry.isFile()) return false;
|
|
25290
|
+
const manifest = JSON.parse(manifestText);
|
|
25291
|
+
return manifest.name === PACKAGE_NAME && manifest.version === version2;
|
|
25292
|
+
} catch {
|
|
25293
|
+
return false;
|
|
25294
|
+
}
|
|
25295
|
+
}
|
|
25296
|
+
async function syncDirectory3(path) {
|
|
25297
|
+
if (process.platform === "win32") return;
|
|
25298
|
+
const directory = await open4(path, "r");
|
|
25299
|
+
try {
|
|
25300
|
+
await directory.sync();
|
|
25301
|
+
} finally {
|
|
25302
|
+
await directory.close();
|
|
25303
|
+
}
|
|
25304
|
+
}
|
|
25305
|
+
function installedReleaseVersion(entry, root = versionsRoot()) {
|
|
25306
|
+
if (!isAbsolute8(entry)) return null;
|
|
25307
|
+
const relativeEntry = relative4(resolve5(root), resolve5(entry));
|
|
25308
|
+
if (!relativeEntry || relativeEntry.startsWith(`..${sep4}`) || isAbsolute8(relativeEntry)) {
|
|
25309
|
+
return null;
|
|
25310
|
+
}
|
|
25311
|
+
const version2 = relativeEntry.split(sep4)[0];
|
|
25312
|
+
if (!version2 || !VERSION_DIR.test(version2)) return null;
|
|
25313
|
+
return resolve5(entry) === resolve5(installedReleaseEntry(version2, root)) ? version2 : null;
|
|
25314
|
+
}
|
|
25315
|
+
function currentReleaseEntry(root = versionsRoot(), platform = process.platform) {
|
|
25316
|
+
return join7(root, platform === "win32" ? "current-launcher.cjs" : CURRENT_RELEASE_ENTRY);
|
|
25317
|
+
}
|
|
25318
|
+
function windowsReleasePointer(root = versionsRoot()) {
|
|
25319
|
+
return join7(root, WINDOWS_RELEASE_POINTER);
|
|
25320
|
+
}
|
|
25321
|
+
var WINDOWS_STABLE_LAUNCHER = `${WINDOWS_LAUNCHER_MARKER}
|
|
25322
|
+
'use strict';
|
|
25323
|
+
const { spawn } = require('node:child_process');
|
|
25324
|
+
const { readFileSync } = require('node:fs');
|
|
25325
|
+
const { dirname, isAbsolute, join } = require('node:path');
|
|
25326
|
+
const pointer = JSON.parse(readFileSync(join(dirname(__filename), '${WINDOWS_RELEASE_POINTER}'), 'utf8'));
|
|
25327
|
+
if (pointer.schema !== 1 || typeof pointer.entry !== 'string' || !isAbsolute(pointer.entry)) process.exit(64);
|
|
25328
|
+
const child = spawn(process.execPath, [pointer.entry, ...process.argv.slice(2)], {
|
|
25329
|
+
// This wrapper owns the only write end of the installed launcher's stdin.
|
|
25330
|
+
// If Task Scheduler/the wrapper dies, EOF reaches launchHostSupervisor and
|
|
25331
|
+
// triggers checked supervisor/worker cleanup before a scheduled restart.
|
|
25332
|
+
stdio: ['pipe', 'inherit', 'inherit'],
|
|
25333
|
+
windowsHide: true,
|
|
25334
|
+
});
|
|
25335
|
+
child.stdin.on('error', () => {});
|
|
25336
|
+
let stopping = false;
|
|
25337
|
+
const closeChildInput = () => {
|
|
25338
|
+
if (stopping) return;
|
|
25339
|
+
stopping = true;
|
|
25340
|
+
try { child.stdin.end(); } catch {}
|
|
25341
|
+
};
|
|
25342
|
+
process.stdin.on('data', (chunk) => {
|
|
25343
|
+
if (!stopping && child.stdin.writable) child.stdin.write(chunk);
|
|
25344
|
+
});
|
|
25345
|
+
process.stdin.on('end', closeChildInput);
|
|
25346
|
+
process.stdin.on('error', closeChildInput);
|
|
25347
|
+
process.stdin.resume();
|
|
25348
|
+
process.on('SIGINT', closeChildInput);
|
|
25349
|
+
process.on('SIGTERM', closeChildInput);
|
|
25350
|
+
child.once('error', () => process.exit(1));
|
|
25351
|
+
child.once('exit', (code) => process.exit(code == null ? 1 : code));
|
|
25352
|
+
`;
|
|
25353
|
+
async function replaceDurableFile(path, contents, sync = syncDirectory3) {
|
|
25354
|
+
const parent = dirname4(path);
|
|
25355
|
+
await mkdir4(parent, { recursive: true, mode: 448 });
|
|
25356
|
+
const temporary = join7(parent, `.${CURRENT_RELEASE_ENTRY}.${process.pid}.${crypto.randomUUID()}`);
|
|
25357
|
+
const handle = await open4(temporary, "wx", 384);
|
|
25358
|
+
try {
|
|
25359
|
+
await handle.writeFile(contents, "utf8");
|
|
25360
|
+
await handle.sync();
|
|
25361
|
+
await handle.close();
|
|
25362
|
+
await rename3(temporary, path);
|
|
25363
|
+
await sync(parent);
|
|
25364
|
+
} catch (error52) {
|
|
25365
|
+
await handle.close().catch(() => void 0);
|
|
25366
|
+
await rm4(temporary, { force: true }).catch(() => void 0);
|
|
25367
|
+
throw error52;
|
|
25368
|
+
}
|
|
25369
|
+
}
|
|
25370
|
+
async function activateInstalledRelease(entry, root = versionsRoot(), platform = process.platform, sync = syncDirectory3) {
|
|
25371
|
+
if (installedReleaseVersion(entry, root) === null) {
|
|
25372
|
+
throw new Error("release entry is outside the Zixt Host versions directory");
|
|
25373
|
+
}
|
|
25374
|
+
await access2(entry);
|
|
25375
|
+
if (platform === "win32") {
|
|
25376
|
+
await mkdir4(root, { recursive: true, mode: 448 });
|
|
25377
|
+
const launcher = currentReleaseEntry(root, platform);
|
|
25378
|
+
const existingLauncher = await lstat5(launcher).catch((error52) => {
|
|
25379
|
+
if (error52.code === "ENOENT") return null;
|
|
25380
|
+
throw error52;
|
|
25381
|
+
});
|
|
25382
|
+
if (existingLauncher) {
|
|
25383
|
+
if (!existingLauncher.isFile()) {
|
|
25384
|
+
throw new Error("the Zixt Host Windows launcher is not a regular file");
|
|
25385
|
+
}
|
|
25386
|
+
const contents = await readFile6(launcher, "utf8");
|
|
25387
|
+
if (!contents.startsWith(WINDOWS_LAUNCHER_OWNED_MARKER)) {
|
|
25388
|
+
throw new Error("the Zixt Host Windows launcher is not owned by Zixt");
|
|
25389
|
+
}
|
|
25390
|
+
if (contents !== WINDOWS_STABLE_LAUNCHER) {
|
|
25391
|
+
await replaceDurableFile(launcher, WINDOWS_STABLE_LAUNCHER, sync);
|
|
25392
|
+
}
|
|
25393
|
+
} else {
|
|
25394
|
+
await replaceDurableFile(launcher, WINDOWS_STABLE_LAUNCHER, sync);
|
|
25395
|
+
}
|
|
25396
|
+
const pointerPath = windowsReleasePointer(root);
|
|
25397
|
+
const existingPointer = await lstat5(pointerPath).catch((error52) => {
|
|
25398
|
+
if (error52.code === "ENOENT") return null;
|
|
25399
|
+
throw error52;
|
|
25400
|
+
});
|
|
25401
|
+
if (existingPointer) {
|
|
25402
|
+
if (!existingPointer.isFile() || existingPointer.size > 4 * 1024) {
|
|
25403
|
+
throw new Error("the Zixt Host Windows release pointer is invalid");
|
|
25404
|
+
}
|
|
25405
|
+
let prior;
|
|
25406
|
+
try {
|
|
25407
|
+
prior = JSON.parse(await readFile6(pointerPath, "utf8"));
|
|
25408
|
+
} catch {
|
|
25409
|
+
throw new Error("the Zixt Host Windows release pointer is invalid");
|
|
25410
|
+
}
|
|
25411
|
+
const candidate = prior;
|
|
25412
|
+
if (!prior || typeof prior !== "object" || candidate.schema !== 1 || typeof candidate.entry !== "string" || installedReleaseVersion(candidate.entry, root) === null) {
|
|
25413
|
+
throw new Error("the Zixt Host Windows release pointer is invalid");
|
|
25414
|
+
}
|
|
25415
|
+
}
|
|
25416
|
+
await replaceDurableFile(
|
|
25417
|
+
pointerPath,
|
|
25418
|
+
`${JSON.stringify({ schema: 1, entry: resolve5(entry) })}
|
|
25419
|
+
`,
|
|
25420
|
+
sync
|
|
25421
|
+
);
|
|
25422
|
+
return launcher;
|
|
25423
|
+
}
|
|
25424
|
+
if (platform !== "linux" && platform !== "darwin") return entry;
|
|
25425
|
+
await mkdir4(root, { recursive: true, mode: 448 });
|
|
25426
|
+
const current = currentReleaseEntry(root, platform);
|
|
25427
|
+
const existing = await lstat5(current).catch((error52) => {
|
|
25428
|
+
if (error52.code === "ENOENT") return null;
|
|
25429
|
+
throw error52;
|
|
25430
|
+
});
|
|
25431
|
+
if (existing && !existing.isSymbolicLink()) {
|
|
25432
|
+
throw new Error("the Zixt Host current-release entry is not a symbolic link");
|
|
25433
|
+
}
|
|
25434
|
+
const temporary = join7(root, `.${CURRENT_RELEASE_ENTRY}.${process.pid}.${crypto.randomUUID()}`);
|
|
25435
|
+
try {
|
|
25436
|
+
await symlink(entry, temporary, "file");
|
|
25437
|
+
await rename3(temporary, current);
|
|
25438
|
+
await sync(root);
|
|
25439
|
+
} catch (error52) {
|
|
25440
|
+
await rm4(temporary, { force: true }).catch(() => void 0);
|
|
25441
|
+
throw error52;
|
|
25442
|
+
}
|
|
25443
|
+
return current;
|
|
25444
|
+
}
|
|
25445
|
+
async function activatedReleaseVersion(root = versionsRoot(), platform = process.platform) {
|
|
25446
|
+
if (platform === "win32") {
|
|
25447
|
+
try {
|
|
25448
|
+
const pointerPath = windowsReleasePointer(root);
|
|
25449
|
+
const metadata = await lstat5(pointerPath);
|
|
25450
|
+
if (!metadata.isFile() || metadata.size > 4 * 1024) return null;
|
|
25451
|
+
const value = JSON.parse(await readFile6(pointerPath, "utf8"));
|
|
25452
|
+
return value.schema === 1 && typeof value.entry === "string" ? installedReleaseVersion(value.entry, root) : null;
|
|
25453
|
+
} catch {
|
|
25454
|
+
return null;
|
|
25455
|
+
}
|
|
25456
|
+
}
|
|
25457
|
+
if (platform !== "linux" && platform !== "darwin") return null;
|
|
25458
|
+
try {
|
|
25459
|
+
const current = currentReleaseEntry(root, platform);
|
|
25460
|
+
const target = await readlink(current);
|
|
25461
|
+
return installedReleaseVersion(resolve5(dirname4(current), target), root);
|
|
25462
|
+
} catch {
|
|
25463
|
+
return null;
|
|
25464
|
+
}
|
|
25465
|
+
}
|
|
25466
|
+
function sanitizedInstallerEnv(inherited) {
|
|
25467
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
25468
|
+
"PATH",
|
|
25469
|
+
"HOME",
|
|
25470
|
+
"USERPROFILE",
|
|
25471
|
+
"SYSTEMROOT",
|
|
25472
|
+
"WINDIR",
|
|
25473
|
+
"TEMP",
|
|
25474
|
+
"TMP",
|
|
25475
|
+
"TMPDIR",
|
|
25476
|
+
"LOCALAPPDATA",
|
|
25477
|
+
"APPDATA",
|
|
25478
|
+
"XDG_CONFIG_HOME",
|
|
24768
25479
|
"XDG_CACHE_HOME",
|
|
24769
25480
|
"HTTP_PROXY",
|
|
24770
25481
|
"HTTPS_PROXY",
|
|
@@ -24851,13 +25562,13 @@ async function installRelease(version2, options = {}) {
|
|
|
24851
25562
|
const platform = options.platform ?? process.platform;
|
|
24852
25563
|
const installerCommand = options.installerCommand ?? npmCommand(platform);
|
|
24853
25564
|
const root = options.root ?? versionsRoot();
|
|
24854
|
-
const prefix =
|
|
25565
|
+
const prefix = join7(root, version2);
|
|
24855
25566
|
const entry = installedReleaseEntry(version2, root);
|
|
24856
25567
|
if (await validReleaseAtPrefix(prefix, version2)) return entry;
|
|
24857
25568
|
if (options.signal?.aborted) return null;
|
|
24858
|
-
await
|
|
24859
|
-
const staging =
|
|
24860
|
-
const quarantine =
|
|
25569
|
+
await mkdir4(root, { recursive: true, mode: 448 });
|
|
25570
|
+
const staging = join7(root, `.install-${version2}-${process.pid}-${crypto.randomUUID()}`);
|
|
25571
|
+
const quarantine = join7(root, `.invalid-${version2}-${process.pid}-${crypto.randomUUID()}`);
|
|
24861
25572
|
const usesWindowsInstallerGuardian = platform === "win32" && options.spawnInstaller === void 0;
|
|
24862
25573
|
const installerGateNonce = usesWindowsInstallerGuardian ? crypto.randomUUID() : null;
|
|
24863
25574
|
const installerArguments = (installPrefix, installVersion) => [
|
|
@@ -24872,14 +25583,14 @@ async function installRelease(version2, options = {}) {
|
|
|
24872
25583
|
const spawnInstaller = options.spawnInstaller ?? ((installPrefix, installVersion) => {
|
|
24873
25584
|
const installerArgs = installerArguments(installPrefix, installVersion);
|
|
24874
25585
|
if (usesWindowsInstallerGuardian) {
|
|
24875
|
-
return
|
|
25586
|
+
return spawn5(process.execPath, ["-e", WINDOWS_INSTALLER_GUARDIAN, installerGateNonce], {
|
|
24876
25587
|
cwd: root,
|
|
24877
25588
|
stdio: ["pipe", "inherit", "inherit", "ipc"],
|
|
24878
25589
|
env: sanitizedInstallerEnv(process.env),
|
|
24879
25590
|
windowsHide: true
|
|
24880
25591
|
});
|
|
24881
25592
|
}
|
|
24882
|
-
return
|
|
25593
|
+
return spawn5(installerCommand, installerArgs, {
|
|
24883
25594
|
stdio: "inherit",
|
|
24884
25595
|
env: sanitizedInstallerEnv(process.env),
|
|
24885
25596
|
detached: true
|
|
@@ -24889,7 +25600,7 @@ async function installRelease(version2, options = {}) {
|
|
|
24889
25600
|
try {
|
|
24890
25601
|
child = spawnInstaller(staging, version2);
|
|
24891
25602
|
} catch {
|
|
24892
|
-
await
|
|
25603
|
+
await rm4(staging, { recursive: true, force: true }).catch(() => void 0);
|
|
24893
25604
|
return null;
|
|
24894
25605
|
}
|
|
24895
25606
|
let resolveChildExited;
|
|
@@ -24934,7 +25645,7 @@ async function installRelease(version2, options = {}) {
|
|
|
24934
25645
|
if (cleanupStarted || finished) return;
|
|
24935
25646
|
cleanupStarted = true;
|
|
24936
25647
|
void (async () => {
|
|
24937
|
-
for (let attempt = 1; attempt <=
|
|
25648
|
+
for (let attempt = 1; attempt <= CLEANUP_ATTEMPTS2; attempt++) {
|
|
24938
25649
|
try {
|
|
24939
25650
|
let containment = null;
|
|
24940
25651
|
try {
|
|
@@ -24948,7 +25659,7 @@ async function installRelease(version2, options = {}) {
|
|
|
24948
25659
|
finish(successfulCleanupResult);
|
|
24949
25660
|
return;
|
|
24950
25661
|
} catch (error52) {
|
|
24951
|
-
if (attempt ===
|
|
25662
|
+
if (attempt === CLEANUP_ATTEMPTS2) {
|
|
24952
25663
|
finished = true;
|
|
24953
25664
|
clearTimeout(timer);
|
|
24954
25665
|
options.signal?.removeEventListener("abort", requestCleanup);
|
|
@@ -25026,39 +25737,39 @@ async function installRelease(version2, options = {}) {
|
|
|
25026
25737
|
return null;
|
|
25027
25738
|
}
|
|
25028
25739
|
if (await validReleaseAtPrefix(prefix, version2)) return entry;
|
|
25029
|
-
const existing = await
|
|
25740
|
+
const existing = await lstat5(prefix).catch((error52) => {
|
|
25030
25741
|
if (error52.code === "ENOENT") return null;
|
|
25031
25742
|
throw error52;
|
|
25032
25743
|
});
|
|
25033
|
-
if (existing) await
|
|
25744
|
+
if (existing) await rename3(prefix, quarantine);
|
|
25034
25745
|
try {
|
|
25035
|
-
await
|
|
25746
|
+
await rename3(staging, prefix);
|
|
25036
25747
|
} catch {
|
|
25037
25748
|
return await validReleaseAtPrefix(prefix, version2) ? entry : null;
|
|
25038
25749
|
}
|
|
25039
25750
|
await syncDirectory3(root);
|
|
25040
25751
|
return await validReleaseAtPrefix(prefix, version2) ? entry : null;
|
|
25041
25752
|
} finally {
|
|
25042
|
-
await
|
|
25043
|
-
await
|
|
25753
|
+
await rm4(staging, { recursive: true, force: true }).catch(() => void 0);
|
|
25754
|
+
await rm4(quarantine, { recursive: true, force: true }).catch(() => void 0);
|
|
25044
25755
|
}
|
|
25045
25756
|
}
|
|
25046
25757
|
async function pruneInstalledVersions(keep, root = versionsRoot()) {
|
|
25047
25758
|
const protectedDirs = new Set(keep);
|
|
25048
|
-
const running = process.argv[1] ?
|
|
25759
|
+
const running = process.argv[1] ? resolve5(process.argv[1]) : null;
|
|
25049
25760
|
let entries;
|
|
25050
25761
|
try {
|
|
25051
|
-
entries = await
|
|
25762
|
+
entries = await readdir3(root);
|
|
25052
25763
|
} catch {
|
|
25053
25764
|
return [];
|
|
25054
25765
|
}
|
|
25055
25766
|
const removed = [];
|
|
25056
25767
|
for (const name of entries) {
|
|
25057
25768
|
if (protectedDirs.has(name) || !VERSION_DIR.test(name)) continue;
|
|
25058
|
-
const dir =
|
|
25059
|
-
if (running && running.startsWith(`${dir}${
|
|
25769
|
+
const dir = join7(root, name);
|
|
25770
|
+
if (running && running.startsWith(`${dir}${sep4}`)) continue;
|
|
25060
25771
|
try {
|
|
25061
|
-
await
|
|
25772
|
+
await rm4(dir, { recursive: true, force: true });
|
|
25062
25773
|
removed.push(name);
|
|
25063
25774
|
} catch {
|
|
25064
25775
|
}
|
|
@@ -25069,7 +25780,7 @@ function durableState(phase, candidateVersion, fallbackVersion) {
|
|
|
25069
25780
|
return { schema: 1, phase, candidateVersion, fallbackVersion };
|
|
25070
25781
|
}
|
|
25071
25782
|
async function validInstalledRelease(version2, root = versionsRoot()) {
|
|
25072
|
-
return validReleaseAtPrefix(
|
|
25783
|
+
return validReleaseAtPrefix(join7(root, version2), version2);
|
|
25073
25784
|
}
|
|
25074
25785
|
async function recoverDurableReleaseState(store, runningVersion, activeBootVersion, activate, log2) {
|
|
25075
25786
|
const state = await store.load();
|
|
@@ -25181,14 +25892,14 @@ function defaultSpawn(command, argv, supervisorVersion, watchdog, compatibilityO
|
|
|
25181
25892
|
delete env.ZIXT_HOST_REJECT_VERSION;
|
|
25182
25893
|
if (command.rejectedVersion) env.ZIXT_HOST_REJECT_VERSION = command.rejectedVersion;
|
|
25183
25894
|
const entry = compatibilityProxy ? process.argv[1] ?? "" : command.entry ?? process.argv[1] ?? "";
|
|
25184
|
-
const child =
|
|
25895
|
+
const child = spawn5(process.execPath, [entry, ...argv, ...ownership?.argv ?? []], {
|
|
25185
25896
|
// stdin is a pipe this process owns: closing it is how the worker is
|
|
25186
25897
|
// asked to stop, which works identically on Windows, where there is no
|
|
25187
25898
|
// real SIGINT to send. stdout/stderr stay the person's terminal.
|
|
25188
25899
|
stdio: watchdog ? ["pipe", "inherit", "inherit", "ipc"] : ["pipe", "inherit", "inherit"],
|
|
25189
25900
|
env,
|
|
25190
25901
|
...containmentGateNonce ? {
|
|
25191
|
-
cwd: ownership?.ownershipFile ?
|
|
25902
|
+
cwd: ownership?.ownershipFile ? dirname4(ownership.ownershipFile) : dirname4(entry)
|
|
25192
25903
|
} : {},
|
|
25193
25904
|
// The launch nonce is not a user-facing CLI argument. Keeping it as
|
|
25194
25905
|
// argv[0] gives the stable launcher an exact cross-platform process
|
|
@@ -25217,7 +25928,7 @@ async function runWorkerCompatibilityProxy(env = process.env, argv = process.arg
|
|
|
25217
25928
|
const ownership = consumeWorkerOwnershipArguments(argv, env);
|
|
25218
25929
|
const nonce = env[WORKER_WATCHDOG_NONCE_ENV];
|
|
25219
25930
|
const ownershipFile = env[WORKER_OWNERSHIP_FILE_ENV];
|
|
25220
|
-
if (typeof target !== "string" || !
|
|
25931
|
+
if (typeof target !== "string" || !isAbsolute8(target) || typeof nonce !== "string" || typeof ownershipFile !== "string" || !ownership.requested || !ownership.valid) {
|
|
25221
25932
|
return 1;
|
|
25222
25933
|
}
|
|
25223
25934
|
if (!recordWorkerOwnership(ownershipFile, nonce)) return 1;
|
|
@@ -25232,7 +25943,7 @@ async function runWorkerCompatibilityProxy(env = process.env, argv = process.arg
|
|
|
25232
25943
|
delete workerEnv[WORKER_OWNERSHIP_FILE_ENV];
|
|
25233
25944
|
delete workerEnv[LAUNCHER_OWNERSHIP_DIR_ENV];
|
|
25234
25945
|
delete workerEnv[SUPERVISOR_OWNERSHIP_FILE_ENV];
|
|
25235
|
-
const child =
|
|
25946
|
+
const child = spawn5(process.execPath, [target, ...ownership.argv], {
|
|
25236
25947
|
stdio: ["pipe", "inherit", "inherit"],
|
|
25237
25948
|
env: workerEnv,
|
|
25238
25949
|
// The proxy is already a detached group/session leader on POSIX. The old
|
|
@@ -25288,7 +25999,7 @@ async function launchHostSupervisor(options = {}) {
|
|
|
25288
25999
|
const generationNonce = ownershipDirectory ? basename3(ownershipDirectory) : null;
|
|
25289
26000
|
if (ownershipDirectory && generationNonce) {
|
|
25290
26001
|
env[LAUNCHER_OWNERSHIP_DIR_ENV] = ownershipDirectory;
|
|
25291
|
-
env[SUPERVISOR_OWNERSHIP_FILE_ENV] =
|
|
26002
|
+
env[SUPERVISOR_OWNERSHIP_FILE_ENV] = join7(ownershipDirectory, `${generationNonce}.json`);
|
|
25292
26003
|
} else {
|
|
25293
26004
|
delete env[LAUNCHER_OWNERSHIP_DIR_ENV];
|
|
25294
26005
|
}
|
|
@@ -25296,10 +26007,10 @@ async function launchHostSupervisor(options = {}) {
|
|
|
25296
26007
|
if (!containmentGateNonce) delete env[WINDOWS_POST_CONTAINMENT_CWD_ENV];
|
|
25297
26008
|
if (version2) env[SUPERVISOR_VERSION_ENV] = version2;
|
|
25298
26009
|
else delete env[SUPERVISOR_VERSION_ENV];
|
|
25299
|
-
const child2 =
|
|
26010
|
+
const child2 = spawn5(process.execPath, [supervisorEntry, ...argv], {
|
|
25300
26011
|
stdio: ["pipe", "inherit", "inherit"],
|
|
25301
26012
|
env,
|
|
25302
|
-
...containmentGateNonce ? { cwd: ownershipDirectory ??
|
|
26013
|
+
...containmentGateNonce ? { cwd: ownershipDirectory ?? dirname4(supervisorEntry) } : {},
|
|
25303
26014
|
detached: platform !== "win32",
|
|
25304
26015
|
// The new launcher can discover the durable PID record and still has
|
|
25305
26016
|
// to bind it to this exact process before signalling a recycled PID.
|
|
@@ -25311,7 +26022,8 @@ async function launchHostSupervisor(options = {}) {
|
|
|
25311
26022
|
});
|
|
25312
26023
|
const customDelay = options.delay;
|
|
25313
26024
|
const ownsWorkerBoundary = options.spawnSupervisor === void 0 || options.ownershipRoot !== void 0;
|
|
25314
|
-
const ownershipRoot = options.ownershipRoot ??
|
|
26025
|
+
const ownershipRoot = options.ownershipRoot ?? join7(versionsRoot(), "launcher-ownership");
|
|
26026
|
+
const runRegistryRoot2 = options.runRegistryRoot ?? (options.ownershipRoot ? join7(dirname4(options.ownershipRoot), "run-registry") : defaultRunRegistryRoot());
|
|
25315
26027
|
const terminateRecordedOwnership = options.terminateRecordedOwnership ?? terminateRecordedProcessTree;
|
|
25316
26028
|
const createSupervisorContainment = options.createSupervisorContainment ?? (platform === "win32" && options.spawnSupervisor === void 0 ? async (target, identityNonce, signal) => {
|
|
25317
26029
|
if (!target.pid) throw new Error("supervisor process id is unavailable");
|
|
@@ -25346,14 +26058,17 @@ async function launchHostSupervisor(options = {}) {
|
|
|
25346
26058
|
return !stopping;
|
|
25347
26059
|
};
|
|
25348
26060
|
const cleanupSupervisorBoundary = async (exitedSupervisor, supervisorExited, ownershipDirectory, containment) => {
|
|
25349
|
-
for (let attempt = 1; attempt <=
|
|
26061
|
+
for (let attempt = 1; attempt <= CLEANUP_ATTEMPTS2; attempt++) {
|
|
25350
26062
|
try {
|
|
25351
26063
|
if (containment) await containment.terminate();
|
|
25352
26064
|
else await terminateProcessTree(exitedSupervisor, supervisorExited);
|
|
25353
26065
|
if (ownershipDirectory) await cleanupOwnershipGeneration(ownershipDirectory);
|
|
26066
|
+
if (ownsWorkerBoundary) {
|
|
26067
|
+
await terminateRecordedRunProcesses(runRegistryRoot2, terminateRecordedOwnership);
|
|
26068
|
+
}
|
|
25354
26069
|
return true;
|
|
25355
26070
|
} catch (error52) {
|
|
25356
|
-
const finalAttempt = attempt ===
|
|
26071
|
+
const finalAttempt = attempt === CLEANUP_ATTEMPTS2;
|
|
25357
26072
|
log2(
|
|
25358
26073
|
`Zixt Host launcher: old supervisor cleanup is incomplete (${error52 instanceof Error ? error52.message : "unknown error"}); ${finalAttempt ? "exiting for service-manager recovery" : "retrying"}`
|
|
25359
26074
|
);
|
|
@@ -25436,15 +26151,16 @@ async function launchHostSupervisor(options = {}) {
|
|
|
25436
26151
|
}
|
|
25437
26152
|
const recoverPriorOwnership = async () => {
|
|
25438
26153
|
if (!ownsWorkerBoundary) return true;
|
|
25439
|
-
for (let attempt = 1; attempt <=
|
|
26154
|
+
for (let attempt = 1; attempt <= CLEANUP_ATTEMPTS2; attempt++) {
|
|
25440
26155
|
try {
|
|
25441
26156
|
const generations = await readLauncherOwnershipGenerations(ownershipRoot);
|
|
25442
26157
|
for (const generation of generations) {
|
|
25443
26158
|
await cleanupOwnershipGeneration(generation.directory);
|
|
25444
26159
|
}
|
|
26160
|
+
await terminateRecordedRunProcesses(runRegistryRoot2, terminateRecordedOwnership);
|
|
25445
26161
|
return true;
|
|
25446
26162
|
} catch (error52) {
|
|
25447
|
-
const finalAttempt = attempt ===
|
|
26163
|
+
const finalAttempt = attempt === CLEANUP_ATTEMPTS2;
|
|
25448
26164
|
log2(
|
|
25449
26165
|
`Zixt Host launcher: prior worker cleanup is incomplete (${error52 instanceof Error ? error52.message : "unknown error"}); ${finalAttempt ? "exiting for service-manager recovery" : "retrying"}`
|
|
25450
26166
|
);
|
|
@@ -25574,10 +26290,10 @@ async function superviseHost(options = {}) {
|
|
|
25574
26290
|
const log2 = options.log ?? ((message) => console.error(message));
|
|
25575
26291
|
const signalWorker = options.signalWorker ?? signalWorkerGroup;
|
|
25576
26292
|
const inheritedOwnershipDirectory = process.env[LAUNCHER_OWNERSHIP_DIR_ENV];
|
|
25577
|
-
const launcherOwnershipDirectory = productionLifecycle && typeof inheritedOwnershipDirectory === "string" &&
|
|
26293
|
+
const launcherOwnershipDirectory = productionLifecycle && typeof inheritedOwnershipDirectory === "string" && isAbsolute8(inheritedOwnershipDirectory) ? inheritedOwnershipDirectory : null;
|
|
25578
26294
|
if (productionLifecycle && isSupervisorRole() && launcherOwnershipDirectory) {
|
|
25579
26295
|
const generationNonce = basename3(launcherOwnershipDirectory);
|
|
25580
|
-
const expectedOwnershipFile =
|
|
26296
|
+
const expectedOwnershipFile = join7(launcherOwnershipDirectory, `${generationNonce}.json`);
|
|
25581
26297
|
const configuredOwnershipFile = process.env[SUPERVISOR_OWNERSHIP_FILE_ENV];
|
|
25582
26298
|
if (process.argv0 !== generationNonce || configuredOwnershipFile !== expectedOwnershipFile || !recordWorkerOwnership(expectedOwnershipFile, generationNonce)) {
|
|
25583
26299
|
log2("Zixt Host: supervisor ownership could not be committed; refusing to start a worker");
|
|
@@ -25675,7 +26391,7 @@ async function superviseHost(options = {}) {
|
|
|
25675
26391
|
return waitOrShutdown(backoff);
|
|
25676
26392
|
};
|
|
25677
26393
|
const cleanupExitedWorker = async (exitedChild, childExited, workerBoundary, containment) => {
|
|
25678
|
-
for (let attempt = 1; attempt <=
|
|
26394
|
+
for (let attempt = 1; attempt <= CLEANUP_ATTEMPTS2; attempt++) {
|
|
25679
26395
|
try {
|
|
25680
26396
|
if (containment) await containment.terminate();
|
|
25681
26397
|
else if (cleanupWorker) await cleanupWorker(exitedChild, childExited);
|
|
@@ -25694,7 +26410,7 @@ async function superviseHost(options = {}) {
|
|
|
25694
26410
|
}
|
|
25695
26411
|
return true;
|
|
25696
26412
|
} catch (error52) {
|
|
25697
|
-
const finalAttempt = attempt ===
|
|
26413
|
+
const finalAttempt = attempt === CLEANUP_ATTEMPTS2;
|
|
25698
26414
|
log2(
|
|
25699
26415
|
finalAttempt ? `Zixt Host: exited worker cleanup could not be confirmed (${error52 instanceof Error ? error52.message : "unknown error"}); handing recovery to the stable launcher` : "Zixt Host: exited worker cleanup could not be confirmed; retrying"
|
|
25700
26416
|
);
|
|
@@ -26104,9 +26820,9 @@ import { homedir as homedir12, hostname as hostname3 } from "node:os";
|
|
|
26104
26820
|
// src/hardware.ts
|
|
26105
26821
|
import { existsSync } from "node:fs";
|
|
26106
26822
|
import { statfs } from "node:fs/promises";
|
|
26107
|
-
import { cpus, freemem, homedir as
|
|
26108
|
-
import { dirname as
|
|
26109
|
-
async function machineHardware(workRoot =
|
|
26823
|
+
import { cpus, freemem, homedir as homedir3, totalmem } from "node:os";
|
|
26824
|
+
import { dirname as dirname5, resolve as resolve6 } from "node:path";
|
|
26825
|
+
async function machineHardware(workRoot = homedir3()) {
|
|
26110
26826
|
return {
|
|
26111
26827
|
// A container or cgroup can hide processors from this count; it is what
|
|
26112
26828
|
// this process can see, which is what its Tasks will actually get.
|
|
@@ -26131,10 +26847,10 @@ async function diskSpace(workRoot) {
|
|
|
26131
26847
|
}
|
|
26132
26848
|
}
|
|
26133
26849
|
function nearestExistingPath(start) {
|
|
26134
|
-
let candidate =
|
|
26850
|
+
let candidate = resolve6(start);
|
|
26135
26851
|
for (let depth = 0; depth < 16; depth++) {
|
|
26136
26852
|
if (existsSync(candidate)) return candidate;
|
|
26137
|
-
const parent =
|
|
26853
|
+
const parent = dirname5(candidate);
|
|
26138
26854
|
if (parent === candidate) return null;
|
|
26139
26855
|
candidate = parent;
|
|
26140
26856
|
}
|
|
@@ -26328,17 +27044,17 @@ function createDemoBrowserAdapterFactory() {
|
|
|
26328
27044
|
}
|
|
26329
27045
|
|
|
26330
27046
|
// src/browser/manager.ts
|
|
26331
|
-
import { lstat as
|
|
26332
|
-
import { homedir as
|
|
26333
|
-
import { dirname as
|
|
26334
|
-
var
|
|
27047
|
+
import { lstat as lstat6, mkdir as mkdir5, open as open5, opendir, readFile as readFile7, rename as rename4, rm as rm5 } from "node:fs/promises";
|
|
27048
|
+
import { homedir as homedir4 } from "node:os";
|
|
27049
|
+
import { dirname as dirname6, join as join8, resolve as resolve7 } from "node:path";
|
|
27050
|
+
var SAFE_SEGMENT2 = /^[A-Za-z0-9_-]{1,200}$/;
|
|
26335
27051
|
var FRAME_MIN_INTERVAL_MS = 100;
|
|
26336
27052
|
var IDLE_TIMEOUT_MS = 15 * 6e4;
|
|
26337
27053
|
var BrowserManager = class {
|
|
26338
27054
|
constructor(opts) {
|
|
26339
27055
|
this.opts = opts;
|
|
26340
|
-
this.profileRoot = opts.profileRoot ??
|
|
26341
|
-
this.profileStateRoot =
|
|
27056
|
+
this.profileRoot = opts.profileRoot ?? join8(homedir4(), ".zixt", "browser-profiles");
|
|
27057
|
+
this.profileStateRoot = join8(this.profileRoot, ".profile-state");
|
|
26342
27058
|
this.viewport = opts.viewport ?? BROWSER_DEFAULT_VIEWPORT;
|
|
26343
27059
|
this.idleTimeoutMs = opts.idleTimeoutMs ?? IDLE_TIMEOUT_MS;
|
|
26344
27060
|
this.frameMinIntervalMs = opts.frameMinIntervalMs ?? FRAME_MIN_INTERVAL_MS;
|
|
@@ -26423,21 +27139,21 @@ var BrowserManager = class {
|
|
|
26423
27139
|
return next;
|
|
26424
27140
|
}
|
|
26425
27141
|
validateAgentId(agentId) {
|
|
26426
|
-
if (!
|
|
27142
|
+
if (!SAFE_SEGMENT2.test(agentId) || !AgentId.safeParse(agentId).success) {
|
|
26427
27143
|
throw new Error("invalid agent id for a browser profile path");
|
|
26428
27144
|
}
|
|
26429
27145
|
}
|
|
26430
27146
|
exactChild(root, child) {
|
|
26431
|
-
const canonicalRoot =
|
|
26432
|
-
const target =
|
|
26433
|
-
if (
|
|
27147
|
+
const canonicalRoot = resolve7(root);
|
|
27148
|
+
const target = resolve7(canonicalRoot, child);
|
|
27149
|
+
if (dirname6(target) !== canonicalRoot) {
|
|
26434
27150
|
throw new Error("browser profile path escaped its owned root");
|
|
26435
27151
|
}
|
|
26436
27152
|
return target;
|
|
26437
27153
|
}
|
|
26438
27154
|
async ensureOwnedDirectory(path) {
|
|
26439
|
-
await
|
|
26440
|
-
const stat3 = await
|
|
27155
|
+
await mkdir5(path, { recursive: true, mode: 448 });
|
|
27156
|
+
const stat3 = await lstat6(path);
|
|
26441
27157
|
if (!stat3.isDirectory() || stat3.isSymbolicLink()) {
|
|
26442
27158
|
throw new Error("browser profile root must be an owned directory, not a symbolic link");
|
|
26443
27159
|
}
|
|
@@ -26450,7 +27166,7 @@ var BrowserManager = class {
|
|
|
26450
27166
|
*/
|
|
26451
27167
|
async syncDirectory(path) {
|
|
26452
27168
|
try {
|
|
26453
|
-
const directory = await
|
|
27169
|
+
const directory = await open5(path, "r");
|
|
26454
27170
|
try {
|
|
26455
27171
|
await directory.sync();
|
|
26456
27172
|
} finally {
|
|
@@ -26472,7 +27188,7 @@ var BrowserManager = class {
|
|
|
26472
27188
|
}
|
|
26473
27189
|
async readProfileState(agentId) {
|
|
26474
27190
|
try {
|
|
26475
|
-
const raw = JSON.parse(await
|
|
27191
|
+
const raw = JSON.parse(await readFile7(this.statePath(agentId), "utf8"));
|
|
26476
27192
|
if (typeof raw !== "object" || raw === null || !Number.isInteger(raw.revision) || Number(raw.revision) < 0 || !["allowed", "purged"].includes(String(raw.state))) {
|
|
26477
27193
|
throw new Error("browser profile lifecycle marker is invalid");
|
|
26478
27194
|
}
|
|
@@ -26491,18 +27207,18 @@ var BrowserManager = class {
|
|
|
26491
27207
|
`${agentId}.${crypto.randomUUID()}.tmp`
|
|
26492
27208
|
);
|
|
26493
27209
|
try {
|
|
26494
|
-
const marker = await
|
|
27210
|
+
const marker = await open5(temporary, "wx+", 384);
|
|
26495
27211
|
try {
|
|
26496
27212
|
await marker.writeFile(JSON.stringify(state), "utf8");
|
|
26497
27213
|
await marker.sync();
|
|
26498
27214
|
} finally {
|
|
26499
27215
|
await marker.close();
|
|
26500
27216
|
}
|
|
26501
|
-
await
|
|
27217
|
+
await rename4(temporary, destination);
|
|
26502
27218
|
await this.syncDirectory(this.profileStateRoot);
|
|
26503
27219
|
await this.syncDirectory(this.profileRoot);
|
|
26504
27220
|
} catch (error52) {
|
|
26505
|
-
await
|
|
27221
|
+
await rm5(temporary, { force: true }).catch(() => {
|
|
26506
27222
|
});
|
|
26507
27223
|
throw error52;
|
|
26508
27224
|
}
|
|
@@ -26571,14 +27287,14 @@ var BrowserManager = class {
|
|
|
26571
27287
|
await this.ensureOwnedDirectory(this.profileRoot);
|
|
26572
27288
|
const profileDir = this.profilePath(agentId);
|
|
26573
27289
|
try {
|
|
26574
|
-
const existingProfile = await
|
|
27290
|
+
const existingProfile = await lstat6(profileDir);
|
|
26575
27291
|
if (existingProfile.isSymbolicLink() || !existingProfile.isDirectory()) {
|
|
26576
27292
|
throw new Error("browser profile path is not an owned directory");
|
|
26577
27293
|
}
|
|
26578
27294
|
} catch (error52) {
|
|
26579
27295
|
if (error52.code !== "ENOENT") throw error52;
|
|
26580
27296
|
}
|
|
26581
|
-
await
|
|
27297
|
+
await mkdir5(profileDir, { recursive: true, mode: 448 });
|
|
26582
27298
|
const adapter = await this.opts.factory.open({
|
|
26583
27299
|
agentId,
|
|
26584
27300
|
profileDir,
|
|
@@ -26657,7 +27373,7 @@ var BrowserManager = class {
|
|
|
26657
27373
|
purgeId
|
|
26658
27374
|
});
|
|
26659
27375
|
await this.closeLocked(agentId, "stopped");
|
|
26660
|
-
await
|
|
27376
|
+
await rm5(this.profilePath(agentId), { recursive: true, force: true, maxRetries: 3 });
|
|
26661
27377
|
await this.syncDirectory(this.profileRoot);
|
|
26662
27378
|
});
|
|
26663
27379
|
}
|
|
@@ -30073,18 +30789,18 @@ function createGithubPushOrchestrator(input) {
|
|
|
30073
30789
|
}
|
|
30074
30790
|
|
|
30075
30791
|
// src/tool-packs/github/git-bridge.ts
|
|
30076
|
-
import { spawn as
|
|
30792
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
30077
30793
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
30078
|
-
import { chmod as
|
|
30079
|
-
import { dirname as
|
|
30794
|
+
import { chmod as chmod4, lstat as lstat8, mkdir as mkdir6, realpath as realpath5, rm as rm6 } from "node:fs/promises";
|
|
30795
|
+
import { dirname as dirname7, isAbsolute as isAbsolute10, join as join10, relative as relative6 } from "node:path";
|
|
30080
30796
|
|
|
30081
30797
|
// src/tool-packs/github/git-credential-broker.ts
|
|
30082
30798
|
import { createServer } from "node:http";
|
|
30083
30799
|
import { randomBytes, randomUUID as randomUUID6, timingSafeEqual } from "node:crypto";
|
|
30084
|
-
import { chmod as
|
|
30085
|
-
import { isAbsolute as
|
|
30800
|
+
import { chmod as chmod3, lstat as lstat7, realpath as realpath4, writeFile as writeFile2 } from "node:fs/promises";
|
|
30801
|
+
import { isAbsolute as isAbsolute9, join as join9, relative as relative5 } from "node:path";
|
|
30086
30802
|
var MAX_REQUEST_BYTES = 16 * 1024;
|
|
30087
|
-
var
|
|
30803
|
+
var FILE_MODE2 = 384;
|
|
30088
30804
|
var HELPER_SOURCE = String.raw`'use strict';
|
|
30089
30805
|
const http = require('node:http');
|
|
30090
30806
|
|
|
@@ -30195,8 +30911,8 @@ async function readBoundedBody2(request) {
|
|
|
30195
30911
|
return Buffer.concat(chunks, size);
|
|
30196
30912
|
}
|
|
30197
30913
|
function assertChildPath(parent, child) {
|
|
30198
|
-
const path =
|
|
30199
|
-
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") ||
|
|
30914
|
+
const path = relative5(parent, child);
|
|
30915
|
+
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") || isAbsolute9(path)) {
|
|
30200
30916
|
throw new Error("Git credential helper path escaped its private run directory");
|
|
30201
30917
|
}
|
|
30202
30918
|
}
|
|
@@ -30206,15 +30922,15 @@ async function createGithubGitCredentialBroker(input) {
|
|
|
30206
30922
|
throw new Error("GitHub credential authority has expired");
|
|
30207
30923
|
}
|
|
30208
30924
|
assertRepositoryFullName(input.repositoryFullName);
|
|
30209
|
-
const rootEntry = await
|
|
30925
|
+
const rootEntry = await lstat7(input.runArtifactsRoot);
|
|
30210
30926
|
if (!rootEntry.isDirectory() || rootEntry.isSymbolicLink()) {
|
|
30211
30927
|
throw new Error("Git credential broker requires a private real run directory");
|
|
30212
30928
|
}
|
|
30213
|
-
const runRoot = await
|
|
30214
|
-
const helperPath =
|
|
30929
|
+
const runRoot = await realpath4(input.runArtifactsRoot);
|
|
30930
|
+
const helperPath = join9(runRoot, `git-credential-${randomUUID6()}.cjs`);
|
|
30215
30931
|
assertChildPath(runRoot, helperPath);
|
|
30216
|
-
await
|
|
30217
|
-
await
|
|
30932
|
+
await writeFile2(helperPath, HELPER_SOURCE, { flag: "wx", mode: FILE_MODE2 });
|
|
30933
|
+
await chmod3(helperPath, FILE_MODE2);
|
|
30218
30934
|
const capability2 = randomBytes(32).toString("base64url");
|
|
30219
30935
|
const expectedPath = `${input.repositoryFullName}.git`;
|
|
30220
30936
|
let closed = false;
|
|
@@ -30285,7 +31001,7 @@ password=${input.accessToken}
|
|
|
30285
31001
|
}
|
|
30286
31002
|
|
|
30287
31003
|
// src/tool-packs/github/git-bridge.ts
|
|
30288
|
-
var
|
|
31004
|
+
var DIRECTORY_MODE2 = 448;
|
|
30289
31005
|
var MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
|
|
30290
31006
|
var DEFAULT_TIMEOUT_MS2 = 12e4;
|
|
30291
31007
|
var SHA = /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/;
|
|
@@ -30309,27 +31025,27 @@ ${stderr}`;
|
|
|
30309
31025
|
);
|
|
30310
31026
|
return explicitGithubRefusal ? "provider_rejected" : "command_failed";
|
|
30311
31027
|
}
|
|
30312
|
-
function
|
|
30313
|
-
const path =
|
|
30314
|
-
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") ||
|
|
31028
|
+
function assertBelow2(parent, child, label) {
|
|
31029
|
+
const path = relative6(parent, child);
|
|
31030
|
+
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") || isAbsolute10(path)) {
|
|
30315
31031
|
throw new GithubGitProcessError("invalid_input");
|
|
30316
31032
|
}
|
|
30317
31033
|
void label;
|
|
30318
31034
|
}
|
|
30319
|
-
async function
|
|
30320
|
-
const entry = await
|
|
31035
|
+
async function requireRealDirectory2(path, label) {
|
|
31036
|
+
const entry = await lstat8(path).catch(() => null);
|
|
30321
31037
|
if (!entry || !entry.isDirectory() || entry.isSymbolicLink()) {
|
|
30322
31038
|
void label;
|
|
30323
31039
|
throw new GithubGitProcessError("invalid_input");
|
|
30324
31040
|
}
|
|
30325
|
-
return
|
|
31041
|
+
return realpath5(path);
|
|
30326
31042
|
}
|
|
30327
31043
|
async function validateTokenlessPaths(command) {
|
|
30328
31044
|
if (command.kind === "clone-from-bridge") {
|
|
30329
|
-
if (!
|
|
30330
|
-
const parent = await
|
|
30331
|
-
|
|
30332
|
-
const destination = await
|
|
31045
|
+
if (!isAbsolute10(command.destination)) throw new GithubGitProcessError("invalid_input");
|
|
31046
|
+
const parent = await requireRealDirectory2(dirname7(command.destination), "clone parent");
|
|
31047
|
+
assertBelow2(parent, command.destination, "clone destination");
|
|
31048
|
+
const destination = await lstat8(command.destination).catch((error52) => {
|
|
30333
31049
|
if (error52.code === "ENOENT") return null;
|
|
30334
31050
|
throw error52;
|
|
30335
31051
|
});
|
|
@@ -30337,8 +31053,8 @@ async function validateTokenlessPaths(command) {
|
|
|
30337
31053
|
return;
|
|
30338
31054
|
}
|
|
30339
31055
|
if ("repositoryPath" in command) {
|
|
30340
|
-
if (!
|
|
30341
|
-
const repositoryPath5 = await
|
|
31056
|
+
if (!isAbsolute10(command.repositoryPath)) throw new GithubGitProcessError("invalid_input");
|
|
31057
|
+
const repositoryPath5 = await requireRealDirectory2(command.repositoryPath, "repository path");
|
|
30342
31058
|
if (repositoryPath5 !== command.repositoryPath) throw new GithubGitProcessError("invalid_input");
|
|
30343
31059
|
}
|
|
30344
31060
|
}
|
|
@@ -30417,13 +31133,13 @@ async function runGit(input, args, env) {
|
|
|
30417
31133
|
if (input.authoritySignal.aborted || input.cancelledNow()) {
|
|
30418
31134
|
throw new GithubGitProcessError("cancelled");
|
|
30419
31135
|
}
|
|
30420
|
-
if (!
|
|
31136
|
+
if (!isAbsolute10(input.executablePath)) throw new GithubGitProcessError("invalid_input");
|
|
30421
31137
|
const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
30422
31138
|
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > DEFAULT_TIMEOUT_MS2) {
|
|
30423
31139
|
throw new GithubGitProcessError("invalid_input");
|
|
30424
31140
|
}
|
|
30425
31141
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
30426
|
-
const child =
|
|
31142
|
+
const child = spawn6(input.executablePath, [...input.commandPrefixArgs ?? [], ...args], {
|
|
30427
31143
|
cwd: input.trustedCwd,
|
|
30428
31144
|
env,
|
|
30429
31145
|
shell: false,
|
|
@@ -30523,7 +31239,7 @@ function tokenlessArgs(command) {
|
|
|
30523
31239
|
switch (command.kind) {
|
|
30524
31240
|
case "clone-from-bridge":
|
|
30525
31241
|
assertRef(command.branch);
|
|
30526
|
-
if (!
|
|
31242
|
+
if (!isAbsolute10(command.destination)) throw new GithubGitProcessError("invalid_input");
|
|
30527
31243
|
return [
|
|
30528
31244
|
"clone",
|
|
30529
31245
|
"--no-recurse-submodules",
|
|
@@ -30535,7 +31251,7 @@ function tokenlessArgs(command) {
|
|
|
30535
31251
|
];
|
|
30536
31252
|
case "fetch-from-bridge":
|
|
30537
31253
|
assertFetchRefspecs(command.refspecs);
|
|
30538
|
-
if (!
|
|
31254
|
+
if (!isAbsolute10(command.repositoryPath)) throw new GithubGitProcessError("invalid_input");
|
|
30539
31255
|
return [
|
|
30540
31256
|
"-C",
|
|
30541
31257
|
command.repositoryPath,
|
|
@@ -30547,7 +31263,7 @@ function tokenlessArgs(command) {
|
|
|
30547
31263
|
...command.refspecs
|
|
30548
31264
|
];
|
|
30549
31265
|
case "copy-commit-to-bridge":
|
|
30550
|
-
if (!
|
|
31266
|
+
if (!isAbsolute10(command.repositoryPath) || !SHA.test(command.sha)) {
|
|
30551
31267
|
throw new GithubGitProcessError("invalid_input");
|
|
30552
31268
|
}
|
|
30553
31269
|
return [
|
|
@@ -30559,11 +31275,11 @@ function tokenlessArgs(command) {
|
|
|
30559
31275
|
`${command.sha}:refs/zixt/push-source`
|
|
30560
31276
|
];
|
|
30561
31277
|
case "rev-parse":
|
|
30562
|
-
if (!
|
|
31278
|
+
if (!isAbsolute10(command.repositoryPath)) throw new GithubGitProcessError("invalid_input");
|
|
30563
31279
|
assertRef(command.ref);
|
|
30564
31280
|
return ["-C", command.repositoryPath, "rev-parse", "--verify", `${command.ref}^{commit}`];
|
|
30565
31281
|
case "remote-configure":
|
|
30566
|
-
if (!
|
|
31282
|
+
if (!isAbsolute10(command.repositoryPath) || !REPOSITORY.test(command.repositoryFullName)) {
|
|
30567
31283
|
throw new GithubGitProcessError("invalid_input");
|
|
30568
31284
|
}
|
|
30569
31285
|
return [
|
|
@@ -30575,7 +31291,7 @@ function tokenlessArgs(command) {
|
|
|
30575
31291
|
`https://github.com/${command.repositoryFullName}.git`
|
|
30576
31292
|
];
|
|
30577
31293
|
case "status":
|
|
30578
|
-
if (!
|
|
31294
|
+
if (!isAbsolute10(command.repositoryPath)) throw new GithubGitProcessError("invalid_input");
|
|
30579
31295
|
return [
|
|
30580
31296
|
"-C",
|
|
30581
31297
|
command.repositoryPath,
|
|
@@ -30593,21 +31309,21 @@ function createGithubGitBridge(input) {
|
|
|
30593
31309
|
let closed = false;
|
|
30594
31310
|
let rootsPromise;
|
|
30595
31311
|
const roots = () => rootsPromise ??= (async () => {
|
|
30596
|
-
const trustedCwd = await
|
|
30597
|
-
const runRoot = await
|
|
30598
|
-
const bridges = await
|
|
31312
|
+
const trustedCwd = await requireRealDirectory2(input.trustedCwd, "trusted cwd");
|
|
31313
|
+
const runRoot = await requireRealDirectory2(input.artifacts.runRoot, "run root");
|
|
31314
|
+
const bridges = await requireRealDirectory2(
|
|
30599
31315
|
input.artifacts.gitBridgesDirectory,
|
|
30600
31316
|
"git bridge root"
|
|
30601
31317
|
);
|
|
30602
|
-
|
|
31318
|
+
assertBelow2(runRoot, bridges, "git bridge root");
|
|
30603
31319
|
if (trustedCwd !== runRoot) throw new GithubGitProcessError("invalid_input");
|
|
30604
31320
|
return { trustedCwd, runRoot, bridges };
|
|
30605
31321
|
})();
|
|
30606
31322
|
const requireBridge = async (value) => {
|
|
30607
31323
|
const current = await roots();
|
|
30608
|
-
if (!
|
|
30609
|
-
const real = await
|
|
30610
|
-
|
|
31324
|
+
if (!isAbsolute10(value) || !active.has(value)) throw new GithubGitProcessError("invalid_input");
|
|
31325
|
+
const real = await requireRealDirectory2(value, "git bridge");
|
|
31326
|
+
assertBelow2(current.bridges, real, "git bridge");
|
|
30611
31327
|
if (real !== value) throw new GithubGitProcessError("invalid_input");
|
|
30612
31328
|
return real;
|
|
30613
31329
|
};
|
|
@@ -30615,10 +31331,10 @@ function createGithubGitBridge(input) {
|
|
|
30615
31331
|
async createPrivateBridge() {
|
|
30616
31332
|
if (closed) throw new GithubGitProcessError("cancelled");
|
|
30617
31333
|
const current = await roots();
|
|
30618
|
-
const path =
|
|
30619
|
-
|
|
30620
|
-
await
|
|
30621
|
-
await
|
|
31334
|
+
const path = join10(current.bridges, `${randomUUID7()}.git`);
|
|
31335
|
+
assertBelow2(current.bridges, path, "git bridge");
|
|
31336
|
+
await mkdir6(path, { mode: DIRECTORY_MODE2 });
|
|
31337
|
+
await chmod4(path, DIRECTORY_MODE2);
|
|
30622
31338
|
try {
|
|
30623
31339
|
await runGit(
|
|
30624
31340
|
{ ...input, trustedCwd: current.trustedCwd },
|
|
@@ -30631,18 +31347,18 @@ function createGithubGitBridge(input) {
|
|
|
30631
31347
|
],
|
|
30632
31348
|
minimalEnvironment(input.artifacts)
|
|
30633
31349
|
);
|
|
30634
|
-
const real = await
|
|
30635
|
-
|
|
30636
|
-
const hooks =
|
|
30637
|
-
await
|
|
30638
|
-
await
|
|
30639
|
-
await
|
|
30640
|
-
const config2 =
|
|
30641
|
-
await
|
|
31350
|
+
const real = await requireRealDirectory2(path, "git bridge");
|
|
31351
|
+
assertBelow2(current.bridges, real, "git bridge");
|
|
31352
|
+
const hooks = join10(real, "hooks");
|
|
31353
|
+
await rm6(hooks, { recursive: true, force: true });
|
|
31354
|
+
await mkdir6(hooks, { mode: DIRECTORY_MODE2 });
|
|
31355
|
+
await chmod4(hooks, DIRECTORY_MODE2);
|
|
31356
|
+
const config2 = join10(real, "config");
|
|
31357
|
+
await chmod4(config2, 384);
|
|
30642
31358
|
active.add(real);
|
|
30643
31359
|
return real;
|
|
30644
31360
|
} catch (error52) {
|
|
30645
|
-
await
|
|
31361
|
+
await rm6(path, { recursive: true, force: true }).catch(() => {
|
|
30646
31362
|
});
|
|
30647
31363
|
throw error52;
|
|
30648
31364
|
}
|
|
@@ -30736,7 +31452,7 @@ function createGithubGitBridge(input) {
|
|
|
30736
31452
|
},
|
|
30737
31453
|
async destroyPrivateBridge(path) {
|
|
30738
31454
|
const bridge = await requireBridge(path);
|
|
30739
|
-
await
|
|
31455
|
+
await rm6(bridge, { recursive: true, force: true });
|
|
30740
31456
|
active.delete(bridge);
|
|
30741
31457
|
credentialed2.delete(bridge);
|
|
30742
31458
|
},
|
|
@@ -30744,7 +31460,7 @@ function createGithubGitBridge(input) {
|
|
|
30744
31460
|
if (closed) return;
|
|
30745
31461
|
closed = true;
|
|
30746
31462
|
const paths = [...active];
|
|
30747
|
-
await Promise.all(paths.map((path) =>
|
|
31463
|
+
await Promise.all(paths.map((path) => rm6(path, { recursive: true, force: true })));
|
|
30748
31464
|
active.clear();
|
|
30749
31465
|
credentialed2.clear();
|
|
30750
31466
|
}
|
|
@@ -31085,12 +31801,12 @@ function createRepositoryTools(runtime) {
|
|
|
31085
31801
|
|
|
31086
31802
|
// src/tool-packs/github/workspace.ts
|
|
31087
31803
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
31088
|
-
import { chmod as
|
|
31089
|
-
import { isAbsolute as
|
|
31804
|
+
import { chmod as chmod5, lstat as lstat9, mkdir as mkdir7, readFile as readFile8, realpath as realpath6, rename as rename5, rm as rm7, writeFile as writeFile3 } from "node:fs/promises";
|
|
31805
|
+
import { isAbsolute as isAbsolute11, join as join11, relative as relative7, resolve as resolve8 } from "node:path";
|
|
31090
31806
|
var SAFE_LOCAL_ID = /^[A-Za-z0-9_-]{1,200}$/;
|
|
31091
31807
|
var FULL_NAME2 = /^[A-Za-z0-9_.-]{1,100}\/[A-Za-z0-9_.-]{1,100}$/;
|
|
31092
|
-
var
|
|
31093
|
-
var
|
|
31808
|
+
var DIRECTORY_MODE3 = 448;
|
|
31809
|
+
var FILE_MODE3 = 384;
|
|
31094
31810
|
var METADATA_VERSION = 1;
|
|
31095
31811
|
var workspaceLocks = /* @__PURE__ */ new Map();
|
|
31096
31812
|
function assertSafeLocalId(value, label) {
|
|
@@ -31102,37 +31818,37 @@ function hasControlCharacter2(value) {
|
|
|
31102
31818
|
return codePoint <= 31 || codePoint === 127;
|
|
31103
31819
|
});
|
|
31104
31820
|
}
|
|
31105
|
-
function
|
|
31106
|
-
const path =
|
|
31107
|
-
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") ||
|
|
31821
|
+
function assertBelow3(parent, child, label) {
|
|
31822
|
+
const path = relative7(parent, child);
|
|
31823
|
+
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") || isAbsolute11(path)) {
|
|
31108
31824
|
throw new Error(`${label} escaped the task workspace`);
|
|
31109
31825
|
}
|
|
31110
31826
|
}
|
|
31111
31827
|
function samePath(left, right) {
|
|
31112
|
-
return process.platform === "win32" ?
|
|
31828
|
+
return process.platform === "win32" ? resolve8(left).toLowerCase() === resolve8(right).toLowerCase() : resolve8(left) === resolve8(right);
|
|
31113
31829
|
}
|
|
31114
|
-
async function
|
|
31115
|
-
const entry = await
|
|
31830
|
+
async function requireRealDirectory3(path, label) {
|
|
31831
|
+
const entry = await lstat9(path).catch(() => null);
|
|
31116
31832
|
if (!entry || !entry.isDirectory() || entry.isSymbolicLink()) {
|
|
31117
31833
|
throw new Error(`${label} must be a real directory, not a symbolic link or junction`);
|
|
31118
31834
|
}
|
|
31119
|
-
const real = await
|
|
31835
|
+
const real = await realpath6(path);
|
|
31120
31836
|
if (!samePath(real, path)) {
|
|
31121
31837
|
throw new Error(`${label} must not traverse a symbolic link or junction`);
|
|
31122
31838
|
}
|
|
31123
31839
|
return real;
|
|
31124
31840
|
}
|
|
31125
31841
|
async function createOrRequirePrivateDirectory(parent, name, label) {
|
|
31126
|
-
const path =
|
|
31127
|
-
|
|
31842
|
+
const path = join11(parent, name);
|
|
31843
|
+
assertBelow3(parent, path, label);
|
|
31128
31844
|
try {
|
|
31129
|
-
await
|
|
31845
|
+
await mkdir7(path, { mode: DIRECTORY_MODE3 });
|
|
31130
31846
|
} catch (error52) {
|
|
31131
31847
|
if (error52.code !== "EEXIST") throw error52;
|
|
31132
31848
|
}
|
|
31133
|
-
const real = await
|
|
31134
|
-
|
|
31135
|
-
await
|
|
31849
|
+
const real = await requireRealDirectory3(path, label);
|
|
31850
|
+
assertBelow3(parent, real, label);
|
|
31851
|
+
await chmod5(real, DIRECTORY_MODE3);
|
|
31136
31852
|
return real;
|
|
31137
31853
|
}
|
|
31138
31854
|
function parseMetadata(text) {
|
|
@@ -31149,7 +31865,7 @@ function parseMetadata(text) {
|
|
|
31149
31865
|
}
|
|
31150
31866
|
async function pathExists(path) {
|
|
31151
31867
|
try {
|
|
31152
|
-
await
|
|
31868
|
+
await lstat9(path);
|
|
31153
31869
|
return true;
|
|
31154
31870
|
} catch (error52) {
|
|
31155
31871
|
if (error52.code === "ENOENT") return false;
|
|
@@ -31176,7 +31892,7 @@ async function createGithubWorkspaceService(input) {
|
|
|
31176
31892
|
const grant = validateGithubTaskGrant(input.grant);
|
|
31177
31893
|
assertSafeLocalId(input.context.agentId, "agent ID");
|
|
31178
31894
|
assertSafeLocalId(input.context.taskId, "task ID");
|
|
31179
|
-
const taskRoot = await
|
|
31895
|
+
const taskRoot = await requireRealDirectory3(input.context.taskRoot, "task workspace");
|
|
31180
31896
|
const repositoriesRoot = await createOrRequirePrivateDirectory(
|
|
31181
31897
|
taskRoot,
|
|
31182
31898
|
"repositories",
|
|
@@ -31206,33 +31922,33 @@ async function createGithubWorkspaceService(input) {
|
|
|
31206
31922
|
if (expectedFullName !== void 0 && repository.fullName !== expectedFullName) {
|
|
31207
31923
|
throw new Error("GitHub repository name does not match this task grant");
|
|
31208
31924
|
}
|
|
31209
|
-
const destination =
|
|
31210
|
-
const metadataPath =
|
|
31925
|
+
const destination = join11(repositoriesRoot, parsed.data);
|
|
31926
|
+
const metadataPath = join11(metadataRoot, `${parsed.data}.json`);
|
|
31211
31927
|
if (!await pathExists(destination) || !await pathExists(metadataPath)) {
|
|
31212
31928
|
throw new Error("GitHub repository workspace has not been prepared");
|
|
31213
31929
|
}
|
|
31214
|
-
const metadataEntry = await
|
|
31930
|
+
const metadataEntry = await lstat9(metadataPath);
|
|
31215
31931
|
if (!metadataEntry.isFile() || metadataEntry.isSymbolicLink()) {
|
|
31216
31932
|
throw new Error("GitHub workspace metadata is invalid");
|
|
31217
31933
|
}
|
|
31218
|
-
const metadata = parseMetadata(await
|
|
31934
|
+
const metadata = parseMetadata(await readFile8(metadataPath, "utf8"));
|
|
31219
31935
|
if (metadata.repositoryId !== parsed.data || metadata.fullName !== repository.fullName || !samePath(metadata.path, destination)) {
|
|
31220
31936
|
throw new Error("GitHub workspace metadata does not match this repository");
|
|
31221
31937
|
}
|
|
31222
|
-
const real = await
|
|
31223
|
-
|
|
31938
|
+
const real = await requireRealDirectory3(destination, "GitHub repository");
|
|
31939
|
+
assertBelow3(repositoriesRoot, real, "repository path");
|
|
31224
31940
|
return real;
|
|
31225
31941
|
};
|
|
31226
31942
|
const cloneRepository = async (clone2) => {
|
|
31227
|
-
const destination =
|
|
31228
|
-
const metadataPath =
|
|
31229
|
-
|
|
31230
|
-
|
|
31943
|
+
const destination = join11(repositoriesRoot, clone2.repositoryId);
|
|
31944
|
+
const metadataPath = join11(metadataRoot, `${clone2.repositoryId}.json`);
|
|
31945
|
+
assertBelow3(repositoriesRoot, destination, "repository path");
|
|
31946
|
+
assertBelow3(metadataRoot, metadataPath, "repository metadata");
|
|
31231
31947
|
if (await pathExists(destination) || await pathExists(metadataPath)) {
|
|
31232
31948
|
throw new Error("GitHub repository workspace already exists or is inconsistent");
|
|
31233
31949
|
}
|
|
31234
|
-
const temporary =
|
|
31235
|
-
|
|
31950
|
+
const temporary = join11(repositoriesRoot, `.clone-${randomUUID8()}`);
|
|
31951
|
+
assertBelow3(repositoriesRoot, temporary, "temporary clone");
|
|
31236
31952
|
try {
|
|
31237
31953
|
await input.git.clone({
|
|
31238
31954
|
accessToken: clone2.accessToken,
|
|
@@ -31241,8 +31957,8 @@ async function createGithubWorkspaceService(input) {
|
|
|
31241
31957
|
destination: temporary,
|
|
31242
31958
|
branch: clone2.defaultBranch
|
|
31243
31959
|
});
|
|
31244
|
-
const temporaryReal = await
|
|
31245
|
-
|
|
31960
|
+
const temporaryReal = await requireRealDirectory3(temporary, "temporary GitHub clone");
|
|
31961
|
+
assertBelow3(repositoriesRoot, temporaryReal, "temporary clone");
|
|
31246
31962
|
await input.git.configureOrigin({
|
|
31247
31963
|
repositoryPath: temporaryReal,
|
|
31248
31964
|
repositoryFullName: clone2.fullName
|
|
@@ -31256,27 +31972,27 @@ async function createGithubWorkspaceService(input) {
|
|
|
31256
31972
|
path: destination,
|
|
31257
31973
|
...clone2.createIntentId === void 0 ? {} : { createIntentId: clone2.createIntentId }
|
|
31258
31974
|
};
|
|
31259
|
-
const metadataTemporary =
|
|
31260
|
-
|
|
31261
|
-
await
|
|
31975
|
+
const metadataTemporary = join11(metadataRoot, `.${clone2.repositoryId}-${randomUUID8()}.tmp`);
|
|
31976
|
+
assertBelow3(metadataRoot, metadataTemporary, "temporary repository metadata");
|
|
31977
|
+
await writeFile3(metadataTemporary, `${JSON.stringify(metadata)}
|
|
31262
31978
|
`, {
|
|
31263
31979
|
flag: "wx",
|
|
31264
|
-
mode:
|
|
31980
|
+
mode: FILE_MODE3
|
|
31265
31981
|
});
|
|
31266
|
-
await
|
|
31982
|
+
await chmod5(metadataTemporary, FILE_MODE3);
|
|
31267
31983
|
try {
|
|
31268
|
-
await
|
|
31984
|
+
await rename5(temporaryReal, destination);
|
|
31269
31985
|
try {
|
|
31270
|
-
await
|
|
31986
|
+
await rename5(metadataTemporary, metadataPath);
|
|
31271
31987
|
} catch (error52) {
|
|
31272
|
-
await
|
|
31988
|
+
await rm7(destination, { recursive: true, force: true });
|
|
31273
31989
|
throw error52;
|
|
31274
31990
|
}
|
|
31275
31991
|
} finally {
|
|
31276
|
-
await
|
|
31992
|
+
await rm7(metadataTemporary, { force: true }).catch(() => {
|
|
31277
31993
|
});
|
|
31278
31994
|
}
|
|
31279
|
-
const path = await
|
|
31995
|
+
const path = await requireRealDirectory3(destination, "GitHub repository");
|
|
31280
31996
|
return {
|
|
31281
31997
|
provider: "github",
|
|
31282
31998
|
repositoryId: clone2.repositoryId,
|
|
@@ -31285,16 +32001,16 @@ async function createGithubWorkspaceService(input) {
|
|
|
31285
32001
|
headSha
|
|
31286
32002
|
};
|
|
31287
32003
|
} finally {
|
|
31288
|
-
await
|
|
32004
|
+
await rm7(temporary, { recursive: true, force: true }).catch(() => {
|
|
31289
32005
|
});
|
|
31290
32006
|
}
|
|
31291
32007
|
};
|
|
31292
32008
|
const prepareRepository = async (authority) => {
|
|
31293
32009
|
const { repository } = authority;
|
|
31294
|
-
const destination =
|
|
31295
|
-
const metadataPath =
|
|
31296
|
-
|
|
31297
|
-
|
|
32010
|
+
const destination = join11(repositoriesRoot, repository.repositoryId);
|
|
32011
|
+
const metadataPath = join11(metadataRoot, `${repository.repositoryId}.json`);
|
|
32012
|
+
assertBelow3(repositoriesRoot, destination, "repository path");
|
|
32013
|
+
assertBelow3(metadataRoot, metadataPath, "repository metadata");
|
|
31298
32014
|
return withWorkspaceLock(destination, async () => {
|
|
31299
32015
|
const destinationExists = await pathExists(destination);
|
|
31300
32016
|
const metadataExists = await pathExists(metadataPath);
|
|
@@ -31313,16 +32029,16 @@ async function createGithubWorkspaceService(input) {
|
|
|
31313
32029
|
expiresAt: authority.expiresAt
|
|
31314
32030
|
});
|
|
31315
32031
|
}
|
|
31316
|
-
const metadataEntry = await
|
|
32032
|
+
const metadataEntry = await lstat9(metadataPath);
|
|
31317
32033
|
if (!metadataEntry.isFile() || metadataEntry.isSymbolicLink()) {
|
|
31318
32034
|
throw new Error("GitHub workspace metadata is invalid");
|
|
31319
32035
|
}
|
|
31320
|
-
const metadata = parseMetadata(await
|
|
32036
|
+
const metadata = parseMetadata(await readFile8(metadataPath, "utf8"));
|
|
31321
32037
|
if (metadata.repositoryId !== repository.repositoryId || metadata.fullName !== repository.fullName || !samePath(metadata.path, destination)) {
|
|
31322
32038
|
throw new Error("GitHub workspace metadata does not match this repository");
|
|
31323
32039
|
}
|
|
31324
|
-
const repositoryPath6 = await
|
|
31325
|
-
|
|
32040
|
+
const repositoryPath6 = await requireRealDirectory3(destination, "GitHub repository");
|
|
32041
|
+
assertBelow3(repositoriesRoot, repositoryPath6, "repository path");
|
|
31326
32042
|
if (!authority.operations.includes("repository.fetch")) {
|
|
31327
32043
|
throw new Error("GitHub authority cannot refresh this repository");
|
|
31328
32044
|
}
|
|
@@ -31457,7 +32173,7 @@ async function createGithubWorkspaceService(input) {
|
|
|
31457
32173
|
throw new Error("GitHub created repository is outside this installation");
|
|
31458
32174
|
}
|
|
31459
32175
|
parseGitRef(cloneInput.repository.defaultBranch, "default branch");
|
|
31460
|
-
return withWorkspaceLock(
|
|
32176
|
+
return withWorkspaceLock(join11(repositoriesRoot, repositoryId2), async () => {
|
|
31461
32177
|
const prepared = await cloneRepository({
|
|
31462
32178
|
repositoryId: repositoryId2,
|
|
31463
32179
|
fullName: cloneInput.repository.fullName,
|
|
@@ -32998,8 +33714,8 @@ function createDefaultToolPackRegistry() {
|
|
|
32998
33714
|
}
|
|
32999
33715
|
|
|
33000
33716
|
// src/runners/attachments.ts
|
|
33001
|
-
import { mkdir as
|
|
33002
|
-
import { join as
|
|
33717
|
+
import { mkdir as mkdir8, writeFile as writeFile4 } from "node:fs/promises";
|
|
33718
|
+
import { join as join12 } from "node:path";
|
|
33003
33719
|
var WINDOWS_RESERVED = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\..*)?$/i;
|
|
33004
33720
|
function sanitizeAttachmentFileName(name) {
|
|
33005
33721
|
const base = name.split(/[/\\]/).pop() ?? "";
|
|
@@ -33028,10 +33744,10 @@ async function materializeAttachments(task, taskRoot) {
|
|
|
33028
33744
|
`attached file "${attachment.name}" arrived incomplete (${bytes.byteLength} of ${attachment.size} bytes)`
|
|
33029
33745
|
);
|
|
33030
33746
|
}
|
|
33031
|
-
const directory =
|
|
33032
|
-
await
|
|
33033
|
-
const path =
|
|
33034
|
-
await
|
|
33747
|
+
const directory = join12(taskRoot, ".zixt-attachments", task.taskId, attachment.id);
|
|
33748
|
+
await mkdir8(directory, { recursive: true });
|
|
33749
|
+
const path = join12(directory, sanitizeAttachmentFileName(attachment.name));
|
|
33750
|
+
await writeFile4(path, bytes);
|
|
33035
33751
|
materialized.push({
|
|
33036
33752
|
path,
|
|
33037
33753
|
name: attachment.name,
|
|
@@ -33995,7 +34711,7 @@ function createAskUserServer() {
|
|
|
33995
34711
|
}
|
|
33996
34712
|
|
|
33997
34713
|
// src/runners/runner-env.ts
|
|
33998
|
-
import { delimiter as delimiter2, isAbsolute as
|
|
34714
|
+
import { delimiter as delimiter2, isAbsolute as isAbsolute12 } from "node:path";
|
|
33999
34715
|
var PROVIDER_AUTHORITY_PREFIXES = ["GH_", "GITHUB_", "GIT_", "SSH_"];
|
|
34000
34716
|
var HOST_AUTHORITY_PREFIXES = [
|
|
34001
34717
|
"ZIXT_",
|
|
@@ -34054,7 +34770,7 @@ function inheritedValue(env, name) {
|
|
|
34054
34770
|
}
|
|
34055
34771
|
function sanitizeInheritedSearchPath(path) {
|
|
34056
34772
|
if (!path) return "";
|
|
34057
|
-
return path.split(delimiter2).filter((entry) => entry !== "" &&
|
|
34773
|
+
return path.split(delimiter2).filter((entry) => entry !== "" && isAbsolute12(entry)).join(delimiter2);
|
|
34058
34774
|
}
|
|
34059
34775
|
function buildRunnerEnv(input) {
|
|
34060
34776
|
const env = {};
|
|
@@ -34135,11 +34851,11 @@ function buildRunnerEnv(input) {
|
|
|
34135
34851
|
// src/runners/github-shell-auth.ts
|
|
34136
34852
|
import { execFile } from "node:child_process";
|
|
34137
34853
|
import { randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
34138
|
-
import { chmod as
|
|
34854
|
+
import { chmod as chmod6, lstat as lstat10, mkdir as mkdir9, realpath as realpath7, writeFile as writeFile5 } from "node:fs/promises";
|
|
34139
34855
|
import { createServer as createServer3 } from "node:http";
|
|
34140
|
-
import { isAbsolute as
|
|
34856
|
+
import { isAbsolute as isAbsolute13, join as join13, relative as relative8 } from "node:path";
|
|
34141
34857
|
var MAX_REQUEST_BYTES2 = 16 * 1024;
|
|
34142
|
-
var
|
|
34858
|
+
var DIRECTORY_MODE4 = 448;
|
|
34143
34859
|
var PRIVATE_FILE_MODE = 384;
|
|
34144
34860
|
var EXECUTABLE_FILE_MODE = 448;
|
|
34145
34861
|
var GIT_HELPER_SOURCE = String.raw`'use strict';
|
|
@@ -34343,7 +35059,7 @@ function parseGhInvocation(body) {
|
|
|
34343
35059
|
}
|
|
34344
35060
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
34345
35061
|
const { args, cwd } = value;
|
|
34346
|
-
if (!Array.isArray(args) || args.length > 256 || args.some((argument) => typeof argument !== "string" || argument.length > 4096) || typeof cwd !== "string" || cwd.length === 0 || cwd.length > 4096 || !
|
|
35062
|
+
if (!Array.isArray(args) || args.length > 256 || args.some((argument) => typeof argument !== "string" || argument.length > 4096) || typeof cwd !== "string" || cwd.length === 0 || cwd.length > 4096 || !isAbsolute13(cwd)) {
|
|
34347
35063
|
return null;
|
|
34348
35064
|
}
|
|
34349
35065
|
return { args, cwd };
|
|
@@ -34501,8 +35217,8 @@ function activationCredential(grant, now = Date.now()) {
|
|
|
34501
35217
|
return expiresAt.getTime() <= now ? null : { accessToken: grant.accessToken, expiresAt };
|
|
34502
35218
|
}
|
|
34503
35219
|
function assertChildPath2(parent, child) {
|
|
34504
|
-
const path =
|
|
34505
|
-
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") ||
|
|
35220
|
+
const path = relative8(parent, child);
|
|
35221
|
+
if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") || isAbsolute13(path)) {
|
|
34506
35222
|
throw new Error("GitHub shell helper path escaped its private run directory");
|
|
34507
35223
|
}
|
|
34508
35224
|
}
|
|
@@ -34513,19 +35229,19 @@ function quoteForPosixShell(value) {
|
|
|
34513
35229
|
return quoteForGitShell2(value);
|
|
34514
35230
|
}
|
|
34515
35231
|
async function writePrivate(path, content, executable = false) {
|
|
34516
|
-
await
|
|
35232
|
+
await writeFile5(path, content, {
|
|
34517
35233
|
flag: "wx",
|
|
34518
35234
|
mode: executable ? EXECUTABLE_FILE_MODE : PRIVATE_FILE_MODE
|
|
34519
35235
|
});
|
|
34520
|
-
await
|
|
35236
|
+
await chmod6(path, executable ? EXECUTABLE_FILE_MODE : PRIVATE_FILE_MODE);
|
|
34521
35237
|
}
|
|
34522
35238
|
async function prepareHelpers(input) {
|
|
34523
|
-
const rootEntry = await
|
|
35239
|
+
const rootEntry = await lstat10(input.runRoot);
|
|
34524
35240
|
if (!rootEntry.isDirectory() || rootEntry.isSymbolicLink()) {
|
|
34525
35241
|
throw new Error("GitHub shell authentication requires a private real run directory");
|
|
34526
35242
|
}
|
|
34527
|
-
const runRoot = await
|
|
34528
|
-
const helperPath =
|
|
35243
|
+
const runRoot = await realpath7(input.runRoot);
|
|
35244
|
+
const helperPath = join13(runRoot, "github-shell-git-credential.cjs");
|
|
34529
35245
|
assertChildPath2(runRoot, helperPath);
|
|
34530
35246
|
await writePrivate(helperPath, GIT_HELPER_SOURCE);
|
|
34531
35247
|
if (!input.ghExecutablePath) {
|
|
@@ -34536,14 +35252,14 @@ async function prepareHelpers(input) {
|
|
|
34536
35252
|
wrapperSourcePath: null
|
|
34537
35253
|
};
|
|
34538
35254
|
}
|
|
34539
|
-
const shellToolsDirectory =
|
|
35255
|
+
const shellToolsDirectory = join13(runRoot, "shell-tools");
|
|
34540
35256
|
assertChildPath2(runRoot, shellToolsDirectory);
|
|
34541
|
-
await
|
|
34542
|
-
await
|
|
34543
|
-
const wrapperSourcePath =
|
|
35257
|
+
await mkdir9(shellToolsDirectory, { mode: DIRECTORY_MODE4 });
|
|
35258
|
+
await chmod6(shellToolsDirectory, DIRECTORY_MODE4);
|
|
35259
|
+
const wrapperSourcePath = join13(runRoot, "github-shell-gh-wrapper.cjs");
|
|
34544
35260
|
assertChildPath2(runRoot, wrapperSourcePath);
|
|
34545
35261
|
await writePrivate(wrapperSourcePath, GH_WRAPPER_SOURCE);
|
|
34546
|
-
const wrapperPath =
|
|
35262
|
+
const wrapperPath = join13(shellToolsDirectory, process.platform === "win32" ? "gh.cmd" : "gh");
|
|
34547
35263
|
assertChildPath2(runRoot, wrapperPath);
|
|
34548
35264
|
const launcher = process.platform === "win32" ? `@"${process.execPath.replaceAll('"', '""')}" "${wrapperSourcePath.replaceAll('"', '""')}" %*\r
|
|
34549
35265
|
` : `#!/bin/sh
|
|
@@ -34766,8 +35482,8 @@ password=${credential.accessToken}
|
|
|
34766
35482
|
}
|
|
34767
35483
|
|
|
34768
35484
|
// src/runners/working-context.ts
|
|
34769
|
-
import { spawn as
|
|
34770
|
-
import { resolve as
|
|
35485
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
35486
|
+
import { resolve as resolve9 } from "node:path";
|
|
34771
35487
|
var COMMAND_TIMEOUT_MS = 5e3;
|
|
34772
35488
|
var OUTPUT_LIMIT_BYTES = 128 * 1024;
|
|
34773
35489
|
var COMMAND_STOP_TIMEOUT_MS = 2e4;
|
|
@@ -34897,7 +35613,7 @@ async function stopWorkingContextCommand(child, childExited, options = {}) {
|
|
|
34897
35613
|
function run(command, args, cwd, env, signal) {
|
|
34898
35614
|
if (signal?.aborted) return Promise.resolve(null);
|
|
34899
35615
|
return new Promise((resolvePromise) => {
|
|
34900
|
-
const child =
|
|
35616
|
+
const child = spawn7(command, [...args], {
|
|
34901
35617
|
cwd,
|
|
34902
35618
|
env: { ...env, GIT_OPTIONAL_LOCKS: "0" },
|
|
34903
35619
|
detached: process.platform !== "win32",
|
|
@@ -35162,8 +35878,8 @@ async function repositoryState(directory, git, env, signal) {
|
|
|
35162
35878
|
const pathLines = paths.trim().split(/\r?\n/);
|
|
35163
35879
|
if (pathLines.length < 3 || !pathLines[0] || !pathLines[1] || !pathLines[2]) return null;
|
|
35164
35880
|
const root = pathLines[0];
|
|
35165
|
-
const gitDirectory =
|
|
35166
|
-
const commonDirectory =
|
|
35881
|
+
const gitDirectory = resolve9(directory, pathLines[1]);
|
|
35882
|
+
const commonDirectory = resolve9(directory, pathLines[2]);
|
|
35167
35883
|
const records = status.split(/\0|\r?\n/).filter(Boolean);
|
|
35168
35884
|
const rawBranch = statusField(records, "branch.head");
|
|
35169
35885
|
if (!rawBranch || rawBranch.length > 512 || !isSafeSingleLineDisplayText(rawBranch)) return null;
|
|
@@ -35314,657 +36030,6 @@ var WorkingContextPullRequestCache = class {
|
|
|
35314
36030
|
}
|
|
35315
36031
|
};
|
|
35316
36032
|
|
|
35317
|
-
// src/runners/run-artifacts.ts
|
|
35318
|
-
import { spawn as spawn7 } from "node:child_process";
|
|
35319
|
-
import {
|
|
35320
|
-
chmod as chmod6,
|
|
35321
|
-
lstat as lstat10,
|
|
35322
|
-
mkdir as mkdir9,
|
|
35323
|
-
open as open5,
|
|
35324
|
-
readdir as readdir3,
|
|
35325
|
-
readFile as readFile8,
|
|
35326
|
-
realpath as realpath7,
|
|
35327
|
-
rename as rename5,
|
|
35328
|
-
rm as rm7,
|
|
35329
|
-
writeFile as writeFile5
|
|
35330
|
-
} from "node:fs/promises";
|
|
35331
|
-
import { homedir as homedir4 } from "node:os";
|
|
35332
|
-
import { dirname as dirname7, isAbsolute as isAbsolute13, join as join13, relative as relative8, resolve as resolve9, sep as sep4, win32 as win322 } from "node:path";
|
|
35333
|
-
var SAFE_SEGMENT2 = /^[A-Za-z0-9_-]{1,200}$/;
|
|
35334
|
-
var DIRECTORY_MODE4 = 448;
|
|
35335
|
-
var FILE_MODE3 = 384;
|
|
35336
|
-
var CLEANUP_ATTEMPTS2 = 3;
|
|
35337
|
-
var CLEANUP_RETRY_DELAY_MS = 50;
|
|
35338
|
-
var WINDOWS_ACL_TIMEOUT_MS = 5e3;
|
|
35339
|
-
var RUNNER_GUARDIAN = String.raw`
|
|
35340
|
-
const { spawn } = require('node:child_process');
|
|
35341
|
-
|
|
35342
|
-
const quote = (value) => '"' + String(value).replace(/\\/g, '/').replaceAll('"', '""') + '"';
|
|
35343
|
-
const launchMarker = '__ZIXT_RELEASE_RUNNER__';
|
|
35344
|
-
const exitMarker = process.argv[2];
|
|
35345
|
-
const guardianNonce = process.argv[3];
|
|
35346
|
-
if (!/^[A-Za-z0-9_-]{16,200}$/.test(guardianNonce || '')) process.exit(1);
|
|
35347
|
-
const containmentNonce = process.env[${JSON.stringify(WINDOWS_CONTAINMENT_GATE_ENV)}];
|
|
35348
|
-
const containmentMarker = ${JSON.stringify(WINDOWS_CONTAINMENT_GATE_PREFIX)};
|
|
35349
|
-
let containmentReady = !containmentNonce;
|
|
35350
|
-
const maxReleaseBytes = 32 * 1024 * 1024;
|
|
35351
|
-
let target = null;
|
|
35352
|
-
let pending = Buffer.alloc(0);
|
|
35353
|
-
let stdinEnded = false;
|
|
35354
|
-
let expectedBytes = null;
|
|
35355
|
-
let config = null;
|
|
35356
|
-
let launchPending = false;
|
|
35357
|
-
let stderrTail = '';
|
|
35358
|
-
let postReleaseInput = Buffer.alloc(0);
|
|
35359
|
-
let reported = false;
|
|
35360
|
-
const report = (result) => {
|
|
35361
|
-
if (reported) return;
|
|
35362
|
-
reported = true;
|
|
35363
|
-
process.stdout.write(
|
|
35364
|
-
'\n' + exitMarker + JSON.stringify({ ...result, stderrTail }) + '\n'
|
|
35365
|
-
);
|
|
35366
|
-
setInterval(() => {}, 2_147_483_647);
|
|
35367
|
-
};
|
|
35368
|
-
|
|
35369
|
-
// Job helper setup + assignment each have a bounded Windows phase. This gate
|
|
35370
|
-
// must outlive both, including slow first-run Add-Type under endpoint protection.
|
|
35371
|
-
const prelaunchTimeout = setTimeout(() => process.exit(1), 130_000);
|
|
35372
|
-
prelaunchTimeout.unref();
|
|
35373
|
-
|
|
35374
|
-
const launchTarget = (release) => {
|
|
35375
|
-
try {
|
|
35376
|
-
const command = config.comspec || config.command;
|
|
35377
|
-
const args = config.comspec
|
|
35378
|
-
? ['/d', '/s', '/c', '"' + [config.command, ...config.args].map(quote).join(' ') + '"']
|
|
35379
|
-
: config.args;
|
|
35380
|
-
target = spawn(command, args, {
|
|
35381
|
-
cwd: config.cwd,
|
|
35382
|
-
env: config.env,
|
|
35383
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
35384
|
-
windowsHide: true,
|
|
35385
|
-
windowsVerbatimArguments: Boolean(config.comspec),
|
|
35386
|
-
});
|
|
35387
|
-
launchPending = false;
|
|
35388
|
-
} catch {
|
|
35389
|
-
process.exit(1);
|
|
35390
|
-
return;
|
|
35391
|
-
}
|
|
35392
|
-
|
|
35393
|
-
target.stdin.on('error', () => {});
|
|
35394
|
-
target.stdout.pipe(process.stdout, { end: false });
|
|
35395
|
-
target.stderr.on('data', (chunk) => {
|
|
35396
|
-
process.stderr.write(chunk);
|
|
35397
|
-
stderrTail = (stderrTail + String(chunk)).slice(-32_768);
|
|
35398
|
-
});
|
|
35399
|
-
let targetResult;
|
|
35400
|
-
let stdoutEnded = false;
|
|
35401
|
-
const maybeReport = () => {
|
|
35402
|
-
if (targetResult && stdoutEnded) report(targetResult);
|
|
35403
|
-
};
|
|
35404
|
-
target.stdout.once('end', () => {
|
|
35405
|
-
stdoutEnded = true;
|
|
35406
|
-
maybeReport();
|
|
35407
|
-
});
|
|
35408
|
-
target.stdout.once('close', () => {
|
|
35409
|
-
stdoutEnded = true;
|
|
35410
|
-
maybeReport();
|
|
35411
|
-
});
|
|
35412
|
-
target.once('error', () => {
|
|
35413
|
-
targetResult = { kind: 'spawn_error' };
|
|
35414
|
-
stdoutEnded = true;
|
|
35415
|
-
maybeReport();
|
|
35416
|
-
});
|
|
35417
|
-
target.once('close', (code, signal) => {
|
|
35418
|
-
targetResult = { kind: 'close', code, signal };
|
|
35419
|
-
maybeReport();
|
|
35420
|
-
});
|
|
35421
|
-
if (config.prompt) target.stdin.write(config.prompt);
|
|
35422
|
-
if (postReleaseInput.length) {
|
|
35423
|
-
target.stdin.write(postReleaseInput);
|
|
35424
|
-
postReleaseInput = Buffer.alloc(0);
|
|
35425
|
-
}
|
|
35426
|
-
if (stdinEnded) target.stdin.end();
|
|
35427
|
-
};
|
|
35428
|
-
|
|
35429
|
-
const launch = (release) => {
|
|
35430
|
-
clearTimeout(prelaunchTimeout);
|
|
35431
|
-
config = release;
|
|
35432
|
-
launchPending = true;
|
|
35433
|
-
if (process.platform === 'win32') {
|
|
35434
|
-
launchTarget(release);
|
|
35435
|
-
return;
|
|
35436
|
-
}
|
|
35437
|
-
|
|
35438
|
-
// A detached POSIX group's numeric id outlives its leader, but without a
|
|
35439
|
-
// live identity witness a stale registry entry cannot distinguish that
|
|
35440
|
-
// inherited group from the same PGID being recycled later. This
|
|
35441
|
-
// credential-free sentinel inherits the guardian's exact group and carries
|
|
35442
|
-
// only its high-entropy nonce in argv. It deliberately survives SIGTERM so
|
|
35443
|
-
// checked recovery can keep the PGID reserved through the graceful pass and
|
|
35444
|
-
// validate the nonce again immediately before a group-wide SIGKILL.
|
|
35445
|
-
const identitySentinel = spawn(
|
|
35446
|
-
process.execPath,
|
|
35447
|
-
[
|
|
35448
|
-
'-e',
|
|
35449
|
-
"process.on('SIGTERM', () => {}); setInterval(() => {}, 2147483647);",
|
|
35450
|
-
guardianNonce,
|
|
35451
|
-
],
|
|
35452
|
-
{ env: process.env, stdio: 'ignore' },
|
|
35453
|
-
);
|
|
35454
|
-
identitySentinel.once('error', () => process.exit(1));
|
|
35455
|
-
identitySentinel.once('spawn', () => launchTarget(release));
|
|
35456
|
-
identitySentinel.once('exit', () => {
|
|
35457
|
-
if (!reported) process.exit(1);
|
|
35458
|
-
});
|
|
35459
|
-
};
|
|
35460
|
-
|
|
35461
|
-
process.stdin.on('data', (chunk) => {
|
|
35462
|
-
if (target) {
|
|
35463
|
-
target.stdin.write(chunk);
|
|
35464
|
-
return;
|
|
35465
|
-
}
|
|
35466
|
-
if (config) {
|
|
35467
|
-
postReleaseInput = Buffer.concat([postReleaseInput, chunk]);
|
|
35468
|
-
if (postReleaseInput.length > maxReleaseBytes) process.exit(1);
|
|
35469
|
-
return;
|
|
35470
|
-
}
|
|
35471
|
-
pending = Buffer.concat([pending, chunk]);
|
|
35472
|
-
if (pending.length > maxReleaseBytes + 256) process.exit(1);
|
|
35473
|
-
if (!containmentReady) {
|
|
35474
|
-
const gateNewline = pending.indexOf(10);
|
|
35475
|
-
if (gateNewline < 0) return;
|
|
35476
|
-
const gate = pending.subarray(0, gateNewline).toString().replace(/\r$/, '');
|
|
35477
|
-
if (gate !== containmentMarker + ' ' + containmentNonce) process.exit(1);
|
|
35478
|
-
containmentReady = true;
|
|
35479
|
-
pending = pending.subarray(gateNewline + 1);
|
|
35480
|
-
}
|
|
35481
|
-
if (expectedBytes === null) {
|
|
35482
|
-
const newline = pending.indexOf(10);
|
|
35483
|
-
if (newline < 0) return;
|
|
35484
|
-
const header = pending.subarray(0, newline).toString();
|
|
35485
|
-
const match = new RegExp('^' + launchMarker + ' ([1-9][0-9]{0,8})$').exec(header);
|
|
35486
|
-
if (!match) process.exit(1);
|
|
35487
|
-
expectedBytes = Number(match[1]);
|
|
35488
|
-
if (expectedBytes > maxReleaseBytes) process.exit(1);
|
|
35489
|
-
pending = pending.subarray(newline + 1);
|
|
35490
|
-
}
|
|
35491
|
-
if (pending.length < expectedBytes) return;
|
|
35492
|
-
try {
|
|
35493
|
-
const release = JSON.parse(pending.subarray(0, expectedBytes).toString('utf8'));
|
|
35494
|
-
postReleaseInput = pending.subarray(expectedBytes);
|
|
35495
|
-
pending = Buffer.alloc(0);
|
|
35496
|
-
launch(release);
|
|
35497
|
-
} catch {
|
|
35498
|
-
process.exit(1);
|
|
35499
|
-
}
|
|
35500
|
-
});
|
|
35501
|
-
process.stdin.on('end', () => {
|
|
35502
|
-
stdinEnded = true;
|
|
35503
|
-
if (!containmentReady) process.exit(1);
|
|
35504
|
-
if (target) target.stdin.end();
|
|
35505
|
-
else if (!launchPending) process.exit(1);
|
|
35506
|
-
});
|
|
35507
|
-
process.stdin.on('error', () => process.exit(1));
|
|
35508
|
-
`;
|
|
35509
|
-
var WINDOWS_PRIVATE_DACL_SCRIPT = String.raw`
|
|
35510
|
-
$ErrorActionPreference = 'Stop'
|
|
35511
|
-
$paths = ConvertFrom-Json $env:ZIXT_RUN_ARTIFACT_PATHS
|
|
35512
|
-
$current = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
35513
|
-
$system = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-18')
|
|
35514
|
-
$admins = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-32-544')
|
|
35515
|
-
$allowed = @($current.Value, $system.Value, $admins.Value)
|
|
35516
|
-
foreach ($path in $paths) {
|
|
35517
|
-
$acl = [System.Security.AccessControl.DirectorySecurity]::new()
|
|
35518
|
-
$acl.SetAccessRuleProtection($true, $false)
|
|
35519
|
-
foreach ($sid in @($current, $system, $admins)) {
|
|
35520
|
-
$rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
|
|
35521
|
-
$sid,
|
|
35522
|
-
[System.Security.AccessControl.FileSystemRights]::FullControl,
|
|
35523
|
-
[System.Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit',
|
|
35524
|
-
[System.Security.AccessControl.PropagationFlags]::None,
|
|
35525
|
-
[System.Security.AccessControl.AccessControlType]::Allow
|
|
35526
|
-
)
|
|
35527
|
-
[void]$acl.AddAccessRule($rule)
|
|
35528
|
-
}
|
|
35529
|
-
$entry = [System.IO.DirectoryInfo]::new($path)
|
|
35530
|
-
$entry.SetAccessControl($acl)
|
|
35531
|
-
$check = $entry.GetAccessControl()
|
|
35532
|
-
if (-not $check.AreAccessRulesProtected) { throw 'DACL inheritance remained enabled' }
|
|
35533
|
-
$seen = @{}
|
|
35534
|
-
foreach ($rule in $check.Access) {
|
|
35535
|
-
$sid = $rule.IdentityReference.Translate(
|
|
35536
|
-
[System.Security.Principal.SecurityIdentifier]
|
|
35537
|
-
).Value
|
|
35538
|
-
if ($rule.AccessControlType -ne 'Allow' -or $allowed -notcontains $sid) {
|
|
35539
|
-
throw 'Unexpected DACL entry'
|
|
35540
|
-
}
|
|
35541
|
-
$seen[$sid] = $true
|
|
35542
|
-
}
|
|
35543
|
-
foreach ($sid in $allowed) {
|
|
35544
|
-
if (-not $seen.ContainsKey($sid)) { throw 'Required DACL entry is missing' }
|
|
35545
|
-
}
|
|
35546
|
-
}
|
|
35547
|
-
`;
|
|
35548
|
-
function defaultRunArtifactRoot() {
|
|
35549
|
-
return join13(homedir4(), ".zixt", "run-artifacts");
|
|
35550
|
-
}
|
|
35551
|
-
function requireSafeSegment(value, field) {
|
|
35552
|
-
if (!SAFE_SEGMENT2.test(value)) {
|
|
35553
|
-
throw new Error(`${field} must be a safe path segment`);
|
|
35554
|
-
}
|
|
35555
|
-
}
|
|
35556
|
-
function isMissing(error52) {
|
|
35557
|
-
return error52.code === "ENOENT";
|
|
35558
|
-
}
|
|
35559
|
-
function assertBelow3(parent, child) {
|
|
35560
|
-
const path = relative8(parent, child);
|
|
35561
|
-
const escapes = path === "" || path === ".." || path.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute13(path);
|
|
35562
|
-
if (escapes) throw new Error("run artifact path escapes its private root");
|
|
35563
|
-
}
|
|
35564
|
-
async function requireRealDirectory3(path, label) {
|
|
35565
|
-
const entry = await lstat10(path);
|
|
35566
|
-
if (entry.isSymbolicLink()) throw new Error(`${label} must not be a symbolic link`);
|
|
35567
|
-
if (!entry.isDirectory()) throw new Error(`${label} must be a directory`);
|
|
35568
|
-
return realpath7(path);
|
|
35569
|
-
}
|
|
35570
|
-
function assertWindowsProfileBoundary(profile, target) {
|
|
35571
|
-
const path = win322.relative(win322.resolve(profile), win322.resolve(target));
|
|
35572
|
-
if (path === "" || path === ".." || path.startsWith("..\\") || win322.isAbsolute(path)) {
|
|
35573
|
-
throw new Error("run artifact root must be inside the current Windows user profile");
|
|
35574
|
-
}
|
|
35575
|
-
}
|
|
35576
|
-
async function rejectWindowsSymlinkAncestors(profile, target) {
|
|
35577
|
-
const path = win322.relative(win322.resolve(profile), win322.resolve(target));
|
|
35578
|
-
let current = win322.resolve(profile);
|
|
35579
|
-
for (const segment of path.split("\\").filter(Boolean)) {
|
|
35580
|
-
current = win322.join(current, segment);
|
|
35581
|
-
try {
|
|
35582
|
-
const entry = await lstat10(current);
|
|
35583
|
-
if (entry.isSymbolicLink()) {
|
|
35584
|
-
throw new Error("run artifact path must not contain symbolic links or junctions");
|
|
35585
|
-
}
|
|
35586
|
-
} catch (error52) {
|
|
35587
|
-
if (isMissing(error52)) return;
|
|
35588
|
-
throw error52;
|
|
35589
|
-
}
|
|
35590
|
-
}
|
|
35591
|
-
}
|
|
35592
|
-
async function prepareRoot(root) {
|
|
35593
|
-
const absolute = resolve9(root);
|
|
35594
|
-
let realProfile;
|
|
35595
|
-
if (process.platform === "win32") {
|
|
35596
|
-
const profile = resolve9(homedir4());
|
|
35597
|
-
assertWindowsProfileBoundary(profile, absolute);
|
|
35598
|
-
await rejectWindowsSymlinkAncestors(profile, absolute);
|
|
35599
|
-
realProfile = await realpath7(profile);
|
|
35600
|
-
}
|
|
35601
|
-
try {
|
|
35602
|
-
await lstat10(absolute);
|
|
35603
|
-
} catch (error52) {
|
|
35604
|
-
if (!isMissing(error52)) throw error52;
|
|
35605
|
-
await mkdir9(absolute, { recursive: true, mode: DIRECTORY_MODE4 });
|
|
35606
|
-
}
|
|
35607
|
-
const real = await requireRealDirectory3(absolute, "run artifact root");
|
|
35608
|
-
if (realProfile) assertWindowsProfileBoundary(realProfile, real);
|
|
35609
|
-
await chmod6(real, DIRECTORY_MODE4);
|
|
35610
|
-
return real;
|
|
35611
|
-
}
|
|
35612
|
-
async function prepareAgentRoot(root, agentId) {
|
|
35613
|
-
const path = join13(root, agentId);
|
|
35614
|
-
assertBelow3(root, path);
|
|
35615
|
-
try {
|
|
35616
|
-
await lstat10(path);
|
|
35617
|
-
} catch (error52) {
|
|
35618
|
-
if (!isMissing(error52)) throw error52;
|
|
35619
|
-
try {
|
|
35620
|
-
await mkdir9(path, { mode: DIRECTORY_MODE4 });
|
|
35621
|
-
} catch (mkdirError) {
|
|
35622
|
-
if (mkdirError.code !== "EEXIST") throw mkdirError;
|
|
35623
|
-
}
|
|
35624
|
-
}
|
|
35625
|
-
const real = await requireRealDirectory3(path, "run artifact Agent directory");
|
|
35626
|
-
assertBelow3(root, real);
|
|
35627
|
-
await chmod6(real, DIRECTORY_MODE4);
|
|
35628
|
-
return real;
|
|
35629
|
-
}
|
|
35630
|
-
async function lockDownWindowsDirectories(paths) {
|
|
35631
|
-
if (process.platform !== "win32") return;
|
|
35632
|
-
const windowsRoot = process.env.SystemRoot ?? process.env.WINDIR;
|
|
35633
|
-
if (!windowsRoot || !win322.isAbsolute(windowsRoot)) {
|
|
35634
|
-
throw new Error("private Windows run-artifact ACL authority is unavailable");
|
|
35635
|
-
}
|
|
35636
|
-
const powershell = join13(windowsRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
35637
|
-
const encoded = Buffer.from(WINDOWS_PRIVATE_DACL_SCRIPT, "utf16le").toString("base64");
|
|
35638
|
-
await new Promise((resolvePromise, reject3) => {
|
|
35639
|
-
const helper = spawn7(
|
|
35640
|
-
powershell,
|
|
35641
|
-
["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", encoded],
|
|
35642
|
-
{
|
|
35643
|
-
env: {
|
|
35644
|
-
SystemRoot: windowsRoot,
|
|
35645
|
-
WINDIR: windowsRoot,
|
|
35646
|
-
...process.env.TEMP ? { TEMP: process.env.TEMP } : {},
|
|
35647
|
-
...process.env.TMP ? { TMP: process.env.TMP } : {},
|
|
35648
|
-
ZIXT_RUN_ARTIFACT_PATHS: JSON.stringify(paths)
|
|
35649
|
-
},
|
|
35650
|
-
stdio: "ignore",
|
|
35651
|
-
windowsHide: true
|
|
35652
|
-
}
|
|
35653
|
-
);
|
|
35654
|
-
let finished = false;
|
|
35655
|
-
const timeout = setTimeout(() => {
|
|
35656
|
-
helper.kill("SIGKILL");
|
|
35657
|
-
finish(new Error("private Windows run-artifact ACL update timed out"));
|
|
35658
|
-
}, WINDOWS_ACL_TIMEOUT_MS);
|
|
35659
|
-
timeout.unref?.();
|
|
35660
|
-
const finish = (error52) => {
|
|
35661
|
-
if (finished) return;
|
|
35662
|
-
finished = true;
|
|
35663
|
-
clearTimeout(timeout);
|
|
35664
|
-
if (error52) reject3(error52);
|
|
35665
|
-
else resolvePromise();
|
|
35666
|
-
};
|
|
35667
|
-
helper.once(
|
|
35668
|
-
"error",
|
|
35669
|
-
() => finish(new Error("private Windows run-artifact ACL update could not start"))
|
|
35670
|
-
);
|
|
35671
|
-
helper.once(
|
|
35672
|
-
"close",
|
|
35673
|
-
(code) => finish(code === 0 ? void 0 : new Error("private Windows run-artifact ACL update failed"))
|
|
35674
|
-
);
|
|
35675
|
-
});
|
|
35676
|
-
}
|
|
35677
|
-
async function createPrivateDirectory(parent, name) {
|
|
35678
|
-
const path = join13(parent, name);
|
|
35679
|
-
assertBelow3(parent, path);
|
|
35680
|
-
await mkdir9(path, { mode: DIRECTORY_MODE4 });
|
|
35681
|
-
await chmod6(path, DIRECTORY_MODE4);
|
|
35682
|
-
const real = await realpath7(path);
|
|
35683
|
-
assertBelow3(parent, real);
|
|
35684
|
-
return real;
|
|
35685
|
-
}
|
|
35686
|
-
async function writePrivateFile(path, content) {
|
|
35687
|
-
await writeFile5(path, content, { flag: "wx", mode: FILE_MODE3 });
|
|
35688
|
-
await chmod6(path, FILE_MODE3);
|
|
35689
|
-
}
|
|
35690
|
-
function quotePosix(value) {
|
|
35691
|
-
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
35692
|
-
}
|
|
35693
|
-
function quoteWindows(value) {
|
|
35694
|
-
if (value.includes('"') || /[\r\n]/.test(value)) {
|
|
35695
|
-
throw new Error("Windows command paths must not contain quotes or newlines");
|
|
35696
|
-
}
|
|
35697
|
-
return `"${value}"`;
|
|
35698
|
-
}
|
|
35699
|
-
function buildDenySshCommand(executablePath, scriptPath, platform = process.platform) {
|
|
35700
|
-
const quote2 = platform === "win32" ? quoteWindows : quotePosix;
|
|
35701
|
-
return `${quote2(executablePath)} ${quote2(scriptPath)}`;
|
|
35702
|
-
}
|
|
35703
|
-
async function createRunArtifacts(input) {
|
|
35704
|
-
requireSafeSegment(input.agentId, "agentId");
|
|
35705
|
-
requireSafeSegment(input.runToken, "runToken");
|
|
35706
|
-
const root = await prepareRoot(input.root);
|
|
35707
|
-
const removeTree = input.removeTree ?? ((path) => rm7(path, { recursive: true, force: true }));
|
|
35708
|
-
const cleanupRetryDelayMs = input.cleanupRetryDelayMs ?? CLEANUP_RETRY_DELAY_MS;
|
|
35709
|
-
const agentRoot = await prepareAgentRoot(root, input.agentId);
|
|
35710
|
-
await lockDownWindowsDirectories([root, agentRoot]);
|
|
35711
|
-
const runRoot = join13(agentRoot, input.runToken);
|
|
35712
|
-
assertBelow3(agentRoot, runRoot);
|
|
35713
|
-
try {
|
|
35714
|
-
await mkdir9(runRoot, { mode: DIRECTORY_MODE4 });
|
|
35715
|
-
await chmod6(runRoot, DIRECTORY_MODE4);
|
|
35716
|
-
const realRunRoot = await realpath7(runRoot);
|
|
35717
|
-
assertBelow3(agentRoot, realRunRoot);
|
|
35718
|
-
const emptyGithubConfigDirectory = await createPrivateDirectory(realRunRoot, "gh-config");
|
|
35719
|
-
const emptyGitHooksDirectory = await createPrivateDirectory(realRunRoot, "git-hooks");
|
|
35720
|
-
const gitBridgesDirectory = await createPrivateDirectory(realRunRoot, "git-bridges");
|
|
35721
|
-
const denySshScript = join13(realRunRoot, "deny-ssh.cjs");
|
|
35722
|
-
const runnerWrapperScript = join13(realRunRoot, "runner-wrapper.cjs");
|
|
35723
|
-
const systemPromptPath = join13(realRunRoot, "system-prompt.txt");
|
|
35724
|
-
const mcpConfigPath = join13(realRunRoot, "mcp.json");
|
|
35725
|
-
await writePrivateFile(denySshScript, "process.exitCode = 127;");
|
|
35726
|
-
await writePrivateFile(runnerWrapperScript, RUNNER_GUARDIAN);
|
|
35727
|
-
return {
|
|
35728
|
-
runRoot: realRunRoot,
|
|
35729
|
-
emptyGithubConfigDirectory,
|
|
35730
|
-
emptyGitHooksDirectory,
|
|
35731
|
-
gitBridgesDirectory,
|
|
35732
|
-
denySshScript,
|
|
35733
|
-
denySshCommand: buildDenySshCommand(process.execPath, denySshScript),
|
|
35734
|
-
runnerWrapperScript,
|
|
35735
|
-
nullConfigPath: process.platform === "win32" ? "NUL" : "/dev/null",
|
|
35736
|
-
async writeSystemPrompt(content) {
|
|
35737
|
-
await writePrivateFile(systemPromptPath, content);
|
|
35738
|
-
return systemPromptPath;
|
|
35739
|
-
},
|
|
35740
|
-
async writeMcpConfig(content) {
|
|
35741
|
-
await writePrivateFile(mcpConfigPath, content);
|
|
35742
|
-
return mcpConfigPath;
|
|
35743
|
-
},
|
|
35744
|
-
async cleanup() {
|
|
35745
|
-
await removePrivateTreeWithRetries(realRunRoot, removeTree, cleanupRetryDelayMs);
|
|
35746
|
-
}
|
|
35747
|
-
};
|
|
35748
|
-
} catch (error52) {
|
|
35749
|
-
await removePrivateTreeWithRetries(runRoot, removeTree, cleanupRetryDelayMs).catch(() => {
|
|
35750
|
-
});
|
|
35751
|
-
throw error52;
|
|
35752
|
-
}
|
|
35753
|
-
}
|
|
35754
|
-
async function removePrivateTreeWithRetries(path, removeTree, retryDelayMs) {
|
|
35755
|
-
for (let attempt = 1; attempt <= CLEANUP_ATTEMPTS2; attempt++) {
|
|
35756
|
-
try {
|
|
35757
|
-
await removeTree(path);
|
|
35758
|
-
return;
|
|
35759
|
-
} catch (error52) {
|
|
35760
|
-
if (attempt === CLEANUP_ATTEMPTS2) throw error52;
|
|
35761
|
-
await new Promise((resolveDelay) => setTimeout(resolveDelay, retryDelayMs));
|
|
35762
|
-
}
|
|
35763
|
-
}
|
|
35764
|
-
}
|
|
35765
|
-
async function sweepOrphanedRunArtifacts(root) {
|
|
35766
|
-
const absolute = resolve9(root);
|
|
35767
|
-
let realProfile;
|
|
35768
|
-
if (process.platform === "win32") {
|
|
35769
|
-
const profile = resolve9(homedir4());
|
|
35770
|
-
assertWindowsProfileBoundary(profile, absolute);
|
|
35771
|
-
await rejectWindowsSymlinkAncestors(profile, absolute);
|
|
35772
|
-
realProfile = await realpath7(profile);
|
|
35773
|
-
}
|
|
35774
|
-
let realRoot;
|
|
35775
|
-
try {
|
|
35776
|
-
realRoot = await requireRealDirectory3(absolute, "run artifact root");
|
|
35777
|
-
} catch (error52) {
|
|
35778
|
-
if (isMissing(error52)) return 0;
|
|
35779
|
-
throw error52;
|
|
35780
|
-
}
|
|
35781
|
-
if (realProfile) assertWindowsProfileBoundary(realProfile, realRoot);
|
|
35782
|
-
await chmod6(realRoot, DIRECTORY_MODE4);
|
|
35783
|
-
await lockDownWindowsDirectories([realRoot]);
|
|
35784
|
-
const agents = await readdir3(realRoot, { withFileTypes: true });
|
|
35785
|
-
let removed = 0;
|
|
35786
|
-
for (const agent of agents) {
|
|
35787
|
-
if (!SAFE_SEGMENT2.test(agent.name) || !agent.isDirectory() || agent.isSymbolicLink()) continue;
|
|
35788
|
-
const agentPath = join13(realRoot, agent.name);
|
|
35789
|
-
const runs = await readdir3(agentPath, { withFileTypes: true });
|
|
35790
|
-
for (const run3 of runs) {
|
|
35791
|
-
if (!SAFE_SEGMENT2.test(run3.name) || !run3.isDirectory() || run3.isSymbolicLink()) continue;
|
|
35792
|
-
const runPath = join13(agentPath, run3.name);
|
|
35793
|
-
assertBelow3(agentPath, runPath);
|
|
35794
|
-
await rm7(runPath, { recursive: true, force: true });
|
|
35795
|
-
removed++;
|
|
35796
|
-
}
|
|
35797
|
-
}
|
|
35798
|
-
return removed;
|
|
35799
|
-
}
|
|
35800
|
-
function defaultRunRegistryRoot() {
|
|
35801
|
-
return join13(homedir4(), ".zixt", "run-registry");
|
|
35802
|
-
}
|
|
35803
|
-
async function syncRunRegistryDirectory(path) {
|
|
35804
|
-
const handle = await open5(path, "r");
|
|
35805
|
-
try {
|
|
35806
|
-
await handle.sync();
|
|
35807
|
-
} finally {
|
|
35808
|
-
await handle.close();
|
|
35809
|
-
}
|
|
35810
|
-
}
|
|
35811
|
-
async function ensureDurableRunRegistryRoot(registryRoot, syncDirectory7) {
|
|
35812
|
-
const firstCreated = await mkdir9(registryRoot, { recursive: true, mode: DIRECTORY_MODE4 });
|
|
35813
|
-
if (firstCreated && process.platform !== "win32") {
|
|
35814
|
-
const first = resolve9(firstCreated);
|
|
35815
|
-
const target = resolve9(registryRoot);
|
|
35816
|
-
await syncDirectory7(dirname7(first));
|
|
35817
|
-
let current = first;
|
|
35818
|
-
for (const part of relative8(first, target).split(sep4).filter(Boolean)) {
|
|
35819
|
-
await syncDirectory7(current);
|
|
35820
|
-
current = join13(current, part);
|
|
35821
|
-
}
|
|
35822
|
-
}
|
|
35823
|
-
await chmod6(registryRoot, DIRECTORY_MODE4);
|
|
35824
|
-
}
|
|
35825
|
-
async function recordRunAssignment(runToken, record2, registryRoot = defaultRunRegistryRoot(), options = {}) {
|
|
35826
|
-
if (!SAFE_SEGMENT2.test(runToken)) return false;
|
|
35827
|
-
const destination = join13(registryRoot, `${runToken}.json`);
|
|
35828
|
-
const temporary = join13(registryRoot, `.${runToken}.${process.pid}.${Date.now()}.tmp`);
|
|
35829
|
-
let handle;
|
|
35830
|
-
try {
|
|
35831
|
-
const syncDirectory7 = options.syncDirectory ?? syncRunRegistryDirectory;
|
|
35832
|
-
await ensureDurableRunRegistryRoot(registryRoot, syncDirectory7);
|
|
35833
|
-
handle = await open5(temporary, "wx", FILE_MODE3);
|
|
35834
|
-
await handle.writeFile(JSON.stringify(record2), "utf8");
|
|
35835
|
-
await handle.sync();
|
|
35836
|
-
await handle.close();
|
|
35837
|
-
handle = void 0;
|
|
35838
|
-
await rename5(temporary, destination);
|
|
35839
|
-
if (process.platform !== "win32") {
|
|
35840
|
-
await syncDirectory7(registryRoot);
|
|
35841
|
-
}
|
|
35842
|
-
return true;
|
|
35843
|
-
} catch {
|
|
35844
|
-
return false;
|
|
35845
|
-
} finally {
|
|
35846
|
-
await handle?.close().catch(() => {
|
|
35847
|
-
});
|
|
35848
|
-
await rm7(temporary, { force: true }).catch(() => {
|
|
35849
|
-
});
|
|
35850
|
-
}
|
|
35851
|
-
}
|
|
35852
|
-
var retainingAssignments = false;
|
|
35853
|
-
function retainRunAssignments() {
|
|
35854
|
-
retainingAssignments = true;
|
|
35855
|
-
}
|
|
35856
|
-
async function forgetRunAssignment(runToken, registryRoot = defaultRunRegistryRoot()) {
|
|
35857
|
-
if (retainingAssignments) return;
|
|
35858
|
-
if (!SAFE_SEGMENT2.test(runToken)) return;
|
|
35859
|
-
try {
|
|
35860
|
-
await rm7(join13(registryRoot, `${runToken}.json`), { force: true });
|
|
35861
|
-
} catch {
|
|
35862
|
-
}
|
|
35863
|
-
}
|
|
35864
|
-
function parseAssignment(text) {
|
|
35865
|
-
let parsed;
|
|
35866
|
-
try {
|
|
35867
|
-
parsed = JSON.parse(text);
|
|
35868
|
-
} catch {
|
|
35869
|
-
return null;
|
|
35870
|
-
}
|
|
35871
|
-
if (typeof parsed !== "object" || parsed === null) return null;
|
|
35872
|
-
const value = parsed;
|
|
35873
|
-
if (typeof value.taskId !== "string" || value.taskId === "") return null;
|
|
35874
|
-
if (typeof value.epoch !== "number" || !Number.isInteger(value.epoch) || value.epoch < 1) {
|
|
35875
|
-
return null;
|
|
35876
|
-
}
|
|
35877
|
-
const pid = value.pid;
|
|
35878
|
-
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 1) return null;
|
|
35879
|
-
let identity;
|
|
35880
|
-
if (value.identity !== void 0) {
|
|
35881
|
-
if (typeof value.identity !== "object" || value.identity === null || value.identity.kind !== "guardian_nonce" || typeof value.identity.nonce !== "string" || !/^[A-Za-z0-9_-]{16,200}$/.test(value.identity.nonce)) {
|
|
35882
|
-
return null;
|
|
35883
|
-
}
|
|
35884
|
-
identity = {
|
|
35885
|
-
kind: "guardian_nonce",
|
|
35886
|
-
nonce: value.identity.nonce
|
|
35887
|
-
};
|
|
35888
|
-
}
|
|
35889
|
-
return { taskId: value.taskId, epoch: value.epoch, pid, ...identity ? { identity } : {} };
|
|
35890
|
-
}
|
|
35891
|
-
async function readRecordedRunAssignmentEntries(registryRoot = defaultRunRegistryRoot()) {
|
|
35892
|
-
let entries;
|
|
35893
|
-
try {
|
|
35894
|
-
entries = await readdir3(registryRoot, { withFileTypes: true });
|
|
35895
|
-
} catch {
|
|
35896
|
-
return [];
|
|
35897
|
-
}
|
|
35898
|
-
const found = [];
|
|
35899
|
-
for (const entry of entries) {
|
|
35900
|
-
if (!entry.isFile() || entry.isSymbolicLink() || !entry.name.endsWith(".json")) continue;
|
|
35901
|
-
const runToken = entry.name.slice(0, -".json".length);
|
|
35902
|
-
if (!SAFE_SEGMENT2.test(runToken)) continue;
|
|
35903
|
-
let text;
|
|
35904
|
-
try {
|
|
35905
|
-
text = await readFile8(join13(registryRoot, entry.name), "utf8");
|
|
35906
|
-
} catch {
|
|
35907
|
-
continue;
|
|
35908
|
-
}
|
|
35909
|
-
const record2 = parseAssignment(text);
|
|
35910
|
-
if (record2) found.push({ runToken, record: record2 });
|
|
35911
|
-
}
|
|
35912
|
-
return found;
|
|
35913
|
-
}
|
|
35914
|
-
async function readRecordedRunAssignmentEntriesStrict(registryRoot = defaultRunRegistryRoot()) {
|
|
35915
|
-
let rootStat;
|
|
35916
|
-
try {
|
|
35917
|
-
rootStat = await lstat10(registryRoot);
|
|
35918
|
-
} catch (error52) {
|
|
35919
|
-
if (isMissing(error52)) return [];
|
|
35920
|
-
throw new Error("run registry state could not be observed");
|
|
35921
|
-
}
|
|
35922
|
-
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
35923
|
-
throw new Error("run registry root is not a trusted directory");
|
|
35924
|
-
}
|
|
35925
|
-
let entries;
|
|
35926
|
-
try {
|
|
35927
|
-
entries = await readdir3(registryRoot, { withFileTypes: true });
|
|
35928
|
-
} catch {
|
|
35929
|
-
throw new Error("run registry state could not be observed");
|
|
35930
|
-
}
|
|
35931
|
-
const found = [];
|
|
35932
|
-
for (const entry of entries) {
|
|
35933
|
-
if (!entry.name.endsWith(".json")) continue;
|
|
35934
|
-
const runToken = entry.name.slice(0, -".json".length);
|
|
35935
|
-
if (!SAFE_SEGMENT2.test(runToken)) continue;
|
|
35936
|
-
if (!entry.isFile() || entry.isSymbolicLink()) {
|
|
35937
|
-
throw new Error("committed run registry witness is not a regular file");
|
|
35938
|
-
}
|
|
35939
|
-
let text;
|
|
35940
|
-
try {
|
|
35941
|
-
text = await readFile8(join13(registryRoot, entry.name), "utf8");
|
|
35942
|
-
} catch {
|
|
35943
|
-
throw new Error("committed run registry witness could not be read");
|
|
35944
|
-
}
|
|
35945
|
-
const record2 = parseAssignment(text);
|
|
35946
|
-
if (!record2) throw new Error("committed run registry witness is malformed");
|
|
35947
|
-
found.push({ runToken, record: record2 });
|
|
35948
|
-
}
|
|
35949
|
-
return found;
|
|
35950
|
-
}
|
|
35951
|
-
async function forgetAcknowledgedRunAssignments(assignments, registryRoot = defaultRunRegistryRoot()) {
|
|
35952
|
-
if (assignments.length === 0) return;
|
|
35953
|
-
const acknowledged = new Set(
|
|
35954
|
-
assignments.map((assignment) => `${assignment.taskId}:${assignment.epoch}`)
|
|
35955
|
-
);
|
|
35956
|
-
const entries = await readRecordedRunAssignmentEntries(registryRoot);
|
|
35957
|
-
await Promise.all(
|
|
35958
|
-
entries.filter(({ record: record2 }) => acknowledged.has(`${record2.taskId}:${record2.epoch}`)).map(({ runToken }) => rm7(join13(registryRoot, `${runToken}.json`), { force: true }))
|
|
35959
|
-
);
|
|
35960
|
-
}
|
|
35961
|
-
async function forgetSupersededRunAssignments(taskId, epoch, registryRoot = defaultRunRegistryRoot()) {
|
|
35962
|
-
const entries = await readRecordedRunAssignmentEntries(registryRoot);
|
|
35963
|
-
await Promise.all(
|
|
35964
|
-
entries.filter(({ record: record2 }) => record2.taskId === taskId && record2.epoch < epoch).map(({ runToken }) => rm7(join13(registryRoot, `${runToken}.json`), { force: true }))
|
|
35965
|
-
);
|
|
35966
|
-
}
|
|
35967
|
-
|
|
35968
36033
|
// src/runners/cli-runner.ts
|
|
35969
36034
|
var PRIVATE_CLEANUP_FAILURE = "Private runner cleanup could not be completed. Restart the Zixt Host before accepting more work.";
|
|
35970
36035
|
var PROCESS_CLEANUP_FAILURE = "Runner process cleanup could not be confirmed. Restart the Zixt Host before accepting more work.";
|
|
@@ -35996,21 +36061,10 @@ function defaultRunnerArtifactRoot() {
|
|
|
35996
36061
|
async function sweepOrphanedRunnerArtifacts(artifactRoot = defaultRunnerArtifactRoot()) {
|
|
35997
36062
|
return sweepOrphanedRunArtifacts(artifactRoot);
|
|
35998
36063
|
}
|
|
35999
|
-
async function recoverRecordedRunAssignments(registryRoot = defaultRunRegistryRoot(), terminate
|
|
36000
|
-
const
|
|
36001
|
-
const terminated = /* @__PURE__ */ new Set();
|
|
36002
|
-
const orderedEntries = [...entries].sort(
|
|
36003
|
-
(left, right) => Number(Boolean(right.record.identity)) - Number(Boolean(left.record.identity))
|
|
36004
|
-
);
|
|
36005
|
-
for (const { record: record2 } of orderedEntries) {
|
|
36006
|
-
const identityKey = record2.identity ? `${record2.identity.kind}:${record2.identity.nonce}` : "legacy";
|
|
36007
|
-
const witnessKey = `${record2.pid}:${identityKey}`;
|
|
36008
|
-
if (terminated.has(witnessKey)) continue;
|
|
36009
|
-
await terminate(record2.pid, record2.identity);
|
|
36010
|
-
terminated.add(witnessKey);
|
|
36011
|
-
}
|
|
36064
|
+
async function recoverRecordedRunAssignments(registryRoot = defaultRunRegistryRoot(), terminate) {
|
|
36065
|
+
const records = terminate ? await terminateRecordedRunProcesses(registryRoot, terminate) : await terminateRecordedRunProcesses(registryRoot);
|
|
36012
36066
|
const assignments = /* @__PURE__ */ new Map();
|
|
36013
|
-
for (const
|
|
36067
|
+
for (const record2 of records) {
|
|
36014
36068
|
const assignment = { taskId: record2.taskId, epoch: record2.epoch };
|
|
36015
36069
|
assignments.set(`${record2.taskId}:${record2.epoch}`, assignment);
|
|
36016
36070
|
}
|