@zixt/host 0.0.136 → 0.0.137

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.
Files changed (2) hide show
  1. package/dist/index.js +750 -179
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ import { homedir as homedir4 } from "node:os";
28
28
  // package.json
29
29
  var package_default = {
30
30
  name: "@zixt/host",
31
- version: "0.0.136",
31
+ version: "0.0.137",
32
32
  type: "module",
33
33
  exports: {
34
34
  ".": "./src/client.ts",
@@ -22562,6 +22562,12 @@ var CompanySetupProgress = external_exports.object({
22562
22562
  /** Every recommended step is done or skipped. */
22563
22563
  recommendedSettled: external_exports.boolean()
22564
22564
  });
22565
+ var COMPANY_SETUP_PLANNING_NOTE_MAX = 200;
22566
+ var COMPANY_SETUP_PLANNING_NOTES_MAX = 20;
22567
+ var CompanySetupPlanningNote = external_exports.object({
22568
+ at: IsoDate,
22569
+ text: external_exports.string().trim().min(1).max(COMPANY_SETUP_PLANNING_NOTE_MAX)
22570
+ });
22565
22571
  var CompanySetupApproval = external_exports.object({
22566
22572
  at: IsoDate,
22567
22573
  by: MemberId.nullable(),
@@ -22614,7 +22620,12 @@ var CompanySetupView = external_exports.object({
22614
22620
  * The plan is being proposed in the background (a planner call can take a
22615
22621
  * minute); the page polls until the plan lands or an error is recorded.
22616
22622
  */
22617
- planning: external_exports.object({ startedAt: IsoDate, error: external_exports.string().max(500).nullable() }).nullable(),
22623
+ planning: external_exports.object({
22624
+ startedAt: IsoDate,
22625
+ error: external_exports.string().max(500).nullable(),
22626
+ /** Oldest first; the planner's visible decisions so far. */
22627
+ notes: external_exports.array(CompanySetupPlanningNote).max(COMPANY_SETUP_PLANNING_NOTES_MAX).default([])
22628
+ }).nullable(),
22618
22629
  approval: CompanySetupApproval.nullable(),
22619
22630
  steps: external_exports.array(CompanySetupStep),
22620
22631
  progress: CompanySetupProgress,
@@ -31705,11 +31716,11 @@ function createPlaywrightBrowserAdapterFactory(dependencies = {}) {
31705
31716
  }
31706
31717
 
31707
31718
  // src/runners/cli-runner.ts
31708
- import { spawn as spawn10 } from "node:child_process";
31709
- import { randomUUID as randomUUID11 } from "node:crypto";
31710
- import { lstat as lstat11, mkdir as mkdir12, realpath as realpath8 } from "node:fs/promises";
31719
+ import { spawn as spawn11 } from "node:child_process";
31720
+ import { randomUUID as randomUUID12 } from "node:crypto";
31721
+ import { lstat as lstat11, mkdir as mkdir13, realpath as realpath8 } from "node:fs/promises";
31711
31722
  import { homedir as homedir7 } from "node:os";
31712
- import { dirname as dirname10, isAbsolute as isAbsolute16, join as join18, resolve as resolve10 } from "node:path";
31723
+ import { dirname as dirname10, isAbsolute as isAbsolute16, join as join19, resolve as resolve10 } from "node:path";
31713
31724
 
31714
31725
  // src/tool-packs/browser/authentication-wall.ts
31715
31726
  var AUTH_PATH_SEGMENT = /(?:^|\/)(?:log[-_]?in|sign[-_]?in|sso|saml|auth|authorize|authenticate|oauth2?|session\/new|checkpoint)(?:\/|$)/i;
@@ -38096,6 +38107,415 @@ function renderAttachmentSection(files) {
38096
38107
 
38097
38108
  // src/runners/ask-user-server.ts
38098
38109
  import { createServer as createServer2 } from "node:http";
38110
+
38111
+ // src/runners/software-install.ts
38112
+ import { spawn as spawn9 } from "node:child_process";
38113
+ import { createHash as createHash6, randomUUID as randomUUID11 } from "node:crypto";
38114
+ import { chmod as chmod6, mkdir as mkdir11, readFile as readFile10, readdir as readdir5, rename as rename6, rm as rm10, symlink as symlink2, writeFile as writeFile7 } from "node:fs/promises";
38115
+ import { join as join17, relative as relative8, resolve as resolvePath, sep as sep5 } from "node:path";
38116
+ var SOFTWARE_INSTALL_METHODS = ["npm", "download"];
38117
+ var SOFTWARE_INSTALL_REFUSAL = "Zixt installs software only into its own folder on this Machine, using npm or a direct download. It never runs a system package manager, never asks for elevation, and never builds from source. Tell the person plainly what is missing, why this work needs it, and the exact command they would run themselves.";
38118
+ var MAX_DOWNLOAD_BYTES = 512 * 1024 * 1024;
38119
+ var PROCESS_TIMEOUT_MS = 10 * 6e4;
38120
+ var DOWNLOAD_TIMEOUT_MS = 10 * 6e4;
38121
+ var FAILURE_COOLDOWN_MS2 = 10 * 6e4;
38122
+ var MAX_ARCHIVE_SEARCH_DEPTH = 5;
38123
+ var MANIFEST_NAME = "installed.json";
38124
+ var COMMAND_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$/;
38125
+ var NPM_SPEC_PATTERN = /^(?:@[a-z0-9~][a-z0-9._~-]*\/)?[a-z0-9~][a-z0-9._~-]*(?:@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)?$/;
38126
+ function defaultSoftwareToolsRoot() {
38127
+ return defaultRunnerToolsRoot();
38128
+ }
38129
+ function softwareSearchPathEntries(toolsRoot = defaultSoftwareToolsRoot()) {
38130
+ return [toolsRoot, join17(toolsRoot, "bin")];
38131
+ }
38132
+ function refuseSoftwareRequest(request) {
38133
+ if (!COMMAND_PATTERN.test(request.command)) {
38134
+ return "The command name must be a plain executable name with no path, spaces, or shell characters.";
38135
+ }
38136
+ if (!SOFTWARE_INSTALL_METHODS.includes(request.method)) {
38137
+ return SOFTWARE_INSTALL_REFUSAL;
38138
+ }
38139
+ if (request.method === "npm") {
38140
+ if (!NPM_SPEC_PATTERN.test(request.source)) {
38141
+ return "The npm source must be a package name, optionally with one exact version as name@1.2.3. Ranges, tags, git specs, URLs, and local paths are not installed.";
38142
+ }
38143
+ if (request.sha256 !== void 0) {
38144
+ return "An expected sha256 applies only to downloads; the npm registry names versions, not file digests.";
38145
+ }
38146
+ return null;
38147
+ }
38148
+ let url3;
38149
+ try {
38150
+ url3 = new URL(request.source);
38151
+ } catch {
38152
+ return "The download source must be an absolute HTTPS URL.";
38153
+ }
38154
+ if (url3.protocol !== "https:") return "The download source must use HTTPS.";
38155
+ if (url3.username || url3.password) {
38156
+ return "The download source must not carry credentials in the URL.";
38157
+ }
38158
+ if (request.sha256 !== void 0 && !/^[a-f0-9]{64}$/i.test(request.sha256)) {
38159
+ return "The expected sha256 must be 64 hexadecimal characters.";
38160
+ }
38161
+ return null;
38162
+ }
38163
+ async function runProcess(plan, label) {
38164
+ const env = sanitizedInstallerEnv(process.env);
38165
+ await new Promise((resolveRun, rejectRun) => {
38166
+ const child = plan.shellLine !== void 0 ? spawn9(plan.shellLine, {
38167
+ ...plan.cwd ? { cwd: plan.cwd } : {},
38168
+ env,
38169
+ // stdin stays closed; output reaches the ordinary worker console, so
38170
+ // progress and failure land on the Host console like a runner install.
38171
+ stdio: ["ignore", "inherit", "inherit"],
38172
+ windowsHide: true,
38173
+ shell: true
38174
+ }) : spawn9(plan.command, plan.args, {
38175
+ ...plan.cwd ? { cwd: plan.cwd } : {},
38176
+ env,
38177
+ stdio: ["ignore", "inherit", "inherit"],
38178
+ windowsHide: true
38179
+ });
38180
+ let settled = false;
38181
+ const finish = (error52) => {
38182
+ if (settled) return;
38183
+ settled = true;
38184
+ clearTimeout(timer);
38185
+ if (error52) rejectRun(error52);
38186
+ else resolveRun();
38187
+ };
38188
+ const timer = setTimeout(() => {
38189
+ child.kill();
38190
+ finish(new Error(`${label} did not finish within 10 minutes`));
38191
+ }, PROCESS_TIMEOUT_MS);
38192
+ timer.unref?.();
38193
+ child.once("error", (error52) => finish(error52));
38194
+ child.once("exit", (code, signal) => {
38195
+ if (code === 0) finish();
38196
+ else if (signal) finish(new Error(`${label} stopped with ${signal}`));
38197
+ else finish(new Error(`${label} exited with code ${code ?? "unknown"}`));
38198
+ });
38199
+ });
38200
+ }
38201
+ async function resolveTarCommand(platform) {
38202
+ if (platform !== "win32") return "tar";
38203
+ const root = process.env["SYSTEMROOT"] ?? process.env["WINDIR"];
38204
+ if (!root) return "tar";
38205
+ return await resolveTrustedCliCommand(join17(root, "System32", "tar.exe"), { platform }) ?? "tar";
38206
+ }
38207
+ function archiveKindFor(url3) {
38208
+ let path;
38209
+ try {
38210
+ path = new URL(url3).pathname.toLowerCase();
38211
+ } catch {
38212
+ return "binary";
38213
+ }
38214
+ if (/\.(?:tar\.(?:gz|xz|bz2)|tgz|tbz2|txz|tar)$/.test(path)) return "tar";
38215
+ if (path.endsWith(".zip")) return "zip";
38216
+ return "binary";
38217
+ }
38218
+ async function findExtractedExecutable(root, command, platform = process.platform) {
38219
+ const names = platform === "win32" ? [`${command}.exe`, `${command}.cmd`, `${command}.bat`, command] : [command];
38220
+ let frontier = [root];
38221
+ for (let depth = 0; depth <= MAX_ARCHIVE_SEARCH_DEPTH && frontier.length > 0; depth += 1) {
38222
+ const next = [];
38223
+ for (const directory of frontier) {
38224
+ let entries;
38225
+ try {
38226
+ entries = await readdir5(directory, { withFileTypes: true });
38227
+ } catch {
38228
+ continue;
38229
+ }
38230
+ for (const name of names) {
38231
+ if (entries.some((entry) => entry.name === name && entry.isFile())) {
38232
+ return join17(directory, name);
38233
+ }
38234
+ }
38235
+ for (const entry of entries) {
38236
+ if (entry.isDirectory()) next.push(join17(directory, entry.name));
38237
+ }
38238
+ }
38239
+ frontier = next;
38240
+ }
38241
+ return null;
38242
+ }
38243
+ function withinRoot(root, candidate) {
38244
+ const base = resolvePath(root);
38245
+ const target = resolvePath(candidate);
38246
+ return target === base || target.startsWith(base.endsWith(sep5) ? base : `${base}${sep5}`);
38247
+ }
38248
+ function downloadedBinaryName(command, source, platform = process.platform) {
38249
+ if (platform !== "win32") return command;
38250
+ const extension = /(\.[A-Za-z0-9]{1,8})$/.exec(new URL(source).pathname)?.[1]?.toLowerCase();
38251
+ if (extension !== ".exe" && extension !== ".com") {
38252
+ throw new Error(
38253
+ "a direct Windows download must be a .exe or .com file; use the tool\u2019s .zip release instead"
38254
+ );
38255
+ }
38256
+ return `${command}${extension}`;
38257
+ }
38258
+ async function placeDownloadedPayload(body, request, options) {
38259
+ const platform = options.platform ?? process.platform;
38260
+ const { toolsRoot } = options;
38261
+ const staging = join17(toolsRoot, ".staging", randomUUID11());
38262
+ const stagedPackage = join17(staging, "pkg");
38263
+ const packageRoot = join17(toolsRoot, "pkgs", request.command);
38264
+ const binDirectory = platform === "win32" ? toolsRoot : join17(toolsRoot, "bin");
38265
+ const kind = archiveKindFor(request.source);
38266
+ await mkdir11(stagedPackage, { recursive: true, mode: 448 });
38267
+ try {
38268
+ if (kind === "binary") {
38269
+ await writeFile7(
38270
+ join17(stagedPackage, downloadedBinaryName(request.command, request.source, platform)),
38271
+ body,
38272
+ {
38273
+ mode: 448
38274
+ }
38275
+ );
38276
+ } else {
38277
+ await writeFile7(join17(staging, "archive"), body, { mode: 448 });
38278
+ if (kind === "tar" || platform !== "linux") {
38279
+ await runProcess(
38280
+ {
38281
+ command: await resolveTarCommand(platform),
38282
+ args: ["-xf", "archive", "-C", "pkg"],
38283
+ cwd: staging
38284
+ },
38285
+ "the archive extraction"
38286
+ );
38287
+ } else {
38288
+ await runProcess(
38289
+ { command: "unzip", args: ["-q", "-o", "archive", "-d", "pkg"], cwd: staging },
38290
+ "the archive extraction"
38291
+ );
38292
+ }
38293
+ await rm10(join17(staging, "archive"), { force: true }).catch(() => void 0);
38294
+ }
38295
+ const stagedExecutable = await findExtractedExecutable(
38296
+ stagedPackage,
38297
+ request.command,
38298
+ platform
38299
+ );
38300
+ if (!stagedExecutable) {
38301
+ throw new Error(`the download did not contain an executable named ${request.command}`);
38302
+ }
38303
+ if (!withinRoot(stagedPackage, stagedExecutable)) {
38304
+ throw new Error("the archive tried to place its executable outside the Zixt folder");
38305
+ }
38306
+ await mkdir11(join17(toolsRoot, "pkgs"), { recursive: true, mode: 448 });
38307
+ await rm10(packageRoot, { recursive: true, force: true });
38308
+ await rename6(stagedPackage, packageRoot);
38309
+ const executable = join17(packageRoot, relative8(stagedPackage, stagedExecutable));
38310
+ await mkdir11(binDirectory, { recursive: true, mode: 448 });
38311
+ if (platform === "win32") {
38312
+ const shim = join17(binDirectory, `${request.command}.cmd`);
38313
+ await writeFile7(shim, `@echo off\r
38314
+ ${quoteForCmd(executable)} %*\r
38315
+ `, {
38316
+ encoding: "utf8",
38317
+ mode: 448
38318
+ });
38319
+ return shim;
38320
+ }
38321
+ await chmod6(executable, 493);
38322
+ const link = join17(binDirectory, request.command);
38323
+ await rm10(link, { force: true });
38324
+ await symlink2(executable, link);
38325
+ return link;
38326
+ } finally {
38327
+ await rm10(staging, { recursive: true, force: true }).catch(() => void 0);
38328
+ }
38329
+ }
38330
+ function createSoftwareInstaller(options = {}) {
38331
+ const platform = options.platform ?? process.platform;
38332
+ const toolsRoot = options.toolsRoot ?? defaultSoftwareToolsRoot();
38333
+ const now = options.now ?? Date.now;
38334
+ const manifestPath = join17(toolsRoot, MANIFEST_NAME);
38335
+ const inFlight = /* @__PURE__ */ new Map();
38336
+ const failures = /* @__PURE__ */ new Map();
38337
+ const searchPath = () => {
38338
+ const inherited = options.resolution?.searchPath ?? process.env.PATH ?? "";
38339
+ const separator = platform === "win32" ? ";" : ":";
38340
+ return [inherited, ...softwareSearchPathEntries(toolsRoot)].filter(Boolean).join(separator);
38341
+ };
38342
+ const resolveCommand = async (command) => resolveTrustedCliCommand(command, {
38343
+ ...options.resolution ?? {},
38344
+ platform,
38345
+ searchPath: searchPath()
38346
+ });
38347
+ const readManifest = async () => {
38348
+ try {
38349
+ const parsed = JSON.parse(await readFile10(manifestPath, "utf8"));
38350
+ return Array.isArray(parsed) ? parsed : [];
38351
+ } catch {
38352
+ return [];
38353
+ }
38354
+ };
38355
+ let manifestTurn = Promise.resolve();
38356
+ const recordInstall = (record2) => {
38357
+ const write = manifestTurn.then(async () => {
38358
+ const existing = (await readManifest()).filter((entry) => entry.command !== record2.command);
38359
+ const staged = `${manifestPath}.${randomUUID11()}`;
38360
+ await writeFile7(staged, `${JSON.stringify([...existing, record2], null, 2)}
38361
+ `, {
38362
+ encoding: "utf8",
38363
+ mode: 384
38364
+ });
38365
+ await rename6(staged, manifestPath);
38366
+ });
38367
+ manifestTurn = write.catch(() => void 0);
38368
+ return write;
38369
+ };
38370
+ const installFromNpm = async (request) => {
38371
+ await mkdir11(toolsRoot, { recursive: true, mode: 448 });
38372
+ const npm = await resolveInstallerCommand({ platform });
38373
+ const args = [
38374
+ "install",
38375
+ "-g",
38376
+ "--prefix",
38377
+ toolsRoot,
38378
+ "--no-audit",
38379
+ "--no-fund",
38380
+ "--ignore-scripts",
38381
+ // `--` ends npm's flag parsing, so even a spec that slipped the pattern
38382
+ // could not become an npm option.
38383
+ "--",
38384
+ request.source
38385
+ ];
38386
+ if (platform === "win32") {
38387
+ await runProcess(
38388
+ { shellLine: windowsInstallerCommandLine(npm, args), cwd: toolsRoot },
38389
+ "the npm install"
38390
+ );
38391
+ } else {
38392
+ await runProcess({ command: npm, args, cwd: toolsRoot }, "the npm install");
38393
+ }
38394
+ };
38395
+ const installFromDownload = async (request) => {
38396
+ const response = await fetch(request.source, {
38397
+ redirect: "follow",
38398
+ signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
38399
+ });
38400
+ if (!response.ok) throw new Error(`the download answered HTTP ${response.status}`);
38401
+ if (response.url && !response.url.startsWith("https:")) {
38402
+ throw new Error("the download redirected off HTTPS and was not installed");
38403
+ }
38404
+ const declared = Number(response.headers.get("content-length") ?? "0");
38405
+ if (Number.isFinite(declared) && declared > MAX_DOWNLOAD_BYTES) {
38406
+ throw new Error("the download is larger than Zixt installs unattended");
38407
+ }
38408
+ if (!response.body) throw new Error("the download had no body");
38409
+ const reader = response.body.getReader();
38410
+ const chunks = [];
38411
+ let total = 0;
38412
+ for (; ; ) {
38413
+ const { done, value } = await reader.read();
38414
+ if (done) break;
38415
+ total += value.byteLength;
38416
+ if (total > MAX_DOWNLOAD_BYTES) {
38417
+ await reader.cancel().catch(() => void 0);
38418
+ throw new Error("the download is larger than Zixt installs unattended");
38419
+ }
38420
+ chunks.push(Buffer.from(value));
38421
+ }
38422
+ const body = Buffer.concat(chunks);
38423
+ if (body.length === 0) throw new Error("the download was empty");
38424
+ const digest = createHash6("sha256").update(body).digest("hex");
38425
+ if (request.sha256 && digest !== request.sha256.toLowerCase()) {
38426
+ throw new Error("the download did not match the expected sha256");
38427
+ }
38428
+ await placeDownloadedPayload(body, request, { toolsRoot, platform });
38429
+ };
38430
+ const perform = options.perform ?? (async (request) => {
38431
+ if (request.method === "npm") await installFromNpm(request);
38432
+ else await installFromDownload(request);
38433
+ const resolved = await resolveCommand(request.command);
38434
+ if (!resolved) {
38435
+ throw new Error(
38436
+ `the install finished but ${request.command} is still not runnable on this Machine`
38437
+ );
38438
+ }
38439
+ return resolved;
38440
+ });
38441
+ const attempt = async (request) => {
38442
+ options.onEvent?.({ command: request.command, state: "started" });
38443
+ try {
38444
+ const path = await perform(request, toolsRoot);
38445
+ if (!withinRoot(toolsRoot, path)) {
38446
+ throw new Error(
38447
+ `${request.command} resolves outside the Zixt folder and was not installed by Zixt`
38448
+ );
38449
+ }
38450
+ const record2 = {
38451
+ command: request.command,
38452
+ method: request.method,
38453
+ source: request.source,
38454
+ path,
38455
+ sha256: request.sha256 ? request.sha256.toLowerCase() : null,
38456
+ installedAt: new Date(now()).toISOString()
38457
+ };
38458
+ await recordInstall(record2).catch(() => void 0);
38459
+ options.onEvent?.({ command: request.command, state: "completed" });
38460
+ return { ok: true, record: record2 };
38461
+ } catch (error52) {
38462
+ const message = error52 instanceof Error ? error52.message : "unknown software install error";
38463
+ options.onEvent?.({ command: request.command, state: "failed", error: message });
38464
+ return { ok: false, error: message };
38465
+ }
38466
+ };
38467
+ return {
38468
+ toolsRoot,
38469
+ async probe(command) {
38470
+ if (!COMMAND_PATTERN.test(command)) {
38471
+ return { command, present: false, path: null, managed: false };
38472
+ }
38473
+ const path = await resolveCommand(command);
38474
+ return {
38475
+ command,
38476
+ present: path !== null,
38477
+ path,
38478
+ managed: path !== null && withinRoot(toolsRoot, path)
38479
+ };
38480
+ },
38481
+ async install(request) {
38482
+ const refusal = refuseSoftwareRequest(request);
38483
+ if (refusal) return { ok: false, error: refusal };
38484
+ const key = [
38485
+ request.command,
38486
+ request.method,
38487
+ request.source,
38488
+ request.sha256?.toLowerCase() ?? ""
38489
+ ].join("\n");
38490
+ const running = inFlight.get(key);
38491
+ if (running) return running;
38492
+ const failure2 = failures.get(key);
38493
+ if (failure2 && now() - failure2.at < FAILURE_COOLDOWN_MS2) {
38494
+ return {
38495
+ ok: false,
38496
+ error: `This exact install already failed on this Machine: ${failure2.error}. Do not repeat it. Finish the work another way, or tell the person what is missing and the command they would run.`
38497
+ };
38498
+ }
38499
+ const started = (async () => {
38500
+ const result = await attempt(request);
38501
+ if (result.ok) failures.delete(key);
38502
+ else failures.set(key, { at: now(), error: result.error });
38503
+ return result;
38504
+ })();
38505
+ inFlight.set(key, started);
38506
+ try {
38507
+ return await started;
38508
+ } finally {
38509
+ inFlight.delete(key);
38510
+ }
38511
+ },
38512
+ async installed() {
38513
+ return readManifest();
38514
+ }
38515
+ };
38516
+ }
38517
+
38518
+ // src/runners/ask-user-server.ts
38099
38519
  var CADENCE_PROPS = {
38100
38520
  cadence_kind: {
38101
38521
  type: "string",
@@ -38211,6 +38631,55 @@ var TOOLS = [
38211
38631
  additionalProperties: false
38212
38632
  }
38213
38633
  },
38634
+ {
38635
+ name: "check_software",
38636
+ description: "Check whether a command is already available on this Machine before assuming it is missing. Returns whether it is present, where it resolved, and whether Zixt is the one that installed it.",
38637
+ inputSchema: {
38638
+ type: "object",
38639
+ properties: {
38640
+ command: {
38641
+ type: "string",
38642
+ description: "The plain command name, for example ffmpeg or gh. No path, no arguments."
38643
+ }
38644
+ },
38645
+ required: ["command"],
38646
+ additionalProperties: false
38647
+ }
38648
+ },
38649
+ {
38650
+ name: "install_software",
38651
+ description: "Install a command this Machine is missing, into the folder Zixt owns, after the person approves it. Prefer not to call this. Installing software on someone else\u2019s computer is friction, so first try to finish the work with what the Machine already has: a different tool, a project dependency, or another approach. Call it only when the missing command genuinely blocks the work or is clearly the right way to do it, and never speculatively. Zixt can install an npm package, or download and unpack a release from an HTTPS URL. It cannot use a system package manager such as apt, brew, winget, or choco, cannot ask for elevation or sudo, and cannot build from source. When the tool needs any of those, or the install looks complicated, do not call this: tell the person what is missing, why this work needs it, and the exact command they would run on their operating system. The person sees your reason and the exact source before anything runs, and may say no.",
38652
+ inputSchema: {
38653
+ type: "object",
38654
+ properties: {
38655
+ command: {
38656
+ type: "string",
38657
+ description: "The plain command name the work needs on PATH, for example ffmpeg."
38658
+ },
38659
+ reason: {
38660
+ type: "string",
38661
+ minLength: 1,
38662
+ maxLength: 500,
38663
+ description: "Why this work needs it, in one plain sentence. The person reads this before deciding."
38664
+ },
38665
+ method: {
38666
+ type: "string",
38667
+ enum: [...SOFTWARE_INSTALL_METHODS],
38668
+ description: "npm for an npm package; download for a release archive or binary."
38669
+ },
38670
+ source: {
38671
+ type: "string",
38672
+ description: "npm: the package name, optionally name@version with an exact version. download: the HTTPS URL of the release archive or binary."
38673
+ },
38674
+ sha256: {
38675
+ type: "string",
38676
+ description: "download only: the published SHA-256 of that file, when the project publishes one. Include it whenever you can."
38677
+ }
38678
+ },
38679
+ required: ["command", "reason", "method", "source"],
38680
+ additionalProperties: false
38681
+ }
38682
+ },
38214
38683
  {
38215
38684
  name: "publish_file",
38216
38685
  description: "Snapshot a file you created in this Task workspace into Zixt so the user can download it. This only creates the Zixt file; it does not send it anywhere externally. The result returns an artifact_id.",
@@ -38996,6 +39465,57 @@ function createAskUserServer() {
38996
39465
  }
38997
39466
  return;
38998
39467
  }
39468
+ if (surface.platform && (name === "check_software" || name === "install_software")) {
39469
+ if (!handlers.software) {
39470
+ toolText("Software checks are unavailable for this runner.", true);
39471
+ return;
39472
+ }
39473
+ const command = typeof args["command"] === "string" ? args["command"].trim() : "";
39474
+ if (!command) {
39475
+ toolText("missing required argument `command`", true);
39476
+ return;
39477
+ }
39478
+ try {
39479
+ if (name === "check_software") {
39480
+ toolText(JSON.stringify(await handlers.software.check(command), null, 2));
39481
+ return;
39482
+ }
39483
+ const reason = typeof args["reason"] === "string" ? args["reason"].trim() : "";
39484
+ const method = typeof args["method"] === "string" ? args["method"] : "";
39485
+ const source = typeof args["source"] === "string" ? args["source"].trim() : "";
39486
+ const sha256 = typeof args["sha256"] === "string" && args["sha256"] ? args["sha256"] : void 0;
39487
+ if (!reason) {
39488
+ toolText(
39489
+ "missing required argument `reason`: the person decides by reading why this work needs it",
39490
+ true
39491
+ );
39492
+ return;
39493
+ }
39494
+ if (!SOFTWARE_INSTALL_METHODS.includes(method)) {
39495
+ toolText(`\`method\` must be one of ${SOFTWARE_INSTALL_METHODS.join(", ")}.`, true);
39496
+ return;
39497
+ }
39498
+ const request = {
39499
+ command,
39500
+ method,
39501
+ source,
39502
+ sha256
39503
+ };
39504
+ const refusal = refuseSoftwareRequest(request);
39505
+ if (refusal) {
39506
+ toolText(refusal, true);
39507
+ return;
39508
+ }
39509
+ const outcome = await handlers.software.install({ ...request, reason });
39510
+ toolText(outcome.detail, !outcome.ok);
39511
+ } catch (err) {
39512
+ toolText(
39513
+ `The software request could not be completed: ${String(err instanceof Error ? err.message : err)}`,
39514
+ true
39515
+ );
39516
+ }
39517
+ return;
39518
+ }
38999
39519
  if (surface.platform && name === "publish_file") {
39000
39520
  if (!handlers.publishFile) {
39001
39521
  toolText("file publishing is unavailable for this runner", true);
@@ -39111,6 +39631,7 @@ function createAskUserServer() {
39111
39631
  agentOp: input.agentOp,
39112
39632
  ..."requestApproval" in input && input.requestApproval ? { requestApproval: input.requestApproval } : {},
39113
39633
  ..."publishFile" in input && input.publishFile ? { publishFile: input.publishFile } : {},
39634
+ ..."software" in input && input.software ? { software: input.software } : {},
39114
39635
  ...toolPacks.length > 0 ? { toolPacks } : {}
39115
39636
  };
39116
39637
  const toolOwners = /* @__PURE__ */ new Map();
@@ -39263,7 +39784,10 @@ function buildRunnerEnv(input) {
39263
39784
  env[name] = value;
39264
39785
  }
39265
39786
  }
39266
- const searchPath = sanitizeInheritedSearchPath(inheritedValue(input.inherited, "PATH"));
39787
+ const searchPath = [
39788
+ sanitizeInheritedSearchPath(inheritedValue(input.inherited, "PATH")),
39789
+ ...(input.softwareToolsPath ?? []).filter((entry) => isAbsolute14(entry))
39790
+ ].filter((entry) => entry !== "").join(delimiter2);
39267
39791
  const gitConfig = input.githubShell ? [
39268
39792
  ["credential.helper", ""],
39269
39793
  ["credential.helper", input.githubShell.gitCredentialHelper],
@@ -39330,9 +39854,9 @@ function buildRunnerEnv(input) {
39330
39854
  // src/runners/github-shell-auth.ts
39331
39855
  import { execFile } from "node:child_process";
39332
39856
  import { randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
39333
- import { chmod as chmod6, lstat as lstat10, mkdir as mkdir11, realpath as realpath7, writeFile as writeFile7 } from "node:fs/promises";
39857
+ import { chmod as chmod7, lstat as lstat10, mkdir as mkdir12, realpath as realpath7, writeFile as writeFile8 } from "node:fs/promises";
39334
39858
  import { createServer as createServer3 } from "node:http";
39335
- import { isAbsolute as isAbsolute15, join as join17, relative as relative8 } from "node:path";
39859
+ import { isAbsolute as isAbsolute15, join as join18, relative as relative9 } from "node:path";
39336
39860
  var MAX_REQUEST_BYTES2 = 16 * 1024;
39337
39861
  var DIRECTORY_MODE4 = 448;
39338
39862
  var PRIVATE_FILE_MODE = 384;
@@ -39696,7 +40220,7 @@ function activationCredential(grant, now = Date.now()) {
39696
40220
  return expiresAt.getTime() <= now ? null : { accessToken: grant.accessToken, expiresAt };
39697
40221
  }
39698
40222
  function assertChildPath2(parent, child) {
39699
- const path = relative8(parent, child);
40223
+ const path = relative9(parent, child);
39700
40224
  if (!path || path === ".." || path.startsWith("../") || path.startsWith("..\\") || isAbsolute15(path)) {
39701
40225
  throw new Error("GitHub shell helper path escaped its private run directory");
39702
40226
  }
@@ -39708,11 +40232,11 @@ function quoteForPosixShell(value) {
39708
40232
  return quoteForGitShell2(value);
39709
40233
  }
39710
40234
  async function writePrivate(path, content, executable = false) {
39711
- await writeFile7(path, content, {
40235
+ await writeFile8(path, content, {
39712
40236
  flag: "wx",
39713
40237
  mode: executable ? EXECUTABLE_FILE_MODE : PRIVATE_FILE_MODE
39714
40238
  });
39715
- await chmod6(path, executable ? EXECUTABLE_FILE_MODE : PRIVATE_FILE_MODE);
40239
+ await chmod7(path, executable ? EXECUTABLE_FILE_MODE : PRIVATE_FILE_MODE);
39716
40240
  }
39717
40241
  async function prepareHelpers(input) {
39718
40242
  const rootEntry = await lstat10(input.runRoot);
@@ -39720,7 +40244,7 @@ async function prepareHelpers(input) {
39720
40244
  throw new Error("GitHub shell authentication requires a private real run directory");
39721
40245
  }
39722
40246
  const runRoot = await realpath7(input.runRoot);
39723
- const helperPath = join17(runRoot, "github-shell-git-credential.cjs");
40247
+ const helperPath = join18(runRoot, "github-shell-git-credential.cjs");
39724
40248
  assertChildPath2(runRoot, helperPath);
39725
40249
  await writePrivate(helperPath, GIT_HELPER_SOURCE);
39726
40250
  if (!input.ghExecutablePath) {
@@ -39731,14 +40255,14 @@ async function prepareHelpers(input) {
39731
40255
  wrapperSourcePath: null
39732
40256
  };
39733
40257
  }
39734
- const shellToolsDirectory = join17(runRoot, "shell-tools");
40258
+ const shellToolsDirectory = join18(runRoot, "shell-tools");
39735
40259
  assertChildPath2(runRoot, shellToolsDirectory);
39736
- await mkdir11(shellToolsDirectory, { mode: DIRECTORY_MODE4 });
39737
- await chmod6(shellToolsDirectory, DIRECTORY_MODE4);
39738
- const wrapperSourcePath = join17(runRoot, "github-shell-gh-wrapper.cjs");
40260
+ await mkdir12(shellToolsDirectory, { mode: DIRECTORY_MODE4 });
40261
+ await chmod7(shellToolsDirectory, DIRECTORY_MODE4);
40262
+ const wrapperSourcePath = join18(runRoot, "github-shell-gh-wrapper.cjs");
39739
40263
  assertChildPath2(runRoot, wrapperSourcePath);
39740
40264
  await writePrivate(wrapperSourcePath, GH_WRAPPER_SOURCE);
39741
- const wrapperPath = join17(shellToolsDirectory, process.platform === "win32" ? "gh.cmd" : "gh");
40265
+ const wrapperPath = join18(shellToolsDirectory, process.platform === "win32" ? "gh.cmd" : "gh");
39742
40266
  assertChildPath2(runRoot, wrapperPath);
39743
40267
  const launcher = process.platform === "win32" ? `@"${process.execPath.replaceAll('"', '""')}" "${wrapperSourcePath.replaceAll('"', '""')}" %*\r
39744
40268
  ` : `#!/bin/sh
@@ -39961,7 +40485,7 @@ password=${credential.accessToken}
39961
40485
  }
39962
40486
 
39963
40487
  // src/runners/working-context.ts
39964
- import { spawn as spawn9 } from "node:child_process";
40488
+ import { spawn as spawn10 } from "node:child_process";
39965
40489
  import { resolve as resolve9 } from "node:path";
39966
40490
  var COMMAND_TIMEOUT_MS = 5e3;
39967
40491
  var OUTPUT_LIMIT_BYTES = 128 * 1024;
@@ -40092,7 +40616,7 @@ async function stopWorkingContextCommand(child, childExited, options = {}) {
40092
40616
  function run(command, args, cwd, env, signal) {
40093
40617
  if (signal?.aborted) return Promise.resolve(null);
40094
40618
  return new Promise((resolvePromise) => {
40095
- const child = spawn9(command, [...args], {
40619
+ const child = spawn10(command, [...args], {
40096
40620
  cwd,
40097
40621
  env: { ...env, GIT_OPTIONAL_LOCKS: "0" },
40098
40622
  detached: process.platform !== "win32",
@@ -40510,6 +41034,7 @@ var WorkingContextPullRequestCache = class {
40510
41034
  };
40511
41035
 
40512
41036
  // src/runners/cli-runner.ts
41037
+ var softwareInstaller = createSoftwareInstaller();
40513
41038
  var PRIVATE_CLEANUP_FAILURE = "Private runner cleanup could not be completed. Restart the Zixt Host before accepting more work.";
40514
41039
  var PROCESS_CLEANUP_FAILURE = "Runner process cleanup could not be confirmed. Restart the Zixt Host before accepting more work.";
40515
41040
  var PROVIDER_CLEANUP_FAILURE = "Provider tool cleanup could not be completed. Restart the Zixt Host before accepting more work.";
@@ -40532,7 +41057,7 @@ async function settlesWithin(promise2, timeoutMs) {
40532
41057
  }
40533
41058
  }
40534
41059
  function defaultRunnerWorkspaceRoot() {
40535
- return join18(homedir7(), ".zixt", "workspaces");
41060
+ return join19(homedir7(), ".zixt", "workspaces");
40536
41061
  }
40537
41062
  function defaultRunnerArtifactRoot() {
40538
41063
  return defaultRunArtifactRoot();
@@ -40581,7 +41106,7 @@ function createCliRunner(adapter, opts = {}) {
40581
41106
  const prefixArgs = opts.commandPrefixArgs ?? [];
40582
41107
  const maxWallTimeMs = opts.maxWallTimeMs;
40583
41108
  const workspaceRoot = opts.workspaceRoot ?? defaultRunnerWorkspaceRoot();
40584
- const artifactRoot = opts.artifactRoot ?? (opts.workspaceRoot === void 0 ? defaultRunnerArtifactRoot() : join18(dirname10(workspaceRoot), "run-artifacts"));
41109
+ const artifactRoot = opts.artifactRoot ?? (opts.workspaceRoot === void 0 ? defaultRunnerArtifactRoot() : join19(dirname10(workspaceRoot), "run-artifacts"));
40585
41110
  const runRegistryRoot2 = opts.runRegistryRoot ?? defaultRunRegistryRoot();
40586
41111
  const createArtifacts = opts.createArtifacts ?? createRunArtifacts;
40587
41112
  const toolPackRegistry2 = opts.toolPackRegistry ?? createDefaultToolPackRegistry();
@@ -40599,7 +41124,7 @@ function createCliRunner(adapter, opts = {}) {
40599
41124
  };
40600
41125
  const askUserServer = createAskUserServer();
40601
41126
  const windowsRoot = process.env.SystemRoot ?? process.env.WINDIR;
40602
- const windowsComspecCandidate = process.platform === "win32" && windowsRoot ? join18(windowsRoot, "System32", "cmd.exe") : void 0;
41127
+ const windowsComspecCandidate = process.platform === "win32" && windowsRoot ? join19(windowsRoot, "System32", "cmd.exe") : void 0;
40603
41128
  let safetyFailure;
40604
41129
  return async (task) => {
40605
41130
  if (safetyFailure) {
@@ -40639,8 +41164,8 @@ function createCliRunner(adapter, opts = {}) {
40639
41164
  usage: { inputTokens: 0, outputTokens: 0 }
40640
41165
  };
40641
41166
  }
40642
- const taskRoot = join18(workspaceRoot, task.agentId);
40643
- await mkdir12(taskRoot, { recursive: true });
41167
+ const taskRoot = join19(workspaceRoot, task.agentId);
41168
+ await mkdir13(taskRoot, { recursive: true });
40644
41169
  if (task.cancelledNow()) return cancelledBeforeRun();
40645
41170
  const configuredWorkspace = task.spec.workspace;
40646
41171
  let cwd = taskRoot;
@@ -40687,7 +41212,7 @@ function createCliRunner(adapter, opts = {}) {
40687
41212
  summaryEvidence: "host_observed"
40688
41213
  };
40689
41214
  }
40690
- const runToken = randomUUID11();
41215
+ const runToken = randomUUID12();
40691
41216
  const artifacts = await createArtifacts({
40692
41217
  root: artifactRoot,
40693
41218
  agentId: task.agentId,
@@ -40743,7 +41268,8 @@ function createCliRunner(adapter, opts = {}) {
40743
41268
  runner: { type: adapter.type, auth: runner.auth },
40744
41269
  gitIdentity: githubCommitIdentity(providerGrants),
40745
41270
  isolation: artifacts,
40746
- githubShell
41271
+ githubShell,
41272
+ softwareToolsPath: softwareSearchPathEntries(softwareInstaller.toolsRoot)
40747
41273
  });
40748
41274
  toolPacks = await toolPackRegistry2.createInstances(providerGrants, {
40749
41275
  taskId: task.taskId,
@@ -40836,6 +41362,51 @@ function createCliRunner(adapter, opts = {}) {
40836
41362
  }
40837
41363
  },
40838
41364
  publishFile,
41365
+ // AG-4a. The person approves in the Task thread through the ordinary
41366
+ // approvals pipeline, so the wall clock pauses while they decide, and
41367
+ // a refusal is an answer the session can act on rather than a failure.
41368
+ software: {
41369
+ check: (command2) => softwareInstaller.probe(command2),
41370
+ install: async ({ reason, ...request }) => {
41371
+ pendingAsks++;
41372
+ let decision;
41373
+ try {
41374
+ decision = await task.requestApproval(
41375
+ "agent.question",
41376
+ `Install ${request.command} on this Machine: ${reason}`.slice(0, 500),
41377
+ JSON.stringify({
41378
+ command: request.command,
41379
+ reason,
41380
+ method: request.method,
41381
+ source: request.source,
41382
+ ...request.sha256 ? { sha256: request.sha256 } : {},
41383
+ folder: softwareInstaller.toolsRoot
41384
+ }).slice(0, MAX_APPROVAL_PAYLOAD)
41385
+ );
41386
+ } finally {
41387
+ pendingAsks--;
41388
+ }
41389
+ if (!decision.approved) {
41390
+ return {
41391
+ ok: false,
41392
+ detail: decision.guidance ?? `The person did not approve installing ${request.command}. Do not ask again for this task. Finish the work another way, or explain what is blocked.`
41393
+ };
41394
+ }
41395
+ task.event("status", `Installing ${request.command} on this Machine`, {
41396
+ tool: "install_software"
41397
+ });
41398
+ const result2 = await softwareInstaller.install(request);
41399
+ task.event(
41400
+ "status",
41401
+ result2.ok ? `Installed ${request.command} on this Machine` : `Could not install ${request.command} on this Machine`,
41402
+ { tool: "install_software" }
41403
+ );
41404
+ return result2.ok ? {
41405
+ ok: true,
41406
+ detail: `${request.command} is installed at ${result2.record.path} and is on your PATH. Continue the work.`
41407
+ } : { ok: false, detail: result2.error };
41408
+ }
41409
+ },
40839
41410
  agentOp: (op) => task.agentOp(op),
40840
41411
  // GitHub repository work belongs in the installed `git` and `gh`
40841
41412
  // commands backed by GithubShellAuth. Do not advertise the bundled
@@ -41060,8 +41631,8 @@ ${attachmentSection}` : prompt;
41060
41631
  }
41061
41632
  comspec = resolvedWindowsComspec;
41062
41633
  }
41063
- const exitMarker = `__ZIXT_RUNNER_EXIT_${randomUUID11()}__`;
41064
- const guardianNonce = randomUUID11();
41634
+ const exitMarker = `__ZIXT_RUNNER_EXIT_${randomUUID12()}__`;
41635
+ const guardianNonce = randomUUID12();
41065
41636
  return runCliProcess({
41066
41637
  command: resolvedCommand,
41067
41638
  args,
@@ -41418,8 +41989,8 @@ function runCliProcess(options) {
41418
41989
  }
41419
41990
  return new Promise((resolve19) => {
41420
41991
  const platform = options.platform ?? process.platform;
41421
- const containmentGateNonce = options.guardian && platform === "win32" ? randomUUID11() : void 0;
41422
- const child = options.guardian ? spawn10(
41992
+ const containmentGateNonce = options.guardian && platform === "win32" ? randomUUID12() : void 0;
41993
+ const child = options.guardian ? spawn11(
41423
41994
  options.guardian.nodeCommand,
41424
41995
  [
41425
41996
  options.guardian.scriptPath,
@@ -41707,12 +42278,12 @@ function runCliProcess(options) {
41707
42278
  }
41708
42279
 
41709
42280
  // src/runners/claude-code.ts
41710
- import { randomUUID as randomUUID12 } from "node:crypto";
42281
+ import { randomUUID as randomUUID13 } from "node:crypto";
41711
42282
 
41712
42283
  // src/runners/runtime-observation.ts
41713
- import { open as open6, readdir as readdir5, realpath as realpath9 } from "node:fs/promises";
42284
+ import { open as open6, readdir as readdir6, realpath as realpath9 } from "node:fs/promises";
41714
42285
  import { homedir as homedir8 } from "node:os";
41715
- import { join as join19 } from "node:path";
42286
+ import { join as join20 } from "node:path";
41716
42287
  var READ_WINDOW_BYTES = 1024 * 1024;
41717
42288
  var CATALOG_TIMEOUT_MS = 15e3;
41718
42289
  var CATALOG_OUTPUT_LIMIT_BYTES = 4 * 1024 * 1024;
@@ -41772,9 +42343,9 @@ function displayValue(value, maxLength) {
41772
42343
  return trimmed;
41773
42344
  }
41774
42345
  function claudeTranscriptPath(input) {
41775
- const configDir = input.env["CLAUDE_CONFIG_DIR"] || join19(homeFrom(input.env), ".claude");
42346
+ const configDir = input.env["CLAUDE_CONFIG_DIR"] || join20(homeFrom(input.env), ".claude");
41776
42347
  const slug = input.resolvedCwd.replace(/[^a-zA-Z0-9]/g, "-");
41777
- return join19(configDir, "projects", slug, `${input.sessionId}.jsonl`);
42348
+ return join20(configDir, "projects", slug, `${input.sessionId}.jsonl`);
41778
42349
  }
41779
42350
  async function readClaudeSessionEffort(input) {
41780
42351
  const resolvedCwd = await realpath9(input.cwd).catch(() => input.cwd);
@@ -41789,19 +42360,19 @@ async function readClaudeSessionEffort(input) {
41789
42360
  return null;
41790
42361
  }
41791
42362
  async function newestDirectories(root, limit) {
41792
- const entries = await readdir5(root, { withFileTypes: true }).catch(() => []);
41793
- return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort((left, right) => right.localeCompare(left)).slice(0, limit).map((name) => join19(root, name));
42363
+ const entries = await readdir6(root, { withFileTypes: true }).catch(() => []);
42364
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort((left, right) => right.localeCompare(left)).slice(0, limit).map((name) => join20(root, name));
41794
42365
  }
41795
42366
  async function findCodexRolloutPath(input) {
41796
- const codexHome = input.env["CODEX_HOME"] || join19(homeFrom(input.env), ".codex");
41797
- const sessions = join19(codexHome, "sessions");
42367
+ const codexHome = input.env["CODEX_HOME"] || join20(homeFrom(input.env), ".codex");
42368
+ const sessions = join20(codexHome, "sessions");
41798
42369
  const suffix = `-${input.threadId}.jsonl`;
41799
42370
  for (const year of await newestDirectories(sessions, 2)) {
41800
42371
  for (const month of await newestDirectories(year, 2)) {
41801
42372
  for (const day of await newestDirectories(month, 3)) {
41802
- const files = await readdir5(day).catch(() => []);
42373
+ const files = await readdir6(day).catch(() => []);
41803
42374
  const match = files.find((name) => name.endsWith(suffix));
41804
- if (match) return join19(day, match);
42375
+ if (match) return join20(day, match);
41805
42376
  }
41806
42377
  }
41807
42378
  }
@@ -41899,7 +42470,7 @@ var claudeCodeAdapter = {
41899
42470
  input.gitDetected,
41900
42471
  liveInput
41901
42472
  );
41902
- const sessionId = input.task.spec.sessionKey ?? randomUUID12();
42473
+ const sessionId = input.task.spec.sessionKey ?? randomUUID13();
41903
42474
  const observeRuntime = createRuntimeReporter(input, sessionId);
41904
42475
  return {
41905
42476
  argsFor: (mode) => [
@@ -42028,7 +42599,7 @@ function createClaudeLiveParser(onStream, onSessionModel) {
42028
42599
  ...parser,
42029
42600
  async start(writer, prompt) {
42030
42601
  write = writer;
42031
- await writer(input(randomUUID12(), prompt));
42602
+ await writer(input(randomUUID13(), prompt));
42032
42603
  },
42033
42604
  async steer(followUp) {
42034
42605
  if (!write) return false;
@@ -42195,22 +42766,22 @@ function improveErrorMessage(error52) {
42195
42766
  }
42196
42767
 
42197
42768
  // src/runners/codex.ts
42198
- import { mkdir as mkdir13, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
42199
- import { randomUUID as randomUUID13 } from "node:crypto";
42769
+ import { mkdir as mkdir14, readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
42770
+ import { randomUUID as randomUUID14 } from "node:crypto";
42200
42771
  import { homedir as homedir9 } from "node:os";
42201
- import { join as join20 } from "node:path";
42772
+ import { join as join21 } from "node:path";
42202
42773
  var CODEX_NOT_FOUND_MESSAGE = "The `codex` CLI was not found on this Machine. Install it (npm install -g @openai/codex) and sign in with `codex login`, or switch the agent to API-key auth.";
42203
42774
  function defaultCodexThreadIndexRoot() {
42204
- return join20(homedir9(), ".zixt", "codex-threads");
42775
+ return join21(homedir9(), ".zixt", "codex-threads");
42205
42776
  }
42206
42777
  var SAFE_SEGMENT3 = /^[A-Za-z0-9_-]{1,200}$/;
42207
42778
  function threadIndexPath(root, agentId, sessionKey) {
42208
42779
  if (!SAFE_SEGMENT3.test(agentId) || !SAFE_SEGMENT3.test(sessionKey)) return null;
42209
- return join20(root, agentId, `${sessionKey}.json`);
42780
+ return join21(root, agentId, `${sessionKey}.json`);
42210
42781
  }
42211
42782
  async function readThreadId(path) {
42212
42783
  try {
42213
- const parsed = JSON.parse(await readFile10(path, "utf8"));
42784
+ const parsed = JSON.parse(await readFile11(path, "utf8"));
42214
42785
  return typeof parsed.threadId === "string" && /^[A-Za-z0-9-]{1,120}$/.test(parsed.threadId) ? parsed.threadId : null;
42215
42786
  } catch {
42216
42787
  return null;
@@ -42310,7 +42881,7 @@ ${value}` : value;
42310
42881
  const recordedThreadId = indexPath ? await readThreadId(indexPath) : null;
42311
42882
  const rememberThread = (threadId) => {
42312
42883
  if (!indexPath) return;
42313
- void mkdir13(join20(threadIndexRoot, task.agentId), { recursive: true }).then(() => writeFile8(indexPath, JSON.stringify({ threadId }), "utf8")).catch(() => {
42884
+ void mkdir14(join21(threadIndexRoot, task.agentId), { recursive: true }).then(() => writeFile9(indexPath, JSON.stringify({ threadId }), "utf8")).catch(() => {
42314
42885
  });
42315
42886
  };
42316
42887
  const observeRuntime = (threadId) => {
@@ -42414,7 +42985,7 @@ function createCodexAppServerParser(onStream, options) {
42414
42985
  params: {
42415
42986
  threadId,
42416
42987
  input: [{ type: "text", text: prompt }],
42417
- clientUserMessageId: randomUUID13(),
42988
+ clientUserMessageId: randomUUID14(),
42418
42989
  ...options.model ? { model: options.model } : {},
42419
42990
  ...options.effort ? { effort: options.effort } : {}
42420
42991
  }
@@ -42773,7 +43344,7 @@ function improveCodexErrorMessage(error52) {
42773
43344
  }
42774
43345
 
42775
43346
  // src/runners/git-preflight.ts
42776
- import { spawn as spawn11 } from "node:child_process";
43347
+ import { spawn as spawn12 } from "node:child_process";
42777
43348
  import { realpath as realpath10 } from "node:fs/promises";
42778
43349
  import { isAbsolute as isAbsolute17, resolve as resolve11 } from "node:path";
42779
43350
  var OUTPUT_LIMIT = 8192;
@@ -42822,7 +43393,7 @@ async function preflightGit(options = {}) {
42822
43393
  }
42823
43394
  async function runVersionProbe(input) {
42824
43395
  return new Promise((resolvePromise) => {
42825
- const child = spawn11(input.executablePath, input.args, {
43396
+ const child = spawn12(input.executablePath, input.args, {
42826
43397
  cwd: input.cwd,
42827
43398
  env: {
42828
43399
  ...process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {},
@@ -43044,16 +43615,16 @@ function run2(command, args) {
43044
43615
  }
43045
43616
 
43046
43617
  // src/linux-service.ts
43047
- import { spawn as spawn12 } from "node:child_process";
43618
+ import { spawn as spawn13 } from "node:child_process";
43048
43619
  import { constants as constants2 } from "node:fs";
43049
- import { access as access5, chmod as chmod8, mkdir as mkdir15, open as open7, rename as rename7, rm as rm11 } from "node:fs/promises";
43620
+ import { access as access5, chmod as chmod9, mkdir as mkdir16, open as open7, rename as rename8, rm as rm12 } from "node:fs/promises";
43050
43621
  import { homedir as homedir11, userInfo } from "node:os";
43051
- import { basename as basename4, dirname as dirname11, join as join22, relative as relative9, resolve as resolve13, sep as sep6 } from "node:path";
43622
+ import { basename as basename4, dirname as dirname11, join as join23, relative as relative10, resolve as resolve13, sep as sep7 } from "node:path";
43052
43623
 
43053
43624
  // src/service-runtime.ts
43054
- import { access as access4, chmod as chmod7, copyFile, mkdir as mkdir14, rename as rename6, rm as rm10 } from "node:fs/promises";
43625
+ import { access as access4, chmod as chmod8, copyFile, mkdir as mkdir15, rename as rename7, rm as rm11 } from "node:fs/promises";
43055
43626
  import { homedir as homedir10 } from "node:os";
43056
- import { join as join21, resolve as resolve12, sep as sep5 } from "node:path";
43627
+ import { join as join22, resolve as resolve12, sep as sep6 } from "node:path";
43057
43628
  async function ensureDurableServiceNode(options = {}) {
43058
43629
  const execPath = resolve12(options.execPath ?? process.execPath);
43059
43630
  const home = options.home ?? homedir10();
@@ -43063,22 +43634,22 @@ async function ensureDurableServiceNode(options = {}) {
43063
43634
  throw new Error("the Node runtime version is not a safe directory name");
43064
43635
  }
43065
43636
  const zixtRoot = resolve12(home, ".zixt");
43066
- if (execPath === zixtRoot || execPath.startsWith(zixtRoot + sep5)) return execPath;
43067
- const directory = join21(zixtRoot, "runtime", `node-${version2}`);
43068
- const destination = join21(directory, platform === "win32" ? "node.exe" : "node");
43637
+ if (execPath === zixtRoot || execPath.startsWith(zixtRoot + sep6)) return execPath;
43638
+ const directory = join22(zixtRoot, "runtime", `node-${version2}`);
43639
+ const destination = join22(directory, platform === "win32" ? "node.exe" : "node");
43069
43640
  const alreadyCopied = await access4(destination).then(
43070
43641
  () => true,
43071
43642
  () => false
43072
43643
  );
43073
43644
  if (alreadyCopied) return destination;
43074
- await mkdir14(directory, { recursive: true, mode: 448 });
43645
+ await mkdir15(directory, { recursive: true, mode: 448 });
43075
43646
  const temporary = `${destination}.${process.pid}.${crypto.randomUUID()}.tmp`;
43076
43647
  try {
43077
43648
  await copyFile(execPath, temporary);
43078
- await chmod7(temporary, 493);
43079
- await rename6(temporary, destination);
43649
+ await chmod8(temporary, 493);
43650
+ await rename7(temporary, destination);
43080
43651
  } catch (error52) {
43081
- await rm10(temporary, { force: true }).catch(() => void 0);
43652
+ await rm11(temporary, { force: true }).catch(() => void 0);
43082
43653
  throw error52;
43083
43654
  }
43084
43655
  await options.syncDirectory?.(directory);
@@ -43114,7 +43685,7 @@ function boundedAppend(current, chunk) {
43114
43685
  async function defaultRunCommand(command, args) {
43115
43686
  const commandEnvironment3 = systemServiceCommandEnvironment();
43116
43687
  return new Promise((resolve19) => {
43117
- const child = spawn12(command, [...args], {
43688
+ const child = spawn13(command, [...args], {
43118
43689
  stdio: ["ignore", "pipe", "pipe"],
43119
43690
  env: commandEnvironment3,
43120
43691
  windowsHide: true
@@ -43186,33 +43757,33 @@ async function defaultSyncDirectory(path) {
43186
43757
  }
43187
43758
  }
43188
43759
  async function ensureDirectory(path, mode, syncDirectory8) {
43189
- const firstCreated = await mkdir15(path, { recursive: true, mode });
43760
+ const firstCreated = await mkdir16(path, { recursive: true, mode });
43190
43761
  if (!firstCreated) return;
43191
43762
  const first = resolve13(firstCreated);
43192
43763
  const target = resolve13(path);
43193
43764
  await syncDirectory8(dirname11(first));
43194
43765
  let current = first;
43195
- const descendants = relative9(first, target);
43196
- for (const part of descendants ? descendants.split(sep6) : []) {
43766
+ const descendants = relative10(first, target);
43767
+ for (const part of descendants ? descendants.split(sep7) : []) {
43197
43768
  await syncDirectory8(current);
43198
- current = join22(current, part);
43769
+ current = join23(current, part);
43199
43770
  }
43200
43771
  }
43201
43772
  async function replacePrivateFile(path, contents, mode, syncDirectory8) {
43202
43773
  const parent = dirname11(path);
43203
43774
  await ensureDirectory(parent, 448, syncDirectory8);
43204
- const temporary = join22(parent, `.${basename4(path)}.${process.pid}.${crypto.randomUUID()}.tmp`);
43775
+ const temporary = join23(parent, `.${basename4(path)}.${process.pid}.${crypto.randomUUID()}.tmp`);
43205
43776
  const handle = await open7(temporary, "wx", mode);
43206
43777
  try {
43207
43778
  await handle.writeFile(contents, "utf8");
43208
43779
  await handle.sync();
43209
43780
  await handle.close();
43210
- await rename7(temporary, path);
43211
- await chmod8(path, mode);
43781
+ await rename8(temporary, path);
43782
+ await chmod9(path, mode);
43212
43783
  await syncDirectory8(parent);
43213
43784
  } catch (error52) {
43214
43785
  await handle.close().catch(() => void 0);
43215
- await rm11(temporary, { force: true }).catch(() => void 0);
43786
+ await rm12(temporary, { force: true }).catch(() => void 0);
43216
43787
  throw error52;
43217
43788
  }
43218
43789
  }
@@ -43253,11 +43824,11 @@ async function installLinuxService(options) {
43253
43824
  "command search path"
43254
43825
  );
43255
43826
  const cloudUrl = options.cloudUrl ? oneLine(options.cloudUrl, "Zixt Cloud address") : void 0;
43256
- const xdgConfigHome = env.XDG_CONFIG_HOME ? oneLine(env.XDG_CONFIG_HOME, "Linux configuration path") : join22(home, ".config");
43257
- const configRoot = options.serviceConfigRoot ?? join22(xdgConfigHome, "zixt");
43258
- const unitRoot = options.userUnitRoot ?? join22(xdgConfigHome, "systemd", "user");
43259
- const environmentPath = join22(configRoot, "host.env");
43260
- const unitPath = join22(unitRoot, SERVICE_NAME);
43827
+ const xdgConfigHome = env.XDG_CONFIG_HOME ? oneLine(env.XDG_CONFIG_HOME, "Linux configuration path") : join23(home, ".config");
43828
+ const configRoot = options.serviceConfigRoot ?? join23(xdgConfigHome, "zixt");
43829
+ const unitRoot = options.userUnitRoot ?? join23(xdgConfigHome, "systemd", "user");
43830
+ const environmentPath = join23(configRoot, "host.env");
43831
+ const unitPath = join23(unitRoot, SERVICE_NAME);
43261
43832
  const installVersion = options.installVersion ?? ((version2, onFailure) => installRelease(version2, onFailure ? { onFailure } : {}));
43262
43833
  const activateVersion = options.activateVersion ?? (options.installVersion ? async (entry) => entry : activateInstalledRelease);
43263
43834
  const resolveCommand = options.resolveCommand ?? defaultResolveCommand;
@@ -43297,7 +43868,7 @@ async function installLinuxService(options) {
43297
43868
  }
43298
43869
  }
43299
43870
  await ensureDirectory(configRoot, 448, syncDirectory8);
43300
- await chmod8(configRoot, 448);
43871
+ await chmod9(configRoot, 448);
43301
43872
  const serviceEnvironment = [
43302
43873
  `ZIXT_HOST_TOKEN=${systemdEnvironmentValue(token2)}`,
43303
43874
  ...cloudUrl ? [`ZIXT_CLOUD_URL=${systemdEnvironmentValue(cloudUrl)}`] : [],
@@ -43385,11 +43956,11 @@ async function installLinuxService(options) {
43385
43956
  }
43386
43957
 
43387
43958
  // src/macos-service.ts
43388
- import { spawn as spawn13 } from "node:child_process";
43959
+ import { spawn as spawn14 } from "node:child_process";
43389
43960
  import { constants as constants3 } from "node:fs";
43390
- import { access as access6, chmod as chmod9, mkdir as mkdir16, open as open8, rename as rename8, rm as rm12 } from "node:fs/promises";
43961
+ import { access as access6, chmod as chmod10, mkdir as mkdir17, open as open8, rename as rename9, rm as rm13 } from "node:fs/promises";
43391
43962
  import { homedir as homedir12, userInfo as userInfo2 } from "node:os";
43392
- import { basename as basename5, dirname as dirname12, join as join23, relative as relative10, resolve as resolve14, sep as sep7 } from "node:path";
43963
+ import { basename as basename5, dirname as dirname12, join as join24, relative as relative11, resolve as resolve14, sep as sep8 } from "node:path";
43393
43964
  var LAUNCH_AGENT_LABEL = "ai.zixt.host";
43394
43965
  var SERVICE_STABILITY_DELAY_MS2 = 2e3;
43395
43966
  var STATUS_WAIT_MS = 2e4;
@@ -43414,32 +43985,32 @@ async function syncDirectory4(path) {
43414
43985
  }
43415
43986
  }
43416
43987
  async function ensureDirectory2(path, sync) {
43417
- const firstCreated = await mkdir16(path, { recursive: true, mode: 448 });
43988
+ const firstCreated = await mkdir17(path, { recursive: true, mode: 448 });
43418
43989
  if (!firstCreated) return;
43419
43990
  const first = resolve14(firstCreated);
43420
43991
  const target = resolve14(path);
43421
43992
  await sync(dirname12(first));
43422
43993
  let current = first;
43423
- for (const part of relative10(first, target).split(sep7).filter(Boolean)) {
43994
+ for (const part of relative11(first, target).split(sep8).filter(Boolean)) {
43424
43995
  await sync(current);
43425
- current = join23(current, part);
43996
+ current = join24(current, part);
43426
43997
  }
43427
43998
  }
43428
43999
  async function replacePrivateFile2(path, contents, mode, sync) {
43429
44000
  const parent = dirname12(path);
43430
44001
  await ensureDirectory2(parent, sync);
43431
- const temporary = join23(parent, `.${basename5(path)}.${process.pid}.${crypto.randomUUID()}.tmp`);
44002
+ const temporary = join24(parent, `.${basename5(path)}.${process.pid}.${crypto.randomUUID()}.tmp`);
43432
44003
  const handle = await open8(temporary, "wx", mode);
43433
44004
  try {
43434
44005
  await handle.writeFile(contents, "utf8");
43435
44006
  await handle.sync();
43436
44007
  await handle.close();
43437
- await rename8(temporary, path);
43438
- await chmod9(path, mode);
44008
+ await rename9(temporary, path);
44009
+ await chmod10(path, mode);
43439
44010
  await sync(parent);
43440
44011
  } catch (error52) {
43441
44012
  await handle.close().catch(() => void 0);
43442
- await rm12(temporary, { force: true }).catch(() => void 0);
44013
+ await rm13(temporary, { force: true }).catch(() => void 0);
43443
44014
  throw error52;
43444
44015
  }
43445
44016
  }
@@ -43453,7 +44024,7 @@ function commandEnvironment(env) {
43453
44024
  }
43454
44025
  async function defaultRunCommand2(command, args, env) {
43455
44026
  return new Promise((resolveResult) => {
43456
- const child = spawn13(command, [...args], {
44027
+ const child = spawn14(command, [...args], {
43457
44028
  stdio: ["ignore", "pipe", "pipe"],
43458
44029
  env: commandEnvironment(env)
43459
44030
  });
@@ -43525,14 +44096,14 @@ async function installMacosService(options) {
43525
44096
  options.path ?? env.PATH ?? "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin",
43526
44097
  "command search path"
43527
44098
  );
43528
- const configRoot = options.configRoot ?? join23(home, "Library", "Application Support", "Zixt");
43529
- const launchAgentsRoot = options.launchAgentsRoot ?? join23(home, "Library", "LaunchAgents");
43530
- const logRoot = options.logRoot ?? join23(home, "Library", "Logs", "Zixt");
43531
- const configPath = join23(configRoot, "host.env");
43532
- const launcherPath = join23(configRoot, "host-launcher.sh");
43533
- const plistPath = join23(launchAgentsRoot, `${LAUNCH_AGENT_LABEL}.plist`);
43534
- const stdoutPath = join23(logRoot, "host.log");
43535
- const stderrPath = join23(logRoot, "host-error.log");
44099
+ const configRoot = options.configRoot ?? join24(home, "Library", "Application Support", "Zixt");
44100
+ const launchAgentsRoot = options.launchAgentsRoot ?? join24(home, "Library", "LaunchAgents");
44101
+ const logRoot = options.logRoot ?? join24(home, "Library", "Logs", "Zixt");
44102
+ const configPath = join24(configRoot, "host.env");
44103
+ const launcherPath = join24(configRoot, "host-launcher.sh");
44104
+ const plistPath = join24(launchAgentsRoot, `${LAUNCH_AGENT_LABEL}.plist`);
44105
+ const stdoutPath = join24(logRoot, "host.log");
44106
+ const stderrPath = join24(logRoot, "host-error.log");
43536
44107
  const installVersion = options.installVersion ?? ((version2, onFailure) => installRelease(version2, onFailure ? { onFailure } : {}));
43537
44108
  const activateVersion = options.activateVersion ?? (options.installVersion ? async (entry) => entry : (entry) => activateInstalledRelease(entry));
43538
44109
  const resolveCommand = options.resolveCommand ?? (async () => defaultResolveCommand2());
@@ -43555,7 +44126,7 @@ async function installMacosService(options) {
43555
44126
  await ensureDirectory2(configRoot, sync);
43556
44127
  await ensureDirectory2(launchAgentsRoot, sync);
43557
44128
  await ensureDirectory2(logRoot, sync);
43558
- await chmod9(configRoot, 448);
44129
+ await chmod10(configRoot, 448);
43559
44130
  const serviceEnvironment = [
43560
44131
  `ZIXT_HOST_TOKEN=${shellValue(token2)}`,
43561
44132
  ...cloudUrl ? [`ZIXT_CLOUD_URL=${shellValue(cloudUrl)}`] : [],
@@ -43633,11 +44204,11 @@ async function installMacosService(options) {
43633
44204
  }
43634
44205
 
43635
44206
  // src/windows-service.ts
43636
- import { spawn as spawn14 } from "node:child_process";
44207
+ import { spawn as spawn15 } from "node:child_process";
43637
44208
  import { constants as constants4 } from "node:fs";
43638
- import { access as access7, mkdir as mkdir17, open as open9, readFile as readFile11, rename as rename9, rm as rm13 } from "node:fs/promises";
44209
+ import { access as access7, mkdir as mkdir18, open as open9, readFile as readFile12, rename as rename10, rm as rm14 } from "node:fs/promises";
43639
44210
  import { homedir as homedir13 } from "node:os";
43640
- import { basename as basename6, dirname as dirname13, isAbsolute as isAbsolute18, join as join24, relative as relative11, resolve as resolve15, sep as sep8 } from "node:path";
44211
+ import { basename as basename6, dirname as dirname13, isAbsolute as isAbsolute18, join as join25, relative as relative12, resolve as resolve15, sep as sep9 } from "node:path";
43641
44212
  var TASK_NAME = "Zixt Host";
43642
44213
  var COMMAND_TIMEOUT_MS3 = 7e4;
43643
44214
  var SERVICE_STABILITY_DELAY_MS3 = 2e3;
@@ -43663,31 +44234,31 @@ async function syncDirectory5(path) {
43663
44234
  }
43664
44235
  }
43665
44236
  async function ensureDirectory3(path, sync) {
43666
- const firstCreated = await mkdir17(path, { recursive: true, mode: 448 });
44237
+ const firstCreated = await mkdir18(path, { recursive: true, mode: 448 });
43667
44238
  if (!firstCreated) return;
43668
44239
  const first = resolve15(firstCreated);
43669
44240
  const target = resolve15(path);
43670
44241
  await sync(dirname13(first));
43671
44242
  let current = first;
43672
- for (const part of relative11(first, target).split(sep8).filter(Boolean)) {
44243
+ for (const part of relative12(first, target).split(sep9).filter(Boolean)) {
43673
44244
  await sync(current);
43674
- current = join24(current, part);
44245
+ current = join25(current, part);
43675
44246
  }
43676
44247
  }
43677
44248
  async function replacePrivateFile3(path, contents, sync, encoding = "utf8") {
43678
44249
  const parent = dirname13(path);
43679
44250
  await ensureDirectory3(parent, sync);
43680
- const temporary = join24(parent, `.${basename6(path)}.${process.pid}.${crypto.randomUUID()}.tmp`);
44251
+ const temporary = join25(parent, `.${basename6(path)}.${process.pid}.${crypto.randomUUID()}.tmp`);
43681
44252
  const handle = await open9(temporary, "wx", 384);
43682
44253
  try {
43683
44254
  await handle.writeFile(encoding === "utf16le" ? `\uFEFF${contents}` : contents, encoding);
43684
44255
  await handle.sync();
43685
44256
  await handle.close();
43686
- await rename9(temporary, path);
44257
+ await rename10(temporary, path);
43687
44258
  await sync(parent);
43688
44259
  } catch (error52) {
43689
44260
  await handle.close().catch(() => void 0);
43690
- await rm13(temporary, { force: true }).catch(() => void 0);
44261
+ await rm14(temporary, { force: true }).catch(() => void 0);
43691
44262
  throw error52;
43692
44263
  }
43693
44264
  }
@@ -43698,7 +44269,7 @@ function commandEnvironment2(env) {
43698
44269
  }
43699
44270
  async function runChild(command, args, env, input) {
43700
44271
  return new Promise((resolveResult) => {
43701
- const child = spawn14(command, [...args], {
44272
+ const child = spawn15(command, [...args], {
43702
44273
  stdio: [input === void 0 ? "ignore" : "pipe", "pipe", "pipe"],
43703
44274
  env: commandEnvironment2(env),
43704
44275
  windowsHide: true
@@ -43732,7 +44303,7 @@ async function runChild(command, args, env, input) {
43732
44303
  async function defaultResolveCommand3(name, env) {
43733
44304
  const root = env.SYSTEMROOT ?? env.WINDIR;
43734
44305
  if (!root || !isAbsolute18(root)) return null;
43735
- const candidate = name === "powershell" ? join24(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe") : join24(root, "System32", `${name}.exe`);
44306
+ const candidate = name === "powershell" ? join25(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe") : join25(root, "System32", `${name}.exe`);
43736
44307
  return access7(candidate, constants4.X_OK).then(
43737
44308
  () => candidate,
43738
44309
  () => null
@@ -43822,7 +44393,7 @@ exit $code
43822
44393
  }
43823
44394
  async function defaultObserveStatus(path, generation) {
43824
44395
  try {
43825
- const text = (await readFile11(path, "utf8")).replace(/^\uFEFF/, "");
44396
+ const text = (await readFile12(path, "utf8")).replace(/^\uFEFF/, "");
43826
44397
  const value = JSON.parse(text);
43827
44398
  if (value.schema !== 1 || value.generation !== generation || typeof value.pid !== "number" || !Number.isSafeInteger(value.pid) || value.pid <= 0) {
43828
44399
  return null;
@@ -43883,12 +44454,12 @@ async function installWindowsService(options) {
43883
44454
  const token2 = oneLine3(options.token, "pairing code");
43884
44455
  const cloudUrl = options.cloudUrl ? oneLine3(options.cloudUrl, "Zixt Cloud address") : void 0;
43885
44456
  const path = oneLine3(options.path ?? env.PATH ?? "", "command search path");
43886
- const configRoot = options.configRoot ?? join24(localAppData, "Zixt", "Host");
43887
- const configPath = join24(configRoot, "host.json");
43888
- const launcherPath = join24(configRoot, "host-launcher.ps1");
43889
- const launchShimPath = join24(configRoot, "host-launch.vbs");
43890
- const taskXmlPath = join24(configRoot, "host-task.xml");
43891
- const statusPath = join24(configRoot, "host-status.json");
44457
+ const configRoot = options.configRoot ?? join25(localAppData, "Zixt", "Host");
44458
+ const configPath = join25(configRoot, "host.json");
44459
+ const launcherPath = join25(configRoot, "host-launcher.ps1");
44460
+ const launchShimPath = join25(configRoot, "host-launch.vbs");
44461
+ const taskXmlPath = join25(configRoot, "host-task.xml");
44462
+ const statusPath = join25(configRoot, "host-status.json");
43892
44463
  const installVersion = options.installVersion ?? ((version2, onFailure) => installRelease(version2, onFailure ? { onFailure } : {}));
43893
44464
  const activateVersion = options.activateVersion ?? (options.installVersion ? async (entry) => entry : (entry) => activateInstalledRelease(entry));
43894
44465
  const resolveCommand = options.resolveCommand ?? ((name) => defaultResolveCommand3(name, env));
@@ -43955,7 +44526,7 @@ async function installWindowsService(options) {
43955
44526
  sync,
43956
44527
  "utf16le"
43957
44528
  );
43958
- await rm13(statusPath, { force: true });
44529
+ await rm14(statusPath, { force: true });
43959
44530
  const acl = await run3(icacls, [
43960
44531
  configRoot,
43961
44532
  "/inheritance:r",
@@ -44015,23 +44586,23 @@ async function installSystemService(options) {
44015
44586
  }
44016
44587
 
44017
44588
  // src/terminal-outcomes.ts
44018
- import { chmod as chmod10, lstat as lstat12, mkdir as mkdir18, open as open10, readdir as readdir6, readFile as readFile12, rename as rename10, rm as rm14 } from "node:fs/promises";
44589
+ import { chmod as chmod11, lstat as lstat12, mkdir as mkdir19, open as open10, readdir as readdir7, readFile as readFile13, rename as rename11, rm as rm15 } from "node:fs/promises";
44019
44590
  import { homedir as homedir14 } from "node:os";
44020
- import { dirname as dirname14, join as join25, relative as relative12, resolve as resolve16, sep as sep9 } from "node:path";
44591
+ import { dirname as dirname14, join as join26, relative as relative13, resolve as resolve16, sep as sep10 } from "node:path";
44021
44592
  var DIRECTORY_MODE5 = 448;
44022
44593
  var FILE_MODE4 = 384;
44023
44594
  var MAX_OUTCOME_BYTES = 4 * 1024 * 1024;
44024
44595
  var HOST_DIRECTORY = /^hst_[0-9a-f]{32}$/;
44025
44596
  var OUTCOME_FILE = /^(tsk_[0-9a-f]{32})\.([1-9][0-9]*)\.json$/;
44026
44597
  function defaultTerminalOutcomeRoot() {
44027
- return join25(homedir14(), ".zixt", "terminal-outcomes");
44598
+ return join26(homedir14(), ".zixt", "terminal-outcomes");
44028
44599
  }
44029
44600
  function hostOutcomeRoot(root, hostId) {
44030
44601
  if (!HOST_DIRECTORY.test(hostId)) throw new Error("terminal outcome Host identity is malformed");
44031
- return join25(root, hostId);
44602
+ return join26(root, hostId);
44032
44603
  }
44033
44604
  function outcomePath(root, hostId, taskId, epoch) {
44034
- return join25(hostOutcomeRoot(root, hostId), `${taskId}.${epoch}.json`);
44605
+ return join26(hostOutcomeRoot(root, hostId), `${taskId}.${epoch}.json`);
44035
44606
  }
44036
44607
  async function syncDirectory6(root) {
44037
44608
  if (process.platform === "win32") return;
@@ -44043,22 +44614,22 @@ async function syncDirectory6(root) {
44043
44614
  }
44044
44615
  }
44045
44616
  async function requirePrivateRoot(root, sync = syncDirectory6) {
44046
- const firstCreated = await mkdir18(root, { recursive: true, mode: DIRECTORY_MODE5 });
44617
+ const firstCreated = await mkdir19(root, { recursive: true, mode: DIRECTORY_MODE5 });
44047
44618
  if (firstCreated) {
44048
44619
  const first = resolve16(firstCreated);
44049
44620
  const target = resolve16(root);
44050
44621
  await sync(dirname14(first));
44051
44622
  let current = first;
44052
- for (const part of relative12(first, target).split(sep9).filter(Boolean)) {
44623
+ for (const part of relative13(first, target).split(sep10).filter(Boolean)) {
44053
44624
  await sync(current);
44054
- current = join25(current, part);
44625
+ current = join26(current, part);
44055
44626
  }
44056
44627
  }
44057
44628
  const stat4 = await lstat12(root);
44058
44629
  if (stat4.isSymbolicLink() || !stat4.isDirectory()) {
44059
44630
  throw new Error("terminal outcome journal root is not a trusted directory");
44060
44631
  }
44061
- await chmod10(root, DIRECTORY_MODE5);
44632
+ await chmod11(root, DIRECTORY_MODE5);
44062
44633
  }
44063
44634
  function parseCommittedOutcome(text, taskId, epoch) {
44064
44635
  let json2;
@@ -44082,7 +44653,7 @@ async function recordTerminalOutcome(hostId, input, root = defaultTerminalOutcom
44082
44653
  const destination = outcomePath(root, hostId, outcome.taskId, outcome.epoch);
44083
44654
  try {
44084
44655
  const existing = parseCommittedOutcome(
44085
- await readFile12(destination, { encoding: "utf8", flag: "r" }),
44656
+ await readFile13(destination, { encoding: "utf8", flag: "r" }),
44086
44657
  outcome.taskId,
44087
44658
  outcome.epoch
44088
44659
  );
@@ -44091,7 +44662,7 @@ async function recordTerminalOutcome(hostId, input, root = defaultTerminalOutcom
44091
44662
  } catch (error52) {
44092
44663
  if (error52.code !== "ENOENT") throw error52;
44093
44664
  }
44094
- const temporary = join25(
44665
+ const temporary = join26(
44095
44666
  scopedRoot,
44096
44667
  `.${outcome.taskId}.${outcome.epoch}.${process.pid}.${Date.now()}.${outcome.resultId}.tmp`
44097
44668
  );
@@ -44102,13 +44673,13 @@ async function recordTerminalOutcome(hostId, input, root = defaultTerminalOutcom
44102
44673
  await handle.sync();
44103
44674
  await handle.close();
44104
44675
  handle = void 0;
44105
- await rename10(temporary, destination);
44676
+ await rename11(temporary, destination);
44106
44677
  await sync(scopedRoot);
44107
44678
  await sync(root);
44108
44679
  } finally {
44109
44680
  await handle?.close().catch(() => {
44110
44681
  });
44111
- await rm14(temporary, { force: true }).catch(() => {
44682
+ await rm15(temporary, { force: true }).catch(() => {
44112
44683
  });
44113
44684
  }
44114
44685
  }
@@ -44123,8 +44694,8 @@ async function readTerminalOutcomesStrict(root = defaultTerminalOutcomeRoot()) {
44123
44694
  if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
44124
44695
  throw new Error("terminal outcome journal root is not a trusted directory");
44125
44696
  }
44126
- await chmod10(root, DIRECTORY_MODE5);
44127
- const hostEntries = await readdir6(root, { withFileTypes: true });
44697
+ await chmod11(root, DIRECTORY_MODE5);
44698
+ const hostEntries = await readdir7(root, { withFileTypes: true });
44128
44699
  const outcomes = [];
44129
44700
  const resultIds = /* @__PURE__ */ new Set();
44130
44701
  for (const hostEntry of hostEntries) {
@@ -44136,21 +44707,21 @@ async function readTerminalOutcomesStrict(root = defaultTerminalOutcomeRoot()) {
44136
44707
  if (scopedStat.isSymbolicLink() || !scopedStat.isDirectory()) {
44137
44708
  throw new Error("terminal outcome Host scope is not a trusted directory");
44138
44709
  }
44139
- await chmod10(scopedRoot, DIRECTORY_MODE5);
44140
- const entries = await readdir6(scopedRoot, { withFileTypes: true });
44710
+ await chmod11(scopedRoot, DIRECTORY_MODE5);
44711
+ const entries = await readdir7(scopedRoot, { withFileTypes: true });
44141
44712
  for (const entry of entries) {
44142
44713
  if (!entry.name.endsWith(".json")) continue;
44143
44714
  const match = OUTCOME_FILE.exec(entry.name);
44144
44715
  if (!match || !entry.isFile() || entry.isSymbolicLink()) {
44145
44716
  throw new Error("committed terminal outcome is not a trusted regular file");
44146
44717
  }
44147
- const path = join25(scopedRoot, entry.name);
44718
+ const path = join26(scopedRoot, entry.name);
44148
44719
  const stat4 = await lstat12(path);
44149
44720
  if (!stat4.isFile() || stat4.isSymbolicLink() || stat4.size > MAX_OUTCOME_BYTES) {
44150
44721
  throw new Error("committed terminal outcome is not a trusted regular file");
44151
44722
  }
44152
44723
  const outcome = parseCommittedOutcome(
44153
- await readFile12(path, "utf8"),
44724
+ await readFile13(path, "utf8"),
44154
44725
  match[1],
44155
44726
  Number(match[2])
44156
44727
  );
@@ -44177,7 +44748,7 @@ async function forgetAcknowledgedTerminalOutcomes(refs, root = defaultTerminalOu
44177
44748
  if (acknowledged.get(`${hostId}:${outcome.taskId}:${outcome.epoch}`) !== outcome.resultId) {
44178
44749
  continue;
44179
44750
  }
44180
- await rm14(outcomePath(root, hostId, outcome.taskId, outcome.epoch), { force: true });
44751
+ await rm15(outcomePath(root, hostId, outcome.taskId, outcome.epoch), { force: true });
44181
44752
  changedHostRoots.add(hostOutcomeRoot(root, hostId));
44182
44753
  }
44183
44754
  for (const scopedRoot of changedHostRoots) await syncDirectory6(scopedRoot);
@@ -44189,22 +44760,22 @@ async function forgetSupersededTerminalOutcomes(hostId, taskId, epoch, root = de
44189
44760
  if (scoped.hostId !== hostId) continue;
44190
44761
  const { outcome } = scoped;
44191
44762
  if (outcome.taskId !== taskId || outcome.epoch >= epoch) continue;
44192
- await rm14(outcomePath(root, hostId, outcome.taskId, outcome.epoch), { force: true });
44763
+ await rm15(outcomePath(root, hostId, outcome.taskId, outcome.epoch), { force: true });
44193
44764
  removed = true;
44194
44765
  }
44195
44766
  if (removed) await syncDirectory6(hostOutcomeRoot(root, hostId));
44196
44767
  }
44197
44768
 
44198
44769
  // src/accepted-assignments.ts
44199
- import { chmod as chmod11, lstat as lstat13, mkdir as mkdir19, open as open11, readdir as readdir7, rename as rename11, rm as rm15 } from "node:fs/promises";
44770
+ import { chmod as chmod12, lstat as lstat13, mkdir as mkdir20, open as open11, readdir as readdir8, rename as rename12, rm as rm16 } from "node:fs/promises";
44200
44771
  import { homedir as homedir15 } from "node:os";
44201
- import { dirname as dirname15, join as join26, relative as relative13, resolve as resolve17, sep as sep10 } from "node:path";
44772
+ import { dirname as dirname15, join as join27, relative as relative14, resolve as resolve17, sep as sep11 } from "node:path";
44202
44773
  var DIRECTORY_MODE6 = 448;
44203
44774
  var FILE_MODE5 = 384;
44204
44775
  var CLAIM_FILE = /^(tsk_[0-9a-f]{32})\.([1-9][0-9]*)\.json$/;
44205
44776
  var TASK_ID = /^tsk_[0-9a-f]{32}$/;
44206
44777
  function defaultAcceptedAssignmentRoot() {
44207
- return join26(homedir15(), ".zixt", "accepted-assignments");
44778
+ return join27(homedir15(), ".zixt", "accepted-assignments");
44208
44779
  }
44209
44780
  async function syncDirectory7(root) {
44210
44781
  if (process.platform === "win32") return;
@@ -44216,29 +44787,29 @@ async function syncDirectory7(root) {
44216
44787
  }
44217
44788
  }
44218
44789
  async function requirePrivateRoot2(root, sync = syncDirectory7) {
44219
- const firstCreated = await mkdir19(root, { recursive: true, mode: DIRECTORY_MODE6 });
44790
+ const firstCreated = await mkdir20(root, { recursive: true, mode: DIRECTORY_MODE6 });
44220
44791
  if (firstCreated) {
44221
44792
  const first = resolve17(firstCreated);
44222
44793
  const target = resolve17(root);
44223
44794
  await sync(dirname15(first));
44224
44795
  let current = first;
44225
- for (const part of relative13(first, target).split(sep10).filter(Boolean)) {
44796
+ for (const part of relative14(first, target).split(sep11).filter(Boolean)) {
44226
44797
  await sync(current);
44227
- current = join26(current, part);
44798
+ current = join27(current, part);
44228
44799
  }
44229
44800
  }
44230
44801
  const stat4 = await lstat13(root);
44231
44802
  if (stat4.isSymbolicLink() || !stat4.isDirectory()) {
44232
44803
  throw new Error("accepted assignment journal root is not a trusted directory");
44233
44804
  }
44234
- await chmod11(root, DIRECTORY_MODE6);
44805
+ await chmod12(root, DIRECTORY_MODE6);
44235
44806
  }
44236
44807
  function claimPath(root, taskId, epoch) {
44237
44808
  if (!TASK_ID.test(taskId)) throw new Error("accepted assignment Task identity is malformed");
44238
44809
  if (!Number.isSafeInteger(epoch) || epoch < 1) {
44239
44810
  throw new Error("accepted assignment epoch is malformed");
44240
44811
  }
44241
- return join26(root, `${taskId}.${epoch}.json`);
44812
+ return join27(root, `${taskId}.${epoch}.json`);
44242
44813
  }
44243
44814
  async function recordAcceptedAssignment(assignment, root = defaultAcceptedAssignmentRoot(), options = {}) {
44244
44815
  const sync = options.syncDirectory ?? syncDirectory7;
@@ -44248,7 +44819,7 @@ async function recordAcceptedAssignment(assignment, root = defaultAcceptedAssign
44248
44819
  } catch {
44249
44820
  return false;
44250
44821
  }
44251
- const temporary = join26(
44822
+ const temporary = join27(
44252
44823
  root,
44253
44824
  `.${assignment.taskId}.${assignment.epoch}.${process.pid}.${Date.now()}.tmp`
44254
44825
  );
@@ -44260,7 +44831,7 @@ async function recordAcceptedAssignment(assignment, root = defaultAcceptedAssign
44260
44831
  await handle.sync();
44261
44832
  await handle.close();
44262
44833
  handle = void 0;
44263
- await rename11(temporary, destination);
44834
+ await rename12(temporary, destination);
44264
44835
  if (process.platform !== "win32") await sync(root);
44265
44836
  return true;
44266
44837
  } catch {
@@ -44268,7 +44839,7 @@ async function recordAcceptedAssignment(assignment, root = defaultAcceptedAssign
44268
44839
  } finally {
44269
44840
  await handle?.close().catch(() => {
44270
44841
  });
44271
- await rm15(temporary, { force: true }).catch(() => {
44842
+ await rm16(temporary, { force: true }).catch(() => {
44272
44843
  });
44273
44844
  }
44274
44845
  }
@@ -44283,9 +44854,9 @@ async function recoverAcceptedAssignments(root = defaultAcceptedAssignmentRoot()
44283
44854
  if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
44284
44855
  throw new Error("accepted assignment journal root is not a trusted directory");
44285
44856
  }
44286
- await chmod11(root, DIRECTORY_MODE6);
44857
+ await chmod12(root, DIRECTORY_MODE6);
44287
44858
  const claims = [];
44288
- for (const entry of await readdir7(root, { withFileTypes: true })) {
44859
+ for (const entry of await readdir8(root, { withFileTypes: true })) {
44289
44860
  if (!entry.isFile()) continue;
44290
44861
  const match = CLAIM_FILE.exec(entry.name);
44291
44862
  if (!match) continue;
@@ -44302,7 +44873,7 @@ async function forgetAcceptedAssignment(assignment, root = defaultAcceptedAssign
44302
44873
  } catch {
44303
44874
  return;
44304
44875
  }
44305
- await rm15(path, { force: true }).catch(() => {
44876
+ await rm16(path, { force: true }).catch(() => {
44306
44877
  });
44307
44878
  }
44308
44879
  async function forgetAcknowledgedAcceptedAssignments(assignments, root = defaultAcceptedAssignmentRoot()) {
@@ -44310,9 +44881,9 @@ async function forgetAcknowledgedAcceptedAssignments(assignments, root = default
44310
44881
  }
44311
44882
 
44312
44883
  // src/local-observability.ts
44313
- import { appendFile, mkdir as mkdir20, open as open12, readdir as readdir8, rename as rename12, rm as rm16, stat as stat3 } from "node:fs/promises";
44884
+ import { appendFile, mkdir as mkdir21, open as open12, readdir as readdir9, rename as rename13, rm as rm17, stat as stat3 } from "node:fs/promises";
44314
44885
  import { homedir as homedir16 } from "node:os";
44315
- import { basename as basename7, dirname as dirname16, join as join27 } from "node:path";
44886
+ import { basename as basename7, dirname as dirname16, join as join28 } from "node:path";
44316
44887
 
44317
44888
  // src/logger.ts
44318
44889
  var ANSI = {
@@ -44424,11 +44995,11 @@ var LOCAL_STATUS_FILE = "status.json";
44424
44995
  var LOCAL_REQUESTS_DIR = "requests";
44425
44996
  var DEFAULT_CONSOLE_ROTATE_BYTES = 2 * 1024 * 1024;
44426
44997
  function defaultLocalObservabilityRoot() {
44427
- return join27(homedir16(), ".zixt", "observability");
44998
+ return join28(homedir16(), ".zixt", "observability");
44428
44999
  }
44429
45000
  function createLocalConsoleSink(options = {}) {
44430
45001
  const root = options.root ?? defaultLocalObservabilityRoot();
44431
- const consolePath = join27(root, LOCAL_CONSOLE_FILE);
45002
+ const consolePath = join28(root, LOCAL_CONSOLE_FILE);
44432
45003
  const rotateBytes = options.rotateBytes ?? DEFAULT_CONSOLE_ROTATE_BYTES;
44433
45004
  let disabled = false;
44434
45005
  let prepared = false;
@@ -44438,7 +45009,7 @@ function createLocalConsoleSink(options = {}) {
44438
45009
  if (disabled) return;
44439
45010
  try {
44440
45011
  if (!prepared) {
44441
- await mkdir20(root, { recursive: true, mode: 448 });
45012
+ await mkdir21(root, { recursive: true, mode: 448 });
44442
45013
  approximateBytes = await stat3(consolePath).then(
44443
45014
  (existing) => existing.size,
44444
45015
  () => 0
@@ -44446,8 +45017,8 @@ function createLocalConsoleSink(options = {}) {
44446
45017
  prepared = true;
44447
45018
  }
44448
45019
  if (approximateBytes >= rotateBytes) {
44449
- await rm16(join27(root, LOCAL_CONSOLE_PREVIOUS_FILE), { force: true });
44450
- await rename12(consolePath, join27(root, LOCAL_CONSOLE_PREVIOUS_FILE)).catch(
45020
+ await rm17(join28(root, LOCAL_CONSOLE_PREVIOUS_FILE), { force: true });
45021
+ await rename13(consolePath, join28(root, LOCAL_CONSOLE_PREVIOUS_FILE)).catch(
44451
45022
  (error52) => {
44452
45023
  if (error52.code !== "ENOENT") throw error52;
44453
45024
  }
@@ -44478,11 +45049,11 @@ function createLocalConsoleSink(options = {}) {
44478
45049
  };
44479
45050
  }
44480
45051
  async function consumeRunnerInstallRequests(root = defaultLocalObservabilityRoot()) {
44481
- const directory = join27(root, LOCAL_REQUESTS_DIR);
45052
+ const directory = join28(root, LOCAL_REQUESTS_DIR);
44482
45053
  const requested = /* @__PURE__ */ new Set();
44483
45054
  let names;
44484
45055
  try {
44485
- names = await readdir8(directory);
45056
+ names = await readdir9(directory);
44486
45057
  } catch {
44487
45058
  return requested;
44488
45059
  }
@@ -44490,7 +45061,7 @@ async function consumeRunnerInstallRequests(root = defaultLocalObservabilityRoot
44490
45061
  const name = `install-runner-${type}.json`;
44491
45062
  if (!names.includes(name)) continue;
44492
45063
  try {
44493
- await rm16(join27(directory, name), { force: true });
45064
+ await rm17(join28(directory, name), { force: true });
44494
45065
  requested.add(type);
44495
45066
  } catch {
44496
45067
  }
@@ -44498,13 +45069,13 @@ async function consumeRunnerInstallRequests(root = defaultLocalObservabilityRoot
44498
45069
  return requested;
44499
45070
  }
44500
45071
  async function writeLocalStatus(status, root = defaultLocalObservabilityRoot()) {
44501
- const destination = join27(root, LOCAL_STATUS_FILE);
44502
- const temporary = join27(
45072
+ const destination = join28(root, LOCAL_STATUS_FILE);
45073
+ const temporary = join28(
44503
45074
  dirname16(destination),
44504
45075
  `.${basename7(destination)}.${process.pid}.${crypto.randomUUID()}.tmp`
44505
45076
  );
44506
45077
  try {
44507
- await mkdir20(root, { recursive: true, mode: 448 });
45078
+ await mkdir21(root, { recursive: true, mode: 448 });
44508
45079
  const handle = await open12(temporary, "wx", 384);
44509
45080
  try {
44510
45081
  await handle.writeFile(`${JSON.stringify(status)}
@@ -44512,14 +45083,14 @@ async function writeLocalStatus(status, root = defaultLocalObservabilityRoot())
44512
45083
  } finally {
44513
45084
  await handle.close();
44514
45085
  }
44515
- await rename12(temporary, destination);
45086
+ await rename13(temporary, destination);
44516
45087
  } catch {
44517
- await rm16(temporary, { force: true }).catch(() => void 0);
45088
+ await rm17(temporary, { force: true }).catch(() => void 0);
44518
45089
  }
44519
45090
  }
44520
45091
 
44521
45092
  // src/demo-state.ts
44522
- import { isAbsolute as isAbsolute19, join as join28, parse as parse3, resolve as resolve18 } from "node:path";
45093
+ import { isAbsolute as isAbsolute19, join as join29, parse as parse3, resolve as resolve18 } from "node:path";
44523
45094
  var DEMO_STATE_ROOT_ENV = "ZIXT_DEMO_STATE_ROOT";
44524
45095
  function resolveDemoHostStatePaths(env = process.env) {
44525
45096
  const configured = env.ZIXT_RUNNER === "demo" ? env[DEMO_STATE_ROOT_ENV] : void 0;
@@ -44529,14 +45100,14 @@ function resolveDemoHostStatePaths(env = process.env) {
44529
45100
  throw new Error(`${DEMO_STATE_ROOT_ENV} must be a dedicated absolute directory`);
44530
45101
  }
44531
45102
  return {
44532
- runRegistryRoot: join28(root, "run-registry"),
44533
- terminalOutcomeRoot: join28(root, "terminal-outcomes"),
44534
- acceptedAssignmentRoot: join28(root, "accepted-assignments"),
44535
- runArtifactRoot: join28(root, "run-artifacts"),
44536
- browserProfileRoot: join28(root, "browser-profiles"),
44537
- runnerWorkspaceRoot: join28(root, "workspaces"),
44538
- codexThreadIndexRoot: join28(root, "codex-threads"),
44539
- localObservabilityRoot: join28(root, "local-observability")
45103
+ runRegistryRoot: join29(root, "run-registry"),
45104
+ terminalOutcomeRoot: join29(root, "terminal-outcomes"),
45105
+ acceptedAssignmentRoot: join29(root, "accepted-assignments"),
45106
+ runArtifactRoot: join29(root, "run-artifacts"),
45107
+ browserProfileRoot: join29(root, "browser-profiles"),
45108
+ runnerWorkspaceRoot: join29(root, "workspaces"),
45109
+ codexThreadIndexRoot: join29(root, "codex-threads"),
45110
+ localObservabilityRoot: join29(root, "local-observability")
44540
45111
  };
44541
45112
  }
44542
45113