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