@naparnik/mcp 0.10.47 → 0.10.50

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.
@@ -1,12 +1,13 @@
1
+ import { createHash, randomUUID, randomBytes } from 'crypto';
2
+ import { existsSync, readFileSync, closeSync, mkdirSync, openSync, rmSync, renameSync, readdirSync, statSync, writeFileSync, symlinkSync, chmodSync, lstatSync, realpathSync, mkdtempSync, linkSync, readlinkSync, appendFileSync, fstatSync, readSync, fsyncSync } from 'fs';
3
+ import { win32, join, dirname, sep, resolve, basename, relative, isAbsolute, extname } from 'path';
4
+ import { gunzipSync } from 'zlib';
1
5
  import { execFile, spawn, execFileSync } from 'child_process';
2
- import { createHash, randomBytes, randomUUID } from 'crypto';
3
- import { existsSync, readFileSync, closeSync, mkdirSync, openSync, readdirSync, statSync, rmSync, writeFileSync, renameSync, symlinkSync, chmodSync, lstatSync, realpathSync, mkdtempSync, linkSync, readlinkSync, appendFileSync, fstatSync, readSync, fsyncSync } from 'fs';
4
6
  import { platform, homedir, arch, release, userInfo, tmpdir } from 'os';
5
- import { win32, join, dirname, sep, resolve, basename, relative, isAbsolute, extname } from 'path';
6
7
  import { promisify } from 'util';
7
8
  import { AsyncLocalStorage } from 'async_hooks';
8
- import { gunzipSync } from 'zlib';
9
9
  import { pathToFileURL, fileURLToPath } from 'url';
10
+ import { Worker } from 'worker_threads';
10
11
  import { readFile } from 'fs/promises';
11
12
 
12
13
  var __defProp = Object.defineProperty;
@@ -729,9 +730,9 @@ function parsePositiveInt(raw, fallback) {
729
730
  }
730
731
 
731
732
  // src/version.ts
732
- var VERSION = "0.10.47";
733
+ var VERSION = "0.10.50";
733
734
  var USER_AGENT = `naparnik-mcp/${VERSION}`;
734
- var MIN_ENGINE = "2026.09.24-466";
735
+ var MIN_ENGINE = "2026.09.24-478";
735
736
  var LAUNCHER_PROTOCOL = 1;
736
737
 
737
738
  // src/contracts/sdk-headers.ts
@@ -1000,6 +1001,258 @@ function describe(err) {
1000
1001
  function sleep(ms) {
1001
1002
  return new Promise((resolve5) => setTimeout(resolve5, ms));
1002
1003
  }
1004
+ var DeliveryError = class extends Error {
1005
+ constructor(message) {
1006
+ super(message);
1007
+ this.name = "DeliveryTrouble";
1008
+ }
1009
+ };
1010
+ async function downloadFile(opts) {
1011
+ const { url: url2, dest, sha256, bytes, timeoutMs = 30 * 6e4, report, key } = opts;
1012
+ if (!/^[a-f0-9]{64}$/.test(sha256)) {
1013
+ throw new DeliveryError(`\u043D\u0435\u0447\u0435\u043C \u0441\u0432\u0435\u0440\u0438\u0442\u044C \u0441\u043A\u0430\u0447\u0430\u043D\u043D\u043E\u0435: \u0441\u0443\u043C\u043C\u0430 \xAB${sha256}\xBB \u043D\u0435 \u043F\u043E\u0445\u043E\u0436\u0430 \u043D\u0430 sha256`);
1014
+ }
1015
+ mkdirSync(dirname(dest), { recursive: true });
1016
+ const temp = `${dest}.downloading-${process.pid}-${randomUUID()}`;
1017
+ const guard = new AbortController();
1018
+ const timer = setTimeout(() => {
1019
+ guard.abort();
1020
+ }, timeoutMs);
1021
+ try {
1022
+ const response = await fetch(url2, {
1023
+ signal: guard.signal,
1024
+ redirect: "follow",
1025
+ ...key !== null && key !== void 0 ? { headers: { authorization: `Bearer ${key}` } } : {}
1026
+ });
1027
+ const hasKey = key !== null && key !== void 0;
1028
+ if (!response.ok) {
1029
+ if (response.status === 401 || response.status === 403) {
1030
+ if (response.status === 403 && hasKey) {
1031
+ throw new DeliveryError(
1032
+ `${url2} \u043D\u0435 \u043E\u0442\u0434\u0430\u0451\u0442 \u0444\u0430\u0439\u043B: \u043A\u043B\u044E\u0447 \u043F\u0440\u0438\u0437\u043D\u0430\u043D, \u043D\u043E \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438 \u043D\u0430 \u043C\u043E\u043D\u0442\u0430\u0436 \u043D\u0435\u0442. \u0421\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u0435 \u043D\u043E\u0432\u043E\u0433\u043E \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E, \u043F\u043E\u043A\u0430 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0430 \u0430\u043A\u0442\u0438\u0432\u043D\u0430; \u0432\u0441\u0451, \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0442\u043E\u0438\u0442 \u043D\u0430 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0442 \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C. \u041F\u0440\u043E\u0434\u043B\u0438\u0442\u0435 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0443 \u0432 \u0431\u043E\u0442\u0435 \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435.`
1033
+ );
1034
+ }
1035
+ throw new DeliveryError(
1036
+ !hasKey ? `${url2} \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043A\u043B\u044E\u0447 \u041D\u0430\u043F\u0430\u0440\u043D\u0438\u043A\u0430, \u0430 \u043E\u043D \u043D\u0435 \u0437\u0430\u0434\u0430\u043D. \u0412\u043F\u0438\u0448\u0438\u0442\u0435 \u043A\u043B\u044E\u0447 \u0432 \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044E MCP-\u0441\u0435\u0440\u0432\u0435\u0440\u0430 (\u0441\u0435\u043A\u0446\u0438\u044F env) \u0438 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0435 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u0443 \u2014 \u041A\u043B\u043E\u0434\u0430 \u0438\u043B\u0438 Codex.` : `${url2} \u043D\u0435 \u043F\u0440\u0438\u043D\u044F\u043B \u043A\u043B\u044E\u0447 \u041D\u0430\u043F\u0430\u0440\u043D\u0438\u043A\u0430 (${response.status}). \u0421\u043A\u043E\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043E, \u043A\u043B\u044E\u0447 \u0437\u0430\u043C\u0435\u043D\u0451\u043D \u0431\u043E\u043B\u0435\u0435 \u043D\u043E\u0432\u044B\u043C: \u043D\u043E\u0432\u044B\u0439 \u0444\u0430\u0439\u043B \u0438\u0437 \u0431\u043E\u0442\u0430 \u0443\u0436\u0435 \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D \u0437\u0434\u0435\u0441\u044C \u0438\u043B\u0438 \u043D\u0430 \u0434\u0440\u0443\u0433\u043E\u043C \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435. \u0412\u043E\u0437\u044C\u043C\u0438\u0442\u0435 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0439 \u0444\u0430\u0439\u043B \u0443 \u0431\u043E\u0442\u0430 ${KEY_SOURCE_BOT} \u0438 \u043F\u043E\u0441\u0442\u0430\u0432\u044C\u0442\u0435 \u0435\u0433\u043E.`
1037
+ );
1038
+ }
1039
+ throw new DeliveryError(`${url2} \u043E\u0442\u0432\u0435\u0442\u0438\u043B ${response.status} ${response.statusText}`);
1040
+ }
1041
+ const body = await response.arrayBuffer();
1042
+ const buffer = Buffer.from(body);
1043
+ const sum = createHash("sha256").update(buffer).digest("hex");
1044
+ if (bytes !== void 0 && buffer.length !== bytes) {
1045
+ throw new DeliveryError(
1046
+ looksLikeHtml(buffer) ? `\u043F\u043E \u0430\u0434\u0440\u0435\u0441\u0443 ${url2} \u043B\u0435\u0436\u0438\u0442 \u043D\u0435 \u0430\u0440\u0445\u0438\u0432, \u0430 \u0432\u0435\u0431-\u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u2014 \u0444\u0430\u0439\u043B \u0435\u0449\u0451 \u043D\u0435 \u0432\u044B\u043B\u043E\u0436\u0435\u043D.` : `\u0441\u043A\u0430\u0447\u0430\u043B\u043E\u0441\u044C ${buffer.length} \u0431\u0430\u0439\u0442 \u0432\u043C\u0435\u0441\u0442\u043E ${bytes} \u2014 \u0441\u0432\u044F\u0437\u044C \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C, \u043F\u043E\u0432\u0442\u043E\u0440\u0438.`
1047
+ );
1048
+ }
1049
+ if (sum !== sha256) {
1050
+ throw new DeliveryError(
1051
+ looksLikeHtml(buffer) ? `\u043F\u043E \u0430\u0434\u0440\u0435\u0441\u0443 ${url2} \u043B\u0435\u0436\u0438\u0442 \u043D\u0435 \u0430\u0440\u0445\u0438\u0432, \u0430 \u0432\u0435\u0431-\u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u2014 \u0444\u0430\u0439\u043B \u0435\u0449\u0451 \u043D\u0435 \u0432\u044B\u043B\u043E\u0436\u0435\u043D.` : `\u0441\u043A\u0430\u0447\u0430\u043D\u043D\u043E\u0435 \u043D\u0435 \u0441\u043E\u0432\u043F\u0430\u043B\u043E \u0441 \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u044C\u043D\u043E\u0439 \u0441\u0443\u043C\u043C\u043E\u0439.
1052
+ \u0436\u0434\u0430\u043B\u0438 ${sha256}
1053
+ \u0432\u044B\u0448\u043B\u043E ${sum}
1054
+ \u041F\u043E\u0432\u0442\u043E\u0440\u0438; \u0435\u0441\u043B\u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0435\u0442\u0441\u044F \u2014 \u0444\u0430\u0439\u043B \u043F\u043E\u0434\u043C\u0435\u043D\u0451\u043D \u0438\u043B\u0438 \u0431\u0438\u0442\u044B\u0439, \u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0435\u0433\u043E \u043D\u0435\u043B\u044C\u0437\u044F.`
1055
+ );
1056
+ }
1057
+ writeFileSync(temp, buffer);
1058
+ if (existsSync(dest)) {
1059
+ throw new DeliveryError(
1060
+ `\u0444\u0430\u0439\u043B ${dest} \u0443\u0436\u0435 \u0437\u0430\u043D\u044F\u043B \u0434\u0440\u0443\u0433\u043E\u0439 \u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u2014 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0443\u0434\u0430\u043B\u044F\u043B. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u044E \u0441 \u043D\u043E\u0432\u044B\u043C staging.`
1061
+ );
1062
+ }
1063
+ renameSync(temp, dest);
1064
+ report?.(`\u0441\u043A\u0430\u0447\u0430\u043D\u043E ${Math.round(buffer.length / 1024 / 1024)} \u041C\u0411`);
1065
+ return { file: dest, bytes: buffer.length, sha256: sum };
1066
+ } catch (trouble) {
1067
+ rmSync(temp, { force: true });
1068
+ if (trouble instanceof DeliveryError) throw trouble;
1069
+ if (guard.signal.aborted) {
1070
+ throw new DeliveryError(`${url2} \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u0444\u0430\u0439\u043B \u0437\u0430 ${Math.round(timeoutMs / 6e4)} \u043C\u0438\u043D.`);
1071
+ }
1072
+ throw new DeliveryError(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043A\u0430\u0447\u0430\u0442\u044C ${url2}: ${describeFailure(trouble)}`);
1073
+ } finally {
1074
+ clearTimeout(timer);
1075
+ }
1076
+ }
1077
+ function looksLikeHtml(buffer) {
1078
+ const start = buffer.subarray(0, 200).toString("latin1").trimStart().toLowerCase();
1079
+ return start.startsWith("<!doctype html") || start.startsWith("<html");
1080
+ }
1081
+ var BLOCK = 512;
1082
+ function tarEntries(data) {
1083
+ const entries = [];
1084
+ let offset = 0;
1085
+ let longName = null;
1086
+ let longTarget = null;
1087
+ while (offset + BLOCK <= data.length) {
1088
+ const header = data.subarray(offset, offset + BLOCK);
1089
+ if (header.every((b) => b === 0)) break;
1090
+ const name = fieldLine(header, 0, 100);
1091
+ const mode = octal(header, 100, 8);
1092
+ const size = fieldSize(header, 124, 12);
1093
+ const type = String.fromCharCode(header[156]);
1094
+ const target = fieldLine(header, 157, 100);
1095
+ const prefix = fieldLine(header, 345, 155);
1096
+ offset += BLOCK;
1097
+ const body = data.subarray(offset, offset + size);
1098
+ offset += Math.ceil(size / BLOCK) * BLOCK;
1099
+ if (type === "L") {
1100
+ longName = body.toString("utf8").replace(/\0+$/, "");
1101
+ continue;
1102
+ }
1103
+ if (type === "K") {
1104
+ longTarget = body.toString("utf8").replace(/\0+$/, "");
1105
+ continue;
1106
+ }
1107
+ if (type === "x") {
1108
+ const fields = parsePax(body);
1109
+ if (fields["path"] !== void 0) longName = fields["path"];
1110
+ if (fields["linkpath"] !== void 0) longTarget = fields["linkpath"];
1111
+ continue;
1112
+ }
1113
+ if (type === "g") continue;
1114
+ const full = longName ?? (prefix !== "" ? `${prefix}/${name}` : name);
1115
+ const linkTarget = longTarget ?? target;
1116
+ longName = null;
1117
+ longTarget = null;
1118
+ const kind = type === "5" ? "dir" : type === "2" ? "symlink" : type === "1" ? "hardlink" : "file";
1119
+ if (type !== "" && type !== "0" && type !== "\0" && kind === "file" && type !== "7") continue;
1120
+ entries.push({ name: full, kind, mode, body, target: linkTarget });
1121
+ }
1122
+ return entries;
1123
+ }
1124
+ function parsePax(body) {
1125
+ const fields = {};
1126
+ let offset = 0;
1127
+ while (offset < body.length) {
1128
+ const space = body.indexOf(32, offset);
1129
+ if (space === -1) break;
1130
+ const length = Number.parseInt(body.subarray(offset, space).toString("ascii"), 10);
1131
+ if (!Number.isFinite(length) || length <= 0 || offset + length > body.length) break;
1132
+ const entry = body.subarray(space + 1, offset + length).toString("utf8").replace(/\n$/, "");
1133
+ const equal = entry.indexOf("=");
1134
+ if (equal > 0) fields[entry.slice(0, equal)] = entry.slice(equal + 1);
1135
+ offset += length;
1136
+ }
1137
+ return fields;
1138
+ }
1139
+ function fieldLine(header, start, length) {
1140
+ const chunk = header.subarray(start, start + length);
1141
+ const end = chunk.indexOf(0);
1142
+ return chunk.subarray(0, end === -1 ? chunk.length : end).toString("utf8");
1143
+ }
1144
+ function octal(header, start, length) {
1145
+ const text2 = fieldLine(header, start, length).trim();
1146
+ const ch = Number.parseInt(text2, 8);
1147
+ return Number.isFinite(ch) ? ch : 0;
1148
+ }
1149
+ function fieldSize(header, start, length) {
1150
+ if ((header[start] & 128) !== 0) {
1151
+ let ch = 0;
1152
+ for (let i = start + 1; i < start + length; i += 1) ch = ch * 256 + header[i];
1153
+ return ch;
1154
+ }
1155
+ return octal(header, start, length);
1156
+ }
1157
+ function inside(root, path2) {
1158
+ if (path2 === "" || path2.startsWith("/") || path2.startsWith("\\")) return false;
1159
+ if (/^[A-Za-z]:/.test(path2)) return false;
1160
+ const target = resolve(root, path2);
1161
+ const bottom = resolve(root);
1162
+ return target === bottom || target.startsWith(bottom + sep);
1163
+ }
1164
+ function unpackArchive(archive, dest) {
1165
+ const entries = tarEntries(gunzipSync(readFileSync(archive)));
1166
+ if (entries.length === 0) throw new DeliveryError(`\u0430\u0440\u0445\u0438\u0432 ${archive} \u043F\u0443\u0441\u0442 \u2014 \u0440\u0430\u0441\u043F\u0430\u043A\u043E\u0432\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E`);
1167
+ for (const entry of entries) {
1168
+ if (!inside(dest, entry.name)) throw escapesRoot(archive, entry.name);
1169
+ if (entry.kind === "symlink" || entry.kind === "hardlink") {
1170
+ if (entry.target.startsWith("/") || entry.target.startsWith("\\") || /^[A-Za-z]:/.test(entry.target)) {
1171
+ throw escapesRoot(archive, `${entry.name} \u2192 ${entry.target}`);
1172
+ }
1173
+ const from = entry.kind === "hardlink" ? entry.target : join(dirname(entry.name), entry.target);
1174
+ if (!inside(dest, from)) throw escapesRoot(archive, `${entry.name} \u2192 ${entry.target}`);
1175
+ }
1176
+ }
1177
+ for (const entry of entries) {
1178
+ const path2 = join(dest, entry.name);
1179
+ if (entry.kind === "dir") {
1180
+ mkdirSync(path2, { recursive: true });
1181
+ continue;
1182
+ }
1183
+ mkdirSync(dirname(path2), { recursive: true });
1184
+ if (entry.kind === "symlink") {
1185
+ rmSync(path2, { force: true });
1186
+ symlinkSync(entry.target, path2);
1187
+ continue;
1188
+ }
1189
+ if (entry.kind === "hardlink") {
1190
+ writeFileSync(path2, readFileSync(join(dest, entry.target)));
1191
+ continue;
1192
+ }
1193
+ writeFileSync(path2, entry.body);
1194
+ if ((entry.mode & 73) !== 0) chmodSync(path2, entry.mode & 511);
1195
+ }
1196
+ }
1197
+ function escapesRoot(archive, name) {
1198
+ return new DeliveryError(
1199
+ `\u0430\u0440\u0445\u0438\u0432 ${archive} \u043F\u044B\u0442\u0430\u0435\u0442\u0441\u044F \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0444\u0430\u0439\u043B \u0437\u0430 \u043F\u0440\u0435\u0434\u0435\u043B\u044B \u043F\u0430\u043F\u043A\u0438 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438: \xAB${name}\xBB.
1200
+ \u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0440\u0430\u0441\u043F\u0430\u043A\u043E\u0432\u0430\u043D\u043E. \u0421\u043A\u0430\u0447\u0430\u0439 \u0437\u0430\u043D\u043E\u0432\u043E \u2014 \u0432\u0435\u0440\u043E\u044F\u0442\u043D\u0435\u0435 \u0432\u0441\u0435\u0433\u043E, \u0444\u0430\u0439\u043B \u043F\u043E\u0432\u0440\u0435\u0436\u0434\u0451\u043D \u0438\u043B\u0438 \u043F\u043E\u0434\u043C\u0435\u043D\u0451\u043D.`
1201
+ );
1202
+ }
1203
+ function dirExists(path2) {
1204
+ try {
1205
+ return statSync(path2).isDirectory();
1206
+ } catch {
1207
+ return false;
1208
+ }
1209
+ }
1210
+ var LOCK_FILE = "deps.lock.json";
1211
+ function platformKey(os = process.platform, arch2 = process.arch) {
1212
+ if (os === "darwin") return arch2 === "arm64" ? "Darwin-arm64" : arch2 === "x64" ? "Darwin-x86_64" : null;
1213
+ if (os === "win32") return arch2 === "x64" ? "Windows-AMD64" : null;
1214
+ if (os === "linux") return arch2 === "x64" ? "Linux-x86_64" : arch2 === "arm64" ? "Linux-aarch64" : null;
1215
+ return null;
1216
+ }
1217
+ function depFolderName(entry) {
1218
+ return `${entry.version}-${entry.archives.map((one) => one.sha256.slice(0, 8)).join("")}`;
1219
+ }
1220
+ function lockedPython(engineDir, home, key = platformKey()) {
1221
+ if (key === null) return null;
1222
+ const file2 = join(engineDir, LOCK_FILE);
1223
+ if (!existsSync(file2)) return null;
1224
+ const lock = JSON.parse(readFileSync(file2, "utf8"));
1225
+ const entry = lock.python?.platforms?.[key];
1226
+ const archive = entry?.archives[0];
1227
+ if (entry === void 0 || archive === void 0) return null;
1228
+ const folder = join(home, "deps", "python", depFolderName(entry));
1229
+ return { version: entry.version, archive, folder, executable: join(folder, entry.run) };
1230
+ }
1231
+ async function bringPython(python, report) {
1232
+ if (existsSync(python.executable)) return python.executable;
1233
+ const stage = `${python.folder}.partial-${process.pid}`;
1234
+ rmSync(stage, { recursive: true, force: true });
1235
+ try {
1236
+ const archive = join(stage, "python.tar.gz");
1237
+ await downloadFile({
1238
+ url: python.archive.url,
1239
+ dest: archive,
1240
+ sha256: python.archive.sha256,
1241
+ bytes: python.archive.bytes,
1242
+ key: null,
1243
+ ...report !== void 0 ? { report } : {}
1244
+ });
1245
+ unpackArchive(archive, join(stage, "tree"));
1246
+ if (!existsSync(join(stage, "tree", python.executable.slice(python.folder.length + 1)))) {
1247
+ throw new DeliveryError("\u0432 \u0430\u0440\u0445\u0438\u0432\u0435 \u043F\u0438\u0442\u043E\u043D\u0430 \u043D\u0435\u0442 \u0438\u043D\u0442\u0435\u0440\u043F\u0440\u0435\u0442\u0430\u0442\u043E\u0440\u0430, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u043D\u0430\u0437\u0432\u0430\u043B \u0437\u0430\u043C\u043E\u043A");
1248
+ }
1249
+ mkdirSync(dirname(python.folder), { recursive: true });
1250
+ if (!dirExists(python.folder)) renameSync(join(stage, "tree"), python.folder);
1251
+ } finally {
1252
+ rmSync(stage, { recursive: true, force: true });
1253
+ }
1254
+ return python.executable;
1255
+ }
1003
1256
  var TAIL_MS = 10 * 6e4;
1004
1257
  var AWAY_NOTE = "\u0421\u043A\u0430\u0436\u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443: \u043C\u043E\u0436\u043D\u043E \u043E\u0442\u043E\u0439\u0442\u0438, \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440 \u043D\u0435 \u0443\u0441\u043D\u0451\u0442, \u043F\u043E\u043A\u0430 \u0438\u0434\u0451\u0442 \u0440\u0430\u0431\u043E\u0442\u0430. \u0422\u043E\u043B\u044C\u043A\u043E \u043A\u0440\u044B\u0448\u043A\u0443 \u043D\u043E\u0443\u0442\u0431\u0443\u043A\u0430 \u043D\u0435 \u0437\u0430\u043A\u0440\u044B\u0432\u0430\u0442\u044C.";
1005
1258
  var realClock = {
@@ -1314,7 +1567,6 @@ function currentMachineFingerprint() {
1314
1567
  return cached;
1315
1568
  }
1316
1569
  var execFileP = promisify(execFile);
1317
- var INSIDE_PYTHON = ["python.exe", join("bin", "python3"), join("bin", "python")];
1318
1570
  var FOREIGN_PYTHON_VARS = [
1319
1571
  "PYTHONHOME",
1320
1572
  "PYTHONPATH",
@@ -1520,94 +1772,44 @@ var Engine = class {
1520
1772
  return null;
1521
1773
  }
1522
1774
  /**
1523
- * Портативный python, который движок доставил сам (шаг «python» установки).
1775
+ * Питон по замку начинки (`engine/deps.lock.json`), если он уже в хранилище.
1524
1776
  *
1525
- * ⚠ ЗНАТЬ ПРО НЕГО ОБЯЗАНА И ЭТА СТОРОНА. Он лежит в `<дом>/bin/python` и в
1526
- * PATH не попадает намеренно — PATH человека мы не трогаем. Пропусти его
1527
- * здесь, и получится: установка принесла питон, окружение на нём собралось,
1528
- * а сервер MCP по-прежнему зовёт системный «python», которого нет или
1529
- * который заглушка из Microsoft Store.
1777
+ * ⚠ ТОЛЬКО СВОЙ. До 2026-09-24 здесь был «python3» из PATH на случай, когда
1778
+ * своего нет, а окружение новой версии собиралось перебором «свой → базовый
1779
+ * питон рабочей версии → системный». Claude Desktop подсунул питон Xcode 3.9,
1780
+ * тот не собрал окружение, и обновление у владельца не вставало целый день.
1781
+ * Решение владельца — «как node_modules»: свой питон по замку, чужого нет.
1530
1782
  */
1531
- ourPython() {
1532
- const root = join(this.home(), "bin", "python");
1533
- for (const rel of INSIDE_PYTHON) {
1534
- const candidate = join(root, rel);
1535
- if (existsSync(candidate)) return candidate;
1536
- }
1537
- return null;
1783
+ ourPython(place = this.place()) {
1784
+ if (place === null) return null;
1785
+ const python = lockedPython(place.engine, this.home());
1786
+ return python !== null && existsSync(python.executable) ? python.executable : null;
1538
1787
  }
1539
1788
  /**
1540
- * Python для команд движка.
1789
+ * Python для команд движка: окружение пака, а пока его нет — свой питон по
1790
+ * замку. Нет ни того, ни другого — путь, которого нет: `pythonTrouble`
1791
+ * ответит словами «позови setup», а не «spawn python3 ENOENT».
1541
1792
  *
1542
- * Окружения пака может не быть вовсе — именно это и проверяет `check_setup`,
1543
- * поэтому запускать проверку его питоном нельзя: получилась бы курица и
1544
- * яйцо. Команды движка написаны на голой стандартной библиотеке и идут
1545
- * системным python'ом, когда своего ещё нет.
1793
+ * ⚠ «python3» ИЗ PATH — ТОЛЬКО ПРИ `NAPARNIK_SYSTEM_TOOLS=1`: так тесты и
1794
+ * разработка гоняют движок из исходников без хранилища (та же переменная у
1795
+ * движка, `engine/media/ffmpeg.py`). У покупателя её нет.
1546
1796
  */
1547
1797
  enginePython() {
1548
- return this.packPython() ?? this.ourPython() ?? (platform() === "win32" ? "python" : "python3");
1549
- }
1550
- /** Python подготовки кандидата — намеренно без fallback на venv старого пака. */
1551
- bootstrapPython() {
1552
- return this.bootstrapPythons()[0] ?? (platform() === "win32" ? "python" : "python3");
1798
+ const fallback = this.env["NAPARNIK_SYSTEM_TOOLS"] === "1" ? platform() === "win32" ? "python" : "python3" : join(this.home(), "deps", "python", "not-installed");
1799
+ return this.packPython() ?? this.ourPython() ?? fallback;
1553
1800
  }
1554
1801
  /**
1555
- * Чем собирать окружение новой версии — по порядку, до первого, кто сумеет.
1556
- *
1557
- * ⚠ БАЗОВЫЙ ПИТОН РАБОЧЕЙ ВЕРСИИ СТОИТ ВПЕРЕДИ «python3» ИЗ PATH, И ЭТО
1558
- * ПОЧИНКА, А НЕ ВКУС. Claude Desktop на маке запускает нас без
1559
- * `/opt/homebrew/bin` в PATH, и «python3» там — `/usr/bin/python3`, питон
1560
- * Xcode 3.9. Он собирает venv только ссылками («This build of python cannot
1561
- * create venvs without using symlinks»), а ссылка на интерпретатор вне версии
1562
- * не проходит сверку `candidateVenvTrouble`. Обновление 2026.09.24-467 у
1563
- * владельца встало ровно на этом, хотя рабочая версия собрана Homebrew 3.14
1564
- * копиями и работает. Движок при первой установке ищет питон сам
1565
- * (`setup/python_finder.py`) и пишет, какой взял, в `pyvenv.cfg` окружения —
1566
- * этот и берём: он уже однажды собрал здесь рабочее окружение копиями.
1567
- *
1568
- * ⚠ НЕ ПИТОН САМОГО VENV. Окружение старой версии удаляется уборкой после
1569
- * обновления, и кандидат, собранный его питоном, остался бы без интерпретатора
1570
- * (`engine-candidate.test.ts`). База из `pyvenv.cfg` лежит вне версий.
1571
- */
1572
- bootstrapPythons() {
1573
- const found = [
1574
- this.ourPython(),
1575
- this.workingBasePython(),
1576
- platform() === "win32" ? "python" : "python3"
1577
- ].filter((one) => one !== null);
1578
- return [...new Set(found)];
1579
- }
1580
- /**
1581
- * Базовый интерпретатор окружения рабочей версии — из её `pyvenv.cfg`.
1582
- *
1583
- * `home` раньше `executable`: у Homebrew `executable` указывает в папку
1584
- * конкретного выпуска (`Cellar/python@3.14/3.14.7/…`), и после `brew upgrade`
1585
- * её уже нет, а `home` (`/opt/homebrew/opt/python@3.14/bin`) переживает
1586
- * обновление. Нет файла или записанного питона на диске — `null`.
1802
+ * Принести питон замка ЭТОГО кандидата (или взять уже лежащий) — им движок
1803
+ * кандидата доводит до замка всё остальное (`env-install --step env`).
1587
1804
  */
1588
- workingBasePython() {
1589
- const place = this.place();
1590
- if (place === null) return null;
1591
- let text2;
1592
- try {
1593
- text2 = readFileSync(join(place.pack, ".venv", "pyvenv.cfg"), "utf8");
1594
- } catch {
1595
- return null;
1805
+ async bringPython(place, report) {
1806
+ const python = lockedPython(place.engine, this.home());
1807
+ if (python === null) {
1808
+ throw new Error(`\u043F\u043E\u0434 \u044D\u0442\u0443 \u0441\u0438\u0441\u0442\u0435\u043C\u0443 \u0432 \u0437\u0430\u043C\u043A\u0435 \u043D\u0435\u0442 \u0441\u0431\u043E\u0440\u043A\u0438 \u043F\u0438\u0442\u043E\u043D\u0430. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043D\u0430\u043F\u0438\u0448\u0438 \u0431\u043E\u0442\u0443 ${KEY_SOURCE_BOT} \u2014 \u043F\u043E\u0434\u0431\u0435\u0440\u0451\u043C \u0441\u0431\u043E\u0440\u043A\u0443`);
1596
1809
  }
1597
- const value = (key) => {
1598
- const found = new RegExp(`^\\s*${key}\\s*=\\s*(.+?)\\s*$`, "m").exec(text2);
1599
- return found?.[1] ?? null;
1600
- };
1601
- const home = value("home");
1602
- if (home !== null) {
1603
- const names = platform() === "win32" ? ["python.exe"] : ["python3", "python"];
1604
- for (const name of names) {
1605
- const candidate = join(home, name);
1606
- if (existsSync(candidate)) return candidate;
1607
- }
1608
- }
1609
- const executable = value("executable");
1610
- return executable !== null && existsSync(executable) ? executable : null;
1810
+ const executable = await bringPython(python, report);
1811
+ this.forgetPython();
1812
+ return executable;
1611
1813
  }
1612
1814
  /** Python ровно названного кандидата, а не текущей версии. */
1613
1815
  candidatePython(place) {
@@ -1631,6 +1833,9 @@ var Engine = class {
1631
1833
  pythonEnv() {
1632
1834
  const env = { ...this.env, PYTHONUTF8: "1", PYTHONIOENCODING: "utf-8" };
1633
1835
  for (const name of FOREIGN_PYTHON_VARS) delete env[name];
1836
+ for (const [name, value] of Object.entries(env)) {
1837
+ if (value !== void 0 && value.trim() !== "" && fromHost(value) === "") delete env[name];
1838
+ }
1634
1839
  delete env["NAPARNIK_API_KEY"];
1635
1840
  return env;
1636
1841
  }
@@ -1640,12 +1845,7 @@ var Engine = class {
1640
1845
  * Весь движок (включая проверку и установку) идёт через запуск python'а.
1641
1846
  * Когда его нет, `execFile` бросает ENOENT, и клиентской нейросети
1642
1847
  * доставалось «spawn python3 ENOENT» — текст, из которого человеку не понять
1643
- * ничего. Шаг «python», написанный ровно для этого случая, при этом
1644
- * недостижим по устройству: он сам запускается python'ом.
1645
- *
1646
- * На Windows есть отдельная беда: в PATH лежит заглушка Microsoft Store —
1647
- * `python` там есть, он печатает рекламу магазина и выходит с кодом 9009.
1648
- * Поэтому проверяем не наличие файла, а ответ на вопрос о версии.
1848
+ * ничего. Проверяем не наличие файла, а ответ на вопрос о версии.
1649
1849
  */
1650
1850
  async pythonTrouble() {
1651
1851
  if (this.pythonChecked !== void 0) return this.pythonChecked;
@@ -1661,7 +1861,7 @@ var Engine = class {
1661
1861
  }
1662
1862
  } catch {
1663
1863
  }
1664
- this.pythonChecked = "\u041D\u0430 \u044D\u0442\u043E\u043C \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 \u043D\u0435\u0442 Python \u2014 \u0430 \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u043C\u043E\u043D\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C: \u0432\u0435\u0441\u044C \u0434\u0432\u0438\u0436\u043E\u043A \u043D\u0430\u043F\u0438\u0441\u0430\u043D \u043D\u0430 \u043D\u0451\u043C.\n" + (platform() === "win32" ? "\u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u0441\u043A\u0430\u0436\u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C Python \u0441 python.org/downloads \u0438 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u043E\u0442\u043C\u0435\u0442\u0438\u0442\u044C \u0433\u0430\u043B\u043E\u0447\u043A\u0443 \xABAdd python.exe to PATH\xBB. \u0415\u0441\u043B\u0438 \u0432 PATH \u0441\u0435\u0439\u0447\u0430\u0441 \u043B\u0435\u0436\u0438\u0442 \u0437\u0430\u0433\u043B\u0443\u0448\u043A\u0430 Microsoft Store (\u043E\u043D\u0430 \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u0435\u0442 \u043C\u0430\u0433\u0430\u0437\u0438\u043D \u0432\u043C\u0435\u0441\u0442\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430) \u2014 \u0435\u0451 \u043D\u0430\u0434\u043E \u043E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C: \u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B \u2192 \u041F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u2192 \u041F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u044B \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044F \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u2192 \u0432\u044B\u043A\u043B\u044E\u0447\u0438\u0442\u044C python.exe.\n" : "\u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u0441\u043A\u0430\u0436\u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C Python \u0441 python.org/downloads.\n") + "\u041F\u043E\u0441\u043B\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u0443 \u2014 \u043F\u0443\u0442\u044C \u043A python \u043F\u043E\u0434\u0445\u0432\u0430\u0442\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u2014 \u0438 \u043F\u043E\u0437\u043E\u0432\u0438 setup \u0441\u043D\u043E\u0432\u0430.";
1864
+ this.pythonChecked = "\u0421\u0432\u043E\u0435\u0433\u043E \u043F\u0438\u0442\u043E\u043D\u0430 \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u043D\u0430 \u044D\u0442\u043E\u043C \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 \u0435\u0449\u0451 \u043D\u0435\u0442 \u2014 \u043E\u043D \u0441\u0442\u0430\u0432\u0438\u0442\u0441\u044F \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u0434\u0432\u0438\u0436\u043A\u043E\u043C, \u043E\u0434\u043D\u043E\u0439 \u043F\u0430\u043F\u043A\u043E\u0439, \u0432 \u0441\u0438\u0441\u0442\u0435\u043C\u0443 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0441\u0442\u0430\u0432\u0438\u0442\u0441\u044F.\n\u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0437\u043E\u0432\u0438 setup \u0441 what=all \u2014 \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u044E \u0434\u0432\u0438\u0436\u043E\u043A \u0438 \u0432\u0441\u0451, \u0447\u0442\u043E \u0435\u043C\u0443 \u043D\u0443\u0436\u043D\u043E.";
1665
1865
  return this.pythonChecked;
1666
1866
  }
1667
1867
  // ─── запуск ─────────────────────────────────────────────────────────────
@@ -13356,6 +13556,9 @@ function candidateVenvTrouble(dir, version2, place, requirePresent = false) {
13356
13556
  if (venvStat.isSymbolicLink() || !venvStat.isDirectory()) return ".venv \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043E\u0431\u044B\u0447\u043D\u043E\u0439 \u043F\u0430\u043F\u043A\u043E\u0439";
13357
13557
  if (!realpathSync(venv).startsWith(buildReal + sep)) return ".venv \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u0432\u044B\u0448\u0435\u043B \u0437\u0430 \u043F\u0440\u0435\u0434\u0435\u043B\u044B \u0432\u0435\u0440\u0441\u0438\u0438";
13358
13558
  const insideBuild = (path2) => path2 === buildReal || path2.startsWith(buildReal + sep);
13559
+ const store = join(dirname(dir), "deps", "python");
13560
+ const storeReal = existsSync(store) ? realpathSync(store) : null;
13561
+ const insideStore = (path2) => storeReal !== null && path2.startsWith(storeReal + sep);
13359
13562
  const inspectedDirectories = /* @__PURE__ */ new Set();
13360
13563
  const inspectTree = (folder, insideCriticalParent, insideSitePackages) => {
13361
13564
  const folderReal = realpathSync(folder);
@@ -13369,8 +13572,10 @@ function candidateVenvTrouble(dir, version2, place, requirePresent = false) {
13369
13572
  const inSitePackages = insideSitePackages || entry.name.toLowerCase() === "site-packages";
13370
13573
  const stat = lstatSync(path2);
13371
13574
  if (stat.isSymbolicLink()) {
13372
- if (inCriticalParent || inSitePackages || /^(python(?:3(?:\.\d+)?)?|python\.exe)$/i.test(entry.name)) {
13575
+ const interpreter = /^(python(?:3(?:\.\d+)?)?|python\.exe)$/i.test(entry.name);
13576
+ if (inCriticalParent || inSitePackages || interpreter) {
13373
13577
  const actual = realpathSync(path2);
13578
+ if (!insideBuild(actual) && interpreter && insideStore(actual)) continue;
13374
13579
  if (!insideBuild(actual)) return `${path2} \u0432\u0435\u0434\u0451\u0442 \u0437\u0430 \u043F\u0440\u0435\u0434\u0435\u043B\u044B \u0432\u0435\u0440\u0441\u0438\u0438 \u0432 ${actual}`;
13375
13580
  if (statSync(path2).isDirectory()) {
13376
13581
  const trouble = inspectTree(path2, inCriticalParent, inSitePackages);
@@ -13955,7 +14160,7 @@ function tidyOld2(engine) {
13955
14160
  }
13956
14161
  function candidateDeps(engine) {
13957
14162
  const value = engine;
13958
- return typeof value.home === "function" && typeof value.placeForVersion === "function" && typeof value.bootstrapPython === "function" && typeof value.candidatePython === "function" && typeof value.runWith === "function";
14163
+ return typeof value.home === "function" && typeof value.placeForVersion === "function" && typeof value.bringPython === "function" && typeof value.candidatePython === "function" && typeof value.runWith === "function";
13959
14164
  }
13960
14165
  async function deploy(engine, manifest, trial = probe, key, hooks = {}) {
13961
14166
  const place = engine.place();
@@ -13998,6 +14203,9 @@ async function deployLocked(engine, manifest, trial, key, hooks) {
13998
14203
  text: `\u041F\u0430\u043F\u043A\u0430 \u0432\u0435\u0440\u0441\u0438\u0438 ${manifest.version} \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u043D\u043E digest \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u0434\u0440\u0443\u0433\u043E\u0439 \u0438\u043B\u0438 \u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043D: \u0447\u0443\u0436\u043E\u0439 final \u043D\u0435 \u043F\u0440\u0438\u0441\u0432\u0430\u0438\u0432\u0430\u043B \u0438 \u043D\u0435 \u0443\u0434\u0430\u043B\u044F\u043B. \u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0438\u0441\u044B\u0432\u0430\u043B. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u0432\u044B\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0434\u0440\u0443\u0433\u043E\u0435 \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u043C\u043E\u0435 \u043F\u043E\u0434 \u043D\u043E\u0432\u044B\u043C \u043D\u043E\u043C\u0435\u0440\u043E\u043C \u0432\u0435\u0440\u0441\u0438\u0438.`
13999
14204
  };
14000
14205
  }
14206
+ if (candidate.state === "blocked" && currentVersion(dir) !== manifest.version && pollutedPayload(dest, candidate)) {
14207
+ keepBroken(dir, manifest.version, "\u0424\u0430\u0439\u043B\u044B \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0438\u0441\u044C \u043F\u043E\u0441\u043B\u0435 \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u044F \u2014 \u043E\u043F\u0438\u0441\u044C \u0431\u0430\u0439\u0442\u043E\u0432 \u043D\u0435 \u0441\u043E\u0448\u043B\u0430\u0441\u044C; \u0441\u043A\u0430\u0447\u0430\u043D \u0437\u0430\u043D\u043E\u0432\u043E.");
14208
+ }
14001
14209
  }
14002
14210
  if (!existsSync(dest)) {
14003
14211
  const stage = createVersionStage(dir, manifest.version);
@@ -14124,16 +14332,12 @@ function recoverPublicationLocked(dir) {
14124
14332
  }
14125
14333
  clearPublication(dir, publication.operationId);
14126
14334
  }
14127
- async function continuePreparedCandidate(engine, expected, bootstrap, trial = probe, extras = "now") {
14335
+ async function continuePreparedCandidate(engine, expected, trial = probe, extras = "now") {
14128
14336
  try {
14129
14337
  return await withVersionLock(engine.versionsDir(), async () => {
14130
- const checked = preparedCandidateBeforeBootstrap(engine, expected);
14338
+ const checked = preparedCandidateBeforeActivation(engine, expected);
14131
14339
  if (!checked.ok) return checked.outcome;
14132
- const bootstrapped = await bootstrap({ candidate: checked.candidate, place: checked.place });
14133
- const after = preparedCandidateAfterBootstrap(engine, checked.candidate);
14134
- if (!after.ok) return after.outcome;
14135
- if (!bootstrapped.ok) return bootstrapped;
14136
- return activatePreparedCandidateLocked(engine, after.candidate, trial, extras);
14340
+ return activatePreparedCandidateLocked(engine, checked.candidate, trial, extras);
14137
14341
  });
14138
14342
  } catch (trouble) {
14139
14343
  if (trouble instanceof VersionLockError) return { ok: false, text: trouble.message };
@@ -14144,7 +14348,7 @@ async function continuePreparedCandidate(engine, expected, bootstrap, trial = pr
14144
14348
  };
14145
14349
  }
14146
14350
  }
14147
- function preparedCandidateBeforeBootstrap(engine, expected) {
14351
+ function preparedCandidateBeforeActivation(engine, expected) {
14148
14352
  const dir = engine.versionsDir();
14149
14353
  const candidate = readCandidate(dir);
14150
14354
  if (candidate === null || !sameCandidateState(candidate, expected)) {
@@ -14152,21 +14356,21 @@ function preparedCandidateBeforeBootstrap(engine, expected) {
14152
14356
  ok: false,
14153
14357
  outcome: {
14154
14358
  ok: false,
14155
- text: "\u041F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0439 candidate \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u0434\u043E bootstrap \u2014 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B \u0438 \u043D\u0435 \u0437\u0430\u0442\u0438\u0440\u0430\u043B. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 setup."
14359
+ text: "\u041F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0439 candidate \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u0434\u043E \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F \u2014 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B \u0438 \u043D\u0435 \u0437\u0430\u0442\u0438\u0440\u0430\u043B. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 setup."
14156
14360
  }
14157
14361
  };
14158
14362
  }
14159
14363
  const place = engine.placeForVersion(candidate.version);
14160
14364
  if (place === null) {
14161
- return { ok: false, outcome: blockCandidate(dir, candidate, "\u0423 prepared-\u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u043D\u0435\u0442 engine/console.py \u2014 bootstrap \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B.") };
14365
+ return { ok: false, outcome: blockCandidate(dir, candidate, "\u0423 prepared-\u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u043D\u0435\u0442 engine/console.py \u2014 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B.") };
14162
14366
  }
14163
14367
  const proofTrouble = candidateProofTrouble(dir, candidate, place);
14164
14368
  if (proofTrouble !== null) {
14165
- return { ok: false, outcome: blockCandidate(dir, candidate, `${proofTrouble} bootstrap \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B; current \u043D\u0435 \u043C\u0435\u043D\u044F\u043B.`) };
14369
+ return { ok: false, outcome: blockCandidate(dir, candidate, `${proofTrouble} \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B; current \u043D\u0435 \u043C\u0435\u043D\u044F\u043B.`) };
14166
14370
  }
14167
14371
  const venvTrouble = candidateVenvTrouble(dir, candidate.version, place, false);
14168
14372
  if (venvTrouble !== null) {
14169
- return { ok: false, outcome: blockCandidate(dir, candidate, `${venvTrouble} bootstrap \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B; current \u043D\u0435 \u043C\u0435\u043D\u044F\u043B.`) };
14373
+ return { ok: false, outcome: blockCandidate(dir, candidate, `${venvTrouble} \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B; current \u043D\u0435 \u043C\u0435\u043D\u044F\u043B.`) };
14170
14374
  }
14171
14375
  const current = currentVersion(dir);
14172
14376
  if (current !== candidate.baseCurrent) {
@@ -14174,58 +14378,14 @@ function preparedCandidateBeforeBootstrap(engine, expected) {
14174
14378
  ok: false,
14175
14379
  outcome: {
14176
14380
  ok: false,
14177
- text: `current \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u0434\u043E bootstrap (${candidate.baseCurrent ?? "\u043D\u0435\u0442"} \u2192 ${current ?? "\u043D\u0435\u0442"}) \u2014 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B \u0438 \u043D\u0435 \u0437\u0430\u0442\u0438\u0440\u0430\u043B. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u0437\u0430\u043D\u043E\u0432\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0438\u0442\u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 setup.`
14381
+ text: `current \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u0434\u043E \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F (${candidate.baseCurrent ?? "\u043D\u0435\u0442"} \u2192 ${current ?? "\u043D\u0435\u0442"}) \u2014 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043B \u0438 \u043D\u0435 \u0437\u0430\u0442\u0438\u0440\u0430\u043B. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u0437\u0430\u043D\u043E\u0432\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0438\u0442\u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 setup.`
14178
14382
  }
14179
14383
  };
14180
14384
  }
14181
14385
  return { ok: true, candidate, place };
14182
14386
  }
14183
- function preparedCandidateAfterBootstrap(engine, before) {
14184
- const dir = engine.versionsDir();
14185
- const candidate = readCandidate(dir);
14186
- if (candidate === null || !sameCandidateState(candidate, before)) {
14187
- return {
14188
- ok: false,
14189
- outcome: {
14190
- ok: false,
14191
- text: "candidate \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u0432\u043E \u0432\u0440\u0435\u043C\u044F bootstrap \u2014 \u0447\u0443\u0436\u043E\u0439 \u0443\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C \u043D\u0435 \u0437\u0430\u0442\u0438\u0440\u0430\u043B. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 setup."
14192
- }
14193
- };
14194
- }
14195
- if (currentVersion(dir) !== before.baseCurrent) {
14196
- return {
14197
- ok: false,
14198
- outcome: {
14199
- ok: false,
14200
- text: "current \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u0432\u043E \u0432\u0440\u0435\u043C\u044F bootstrap \u2014 \u0447\u0443\u0436\u043E\u0439 current \u043D\u0435 \u0437\u0430\u0442\u0438\u0440\u0430\u043B. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 setup."
14201
- }
14202
- };
14203
- }
14204
- const place = engine.placeForVersion(candidate.version);
14205
- if (place === null) {
14206
- return {
14207
- ok: false,
14208
- outcome: {
14209
- ok: false,
14210
- text: "\u041F\u0443\u0442\u044C prepared-\u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u0432\u043E \u0432\u0440\u0435\u043C\u044F bootstrap \u2014 current \u043D\u0435 \u043C\u0435\u043D\u044F\u043B. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 setup."
14211
- }
14212
- };
14213
- }
14214
- const proofTrouble = candidateProofTrouble(dir, candidate, place);
14215
- const venvTrouble = candidateVenvTrouble(dir, candidate.version, place, false);
14216
- if (proofTrouble !== null || venvTrouble !== null) {
14217
- return {
14218
- ok: false,
14219
- outcome: {
14220
- ok: false,
14221
- text: `${proofTrouble ?? venvTrouble} \u043F\u043E\u0441\u043B\u0435 bootstrap \u2014 candidate \u0438 current \u043D\u0435 \u0437\u0430\u0442\u0438\u0440\u0430\u043B. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 setup \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0444\u0430\u0439\u043B\u043E\u0432 \u043D\u0430 \u0434\u0438\u0441\u043A\u0435.`
14222
- }
14223
- };
14224
- }
14225
- return { ok: true, candidate, place };
14226
- }
14227
- function blockCandidate(dir, candidate, text2) {
14228
- if (!sameCandidateState(readCandidate(dir), candidate)) {
14387
+ function blockCandidate(dir, candidate, text2) {
14388
+ if (!sameCandidateState(readCandidate(dir), candidate)) {
14229
14389
  return {
14230
14390
  ok: false,
14231
14391
  text: "\u0423\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C candidate \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0441\u044F \u0432\u043E \u0432\u0440\u0435\u043C\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u2014 \u0447\u0443\u0436\u043E\u0439 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u044B\u0432\u0430\u043B. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 setup."
@@ -14238,7 +14398,7 @@ function blockCandidate(dir, candidate, text2) {
14238
14398
  text: text2.includes("\u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C:") ? `${lead}
14239
14399
  ${text2}` : `${lead}
14240
14400
  ${text2}
14241
- \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0432\u0442\u043E\u0440\u0438 setup \u043F\u043E\u0437\u0436\u0435 \u2014 \u0441\u0432\u044F\u0437\u044C \u043C\u043E\u0433\u043B\u0430 \u043E\u0431\u043E\u0440\u0432\u0430\u0442\u044C\u0441\u044F. \u041E\u0442\u043A\u0430\u0436\u0435\u0442 \u0442\u0435\u043C \u0436\u0435 \u2014 \u044D\u0442\u0430 \u0432\u0435\u0440\u0441\u0438\u044F \u0441\u043B\u043E\u043C\u0430\u043D\u0430: \u043C\u043E\u043D\u0442\u0438\u0440\u0443\u0439 \u043D\u0430 \u043F\u0440\u0435\u0436\u043D\u0435\u0439 \u0438 \u0434\u043E\u0436\u0434\u0438\u0441\u044C \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0439.`
14401
+ \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043C\u043E\u043D\u0442\u0438\u0440\u0443\u0439 \u043D\u0430 \u043F\u0440\u0435\u0436\u043D\u0435\u0439. \u0421\u0440\u0430\u0437\u0443 setup \u043D\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0439 \u2014 \u043E\u0442\u043A\u0430\u0436\u0435\u0442 \u0442\u0435\u043C \u0436\u0435; \xAB\u043E\u0431\u043D\u043E\u0432\u0438\xBB \u0435\u0449\u0451 \u0440\u0430\u0437 \u2014 \u043F\u043E\u0437\u0436\u0435 \u0438\u043B\u0438 \u043A\u043E\u0433\u0434\u0430 \u0432\u044B\u0439\u0434\u0435\u0442 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F.`
14242
14402
  };
14243
14403
  }
14244
14404
  function commandWords(trouble) {
@@ -14259,12 +14419,25 @@ function candidateProofTrouble(dir, candidate, place) {
14259
14419
  if (pathTrouble !== null) return pathTrouble;
14260
14420
  try {
14261
14421
  const actual = payloadProof(join(dir, candidate.version));
14262
- if (!samePayloadProof(candidate.payload, actual)) return "\u0424\u0430\u043A\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0435 \u0431\u0430\u0439\u0442\u044B prepared-\u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0438\u0441\u044C \u043F\u043E\u0441\u043B\u0435 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043A\u0438.";
14422
+ if (!samePayloadProof(candidate.payload, actual)) {
14423
+ const said = "\u0424\u0430\u0439\u043B\u044B \u043D\u043E\u0432\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0438\u0441\u044C \u043F\u043E\u0441\u043B\u0435 \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u044F (\u043E\u043F\u0438\u0441\u044C \u0431\u0430\u0439\u0442\u043E\u0432 \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u043D\u0435 \u0441\u043E\u0448\u043B\u0430\u0441\u044C).";
14424
+ noteInstallFault("candidate-payload", new Error(said));
14425
+ return said;
14426
+ }
14263
14427
  } catch (trouble) {
14264
- return `\u0424\u0430\u043A\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0435 \u0431\u0430\u0439\u0442\u044B prepared-\u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C: ${describeFailure(trouble)}`;
14428
+ return `\u0424\u0430\u0439\u043B\u044B \u043D\u043E\u0432\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C (\u043E\u043F\u0438\u0441\u044C \u0431\u0430\u0439\u0442\u043E\u0432 \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430): ${describeFailure(trouble)}`;
14265
14429
  }
14266
14430
  return null;
14267
14431
  }
14432
+ function pollutedPayload(dest, candidate) {
14433
+ if (candidate.payload === void 0) return false;
14434
+ try {
14435
+ removePythonBytecode(dest);
14436
+ return !samePayloadProof(candidate.payload, payloadProof(dest));
14437
+ } catch {
14438
+ return false;
14439
+ }
14440
+ }
14268
14441
  function samePreparedCandidate(actual, expected) {
14269
14442
  return actual !== null && actual.version === expected.version && actual.sha256 === expected.sha256 && actual.state === "prepared" && actual.baseCurrent === expected.baseCurrent && actual.environment === expected.environment && actual.mineDigest === expected.mineDigest && actual.payload !== void 0 && expected.payload !== void 0 && samePayloadProof(actual.payload, expected.payload);
14270
14443
  }
@@ -14285,71 +14458,6 @@ async function runCandidateWith(engine, dir, version2, executable, place, args,
14285
14458
  removePythonBytecode(join(dir, version2));
14286
14459
  return result;
14287
14460
  }
14288
- async function prepareCopiedCandidateVenv(engine, dir, version2, place) {
14289
- const trouble = candidateVenvTrouble(dir, version2, place, false);
14290
- if (trouble !== null) throw new Error(trouble);
14291
- const venv = join(place.pack, ".venv");
14292
- const existingPython = engine.candidatePython(place);
14293
- const ready = existingPython !== null && await candidatePipReady(engine, existingPython);
14294
- if (!ready) {
14295
- rmSync(venv, { recursive: true, force: true });
14296
- if (engine.prepareCandidateVenv !== void 0) {
14297
- await engine.prepareCandidateVenv(place);
14298
- } else {
14299
- await copiedVenv(engine, venv);
14300
- }
14301
- }
14302
- const createdTrouble = candidateVenvTrouble(dir, version2, place, true);
14303
- if (createdTrouble !== null) throw new Error(createdTrouble);
14304
- const python = engine.candidatePython(place);
14305
- if (python === null || !await candidatePipReady(engine, python)) {
14306
- throw new Error("\u0448\u0442\u0430\u0442\u043D\u044B\u0439 .venv \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u0441\u043E\u0437\u0434\u0430\u043D \u0431\u0435\u0437 \u0440\u0430\u0431\u043E\u0447\u0435\u0433\u043E pip");
14307
- }
14308
- }
14309
- var SYMLINKS_ONLY = "cannot create venvs without using symlinks";
14310
- async function copiedVenv(engine, venv) {
14311
- const { execFile: execFile4 } = await import('child_process');
14312
- const { promisify: promisify3 } = await import('util');
14313
- const pythons = engine.bootstrapPythons?.() ?? [engine.bootstrapPython()];
14314
- const refusals = [];
14315
- for (const python of pythons) {
14316
- try {
14317
- await promisify3(execFile4)(python, ["-m", "venv", "--copies", venv], {
14318
- timeout: 10 * 6e4,
14319
- encoding: "utf8",
14320
- env: { ...engine.pythonEnv(), PYTHONDONTWRITEBYTECODE: "1" }
14321
- });
14322
- return;
14323
- } catch (trouble) {
14324
- rmSync(venv, { recursive: true, force: true });
14325
- const said = `${String(trouble.stderr ?? "")} ${describeFailure(trouble)}`;
14326
- refusals.push(said.includes(SYMLINKS_ONLY) ? `${python}: \u0441\u043E\u0431\u0438\u0440\u0430\u0435\u0442 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u0441\u0441\u044B\u043B\u043A\u0430\u043C\u0438` : `${python}: ${lastLine(said.trim())}`);
14327
- }
14328
- }
14329
- throw new Error(
14330
- `\u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043D\u043E\u0432\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u0441\u043E\u0431\u0440\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C \u2014 ${refusals.join("; ")}. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0441\u0442\u0430\u0432\u044C Python \u0441 python.org/downloads \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438 \xAB\u043E\u0431\u043D\u043E\u0432\u0438\xBB.`
14331
- );
14332
- }
14333
- async function candidatePipReady(engine, python) {
14334
- if (engine.candidatePipReady !== void 0) return engine.candidatePipReady(python);
14335
- const { execFile: execFile4 } = await import('child_process');
14336
- const { promisify: promisify3 } = await import('util');
14337
- const env = { ...engine.pythonEnv(), PYTHONDONTWRITEBYTECODE: "1" };
14338
- delete env["PYTHONHOME"];
14339
- delete env["PYTHONPATH"];
14340
- delete env["VIRTUAL_ENV"];
14341
- delete env["__PYVENV_LAUNCHER__"];
14342
- try {
14343
- await promisify3(execFile4)(python, ["-B", "-m", "pip", "--version"], {
14344
- timeout: 6e4,
14345
- encoding: "utf8",
14346
- env
14347
- });
14348
- return true;
14349
- } catch {
14350
- return false;
14351
- }
14352
- }
14353
14461
  async function compatibilityProof(engine, dir, version2, python, place) {
14354
14462
  let output = "";
14355
14463
  try {
@@ -14412,24 +14520,12 @@ async function activatePreparedCandidateLocked(engine, manifest, trial, extras =
14412
14520
  const resumesReadyBeforeCurrent = candidate.state === "ready" && current !== candidate.version;
14413
14521
  if (candidate.environment !== "ready" || resumesReadyBeforeCurrent) {
14414
14522
  try {
14415
- if (candidate.baseCurrent === null) {
14416
- await runCandidateWith(
14417
- engine,
14418
- dir,
14419
- candidate.version,
14420
- engine.bootstrapPython(),
14421
- place,
14422
- ["env-install", "--pack", place.pack, "--step", "ffmpeg", "--consent"],
14423
- { timeout: 60 * 6e4 },
14424
- false
14425
- );
14426
- }
14427
- await prepareCopiedCandidateVenv(engine, dir, candidate.version, place);
14523
+ const base = await engine.bringPython(place);
14428
14524
  await runCandidateWith(
14429
14525
  engine,
14430
14526
  dir,
14431
14527
  candidate.version,
14432
- engine.bootstrapPython(),
14528
+ base,
14433
14529
  place,
14434
14530
  [
14435
14531
  "env-install",
@@ -14441,7 +14537,7 @@ async function activatePreparedCandidateLocked(engine, manifest, trial, extras =
14441
14537
  ...extras === "later" ? requiredOnly(dir, candidate.version) : []
14442
14538
  ],
14443
14539
  { timeout: 60 * 6e4 },
14444
- true
14540
+ false
14445
14541
  );
14446
14542
  const installedVenvTrouble = candidateVenvTrouble(dir, candidate.version, place, true);
14447
14543
  if (installedVenvTrouble !== null) throw new Error(installedVenvTrouble);
@@ -14654,9 +14750,9 @@ async function dryRun(engine, build, expected, trial) {
14654
14750
  }
14655
14751
  }
14656
14752
  async function probe(engine, consolePy) {
14657
- const { execFile: execFile4 } = await import('child_process');
14658
- const { promisify: promisify3 } = await import('util');
14659
- return promisify3(execFile4)(engine.enginePython(), [consolePy, "version"], {
14753
+ const { execFile: execFile3 } = await import('child_process');
14754
+ const { promisify: promisify2 } = await import('util');
14755
+ return promisify2(execFile3)(engine.enginePython(), [consolePy, "version"], {
14660
14756
  timeout: 6e4,
14661
14757
  encoding: "utf8",
14662
14758
  env: engine.pythonEnv()
@@ -14697,212 +14793,6 @@ function rollback(engine) {
14697
14793
  };
14698
14794
  }
14699
14795
  }
14700
- var DeliveryError = class extends Error {
14701
- constructor(message) {
14702
- super(message);
14703
- this.name = "DeliveryTrouble";
14704
- }
14705
- };
14706
- async function downloadFile(opts) {
14707
- const { url: url2, dest, sha256, bytes, timeoutMs = 30 * 6e4, report, key } = opts;
14708
- if (!/^[a-f0-9]{64}$/.test(sha256)) {
14709
- throw new DeliveryError(`\u043D\u0435\u0447\u0435\u043C \u0441\u0432\u0435\u0440\u0438\u0442\u044C \u0441\u043A\u0430\u0447\u0430\u043D\u043D\u043E\u0435: \u0441\u0443\u043C\u043C\u0430 \xAB${sha256}\xBB \u043D\u0435 \u043F\u043E\u0445\u043E\u0436\u0430 \u043D\u0430 sha256`);
14710
- }
14711
- mkdirSync(dirname(dest), { recursive: true });
14712
- const temp = `${dest}.downloading-${process.pid}-${randomUUID()}`;
14713
- const guard = new AbortController();
14714
- const timer = setTimeout(() => {
14715
- guard.abort();
14716
- }, timeoutMs);
14717
- try {
14718
- const response = await fetch(url2, {
14719
- signal: guard.signal,
14720
- redirect: "follow",
14721
- ...key !== null && key !== void 0 ? { headers: { authorization: `Bearer ${key}` } } : {}
14722
- });
14723
- const hasKey = key !== null && key !== void 0;
14724
- if (!response.ok) {
14725
- if (response.status === 401 || response.status === 403) {
14726
- if (response.status === 403 && hasKey) {
14727
- throw new DeliveryError(
14728
- `${url2} \u043D\u0435 \u043E\u0442\u0434\u0430\u0451\u0442 \u0444\u0430\u0439\u043B: \u043A\u043B\u044E\u0447 \u043F\u0440\u0438\u0437\u043D\u0430\u043D, \u043D\u043E \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438 \u043D\u0430 \u043C\u043E\u043D\u0442\u0430\u0436 \u043D\u0435\u0442. \u0421\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u0435 \u043D\u043E\u0432\u043E\u0433\u043E \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E, \u043F\u043E\u043A\u0430 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0430 \u0430\u043A\u0442\u0438\u0432\u043D\u0430; \u0432\u0441\u0451, \u0447\u0442\u043E \u0443\u0436\u0435 \u0441\u0442\u043E\u0438\u0442 \u043D\u0430 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0442 \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C. \u041F\u0440\u043E\u0434\u043B\u0438\u0442\u0435 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0443 \u0432 \u0431\u043E\u0442\u0435 \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435.`
14729
- );
14730
- }
14731
- throw new DeliveryError(
14732
- !hasKey ? `${url2} \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043A\u043B\u044E\u0447 \u041D\u0430\u043F\u0430\u0440\u043D\u0438\u043A\u0430, \u0430 \u043E\u043D \u043D\u0435 \u0437\u0430\u0434\u0430\u043D. \u0412\u043F\u0438\u0448\u0438\u0442\u0435 \u043A\u043B\u044E\u0447 \u0432 \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044E MCP-\u0441\u0435\u0440\u0432\u0435\u0440\u0430 (\u0441\u0435\u043A\u0446\u0438\u044F env) \u0438 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0435 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u0443 \u2014 \u041A\u043B\u043E\u0434\u0430 \u0438\u043B\u0438 Codex.` : `${url2} \u043D\u0435 \u043F\u0440\u0438\u043D\u044F\u043B \u043A\u043B\u044E\u0447 \u041D\u0430\u043F\u0430\u0440\u043D\u0438\u043A\u0430 (${response.status}). \u0421\u043A\u043E\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043E, \u043A\u043B\u044E\u0447 \u0437\u0430\u043C\u0435\u043D\u0451\u043D \u0431\u043E\u043B\u0435\u0435 \u043D\u043E\u0432\u044B\u043C: \u043D\u043E\u0432\u044B\u0439 \u0444\u0430\u0439\u043B \u0438\u0437 \u0431\u043E\u0442\u0430 \u0443\u0436\u0435 \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D \u0437\u0434\u0435\u0441\u044C \u0438\u043B\u0438 \u043D\u0430 \u0434\u0440\u0443\u0433\u043E\u043C \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435. \u0412\u043E\u0437\u044C\u043C\u0438\u0442\u0435 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0439 \u0444\u0430\u0439\u043B \u0443 \u0431\u043E\u0442\u0430 ${KEY_SOURCE_BOT} \u0438 \u043F\u043E\u0441\u0442\u0430\u0432\u044C\u0442\u0435 \u0435\u0433\u043E.`
14733
- );
14734
- }
14735
- throw new DeliveryError(`${url2} \u043E\u0442\u0432\u0435\u0442\u0438\u043B ${response.status} ${response.statusText}`);
14736
- }
14737
- const body = await response.arrayBuffer();
14738
- const buffer = Buffer.from(body);
14739
- const sum = createHash("sha256").update(buffer).digest("hex");
14740
- if (bytes !== void 0 && buffer.length !== bytes) {
14741
- throw new DeliveryError(
14742
- looksLikeHtml(buffer) ? `\u043F\u043E \u0430\u0434\u0440\u0435\u0441\u0443 ${url2} \u043B\u0435\u0436\u0438\u0442 \u043D\u0435 \u0430\u0440\u0445\u0438\u0432, \u0430 \u0432\u0435\u0431-\u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u2014 \u0444\u0430\u0439\u043B \u0435\u0449\u0451 \u043D\u0435 \u0432\u044B\u043B\u043E\u0436\u0435\u043D.` : `\u0441\u043A\u0430\u0447\u0430\u043B\u043E\u0441\u044C ${buffer.length} \u0431\u0430\u0439\u0442 \u0432\u043C\u0435\u0441\u0442\u043E ${bytes} \u2014 \u0441\u0432\u044F\u0437\u044C \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u0430\u0441\u044C, \u043F\u043E\u0432\u0442\u043E\u0440\u0438.`
14743
- );
14744
- }
14745
- if (sum !== sha256) {
14746
- throw new DeliveryError(
14747
- looksLikeHtml(buffer) ? `\u043F\u043E \u0430\u0434\u0440\u0435\u0441\u0443 ${url2} \u043B\u0435\u0436\u0438\u0442 \u043D\u0435 \u0430\u0440\u0445\u0438\u0432, \u0430 \u0432\u0435\u0431-\u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u2014 \u0444\u0430\u0439\u043B \u0435\u0449\u0451 \u043D\u0435 \u0432\u044B\u043B\u043E\u0436\u0435\u043D.` : `\u0441\u043A\u0430\u0447\u0430\u043D\u043D\u043E\u0435 \u043D\u0435 \u0441\u043E\u0432\u043F\u0430\u043B\u043E \u0441 \u043A\u043E\u043D\u0442\u0440\u043E\u043B\u044C\u043D\u043E\u0439 \u0441\u0443\u043C\u043C\u043E\u0439.
14748
- \u0436\u0434\u0430\u043B\u0438 ${sha256}
14749
- \u0432\u044B\u0448\u043B\u043E ${sum}
14750
- \u041F\u043E\u0432\u0442\u043E\u0440\u0438; \u0435\u0441\u043B\u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0435\u0442\u0441\u044F \u2014 \u0444\u0430\u0439\u043B \u043F\u043E\u0434\u043C\u0435\u043D\u0451\u043D \u0438\u043B\u0438 \u0431\u0438\u0442\u044B\u0439, \u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0435\u0433\u043E \u043D\u0435\u043B\u044C\u0437\u044F.`
14751
- );
14752
- }
14753
- writeFileSync(temp, buffer);
14754
- if (existsSync(dest)) {
14755
- throw new DeliveryError(
14756
- `\u0444\u0430\u0439\u043B ${dest} \u0443\u0436\u0435 \u0437\u0430\u043D\u044F\u043B \u0434\u0440\u0443\u0433\u043E\u0439 \u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u2014 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0443\u0434\u0430\u043B\u044F\u043B. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u044E \u0441 \u043D\u043E\u0432\u044B\u043C staging.`
14757
- );
14758
- }
14759
- renameSync(temp, dest);
14760
- report?.(`\u0441\u043A\u0430\u0447\u0430\u043D\u043E ${Math.round(buffer.length / 1024 / 1024)} \u041C\u0411`);
14761
- return { file: dest, bytes: buffer.length, sha256: sum };
14762
- } catch (trouble) {
14763
- rmSync(temp, { force: true });
14764
- if (trouble instanceof DeliveryError) throw trouble;
14765
- if (guard.signal.aborted) {
14766
- throw new DeliveryError(`${url2} \u043D\u0435 \u043E\u0442\u0434\u0430\u043B \u0444\u0430\u0439\u043B \u0437\u0430 ${Math.round(timeoutMs / 6e4)} \u043C\u0438\u043D.`);
14767
- }
14768
- throw new DeliveryError(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043A\u0430\u0447\u0430\u0442\u044C ${url2}: ${describeFailure(trouble)}`);
14769
- } finally {
14770
- clearTimeout(timer);
14771
- }
14772
- }
14773
- function looksLikeHtml(buffer) {
14774
- const start = buffer.subarray(0, 200).toString("latin1").trimStart().toLowerCase();
14775
- return start.startsWith("<!doctype html") || start.startsWith("<html");
14776
- }
14777
- var BLOCK = 512;
14778
- function tarEntries(data) {
14779
- const entries = [];
14780
- let offset = 0;
14781
- let longName = null;
14782
- let longTarget = null;
14783
- while (offset + BLOCK <= data.length) {
14784
- const header = data.subarray(offset, offset + BLOCK);
14785
- if (header.every((b) => b === 0)) break;
14786
- const name = fieldLine(header, 0, 100);
14787
- const mode = octal(header, 100, 8);
14788
- const size = fieldSize(header, 124, 12);
14789
- const type = String.fromCharCode(header[156]);
14790
- const target = fieldLine(header, 157, 100);
14791
- const prefix = fieldLine(header, 345, 155);
14792
- offset += BLOCK;
14793
- const body = data.subarray(offset, offset + size);
14794
- offset += Math.ceil(size / BLOCK) * BLOCK;
14795
- if (type === "L") {
14796
- longName = body.toString("utf8").replace(/\0+$/, "");
14797
- continue;
14798
- }
14799
- if (type === "K") {
14800
- longTarget = body.toString("utf8").replace(/\0+$/, "");
14801
- continue;
14802
- }
14803
- if (type === "x") {
14804
- const fields = parsePax(body);
14805
- if (fields["path"] !== void 0) longName = fields["path"];
14806
- if (fields["linkpath"] !== void 0) longTarget = fields["linkpath"];
14807
- continue;
14808
- }
14809
- if (type === "g") continue;
14810
- const full = longName ?? (prefix !== "" ? `${prefix}/${name}` : name);
14811
- const linkTarget = longTarget ?? target;
14812
- longName = null;
14813
- longTarget = null;
14814
- const kind = type === "5" ? "dir" : type === "2" ? "symlink" : type === "1" ? "hardlink" : "file";
14815
- if (type !== "" && type !== "0" && type !== "\0" && kind === "file" && type !== "7") continue;
14816
- entries.push({ name: full, kind, mode, body, target: linkTarget });
14817
- }
14818
- return entries;
14819
- }
14820
- function parsePax(body) {
14821
- const fields = {};
14822
- let offset = 0;
14823
- while (offset < body.length) {
14824
- const space = body.indexOf(32, offset);
14825
- if (space === -1) break;
14826
- const length = Number.parseInt(body.subarray(offset, space).toString("ascii"), 10);
14827
- if (!Number.isFinite(length) || length <= 0 || offset + length > body.length) break;
14828
- const entry = body.subarray(space + 1, offset + length).toString("utf8").replace(/\n$/, "");
14829
- const equal = entry.indexOf("=");
14830
- if (equal > 0) fields[entry.slice(0, equal)] = entry.slice(equal + 1);
14831
- offset += length;
14832
- }
14833
- return fields;
14834
- }
14835
- function fieldLine(header, start, length) {
14836
- const chunk = header.subarray(start, start + length);
14837
- const end = chunk.indexOf(0);
14838
- return chunk.subarray(0, end === -1 ? chunk.length : end).toString("utf8");
14839
- }
14840
- function octal(header, start, length) {
14841
- const text2 = fieldLine(header, start, length).trim();
14842
- const ch = Number.parseInt(text2, 8);
14843
- return Number.isFinite(ch) ? ch : 0;
14844
- }
14845
- function fieldSize(header, start, length) {
14846
- if ((header[start] & 128) !== 0) {
14847
- let ch = 0;
14848
- for (let i = start + 1; i < start + length; i += 1) ch = ch * 256 + header[i];
14849
- return ch;
14850
- }
14851
- return octal(header, start, length);
14852
- }
14853
- function inside(root, path2) {
14854
- if (path2 === "" || path2.startsWith("/") || path2.startsWith("\\")) return false;
14855
- if (/^[A-Za-z]:/.test(path2)) return false;
14856
- const target = resolve(root, path2);
14857
- const bottom = resolve(root);
14858
- return target === bottom || target.startsWith(bottom + sep);
14859
- }
14860
- function unpackArchive(archive, dest) {
14861
- const entries = tarEntries(gunzipSync(readFileSync(archive)));
14862
- if (entries.length === 0) throw new DeliveryError(`\u0430\u0440\u0445\u0438\u0432 ${archive} \u043F\u0443\u0441\u0442 \u2014 \u0440\u0430\u0441\u043F\u0430\u043A\u043E\u0432\u044B\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E`);
14863
- for (const entry of entries) {
14864
- if (!inside(dest, entry.name)) throw escapesRoot(archive, entry.name);
14865
- if (entry.kind === "symlink" || entry.kind === "hardlink") {
14866
- if (entry.target.startsWith("/") || entry.target.startsWith("\\") || /^[A-Za-z]:/.test(entry.target)) {
14867
- throw escapesRoot(archive, `${entry.name} \u2192 ${entry.target}`);
14868
- }
14869
- const from = entry.kind === "hardlink" ? entry.target : join(dirname(entry.name), entry.target);
14870
- if (!inside(dest, from)) throw escapesRoot(archive, `${entry.name} \u2192 ${entry.target}`);
14871
- }
14872
- }
14873
- for (const entry of entries) {
14874
- const path2 = join(dest, entry.name);
14875
- if (entry.kind === "dir") {
14876
- mkdirSync(path2, { recursive: true });
14877
- continue;
14878
- }
14879
- mkdirSync(dirname(path2), { recursive: true });
14880
- if (entry.kind === "symlink") {
14881
- rmSync(path2, { force: true });
14882
- symlinkSync(entry.target, path2);
14883
- continue;
14884
- }
14885
- if (entry.kind === "hardlink") {
14886
- writeFileSync(path2, readFileSync(join(dest, entry.target)));
14887
- continue;
14888
- }
14889
- writeFileSync(path2, entry.body);
14890
- if ((entry.mode & 73) !== 0) chmodSync(path2, entry.mode & 511);
14891
- }
14892
- }
14893
- function escapesRoot(archive, name) {
14894
- return new DeliveryError(
14895
- `\u0430\u0440\u0445\u0438\u0432 ${archive} \u043F\u044B\u0442\u0430\u0435\u0442\u0441\u044F \u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C \u0444\u0430\u0439\u043B \u0437\u0430 \u043F\u0440\u0435\u0434\u0435\u043B\u044B \u043F\u0430\u043F\u043A\u0438 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438: \xAB${name}\xBB.
14896
- \u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0440\u0430\u0441\u043F\u0430\u043A\u043E\u0432\u0430\u043D\u043E. \u0421\u043A\u0430\u0447\u0430\u0439 \u0437\u0430\u043D\u043E\u0432\u043E \u2014 \u0432\u0435\u0440\u043E\u044F\u0442\u043D\u0435\u0435 \u0432\u0441\u0435\u0433\u043E, \u0444\u0430\u0439\u043B \u043F\u043E\u0432\u0440\u0435\u0436\u0434\u0451\u043D \u0438\u043B\u0438 \u043F\u043E\u0434\u043C\u0435\u043D\u0451\u043D.`
14897
- );
14898
- }
14899
- function dirExists(path2) {
14900
- try {
14901
- return statSync(path2).isDirectory();
14902
- } catch {
14903
- return false;
14904
- }
14905
- }
14906
14796
  var BODY_ENTRY = "cli.js";
14907
14797
  var BODY_MANIFEST = "shell.json";
14908
14798
  function shellDir(home) {
@@ -15085,50 +14975,100 @@ function removePublication(dir, version2) {
15085
14975
  function publicationMatchesManifest(publication, manifest) {
15086
14976
  return publication.version === manifest.version && publication.artifactSha256 === manifest.body?.sha256 && publication.artifactBytes === manifest.body.bytes;
15087
14977
  }
14978
+ var PROBE_TIMEOUT_MS = 6e4;
14979
+ var REQUIRED_TOOLS = ["setup", "render", "wait"];
14980
+ var PROBE_THREAD = `
14981
+ const { parentPort, workerData } = require('node:worker_threads')
14982
+ import(workerData.entry).then((body) => {
14983
+ if (typeof body.start !== 'function') throw new Error('\u0432\u0445\u043E\u0434 \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u043D\u0435 \u043E\u0442\u0434\u0430\u0451\u0442 start()')
14984
+ return body.start()
14985
+ }).catch((trouble) => {
14986
+ parentPort.postMessage(trouble instanceof Error
14987
+ ? { message: trouble.name + ': ' + String(trouble.message).split('\\n')[0], stack: String(trouble.stack || '') }
14988
+ : { message: String(trouble), stack: '' })
14989
+ })
14990
+ `;
15088
14991
  var probeShell = async (entry) => {
15089
14992
  const probeHome = mkdtempSync(join(tmpdir(), "naparnik-shell-probe-"));
15090
- const env = {
15091
- ...process.env,
15092
- NAPARNIK_HOME: probeHome,
15093
- NAPARNIK_NO_BODY: "1",
15094
- NAPARNIK_NO_UPDATE: "1"
15095
- };
14993
+ const env = {};
14994
+ for (const [name, value] of Object.entries(process.env)) {
14995
+ if (value !== void 0 && name !== "NAPARNIK_API_KEY") env[name] = value;
14996
+ }
14997
+ Object.assign(env, { NAPARNIK_HOME: probeHome, NAPARNIK_NO_BODY: "1", NAPARNIK_NO_UPDATE: "1" });
14998
+ const thread = new Worker(PROBE_THREAD, {
14999
+ eval: true,
15000
+ argv: [],
15001
+ env,
15002
+ stdin: true,
15003
+ stdout: true,
15004
+ stderr: true,
15005
+ workerData: { entry: pathToFileURL(entry).href }
15006
+ });
15096
15007
  try {
15097
- const version2 = await promisify(execFile)(process.execPath, [entry, "--version"], {
15098
- timeout: 6e4,
15099
- encoding: "utf8",
15100
- env
15101
- });
15102
- await new Promise((resolve5, reject) => {
15103
- const child = spawn(process.execPath, [entry], { stdio: ["pipe", "pipe", "pipe"], env });
15008
+ const named = await new Promise((resolve5, reject) => {
15104
15009
  let output = "";
15105
15010
  let errors = "";
15106
15011
  let finished = false;
15107
- const timer = setTimeout(() => finish(new Error("\u0442\u0435\u043B\u043E \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B\u043E \u043D\u0430 initialize/tools-list")), 15e3);
15108
- const finish = (trouble) => {
15012
+ const timer = setTimeout(
15013
+ () => finish(failure(`\u0442\u0435\u043B\u043E \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B\u043E \u043D\u0430 initialize/tools-list \u0437\u0430 ${PROBE_TIMEOUT_MS / 1e3} \u0441`)),
15014
+ PROBE_TIMEOUT_MS
15015
+ );
15016
+ const finish = (trouble, said) => {
15109
15017
  if (finished) return;
15110
15018
  finished = true;
15111
15019
  clearTimeout(timer);
15112
- child.kill();
15113
- if (trouble === void 0) resolve5();
15020
+ if (trouble === null) resolve5(said ?? "");
15114
15021
  else reject(trouble);
15115
15022
  };
15116
15023
  const send = (message) => {
15117
- child.stdin.write(`${JSON.stringify(message)}
15024
+ thread.stdin?.write(`${JSON.stringify(message)}
15118
15025
  `);
15119
15026
  };
15120
- child.stderr.setEncoding("utf8");
15121
- child.stderr.on("data", (chunk) => {
15122
- errors += chunk;
15027
+ const failure = (text2, stack = "") => {
15028
+ const trouble = new Error(text2);
15029
+ const said = errors.trim();
15030
+ trouble.stack = [text2, stack, said === "" ? "" : `stderr \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430:
15031
+ ${said.slice(-4e3)}`].filter((part) => part !== "").join("\n");
15032
+ return trouble;
15033
+ };
15034
+ thread.on("message", (message) => {
15035
+ finish(failure(`\u0442\u0435\u043B\u043E \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u043B\u043E\u0441\u044C: ${String(message.message)}`, String(message.stack ?? "")));
15123
15036
  });
15124
- child.on("error", (trouble) => {
15125
- finish(trouble);
15037
+ thread.on("error", (trouble) => {
15038
+ finish(failure(`\u0442\u0435\u043B\u043E \u0443\u043F\u0430\u043B\u043E: ${describeFailure(trouble)}`, trouble.stack ?? ""));
15126
15039
  });
15127
- child.on("exit", (code) => {
15128
- if (!finished) finish(new Error(`\u0442\u0435\u043B\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u043E\u0441\u044C \u0441 \u043A\u043E\u0434\u043E\u043C ${String(code)}: ${errors.trim()}`));
15040
+ thread.on("exit", (code) => {
15041
+ finish(failure(`\u0442\u0435\u043B\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u043E\u0441\u044C \u0441 \u043A\u043E\u0434\u043E\u043C ${String(code)}`));
15129
15042
  });
15130
- child.stdout.setEncoding("utf8");
15131
- child.stdout.on("data", (chunk) => {
15043
+ thread.stderr.setEncoding("utf8");
15044
+ thread.stderr.on("data", (chunk) => {
15045
+ errors += chunk;
15046
+ });
15047
+ let version2 = null;
15048
+ const answer = (reply) => {
15049
+ if (reply.error !== void 0) {
15050
+ finish(failure(`MCP-\u043F\u0440\u043E\u0431\u0430 \u043F\u043E\u043B\u0443\u0447\u0438\u043B\u0430 \u043E\u0442\u043A\u0430\u0437: ${JSON.stringify(reply.error)}`));
15051
+ } else if (reply.id === 1) {
15052
+ const named2 = reply.result?.serverInfo?.version;
15053
+ if (typeof named2 !== "string" || named2 === "") {
15054
+ finish(failure("initialize \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043B \u0432\u0435\u0440\u0441\u0438\u044E \u0442\u0435\u043B\u0430"));
15055
+ return;
15056
+ }
15057
+ version2 = named2;
15058
+ send({ jsonrpc: "2.0", method: "notifications/initialized" });
15059
+ send({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} });
15060
+ } else if (reply.id === 2 && version2 !== null) {
15061
+ const names = new Set((reply.result?.tools ?? []).map((tool) => tool.name));
15062
+ const missing = REQUIRED_TOOLS.filter((name) => !names.has(name));
15063
+ if (missing.length > 0) {
15064
+ finish(failure(`tools/list \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u043D\u0435 \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u0435 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B: ${missing.join(", ")}`));
15065
+ return;
15066
+ }
15067
+ finish(null, version2);
15068
+ }
15069
+ };
15070
+ thread.stdout.setEncoding("utf8");
15071
+ thread.stdout.on("data", (chunk) => {
15132
15072
  output += chunk;
15133
15073
  for (; ; ) {
15134
15074
  const end = output.indexOf("\n");
@@ -15136,36 +15076,14 @@ var probeShell = async (entry) => {
15136
15076
  const line = output.slice(0, end).trim();
15137
15077
  output = output.slice(end + 1);
15138
15078
  if (line === "") continue;
15139
- let response;
15079
+ let reply;
15140
15080
  try {
15141
- response = JSON.parse(line);
15081
+ reply = JSON.parse(line);
15142
15082
  } catch {
15143
- finish(new Error(`\u0442\u0435\u043B\u043E \u043D\u0430\u043F\u0435\u0447\u0430\u0442\u0430\u043B\u043E \u043D\u0435 JSON: ${line.slice(0, 200)}`));
15144
- return;
15145
- }
15146
- if (response.error !== void 0) {
15147
- finish(new Error(`MCP-\u043F\u0440\u043E\u0431\u0430 \u043F\u043E\u043B\u0443\u0447\u0438\u043B\u0430 \u043E\u0442\u043A\u0430\u0437: ${JSON.stringify(response.error)}`));
15083
+ finish(failure(`\u0442\u0435\u043B\u043E \u043D\u0430\u043F\u0435\u0447\u0430\u0442\u0430\u043B\u043E \u043D\u0435 JSON: ${line.slice(0, 200)}`));
15148
15084
  return;
15149
15085
  }
15150
- if (response.id === 1) {
15151
- const expected = version2.stdout.trim();
15152
- if (response.result?.serverInfo?.version !== expected) {
15153
- finish(new Error(
15154
- `initialize \u043F\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043B \u0442\u0435\u043B\u043E \u0432\u0435\u0440\u0441\u0438\u0435\u0439 \xAB${String(response.result?.serverInfo?.version)}\xBB, \u0430 --version \u043E\u0442\u0432\u0435\u0442\u0438\u043B \xAB${expected}\xBB`
15155
- ));
15156
- return;
15157
- }
15158
- send({ jsonrpc: "2.0", method: "notifications/initialized" });
15159
- send({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} });
15160
- } else if (response.id === 2) {
15161
- const names = new Set((response.result?.tools ?? []).map((tool) => tool.name));
15162
- const missing = ["setup", "render", "wait"].filter((name) => !names.has(name));
15163
- if (missing.length > 0) {
15164
- finish(new Error(`tools/list \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u0430 \u043D\u0435 \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u0435 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B: ${missing.join(", ")}`));
15165
- return;
15166
- }
15167
- finish();
15168
- }
15086
+ answer(reply);
15169
15087
  }
15170
15088
  });
15171
15089
  send({
@@ -15175,8 +15093,10 @@ var probeShell = async (entry) => {
15175
15093
  params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "update-probe", version: "1" } }
15176
15094
  });
15177
15095
  });
15178
- return version2;
15096
+ return { stdout: `${named}
15097
+ ` };
15179
15098
  } finally {
15099
+ await thread.terminate();
15180
15100
  rmSync(probeHome, { recursive: true, force: true, maxRetries: 3 });
15181
15101
  }
15182
15102
  };
@@ -15327,9 +15247,14 @@ async function dryRun2(build, expected, trial) {
15327
15247
  const { stdout } = await trial(entry);
15328
15248
  const line = stdout.trim().split("\n").pop() ?? "";
15329
15249
  const said = versionFrom(line);
15330
- if (said !== expected) return `\u0441\u0431\u043E\u0440\u043A\u0430 \u043F\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043B\u0430\u0441\u044C \u0432\u0435\u0440\u0441\u0438\u0435\u0439 \xAB${said}\xBB, \u0430 \u0436\u0434\u0430\u043B\u0438 \xAB${expected}\xBB`;
15250
+ if (said !== expected) {
15251
+ const trouble = `\u0441\u0431\u043E\u0440\u043A\u0430 \u043F\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043B\u0430\u0441\u044C \u0432\u0435\u0440\u0441\u0438\u0435\u0439 \xAB${said}\xBB, \u0430 \u0436\u0434\u0430\u043B\u0438 \xAB${expected}\xBB`;
15252
+ noteInstallFault("shell-probe", new Error(trouble));
15253
+ return trouble;
15254
+ }
15331
15255
  return null;
15332
15256
  } catch (trouble) {
15257
+ noteInstallFault("shell-probe", trouble);
15333
15258
  return describeFailure(trouble);
15334
15259
  }
15335
15260
  }
@@ -15382,7 +15307,6 @@ function rollbackShell(home) {
15382
15307
  };
15383
15308
  }
15384
15309
  }
15385
- var SOURCES_FILE = "python-sources.json";
15386
15310
  var PUBLICATION_FILE2 = ".publication.json";
15387
15311
  var CANDIDATE_FILE = ".candidate.json";
15388
15312
  function recoverInterruptedPublication(dir) {
@@ -15446,26 +15370,6 @@ function recoverInterruptedPublication(dir) {
15446
15370
  \u041E\u043D\u0430 \u0435\u0449\u0451 \u043D\u0435 \u0440\u0430\u0431\u043E\u0447\u0430\u044F: setup \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442 \u044D\u0442\u043E\u0442 \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442 \u0438 \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u0441\u043E\u0431\u0435\u0440\u0451\u0442 \u0435\u0433\u043E \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435.`
15447
15371
  };
15448
15372
  }
15449
- function platformKey(os = process.platform, arch2 = process.arch) {
15450
- if (os === "darwin") return arch2 === "arm64" ? "Darwin-arm64" : arch2 === "x64" ? "Darwin-x86_64" : null;
15451
- if (os === "win32") return arch2 === "x64" ? "Windows-AMD64" : null;
15452
- if (os === "linux") return arch2 === "x64" ? "Linux-x86_64" : arch2 === "arm64" ? "Linux-aarch64" : null;
15453
- return null;
15454
- }
15455
- function pythonSource(engineDir, key = platformKey()) {
15456
- if (key === null) return null;
15457
- const file2 = join(engineDir, SOURCES_FILE);
15458
- if (!existsSync(file2)) return null;
15459
- const table = JSON.parse(readFileSync(file2, "utf8"));
15460
- const row = table["platforms"][key];
15461
- if (row === void 0) return null;
15462
- return { url: table["base"] + row["tail"], sha256: row.sha256, mb: row["mb"] };
15463
- }
15464
- function pythonVersion(engineDir) {
15465
- const file2 = join(engineDir, SOURCES_FILE);
15466
- if (!existsSync(file2)) return "";
15467
- return JSON.parse(readFileSync(file2, "utf8"))["version"];
15468
- }
15469
15373
  async function installPayload(engine, opts, hooks = {}) {
15470
15374
  if (engine.pathSetByHand()) {
15471
15375
  return {
@@ -15564,7 +15468,7 @@ ${VOICE_CLEANER_NOTE}
15564
15468
  return {
15565
15469
  ok: true,
15566
15470
  text: `\u041D\u0430\u0447\u0438\u043D\u043A\u0430 ${manifest.version} \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043B\u0435\u043D\u0430: ${dest}
15567
- \u041E\u043D\u0430 \u0435\u0449\u0451 \u043D\u0435 \u0441\u0442\u0430\u043B\u0430 \u0440\u0430\u0431\u043E\u0447\u0435\u0439. \u0414\u0430\u043B\u044C\u0448\u0435 setup \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442 \u044D\u0442\u043E\u0442 \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442: \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442 python, \u0441\u043E\u0431\u0435\u0440\u0451\u0442 \u0435\u0433\u043E \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442 current.
15471
+ \u041E\u043D\u0430 \u0435\u0449\u0451 \u043D\u0435 \u0441\u0442\u0430\u043B\u0430 \u0440\u0430\u0431\u043E\u0447\u0435\u0439. \u0414\u0430\u043B\u044C\u0448\u0435 setup \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442 \u044D\u0442\u043E\u0442 \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442: \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442 \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438 \u043F\u043E \u0435\u0433\u043E \u0437\u0430\u043C\u043A\u0443 \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442 current.
15568
15472
  \u0420\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0447\u0438, \u0432\u044B\u0440\u0435\u0437 \u0433\u0435\u0440\u043E\u044F \u0438 \u0447\u0438\u0441\u0442\u043A\u0430 \u0433\u043E\u043B\u043E\u0441\u0430 \u0432 \u043D\u0435\u0433\u043E \u043D\u0435 \u0432\u0445\u043E\u0434\u044F\u0442: \u043E\u043D\u0438 \u0441\u0442\u0430\u0432\u044F\u0442\u0441\u044F \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u043C\u0438 \u0448\u0430\u0433\u0430\u043C\u0438 \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E \u043F\u0440\u043E\u0441\u044C\u0431\u0435 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430.`
15569
15473
  };
15570
15474
  });
@@ -15604,113 +15508,6 @@ async function deliver(manifest, dest, intakeNear, key, baseCurrent, hooks) {
15604
15508
  discardVersionStage(stage);
15605
15509
  }
15606
15510
  }
15607
- async function installPython(engine, opts) {
15608
- try {
15609
- return await withVersionLock(
15610
- engine.versionsDir(),
15611
- async () => installPythonUnderLock(engine, opts)
15612
- );
15613
- } catch (trouble) {
15614
- if (trouble instanceof VersionLockError) return { ok: false, text: trouble.message };
15615
- return { ok: false, text: troubleText("Python \u0441\u043A\u0430\u0447\u0430\u0442\u044C \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C", trouble) };
15616
- }
15617
- }
15618
- async function installPythonUnderLock(engine, opts) {
15619
- const place = opts.version === void 0 ? engine.place() : engine.placeForVersion(opts.version);
15620
- if (place === null) {
15621
- return { ok: false, text: engine.whyNoEngine() };
15622
- }
15623
- const key = platformKey();
15624
- const source = pythonSource(place.engine, key);
15625
- if (source === null || source.sha256 === "") {
15626
- return {
15627
- ok: false,
15628
- text: `\u0413\u043E\u0442\u043E\u0432\u043E\u0439 \u043F\u043E\u0440\u0442\u0430\u0442\u0438\u0432\u043D\u043E\u0439 \u0441\u0431\u043E\u0440\u043A\u0438 Python \u043F\u043E\u0434 \u044D\u0442\u0443 \u0441\u0438\u0441\u0442\u0435\u043C\u0443 (${key ?? `${process.platform}-${process.arch}`}) \u0443 \u043C\u0435\u043D\u044F \u043D\u0435\u0442.
15629
- \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u0441\u043A\u0430\u0436\u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C Python 3.10 \u0438\u043B\u0438 \u043D\u043E\u0432\u0435\u0435 \u0441 python.org/downloads \u0438 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u0443.`
15630
- };
15631
- }
15632
- const dest = join(engine.home(), "bin");
15633
- const version2 = pythonVersion(place.engine);
15634
- if (!opts.consent) {
15635
- return {
15636
- ok: false,
15637
- text: `\u041D\u0430 \u044D\u0442\u043E\u0439 \u043C\u0430\u0448\u0438\u043D\u0435 \u043D\u0435\u0442 Python, \u0430 \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u043C\u043E\u043D\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043D\u0435\u0447\u0435\u043C.
15638
- \u041C\u043E\u0433\u0443 \u0441\u043A\u0430\u0447\u0430\u0442\u044C \u043F\u043E\u0440\u0442\u0430\u0442\u0438\u0432\u043D\u044B\u0439 Python ${version2} (${source.mb} \u041C\u0411) \u0432 ${dest}: \u043E\u043D \u043B\u0435\u0436\u0438\u0442 \u043E\u0434\u043D\u043E\u0439 \u043F\u0430\u043F\u043A\u043E\u0439 \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043E\u0442\u0442\u0443\u0434\u0430 \u2014 \u0432 \u0441\u0438\u0441\u0442\u0435\u043C\u0443 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0441\u0442\u0430\u0432\u0438\u0442\u0441\u044F, PATH \u043D\u0435 \u043C\u0435\u043D\u044F\u0435\u0442\u0441\u044F, \u043F\u0440\u0430\u0432\u0430 \u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u0430 \u043D\u0435 \u043D\u0443\u0436\u043D\u044B, \u0443\u0434\u0430\u043B\u0438\u0442\u044C \u0437\u043D\u0430\u0447\u0438\u0442 \u0443\u0434\u0430\u043B\u0438\u0442\u044C \u043F\u0430\u043F\u043A\u0443. \u041F\u043E\u0434\u043F\u0438\u0441\u044C \u0444\u0430\u0439\u043B\u0430 \u0443 \u043C\u0435\u043D\u044F \u0435\u0441\u0442\u044C, \u0441\u0432\u0435\u0440\u044E.
15639
- \u0421\u043F\u0440\u043E\u0441\u0438 \u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0438 \u043F\u043E\u0437\u043E\u0432\u0438 setup \u0441 what=all \u0438 consent=true.`
15640
- };
15641
- }
15642
- const pythonDir = join(dest, "python");
15643
- try {
15644
- safePythonParent(engine.home(), dest);
15645
- if (existsSync(pythonDir)) {
15646
- safePythonDirectory(engine.home(), pythonDir);
15647
- engine.forgetPython();
15648
- if (await engine.pythonTrouble() === null) {
15649
- return {
15650
- ok: true,
15651
- text: `Python ${version2} \u0443\u0436\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D: ${pythonDir}
15652
- \u041F\u043E\u0432\u0442\u043E\u0440\u043D\u043E \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u043B.`
15653
- };
15654
- }
15655
- throw new DeliveryError(
15656
- `\u0412 ${pythonDir} \u0443\u0436\u0435 \u0447\u0442\u043E-\u0442\u043E \u043B\u0435\u0436\u0438\u0442, \u043D\u043E \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u044D\u0442\u043E \u043D\u0435 \u0432\u044B\u0448\u043B\u043E. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435, \u043D\u0435 \u0438\u0434\u0451\u0442 \u043B\u0438 \u0434\u0440\u0443\u0433\u0430\u044F \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0430, \u0437\u0430\u0442\u0435\u043C \u0443\u0434\u0430\u043B\u0438\u0442\u0435 \u043E\u0441\u0442\u0430\u0442\u043E\u043A \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435.`
15657
- );
15658
- }
15659
- const stage = createVersionStage(engine.versionsDir(), opts.version ?? "0-python-bootstrap");
15660
- try {
15661
- const archive = join(stage.root, "python.tar.gz");
15662
- await downloadFile({ url: source.url, dest: archive, sha256: source.sha256, key: null });
15663
- unpackArchive(archive, stage.payload);
15664
- const stagedPython = join(stage.payload, "python");
15665
- if (!dirExists(stagedPython)) throw new DeliveryError("\u0432 \u0430\u0440\u0445\u0438\u0432\u0435 Python \u043D\u0435\u0442 \u043F\u0430\u043F\u043A\u0438 python");
15666
- if (dirExists(pythonDir)) {
15667
- throw new DeliveryError(`\u043F\u0430\u043F\u043A\u0430 ${pythonDir} \u0437\u0430\u043D\u044F\u0442\u0430 \u0434\u0440\u0443\u0433\u0438\u043C \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u043E\u043C. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 setup \u043F\u043E\u0437\u0436\u0435`);
15668
- }
15669
- renameSync(stagedPython, pythonDir);
15670
- } finally {
15671
- discardVersionStage(stage);
15672
- }
15673
- } catch (trouble2) {
15674
- return { ok: false, text: troubleText("Python \u0441\u043A\u0430\u0447\u0430\u0442\u044C \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C", trouble2) };
15675
- }
15676
- engine.forgetPython();
15677
- const trouble = await engine.pythonTrouble();
15678
- if (trouble !== null) {
15679
- return {
15680
- ok: false,
15681
- text: `Python \u0441\u043A\u0430\u0447\u0430\u043B\u0441\u044F \u0438 \u0440\u0430\u0441\u043F\u0430\u043A\u043E\u0432\u0430\u043B\u0441\u044F \u0432 ${pythonDir}, \u043D\u043E \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u043B\u0441\u044F.
15682
- ${trouble}`
15683
- };
15684
- }
15685
- return {
15686
- ok: true,
15687
- text: `Python ${version2} \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D: ${pythonDir}
15688
- \u0412 \u0441\u0438\u0441\u0442\u0435\u043C\u0443 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0441\u0442\u0430\u0432\u0438\u043B\u043E\u0441\u044C. \u0414\u0430\u043B\u044C\u0448\u0435 \u2014 setup \u0441 what=all.`
15689
- };
15690
- }
15691
- function safePythonParent(home, parent) {
15692
- mkdirSync(parent, { recursive: true });
15693
- const stat = lstatSync(parent);
15694
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
15695
- throw new DeliveryError(`${parent} \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043E\u0431\u044B\u0447\u043D\u043E\u0439 \u043F\u0430\u043F\u043A\u043E\u0439 \u0432\u043D\u0443\u0442\u0440\u0438 \u0434\u043E\u043C\u0430 \u041D\u0430\u043F\u0430\u0440\u043D\u0438\u043A\u0430`);
15696
- }
15697
- const homeReal = realpathSync(home);
15698
- const parentReal = realpathSync(parent);
15699
- if (!parentReal.startsWith(homeReal + sep)) {
15700
- throw new DeliveryError(`${parent} \u0432\u044B\u0448\u0435\u043B \u0437\u0430 \u043F\u0440\u0435\u0434\u0435\u043B\u044B \u0434\u043E\u043C\u0430 \u041D\u0430\u043F\u0430\u0440\u043D\u0438\u043A\u0430`);
15701
- }
15702
- }
15703
- function safePythonDirectory(home, pythonDir) {
15704
- const stat = lstatSync(pythonDir);
15705
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
15706
- throw new DeliveryError(`${pythonDir} \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043E\u0431\u044B\u0447\u043D\u043E\u0439 \u043F\u0430\u043F\u043A\u043E\u0439`);
15707
- }
15708
- const homeReal = realpathSync(home);
15709
- const pythonReal = realpathSync(pythonDir);
15710
- if (!pythonReal.startsWith(homeReal + sep)) {
15711
- throw new DeliveryError(`${pythonDir} \u0432\u044B\u0448\u0435\u043B \u0437\u0430 \u043F\u0440\u0435\u0434\u0435\u043B\u044B \u0434\u043E\u043C\u0430 \u041D\u0430\u043F\u0430\u0440\u043D\u0438\u043A\u0430`);
15712
- }
15713
- }
15714
15511
  function troubleText(what, trouble) {
15715
15512
  const tail2 = trouble instanceof DeliveryError ? trouble.message : trouble instanceof Error ? trouble.message : String(trouble);
15716
15513
  return `${what}, \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043C\u0435\u043D\u044F\u043B.
@@ -16888,6 +16685,10 @@ function cardRows(readiness) {
16888
16685
  }));
16889
16686
  return rows.map((row) => ({ ...row, mb: row.state === "ok" ? 0 : row.mb, say: rowWords(row.state, row.mb) }));
16890
16687
  }
16688
+ var FOR_MODEL = /\bsetup\b|\bwait\b|what=|consent|\bjob\b|Скажи человеку|Спроси человека|скажи ему|перескажи/i;
16689
+ function forHuman(line) {
16690
+ return FOR_MODEL.test(line) ? "" : line;
16691
+ }
16891
16692
  function setupCard(o) {
16892
16693
  const rows = cardRows(o.readiness);
16893
16694
  const missingMb = rows.filter((row) => row.state === "missing").reduce((all, row) => all + row.mb, 0);
@@ -16899,7 +16700,7 @@ function setupCard(o) {
16899
16700
  next = `\u041D\u0430\u043F\u0438\u0448\u0438 \xAB\u0441\u043A\u0430\u0447\u0430\u0439\xBB \u2014 \u0441\u043A\u0430\u0447\u0430\u044E ${o.noEngine ? "\u043C\u043E\u043D\u0442\u0430\u0436" : "Python"}.`;
16900
16701
  } else if (o.readiness === null) {
16901
16702
  title = o.title ?? TITLE_UNKNOWN;
16902
- next = o.note ?? "";
16703
+ next = forHuman(o.note ?? "");
16903
16704
  } else if (!ready) {
16904
16705
  title = missingMb ? `\u041D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0441\u043A\u0430\u0447\u0430\u0442\u044C ${weight(missingMb)} \u0434\u043B\u044F \u043F\u043E\u043B\u043D\u043E\u0446\u0435\u043D\u043D\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u044B` : "\u041D\u0435 \u0432\u0441\u0451 \u0433\u043E\u0442\u043E\u0432\u043E \u0434\u043B\u044F \u043F\u043E\u043B\u043D\u043E\u0446\u0435\u043D\u043D\u043E\u0439 \u0440\u0430\u0431\u043E\u0442\u044B";
16905
16706
  next = missingMb ? `\u041D\u0430\u043F\u0438\u0448\u0438 \xAB\u0441\u043A\u0430\u0447\u0430\u0439\xBB \u2014 \u0441\u043A\u0430\u0447\u0430\u044E ${weight(missingMb)}.` : "\u041D\u0430\u043F\u0438\u0448\u0438 \xAB\u0441\u043A\u0430\u0447\u0430\u0439\xBB \u2014 \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u044E, \u0447\u0435\u0433\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442.";
@@ -18055,8 +17856,6 @@ function nameOrRefusal(candidate) {
18055
17856
  \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043D\u0430\u0437\u043E\u0432\u0438 \u0448\u0430\u0433 \u043E\u0434\u043D\u0438\u043C \u0441\u043B\u043E\u0432\u043E\u043C \u2014 \u0442\u0430\u043A, \u043A\u0430\u043A \u043D\u0430\u0437\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u0435\u0433\u043E \u043F\u0430\u043F\u043A\u0430. \u0421\u0432\u043E\u0439 \u0448\u0430\u0433 \u043B\u0435\u0436\u0438\u0442 \u0432 \`mine/steps/<\u0438\u043C\u044F>\` \u0432 \u0434\u043E\u043C\u0435 \u041D\u0430\u043F\u0430\u0440\u043D\u0438\u043A\u0430, \u043D\u0430\u0448\u0438 \u043F\u0435\u0440\u0435\u0447\u0438\u0441\u043B\u044F\u0435\u0442 setup, \u0438\u043C\u044F \u0443 \u043A\u0430\u0436\u0434\u043E\u0433\u043E \u0441\u0442\u043E\u0438\u0442 \u0432 \u0441\u043A\u043E\u0431\u043A\u0430\u0445.`
18056
17857
  };
18057
17858
  }
18058
-
18059
- // src/setup-job.ts
18060
17859
  var SETUP_JOB_MARK = "setup-";
18061
17860
  function isSetupJob(id) {
18062
17861
  return id.startsWith(SETUP_JOB_MARK);
@@ -18071,13 +17870,108 @@ var CHECK = "\u041F\u0440\u043E\u0432\u0435\u0440\u044F\u044E, \u0447\u0442\u043
18071
17870
  var INSTALL_NEW = "\u0421\u0442\u0430\u0432\u043B\u044E \u043D\u043E\u0432\u0443\u044E \u0432\u0435\u0440\u0441\u0438\u044E";
18072
17871
  var MISSING = "\u0421\u0442\u0430\u0432\u043B\u044E \u043D\u0435\u0434\u043E\u0441\u0442\u0430\u044E\u0449\u0435\u0435";
18073
17872
  var works = /* @__PURE__ */ new Map();
18074
- function running() {
18075
- for (const work of works.values()) if (work.ended === null) return work;
17873
+ var JOBS_DIR = "jobs";
17874
+ var JOBS_KEPT2 = 10;
17875
+ var BEAT_MS = 1e4;
17876
+ var STALE_MS = 6e4;
17877
+ var JOB_ID = /^setup-[\w-]{1,80}$/;
17878
+ function running(home, clock) {
17879
+ for (const work of works.values()) if (work.ended === null) return work.id;
17880
+ if (home === null) return null;
17881
+ for (const record2 of records(home)) {
17882
+ if (record2.ended === null && alive(record2, clock)) return record2.id;
17883
+ }
18076
17884
  return null;
18077
17885
  }
18078
- function startSetupJob(what, run, clock = Date.now) {
18079
- const already = running();
18080
- if (already !== null) return already.id;
17886
+ function records(home) {
17887
+ let names;
17888
+ try {
17889
+ names = readdirSync(join(home, JOBS_DIR)).filter((name) => name.endsWith(".json"));
17890
+ } catch {
17891
+ return [];
17892
+ }
17893
+ return names.sort().reverse().map((name) => load(home, name.slice(0, -".json".length))).filter((record2) => record2 !== null);
17894
+ }
17895
+ function load(home, id) {
17896
+ if (!JOB_ID.test(id)) return null;
17897
+ try {
17898
+ const value = JSON.parse(readFileSync(join(home, JOBS_DIR, `${id}.json`), "utf8"));
17899
+ if (value.id !== id || typeof value.what !== "string" || typeof value.started !== "number" || !Array.isArray(value.stages) || typeof value.at !== "number" || typeof value.pid !== "number" || typeof value.beat !== "number") return null;
17900
+ return {
17901
+ id,
17902
+ what: value.what,
17903
+ started: value.started,
17904
+ stages: value.stages.map(String),
17905
+ at: value.at,
17906
+ message: typeof value.message === "string" ? value.message : "",
17907
+ ended: value.ended ?? null,
17908
+ pid: value.pid,
17909
+ beat: value.beat
17910
+ };
17911
+ } catch {
17912
+ return null;
17913
+ }
17914
+ }
17915
+ function alive(record2, clock) {
17916
+ if (clock() - record2.beat > STALE_MS) return false;
17917
+ try {
17918
+ process.kill(record2.pid, 0);
17919
+ return true;
17920
+ } catch (trouble) {
17921
+ return trouble.code === "EPERM";
17922
+ }
17923
+ }
17924
+ function save(work, clock) {
17925
+ if (work.home === null) return;
17926
+ try {
17927
+ const dir = join(work.home, JOBS_DIR);
17928
+ mkdirSync(dir, { recursive: true });
17929
+ const file2 = join(dir, `${work.id}.json`);
17930
+ const temp = `${file2}.${process.pid}.tmp`;
17931
+ const record2 = {
17932
+ id: work.id,
17933
+ what: work.what,
17934
+ started: work.started,
17935
+ stages: work.stages,
17936
+ at: work.at,
17937
+ message: work.message,
17938
+ ended: work.ended,
17939
+ pid: process.pid,
17940
+ beat: clock()
17941
+ };
17942
+ writeFileSync(temp, JSON.stringify(record2), "utf8");
17943
+ renameSync(temp, file2);
17944
+ } catch {
17945
+ }
17946
+ }
17947
+ function prune2(home) {
17948
+ try {
17949
+ const dir = join(home, JOBS_DIR);
17950
+ const names = readdirSync(dir).filter((name) => JOB_ID.test(name.replace(/\.json$/, ""))).sort();
17951
+ for (const name of names.slice(0, Math.max(0, names.length - JOBS_KEPT2))) rmSync(join(dir, name), { force: true });
17952
+ } catch {
17953
+ }
17954
+ }
17955
+ function fromDisk(home, id, clock) {
17956
+ const record2 = load(home, id);
17957
+ if (record2 === null) return null;
17958
+ if (record2.ended !== null || alive(record2, clock)) return record2;
17959
+ return {
17960
+ ...record2,
17961
+ ended: {
17962
+ at: record2.beat,
17963
+ ending: {
17964
+ ok: false,
17965
+ trouble: "\u041E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u043E\u0431\u043E\u0440\u0432\u0430\u043B\u043E\u0441\u044C: \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u0430, \u043A\u043E\u0442\u043E\u0440\u0430\u044F \u0435\u0433\u043E \u0432\u0435\u043B\u0430, \u0437\u0430\u043A\u0440\u044B\u043B\u0430\u0441\u044C \u043F\u043E\u0441\u0440\u0435\u0434\u0438 \u0440\u0430\u0431\u043E\u0442\u044B. \u041F\u0440\u0435\u0436\u043D\u044F\u044F \u0432\u0435\u0440\u0441\u0438\u044F \u043D\u0430 \u043C\u0435\u0441\u0442\u0435, \u043C\u043E\u043D\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043C\u043E\u0436\u043D\u043E.",
17966
+ advice: "\u041D\u0430\u043F\u0438\u0448\u0438 \xAB\u043E\u0431\u043D\u043E\u0432\u0438\xBB \u0435\u0449\u0451 \u0440\u0430\u0437 \u2014 \u043D\u0430\u0447\u043D\u0443 \u0437\u0430\u043D\u043E\u0432\u043E.",
17967
+ report: `\u041F\u0440\u043E\u0446\u0435\u0441\u0441 \u043E\u0431\u043E\u043B\u043E\u0447\u043A\u0438 ${record2.pid}, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0432\u0451\u043B \u0437\u0430\u0434\u0430\u0447\u0443, \u043F\u0440\u043E\u043F\u0430\u043B \u043D\u0430 \u044D\u0442\u0430\u043F\u0435 \xAB${record2.stages[record2.at - 1] ?? CHECK}\xBB.`
17968
+ }
17969
+ }
17970
+ };
17971
+ }
17972
+ function startSetupJob(what, run, clock = Date.now, home = null) {
17973
+ const already = running(home, clock);
17974
+ if (already !== null) return already;
18081
17975
  const started = clock();
18082
17976
  const id = `${SETUP_JOB_MARK}${new Date(started).toISOString().replace(/[:.]/g, "-")}`;
18083
17977
  const work = {
@@ -18088,23 +17982,31 @@ function startSetupJob(what, run, clock = Date.now) {
18088
17982
  at: 1,
18089
17983
  message: "",
18090
17984
  ended: null,
18091
- done: Promise.resolve()
17985
+ done: Promise.resolve(),
17986
+ home
18092
17987
  };
18093
17988
  const control = {
18094
17989
  plan(stages) {
18095
17990
  work.stages = stages;
18096
17991
  work.at = Math.min(work.at, stages.length);
17992
+ save(work, clock);
18097
17993
  },
18098
17994
  go(stage) {
18099
17995
  const index = work.stages.indexOf(stage);
18100
17996
  if (index >= 0) work.at = index + 1;
18101
17997
  work.message = "";
17998
+ save(work, clock);
18102
17999
  },
18103
18000
  line(text2) {
18104
18001
  work.message = text2;
18002
+ save(work, clock);
18105
18003
  }
18106
18004
  };
18107
18005
  works.set(id, work);
18006
+ if (home !== null) prune2(home);
18007
+ save(work, clock);
18008
+ const beat = home === null ? null : setInterval(() => save(work, clock), BEAT_MS);
18009
+ beat?.unref();
18108
18010
  work.done = collectJobInstallFaults(id, () => run(control).catch((trouble) => {
18109
18011
  noteInstallFault("update-job", trouble);
18110
18012
  return {
@@ -18115,15 +18017,17 @@ function startSetupJob(what, run, clock = Date.now) {
18115
18017
  };
18116
18018
  })).then((ending) => {
18117
18019
  work.ended = { at: clock(), ending };
18020
+ if (beat !== null) clearInterval(beat);
18021
+ save(work, clock);
18118
18022
  });
18119
18023
  return id;
18120
18024
  }
18121
18025
  function quiet(line) {
18122
18026
  return line.replace(/^⚠\s*/, "").replace(/^Что делать:/, "\u0421\u043E\u0432\u0435\u0442:");
18123
18027
  }
18124
- function setupJobText(id, clock = Date.now) {
18125
- const work = works.get(id);
18126
- if (work === void 0) return null;
18028
+ function setupJobText(id, clock = Date.now, home = null) {
18029
+ const work = works.get(id) ?? (home === null ? null : fromDisk(home, id, clock));
18030
+ if (work === null) return null;
18127
18031
  const head2 = `\u0417\u0430\u0434\u0430\u043D\u0438\u0435 ${work.id}: ${work.what}`;
18128
18032
  if (work.ended === null) {
18129
18033
  const sec2 = Math.max(0, Math.round((clock() - work.started) / 1e3));
@@ -18147,8 +18051,8 @@ function setupJobText(id, clock = Date.now) {
18147
18051
  `\u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: ${ending.advice}`
18148
18052
  ].join("\n");
18149
18053
  }
18150
- function setupJobReport(id) {
18151
- const text2 = setupJobText(id);
18054
+ function setupJobReport(id, home = null) {
18055
+ const text2 = setupJobText(id, Date.now, home);
18152
18056
  return text2 === null ? null : readJob(text2);
18153
18057
  }
18154
18058
  function weightExact(bytes) {
@@ -18229,9 +18133,10 @@ async function runUpdate(job, deps) {
18229
18133
  updated = true;
18230
18134
  steps.push({ report: outcome.text });
18231
18135
  } else {
18136
+ const cause = causeOf(outcome.text);
18232
18137
  steps.push({
18233
18138
  report: outcome.text,
18234
- trouble: `\u041D\u043E\u0432\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F \u0434\u0432\u0438\u0436\u043A\u0430 \u043D\u0435 \u0432\u0441\u0442\u0430\u043B\u0430 \u2014 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043F\u0440\u0435\u0436\u043D\u044F\u044F${engineWas === null ? "" : ` ${engineWas}`}, \u043C\u043E\u043D\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043C\u043E\u0436\u043D\u043E.`
18139
+ trouble: `\u041D\u043E\u0432\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F \u0434\u0432\u0438\u0436\u043A\u0430 \u043D\u0435 \u0432\u0441\u0442\u0430\u043B\u0430${cause === "" ? "" : `: ${cause}`}. \u0420\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043F\u0440\u0435\u0436\u043D\u044F\u044F${engineWas === null ? "" : ` ${engineWas}`}, \u043C\u043E\u043D\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043C\u043E\u0436\u043D\u043E.`
18235
18140
  });
18236
18141
  }
18237
18142
  }
@@ -18247,6 +18152,11 @@ async function runUpdate(job, deps) {
18247
18152
  if (updated) return { ok: true, say: SAY_RESTART, report };
18248
18153
  return { ok: true, say: installed ? SAY_INSTALLED : SAY_NOTHING, report };
18249
18154
  }
18155
+ function causeOf(text2) {
18156
+ const line = text2.split("\n").map((one) => one.trim()).find((one) => one !== "" && !/^(Новая версия|Версия) \S+ не встала/.test(one) && !/^Что делать:/.test(one)) ?? "";
18157
+ const phrase = (line.split(/\.\s|\s—\s|;\s/)[0] ?? "").replace(/\s*\([^)]*\)/g, "").replace(/\.$/, "").trim();
18158
+ return phrase === "" ? "" : phrase[0].toLowerCase() + phrase.slice(1);
18159
+ }
18250
18160
  async function deliverMissing(job, deps) {
18251
18161
  const answer = await deps.installMissing();
18252
18162
  if (answer.type === "error") {
@@ -18346,25 +18256,15 @@ async function installStep(engine, asked, key, baseUrl, tell) {
18346
18256
  `\u041A\u0430\u043D\u0434\u0438\u0434\u0430\u0442 ${candidate.version} \u0443\u0436\u0435 \u043F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u043B\u0435\u043D, \u043D\u043E \u0435\u0449\u0451 \u043D\u0435 \u0440\u0430\u0431\u043E\u0447\u0438\u0439. \u0421\u043F\u0440\u043E\u0441\u0438 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0441\u043E\u0431\u0440\u0430\u0442\u044C \u0435\u0433\u043E \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0438 \u043F\u043E\u0437\u043E\u0432\u0438 setup \u0441 consent=true.`
18347
18257
  );
18348
18258
  }
18349
- let bootstrapText = "";
18350
18259
  const extras = step === "all" ? "later" : "now";
18351
- const activated = await continuePreparedCandidate(engine, candidate, async ({ candidate: locked }) => {
18352
- if (await engine.pythonTrouble() === null) {
18353
- return { ok: true, text: "Python \u0443\u0436\u0435 \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D; \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u043E \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u043B." };
18354
- }
18355
- const python2 = await installPythonUnderLock(engine, { consent, version: locked.version });
18356
- bootstrapText = python2.text;
18357
- return python2;
18358
- }, void 0, extras);
18260
+ const activated = await continuePreparedCandidate(engine, candidate, void 0, extras);
18359
18261
  if (!activated.ok) return toolError(activated.text);
18360
18262
  engine.forgetPlace();
18361
- if (step === "python") {
18362
- return toolSuccess(`${bootstrapText}
18363
- ${activated.text}`.trim());
18364
- }
18263
+ engine.forgetPython();
18264
+ if (step === "python") return toolSuccess(activated.text);
18365
18265
  if (step === "all") {
18366
18266
  const ready = `${activated.text}
18367
- \u041A\u0430\u043D\u0434\u0438\u0434\u0430\u0442 \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u043B ffmpeg, \u0441\u043E\u0431\u0440\u0430\u043B \u0441\u0432\u043E\u0451 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0438 \u043F\u0440\u043E\u0448\u0451\u043B env-check. \u0412\u0441\u0451 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u0433\u043E\u0442\u043E\u0432\u043E.`;
18267
+ \u041A\u0430\u043D\u0434\u0438\u0434\u0430\u0442 \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u043B \u0431\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0438 \u043F\u043E \u0441\u0432\u043E\u0435\u043C\u0443 \u0437\u0430\u043C\u043A\u0443 \u0438 \u043F\u0440\u043E\u0448\u0451\u043B env-check. \u0412\u0441\u0451 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0435 \u0433\u043E\u0442\u043E\u0432\u043E.`;
18368
18268
  const later = await installStep(engine, asked, key, baseUrl, tell);
18369
18269
  if (later.type === "error") {
18370
18270
  return toolSuccess(
@@ -18378,16 +18278,15 @@ ${activated.text}`.trim());
18378
18278
  ${EXTRAS_LATER}`), later]);
18379
18279
  }
18380
18280
  }
18381
- if (step === "python" || step === "all") {
18382
- if (await engine.pythonTrouble() !== null) {
18383
- const result = await installPython(engine, { consent });
18384
- if (!result.ok) return toolError(result.text);
18385
- if (step === "python") return toolSuccess(result.text);
18386
- return toolSuccess(result.text + "\n\u041F\u043E\u0437\u043E\u0432\u0438 setup \u0441 what=all \u0435\u0449\u0451 \u0440\u0430\u0437.");
18387
- }
18388
- }
18389
18281
  const place = engine.place();
18390
18282
  if (place === null) return toolError(engine.whyNoEngine());
18283
+ if (consent && await engine.pythonTrouble() !== null) {
18284
+ try {
18285
+ await engine.bringPython(place);
18286
+ } catch (trouble) {
18287
+ return toolError(`\u041F\u0438\u0442\u043E\u043D \u043C\u043E\u043D\u0442\u0430\u0436\u0430 \u043F\u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C: ${describeFailure(trouble)}. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0432\u0442\u043E\u0440\u0438 setup.`);
18288
+ }
18289
+ }
18391
18290
  const common = [
18392
18291
  "--pack",
18393
18292
  place.pack,
@@ -18623,7 +18522,7 @@ async function lookAround(engine, model, key, baseUrl) {
18623
18522
  shell: shellNews,
18624
18523
  ...o.noEngine === true ? { noEngine: true } : {},
18625
18524
  ...o.noPython === true ? { noPython: true } : {},
18626
- note: firstLine(said),
18525
+ note: o.note ?? firstLine(said),
18627
18526
  shellVersion: VERSION
18628
18527
  }));
18629
18528
  const shell = updateShell(engine, false, key, baseUrl, NEWS_POINTER_MS, toShell);
@@ -18635,10 +18534,10 @@ async function lookAround(engine, model, key, baseUrl) {
18635
18534
  });
18636
18535
  return card(together([toolSuccess(payload.text), await shell]), { noEngine: true });
18637
18536
  }
18638
- if (await engine.pythonTrouble() !== null) {
18639
- const python = await installPython(engine, { consent: false });
18537
+ const noPython = await engine.pythonTrouble();
18538
+ if (noPython !== null) {
18640
18539
  return card(together([
18641
- toolSuccess(python.text),
18540
+ toolSuccess(noPython),
18642
18541
  await updateEngine(engine, false, key, baseUrl, NEWS_POINTER_MS, toEngine),
18643
18542
  await shell
18644
18543
  ]), { noPython: true });
@@ -18650,7 +18549,7 @@ async function lookAround(engine, model, key, baseUrl) {
18650
18549
  ]);
18651
18550
  return card(
18652
18551
  together([newsForModel(shellNews, engineNews), machine.result, news, own]),
18653
- { readiness: machine.readiness }
18552
+ { readiness: machine.readiness, note: firstLine(together([machine.result, news, own])) }
18654
18553
  );
18655
18554
  }
18656
18555
  function newsForModel(shell, engine) {
@@ -18680,8 +18579,8 @@ function startUpdate(engine, what, key, baseUrl) {
18680
18579
  what,
18681
18580
  pointerMs: ASKED_POINTER_MS,
18682
18581
  installMissing: () => installMissing(engine, key, baseUrl, void 0)
18683
- }));
18684
- const report = setupJobReport(id);
18582
+ }), Date.now, engine.home());
18583
+ const report = setupJobReport(id, engine.home());
18685
18584
  return toolSuccess(
18686
18585
  `\u0417\u0430\u0434\u0430\u0447\u0430 ${id}: ${report?.what ?? title}.
18687
18586
  \u0418\u0434\u0451\u0442 \u043D\u0430 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F: \u043F\u0440\u043E\u0432\u0435\u0440\u044E, \u0447\u0442\u043E \u0432\u044B\u0448\u043B\u043E, \u0441\u043A\u0430\u0447\u0430\u044E \u0438 \u043F\u043E\u0441\u0442\u0430\u0432\u043B\u044E. \u041F\u043E\u043B\u043E\u0441\u0430 \u0443 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u0441\u0430\u043C\u0430. \u041F\u043E\u0437\u043E\u0432\u0438 wait \u0441 job "` + id + '" \u2014 \u0434\u043E\u0436\u0434\u0451\u0442\u0441\u044F \u0438 \u0441\u043A\u0430\u0436\u0435\u0442, \u0447\u0435\u043C \u043A\u043E\u043D\u0447\u0438\u043B\u043E\u0441\u044C; setup \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u043E \u043D\u0435 \u0437\u043E\u0432\u0438. \u0413\u043E\u0442\u043E\u0432\u043E \u2014 \u043F\u0435\u0440\u0435\u0441\u043A\u0430\u0436\u0438 \u0441\u0442\u0440\u043E\u043A\u0443 \xAB\u0427\u0435\u043B\u043E\u0432\u0435\u043A\u0443\xBB.',
@@ -18713,10 +18612,8 @@ async function askBeforeHeavy(engine, step, model, key, baseUrl) {
18713
18612
  });
18714
18613
  return toolSuccess(payload.text);
18715
18614
  }
18716
- if (await engine.pythonTrouble() !== null) {
18717
- const python = await installPython(engine, { consent: false });
18718
- return toolSuccess(python.text);
18719
- }
18615
+ const noPython = await engine.pythonTrouble();
18616
+ if (noPython !== null) return toolSuccess(noPython);
18720
18617
  return asToolResult(await engine.command([
18721
18618
  "env-install",
18722
18619
  "--pack",
@@ -18821,7 +18718,7 @@ var TRIALS_STEP = "\u041F\u043E\u0442\u043E\u043C \u0441\u043E\u0431\u0435\u0440
18821
18718
  async function followWork(engine, asked, pace = realPace, tell) {
18822
18719
  const ask2 = async () => {
18823
18720
  if (!isSetupJob(asked.job)) return engine.command(["job", asked.job]);
18824
- const text2 = setupJobText(asked.job);
18721
+ const text2 = setupJobText(asked.job, Date.now, engine.home());
18825
18722
  return text2 !== null ? { ok: true, text: text2 } : {
18826
18723
  ok: false,
18827
18724
  text: `\u0417\u0430\u0434\u0430\u0447\u0438 ${asked.job} \u043D\u0435\u0442: \u0435\u0451 \u0432\u0451\u043B \u043F\u0440\u0435\u0436\u043D\u0438\u0439 \u0437\u0430\u043F\u0443\u0441\u043A \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B. \u0427\u0442\u043E \u0434\u0435\u043B\u0430\u0442\u044C: \u043F\u043E\u0437\u043E\u0432\u0438 setup \u2014 \u043E\u043D \u0441\u043A\u0430\u0436\u0435\u0442, \u0447\u0442\u043E \u0441\u0442\u043E\u0438\u0442 \u0441\u0435\u0439\u0447\u0430\u0441.`
@@ -23129,4 +23026,4 @@ ${inTable}`, file2);
23129
23026
  };
23130
23027
  }
23131
23028
 
23132
- export { CrashReporter, DEFAULT_HOME, DeliveryError, Engine, ForeignBlockError, INSIDE_PYTHON, INSTALL_STEPS, KEEP_COPIES, KEEP_VERSIONS, LONG_STEPS, MANIFEST_TIMEOUT_MS, MANIFEST_URL, MARKER, MIN_ENGINE, McpServer, NaparnikApiClient, NaparnikAuthError, NaparnikClientError, NaparnikConfigError, NaparnikError, NaparnikInsufficientBalanceError, NaparnikScopeError, NaparnikServerError, NaparnikTimeoutError, PAUSE_HOURS, SERVER_NAME, SOURCES_FILE, START_SEC, StdioServerTransport, TOOL_SEC, USER_AGENT, VERSION, WHERE_TO_GET_KEY, Wakefulness, buildAll, buildBlock, buildResources, bundleUrlFor, chooseBody, compare, configPath, currentMachineFingerprint, decide, decideShell, defineTool, deploy, downloadFile, fetchManifest, fetchShellManifest, fileOrRefusal, findBlock, fromHost, fromNpxCache, handOverToBody, inside, install, installPayload, installPython, installStep, installedShell, jsonSchemaOf, keyFromBlock, keyInTable, lastLine, loadConfig, manifestUrlFor, markChecked, markChecked2, markCurrent2 as markCurrent, parseEngineManifest, parseFailure, path, platformKey, previousVersion2 as previousVersion, probe, pythonSource, readConfig, realPace, refusalFor, remove, reportInstallFaults, rollback, rollbackShell, shellDir, systemBlocker, tarEntries, tidyOld2 as tidyOld, timeToCheck, timeToCheck2, unpackArchive, updateEngine, watchProcessCrashes, withArgs };
23029
+ export { CrashReporter, DEFAULT_HOME, DeliveryError, Engine, ForeignBlockError, INSTALL_STEPS, KEEP_COPIES, KEEP_VERSIONS, LOCK_FILE, LONG_STEPS, MANIFEST_TIMEOUT_MS, MANIFEST_URL, MARKER, MIN_ENGINE, McpServer, NaparnikApiClient, NaparnikAuthError, NaparnikClientError, NaparnikConfigError, NaparnikError, NaparnikInsufficientBalanceError, NaparnikScopeError, NaparnikServerError, NaparnikTimeoutError, PAUSE_HOURS, SERVER_NAME, START_SEC, StdioServerTransport, TOOL_SEC, USER_AGENT, VERSION, WHERE_TO_GET_KEY, Wakefulness, bringPython, buildAll, buildBlock, buildResources, bundleUrlFor, chooseBody, compare, configPath, currentMachineFingerprint, decide, decideShell, defineTool, depFolderName, deploy, downloadFile, fetchManifest, fetchShellManifest, fileOrRefusal, findBlock, fromHost, fromNpxCache, handOverToBody, inside, install, installPayload, installStep, installedShell, jsonSchemaOf, keyFromBlock, keyInTable, lastLine, loadConfig, lockedPython, manifestUrlFor, markChecked, markChecked2, markCurrent2 as markCurrent, parseEngineManifest, parseFailure, path, platformKey, previousVersion2 as previousVersion, probe, readConfig, realPace, refusalFor, remove, reportInstallFaults, rollback, rollbackShell, shellDir, systemBlocker, tarEntries, tidyOld2 as tidyOld, timeToCheck, timeToCheck2, unpackArchive, updateEngine, watchProcessCrashes, withArgs };